首页
社区
课程
招聘
[原创]看雪 2016 CTF 第二十九 哑弹
发表于: 2016-12-29 15:37 5405

[原创]看雪 2016 CTF 第二十九 哑弹

HHHso 活跃值
22
2016-12-29 15:37
5405

虽有俄罗斯方块游戏框架,"Game Over"却炸不响,是为哑弹。
后面的libdasm反汇编引擎使用是解题后拓展的内容,
因为一开始有个思路是用它,虽然用在这里并非好的选择,但毕竟由题目引出,
也就不开新帖讨论libdasm反汇编引擎的使用了;
由于我们已经猜定解密字节码的部分指令特征,所以可以直接搜索特征指令的存在
来判断解密的正确与否。如果没法确定指令特征,则可以结合收集的特殊指令集
结合libdasm的反汇编结果判定是否解密正确。
这里主要拓展libdasm用作python拓展模块的pydasm的编译、安装和使用。
其用于其他语言应用中也很方便快捷,详细参考开源文件的readme和后续拓展。

清除几个检测F2断点或修改的反调试异常
将条件跳转修改为直接跳转即可
401069 jz short loc_401072 >> jmp
401505 jz short loc_40150E >> jmp
401847 jz short loc_401850 >> jmp

程序入口函数如下
a. 其首先通过Hi_importFun_bpcheck_sub_401039函数初始化自己的导数函数机制(该机制参考后续分析)
b. 接着通过DialogBoxParamA启动对话框,消息处理例程为 Hi_DialogFunc_sub_401265
.text:00401000 public Hi_start_sub_401000
.text:00401000 Hi_start_sub_401000 proc near
.text:00401000 push    0               ; lpModuleName
.text:00401002 call    GetModuleHandleA
.text:00401007 mov     Hi_hInstance, eax
.text:0040100C push    0
.text:0040100E push    0
.text:00401010 push    0
.text:00401012 call    Hi_importFun_bpcheck_sub_401039
.text:00401017 call    InitCommonControls
.text:0040101C push    0               ; dwInitParam
.text:0040101E push    offset Hi_DialogFunc_sub_401265 ; lpDialogFunc
.text:00401023 push    0               ; hWndParent
.text:00401025 push    65h             ; lpTemplateName
.text:00401027 push    Hi_hInstance    ; hInstance
.text:0040102D call    DialogBoxParamA
.text:00401032 push    0               ; uExitCode
.text:00401034 call    ExitProcess
.text:00401034 Hi_start_sub_401000 endp

在对话框消息处理例程函数Hi_DialogFunc_sub_401265中,
对于窗口初始化WM_INITDIALOG消息,其会启动下述线程函数T1
Hi_ThreadFun_1_sub_40149C             0040149C  

在T1中其又继续启动以下五个线程,T11,T12,T13,T14,T15
并初始化以下线程同步通信对象event1~event6
Hi_ThreadFun11_sub_401918             00401918  
Hi_ThreadFun12_sub_40197C             0040197C  
Hi_ThreadFun13_sub_4016F0             004016F0  
Hi_ThreadFun14_sub_4019BF             004019BF  
Hi_ThreadFun15_sub_4017F9             004017F9  

大体逻辑框架如下
T1:
  T11:wait(2,[event3,event4]) >> VirtualProtect(PAGE_EXECUTE_READWRITE,0x401000,cbSize:0x3000)
  T12:wait(2,[Event4,event5]) >>
  T13:
     keyxor.403C85 = key8 ^ 0x66
     switch(cs){
       case 0x353: event4
       case 0x325: event2
       case 0x29B: event3
       case 0x363: event6
     }
  T14:wait(2,[Event5,Event6])
  T15:wait(2,[event1,event2])
    VirtualProtect(0x401000,0x3000,PAGE_EXECUTE_READWRITE)
    bDecryptRun add.xor.decrypt byte key8
  wait(5,[T11,T12,T13,T14,T15])

在对话框消息处理例程函数Hi_DialogFunc_sub_401265中,
下述函数响应注册检测按钮点击事件
004012BD call Hi_ButtonClick_sub_4012E7

