您当前的位置: 首页 > 

合天网安实验室

暂无认证

  • 0浏览

    0关注

    748博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

DDCTF2019两个逆向writeup

合天网安实验室 发布时间:2019-05-07 18:00:00 ,浏览量:0

01

Confused

首先参考链接 https://www.52pojie.cn/forum.php?mod=viewthread&tid=860237&page=1

首先分析到这个sub_1000011D0是关键函数是没有什么问题的,直接shift+f12定位DDCTF的字符串就到了这一部分逻辑

if ( (unsigned int)sub_1000011D0(*((__int64 *)&v14 + 1)) == 1 )
    objc_msgSend(v17, "onSuccess");
else
    objc_msgSend(v17, "onFailed");

跟进去以后发现

__int64 __fastcall sub_1000011D0(__int64 a1)
{
  char v2; // [rsp+20h] [rbp-C0h]
  __int64 v3; // [rsp+D8h] [rbp-8h]
  v3 = a1;
  memset(&v2, 0, 0xB8uLL);
  sub_100001F60(&v2, a1);
  return (unsigned int)sub_100001F00(&v2);
}

这个函数首先分配了一个0xb8大小的空间,然后填充为0x00,然后把这个空间传入了一个sub_100001F60函数 跟进去

__int64 __fastcall sub_100001F60(__int64 a1, __int64 a2)
{
  *(_DWORD *)a1 = 0;
  *(_DWORD *)(a1 + 4) = 0;
  *(_DWORD *)(a1 + 8) = 0;
  *(_DWORD *)(a1 + 12) = 0;
  *(_DWORD *)(a1 + 16) = 0;
  *(_DWORD *)(a1 + 176) = 0;
  *(_BYTE *)(a1 + 32) = 0xF0u;
  *(_QWORD *)(a1 + 40) = sub_100001D70;
  *(_BYTE *)(a1 + 48) = 0xF1u;
  *(_QWORD *)(a1 + 56) = sub_100001A60;
  *(_BYTE *)(a1 + 64) = 0xF2u;
  *(_QWORD *)(a1 + 72) = sub_100001AA0;
  *(_BYTE *)(a1 + 80) = 0xF4u;
  *(_QWORD *)(a1 + 88) = sub_100001CB0;
  *(_BYTE *)(a1 + 96) = 0xF5u;
  *(_QWORD *)(a1 + 104) = sub_100001CF0;
  *(_BYTE *)(a1 + 112) = 0xF3u;
  *(_QWORD *)(a1 + 120) = sub_100001B70;
  *(_BYTE *)(a1 + 128) = 0xF6u;
  *(_QWORD *)(a1 + 136) = sub_100001B10;
  *(_BYTE *)(a1 + 144) = 0xF7u;
  *(_QWORD *)(a1 + 152) = sub_100001D30;
  *(_BYTE *)(a1 + 160) = 0xF8u;
  *(_QWORD *)(a1 + 168) = sub_100001C60;
  qword_100003F58 = malloc(0x400uLL);
  return __memcpy_chk((char *)qword_100003F58 + 48, a2, 18LL, -1LL);
}

首先先按H把-16这些改成 unsigned_int8类型,看着方便。 这里对照前面那个文章里的看,就是在对vm_cpu结构体进行初始化,前6个四字节的显然是寄存器,后几个是绑定虚拟机字节码和字节码对应的函数 这里可以切换到structures窗口按insert创建一个结构体,这样来更清晰的看代码 首先要对下面初始化的结构体和opcode对应的每个函数简要分析 第一个0xF0虚拟机指令对应的函数

