看雪.纽盾 KCTF 2019 Q3 | 第二题点评及解题思路

发布者:Editor
发布于:2019-09-26 18:06
长久以来,佣兵辗转于不同主人之间,用血肉之躯为自己赢得一席之地。朝不保夕,危险,背叛和死亡都是家常便饭。今天的朋友可能是明日的敌人,过去的对手也能摇身一变成为如今的同盟。
但仍有杰出者脱颖而出,人称他为“龙”。龙不仅是收钱杀人,更以强烈的领袖气质将亡命之徒们团结起来,发展为强大的佣兵组织。
曹操多次雇佣龙为自己效命,此后,曹操巧妙撩拨着龙的野心。龙产生了摆脱佣兵身份,成为人上之人的渴望。杀死战神吕布,则是实现这个梦想的第一步。
数年后,曹操的大军席卷了三国,却在长坂坡遭遇前所未有的重挫。惊天动地的雷霆涤荡着曹军引以自豪的名将——赵子龙。



题目简介



本题共有1490人围观,最终只有9支团队攻破成功。其中金左手战队一马当先,在开赛当天就以最快的速度破解此题。


攻破此题的战队排名一览:



不知道这道题有没有让你苦思冥想呢?接下来我们一起来看一下这道题的点评和详细解析吧。


看雪评委crownless点评



这道题是一系列连载题中的一道题。和上一次的题相比,加入了反调试,而且虚拟执行的部分强化了很多。CPU指令更是增加了多种混淆,让题目的难度更上一层楼!




出题团队简介



本题出题战队 战神伽罗 :



战神伽罗团队成员只有simpower一个人,但依然出了难度很高的题,下面是相关简介:

野生程序猿,临床医学专业转行,现在北京某网络安全研究院工作,研究分析内核,对黑科技以及产品架构有很大兴趣,在编写分析内核的同时设计出一款恶意代码分析神器,适合安服和人员使用,欢迎找我索取内测版,kctf中的战神伽罗系列题目某种程度上反应了本猿平时的工作特征,本系列赛题将会和你一起不断进化,成长,欢迎继续关注,感谢看雪!感谢有你参与!




设计思路



1、首先将一部分密码封装在javascript中,通过javascript将自身进行加密。
2、通过简单的汇编代码变形算法 (加减固定数值) ,将一部分密码代码编译到可执行区域,通过指令跳转和对硬编码的变形对这部分密码进行恢复比对。
3、将恢复的代码注入到IE内核当中,并显示出来。
4、增加了一些反调试。
5、密码部分增加了混淆。
6、CPU指令增加了多种混淆。
破解思路:
1、有多种反调试机制,破解方法网上有,反调对抗过试之后,搜索内存可以搜到javascript代码,并将代码中的一部分密码获取出来。



2、解密后代码,密码为:simpower91
function ckpswd() {
    key="Simpower91";
    a = document.all.pswd.value;
    if (a.indexOf(key) ==0) {
        l=a.length;
        i=key.length;
        sptWBCallback(a.substring(i,l));
    } else {
        alert("wrong!<" + a + "> is not my GUID ;-)");
        return "1234";
    }
}
function ok(){
  alert("congratulations!");
}


3、剩下4位很容易就可以跟踪到比对代码的那个call处,有多个很怪异的指令被跳过,将 每个字节-7F就是剩余的ascii码 。



由于有多个这样的字节,因此需要尝试一下,经过尝试后,得到如下密码:

其中E0-7F='a'B2-7F='3'
B1-7F='2'B0-7F='1'


界面如下所示(注册成功后):



解题思路



本题解题思路由看雪论坛oooAooo提供:





概述



1、依然是dephi+脚本形式。
2、与前版相比加入了反调试。
3、与前版相比解密部分全部用虚拟机实现,不会用代码执行非虚拟机指令。
4、使用异常处理进入真正的解密函数。

脚本获取



1、通过跟踪函数Tfrmcrackme_FormShow,可以获取脚本,如下:
Function tion sptWBCallback(spt_wb_id,spt_wb_name,optionstr)
{
    url='#sptWBCallback:id=';
    url=url+spt_wb_id+';
    eventName='+spt_wb_name;
    if(optionstr)
        url=url+';
    params=optionstr';
    location=url;
}
 
<script language="vbscript">
function alert(msg_str)
    MsgBox msg_str,vbOKOnly + vbExclamation + vbApplicationModal,""
     
End Function
     
</script>
 
 
<body bgcolor=f0f0f0 topmargn=0 leftmargn=0 >、
    <center>
    <br><br><br>
    <input value="" id="pswd" size=39>
    </input>
    <br><br><br>
    <input type=button value="checkMyFlag" onclick="ckpswd();">
    </center>
 