以下是重名的一些全局变量、函数,
Hi_ButtonClick_sub_4012E7             004012E7  
Hi_CloseDesktop_byte_403B71           00403B71  
Hi_CloseWindow_byte_403BD4            00403BD4  
Hi_CreateEventA_byte_403BDF           00403BDF  
Hi_CreateThread_byte_403BA8           00403BA8  
Hi_Event2_dword_4032F5                004032F5  
Hi_Event3_dword_4032F9                004032F9  
Hi_Event4_dword_4032FD                004032FD  
Hi_Event5_dword_403301                00403301  
Hi_Event6_dword_403305                00403305  
Hi_GetDlgItemTextA_byte_403B3A        00403B3A  
Hi_Hthread11_dword_403309             00403309  
Hi_Init3_MEB_RefcolIdx_dword_4030D5   004030D5  
Hi_IsDebuggerPresent_byte_403BC9      00403BC9  
Hi_MEB_byte_40303B                    0040303B  
Hi_MessageBoxA_byte_403B19            00403B19  
Hi_PostQuitMessage_byte_403B2F        00403B2F  
Hi_SendMessageTimeoutA_byte_403B87    00403B87  
Hi_SendNotifyMessageA_byte_403B92     00403B92  
Hi_SetEvent_byte_403BEA               00403BEA  
Hi_SetWinEventHook_byte_403B9D        00403B9D  
Hi_Sleep_byte_403BF5                  00403BF5  
Hi_ThreadFun11_sub_401918             00401918  
Hi_ThreadFun12_sub_40197C             0040197C  
Hi_ThreadFun13_sub_4016F0             004016F0  
Hi_ThreadFun14_sub_4019BF             004019BF  
Hi_ThreadFun15_sub_4017F9             004017F9  
Hi_ThreadFun_1_sub_40149C             0040149C  
Hi_VirtualAlloc_byte_403C0B           00403C0B  
Hi_VirtualFree_byte_403C16            00403C16  
Hi_VirtualProtect_byte_403C00         00403C00  
Hi_WaitForMultipleObjects_byte_403BBE 00403BBE  
Hi_WaitForSingleObject_byte_403BB3    00403BB3  
Hi_bDecryptRun_byte_403C99            00403C99  
Hi_bpF2_check_dword_403C51            00403C51  
Hi_crchek2_dword_403C21               00403C21  
Hi_dec_check_sub_401DA0               00401DA0  
Hi_getExportTblPtr_sub_401377         00401377  
Hi_hEvent1_dword_4032F1               004032F1  
Hi_hInstance                          00403C9C  
Hi_hThread12_dword_40330D             0040330D  
Hi_hThread13_dword_403311             00403311  
Hi_hThread14_dword_403315             00403315  
Hi_hthread15_dword_403319             00403319  
Hi_importFun_bpcheck_sub_401039       00401039  
Hi_inc_check_sub_401DC1               00401DC1  
Hi_init0_MEB_RefrowIdx_dword_4030D9   004030D9  
Hi_init1_dword_403037                 00403037  
Hi_key8_byte_403C85                   00403C85  
Hi_rand0x1C_dword_4030E1              004030E1  
Hi_startM_asc_4030ED                  004030ED  
Hi_startMrand1ChPtr_dword_4030DD      004030DD  
Hi_start_sub_401000                   00401000

其中包括程序样例采用的自定义函数地址导入表条目Self_AIT_entry,如
Hi_VirtualAlloc_byte_403C0B
Hi_MessageBoxA_byte_403B19

自定义导入函数表条目的结构大意如下
#param 1
Self_AIT_entry{
  byte  bFuncAddrImportInit;
  dword dwFuncSymbolNameCheckSum;
  char cSymNameCh3;
  char cSymNameCh5;
  word wFunAddrHigh;
  word wFunAddrLow;
}
在Hi_Init_import_entries_sub_401080函数中
通过调用Hi_Init_P1_SelfIATentry_sub_40139E传递不同的Self_AIT_entry完成
不同导入函数Self_AIT_entry的初始化,以备后续使用

Hi_Init_P1_SelfIATentry_sub_40139E完成初始化的基本原理是
若bFuncAddrImportInit没有设置为1(已完成初始化),则搜索目标dll模块的导出函数表,
遍历导出表的名称表,匹配函数名的第三、五字符cSymNameCh3,cSymNameCh5,
并累加函数名各字符的校验和。如果匹配cSymNameCh3,cSymNameCh5和校验和dwFuncSymbolNameCheckSum
即找到相应的Self_AIT_entry对应的模块的导出函数条目,通过对应的地址索引,在导出表的地址表中
获取相应的RVA加上模块基址得到函数的真正地址VA,
将函数地址VA高低两字分开存储到wFunAddrHigh,wFunAddrLow,并设置bFuncAddrImportInit为已初始化

以下python代码用于实现IDA中的Self_AIT_entry解析,
getMs35将编译目标模块的所有导出函数生成索引字典(由于导出函数太多,会有些慢)
对于长名称(Name[5]有效的名称,相应的Sleep函数名为短名称)的导出函数,其索引的键
为cSymNameCh3+cSymNameCh5+"_"+零补齐的8位校验和十六进制值
对于断名称则只为 零补齐的8位校验和十六进制值

