首页
社区
课程
招聘
[原创]看雪 2016 CTF 第七题 数学之美
发表于: 2016-11-17 18:38 5501

[原创]看雪 2016 CTF 第七题 数学之美

HHHso 活跃值
22
2016-11-17 18:38
5501

拿到手分析得到第一手信息时,本想着能第一个攻破它,可惜初心还是没守住,连末班车都没赶上。
而最关键的时掉进了自己挖的坑里,骄兵必败不无道理,有时候我们不成功原因并非技术,而是心态。

这里从完成的数据逻辑着手提出解决方案,非爆破的多解,做这题的时候估摸到了作者的学历,你信吗?

直白的IDA反汇编代码逻辑有些绕,估计代码使用了"P.J. Plauger STL"的原因。
下面是"P.J.Plauger" 的版权声明。
0042C010  43 6F 70 79 72 69 67 68 74 20 28 63 29 20 62 79  Copyright (c) by
0042C020  20 50 2E 4A 2E 20 50 6C 61 75 67 65 72 2C 20 6C   P.J. Plauger, l
0042C030  69 63 65 6E 73 65 64 20 62 79 20 44 69 6E 6B 75  icensed by Dinku
0042C040  6D 77 61 72 65 2C 20 4C 74 64 2E 20 41 4C 4C 20  mware, Ltd. ALL
0042C050  52 49 47 48 54 53 20 52 45 53 45 52 56 45 44 2E  RIGHTS RESERVED.

一开始在代码的线求里找不到北,断 ReadFile,输入注册码回车断下,跟踪调用栈得到突破口。
当然,程序做了反调试,需要先处理反调试,反调试线程比较耗CPU资源。
OD加载跑起来后,切换到线程窗口,直接将CPU耗时最多的两个线程挂起后再下断调试。
如下线程窗口状态数据,"User time"或"System time"值较大的两个线程为反调试线程,右键Suspend挂起即可。

