首页
社区
课程
招聘
[原创] KCTF 2020 Win. 第五题 砸开套娃思路
发表于: 2020-11-27 23:35 6870

[原创] KCTF 2020 Win. 第五题 砸开套娃思路

HHHso 活跃值
22
2020-11-27 23:35
6870

摘要:本文提供一种从样本弱性VM到人性化阅读反编译代码的思路。反编译器前后经历了两个版本,xdisasm.py和ydisasm.py,这里只提供最终版,也只是大概展示了相关成果,心路历程还需各位道友亲自走上一走。
大致经历过的心路:线性反编译,到启发式反编译,代码分块,流程图可视化,反编译到正编译再到反编译。反编译器简单模仿IDAPython或unicorn或miasm的部分思想。xdisasm.py快速的线性反编译能很好应对展平的第0层vm代码,对后面涉及多个函数,尤其再堆栈平衡追踪上有涉及缺陷,所以使用了启发式思路,即后续的反编译由已经反编译的指令引用决定,这也是多数反编译器的合理思路。

如图,程序将将ikey通过hex解码得到bkey(应该是32字节,16个short int)
vmi_t 声明了每层vm使用的参数信息
info_type 前三个vm为6,第四个为3,用来定位后面的pkey所在的表
vmID用来激发各层vm的memset和memcpy函数额外代码。
vmc_ptr指向下一个vm信息项

图片描述

我们通过IDAPython剥离四个vm代码,用于反编译测试

通过Hi_start_vm_with_vmInfoType_vmInfoTable函数得到每个vm执行的内存结构,和第0层vm的操作码。

图片描述

图片描述

第0层vm操作码定义,其他同理

有了第0层vm操作码,启用ydisasm.py下图代码,执行下述命令,
需要有unicorn,keystone的。会生成四个文件,其中
(1)fun0.v.asm 是vm指令反汇编文件。
(2)fun0.gv是vm指令反汇编代码的控制流程图dot,可以通过graphviz的的dot.exe编译生成图片等。如附件的fun0.png
(3)fun0.bin是通过keystone编译的代码,结果类似(4)。
(4)fun0.asm 是yasm(即以前的nasm)可以编译的代码。

其中(3)和(4)是快速打开后续vm的关键,基本原理是用x86汇编,维持栈平衡的各种运算条件下,替换vm指令,然后通过keystone编译为机器码或通过yasm编译器编译为机器码,然后通过IDA反编译为高级伪码。

图片描述

通过yasm将fun0.asm编译为coff文件

通过IDA反编译fun0.o,如图,这与vm0的代码就类似。
我们可以看到memset(dst,0x33333333,100)在vmID.1层触发的动作
实际效果是memset(dst,1,100),且dst赋值给了pkey位置

图片描述

图片描述

有了高级伪码,我们可以轻易得到下一层vm的操作码

图片描述

同理,ydiasm.py启用fun1相关部分代码,根据选择生成asm,bin,gv等

图片描述

依次类推,可以得到各vm的可阅读代码,举例如下
各vm中的memset等敏感操作,从fun3.o开始

除了对底层代码段一些数据的比对读写,看下fun3.o的memset触发点,
阅读IDA的高级伪码如果有不清楚的地方,估计还得到汇编代码取看一眼,如下
fun3.o的开头位置,没有任何运算就进行了比较,肯定不对劲,且m2位置传入是零。

图片描述

图片描述

回到汇编我们看到F5伪码过滤掉了memcpy(pbkey,pbkey,0)操作,这回触发底层运算,其再fun2.o触发的操作如图,pmem+8即上图的m2

图片描述

图片描述

memcpy交给fun1.so继续

图片描述

fun0.o的memcpy没有额外操作

图片描述

再回到fun3.o的后续代码。图下,途中对vmcptr1即fun1代码做修改,
位置是hex(0x130*4)=0x4C0位置,

图片描述

我们看下fun1.v.asm(即vm指令反汇编,不是x86反汇编fun1.asm)
如图,
图片描述
实际是对fun1.o 的memset触发的代码的变动

图片描述

图片描述

图片描述

图片描述
其会交给下一层的fun2.o的memset处理,同理,会先触发fun1.so的memset操作(再往下是fun0.o),再调用后续fun2.so运算函数处理,如图
图片描述
再fun1.o中,如图,对于大小100的操作,其会先运算,再调用memset,这时触发fun0.so层的memset;
图片描述
fun1.o对于大小0x640的操作,会再memset前后加入运算。同理,memset会触发fun0.o的memset
图片描述
fun0.o的情况如下,再特定参数如(dst,0x33333333,100)会先做一些操作再迁移数据,这时fun0.o触发的memset就是主程序的memset操作。
图片描述
fun3.png 原始vm指令流程图
生成命令

图片描述