</body>
 
function ckpswd() {
    key = "Simpower91";
    a = document.all.pswd.value;
    if (a.indexOf(key) == 0) {
        l = a.length;
        i = key.length;
        sptWBCallback(a.substring(i, l));
    } else {
        alert("wrong!<" + a + "> is not my GUID ;-)");
        return "1234";
    }
}
 
function ok() {
    alert("congratulations!");
}
 
CMShowingChanged
Tfrmcrackme_ApplicationEvents1Message
_Tfrmcrackme_FormCreate
 
 
about:blank#sptWBCallback:id=111;eventName=undefined

2、在脚本部分要求输入sn前部分字符必须是“Simpower91”。
3、然后通过sptWBCallback进入dephi回调函数执行校验。

回调函数定位-sncheck



在函数Tfrmcrackme_FormCreate中注册:0049945C
HIDWORD(v5) = v1;
  LODWORD(v5) = &sncheck;
  Teengine::TTeeFunction::InternalSetPeriod(*(Teengine::TTeeFunction **)(v1 + 824), v5);
  Idsyslogmessage::TIdSysLogMessage::SetTimeStamp(*(Idsyslogmessage::TIdSysLogMessage **)(v1 + 824), v6);


反调试



1、在Tfrmcrackme_FormCreate中调用反调试相关函数477DDC:
v10 = sub_477DDC((int)&cls_antiDebug_TAntiDebug, 1, 0);
  *(_DWORD *)(v1 + 828) = v10;

2、在Tfrmcrackme_FormCreate中貌似设置反调试函数:49978C:
HIDWORD(v11) = v1;
  LODWORD(v11) = sub_49978C;
  Teengine::TTeeFunction::InternalSetPeriod(v10, v12, v13, v11);
  Idsyslogmessage::TIdSysLogMessage::SetTimeStamp(*(Idsyslogmessage::TIdSysLogMessage **)(v1 + 828), v14);

3、反调试函数477F64:
int __usercall antiDebug5@<eax>(int a1@<eax>, int a2@<ebx>)
{
  int v2; // esi
  int v4; // [esp+0h] [ebp-408h]
 
  v2 = a1;
  Windows::FillMemory(&v4, 0x400u, 0);
  DeleteFiber(&v4);
  if ( GetLastError_0() == 0x57 )
  {
    a2 = 0;
    unknown_libname_1058(v2);
  }
  else
  {
    LOBYTE(a2) = 1;
    unknown_libname_1059(v2);
  }
  return a2;
}

4、反调试函数478110:
int __usercall antiDebug3@<eax>(int a1@<eax>, int a2@<ebx>)
{
  if ( *(_BYTE *)(__readfsdword(0x30u) + 2) )
  {
    LOBYTE(a2) = 1;
    unknown_libname_1059(a1);
  }
  else
  {
    a2 = 0;
    unknown_libname_1058(a1);
  }
  return a2;
}

5、反调试47814C:
int __usercall antiDebug4@<eax>(int a1@<eax>, int a2@<ebx>)
{
  if ( *(_DWORD *)(__readfsdword(0x30u) + 104) & 0x70 )
  {
    LOBYTE(a2) = 1;
    unknown_libname_1059(a1);
  }
  else
  {
    a2 = 0;
    unknown_libname_1058(a1);
  }
  return a2;
}

6、反调试函数:477F64
int __usercall antiDebug5@<eax>(int a1@<eax>, int a2@<ebx>)
{
  int v2; // esi
  int v4; // [esp+0h] [ebp-408h]
 
  v2 = a1;
  Windows::FillMemory(&v4, 0x400u, 0);
  DeleteFiber(&v4);
  if ( GetLastError_0() == 0x57 )
  {
    a2 = 0;
    unknown_libname_1058(v2);
  }
  else
  {
    LOBYTE(a2) = 1;
    unknown_libname_1059(v2);
  }
  return a2;
}

7、反调试函数4780B8
int antiDebug7()
{
  HMODULE v0; // eax
  HANDLE v1; // eax
 
  v0 = LoadLibraryA("ntdll.dll");
  dword_49DCC0 = (int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD))GetProcAddress_0(v0, "NtSetInformationThread");
  v1 = GetCurrentThread();
  return dword_49DCC0(v1, 17, 0, 0);
}

8、过掉反调试
#coding=utf-8
import struct
from idaapi import *
from idc import *
from idautils import *
 
dbg_write_memory(0x478070, '\xEB')#antiDebug2
refresh_debugger_memory()
 