gets35fn用于查询长名称,在IDAPython中调用提供Self_AIT_entry地址即可,如
Python>gets35fn(0x403B19)
返回
sg_0000042F
MessageBoxA
则可将0x403B19条目重命名为 Hi_MessageBoxA_byte_403B19
由于事件条目不多可,可以直接一个个修改,
模块或函数名都多的情况下,可以考虑引进MakeName自动遍历修改
#------- ------- ------- ------- ------- ------- -------
import pefile
def getMs35(mp = r'c:\WINDOWS\system32\ntdll.dll'):
  pe =  pefile.PE(mp)
  pe.DIRECTORY_ENTRY_EXPORT.symbols[0].name
  s35 = {}
  for sy in pe.DIRECTORY_ENTRY_EXPORT.symbols:
    syname = sy.name
    if syname.__len__() > 5:
      s = sum([ord(ch) for ch in syname])
      s35key = "{}{}_{:08X}".format(syname[3],syname[5],s)
      s35[s35key]=syname
    else:
      s = sum([ord(ch) for ch in syname])
      s35key = "{:08X}".format(s)
      s35[s35key]=syname
  return s35

s35Ntdll = getMs35(r'c:\WINDOWS\system32\ntdll.dll')
s35User32 = getMs35(r'c:\WINDOWS\system32\User32.dll')
s35Kernel32 = getMs35(r'c:\WINDOWS\system32\Kernel32.dll')

def gets35fn(ea):
  s35key = gets35key(ea)
  print s35key
  if s35key in s35Ntdll:
    return s35Ntdll[s35key]
  elif s35key in s35User32:
    return s35User32[s35key]
  elif s35key in s35Kernel32:
    return s35Kernel32[s35key]
  else:
    return ""

def gets35fnbyKey(s35key):
  if s35key in s35Ntdll:
    return s35Ntdll[s35key]
  elif s35key in s35User32:
    return s35User32[s35key]
  elif s35key in s35Kernel32:
    return s35Kernel32[s35key]
  else:
    return ""   
#------- ------- ------- ------- ------- ------- -------

在Hi_ThreadFun13_sub_4016F0中,下述代码片段会获取输入的key并xor 0x66加密
存储在全局变量 Hi_keyxor8_byte_403C85 中,
其后续是根据key的不同校验和启动不同的事件触发俄罗斯方块游戏自动跑起来。

.text:0040172B mov     eax, offset Hi_GetDlgItemTextA_byte_403B3A
.text:00401730 mov     bx, [eax+7]
.text:00401734 shl     ebx, 10h
.text:00401737 add     bx, [eax+9]
.text:0040173B push    100h
.text:00401740 lea     eax, [ebp+var_100]
.text:00401746 push    eax
.text:00401747 push    3E9h
.text:0040174C push    hWnd
.text:00401752 call    ebx
.text:00401754 xor     edx, edx
.text:00401756 xor     ecx, ecx
.text:00401758 lea     eax, [ebp+var_100]
.text:0040175E lea     esi, Hi_keyxor8_byte_403C85
.text:00401764 mov     cl, [eax]
.text:00401766 mov     ebx, 8

单击检测按钮后,会触发相应函数Hi_ButtonClick_sub_4012E7的调用
在函数中,根据标记Hi_bIntStartGame_1_dword_403037 初始化启动俄罗斯方块游戏

.text:0040131B                 call    GetDlgItemTextA
.text:00401320                 mov     eax, Hi_bIntStartGame_1_dword_403037
.text:00401325                 or      eax, eax
.text:00401327                 jnz     short loc_40132E
.text:00401329                 call    Hi_Init_start_game_sub_401BA1

接着对输入的key的八个字符计算校验和作为俄罗斯方块游戏的操作码
.text:0040136C push    edx
.text:0040136D call    Hi_handle_game_director_sub_401B0A

不同key校验和,分别代码左移,右移,顺时针旋转90度,往下
  case 0x566:Hi_dec_left_sub_401DA0();
  case 0x79A:Hi_inc_right_sub_401DC1();
  case 0x86B:Hi_ClockCircle_90_sub_401E16();
  case 0x5D5:Hi_down_sub_401DE2();
  case 0x325:Hi_down_sub_401DE2();
---------------------
1         1
1         1
1         1
1         1
1         1
1         1
1         1
1         1
1         1
1         1
1         1
1         1
1         1
11111111111
---------------------

char *__stdcall Hi_handle_game_director_sub_401B0A(int P1_cs)
{
  char *result; // eax@2

  if ( Hi_bIntStartGame_1_dword_403037 )
  {
    ++Hi_bDecryptRun_byte_403C99;
    switch ( P1_cs )
    {
      case 0x566:
        result = (char *)Hi_dec_left_sub_401DA0();
        break;
      case 0x79A:
        result = (char *)Hi_inc_right_sub_401DC1();
        break;
      case 0x86B:
        result = Hi_ClockCircle_90_sub_401E16();
        break;
      case 0x5D5:
        result = (char *)Hi_down_sub_401DE2();
        break;
      case 0x325:
        result = (char *)Hi_down_sub_401DE2();
        break;
      default:
        Hi_bDecryptRun_byte_403C99 = 0;
        result = (char *)MessageBoxA(0, Caption, Caption, 0);
        break;
    }
  }
  else
  {
    result = (char *)MessageBoxA(0, Caption, Caption, 0);
  }
  return result;
}

