跳转至

爆破

这是一道vm题,但略加调试后发现,算法是某种一对一的运算,将输入的flag经过运算后与目标值进行对比。暂且算法看作黑盒,于是单纯从解题获得flag这个目标上来看,这道题不需要分析handles还原算法,直接爆破即可。

大致思路: 首先同样手动关闭程序的基地址随机化。 在vm_run调用处(0x401717)后的xor ecx,ecx处可以得到运算后的flag结果(存在栈上),将其与目标值(value)对比,不正确则设置新的flag值,并设置eip至0x4010a8对flag以及vm字节码进行初始化,期间需要注意的是由于0x4010a8跳过了0x4010a3处的call,其后的栈平衡操作应舍弃,具体操作见源代码。

Loader程序及ctf原题(已去除动态基址)打包: source.7z

这种解题方法没有技术含量,但是可以看到DebugHook的恰当运用还是棒棒的。

具体算法

进入vm_run,整理各变量:

  vmcodes_control_eip = vm_codes;
  data_1 = data;
  mem1 = malloc(0xCu);
  if ( !mem1 )
    printf("Create Stack Malloc Fail CODE 1");
  mem2 = malloc(0x100u);
  mem1[2] = mem2;
  if ( !mem2 )
    printf("Create Stack Malloc Fail CODE 2");
  v5 = 0;
  *mem1 = 64;
  cmp_result_1 = 0;
  mem1[1] = 0;
  v41 = 0;
  v38 = 0;
  cmp_result = 0;
  while ( vmcodes_control_eip )
  {
    opcode = *((unsigned __int8 *)vmcodes_control_eip + 1);
    vmcodes_control_eip = (int *)((char *)vmcodes_control_eip + 1);
    switch ( opcode )
    {
      case 1:
        *(_DWORD *)(mem1[2] + 4 * ++mem1[1]) = v5;
        break;
      case 2:
        v8 = mem1[1];
        v5 = *(_DWORD *)(mem1[2] + 4 * v8);
        v41 = *(_DWORD *)(mem1[2] + 4 * v8);
        mem1[1] = v8 - 1;
        break;
      case 3:
        *(_DWORD *)(mem1[2] + 4 * ++mem1[1]) = v38;
        goto LABEL_22;
      case 4:
        v9 = mem1[1];
        v38 = *(_DWORD *)(mem1[2] + 4 * v9);
        mem1[1] = v9 - 1;
        break;
      case 5:
        v10 = mem1[2];
        v11 = *data_1;
        ++mem1[1];
        ++data_1;
        *(_DWORD *)(v10 + 4 * mem1[1]) = v11;
        goto LABEL_22;
      case 6:
        v29 = mem1[1];
        v30 = mem1[2];
        v31 = *(unsigned __int8 **)(v30 + 4 * v29--);
        mem1[1] = v29++;
        v32 = *v31;
        mem1[1] = v29;
        *(_DWORD *)(v30 + 4 * v29) = v32;
        goto LABEL_22;
      case 7:
        v33 = mem1[1];
        v34 = mem1[2];
        v35 = *(_BYTE **)(v34 + 4 * v33);
        mem1[1] = v33 - 1;
        v36 = *(_DWORD *)(v34 + 4 * (v33 - 1));
        mem1[1] = v33 - 2;
        *v35 = v36;
        goto LABEL_21;
      case 8:
        v12 = mem1[1];
        v13 = mem1[2];
        v14 = *(_DWORD *)(v13 + 4 * v12--);
        mem1[1] = v12;
        *(_DWORD *)(v13 + 4 * v12) += v14;
        goto LABEL_11;
      case 9:
        v15 = mem1[1];
        v16 = mem1[2];
        v17 = *(_DWORD *)(v16 + 4 * v15--);
        mem1[1] = v15;
        *(_DWORD *)(v16 + 4 * v15) -= v17;
LABEL_11:
        cmp_result = *(_DWORD *)(mem1[2] + 4 * mem1[1]) == 0;
        goto LABEL_21;
      case 0xA:
        v18 = mem1[1];
        v19 = mem1[2];
        v20 = *(_DWORD *)(v19 + 4 * v18--);
        mem1[1] = v18;
        *(_DWORD *)(v19 + 4 * v18) ^= v20;
        goto LABEL_22;
      case 0xB:
        v21 = mem1[1];
        v22 = *(_DWORD *)(mem1[2] + 4 * v21);
        mem1[1] = v21 - 1;
        vmcodes_control_eip = (int *)((char *)vmcodes_control_eip + v22);
        goto LABEL_22;
      case 0xC:
        v23 = mem1[1];
        v24 = *(_DWORD *)(mem1[2] + 4 * v23);
        mem1[1] = v23 - 1;
        if ( cmp_result_1 )
          vmcodes_control_eip = (int *)((char *)vmcodes_control_eip + v24);
        goto LABEL_22;
      case 0xD:
        free((void *)mem1[2]);
        free(mem1);
        return 1;
      case 0xE:
        v28 = (_DWORD *)(mem1[2] + 4 * mem1[1]);
        cmp_result = *(v28 - 1) == *v28;
LABEL_21:
        cmp_result_1 = cmp_result;
        goto LABEL_22;
      case 0xF:
        v25 = mem1[1];
        v26 = mem1[2];
        v27 = *(const char **)(v26 + 4 * v25);
        mem1[1] = v25 - 1;
        mem1[1] = v25;
        *(_DWORD *)(v26 + 4 * v25) = strlen(v27);
LABEL_22:
        v5 = v41;
        break;
      default:
        break;
    }
  }
  return 0;