dbg_write_memory(0x47812E, '\x90\x90')#antiDebug3
refresh_debugger_memory()
 
dbg_write_memory(0x47816C, '\x90\x90')#antiDebug4
refresh_debugger_memory()
 
dbg_write_memory(0x4781F4, '\x90\x90')#antiDebug6
refresh_debugger_memory()
 
dbg_write_memory(0x477F64, '\x33\xC0\xC3\x90')#antiDebug5
refresh_debugger_memory()
 
dbg_write_memory(0x4780B8, '\x33\xC0\xC3\x90')#antiDebug7
refresh_debugger_memory()



sncheck分析



1、异常处理函数注册
CODE:004994A7                 push    dword ptr fs:[eax]
CODE:004994AA                 mov     fs:[eax], esp
CODE:004994AD                 mov     eax, offset sub_475ED8
CODE:004994B2                 mov     edx, ds:off_49C3D8
CODE:004994B8                 mov     [edx+4], eax
CODE:004994BB                 mov     eax, ds:off_49C3D8
CODE:004994C0                 call    sub_46590C

其中sub_475ED8为异常处理函数:
signed int __cdecl sub_475ED8(_DWORD *a1, int a2, int a3)
{
  signed int result; // eax
 
  result = 1;
  if ( !a1[1] )
  {
    if ( *a1 == 0xC0000096 )
    {
      *(_DWORD *)(a3 + 184) = realSnCheck;
      result = 0;
    }
    else if ( *a1 == 0xC00000FD )
    {
      *(_DWORD *)(a3 + 196) -= 256;
    }
  }
  return result;
}

其中475EBC(realSnCheck)为异常0C0000096h处理函数。
2、获取vmp指令
CODE:00499530                 call    GetVmpCode
CODE:00499535                 mov     [ebp+var_10], eax

其中eax+4为VMP指令地址,eax+8为指令大小。
3、产生异常,进入475EBC(realSnCheck)

realSncheck(475EBC)



realSnCheck调用477778与之前类似


(1)解密代码和指令。(2)判断加密的指令是否是真实代码,如果是,这将相应代码进行重定位动态执行。(3)如果是VMP指令,则进入虚拟机执行。本题没有真实代码,所有指令都为虚拟机代码。

最终会进入476B8C(codeExe)函数中



codeExe函数