由于字符集为'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
且要求为8个字符内,
所有字符都是最大值的"z"得到的校验和为0x3D0=hex(ord('z')*8) ,
而校验和又必须命中操作码,所以只有0x325的情形,即得到key的两个边界条件
a. strlen(key) == 8
b. sum(key) = 0x325

在进入函数Hi_handle_game_director_sub_401B0A中,
会改变Hi_bDecryptRun_byte_403C99增量,
其控制触发线程函数Hi_ThreadFun15_sub_4017F9的解密调用行为
如果是正确的key会有正确的 Hi_keyxor8_byte_403C85,就会解密出
正确的代码调用显示成功信息

.text:00401890 cmp     Hi_bDecryptRun_byte_403C99, 0
.text:00401897 jz      short loc_40190B
.text:00401899 mov     eax, offset Hi_VirtualAlloc_byte_403C0B
.text:0040189E mov     bx, [eax+7]
.text:004018A2 shl     ebx, 10h
.text:004018A5 add     bx, [eax+9]
.text:004018A9 push    PAGE_EXECUTE_READWRITE
.text:004018AB push    1000h
.text:004018B0 push    3Ch
.text:004018B2 push    0
.text:004018B4 call    ebx  //-------------分配解密执行代码存储区
.text:004018B6 lea     ecx, Hi_encCodeChip_dword_40331D
.text:004018BC mov     esi, eax
.text:004018BE lea     ebx, Hi_keyxor8_byte_403C85
.text:004018C4 mov     edx, [ebx]
.text:004018C6 mov     [ebp+var_4_keydwL], edx
.text:004018C9 add     ebx, 4
.text:004018CC mov     edx, [ebx]
.text:004018CE mov     [ebp+var_8_keydwH], edx
.text:004018D1 add     edx, [ebp+var_4_keydwL]
.text:004018D4 cmp     edx, 32113442h  //加密keyxor8的高低字之和
.text:004018DA jz      short loc_4018E1
.text:004018DC jmp     loc_401811
.text:004018E1 loc_4018E1:            
.text:004018E1 or      eax, eax
.text:004018E3 jz      short loc_401904
.text:004018E5 mov     ebx, 0Ah  //对Hi_encCodeChip_dword_40331D十个双字内容解密
.text:004018EA jmp     short loc_4018FD
.text:004018EC loc_4018EC:           
.text:004018EC mov     edx, [ecx]
.text:004018EE add     edx, [ebp+var_8_keydwH] //解密步骤a:字域keyxor高位字相加
.text:004018F1 xor     edx, [ebp+var_4_keydwL] //解密步骤b:相加和异或keyxor低位字
.text:004018F4 mov     [eax], edx
.text:004018F6 add     ecx, 4
.text:004018F9 add     eax, 4
.text:004018FC dec     ebx
.text:004018FD loc_4018FD:            
.text:004018FD cmp     ebx, 0
.text:00401900 ja      short loc_4018EC
.text:00401902 call    esi  //调用解密的代码
.text:00401904 loc_401904:      
.text:00401904 mov     Hi_bDecryptRun_byte_403C99, 0 //

从上述代码我们可知道key的一些边界条件,即
keyxor8[i] = key[i] ^ 0x66 (其中i=0,1,...,7)
keyxor8 = [dwKeyxorLow,dwKeyxorHigh]
dwKeyxorLow+dwKeyxorHigh=0x32113442

解密算法如下
dword Hi_encCodeChip_dword_40331D[0x0A];
dword decCodeChip[0x0F]; //VirtualAlloc(0,0x3C,0x1000,PAGE_EXECUTE_READWRITE)
for i in xrange(0,0x0A):
  decCodeChip[i] = (Hi_encCodeChip_dword_40331D[i]+dwKeyxorHigh) ^ dwKeyxorLow

俄罗斯方块游戏在这里更多十个幌子,不必管。
这里我们结合三个条件进行收敛枚举
xorsum = [0x42,0x34,0x11,0x32]
a. (key[i] ^ 0x66) + (key[i+4] ^ 0x66) = xorsum[i]; 其中i=0,1,2,3
b. sum(key) = 0x325
c. 解密的代码应该存在 retn (0xC3) 或
   "mov eax,offset Hi_MessageBoxA_byte_403B19" (0xB8 0x19 0x3B 0x40 0x00)一类的指令