附件内容

 
typedef struct vmi_t{ //size:0x20
  int flag;
  int next;
  void* vm_fptr;
  int vm_zs;
  int init;
  int vmID;
  int rev1;
  int rev2;
} vmi;
 
typedef struct _vmx4{
  unsigne char* pbkey;
  unsigne char* pmem;
  void* vmx_table;
} vmx4;
 
vmi_t vmx_info_table[] = {
  .00 vix0{flag.6,next:=.20,vmcptr,vmzs,0,vmID.0,rev1,rev2}
  .20 vix1{flag.6,next:=.40,vmcptr,vmzs,0,vmID.0,rev1,rev2}
  .40 vix2{flag.6,next:=.60,vmcptr,vmzs,0,vmID.0,rev1,rev2}
  .60 vix3{flag.3,next:=.80,vmcptr,vmzs,0,vmID.0,rev1,rev2}
  .80 vix4{.00hww.pbkey,.04hww.pmem{.00hww.bkLen},&vmx_info_table[0]}
}
pmem{ dword m[]
  .00 bkLen
  .04 m1
  .08 m2 should==0xDE05629C
  .0C
  .10...
}
}
typedef struct vmi_t{ //size:0x20
  int flag;
  int next;
  void* vm_fptr;
  int vm_zs;
  int init;
  int vmID;
  int rev1;
  int rev2;
} vmi;
 
typedef struct _vmx4{
  unsigne char* pbkey;
  unsigne char* pmem;
  void* vmx_table;
} vmx4;
 
vmi_t vmx_info_table[] = {
  .00 vix0{flag.6,next:=.20,vmcptr,vmzs,0,vmID.0,rev1,rev2}
  .20 vix1{flag.6,next:=.40,vmcptr,vmzs,0,vmID.0,rev1,rev2}
  .40 vix2{flag.6,next:=.60,vmcptr,vmzs,0,vmID.0,rev1,rev2}
  .60 vix3{flag.3,next:=.80,vmcptr,vmzs,0,vmID.0,rev1,rev2}
  .80 vix4{.00hww.pbkey,.04hww.pmem{.00hww.bkLen},&vmx_info_table[0]}
}
pmem{ dword m[]
  .00 bkLen
  .04 m1
  .08 m2 should==0xDE05629C
  .0C
  .10...
}
}
 
import idc
idc.savefile(r'.\403000_0790.bin',0,0x403000,0x0790)
idc.savefile(r'.\403794_1CBD.bin',0,0x403794,0x1CBD)
idc.savefile(r'.\405454_134A.bin',0,0x405454,0x134A)
idc.savefile(r'.\4067A0_0309.bin',0,0x4067A0,0x0309)
import idc
idc.savefile(r'.\403000_0790.bin',0,0x403000,0x0790)
idc.savefile(r'.\403794_1CBD.bin',0,0x403794,0x1CBD)
idc.savefile(r'.\405454_134A.bin',0,0x405454,0x134A)
idc.savefile(r'.\4067A0_0309.bin',0,0x4067A0,0x0309)
 