int __stdcall codeExe(int a1, int codeData, int retAddr, int dataOfFile, _DWORD *a5, int *a6, _DWORD *a7)
{
  struct globalInfo *global; // ebx
  struct vmpInfo *vmpInfo; // esi
  int v9; // edi
  int v10; // ecx
  int v11; // esi
  int v12; // edx
  int *v14; // [esp+1Ch] [ebp-380h]
  void *v15; // [esp+20h] [ebp-37Ch]
  int *v16; // [esp+24h] [ebp-378h]
  void *v17; // [esp+28h] [ebp-374h]
  int v18; // [esp+34h] [ebp-368h]
  int *v19; // [esp+38h] [ebp-364h]
  int v20; // [esp+3Ch] [ebp-360h]
  char v21; // [esp+40h] [ebp-35Ch]
  char v22; // [esp+140h] [ebp-25Ch]
  _BYTE *v23; // [esp+374h] [ebp-28h]
  char a4[6]; // [esp+37Ah] [ebp-22h]
  int v25; // [esp+380h] [ebp-1Ch]
  char *a2; // [esp+384h] [ebp-18h]
  int v27; // [esp+388h] [ebp-14h]
  int vmpDataSize; // [esp+38Ch] [ebp-10h]
  char *v29; // [esp+390h] [ebp-Ch]
  char *insSize; // [esp+394h] [ebp-8h]
  int v31; // [esp+398h] [ebp-4h]
  int vars0; // [esp+39Ch] [ebp+0h]
 
  v19 = 0;
  v18 = 0;
  v27 = 0;
  v16 = &vars0;
  v15 = &loc_477095;
  v14 = (int *)__readfsdword(0);
  __writefsdword(0, (unsigned int)&v14);
  global = sub_4760D0();
  v31 = vars0 + 8;
  global->retAddr = retAddr;
  *(_DWORD *)&a4[2] = global->curVmpCode1;
  a2 = *(char **)&a4[2];
  vmpInfo = global->vmpInfo;
  if ( !vmpInfo )
  {
    sub_4778E8(global);
    vmpInfo = global->vmpInfo;
  }
  a4[1] = 0;
  if ( vmpInfo->isVmpFlag == 1 )
  {
    a4[1] = 1;
    simvm_Init(global, v31, 16);
  }
  while ( 1 )
  {
    callCodeDecrypt((int)global, dataOfFile, *(int *)&a4[2], &vmpDataSize);
    while ( 1 )
    {
      while ( vmpInfo->isVmpFlag == 1 )
      {
        a4[0] = 0;
        v25 = (int)callVmpHandle(vmpInfo, codeData, *(_BYTE **)&a4[2], (int)a4);
        insSize = (char *)(v25 - *(_DWORD *)&a4[2]);
        if ( dataOfFile == global->curVmpCodeAddr )
        {
          GetNextValueByLen(&codeData, (unsigned __int8)a4[0]);
          global->curCodeAddr = codeData;
          GetNextValueByLen(&dataOfFile, insSize);
          global->curVmpCodeAddr = dataOfFile;
        }
        else
        {
          dataOfFile = global->curVmpCodeAddr;
          codeData = global->curCodeAddr;
        }
        callCodeDecrypt((int)global, dataOfFile, *(int *)&a4[2], &vmpDataSize);
      }
      if ( ifCode_simvm_orign(vmpInfo, &a2) != 1 )
        break;
      a2 = *(char **)&a4[2];
      a4[1] = 1;
      simvm_Init(global, v31, 16);
    }
    insSize = &a2[-*(_DWORD *)&a4[2]];
    a2 = *(char **)&a4[2];
    if ( !a4[1] )
      break;
    a4[1] = 0;
    vmpInfo->field_914 = 1;
  }
  if ( (signed int)insSize > 0 )
  {
    GetNextValueByLen(&dataOfFile, insSize);
    callCodeDecrypt((int)global, dataOfFile, *(int *)&a4[2], &vmpDataSize);
  }
  vmpDataSize = off_49DCA8(*(_DWORD *)&a4[2], vmpDataSize, 0, &v20, 4, v14);
  *a5 = vmpDataSize;
  v9 = unknown_libname_29(32 - vmpDataSize - 6);
  a2 = (char *)&global->field_1038 + v9;
  qmemcpy(a2, *(const void **)&a4[2], vmpDataSize);
  v14 = 0;
  *((_BYTE *)&global->field_1038 + v9 + vmpDataSize) = 104;
  unknown_libname_56(&v27, &v22);
  if ( (unsigned __int8)sub_476200(v27, global->cmpReg1) )
  {
    unknown_libname_56(&v27, &v21);
    if ( sub_466514(&str_FF_0[1], v27) != 1 )
    {
      insSize = (char *)sub_466514(&str___29[1], v27);
      v14 = &v27;
      sn11111(v27);
      System::__linkproc__ LStrCopy(v14);
      insSize = (char *)sub_466578(v27);
      if ( vmpDataSize - 1 >= 0 )
      {
        v10 = vmpDataSize;
        v29 = 0;
        do
        {
          v14 = 0;
          (v29++)[v9 + 4152 + (_DWORD)global] = -112;
          --v10;
        }
        while ( v10 );
      }
      insSize += vmpDataSize;
      vmpInfo->isVmpFlag1 = vmpInfo->isVmpFlag;
      if ( (signed int)insSize <= 0 )
      {
        insSize += dataOfFile - global->field_1070;
        v14 = (int *)&v29;
        callCodeDecrypt((int)global, global->field_1070, *(int *)&a4[2], &v29);
      }
      v23 = sub_475A90(vmpInfo, *(int *)&a4[2], (unsigned int)insSize);
      vmpInfo->isVmpFlag = vmpInfo->isVmpFlag1;
      if ( !v23 )
        global->curCodeAddr1 = global->curCodeAddr;
    }
  }
  else if ( sub_466514(&str_CALL_0[1], v27) == 1 )
  {
    unknown_libname_56(&v27, &v21);
    if ( sub_466514(&str_FF_0[1], v27) != 1 )
    {
      insSize = (char *)sub_466514(&str___29[1], v27);
      v29 = insSize - 1;
      v11 = (signed int)(insSize - 1) / 2;
      a2 = (char *)&global->field_1038 + v9 + v11;
      v14 = &v27;
      sn11111(v27);
      System::__linkproc__ LStrCopy(v14);
      insSize = (char *)sub_466578(v27);
      v29 = (char *)(&insSize[codeData] - ((char *)&global->field_1038 + v9));
      qmemcpy(a2, &v29, vmpDataSize - v11);
    }
    insSize = (char *)vmpDataSize;
    v23 = (_BYTE *)vmpDataSize;
  }
  else
  {
    insSize = (char *)vmpDataSize;
    v23 = (_BYTE *)vmpDataSize;
  }
  *a7 = insSize;
  insSize = (char *)retAddr;
  a2 = (char *)&global->field_1038 + v9 + vmpDataSize + 1;
  qmemcpy(a2, &insSize, 4u);
  v14 = 0;
  *((_BYTE *)&global->field_103C + v9 + vmpDataSize + 1) = -61;
  *a6 = (int)&global->field_1038 + v9;
  global->curCodeAddr = codeData + *a7;
  global->curVmpCodeAddr = (int)&v23[dataOfFile];
  unknown_libname_56(&v19, &v22);
  v14 = v19;
  unknown_libname_56(&v18, &v21);
  v12 = *a6;
  sub_47745C(vmpDataSize, v18, v14);
  __writefsdword(0, (unsigned int)v15);
  v17 = &loc_47709C;
  System::__linkproc__ LStrArrayClr(&v18, 2);
  return System::__linkproc__ LStrClr(&v27);
}