__int64 __fastcall sub_100001D70(__int64 a1)
{
  __int64 result; // rax
  signed int *v2; // [rsp+Ch] [rbp-18h]
  v2 = (signed int *)(*(_QWORD *)(a1 + 24) + 2LL);
  switch ( *(unsigned __int8 *)(*(_QWORD *)(a1 + 24) + 1LL) )
  {
    case 0x10u:
      *(_DWORD *)a1 = *v2;
      break;
    case 0x11u:
      *(_DWORD *)(a1 + 4) = *v2;
      break;
    case 0x12u:
      *(_DWORD *)(a1 + 8) = *v2;
      break;
    case 0x13u:
      *(_DWORD *)(a1 + 12) = *v2;
      break;
    case 0x14u:
      *(_DWORD *)a1 = *((char *)qword_100003F58 + *v2);
      break;
    default:
      break;
  }
  result = a1;
  *(_QWORD *)(a1 + 24) += 6LL;
  return result;
}

首先可以看到每个函数中一开始都在引用 (_QWORD*)(a1+24),于是猜测这是指令指针寄存器,在structure窗口中先创建结构体给这个偏移处的qword*取个名字叫myeip

可以看到v2是虚拟机指令指针寄存器指向的地方后面第二个字节的内容,然后有一个switch结构,是在判断虚拟机指令后面第一个字节指定的是哪个虚拟机寄存器,然后将v2赋值到寄存器里,如果是0x14的话就把 *((char*)qword_100003F58+*v2)赋值给第一个寄存器,显然这qword100003F58相当于虚拟机的栈,然后函数负责移动指令指针寄存器,这个函数对应的虚拟机指令占六个字节,所以在最后有 *(_QWORD*)(a1+24)+=6LL;,所以这个函数的功能就是把一个立即数传入虚拟机的一个寄存器中,给他取个名字叫movreg_imm,方便后面查看

然后第二个0xF1对应的函数

__int64 __fastcall sub_100001A60(__int64 a1)
{
  __int64 result; // rax
  result = (unsigned int)(*(_DWORD *)(a1 + 4) ^ *(_DWORD *)a1);
  *(_DWORD *)a1 = result;
  ++*(_QWORD *)(a1 + 24);
  return result;
}

可以看到他把前两个寄存器里的值进行异或然后又把结果放到了第一个寄存器中,同样的这个虚拟机指令占一个字节,此函数负责将指令指针寄存器加1字节

0xF2对应的函数和0xF6对应的函数要结合来看

__int64 __fastcall sub_100001AA0(__int64 a1)
{
  __int64 result; // rax
  *(_DWORD *)(a1 + 16) = *(_DWORD *)a1 == *((char *)qword_100003F58 + *(unsigned __int8 *)(*(_QWORD *)(a1 + 24) + 1LL));
  result = a1;
  *(_QWORD *)(a1 + 24) += 2LL;
  return result;
}
__int64 __fastcall sub_100001B10(__int64 a1)
{
  __int64 result; // rax
  if ( *(_DWORD *)(a1 + 16) )
    *(_DWORD *)(a1 + 16) = 0;
  else
    *(_QWORD *)(a1 + 24) += *(unsigned __int8 *)(*(_QWORD *)(a1 + 24) + 1LL);
  result = a1;
  *(_QWORD *)(a1 + 24) += 2LL;
  return result;
}

可以看到0xF2中他把

*(_DWORD*)a1==*((char*)qword_100003F58+*(unsigned__int8*)(*(_QWORD*)(a1+24)+1LL));这个表达式的结果放到了 *(_DWORD*)(a1+16)里,0xF6中他有根据 *(_DWORD*)(a1+16)移动eip指令指针寄存器,这就很明显了,这个寄存器的功能就是标志寄存器,存放对比的结果,然后后面一个函数就是条件跳转指令了

然后0xF4

__int64 __fastcall sub_100001CB0(__int64 a1)
{
  __int64 result; // rax
  result = (unsigned int)(*(_DWORD *)(a1 + 4) + *(_DWORD *)a1);
  *(_DWORD *)a1 = result;
  ++*(_QWORD *)(a1 + 24);
  return result;
}

很明显的看出这个实在做加法运算,把前两个寄存器的值加起来放到第一个寄存器里,取个名字叫addregimm

然后0xF5

__int64 __fastcall sub_100001CF0(__int64 a1)
{
  __int64 result; // rax
  result = (unsigned int)(*(_DWORD *)a1 - *(_DWORD *)(a1 + 4));
  *(_DWORD *)a1 = result;
  ++*(_QWORD *)(a1 + 24);
  return result;
}

