首页
论坛
课程
招聘
[原创] KCTF 2020 Win. 第五题 砸开套娃思路
2020-11-27 23:35 2308

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

HHHso 活跃值
20
2020-11-27 23:35
2308

摘要:本文提供一种从样本弱性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信息项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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...
}
}

图片描述

 

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

1
2
3
4
5
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)

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

 

图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
{
  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
    }
  }
}

图片描述

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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

有了第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反编译为高级伪码。

1
python ydisasm.py

图片描述

 

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

1
yasm.exe -a x86 -f coff -o fun0.o fun0.asm

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

 

图片描述

 

图片描述

 

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

 

图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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

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

 

图片描述

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
nt __cdecl xFun_00060000(int *p_last_vi, int a2)
{
  BOOL v3; // eax
  BOOL v4; // eax
  char lv_len; // [esp+0h] [ebp-2Ch]
  vmi *lv_vit; // [esp+4h] [ebp-28h]
  char *v7; // [esp+8h] [ebp-24h]
  char v8; // [esp+Ch] [ebp-20h]
  int *lv_vm3_ea; // [esp+10h] [ebp-1Ch]
  int *lv_vm2_ea; // [esp+14h] [ebp-18h]
  _DWORD *lv_vm1_ea; // [esp+18h] [ebp-14h]
  int *lv_vm0_ea; // [esp+1Ch] [ebp-10h]
  int *lv_rLen; // [esp+20h] [ebp-Ch]
  int *lv_pbkey; // [esp+24h] [ebp-8h]
  int v15; // [esp+28h] [ebp-4h]
 
  v15 = 0;
  if ( a2 != 3 )
    return 0;
  lv_pbkey = (int *)*p_last_vi;
  lv_rLen = (int *)p_last_vi[1];
  lv_vit = (vmi *)p_last_vi[2];
  lv_vm0_ea = (int *)lv_vit->vm_fptr;
  lv_vit = (vmi *)lv_vit->next;
  lv_vm1_ea = lv_vit->vm_fptr;
  lv_vit = (vmi *)lv_vit->next;
  lv_vm2_ea = (int *)lv_vit->vm_fptr;
  lv_vm3_ea = *(int **)(lv_vit->next + 8);
  v7 = &v8;
  lv_vit = (vmi *)lv_rLen;
  *(_DWORD *)&v8 = lv_rLen[2];
  lv_rLen[2] = 0;
  v7 = *(char **)&v8;
  if ( *(_DWORD *)&v8 != 0xDE05629C )
    return v15;
  lv_rLen[5] = 15;
  lv_vm1_ea[304] = 65008;
  *(_DWORD *)((char *)lv_vm1_ea + 0x4C6) = 0xFDF3;
  *(_DWORD *)((char *)lv_vm1_ea + 0x1279) = 1;
  *(int *)((char *)lv_vm2_ea + 0xA2D) = 1;
  lv_vm0_ea[339] = 0;
  v7 = &lv_len;
  lv_vit = (vmi *)lv_rLen;
  *(_DWORD *)&lv_len = *lv_rLen;
  v7 = (char *)(lv_rLen + 1024);
  lv_vit = (vmi *)(xFun_00060000 + 1);
  memset(lv_rLen + 1024, 1, 0x64u);
  lv_vm1_ea[0x130] = 0xCCCCCCCC;
  lv_vm2_ea = (_DWORD *)((char *)lv_vm1_ea + 0x4C6);
  *lv_vm2_ea = 0xEEEEEEEE;
  lv_vm2_ea = (int *)((char *)lv_vm2_ea + 0xA2D);
  *lv_vm2_ea = 0x22222200;
  lv_vm0_ea[0x153] = 0x33333333;
  lv_vm2_ea = *(int **)&lv_len;
  if ( *(_DWORD *)&lv_len == 0x38 )
  {
    lv_rLen[5] = 30;
    lv_vm2_ea = lv_rLen;
    lv_vm2_ea = (int *)lv_rLen[2];
    v3 = lv_vm2_ea == (int *)0x4A752000;
    if ( lv_vm2_ea == (int *)0x4A752000 )
    {
      lv_vm2_ea = lv_rLen;
      lv_vm2_ea = (int *)lv_rLen[4];
      v3 = lv_vm2_ea == (int *)0xD540;
    }
    if ( v3 )
    {
      lv_rLen[5] = 0x2D;
      lv_vm2_ea = lv_pbkey;
      lv_vm2_ea = (int *)lv_pbkey[5];
      v4 = lv_vm2_ea == (int *)0x204BCEF1;
      if ( lv_vm2_ea == (int *)0x204BCEF1 )
      {
        lv_vm2_ea = lv_pbkey;
        lv_vm2_ea = (int *)lv_pbkey[6];
        v4 = lv_vm2_ea == (int *)0x7AD17AD1;
      }
      if ( v4 )
      {
        lv_vm2_ea = lv_pbkey;
        lv_vm2_ea = (int *)lv_pbkey[7];
        v4 = lv_vm2_ea == (int *)0x514F89F9;
      }
      if ( v4 )
      {
        lv_rLen[5] = 60;
        lv_vm2_ea = lv_rLen;
        lv_vm2_ea = (int *)*((unsigned __int8 *)lv_rLen + 0xC);
        if ( lv_vm2_ea == (int *)((char *)xFun_00060000 + 1) )
        {
          lv_rLen[5] = 90;
          *((_BYTE *)lv_rLen + 12) = 0;
          lv_vm2_ea = lv_rLen + 0x400;
          lv_vm3_ea = (_DWORD *)(xFun_00060000 + 1);
          memset(lv_rLen + 0x400, 1, 0x640u);
          *(_DWORD *)((char *)lv_vm1_ea + 0x1279) = 0x22222200;
          lv_rLen = &v15;
          lv_vm0_ea = &v15;
          v15 = (unsigned __int8)p_last_vi;
        }
      }
    }
    lv_rLen += 2;
    *lv_rLen = 0;
    lv_rLen += 3;
    *lv_rLen = 0;
    lv_rLen += 4;
    *lv_rLen = 0;
    return v15;
  }
  lv_vm2_ea = (int *)&v8;
  for ( *(_DWORD *)&v8 = 0; ; lv_vm2_ea = *(int **)&v8 )
  {
    lv_vm2_ea = *(int **)&v8;
    if ( *(_DWORD *)&v8 >= 20 )
      break;
    lv_vm2_ea = (int *)((char *)lv_rLen + *(_DWORD *)&v8);
    lv_vm3_ea = lv_rLen;
    lv_vm3_ea = (int *)*((unsigned __int8 *)lv_rLen + *(_DWORD *)&v8);
    *(_BYTE *)lv_vm2_ea = (unsigned __int8)lv_vm3_ea ^ v8;
    lv_vm3_ea = (int *)(*(_DWORD *)&v8)++;
  }
  return 0;
}

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

 