1、调用callCodeDecrypt(4774D8)对指令进行解密
2、调用callVmpHandle(475C08)对指令进行分析和执行,一直到所有指令执行完毕。

callVmpHandle函数



char *__fastcall callVmpHandle(struct vmpInfo *vmpInfo, int codeAddr, _BYTE *vmpCode_1, int pinsLen)
{
  struct vmpInfo *vmpInfo1; // esi
  struct tempStruct tempStruct; // [esp+8h] [ebp-10h]
  char *vmpCodeEnd; // [esp+10h] [ebp-8h]
  int vmpCode; // [esp+14h] [ebp-4h]
 
  vmpCode = (int)vmpCode_1;
  vmpInfo1 = vmpInfo;
  vmpCodeEnd = vmpCode_1;
  if ( vmpInfo->isVmpFlag == 1 )
  {
    if ( ifCode_simvm_orign(vmpInfo, (char **)&vmpCode) == 2 )
    {
      vmpCodeEnd = (char *)vmpCode;
    }
    else
    {
      vmpCodeEnd = (char *)vmpCode;
      if ( GetTempStructByVmpCmd(vmpInfo1, *(_BYTE *)vmpCode, &tempStruct) )
        vmpCodeEnd = vmpHandle(vmpInfo1, vmpCode, (_BYTE *)pinsLen, (int)tempStruct.vmpInfo, tempStruct.copyFun);
      else
        vmpInfo1->isVmpFlag = 2;
    }
  }
  return vmpCodeEnd;
}

1、调用ifCode_simvm_orign判断指令类型。
2、如果是VMP指令则。
(1)调用GetTempStructByVmpCmd函数初始化相关结构,并获取对应真实代码的处理函数(474F08)。
char *__fastcall callVmpHandle(struct vmpInfo *vmpInfo, int codeAddr, _BYTE *vmpCode_1, int pinsLen)
{
  struct vmpInfo *vmpInfo1; // esi
  struct tempStruct tempStruct; // [esp+8h] [ebp-10h]
  char *vmpCodeEnd; // [esp+10h] [ebp-8h]
  int vmpCode; // [esp+14h] [ebp-4h]
 
  vmpCode = (int)vmpCode_1;
  vmpInfo1 = vmpInfo;
  vmpCodeEnd = vmpCode_1;
  if ( vmpInfo->isVmpFlag == 1 )
  {
    if ( ifCode_simvm_orign(vmpInfo, (char **)&vmpCode) == 2 )
    {
      vmpCodeEnd = (char *)vmpCode;
    }
    else
    {
      vmpCodeEnd = (char *)vmpCode;
      if ( GetTempStructByVmpCmd(vmpInfo1, *(_BYTE *)vmpCode, &tempStruct) )
        vmpCodeEnd = vmpHandle(vmpInfo1, vmpCode, (_BYTE *)pinsLen, (int)tempStruct.vmpInfo, tempStruct.copyFun);
      else
        vmpInfo1->isVmpFlag = 2;
    }
  }
  return vmpCodeEnd;
}

(2)调用vmpHandle(474EAC)。

vmpHandle(474EAC)函数



char *__fastcall vmpHandle(struct vmpInfo *vmpInfo, int vmpCode, _BYTE *pinsLen1, int vmpInfo1, int vmpExeFun)
{
  _BYTE *pinsLen; // esi
  struct vmpInfo *vmpInfo_1; // ebx
  char *vmpInsEnd; // edi
  int out_size; // [esp+Ch] [ebp-14h]
  char out21; // [esp+10h] [ebp-10h]
  char out11; // [esp+14h] [ebp-Ch]
  int out_orgReg; // [esp+18h] [ebp-8h]
  int out_aimReg; // [esp+1Ch] [ebp-4h]
 
  pinsLen = pinsLen1;
  vmpInfo_1 = vmpInfo;
  vmpInsEnd = copyVMPcode(vmpInfo, (char *)vmpCode);
  *pinsLen = vmpInfo_1->vmpCode.codeInsLen;
  getVmpReg(vmpInfo_1, &out_aimReg, &out_orgReg, &out11, &out21, &out_size);
  vmpRealFunHandle((int)vmpInfo_1, out_aimReg, out_orgReg, vmpInfo1, (int (__fastcall *)(int, int))vmpExeFun, out_size);
  return vmpInsEnd;
}