系数代码中,getpair根据keyxor8高低字对应位之和的特征获取相应位的字符对的取值集合,
#------- ------- ------- ------- ------- ------- -------
keyset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
def getpair(adds = 0x42):
  c0c4 = []
  for c0 in keyset:
    for c4 in keyset:
      cs = ((ord(c0) ^ 0x66) + (ord(c4) ^ 0x66))
      if (cs&0xFF) == adds:
        #if (c0+c4 not in c0c4) and (c4+c0 not in c0c4):
        if c0+c4 not in c0c4:
          print c0,c4,hex(cs)
          c0c4.append(c0+c4)
  return c0c4

p1 = getpair(0x42)
p2 = getpair(0x34)
p3 = getpair(0x11)
p4 = getpair(0x32)

#总共四对集合
#>>> p1
#['bX', 'eY', 'hR', 'iU', 'jP', 'kS', 'mQ', 'pJ', 'qM', 'rH', 'sK', 'tV', 'uI', 'vT', 'wW', 'xB', 'yE', 'Bx', 'DF', 'Ey', 'FD', 'GG', 'Hr', 'Iu', 'Jp', 'Ks', 'Mq', 'Pj', 'Qm', 'Rh', 'Sk', 'Tv', 'Ui', 'Vt', 'Ww', 'Xb', 'Ye']
#>>> p2
#['aK', 'bV', 'cI', 'dT', 'eW', 'fR', 'gU', 'iC', 'jN', 'kA', 'lL', 'mO', 'nJ', 'oM', 'px', 'rF', 'sy', 'tD', 'uG', 'vB', 'wE', 'xp', 'ys', 'Ak', 'Bv', 'Ci', 'Dt', 'Ew', 'Fr', 'Gu', 'Ic', 'Jn', 'Ka', 'Ll', 'Mo', 'Nj', 'Om', 'Rf', 'Td', 'Ug', 'Vb', 'We']
#>>> p3
#['al', 'bk', 'cj', 'di', 'eh', 'fw', 'gv', 'he', 'id', 'jc', 'kb', 'la', 'no', 'on', 'vg', 'wf']
#>>> p4
#['aM', 'bH', 'cK', 'dV', 'eI', 'fT', 'gW', 'hB', 'iE', 'kC', 'lN', 'mA', 'nL', 'oO', 'pz', 'rx', 'tF', 'uy', 'vD', 'wG', 'xr', 'yu', 'zp', 'Am', 'Bh', 'Ck', 'Dv', 'Ei', 'Ft', 'Gw', 'Hb', 'Ie', 'Kc', 'Ln', 'Ma', 'Nl', 'Oo', 'Tf', 'Vd', 'Wg']

ks = []
for cc1 in p1:
  for cc2 in p2:
    for cc3 in p3:
      for cc4 in p4:
        keydwH = (ord(cc4[1])<<0x18)+(ord(cc3[1])<<0x10)+(ord(cc2[1])<<0x08)+ord(cc1[1])
        keydwH = keydwH ^ 0x66666666
        keydwL = (ord(cc4[0])<<0x18)+(ord(cc3[0])<<0x10)+(ord(cc2[0])<<0x08)+ord(cc1[0])
        keydwL = keydwL ^ 0x66666666
        if 0x325 == sum([ord(ch) for ch in [cc4[1],cc3[1],cc2[1],cc1[1],cc4[0],cc3[0],cc2[0],cc1[0]]]):
          ks.append(cc1[0]+cc2[0]+cc3[0]+cc4[0]+cc1[1]+cc2[1]+cc3[1]+cc4[1])

#执行上述代码,我们可得到7264个满足a和b边界条件的key
#上述代码注释
#>>> ks.__len__()
#7264

enccodestr = ""
for i in xrange(0,0x0A*4):
  enccodestr += chr(Byte(0x40331D+i))
#上述IDAPython提取加密的代码片段作为解密输入
enccodestr = 'i\x92\x14\tX\x1aey`\x13\xcc\xd1(4\n,\x0f@\x0b\\\x83\x08K\xf40\x8594\x18@\x0b\xcd\xe9o\xca\x8c\x1f\x0fK\xf4'

#getdeccodestr函数根据不同的key解密enccodestr
import struct
def getdeccodestr(k):
  L,H = struct.unpack("<LL",k)
  L = L ^ 0x66666666
  H = H ^ 0x66666666
  encLs = struct.unpack("<10L",enccodestr)
  decLs = []
  for el in encLs:
    decLs.append(((el+H)^L)&0xFFFFFFFF)
  deccodestr = ""
  for dl in decLs:
    deccodestr+= struct.pack("<L",dl)
  return b"".join(["{:02X} ".format(ord(ch)) for ch in deccodestr])

#我们将不同的key及其解密结果存盘为文件以备搜索
with open(r"C:\CFT\CTF1D\ksc.h","wb") as fout:
  for i in ks:
    fout.write(i+"\t"+getdeccodestr(i)+"\n")

