一点理论
如果你想看到在自定义地址跳转的实际技巧,请跳到第二部分。
让我们尝试调整printf()
技巧中的格式字符串。
printf("ABABABAB");
但是直接将HEX地址编码为格式字符串是不起作用的。整个重点是将某个地址伪装成堆栈中会被攻击利用的地址,但是我的格式字符串"ABABABAB"最终在.rodata节中而不是我们想要的堆栈中。
Breakpoint 1, __printf (format=0x555555556004 "ABABABAB") at ./stdio-common/printf.c:28
(gdb) i args
format = 0x555555556004 "ABABABAB"
当在进程内存映射中查找此地址时,很可能是.rodata节。
Start Addr End Addr Size Offset Perms objfile
0x555555554000 0x555555555000 0x1000 0x0 r--p /home/drazen/proba/main
0x555555555000 0x555555556000 0x1000 0x1000 r-xp /home/drazen/proba/main
0x555555556000 0x555555557000 0x1000 0x2000 r--p /home/drazen/proba/main
0x555555557000 0x555555558000 0x1000 0x2000 r--p /home/drazen/proba/main
0x555555558000 0x555555559000 0x1000 0x3000 rw-p /home/drazen/proba/main
并使用readelf进行检查:
drazen@HP-ProBook-640G1:~/proba$ readelf -p .rodata main
String dump of section '.rodata':
[ 4] ABABABAB
到目前为止还好,但奇怪的是当我转储堆栈并期望在堆栈帧中找到作为传递给printf()的参数的ABABABAB字符串地址时。
(gdb) i frame
Stack level 0, frame at 0x7fffffffddf0:
rip = 0x7ffff7de16f0 in __printf (./stdio-common/printf.c:28); saved rip = 0x555555555165
called by frame at 0x7fffffffde00
source language c.
Arglist at 0x7fffffffdde0, args: format=0x555555556004 "ABABABAB"
你可以看到返回地址到main()的
0x555555555165,并期望在地址
0x7fffffffdde0上找到堆栈上的格式化字符串地址
但是当我们转储堆栈时,而不是格式化字符串地址,只有8个字节的零,应该是函数参数,在
__libc_start_call_main()堆栈帧返回地址和
printf()堆栈帧返回地址之间。
(gdb) x/32gx $sp
0x7fffffffdde0: 0x0000000000000000 0x0000555555555165
0x7fffffffddf0: 0x0000000000000001 0x00007ffff7daad90
0x7fffffffde00: 0x0000000000000000 0x0000555555555149
0x7fffffffde10: 0x0000000100000000 0x00007fffffffdf08
那么地址格式字符串是如何传递给prIntf()的呢?
当我们转储寄存器时,我们在rsi寄存器中看到了格式字符串的地址。
(gdb) i r
rax 0x7ffff7f9b868 140737353726056
rbx 0x0 0
rcx 0x0 0
rdx 0x7fffffffdcf0 140737488346352
rsi 0x555555556004 93824992239624
rdi 0x7ffff7f9b780 140737353725824
因为函数参数(在这种情况下是字符串地址)将通过rsi和rdi寄存器传递,以提高速度,并且不会在堆栈中使用格式化字符串和字符串参数来实现这个技巧。
所以我们可以只使用作为本地(自动)变量创建的字符串,将其放入堆栈中,放在当前堆栈帧的返回地址之前。
实际例子
无论如何,我尝试了这个小例子,它起作用了,打印出地址并放入本地字符串(在堆栈上创建)。因此,我们可以使用这个技巧来使本地字符串模拟我们想要访问的地址:
我们必须打印5个随机值,直到我们得到我们想要的本地字符串!
使用十六进制格式%x显示堆栈上字符串
avro,
nana,
loli的HEX表示(使用%s字符串格式会导致分段错误,因为printf()会将这些值解释为字符串的地址,但这些“地址”可能不在进程的映射区域内或者在受保护的内存区域内)。
所以现在我们使用本地变量在堆栈上“伪装”成数据访问。
但是如果我们可以利用这个来尝试写入那个地址呢?
让我们将最后一个%X格式说明符更改为%n。
不再使用%X打印堆栈上的数据内容,而是将这个数据作为变量的地址,printf()将在其中存储已打印字符的数量。
因此,我们的想法是获得对自定义地址的写入权限。
printf("ABABABAB\n,%016llX\n,%016llX\n,%016llX\n,%016llX\n,%016llX\n,%016llX\n,%016llX\n,%n");
我们的虚假地址0x61616161616161以ASCII码"aaaaaaa"表示,最终存储在%rax寄存器中,printf函数将在该地址写入已打印字符的数量(存储在r12寄存器中):
(gdb) i r
rax 0x61616161616161 27410143614427489
rbx 0x555555556052 93824992239698
0x00007ffff7df7c3c <+7180>: jne 0x7ffff7df8276 <__vfprintf_internal+8774>
=> 0x00007ffff7df7c42 <+7186>: mov %r12d,(%rax)
但在我们的情况下,由于地址0x61616161616161没有映射到进程内存中,这将导致SEGV分段错误。
Continuing.
ABABABAB
,00007FFFFFFFDF08
,00007FFFFFFFDF18
,0000555555557DB8
,00007FFFF7F9BF10
,00007FFFF7FC9040
,0031313131313131
,0032323232323232
Program received signal SIGSEGV, Segmentation fault.
printf
要从堆栈中弹出任何内容?它不知道(也不关心)最初推了多少参数(甚至大小)... - user541686