1、调用copyVMPcode拷贝当前条VMP指令,一条指令长度为0x64。
2、调用getVmpReg,应该是获取相关寄存器或者执行简单指令执行。
3、执行该指令的真实指令处理函数。
4、稍微分析了下虚拟指令。
firt ins
+0      01 00 00 00 
+4         00 00 00 00             setout1Flag
+8         17 11 01 F0 vmpcmd 1 reg1
+c 00 00 00 00 
+10     00 00 00 00 
+14     00 00 00 00                      offset
+18     00 00 00 00 
+1c 00 00 00 00 
+20     00 00 00 00 
+24     00 00 00 00 
+28     00 
+29     04                              unknowcmd1
+2A 01 
+2B 00 
+2C 0C 00 00 F0 vmdcmp2
+30     00 00 00 00 
+34   00 00 00 00 
+38     30 00 00 00                      offset
+3C 01 00 00 00 
+40     00 00 00 00 
+44     00 00 00 00 
+48     00 00 00 00 
+4C 00 
+4D 04 
+4E 00 
+4F 02 
+50     07                 insSize
+51     00 
+52     00 
+53     00 
+54     00 00 00 00 
+58     00 00 00 00 
+5C 00 00 00 00 
+60     00 00 00 00
 
if ( vmpInfo->vmpCode.vmpCmd1 ) F0011117
{
    out1 = result0Reg
    out3 = [result0Reg]+offset
    if(unknowcmd1 !=3)
        out5 = unknowcmd1
    if(setout1Flag || offset)
        out1 = out3
     
}
 
//0x01
varc = fs:[offset2]
var7 = fs:[offset2] + offset1
================================================
 
+0      01 00 00 00 
+4      00                     aimreg_SetFlag
+5      00 
+6      00 
+7      00                
+8      17 01 00 F0 aimreg_cmd
+C 00 00 00 00 
+10     00 00 00 00 
+14     00 00 00 00                 aimreg_offset
+18     00 00 00 00 
+1C 00 00 00 00 
+20     00 00 00 00 
+24     00 00 00 00 
+28     00 
+29     01                     aimreg_size
+2A 01                            
+2B 00 
+2C 17 11 01 F0 orgreg_cmd
+30     00 00 00 00 
+34     00 00 00 00 
+38     02 00 00 00                     orgreg_offset
+3C 01 00 00 00 
+40     00 00 00 00 
+44     00 00 00 00 
+48     00 00 00 00 
+4C 00 
+4D 04                     orgreg_size
+4E 01 
+4F 02 
+50     03                                  
+51     00 
+52     00
+53     00 
+54     00 00 00 00 
+58     00 00 00 00 
+5C 00 00 00 00 
+60     00 00 00 00
//==============================================================
+0      01 00 00 00 
+4      01 00 00 00                          aimreg_SetFlag
+8      02 00 00 F0 aimreg_cmd
+c 00 00 00 00 
+10     00 00 00 00 
+14     EF FF FF FF aimreg_offset
+18     02 00 00 00
+1c 00 00 00 00 
+20     00 00 00 00 
+24     00 00 00 00 
+28     00 
+29     04                  aimreg_size
+2A 00 
+2B 00
+2c 17 01 00 F0 orgreg_cmd
+30     00 00 00 00 
+34     00 00 00 00 
+38     00 00 00 00              orgreg_offset
+3C 00 00 00 00 
+40     00 00 00 00 
+44     00 00 00 00 
+48     00 00 00 00
+4C 00 
+4D 01                      orgreg_size
+4E 00 
+4F 02 
+50     03 
+51     00 
+52     00 
+53     00 
+54     00 00 00 00 
+58     00 00 00 00
+5C 00 00 00 00 
+60     00 00 00 00 
 