Threads
Ident      Entry      Data block   Last error          Status      Priority   User time     System time
00000A28   7C810729   7FFDD000     ERROR_SUCCESS (000  Active       32 + 0       0.0000 s      0.8281 s
00002138   7C810729   7FFDE000     ERROR_SUCCESS (000  Active       32 + 0       0.0000 s      0.0000 s
000025B0   00408681   7FFDF000     ERROR_SUCCESS (000  Active       32 + 0       0.0156 s      0.0625 s
000025DC   7C810729   7FFDB000     ERROR_SUCCESS (000  Active       32 + 0      25.8593 s      5.1718 s
0000277C   7C810729   7FFDA000     ERROR_SUCCESS (000  Active       32 + 0      32.3437 s      1.9687 s

输入注册码回车后端在ReadFile读入控制台缓冲区内容时断下,
其调用栈位于函数 00402F0A call    Hi_read_input_sub_403DC4 内,在其内分别对部分函数分析重命名
00404602 call    Hi_getObjIO_sub_4043D4            获取IO对象
00404615 call    Hi_setEAXNeg1_sub_40155           设置EAX=-1,
00404625 call    Hi_IsEQ_sub_40154D                比对两指针指向值是否相等,如IO输入为-1表示缓冲区内容读取完毕
0040463E call    Hi_pChar_to_Char_sub_40153E       从指针到其所指向值,相当于 *pchar
0040464D call    Hi_IsPunctuationEct_sub_401A90           ctype判断
00404679 call    Hi_Cin_readChar_sub_40491F        从缓冲区读入

Hi_IsPunctuationEct_sub_401A90 的入口参数 ecx.ThisPtr 指针的虚拟函数表位于 420268,运行时信息为 ?AV?$ctype@D@std@
参考ctype的操作,可以确定 输入参数 0x48h 的含义,为 _CN 和 _PU 的组合,遇到这些字符则表示有效输入结束。
/* _Ctype 转换位 */
#define _XA        0x200        /* extra alphabetic */
#define _XS        0x100        /* extra space */
#define _BB        0x80        /* BEL, BS, etc. */
#define _CN        0x40        /* CR, FF, HT, NL, VT */
#define _DI        0x20        /* '0' - '9' */
#define _LO        0x10        /* 'a' - 'z' */
#define _PU        0x08        /* punctuation */
#define _SP        0x04        /* space */
#define _UP        0x02        /* 'A' - 'Z' */
#define _XD        0x01        /* '0' - '9', 'A' - 'F', 'a' - 'f' */
.text:00404648 push    48h
.text:0040464A mov     ecx, [ebp-2Ch]  ; vft 420268 ctype
.text:0040464D call    Hi_IsPunctuationEct_sub_401A90 ;
.text:0040464D                         ; _CN        0x40        /* CR, FF, HT, NL, VT */
.text:0040464D                         ; _PU        0x08        /* punctuation */

Hi_read_input_sub_403DC4 溯源至 Hi_tlsMainFun_sub_402EEF 函数,该函数为主要校验逻辑。
在调用分发计算函数Hi_mainDispatchCalculator_sub_40284B 前后通过调用 Hi_rdtsc_sub_402668 获取CPU计数差进一步检测反调试
经过 在调用分发计算函数Hi_mainDispatchCalculator_sub_40284B 分散计算校验和后 进入 Hi_FinalCheck_sub_402B93 做最后校验。

另,溯源 Hi_tlsMainFun_sub_402EEF 得到宿主函数 Hi_ThreadFunc1_sub_402F5B 为线程启动函数。
Hi_ThreadFunc1_sub_402F5B 其在 Hi_StartMainThreadAndAntiDebugThread_sub_403048 通过 CreateThread 启动,
在 Hi_StartMainThreadAndAntiDebugThread_sub_403048 内后续还有两个 CreateThread 启动的反调试线程,
即反调试也可以直接nop掉该函数体的后两处 CreateThread 调用
"40306D call esi"和"40307E call esi"实现(也应nop掉相应的输入参数维持堆栈平衡)

.text:00402EEF ; =============== S U B R O U T I N E =======================================
.text:00402EEF Hi_tlsMainFun_sub_402EEF proc near      ; DATA XREF: Hi_ThreadFunc1_sub_402F5B+52o
.text:00402EEF                 push    1Ch
.text:00402EF1                 mov     eax, offset sub_41F3C0
.text:00402EF6                 call    Hi__SEH_prolog_sub_4086EB
.text:00402EFB                 lea     ecx, [ebp-28h]
.text:00402EFE                 call    sub_4033C3
.text:00402F03                 and     dword ptr [ebp-4], 0
.text:00402F07                 lea     edx, [ebp-28h]  ; ebp.-28h: //----------获取输入的注册码,存放于局部变量ebp.-28h处,为basic_string类型
.text:00402F0A                 call    Hi_read_input_sub_403DC4
.text:00402F0F                 call    Hi_rdtsc_sub_402668
.text:00402F14                 mov     esi, eax
.text:00402F16                 mov     edi, edx
.text:00402F18                 lea     eax, [ebp-28h]
.text:00402F1B                 push    eax
.text:00402F1C                 call    Hi_mainDispatchCalculator_sub_40284B
.text:00402F21                 call    Hi_rdtsc_sub_402668
.text:00402F26                 sub     eax, esi
.text:00402F28                 sbb     edx, edi
.text:00402F2A                 cmp     edx, 2
.text:00402F2D                 jb      short loc_402F40
.text:00402F2F                 ja      short loc_402F38
.text:00402F31                 cmp     eax, 540BE400h
.text:00402F36                 jbe     short loc_402F40
.text:00402F38
.text:00402F38 loc_402F38:                             ; CODE XREF: Hi_tlsMainFun_sub_402EEF+40j
.text:00402F38                                         ; Hi_tlsMainFun_sub_402EEF+58j
.text:00402F38                 push    0               ; uExitCode
.text:00402F3A                 call    ds:ExitProcess
.text:00402F40 ; ------------------------------------------------
.text:00402F40
.text:00402F40 loc_402F40:                             ; CODE XREF: Hi_tlsMainFun_sub_402EEF+3Ej
.text:00402F40                                         ; Hi_tlsMainFun_sub_402EEF+47j
.text:00402F40                 call    Hi_FinalCheck_sub_402B93
.text:00402F45                 test    al, al
.text:00402F47                 jz      short loc_402F38
.text:00402F49                 or      dword ptr [ebp-4], 0FFFFFFFFh
.text:00402F4D                 lea     ecx, [ebp-28h]
.text:00402F50                 call    sub_4032E8
.text:00402F55                 call    Hi___SEH_epilog_sub_4086A6
.text:00402F5A                 retn
.text:00402F5A Hi_tlsMainFun_sub_402EEF endp
.text:00402F5A ; ---------------------------------------------------------------------------

Hi_mainDispatchCalculator_sub_40284B 函数检测注册码长度是否为 0x55 和第9位置字符为"F",
并设置全局变量Hi_bValid_LenFflag_byte_13BEE70用于在 Hi_FinalCheck_sub_402B93 中检测。
402876 mov     Hi_bValid_LenFflag_byte_13BEE70, al

即Hi_bValid_LenFflag_byte_13BEE70 = ((strlen(keystr) == 0x55) and keystr[9]=='F')?  True:False

Hi_mainDispatchCalculator_sub_40284B根据注册注册码keystr分发计算算法过程如下

Hi_IncTickLeftIndexFrom0x000_dword_42E210  = 0x000
Hi_DecTickRightIndexFrom0x3F1_dword_42E214 = 0x3F1
Hi_LRTickCount_dword_42E21C = 0
Hi_TickCount_dword_42E218 = 0
Hi_OddR_EvenLFlag_switch_dword_42E1FC = 1
Hi_SumBox01Switch_dword_42E1F8 = 0
qword Hi_SumBox01_qword_42E200[2] = {0,0}
foreach ch in keystr:
  ch_int = ch_to_int(ch){"0~9" >> 0~9; "A~Z" >> 10~35;"a~z" >> 36~61) 将字符转换为相应的数值}
  Hi_LRTickCount_dword_42E21C += ch_int //累加和需要小于 0x3F2 即 1010
  while (0!=ch_int--){
    Hi_SumBox01_DispatchCalculator_sub_402769(){
      if 0 != Hi_OddR_EvenLFlag_switch_dword_42E1FC{
        Hi_SumBox01_qword_42E200[Hi_SumBox01Switch_dword_42E1F8] += \
          Hi_MA_wwArry0x3F2_42E1F4[Hi_DecTickRightIndexFrom0x3F1_dword_42E214--]
      }else{
        Hi_SumBox01_qword_42E200[Hi_SumBox01Switch_dword_42E1F8] += \
          Hi_MA_wwArry0x3F2_42E1F4[Hi_IncTickLeftIndexFrom0x000_dword_42E210++]
      }
      Hi_TickCount_dword_42E218 ++
      Hi_SumBox01Switch_dword_42E1F8 = Hi_SumBox01Switch_dword_42E1F8? 0:1
    }
  }
  Hi_OddR_EvenLFlag_switch_dword_42E1FC = Hi_OddR_EvenLFlag_switch_dword_42E1FC? 0:1
//---------------
while(Hi_IncTickLeftIndexFrom0x000_dword_42E210 <= Hi_DecTickRightIndexFrom0x3F1_dword_42E214){
  Hi_SumBox01_DispatchCalculator_sub_402769(参考上述描述)
}
  
根据keystr分发计算的自然语言简单描述为:
从数组Hi_MA_wwArry0x3F2_42E1F4[0...0x3F1]的右边或左边选出ch_int个元素,
再将ch_int个元素交替累加到 Hi_SumBox01_qword_42E200[0] 和 Hi_SumBox01_qword_42E200[1]中

其中从数组的右边选择还是左边选择由 Hi_OddR_EvenLFlag_switch_dword_42E1FC 交替控制,
实际对于keystr中各字节的奇偶次序(这里定义keystr[0]的次序为1,为奇)
即keystr的第1,3,5,7,9,...会分别从数组右边选出keystr[i]对应的cn_int个元素分发累加,
而keystr的第2,4,6,8,...会分别从数组左边选出keystr[i]对应的cn_int个元素分发累加,

至于累加到 Hi_SumBox01_qword_42E200[0],还是 Hi_SumBox01_qword_42E200[1],
则有 Hi_SumBox01Switch_dword_42E1F8 决定,也是交替分发
由于 Hi_SumBox01Switch_dword_42E1F8 每次累加加上时都会交替,所以
无论keystr如何变化,累加到 Hi_SumBox01_qword_42E200[0],还是 Hi_SumBox01_qword_42E200[1]的个数都是
数组Hi_MA_wwArry0x3F2_42E1F4 元素个数的一半 0x3F2/2 = 505
由 keystr 决定分发的元素个数为 Hi_LRTickCount_dword_42E21C = sum([cn_int of keystr[i]])

在 Hi_FinalCheck_sub_402B93 函数的 Hi_checkTick_sub_402930 过程中,
要求 0x3F2 - Hi_LRTickCount_dword_42E21C = 0x29,即
Hi_LRTickCount_dword_42E21C = 0x3F2 - 0x29 = 960 (剩下的0x29个元素最后做交替分发累加),
当最后的0x29个元素也分发完之后,会有
Hi_IncTickLeftIndexFrom0x000_dword_42E210(506) - Hi_DecTickRightIndexFrom0x3F1_dword_42E214(505) = 1
因为两个全局量都是累加后递增或递减,
即实际由左边分发累加的元素个数为 Hi_MA_wwArry0x3F2_42E1F4[i], i = 0~505,
即实际由右边分发累加的元素个数为 Hi_MA_wwArry0x3F2_42E1F4[i], i = 1009~506,

只要确定Hi_MA_wwArry0x3F2_42E1F4中分别累加到 Hi_SumBox01_qword_42E200[0],还是 Hi_SumBox01_qword_42E200[1]的
元素,即可以逆推得到keystr的分布,但要确定下述两个累加和在 Hi_MA_wwArry0x3F2_42E1F4 数组的原始分布,谈何容易。
不过存在即合理,这里我们先尝试确定两个累加和是多少。
Hi_SumBox01_qword_42E200[0]  新定义为 SumBox0
Hi_SumBox01_qword_42E200[1]  新定义为 SumBox1

在 Hi_FinalCheck_sub_402B93函数的Hi_check_TickSum_SubEQ_sub_40295F和Hi_check_TickSum_XorEQ_sub_402999可以得到
SumBox1 - SumBox0 - c + a == 0
SumBox0 ^ SumBox1 ^ c ^ a == 0

其中
a = 0x000000FEF635D82A          //为全局变量 Hi_a_qword_42E220
b = 0x000001F9CEB9C18E          //为中间变量
c = 0x000000FAD883E964 #= b - a //为全局变量 Hi_c_qword_42E228
上述"方程组"化简下,
SumBox0 - SumBox1 = a - c
SumBox0 ^ SumBox1 = a ^ c

这种带异或的方程组真不好解,有没代数解也个人也没研究。
不过在瞎折腾一阵后,灵光一闪可以直接得到一组直观解,
观察上述等式SumBox0,SumBox1和a,c的位置,令
SumBox0 = a
SumBox1 = c
即可得到一组解,于是有 SumBox0 = a = 0x000000FEF635D82A; SumBox1 = c = 0x000000FAD883E964

我们再溯源下 Hi_c_qword_42E228 和 Hi_a_qword_42E220 的由来,
通过溯源,可知道a,c是在 Hi_ExpandM1M2_3F2x3F2_sub_402C34 函数内赋值的
Hi_ExpandM1M2_3F2x3F2_sub_402C34 调用于 运行时初始化函数 Hi_runtime_init_sub_4026CD

在运行时初始化函数Hi_runtime_init_sub_4026CD 中,
先初始化 Hi_MA_wwArry0x3F2_42E1F4 数组,再根据该数组扩散初始化下述两个二维数组(矩阵)
qword M1[0x3F2][0x3F2] //全局变量首地址 0x42E230 cbSize:0x7C8620 = 0x3F2*0x3F2*sizeof(qword:8)
qword M2[0x3F2][0x3F2] //全局变量首地址 0xBF6850 cbSize:0x7C8620 = 0x3F2*0x3F2*sizeof(qwrod:8) 紧跟M1之后
将 Hi_MA_wwArry0x3F2_42E1F4 新定义为  dword MA[0x3F2] //全局变量 cbSize:0xFC8 = 0x3F2*sizeof(dword:4)

MA一维矩阵可以通过以下python脚本产生
#----------------------------------------------------------
MA = [0x6C35B49D,0xA645500D,0xCB9E682E] #= DwordArray[0x3F2]
muladd = c_ulong(0)
for edx in xrange(0x0C,0xFC8,4):
  edxIndex = edx/4
  MA.append(0)
  muladd.value = MA[edxIndex-2]*MA[edxIndex-1]
  muladd.value = muladd.value + MA[edxIndex-3]
  MA[edxIndex] = muladd.value
#----------------------------------------------------------

M1和M2的初始化函数比较特殊,其实际是取值函数(操作符M[x,y]),只是取值函数
在取未初始化的坐标(x,y)的元素时,会自动迭代进行初始计算。
Hi_getM1_x_y_sub_402ABA
Hi_getM2_x_y_sub_402A3F
两个初始化功能的矩阵初始化取值函数算法如下

Hi_getM1_x_y_sub_402ABA(x:0,y:0x3F1,M1){#ecx obj{.04hww:MA}
  if x > y:
    return 0
  if M1[x,y] == -1:
    if x == y:
      return M1[x,y] = MA[x]
    else:
      qw1 = iter.Hi_getM1_x_y_sub_402ABA(x+1,y,M1) #位于其下部的元素值 M1[x+1,y]
      qw2 = iter.Hi_getM1_x_y_sub_402ABA(x,y-1,M1) #位于其左边的元素值 M1[x,y-1]
      if qw2 >= qw1:
        qw3_min = iter.Hi_getM1_x_y_sub_402ABA(x+1,y,M1) #位于其下部的元素值 M1[x+1,y]
      else:
        qw3_min = iter.Hi_getM1_x_y_sub_402ABA(x,y-1,M1) #位于其左边的元素值 M1[x,y-1]
      -------
      qw4 = Hi_getM2_x_y_sub_402A3F(x,y,M2) #矩阵M2[x,y]
      return M1[x,y] = qw4 - qw3_min
  else:
    return M1[x,y]
}

Hi_getM2_x_y_sub_402A3F(x:0,y:0x3F1,M2){#ecx obj{.04hww:MA}
  if x > y:
    return 0
  if M2[x,y] == -1:
    if x == y:
      return M2[x,y] = MA[x]
    else:
      qw = iter.Hi_getM2_x_y_sub_402A3F(x+1,y,M2) #位于其下部的元素值 M1[x+1,y]
      return M2[x,y] = (qw + MA[x]))
  else:
    return M2[x,y]
}

M1和M2的所有元素在Hi_ExpandM1M2_3F2x3F2_sub_402C34的一开始被初始化为-1,作为无效数据
当取值函数所取坐标值为无效数据-1时,将通过上面的算法进行迭代累加计算。

Hi_ExpandM1M2_3F2x3F2_sub_402C34
.text:00402C34 Hi_ExpandM1M2_3F2x3F2_sub_402C34 proc near
.text:00402C34 push    ebx
.text:00402C35 push    esi
.text:00402C36 push    edi
.text:00402C37 mov     esi, 7C8620h
.text:00402C3C mov     edi, offset Hi_M1_3F2x3F2_qwArray_42E230
.text:00402C41 push    esi
.text:00402C42 push    0FFFFFFFFh
.text:00402C44 push    edi
.text:00402C45 call    Hi_memset_P1memAddr_P2Val_P3cbsize_sub_40B2D0
.text:00402C4A push    esi
.text:00402C4B push    0FFFFFFFFh
.text:00402C4D mov     ebx, offset Hi_M2_3F2x3F2_qwArray_BF6850
.text:00402C52 push    ebx
.text:00402C53 call    Hi_memset_P1memAddr_P2Val_P3cbsize_sub_40B2D0
.text:00402C58 add     esp, 18h

M1和M2矩阵进一步分析,通过Hi_getM1_x_y_sub_402ABA 和 Hi_getM2_x_y_sub_402A3F我们知道
M1[x,y] = M2[x,y] - min(M1[x+1,y],M1[x,y-1]); (其中x<y)
M2[x,y] = MA[x] + M2[x+1,y]; (其中x<y)
而对于x==y,都有 M1[x,y] = MA[x],M2[x,y] = MA[x]
进一步化简M2的关系,得到
M2[x,y] = MA[x] + MA[x+1] + ... + MA[y] = MA[x:y]

(1)M1[x,y] = M2[x,y] - min(M1[x+1,y],M1[x,y-1]); (其中x<y)
        = MA[x:y] - min(M1[x+1,y],M1[x,y-1])
特别地,
若(1)min(M1[x+1,y],M1[x,y-1]) = M1[x+1,y],则有
(1.1)M1[x,y]= MA[x:y] - M1(x+1,y)
            = MA[x:y] - (MA[x+1:y] - min(M1[x+2,y],M1[x+1,y-1]))
            = (MA[x:y] - MA[x+1:y]) + min(M1[x+2,y],M1[x+1,y-1]))
            = MA[x] + min(M1[x+2,y],M1[x+1,y-1]))
同理,若若(1)min(M1[x+1,y],M1[x,y-1]) = M1[x,y-1],则有
(1.2)M1[x,y]= MA[x:y] - M1(x,y-1)
            = MA[x:y] - (MA[x:y-1] - min(M1[x+1,y-1],M1[x,y-2]))
            = (MA[x:y] - MA[x:y-1]) + min(M1[x+1,y-1],M1[x,y-2])
            = M[y] + min(M1[x+1,y-1],M1[x,y-2])

由于x<y,即M[x],M[y]分别代表数组MA左侧累加和右侧累加的展开过程,

如何展开?
M1[0,0x3F1] = Hi_getM1_x_y_sub_402ABA(0,0x3F1,M1)
M2[0,0x3F1] = Hi_get_M2_x_y_sub_402A3F(0,0x3F1,M2)
在 内完成上述取值后,在 402C86 断下,通过下属OD脚本dump出M1和M2两个矩阵数据
dm 42E230,0f90c40,"C:\M1M2_3F2x3F2_qw.Matrix"

通过下述python脚本,我们可以得到M1,M2两个矩阵的直观输出文件M1_3F2x3F2.h和M2_3F2x3F2
#----------------------------------------------------------
import struct
with open(r"C:\M1M2_3F2x3F2_qw.Matrix",'rb') as fin:
  m1str = fin.read(0x3F2*0x3F2*8)
  m2str = fin.read(0x3F2*0x3F2*8)
  m1 = struct.unpack("<{}Q".format(0x3F2*0x3F2),m1str)
  m2 = struct.unpack("<{}Q".format(0x3F2*0x3F2),m2str)
  with open(r"C:\M1_3F2x3F2.h",'wb') as fout:  
    for mi in xrange(0,0x3F2):
      for mj in xrange(0,0x3F2):
        fout.write("{:016X} ".format(m1[mi*0x3F2+mj]))
      fout.write("\n")
    fout.write("\n")
  with open(r"D:\M2_3F2x3F2.h",'wb') as fout:  
    for mi in xrange(0,0x3F2):
      for mj in xrange(0,0x3F2):
        fout.write("{:016X} ".format(m2[mi*0x3F2+mj]))
      fout.write("\n")
    fout.write("\n")
#----------------------------------------------------------

M1_3F2x3F2.h 中 M1二维矩阵四个角落元素示意
000000006C35B49D 00000000A645500D 0000000137D41CCB ... ... ... 000000FE3744C38D 000000FAD883E964 000000FEF635D82A
FFFFFFFFFFFFFFFF 00000000A645500D 00000000CB9E682E ... ... ... 000000FA760BB9B7 000000FE3744C38D 000000FB2B3F4964
FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 00000000CB9E682E ... ... ... 000000FDD95C0489 000000FA6C4E34C7 000000FE5C9DDBAE
                      ...                                                              ...
                      ...                              ...                             ...
                      ...                                                              ...
FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF ... ... ... 000000008341D725 000000008341D725 00000001387566D2
FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF ... ... ... FFFFFFFFFFFFFFFF 0000000079845235 00000000BEF1149D
FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF ... ... ... FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 00000000BEF1149D

//---------------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------
M2_3F2x3F2.h 中 M2二维矩阵四个角落元素示意
FFFFFFFFFFFFFFFF 00000001127B04AA 00000001DE196CD8 ... ... ... 000001F896445ABC 000001F90FC8ACF1 000001F9CEB9C18E
FFFFFFFFFFFFFFFF 00000000A645500D 0000000171E3B83B ... ... ... 000001F82A0EA61F 000001F8A392F854 000001F962840CF1
FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 00000000CB9E682E ... ... ... 000001F783C95612 000001F7FD4DA847 000001F8BC3EBCE4
                      ...                                                              ...
                      ...                              ...                             ...
                      ...                                                              ...
FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF ... ... ... 000000008341D725 00000000FCC6295A 00000001BBB73DF7
FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF ... ... ... FFFFFFFFFFFFFFFF 0000000079845235 00000001387566D2
FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF ... ... ... FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 00000000BEF1149D

在 Hi_ExpandM1M2_3F2x3F2_sub_402C34 中
a = M1[0,0x3F1] = Hi_getM1_x_y_sub_402ABA(0,0x3F1,M1) = 000000FEF635D82A
b = M2[0,0x3F1] = Hi_get_M2_x_y_sub_402A3F(0,0x3F1,M2)
c = b - a ,即 a = b - c
又因为
M1[0,0x3F1] = M2[0,0x3F1] - min(M1[0+1,0x3F1],M1[0,0x3F1-1])
            = M2[0,0x3F1] - M1[0,0x3F0]
            (如上矩阵所示 M1[0,0x3F0] = 000000FAD883E964 小于 M1[1,0x3F1] = 000000FB2B3F4964)
            (其中 M1[0,0x3F1] 为 a,M2[0,0x3F1]为b,而 a = b - c)
所以 c = M1[0,0x3F0] = 000000FAD883E964
总结一下即
SumBox0 = a = M1[0,0x3F1] = 000000FEF635D82A
SumBox1 = c = M1[0,0x3F0] = 000000FAD883E964

SumBox0和SumBox1的迭代路径展开,以下python是以左优先的跌停展开经过的M1路径
所谓左优先,在 M1[x,y] = M2[x,y] - min(M1[x+1,y],M1[x,y-1]); 中,
当M1[x,y]的左边元素与其地下元素相等时,就存在分叉的跌停路径,
左优先,定义在出现分叉路径时选择左边元素进行展开
(也正因为有相等的情形,左优先和下优先之间会存在N条有效的迭代路径)
#----------------------------------------------------------
def iterroad(x,y,irxy = []):
  global M1
  irxy.append([x,y])
  if x == y:
    return irxy
  else:
    qw_xydown = M1(x+1,y)
    qw_xyleft = M1(x,y-1)
    if qw_xydown == qw_xyleft:
      #return iterroad(x+1,y,irxy) #下优先展开
      return iterroad(x,y-1,irxy)  #左优先展开
    elif qw_xydown == min(qw_xydown,qw_xyleft):
      return iterroad(x+1,y,irxy)
    else:
      return iterroad(x,y-1,irxy)

SumBox0_iterRoad = iterroad(0,0x3F1,[])
SumBox1_iterRoad = iterroad(0,0x3F0,[])
#----------------------------------------------------------

由迭代路径SumBox0_iterRoad 和 SumBox1_iterRoad 进一步化简处理
得到只包含 MA 数组索引的叠加索引集合
#----------------------------------------------------------
def getsxis(sxir,x,y):
  global M1
  sxis = []
  sxirlen = sxir.__len__()
  iterend = 0
  bodd = sxirlen % 2
  if bodd:
     iterend = sxirlen - 1 # ;a,b;c
  else:
     iterend = sxirlen - 2 # ;a,b;c
  for i in xrange(0,iterend,2):
    irxy = sxir[i]
    irdl = sxir[i+1]
    if (irxy[0] != irdl[0]) and (irxy[1] != irdl[1]):
      raise Exception("!x!y index: {}".format(i))
    if irxy[0] == irdl[0]:
      sxis.append(irxy[1])
    else:
      sxis.append(irxy[0])
    if (sum([MA[j] for j in sxis])+M1(sxir[i+2][0],sxir[i+2][1])) != M1(x,y):
      print sxir[i],sxir[i+1],"[{},...,{}] + {} ***xxx***".format(sxis[0],sxis[sxis.__len__()-1],sxir[i+2])
      for i in sxir[i:i + 0x10]:
        print "{:03X},{:03X}".format(i[0],i[1])
      raise Exception("No Match, i={}".format(i))
    else:
      print sxir[i],sxir[i+1],"[{},...,{}] + {}".format(sxis[0],sxis[sxis.__len__()-1],sxir[i+2])
  #
  if bodd:
    if sxir[sxirlen-1][0] == sxir[sxirlen-1][1]:
      sxis.append(sxir[sxirlen-1][0])
    else:
      if MA[sxir[sxirlen-1][0]] < MA[sxir[sxirlen-1][1]]:
        sxis.append(sxir[sxirlen-1][1])
      else:
        sxis.append(sxir[sxirlen-1][0])
  else:
    if sxir[sxirlen-2][0] == sxir[sxirlen-1][0]:
      sxis.append(sxir[sxirlen-2][1])
    else:
      sxis.append(sxir[sxirlen-2][0])
  return sxis

SumBox0_MA_indexes = getsxis(SumBox0_iterRoad,0,0x3F1)
SumBox1_MA_indexes = getsxis(SumBox1_iterRoad,0,0x3F0)
#----------------------------------------------------------

SumBox0(或a) 是 SumBox0_MA_indexes 中的所有505个索引值对数组MA进行索引的元素累加和
1009 1007 0001 0003 1005 1003 1001 0004
0006 0008 0010 0998 0996 0994 0012 0014
0016 0018 0020 0022 0024 0026 0028 0030
0032 0034 0036 0038 0040 0042 0992 0990
...
0619 0617 0615 0613 0611 0609 0607 0605
0603

SumBox1(或c) 是 SumBox1_MA_indexes 中的所有505个索引值对数组MA进行索引的元素累加和
1008 0000 0002 1006 1004 1002 1000 0005
0007 0009 0999 0997 0995 0011 0013 0015
0017 0019 0021 0023 0025 0027 0029 0031
0033 0035 0037 0039 0041 0993 0991 0043
...
0618 0616 0614 0612 0610 0608 0606 0604
0602

如何确定分发的切换?实际上累加的过程都是交替进行的,
我们将位置稍微错位下,即可得到所需结果,
大的索引是从右边分发的(keystr奇数次序cn_int),从1009开始递减
小的索引是从左边分发的(keystr偶数次序cn_int),从0开始递增
举例如下
1009 1007 0001 0003 1005 1003 1001 0004 0006 0008 0010 0998 0996 0994 0012 0014
  1008 0000 0002 1006 1004 1002 1000 0005 0007 0009 0999 0997 0995 0011 0013 0015

从上面可知,先从右边取3个(索引分别是1009,1008,1007)进行分发累加,
然后接着从左边去4个(索引分别是0000,0001,0002,0003)进行分发累加,
再接着右边取(1006,1005,1004,1003,1002,1001,1000)七个进行分发累加,
即keystr的ch_int依次为 3,4,7,...
我们通过下面的python脚本自动化处理下得到完整的ch_int序列ch_ints
#----------------------------------------------------------
def getsrlis(slis,sris):
  srlis = []
  firsM = slis
  sndM = sris
  if slis[0] < sris[0]:
    firsM = sris
    sndM = slis
  for i in xrange(0,505):
    srlis.append(firsM[i])
    srlis.append(sndM[i])
  return srlis

def get_ch_ints(srlis):
  ch_int = []
  scnt = 1
  bDec = True
  for i in xrange(1,srlis.__len__()):
    if bDec:
      if srlis[i] == srlis[i-1] - 1:
        scnt = scnt + 1
      else:
        bDec = False
        ch_int.append(scnt)
        scnt = 1
    else:
      if srlis[i] == srlis[i-1] + 1:
        scnt = scnt + 1
      else:
        bDec = True
        ch_int.append(scnt)
        scnt = 1
  ch_int.append(scnt)
  return ch_int

SumBox01_MA_switchIndex = getsrlis(SumBox0_MA_indexes,SumBox1_MA_indexes)
ch_ints = get_ch_ints(SumBox01_MA_switchIndex)
#----------------------------------------------------------

于是我们得到keystr的ch_int序列
ch_ints = [3, 4, 7, 7, 6, 32, 4, 8, 12, 6, 49, 5,
2, 2, 16, 22, 6, 22, 2, 8, 2, 2, 12, 8,
2, 8, 2, 2, 2, 2, 1, 15, 2, 40, 18, 18,
2, 10, 30, 16, 44, 42, 2, 6, 4, 58, 2,
8, 4, 18, 12, 18, 47, 1, 2, 4, 2, 22,
2, 6, 2, 16, 2, 2, 6, 28, 2, 18, 38,
52, 6, 10, 6, 50, 4, 4, 2, 2, 39]

但还需修整,满足以下边界条件
(1)ch_ints数组累加和Hi_LRTickCount_dword_42E21C只能为 0x3F2-0x29 = 1010 - 41 = 969;
(2)ch_ints[9] = 15 (对应keystr char 为 "F")
(3)ch_ints数组长度为0x55 = 85

对于(1),直接忽略掉ch_ints最后两个值(2,39;其中 2 + 39 = 41),即可满足该边界条件
对应(2)和(3)的修正只需考虑两条原则即可实现任意拆解、合并以及增加
原则一:拆解、合并及增减不能改变任意ch_int前所有ch_int之和的奇偶性,
    如 ch_ints[4]=6 之前所有元素之和为3+4+7+7为奇数,
    即对前面元素的拆分、合并已经增减不能改变其前面所有元素之和为奇数的属性,
    否则会致使ch_ints[4]选取的元素进行错误的累加
原则二:拆解、合并要保持次序先后的奇偶性不变,增减在保持和不变的情况下,只能在相同奇偶属性之间进行。

简言之,
增减:在奇(或偶)次序ch_ints元素上增(或减)2*n,则必须在其它ch_ints奇(或偶)次序上减(或增)2*n;
拆分:由于拆分后的次序的奇偶性要不变,拆分会导致成对出现,如简单清晰 ...a,b...要拆分a,则需要同时拆分b辅助
   ...a,b... >> ...,a1,b1,a2,b2.... 其中 a=a1+a2,b=b1+b2
合并即拆分的逆操作,可见拆分与合并都会导致2*n的元素增加和减少

废话少说,
                         ch_ints[9]
   |     |     |      |      |      |
3, 4, 7, 7, 6, 32, 4, 8, 12, 6, 49, 5
由于(2)要求ch_ints[9]为15,增加只能是偶数,
可以考虑将前面元素拆分将使得奇数7后移到ch_ints[9],但数值不够;
只能考虑合并前面的元素将后面的奇数5前移再进行增加
如下,
               --------
            -------
   |     |     |      |      |      |
3, 4, 7, 7, 6, 32, 4, 8, 12, 6, 49, 5
我们将6,4和32,8分别合并得到
3, 4, 7, 7, 10, 40, 12, 6, 49, 5, 2, 2, 16, 22, 6, 22, 2, 8, 2, 2, 12, 8,
然后我们再将5后面22减去10加到5身上得到
3, 4, 7, 7, 10, 40, 12, 6, 49, 15, 2, 2, 16, 12, 6, 22, 2, 8, 2, 2, 12, 8,
即我们得到了一个满足(1)和(2)边界条件的ch_ints,如下
ch_ints =[3, 4, 7, 7, 10, 40, 12, 6, 49, 15,
2, 2, 16, 12, 6, 22, 2, 8, 2, 2, 12, 8,
2, 8, 2, 2, 2, 2, 1, 15, 2, 40, 18, 18,
2, 10, 30, 16, 44, 42, 2, 6, 4, 58, 2,
8, 4, 18, 12, 18, 47, 1, 2, 4, 2, 22,
2, 6, 2, 16, 2, 2, 6, 28, 2, 18, 38,
52, 6, 10, 6, 50, 4, 4, 2]
其长度为75,要达到85,还需要增加10个元素,由于每次拆分都会新增2*n个元素,则只需查分五次即可
这里我们将上述上述ch_ints[12]和ch_ints[13]的 16,12进行拆分,直接拆分五次
设e = 16 = e1+e2+e3+e4+e5+e6,f = 12 = f1+f2+f3+f4+f5+f6,即查分为
e1.2,f1.2;e2.2,f2.2;e3.2,f3.2;e4.2,f4.2;e5.2,f5.2;e6.6,f6.2
即(16,12)最终拆分为(2,2,2,2,2,2,2,2,2,2,6,2)其中(6.2)可以任意转换位置,如(6,2,2,2,2,2,2,2,2,2,2,2)
于是我们得到进一步满足边界(3)的keystr的 ch_ints序列
ch_ints = [3,4,7,7,10,40,12,6,49,15,2,2,
  2,2,2,2,2,2,2,2,2,2,6,2, #16,12,
  6, 22, 2, 8, 2, 2, 12, 8,
2, 8, 2, 2, 2, 2, 1, 15, 2, 40, 18, 18,
2, 10, 30, 16, 44, 42, 2, 6, 4, 58, 2,
8, 4, 18, 12, 18, 47, 1, 2, 4, 2, 22,
2, 6, 2, 16, 2, 2, 6, 28, 2, 18, 38,
52, 6, 10, 6, 50, 4, 4, 2]

#----------------------------------------------------------
def getKeyFrom_chints(ch_ints):
  i2adict = {}
  for i in xrange(1,10):
    i2adict[i] = chr(0x30+i)
  for i in xrange(10,35+1):
    i2adict[i] = chr(0x37 + i)
  for i in xrange(36,61+1):
    i2adict[i] = chr(0x3D + i)
  keystr = b"".join([i2adict[i] for i in ch_ints])
  return keystr

keystr = getKeyFrom_chints(ch_ints)
print keystr
得到 3477AeC6nF222222222222626M2822C82822221F2eII2AUGig264w284ICIl1242M262G226S2Icq6A6o442
只有根据增加属性,就可以得到任意个注册码,如
                         | | 加四减四
     3477AeC6nF222222222262226M2822C82822221F2eII2AUGig264w284ICIl1242M262G226S2Icq6A6o442
      
                       | | 加二减二
     3477AeC6nF222222224242226M2822C82822221F2eII2AUGig264w284ICIl1242M262G226S2Icq6A6o442


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 2
支持
分享
最新回复 (3)
雪    币: 47147
活跃值: (20450)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
分析的很到位,厉害!
2016-11-17 19:05
0
雪    币: 346
活跃值: (45)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
算法玩死人 !在理解算法思路中对数学功底不高的人就是一个很耗时的过程。  看到牛人们解提的思路学到了很多东西!
2016-11-18 09:04
0
雪    币: 996
活跃值: (1443)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
有样本么 发个样本过过瘾
2016-11-19 01:20
0
游客
登录 | 注册 方可回帖
返回
//