搜索"C:\CFT\CTF1D\ksc.h"文件 C3 (retn)时命中太多,尝试搜着 19 3B 40 00 (offset Hi_MessageBoxA_byte_403B19)
后面是命中的解集,集合缩小了许多,而指令在代码片段开头的只有下述三个,其它都是在C3返回之后,属于其它加密片段的开头

逐个尝试下三个码,得到正确的注册码 KAhuskey

        Line 4753: KAauskly        53 B8 19 3B 40 00 68 8B 58 07 D1 E3 10 66 13 58 09 6A 12 68 B5 32 52 00 68 B5 44 40 00 6A 12 FF D3 5B D3 B8 19 3B 52 00
        Line 4763: KAhuskey        53 B8 19 3B 40 00 66 8B 58 07 C1 E3 10 66 03 58 09 6A 00 68 B5 32 40 00 68 B5 32 40 00 6A 00 FF D3 5B C3 B8 19 3B 40 00
        Line 4765: KAiuskdy        53 B8 19 3B 40 00 68 8B 58 07 C1 E3 10 66 03 58 09 6A 02 68 B5 32 42 00 68 B5 34 40 00 6A 02 FF D3 5B C3 B8 19 3B 42 00

#------- ------- ------- ------- ------- ------- -------
        Line 4443: KahusKey        53 B8 19 3B 40 40 66 8B 58 47 C1 E3 10 66 03 58 09 6A 00 68 B5 32 40 00 68 B5 32 40 00 6A 00 FF D3 9B C3 B8 19 3B 40 00
        Line 4463: KchusIey        53 C4 19 3B 40 4C 66 8B 58 47 C1 E3 10 66 03 58 09 6A 00 68 B5 32 40 00 68 B1 32 40 00 6A 00 FF D3 9B C3 B8 19 3B 40 00
        Line 4483: KihusCey        53 B8 19 3B 40 30 66 8B 58 37 C1 E3 10 56 03 58 09 6A 00 68 B5 22 40 00 68 A5 32 40 00 6A 00 FF D3 9B C3 B8 19 3B 40 00
        Line 4503: KkhusAey        53 B4 19 3B 40 4C 66 8B 58 37 C1 E3 10 56 03 58 09 6A 00 68 B5 22 40 00 68 A1 32 40 00 6A 00 FF D3 9B C3 B8 19 3B 40 00
        Line 4753: KAauskly        53 B8 19 3B 40 00 68 8B 58 07 D1 E3 10 66 13 58 09 6A 12 68 B5 32 52 00 68 B5 44 40 00 6A 12 FF D3 5B D3 B8 19 3B 52 00
        Line 4763: KAhuskey        53 B8 19 3B 40 00 66 8B 58 07 C1 E3 10 66 03 58 09 6A 00 68 B5 32 40 00 68 B5 32 40 00 6A 00 FF D3 5B C3 B8 19 3B 40 00
        Line 4765: KAiuskdy        53 B8 19 3B 40 00 68 8B 58 07 C1 E3 10 66 03 58 09 6A 02 68 B5 32 42 00 68 B5 34 40 00 6A 02 FF D3 5B C3 B8 19 3B 42 00
        Line 4783: KChusiey        53 84 19 3B 40 0C 66 8B 58 07 C1 E3 10 66 03 58 09 6A 00 68 B5 32 40 00 68 B1 32 40 00 6A 00 FF D3 5B C3 B8 19 3B 40 00
        Line 4803: KIhuscey        53 B8 19 3B 40 30 66 8B 58 37 C1 E3 10 16 03 58 09 6A 00 68 B5 22 40 00 68 A5 32 40 00 6A 00 FF D3 5B C3 B8 19 3B 40 00
        Line 4823: KKhusaey        53 B4 19 3B 40 0C 66 8B 58 37 C1 E3 10 16 03 58 09 6A 00 68 B5 22 40 00 68 A1 32 40 00 6A 00 FF D3 5B C3 B8 19 3B 40 00
        Line 5643: SahukKey        43 B8 19 3B 50 40 66 8B 58 47 C1 E3 00 66 03 58 29 6A 00 68 A5 32 40 00 08 B5 32 40 10 6A 00 FF C3 9B C3 B8 19 3B 40 00
        Line 5663: SchukIey        43 C4 19 3B 50 4C 66 8B 58 47 C1 E3 00 66 03 58 29 6A 00 68 A5 32 40 00 08 B1 32 40 10 6A 00 FF C3 9B C3 B8 19 3B 40 00
        Line 5683: SihukCey        43 B8 19 3B 50 30 66 8B 58 37 C1 E3 00 56 03 58 29 6A 00 68 A5 22 40 00 08 A5 32 40 10 6A 00 FF C3 9B C3 B8 19 3B 40 00
        Line 5703: SkhukAey        43 B4 19 3B 50 4C 66 8B 58 37 C1 E3 00 56 03 58 29 6A 00 68 A5 22 40 00 08 A1 32 40 10 6A 00 FF C3 9B C3 B8 19 3B 40 00
        Line 5963: SAhukkey        43 B8 19 3B 50 00 66 8B 58 07 C1 E3 00 66 03 58 29 6A 00 68 A5 32 40 00 08 B5 32 40 10 6A 00 FF C3 5B C3 B8 19 3B 40 00
        Line 5983: SChukiey        43 84 19 3B 50 0C 66 8B 58 07 C1 E3 00 66 03 58 29 6A 00 68 A5 32 40 00 08 B1 32 40 10 6A 00 FF C3 5B C3 B8 19 3B 40 00
        Line 6003: SIhukcey        43 B8 19 3B 50 30 66 8B 58 37 C1 E3 00 16 03 58 29 6A 00 68 A5 22 40 00 08 A5 32 40 10 6A 00 FF C3 5B C3 B8 19 3B 40 00
        Line 6023: SKhukaey        43 B4 19 3B 50 0C 66 8B 58 37 C1 E3 00 16 03 58 29 6A 00 68 A5 22 40 00 08 A1 32 40 10 6A 00 FF C3 5B C3 B8 19 3B 40 00