//=====================================================
cmp [aimreg+offset], orgreg_value set global.cmpReg 0x80位
+0      08 00 00 00  
+4      01 00 00 00                          aimreg_SetFlag
+8      02 00 00 F0 aimreg_cmd
+c 00 00 00 00 
+10     00 00 00 00  
+14     EF FF FF FF aimreg_offset
+18     02 00 00 00 
+1c 00 00 00 00 
+20     00 00 00 00  
+24     00 00 00 00 
+28     00 
+29     04                      aimreg_size
+2A 00 
+2B 00 
+2c 00 00 00 00                          orgreg_cmd
+30     00 00 00 00  
+34     01 00 00 00                             orgreg_value
+38     00 00 00 00                         orgreg_offset
+3C 00 00 00 00 
+40     00 00 00 00  
+44     00 00 00 00 
+48     00 00 00 00 
+4C 00 
+4D 04                             orgreg_size
+4E 01 
+4F 02 
+50     04 
+51     00 
+52     00 
+53     00  
+54     00 00 00 00 
+58     00 00 00 00 
+5C 00 00 00 00 
+60     00 00 00 00  
 
//==========================================================================
+0      1F 00 00 00  
+4      00 00 00 00                      aimreg_SetFlag
+8      00 00 00 00 
+c 00 00 00 00 
+10     6A 01 00 00                              branch_offset
+14     00 00 00 00                      aimreg_offset
+18     00 00 00 00 
+1c 00 00 00 00 
+20     00 00 00 00  
+24     00 00 00 00 
+28     00 
+29     04                      aimreg_size
+2A 00 
+2B 00 
+2c 00 00 00 00                      orgreg_cmd
+30     00 00 00 00  
+34     00 00 00 00                      orgreg_value
+38     00 00 00 00                      orgreg_offset
+3C 00 00 00 00 
+40     00 00 00 00  
+44     00 00 00 00 
+48     00 00 00 00 
+4C 00 
+4D 00                      orgreg_size
+4E 00 
+4F 01 
+50     06 
+51     00 
+52     00 
+53     00  
+54     00 00 00 00 
+58     00 00 00 00 
+5C 00 00 00 00 
+60     00 00 00 00
//===================================================
cmp [aimreg+offset], orgreg_value set global.cmpReg 0x80位
+0      08 00 00 00  
+4      01 00 00 00                     aimreg_SetFlag
+8      14 11 01 F0 aimreg_cmd
+c 00 00 00 00            
+10     00 00 00 00  
+14     44 03 00 00                     aimreg_offset
+18     01 00 00 00 
+1c 00 00 00 00 
+20     00 00 00 00  
+24     00 00 00 00 
+28     00 
+29     04                     aimreg_size
+2A 00 
+2B 00 
+2c 00 00 00 00                     orgreg_cmd
+30     00 00 00 00  
+34     01 00 00 00                     orgreg_value
+38     00 00 00 00 
+3C 00 00 00 00 
+40     00 00 00 00  
+44     00 00 00 00 
+48     00 00 00 00 
+4C 00 
+4D 04                         orgreg_size
+4E 01 
+4F 02 
+50     07 
+51     00 
+52     00 
+53     00  
+54     00 00 00 00 
+58     00 00 00 00 
+5C 00 00 00 00 
+60     00 00 00 00
============================================================================
+0      1F 00 00 00       
+4      00 00 00 00                       aimreg_SetFlag
+8      00 00 00 00                aimreg_cmd
+c 00 00 00 00 
+10     5D 01 00 00                      branch_offset
+14     00 00 00 00                       aimreg_offset
+18     00 00 00 00
+1c 00 00 00 00 
+20     00 00 00 00  
+24     00 00 00 00 
+28     00 
+29     04                           aimreg_size
+2A 00 
+2B 00
+2c 00 00 00 00                       orgreg_cmd
+30     00 00 00 00  
+34     00 00 00 00                      orgreg_value
+38     00 00 00 00
+3C 00 00 00 00 
+40     00 00 00 00  
+44     00 00 00 00 
+48     00 00 00 00
+4C 00 
+4D 00                      orgreg_size
+4E 00 
+4F 01 
+50     06
+51     00 
+52     00 
+53     00  
+54     00 00 00 00 
+58     00 00 00 00
+5C 00 00 00 00 
+60     00 00 00 00
 