与上面的函数类似,这个是减法,取个名字叫subregimm

跟进0xF3对应的函数发现只有一个花括号,于是取名为nop

然后0xF7

__int64 __fastcall sub_100001D30(__int64 a1)
{
  __int64 result; // rax
  result = *(unsigned int *)(*(_QWORD *)(a1 + 24) + 1LL);
  *(_DWORD *)(a1 + 176) = result;
  *(_QWORD *)(a1 + 24) += 5LL;
  return result;
}

可以看到他把操作数放进了 (_DWORD*)(a1+176)处,先不管他是在干啥,先看0xF8

__int64 __fastcall sub_100001C60(__int64 a1)
{
  __int64 result; // rax
  result = sub_100001B80((unsigned int)(char)*(_DWORD *)a1, 2LL);
  *(_DWORD *)a1 = (char)result;
  ++*(_QWORD *)(a1 + 24);
  return result;
}
__int64 __fastcall sub_100001B80(char a1, int a2)
{
  bool v3; // [rsp+7h] [rbp-11h]
  bool v4; // [rsp+Fh] [rbp-9h]
  char v5; // [rsp+17h] [rbp-1h]
  v4 = 0;
  if ( a1 >= 65 )
    v4 = a1 = 97 )
      v3 = a1 vm_r1 = 0;
  a1->vm_r2 = 0;
  a1->vm_r3 = 0;
  a1->vm_r4 = 0;
  a1->flag = 0;
  a1->myeip = 0;
  LOBYTE(a1->opcode_f0) = 0xF0u;
  a1->mov_reg_imm = (__int64)mov_reg_imm;
  LOBYTE(a1->opcode_f1) = 0xF1u;
  a1->xor_r1_r2 = (__int64)xor_r1_r2;
  LOBYTE(a1->opcode_f2) = 0xF2u;
  a1->cmp_r1_imm = (__int64)cmp_r1_imm;
  LOBYTE(a1->opcode_f4) = 0xF4u;
  a1->add_r1_r2 = (__int64)add_r1_r2;
  LOBYTE(a1->opcode_f5) = 0xF5u;
  a1->dec_r1_r2 = (__int64)sub_r1_r2;
  LOBYTE(a1->opcode_f3) = 0xF3u;
  a1->nop = (__int64)nop;
  LOBYTE(a1->opcode_f6) = 0xF6u;
  a1->jz_imm = (__int64)jz_imm;
  LOBYTE(a1->opcode_f7) = 0xF7u;
  a1->mov_buff_imm = (__int64)mov_buff_imm;
  LOBYTE(a1->opcode_f8) = 0xF8u;
  a1->shift_r1_2 = (__int64)shift_r1_2;
  qword_100003F58 = malloc(0x400uLL);
  return __memcpy_chk((char *)qword_100003F58 + 48, a2, 18LL, -1LL);
}

现在返回去查看sub_100001F00函数里调用的第二个函数

__int64 __fastcall sub_100001F00(vm_cpu *a1)
{
  a1->myeip = (__int64)&loc_100001980 + 4;
  while ( *(unsigned __int8 *)a1->myeip != 0xF3 )
    sub_100001E50(a1);
  free(qword_100003F58);
  return a1->vm_r6;
}

可以看到此函数初始化了myeip指令指针寄存器将其指向 (__int64)&loc_100001980+4的地方 现在vm_cpu结构体搞清楚了,就可以照着结构体翻译 (__int64)&loc_100001980+4处的虚拟机opcode了 这里既然我们已经了解了这些opcode的功能,其实并不需要费力去提取出代码执行或者写脚本一句一句翻译了 他就是把一个立即数装入r1中,然后判断大小写,对其进行凯撒移位,我们直接在idapython里写一句脚本便出来了

"".join([chr(0x41+(int(i[-2:],16)+2-0x41)%26) if int(i[-2:],16)            
关注
打赏
1665306545
查看更多评论
0.8076s