其中,程序循环读取vmcodes_control控制流:
0x05,0x04,0x03,0x0F,0x05,0x0E,0x05,0x0C,0x02,0x03,0x01,0x08,0x06,0x05,0x01,0x09,0x08,0x05,0x0A,0x03,0x01,0x08,0x07,0x01,0x05,0x08,0x02,0x01,0x05,0x0B,0x0D

并在data中存放数据流:

//data
F4 FE 19 00 //flag_addr
00 00 00 00
16 00 00 00
17 00 00 00
CC 00 00 00
01 00 00 00 
E7 FF FF FF //-25
//---
16 00 00 00
17 00 00 00
CC 00 00 00
01 00 00 00 
E7 FF FF FF 
//......

data[0]=F4 FE 19 00,为用户输入flag的地址,之后为0x16,0x17,0xcc,0x1,0xe7ffffff循环,简易分析各个handle得:

0x5控制data数据流当前指针,并将1字节的data传送到mem中供调用 0xf检查flag长度,为0则通过0xc指令以data[2]=0x16个单位偏移vmcodes_control_eip,即偏移至末指令0xd处释放内存,退出循环 //算法 通过0x5,0x8,0x2,0x1序列从data流中取出1并对index(edx)加一 通过0x9,0x8将data中的0x17减去index并加上flag_eip处的byte 通过0xa,将其与data中的0xcc异或 通过0x7将异或结果设置到flag原位置 最后通过0x5,0xb实现控制流ip循环

前几步运行流程概览:

0x5:
    count++;
    mem[1]=data[0]//flag_addr
    data_eip++;
0x4:
    v38=mem[1]
    count--;
0x3:
    count++;
    mem[1]=v38
0xf:
    mem[1]=strlen(flag)

0x5:
    count++;
    mem[2]=data[1]
    data_eip++;

0xE:
    v28=mem[2]//0
    cmp strlen,v28

0x5:
    count++;
    mem[3]=data[2]
    data_eip++; 
0xc:
    if(cmp_equal)
        vm_codes_eip+=mem[3];//go to end
    count--;

0x2:
    v5=mem[2]//0
    v41=mem[2]
    count--;

0x3:
    count++;
    mem[2]=v38//flag_addr
    v5=v41

0x1:
    count++;
    mem[3]=v5;//0

0x8:
    mem[2]+=mem[3]
    count--;//==2
    cmp mem[2],0

0x6:
    v32=mem[2]//flag_addr
    mem[2]=byte[mem[2]]

0x5:
    count++;
    mem[3]=data[3]
    data_eip++;

0x1:
    count++;
    mem[4]=v5;//0

0x9:
    mem[3]-=mem[4]
    count--
    cmp mem[3],0

0x8:
    mem[2]+=mem[3]
    count--;//==2
    cmp mem[2],0

0x5:
    count++;
    mem[3]=data[4]
    data_eip++;
0xa:
    mem[2]=mem[2] xor mem[3]
    count--;

0x3:
    count++;
    mem[3]=v38//flag_addr

0x1:
    count++;
    mem[4]=v5;//0
0x8:
    mem[3]+=mem[4]
    count--;//==3
    cmp mem[3],0
0x7:
    v35=flag_addr
    count--//==2
    v36=mem[2]
    count--//==1
    byte[flag_addr]=v36//set the result

0x1:
    count++;//==2
    mem[3]=v5;//0
0x5:
    count++;//==3
    mem[3]=data[5]//==1
    data_eip++;

0x8:
    mem[2]+=mem[3]//+=1
    count--;//==2
    cmp mem[2],0

0x2:
    v5=mem[2]//1
    v41=mem[2]
    count--;

0x1:
    count++;
    mem[2]=v5;//1


0x5:
    count++;
    mem[3]=data[6]//==E7 FF FF FF==-25
    data_eip++;
0xb:
    vmcodes_control_eip+=mem[3];//-=25->to 0x5 line2
    count--;

其实还没有分析几个handle时就已经可以看出算法了,入门题的算法很简单:

result=[0x86,0x5C,0xB8,0x46,0x4C,0xBD,0x4A,0xA3,0xBE,0x4C,0x8D,0xA3,0xBA,0xF3,0xA1,0xAB,0xA2,0xFA,0xF9,0xA4,0xAE,0x80,0xFD,0xAE]
flag=[]
for index in range(0x18):
    flag.append((result[index]^ 0xcc)-(0x17-index))
print bytearray(flag)

output: 3z_vm_u_cr4ck5d_g00d_J0b