//====================================================
//获取输入sn的地址
mov aimreg+aimReg, [org+orgOfset]
+0      01 00 00 00  
+4      00 00 00 00                          aimreg_SetFlag
+8      17 11 01 F0 aimreg_cmd
+c 00 00 00 00 
+10     00 00 00 00  
+14     00 00 00 00                          aimreg_offset
+18     00 00 00 00
+1c 00 00 00 00 
+20     00 00 00 00  
+24     00 00 00 00 
+28     00 
+29     04                      aimreg_size
+2A 01 
+2B 00
+2c 02 00 00 F0 orgreg_cmd
+30     00 00 00 00  
+34     00 00 00 00                          orgreg_value
+38     E4 FF FF FF orgreg_offset
+3C 02 00 00 00 
+40     00 00 00 00  
+44     00 00 00 00 
+48     00 00 00 00
+48     00 04 00 02 
+4C 03 
+4D 00                      orgreg_size
+4E 00 
+4F 00  
+50     00 
+51     00 
+52     00 
+53     00 
+54     00 00 00 00
+58     00 00 00 00 
+5C 00 00 00 00  
//===================================================
+0      0B 00 00 00  
+4      00 00 00 00                aimreg_SetFlag
+8      00 00 00 00              aimreg_cmd
+c 00 00 00 00 
+10     6D B1 F6 FF branch_offset
+14     00 00 00 00                 aimreg_offset
+18     00 00 00 00
+1c 00 00 00 00 
+20     00 00 00 00  
+24     00 00 00 00 
+28     00    
+29     04                     aimreg_size
+2A 00 
+2B 00
+2c 00 00 00 00                 orgreg_cmd
+30     00 00 00 00  
+34     00 00 00 00 
+38     00 00 00 00
+3C 00 00 00 00 
+40     00 00 00 00  
+44     00 00 00 00 
+48     00 00 00 00
+48     00 00 00 01 
+4C 05                     code_offset
+4D 00                     orgreg_size
+4E 00 
+4F 00  
+50     00 
+51     00 
+52     00 
+53     00 
+54     00 00 00 00
+58     00 00 00 00 
+5C 00 00 00 00  
+60     08 00 00 00

其中如下函数vmp_opcode_0x08为处理比较指令的,跟踪到这里后即基本可以还原了。
truct vmpInfo *__userpurge vmp_opcode_0x08@<eax>(struct vmpInfo *vmpInfo@<eax>, _DWORD *out1@<edx>, _DWORD *vmpcodeLocation@<ecx>, unsigned int vmpcodeEnd@<edi>, int out5)
{
  unsigned int v5; // et0
  unsigned int v6; // et0
  unsigned int v7; // et0
  unsigned int v8; // et0
  struct vmpInfo *result; // eax
 
  switch ( out5 )
  {
    case 1:
      v5 = __readeflags();
      __writeeflags(v5);
      result = vmp_opcode_0x08_setCmpFlag(
                 vmpInfo,
                 *(unsigned __int8 *)out1 - *(unsigned __int8 *)vmpcodeLocation,
                 1,
                 v5);
      break;
    case 2:
      v6 = __readeflags();
      __writeeflags(v6);
      result = vmp_opcode_0x08_setCmpFlag(vmpInfo, vmpcodeEnd, 2, v6);
      break;
    case 4:
      v7 = __readeflags();
      __writeeflags(v7);
      result = vmp_opcode_0x08_setCmpFlag(vmpInfo, *out1 - *vmpcodeLocation, out5, v7);
      break;
    default:
      v8 = __readeflags();
      __writeeflags(v8);
      result = vmp_opcode_0x08_setCmpFlag(vmpInfo, *out1 - *vmpcodeLocation, 4, v8);
      break;
  }
  return result;
}

使用输入的字符-0x7F,然后与如下地址中的E0  B2 B1比较相等即为正确。
CODE:00499570                 clts
CODE:00499572                 mov al, 0B1h
CODE:00499574                 mov dl, 0B8h
CODE:00499576                 mov bh, 0E0h
CODE:00499578                 mov dl, 0B1h
CODE:0049957A mov al, 0E2h

其中 499572地址敲好在产生异常clts指令的下方。
最后得到flag为:Simpower91a321
ps:作者的dephi相关题目已经出到了3.0阶段

1.0 ---dephi+jscrypt 内存中暴露了key

2.0--- dephi+代码重定位 +VMP;虽然有vmp但是主要算法在代码重定位部分

3.0--- dephi+代码重定位 +VMP:所有代码都在vmp

4.0---dephi+代码重定位 +VMP:算法复杂化




- End -

往期赛题

* 看雪.纽盾 KCTF 2019 Q3 | 第一题点评及解题思路


合作伙伴

上海纽盾科技股份有限公司(www.newdon.net)成立于2009年,是一家以“网络安全”为主轴,以“科技源自生活,纽盾服务社会”为核心经营理念,以网络安全产品的研发、生产、销售、售后服务与相关安全服务为一体的专业安全公司,致力于为数字化时代背景下的用户提供安全产品、安全服务以及等级保护等安全解决方案。





公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com




声明:该文观点仅代表作者本人,转载请注明来自看雪