要分析其俄罗斯方块机制?
下述代码得到"rand()%0x1C"随机数选中的俄罗斯方块

#------- ------- ------- ------- -------
startM = GetString(0x4030ED)
for i in xrange(0,28+1):
  #print '"{}"'.format(startM[i*0x10:i*0x10+0x10])
  print "-------{:02X}-------".format(i)
  printUnit(startM[i*0x10:i*0x10+0x10])
  
def printUnit(UMstr):
  for i in xrange(0,0x10,4):
    print UMstr[i:i+4]
#------- ------- ------- ------- -------
其每四个是一组的,分别代表同一个方块的不同朝向,
这样放置是融合方块旋转函数Hi_ClockCircle_90_sub_401E16而设计。
至于为何断定俄罗斯方块在注册中无大用,那是因为最初的方块是随机产生的
且key8只有一个操作码,就算其能自动启动,
在只能向下,不能操作的情况下,在不久后就会累积慢而"Game Over",
一开始只猜其用来启动第一次尝试注册码检测后,
如果在"Game Over"之前不能输入正确的注册码就会引爆核弹,可惜没有发现核弹的存在。
虚惊一场。

-------00-------
****
   
   
   
-------01-------
*   
*   
*   
*   
-------02-------
****
   
   
   
-------03-------
*   
*   
*   
*   
-------04-------
***
*  
   
   
-------05-------
*  
**  
*  
   
-------06-------
*  
***
   
   
-------07-------
*  
**
*  
   
-------08-------
   
**
**
   
-------09-------
   
**
**
   
-------0A-------
   
**
**
   
-------0B-------
   
**
**
   
-------0C-------
**  
**
   
   
-------0D-------
  *
**
*  
   
-------0E-------
**  
**
   
   
-------0F-------
  *
**
*  
   
-------10-------
**
**  
   
   
-------11-------
*   
**  
*  
   
-------12-------
**
**  
   
   
-------13-------
*   
**  
*  
   
-------14-------
*  
*  
**
   
-------15-------
   
***
*   
   
-------16-------
**  
*  
*  
   
-------17-------
  *
***
   
   
-------18-------
*  
*  
**  
   
-------19-------
   
*   
***
   
-------1A-------
   
**  
*   
*   
-------1B-------
   
***
  *
   
-------1C-------
:%p

pydasm反汇编引擎应用拓展

除了搜索解密的字节码是否有0xC3(retn) 和 (mov eax, offset MessageBoxA)外
另一个想法是通过0x86 32-bit 反汇编引擎 libdasm 来排除一些非法指令从何进一步排除错误解密内容
libdasm反汇编引擎可以用普通的c编译器编译成linux、window等系统的不同应用模块,
以可以编译为python和ruby使用的模块,一开始由于直接用 pip install pydasm 找不到,
而又需要自己动手编译定制,在有限的时间内就放弃了,这里事后补充探索
libdasm核心文件三个:"libdasm.c" "libdasm.h" "opcode_tables.h",
可直接静态或动态链接到main-c文件中集成分析。
这里为python编译安装,下载libdasm-beta.zip解压进入pydasm目录
启动VC的命令行编译控制台,进入pydasm目录,
根据不同vc版本设置 VS90COMNTOOLS 环境变量,python 2.7 编译搜索专用VC2008,我的是vc2010
使用 SET VS90COMNTOOLS=%VS100COMNTOOLS% 这步很重要,以前也遇到过,但凡python 2.7需要自行
编译添加的模块都可或都要这样设置才能编译,否则找不到vcvarsall.bat
//SET VS90COMNTOOLS=%VS110COMNTOOLS%
//SET VS90COMNTOOLS=%VS100COMNTOOLS%
//SET VS90COMNTOOLS=%VS110COMNTOOLS%