图片描述

 

图片描述

 

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

 

图片描述

 

图片描述

 

memcpy交给fun1.so继续

 

图片描述

 

fun0.o的memcpy没有额外操作

 

图片描述

 

1
2
3
4
5
6
7
8
9
10
m2 = *(dword*)pbkey  //m2为四字节
m2 = fun2._crc_0xEB31D82E(0xffffffff,&m2)
m2 = fun2._crc_0xEB31D82E(0xffffffff,&m2)
m2 = fun2._crc_0xEB31D82E(0xffffffff,&m2)
m2 = fun2._crc_0xEB31D82E(0xffffffff,&m2)
m2 = fun1._crc_0xEB31D82E(0xffffffff,&m2)
m2 = fun1._crc_0xEB31D82E(0xffffffff,&m2)
m2 = fun1._crc_0xEB31D82E(0xffffffff,&m2)
m2 = fun1._crc_0xEB31D82E(0xffffffff,&m2)
m2 应等于 0xDE05629C

再回到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指令流程图
生成命令

1
c:Graphviz\bin\dot.exe -Tpng -o fun3.png fun3.gv

图片描述

 

附件内容

1
2
3
4
5
403000_0790.bin   403794_1CBD.bin   405454_134A.bin   4067A0_0309.bin   Butterfly.exe     fun0.asm
fun0.bin          fun0.gv           fun0.o            fun0.png          fun0.v.asm        fun1.asm          fun1.bin          fun1.gv
fun1.o            fun1.png          fun1.v.asm        fun2.asm          fun2.bin          fun2.gv           fun2.o            fun2.png
fun2.v.asm        fun3.asm          fun3.bin          fun3.gv           fun3.o            fun3.png          fun3.v.asm        KCTF05.rar
ydisasm.py

安卓应用层抓包通杀脚本发布!《高研班》2021年3月班开始招生!

最后于 2020-11-28 16:47 被HHHso编辑 ,原因:
上传的附件:
收藏
点赞3
打赏
分享
打赏 + 2.00
打赏次数 1 金额 + 2.00
 
赞赏  kanxue   +2.00 2020/11/28 精品文章~
最新回复 (3)
雪    币: 8486
活跃值: 活跃值 (783)
能力值: ( LV15,RANK:1920 )
在线值:
发帖
回帖
粉丝
ccfer 活跃值 15 2020-11-28 12:52
2
0

强大强大强大

最后于 2020-11-28 12:58 被ccfer编辑 ,原因:
雪    币: 2155
活跃值: 活跃值 (9532)
能力值: (RANK:75 )
在线值:
发帖
回帖
粉丝
Editor 活跃值 2020-11-28 13:40
3
0
佩服!
雪    币: 953
活跃值: 活跃值 (245)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Dyingchen 活跃值 2020-11-29 10:41
4
0
强啊 ,关注了 
游客
登录 | 注册 方可回帖
返回