C++PWN
C++PWN 也是最近偶尔会碰见的题型,其本质和 C 是一样的,由于可读性不如 C 而具有一定难度,学习一下
[BUUCTF] pwn1_sctf_2016,很早就碰到过这个题目了,重新拿来学习一下:
1 | int vuln() |
局部变量定义:
1 | int v3; // [esp+3Ch] [ebp-1Ch] BYREF |
这四行声明了几个栈变量(v3, v4, v5, v6)。在 C++ 反编译代码中,由于 C++ 对象的构造和析构,这些变量通常被用来作为 std::string(字符串)或 std::allocator(内存分配器)对象的底层内存空间。
赋值运算:
1 | std::string::operator=(&input, s); |
在 C++ 中,std::string::operator= 代表的是 std::string 类的赋值运算符重载函数。
简单来说,它的实际意思就是我们平时写代码时用的 等号 (=)。还原回 C 代码就是:
1 | input = s; |
创建字符串并赋值:
1 | std::allocator<char>::allocator(v5); |
line 13:调用了 std::allocator(标准内存分配器)的构造函数,把这个分配器对象建在 v5 这块内存地址上
line 14: 调用了 std::string 类的构造函数(函数名和类名相同,即 string::string),在 v4 创建一个字符串对象,内容为”you”,并把 v5 给他使用,相当于 read 或者 write,printf。
1 | replace((std::string *)&v3); |
相当于:
1 | input = replace(input, "I", "you"); |
line 17: 在 C++ 汇编层面,如果一个函数返回一个比较大的对象(比如 std::string),程序不会直接通过寄存器返回,而是在调用函数之前,先在栈上预留一块空地(这就是 v3),然后把这块空地的地址作为一个隐藏参数传给函数。函数执行完后,会把结果直接写到这块空地里。
line 18: 本意是 std::string::operator=(&input, &v3); 也就是把字符串放入 v3,由于后面跟着 *v5+1 和 v4 两个参数,也就调用了 replace 函数,意为交换 I 和 you 的位置
销毁临时对象:
在 C++ 中,带有 ~ 符号的函数被称为析构函数。当一个对象的作用域结束,或作为临时变量不再被需要时,程序会自动调用析构函数来释放该对象所占用的系统资源,比如动态分配的堆内存,以防止内存泄漏。
1 | std::string::~string(&v3); |
- 销毁字符串对象
v3。 - v3 是一个临时对象,用来存放 replace 替换操作完成后的“新字符串”。此时新字符串已经成功赋值给最终目标 input 了,v3 也就完成了历史使命,必须被销毁释放。
1 | std::string::~string((char *)v5 + 1); |
- 销毁存放在 v5 + 1 位置的字符串对象。
- 这个对象就是前面创建的包含内容 “I” 的临时字符串。替换工作做完了,被销毁。
1 | std::allocator<char>::~allocator(&v6); |
- 销毁内存分配器 v6。
- 这个分配器是专门用来给上面的
"I"字符串管理内存的。既然字符串都没了,分配器也该下岗了。
input 赋值:
1 | src = (const char *)std::string::c_str((std::string *)&input); |
相当于:
1 | src = input.c_str(); |
栈溢出:
1 | strcpy(s, src); |
题目分析:
程序会把我们一开始输入的字符串中的 I 全部替换成 you,这也就导致我们字符串数量会膨胀,
利用这个我们就可以制造栈溢出:
1 | from pwn import * |
这道题并不算难题,但是因为 C++编译导致 F5 伪代码可读性下降,这类题还需要多练习。
接下来会继续用两道长城杯的题目去学习 C++PWN
更新: 2026-05-06 22:01:22
原文: https://www.yuque.com/idcm/wnemg9/mqrzt6v2qsp3o55u