可以先通过执行"python setup.py build"编译得到以下结果
LIBDASM-BETA\PYDASM\BUILD
├─lib.win32-2.7
│      pydasm.pyd

└─temp.win32-2.7
    │  libdasm.obj
    │  pydasm.exp
    │  pydasm.lib
    │
    └─Release
            pydasm.obj

接着进行python安装"python setup.py --verbose install",也可以直接执行此命令一步到位编译安装
running install
running build
running build_ext
Importing new compiler from distutils.msvc9compiler
skipping 'pydasm' extension (up-to-date)
running install_lib
copying build\lib.win32-2.7\pydasm.pyd -> %pythondir%\Lib\site-packages
running install_egg_info
Writing %pythondir%\Lib\site-packages\pydasm-1.5-py2.7.egg-info

安装好python的pydasm模块后,我们就可以稍稍改进,如下
#------- ------- ------- ------- ------- ------- -------
import pydasm
#dasmbytes(getdeccodestr('KAhuskey'))
def dasmbytes(data):
  dasmstr = "{"
  offset = 0
  while offset < len(data):
    i = pydasm.get_instruction(data[offset:], pydasm.MODE_32)
    if i == None:
      #raise Exception("Not-support instruction")
      return "{Not-support instruction}"
    #print i,pydasm.get_instruction_string(i, pydasm.FORMAT_INTEL, 0+offset)
    dasmstr = dasmstr + "\n" + pydasm.get_instruction_string(i, pydasm.FORMAT_INTEL, 0+offset)
    offset += i.length
  dasmstr += "\n}"
  return dasmstr

with open(r"C:\CFT\CTF1D\kbd.h","wb") as fout:
  for i in ks:
    print i
    bd = getdeccodestr(i)
    fout.write(i+"\t"+bd[0]+"\n"+dasmbytes(bd[1])+"\n")

def getdeccodestr(k):
  L,H = struct.unpack("<LL",k)
  L = L ^ 0x66666666
  H = H ^ 0x66666666
  encLs = struct.unpack("<10L",enccodestr)
  decLs = []
  for el in encLs:
    decLs.append(((el+H)^L)&0xFFFFFFFF)
  deccodestr = ""
  for dl in decLs:
    deccodestr+= struct.pack("<L",dl)
  return [b"".join(["{:02X} ".format(ord(ch)) for ch in deccodestr]),deccodestr]
#------- ------- ------- ------- ------- ------- -------
这样kbd.h文件就会在签名ksc.h文件的基础上添加现有的字节码反编译结果,举例如下
对于有非法指令的情况(即解密错误),标记着 "{Not-support instruction}"
而其它一些错误解密则没那么直观,如下面eacuYKjy解密得到的错误字节码反编译的结果,
其包含许多特殊指令,如果遇到大面积的情况,需要专门统计收集这些视为解密错误的特殊指令集,
以进一步进行排除,这里仅出于对libdasm反汇编引擎的性能测试,用作解题方案则非好选择。

eabyYKku        AB B8 25 03 94 40 76 93 9C 47 DD FB 64 66 13 20 4D 6A 1C 70 C1 32 5C 18 6C B5 42 58 54 6A 1C FF 2B 9A D3 80 5D 3B 5C 18
{Not-support instruction}
由libdasm对错误解密的字节码反汇编结果
eacuYKjy        AB B8 25 3B 94 40 74 8B 9C 47 DD E3 64 66 13 58 4D 6A 12 68 C1 32 52 00 6C B5 40 40 54 6A 12 FF 2B 9A D3 B8 5D 3B 52 00
{
stosd
mov eax,0x40943b25
jz 0xffffff93
pushf
inc edi
fucom st(3),st(0)
adc bx,fs:[eax+0x4d]
push byte 0x12
push dword 0x5232c1
insb
mov ch,0x40
inc eax
push esp
push byte 0x12
jmpf [ebx]
callf 0x52:0x3b5db8d3
}
由libdasm对正确解密的字节码反汇编结果
KAhuskey        53 B8 19 3B 40 00 66 8B 58 07 C1 E3 10 66 03 58 09 6A 00 68 B5 32 40 00 68 B5 32 40 00 6A 00 FF D3 5B C3 B8 19 3B 40 00
{
push ebx
mov eax,0x403b19
mov bx,[eax+0x7]
shl ebx,0x10
add bx,[eax+0x9]
push byte 0x0
push dword 0x4032b5
push dword 0x4032b5
push byte 0x0
call ebx
pop ebx
ret
mov eax,0x403b19
}

libdasm反汇编引擎附件: libdasm-beta.zip

上传的附件:
收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 9
活跃值: (175)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
2
膜拜,很扎实的分析!
2016-12-31 16:23
0
游客
登录 | 注册 方可回帖
返回
//