{
  esi = vmi.vm_ptr
  ebp = &vmi.vm_ptr+vmi.zone_size*2
  edi = ebp  <----vm_stack_bottom frame_ptr  fr
  py vm_push(0x2B
  px vm_push(0x2B11) <----vm_ret {push r;exit;exit;}//@eax=r
  p3 vm_push(vmi.info_type)   
  p2 vm_push(vmi* vmi_ptr)     
  p1 vm_push(vm_ret)         
  cpu{;esi as pc;ebx as r;edi as frp;ebp as stp;
    opcode = *pc
    switch(opcode){
      case 00h: lea_pb      #r=&stk.frp[+(*(byte*)pc++)]
      case 01h: lea_vb      #r=&stk.frp[-(*(byte*)pc++)] 
      case 02h: lea_pdw     #r=&stk.frp[+(*(dword*)pc)];pc+=4
      case 03h: movzx_imm8  #r=(unsigned int)(*(unsigne byte*)pc++)
      case 04h: mov_imm32   #r=(unsigned int)(*(unsigne  int*)pc++)
      case 06h: jmp         #pc+=(*(unsigne  int*)pc)
      case 07h: call        #push pc+4;pc+=(*(unsigne  int*)pc)  //*(--stp)=pc+4
      case 08h: jz          #r==0? pc+=(*(unsigne  int*)pc):pc+=4
      case 09h: jnz         #r!=0? pc+=(*(unsigne  int*)pc):pc+=4
      case 0Ah: begin       #push frp;stp-=(*(unsigne  int*)pc);pc+=4  //push ebp;mov ebp,esp;sub esp,xxx
      case 0Bh: stk_free    #stp+=(*(unsigne  int*)pc);pc+=4
      case 0Ch: end         #stp=frp;pop frp;ret      //frp=*((unsigned int*)stp++);pc=*((unsigned int*)stp++)
      case 0Dh: read_dw     #r=*r
      case 0Eh: read_b      #r=(unsigned int)(*(byte*)r)
      case 0Fh: write_dw    #*((unsigned int*)stp++)=r
      case 10h: write_b     #*((byte*)stp++)=(byte)r; r=(int)(byte)r 
      case 11h: push        #push r
      case 12h: or          #r|=*(stp++)
      case 13h: xor         #r^=*(stp++)
      case 14h: and         #r&=*(stp++)
      case 15h: eq          #r=(*(stp++)==r)
      case 16h: ne          #r=(*(stp++)!=r)
      case 17h: lt          #r=(*(stp++)< r)
      case 18h: gt          #r=(*(stp++)> r)
      case 1Ah: ge          #r=(*(stp++)>=r)
      case 1Bh: shl         #r=(*(stp++)<<(unsigne byte)r)
      case 1Ch: shr         #r=(*(stp++)>>(unsigne byte)r)
      case 1Dh: add         #r=(*(stp++)+r
      case 1Eh: subr        #r=(*(stp++)-r
      case 1Fh: imul        #r=(*(stp++)*r
      case 20h: divr        #r=(*(stp++)/r
      case 21h: modr        #r=(*(stp++)%r
      case 28h: memset      #size=*(stp++);val=*(byte*)(stp++);dst=*(stp++);r=dst memset(dst,val,size)
      case 2Ah: memcpy      #size=*(stp++);src=*(stp++);       dst=*(stp++);r=dst memset(dst,scr,size)
      case 2Bh: exit        #@eax=*stp #pop edi,esi,ebp,ebx,ecx
    }
  }
}
{
  esi = vmi.vm_ptr
  ebp = &vmi.vm_ptr+vmi.zone_size*2
  edi = ebp  <----vm_stack_bottom frame_ptr  fr
  py vm_push(0x2B
  px vm_push(0x2B11) <----vm_ret {push r;exit;exit;}//@eax=r
  p3 vm_push(vmi.info_type)   
  p2 vm_push(vmi* vmi_ptr)     
  p1 vm_push(vm_ret)         
  cpu{;esi as pc;ebx as r;edi as frp;ebp as stp;
    opcode = *pc
    switch(opcode){
      case 00h: lea_pb      #r=&stk.frp[+(*(byte*)pc++)]
      case 01h: lea_vb      #r=&stk.frp[-(*(byte*)pc++)] 
      case 02h: lea_pdw     #r=&stk.frp[+(*(dword*)pc)];pc+=4
      case 03h: movzx_imm8  #r=(unsigned int)(*(unsigne byte*)pc++)
      case 04h: mov_imm32   #r=(unsigned int)(*(unsigne  int*)pc++)
      case 06h: jmp         #pc+=(*(unsigne  int*)pc)
      case 07h: call        #push pc+4;pc+=(*(unsigne  int*)pc)  //*(--stp)=pc+4
      case 08h: jz          #r==0? pc+=(*(unsigne  int*)pc):pc+=4
      case 09h: jnz         #r!=0? pc+=(*(unsigne  int*)pc):pc+=4
      case 0Ah: begin       #push frp;stp-=(*(unsigne  int*)pc);pc+=4  //push ebp;mov ebp,esp;sub esp,xxx
      case 0Bh: stk_free    #stp+=(*(unsigne  int*)pc);pc+=4
      case 0Ch: end         #stp=frp;pop frp;ret      //frp=*((unsigned int*)stp++);pc=*((unsigned int*)stp++)
      case 0Dh: read_dw     #r=*r
      case 0Eh: read_b      #r=(unsigned int)(*(byte*)r)
      case 0Fh: write_dw    #*((unsigned int*)stp++)=r
      case 10h: write_b     #*((byte*)stp++)=(byte)r; r=(int)(byte)r 
      case 11h: push        #push r
      case 12h: or          #r|=*(stp++)
      case 13h: xor         #r^=*(stp++)
      case 14h: and         #r&=*(stp++)
      case 15h: eq          #r=(*(stp++)==r)
      case 16h: ne          #r=(*(stp++)!=r)
      case 17h: lt          #r=(*(stp++)< r)
      case 18h: gt          #r=(*(stp++)> r)
      case 1Ah: ge          #r=(*(stp++)>=r)
      case 1Bh: shl         #r=(*(stp++)<<(unsigne byte)r)
      case 1Ch: shr         #r=(*(stp++)>>(unsigne byte)r)
      case 1Dh: add         #r=(*(stp++)+r
      case 1Eh: subr        #r=(*(stp++)-r
      case 1Fh: imul        #r=(*(stp++)*r
      case 20h: divr        #r=(*(stp++)/r
      case 21h: modr        #r=(*(stp++)%r
      case 28h: memset      #size=*(stp++);val=*(byte*)(stp++);dst=*(stp++);r=dst memset(dst,val,size)
      case 2Ah: memcpy      #size=*(stp++);src=*(stp++);       dst=*(stp++);r=dst memset(dst,scr,size)
      case 2Bh: exit        #@eax=*stp #pop edi,esi,ebp,ebx,ecx
    }
  }
}
 
class VmOps0(IntEnum):
  lea_pb = 0
  lea_vb = 1
  lea_pww = 2
  movzx = 3
  mov = 4
  jmp = 6
  call = 7
  jz = 8
  jnz = 9
  begin = 0x0A
  stk_free = 0x0B
  end = 0x0C
  read_ww = 0x0D
  read_b = 0x0E
  write_ww = 0x0F
  write_b = 0x10
  push = 0x11
  _or = 0x12
  xor = 0x13
  _and = 0x14
  ifeq = 0x15
  ifne = 0x16
  iflt = 0x17
  ifgt = 0x18
  ifge = 0x1A
  shl = 0x1B
  shr = 0x1C
  add = 0x1D
  subr = 0x1E
  imul = 0x1F
  divr = 0x20
  modr = 0x21
  memset = 0x28
  memcpy = 0x2A
  exit = 0x2B
class VmOps0(IntEnum):
  lea_pb = 0
  lea_vb = 1
  lea_pww = 2
  movzx = 3
  mov = 4
  jmp = 6
  call = 7
  jz = 8
  jnz = 9
  begin = 0x0A
  stk_free = 0x0B
  end = 0x0C
  read_ww = 0x0D
  read_b = 0x0E
  write_ww = 0x0F
  write_b = 0x10
  push = 0x11
  _or = 0x12
  xor = 0x13
  _and = 0x14
  ifeq = 0x15
  ifne = 0x16
  iflt = 0x17
  ifgt = 0x18
  ifge = 0x1A
  shl = 0x1B
  shr = 0x1C
  add = 0x1D
  subr = 0x1E
  imul = 0x1F
  divr = 0x20
  modr = 0x21
  memset = 0x28
  memcpy = 0x2A
  exit = 0x2B
 
python ydisasm.py
python ydisasm.py
 
yasm.exe -a x86 -f coff -o fun0.o fun0.asm
yasm.exe -a x86 -f coff -o fun0.o fun0.asm
 
 
 
 
class VmOps1(IntEnum):
  lea_pb  = 0x1B
  lea_vb  = 0x07
  lea_pww  = 0x05
  movzx  = 0x24
  mov  = 0x22
  jmp  = 0x20
  call  = 0x00
  jz  = 0x0E
  jnz  = 0x06
  begin  = 0x09
  stk_free  = 0x0A
  end  = 0x01
  read_ww  = 0x13
  read_b  = 0x1C
  write_ww  = 0x02
  write_b  = 0x0D
  push  = 0x29
  _or  = 0x0C
  xor  =  0x11
  _and  =  0x1D
  ifeq  = 0x2A
  ifne  = 0x27
  iflt  = 0x15
  ifgt  = 0x1A
  ifge  = 0x28
  shl  = 0x10
  shr  = 0x2B
  add  = 3
  subr  = 0x17
  imul  = 0x08
  divr  = 0x21
  modr  = 0x26
  memset  = 0x0B
  memcpy  = 0x0F
  exit  = 0x1F
class VmOps1(IntEnum):
  lea_pb  = 0x1B
  lea_vb  = 0x07
  lea_pww  = 0x05
  movzx  = 0x24
  mov  = 0x22
  jmp  = 0x20
  call  = 0x00
  jz  = 0x0E
  jnz  = 0x06
  begin  = 0x09
  stk_free  = 0x0A
  end  = 0x01
  read_ww  = 0x13
  read_b  = 0x1C

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-11-28 16:47 被HHHso编辑 ,原因:
上传的附件:
收藏
免费 6
支持
分享
打赏 + 2.00雪花
打赏次数 1 雪花 + 2.00
 
赞赏  kanxue   +2.00 2020/11/28 精品文章~
最新回复 (3)
雪    币: 8209
活跃值: (4518)
能力值: ( LV15,RANK:2473 )
在线值:
发帖
回帖
粉丝
2

强大强大强大

最后于 2020-11-28 12:58 被ccfer编辑 ,原因:
2020-11-28 12:52
0
雪    币: 26205
活跃值: (63302)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
3
佩服!
2020-11-28 13:40
0
雪    币: 3165
活跃值: (5146)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
4
强啊 ,关注了 
2020-11-29 10:41
0
游客
登录 | 注册 方可回帖
返回
//