首页
社区
课程
招聘
[原创]看雪 2016 CTF 第十五题 七星阵
发表于: 2016-12-1 22:06 5826

[原创]看雪 2016 CTF 第十五题 七星阵

HHHso 活跃值
22
2016-12-1 22:06
5826

七星阵
跑起,还没干任何事,CPU 100%,机器假死,我没法叫醒假死的电脑,就跟没法叫醒装睡的人一样。
有点恶作剧的嫌疑。
强制关机重启,40KB 样例,小样本,直上IDA静态分析。

从程序的入口点线头扯起 0040569E start proc near,以下为其中的C++运行时向量初始化
    .text:00405759 push    offset Hi_xc_z_40800C
    .text:0040575E push    offset Hi_xc_a_408000
    .text:00405763 call    _initterm
   
C++运行时初始化向量表:
.data:00408000 Hi_xc_a_408000  dd 0                    ;
.data:00408004                 dd offset Hi_Afx_Init_sub_405883
.data:00408008                 dd offset Hi_CWinApp_Init_sub_4031A0
.data:0040800C Hi_xc_z_40800C  dd 0                    ;

注意到C++运行时向量初始化函数 Hi_CWinApp_Init_sub_4031A0,
其中 Hi_CWinApp_ctor_sub_4031B0 为CWinApp构造函数,Hi_CWinApp_dtor_sub_4031D0 为析构函数。
.text:004031A0 call    Hi_CWinApp_ctor_sub_4031B0 ; Microsoft VisualC 2-11/net runtime
.text:004031A5 jmp     loc_4031C0
...
.text:004031C0 loc_4031C0:            
.text:004031C0 push    offset Hi_CWinApp_dtor_sub_4031D0
.text:004031C5 call    _atexit
.text:004031CA pop     ecx
.text:004031CB retn
   
跟进构造函数 Hi_CWinApp_ctor_sub_4031B0,
其主要完成全局静态变量 static CWinApp Hi_CWinApp_dword_4081C0 的初始化及启动带反调试的校验线程。
核心的数据结构架构简化如下
Hi_CWinApp_dword_4081C0{
  {hThread1,hThread2,hThread3,hThread6,hThread7}
  Thread123Param,Thread67Param
  CheckResult
  kData{ykey;ekey1...ekey7;Thread45Param;hThread4,hThread5}
}
hThread1...hThread7为七个启动的带反调试的校验线程句柄
Thread123Param,Thread45Param,Thread67Param为每个校验线程的参数
CheckResult为校验结果状态
kData为核心数据,其中
  ykey为程序样例镜像文件偏移0x48开始的大小为0x50的特征数据,来自 MapView.48h,cbSize:4*0x14=0x50
  ekey1...ekey7为输入的xkey与ykey经过奇偶穿插排列和分段xor异或加密后分割成的七个初步加密key
  ekey1...ekey7会被相应的校验线程进一步产生最终的比对ckey用来和内部cmpkey比对
  比对结果分别存于 CheckResult 的stepResult[7]数组中。
  其中线程 hThread7 带有最终对 CheckResult 的检测,如果通过,则
  调用 Hi_set_success_info_sub_404220 设置显示成功信息。

程序启动过程
a.在 Hi_CWinApp_ctor_sub_4031B0 中,完成
五个检验线程的启动 hThread1,hThread2,hThread3,hThread4,hThread5
    Hi_Thread1_cmpStep1_sub_4033D0(Thread123Param)
    Hi_Thread2_cmpStep2_sub_403530(Thread123Param)
    Hi_Thread3_cmpStep3_sub_403680(Thread123Param)
    Hi_StartThread_4_5_sub_405070}{
      Hi_Thread4_cmpStep6_sub_4046D0(Thread45Param)
      Hi_Thread5_cmpStep7_sub_404830(Thread45Param)
    }
b.在 Hi_CWinApp_ctor_sub_4031B0 我们注意到 CWinApp 的虚拟函数表 Hi_vft_CWinapp_off_4062D0
    ...
    .rdata:00406328                 dd offset Hi_CWinApp_Init_sub_4031E0
    .rdata:0040632C                 dd offset CWinApp::Run(void)
    ...
其中虚表 CWinApp::Run成员函数上定义一个函数 Hi_CWinApp_Init_sub_4031E0 是我们关系的,
根据Window的MFC机制,它会在上述运行时环境初始化完成后被执行。
在 Hi_CWinApp_Init_sub_4031E0 中
    b.1 00403249 call Hi_CDialog0x66_ctor_sub_403870  //注册对话框初始化
    b.2 0040326E call Hi_StartThread_6_7_sub_4043B0   //启动6,7号校验线程
        Hi_Thread6_cmpStep4_sub_403C20(Thread67Param)
        Hi_Thread7_cmpStep5_sub_403DB0(Thread67Param)
    b.3 00403277 call    CDialog::DoModal(void)       //显示注册对话框

至此,整个程序就跑起来,七个校验线程会不停地使用ekey1...ekey7数据进行校验比对。

以下是上面所提及的数据结构对象略细化结构
Hi_WinApp_dword_4081C0{
  .C4hww.ThreadXParam = new(0x0C){
    .00hww = .DChww.CheckResult
    .04hww = CWinApp.thisPtr
    .08hww = .D8hww.kData
  }
  .C8hww = hThread1
  .CChww = hThread2
  .D0hww = hThread3
   Must exists
  .D8hww.kData
  .DChww.CheckResult
  .170hww.Thread67Param = new(0x0C){
    .00hww = CheckResult
    .04hww = CWinApp.thisPtr
    .08hww = kData
  }
  .174hww = hThread6
  .178hww = hThread7
  
}

CheckResult{ cbSize:0x28
  Hi_vft_off_406290
  .04h.stepResult cbSize: 4*7 //dword stepResult[7];
  .24hww
}

kData{ cbSize:0xA8C
  vft: Hi_vft_off_406748
  InitializeCriticalSection(4082A0)
  .04h  cbSize:0x104 = GetModuleFileNameA
  .108h.ykey cbSize:0x104 = MapView.48h,cbSize:4*0x14=0x50
  .20Ch cbSize:0x104 = MapView.80h,cbSize:4*0x14=0x50
  .310h cbSize: 0x71C = 4*0x1C7
    .000h.expKey1
    .104h.expKey2
    .208h.expKey3
    .30Ch.expKey4
    .410h.expKey5
    .514h.expKey6
    .618h.expKey7
  .A2Ch cbSize:0x40=4*0x10
  .A6Chww -1 = CreateFileA(GetModuleFileNameA,80000000,1,0,3,0x80,0)
  .A70hww -1 = CreateFileMappingA(.A6Chww,0,2,0,0,0)
  .A74hww = hThread4
  .A78hww = hThread5
  .A80hww = MapViewOfFile(.A70hww,4,0,0,0)
  .A84hww = CWinApp.Ptr
  .A88hww.Thread45Param = new(8){
    .00hww = CheckResult
    .04hww = kData
  }
}

如上所述,程序启动过程触发的主要业务逻辑函数如下
1. Hi_CWinApp_ctor_sub_4031B0 全局App变量构造函数
2. Hi_CWinApp_Init_sub_4031E0 全局App变量初始化函数
3. 七个校验线程函数

兵来将挡水来土掩,逐步清除其反调试机制
1. Hi_CWinApp_ctor_sub_4031B0 中
(1.1)00403071 call Hi_NtSetInformationThread_anti_sub_402410,通过NtSetInformationThread让线程脱离调试器的束缚。
     直接将函数体修改为"xor eax,eax; retn"即可,即直接将函数首地址402410处对应的镜像文件三个字节机器码修改为 "33 C0 C3"
(1.2)04030AE call Hi_rdtsc_anti_ifCheckDbg_BusyCPU_sub_402450,其在此处的作用不大,
  只有当调试器在该函数的"40245B rdtsc"和"40248F rdtsc"之间停下才会有可能触发检测,这个可能就是停留超过2*0x100000000时钟周期。
  触发的结果是跑死CPU,因为其后续代码"402532 shr ecx, 1Fh"会致使进入死循环,
  其内部调用的 Hi_rdtsc_anti_BusyCPU_exit_sub_402570 也类似, ExitProcess不会有机会被执行的。
  该函数的存在,本来只有在死循环的时候才会耗尽CPU,但由于也放置在七个线程的循环中,虽不进入其内部的死循环,
  也相当于在各个线程循环rdtsc的片段耗尽CPU时间。
  和(1.1)的处理方式一样,将函数首地址402450与402570(这个可以不处理)处对应的镜像文件三个字节机器码修改为 "33 C0 C3"
2. Hi_CWinApp_Init_sub_4031E0 中
    .text:00403209 push    offset ProcName ; "NtSetInformationThread"
    .text:0040320E push    offset ModuleName ; "NTDLL"
    .text:00403213 call    ds:GetModuleHandleA
    .text:00403219 push    eax             ; hModule
    .text:0040321A call    ds:GetProcAddress
    .text:00403220 mov     ecx, [esi+0DCh]
    .text:00403226 mov     edi, eax
    .text:00403228 call    Hi_rdtsc_anti_ifCheckDbg_BusyCPU_sub_402450
    .text:0040322D push    0
    .text:0040322F push    0
    .text:00403231 push    11h
    .text:00403233 call    ds:GetCurrentThread
    .text:00403239 push    eax
    .text:0040323A call    edi
   上述代码片段是NtSetInformationThread反调试,直接将镜像文件对应的0x00403209至0040323A字节全部nop掉即可,即全部替换为0x90(nop指令)
3. 各线程的反调式机制相同,以Hi_Thread1_cmpStep1_sub_4033D0为例,在函数的永动循环体中,
(3.1)0040340E call Hi_rdtsc_anti_ifCheckDbg_BusyCPU_sub_402450 在(1.2)已经处理,不必理会
(3.2)00403415 call Hi_anti_od1_sub_402DE0 是检测OllyDbg 1.1 的特征
  函数遍历所有进程,并尝试读取进程地址0x404000处的0x20个特征字节,如果匹配上即发现Ollydbg 1.x。
  这个反调试在 NT 6.0 win7 之后可能会因为Ollydbg基址的随机性而失效。
  Ollydbg.IDbytes = "55F033C9894B04890B6A00E840D1FFFF59E983FDFFFF837DFC00750E837DF800"
  和(1.1)的处理方式一样,将函数首地址 402DE0 处对应的镜像文件三个字节机器码修改为 "33 C0 C3"
(3.3)00403455 call Hi_anti_ifNotDoubleClickStartFromExplorer_Progman_sub_402280 检测父进程甄别程序启动方式
  双击启动的程序父进程为explorer.exe,如果是调试器启动的,其父进程就不是explorer.exe,以此来弱检测。(cmd.exe启动呢?)
  和(1.1)的处理方式一样,将函数首地址 402280 处对应的镜像文件三个字节机器码修改为 "33 C0 C3"
(3.4)00403475 call Hi_anti_SeDebugPrivilege_with_csrss_sub_402150 通过检测SeDebugPrivilege权限来检测是否被调试
  程序默认不会有SeDebugPrivilege权限,调试后,进程继承调试器的SeDebugPrivilege权限,而权限的有无通过能否打开csrss.exe进程来判断
  和(1.1)的处理方式一样,将函数首地址 402150 处对应的镜像文件三个字节机器码修改为 "33 C0 C3"
(3.5)00403495 call Hi_anti_peb_NtGlobalFlag_sub_402390 显然,这是诊断PEB.NtGlobalFlag的
  和(1.1)的处理方式一样,将函数首地址 402390 处对应的镜像文件三个字节机器码修改为 "33 C0 C3"
(3.6)004034B5 call Hi_NtSetInformationThread_anti_sub_402410 在(1.1)已经处理,不必理会
(3.7)004034BC call Hi_anti_HardBreakPoint_sub_4023A0 检测线程的四个硬件断点寄存器反调试
  Context.Dr0 Context.Dr1 Context.Dr2 Context.Dr3
  和(1.1)的处理方式一样,将函数首地址 402390 处对应的镜像文件三个字节机器码修改为 "33 C0 C3"
(3.8)004034F2 call Hi_anti_driver_pipe_sub_4027C0 检测管道驱动的不同进程监控程序和调试器。
  和(1.1)的处理方式一样,将函数首地址 4027C0 处对应的镜像文件三个字节机器码修改为 "33 C0 C3"

经过1,2,3反-反调试的处理后,镜像想怎么调戏就怎么调戏了。
由于镜像的各节被动了手脚,只有.rsrc,reloc节,OD里直接修改保存是没法实现的。
这时候CFF Explorer可以牛刀小试。

4. 注册对话框CDialog的消息响应函数 Hi_check1_TriggerWhenInputKeyChange_sub_403F80 会在每次key发生变化时触发调用。
    该函数为校验线程提供初步的注册信息。
(4.1)获取用户输入的xkey
.text:00403FC5 lea     eax, [esp+1CCh+var_1C4_cstrKey]    // 获取用户输入的xkey
.text:00403FC9 lea     ecx, [ebp+0E0h] ; this
.text:00403FCF push    eax             ; struct CString *
.text:00403FD0 mov     byte ptr [esp+1D0h+var_4], 1
.text:00403FD8 call    CWnd::GetWindowTextA(CString &)
.text:00403FDD lea     ecx, [esp+1CCh+var_1C0_cstrTitle]  //获取提示信息,此信息可用于解密成功的提示信息,此处用上
.text:00403FE1 push    ecx             ; struct CString *
.text:00403FE2 lea     ecx, [ebp+60h]  ; this
.text:00403FE5 call    CWnd::GetWindowTextA(CString &)

在00404001 cmp     ebx, 26h 检测输入的xkey长度不应该超过0x26
(4.2)将xkey与kData.ykey进行奇偶穿插式洗牌操作,下述函数的输入参数为xkey和ykey(参考前面,为镜像文件特定位置特征字节)
004040D7 call    Hi_odd_even_switch_sub_404440
Hi_odd_even_switch_sub_404440函数的python实现参考后续odd_even_switch代码
(4.3)在完成奇偶穿插洗牌后得到xykey,虽然进行分段异或xor加密,其python实现参考后续 splitxor 代码
(4.4)经过(4.3)的splitxor过程得到ekey,最后以功能号参数0调用 004041CE call Hi_set_check_sub_405240,
Hi_set_check_sub_405240也被线程校验函数Hi_odd_even_xor_cmpCheck_sub_404A40以不懂功能号调用实现检测
在这里,功能号0参数,表示将ekey信息分段存放于kdata的ekey1...ekey7七个信息存储区,每个信息大学是0x0B字节

5.经过(4.x)的注册码信息准备逻辑,七个检验线程就会分别进行校验,
    Hi_Thread1_cmpStep1_sub_4033D0(Thread123Param)
    Hi_Thread2_cmpStep2_sub_403530(Thread123Param)
    Hi_Thread3_cmpStep3_sub_403680(Thread123Param)
    Hi_Thread4_cmpStep6_sub_4046D0(Thread67Param)
    Hi_Thread5_cmpStep7_sub_404830(Thread67Param)
    Hi_Thread6_cmpStep4_sub_403C20(Thread45Param)
    Hi_Thread7_cmpStep5_sub_403DB0(Thread45Param)
相应线性的名称cmpStepX或使用(4.x)中准备好的ekeyX进行运算,并最终以相应的功能号X调用Hi_set_check_sub_405240进行比对。
(5.1)以X=1为例,在线程 Hi_Thread1_cmpStep1_sub_4033D0 中
    .text:0040341A mov     eax, [ebx+8]              //eax = Thread123Param.kdata  获取线程参数传递的kdata
    .text:0040341D lea     ecx, [esp+20h+var_10]
    .text:00403421 add     eax, 310h                 //eax = kdata.ekey1  在这里是ekey1,在其它线性为其它 ekeyx
    .text:00403426 push    eax
    .text:00403427 call    CString::operator=(char const *)
    .text:0040342C push    104h            ; unsigned int
    .text:00403431 call    operator new(uint)
    .text:00403436 add     esp, 4
    .text:00403439 lea     ecx, [esp+20h+var_10] ; this
    .text:0040343D mov     ebp, eax                  //为要拷贝的ekey1准备新存储空间
    .text:0040343F push    0               ; int
    .text:00403441 call    CString::GetBuffer(int)
    .text:00403446 mov     ecx, 11h
    .text:0040344B mov     esi, eax
    .text:0040344D mov     edi, ebp
    .text:0040344F rep movsd                        //拷贝,至此ebp为 ekey1的一份拷贝数据
    .text:00403451 movsw
(5.2)
    .text:004034C3 push    1               ; StepX   //校验哪一部分信息,七个校验线程分别校验不同七个部分,这里是step1
    .text:004034E1 push    1               ; OpCode  //操作码,决定是否对ekeyX进行处理并校验,1,8,114h等都表示执行转换并校验,
                                                     //其它以下操作码,保留,如7,315h等不执行任何操作,部分由反调试触发,
                                                     //不校验,则不会有成功注册出现。
    .text:004034E3 push    ebp             ; ekeyX   //(4.x)由输入的注册码xkey加工得到的分段信息,这里是ekey1
    .text:004034E4 call    Hi_odd_even_xor_cmpCheck_sub_404A4

(5.3)在 Hi_odd_even_xor_cmpCheck_sub_404A4 中,
(5.3.1)先将ekeyX和Hi_poem_408168经过 Hi_odd_even_switch_sub_405100 奇偶穿插洗牌一次得到 oe_keyX (与上面的三次不同,这里只一次)
    .text:00404A8A push    offset Hi_poem_408168 ;
    .text:00404A8A                         ; 大江东去,浪淘尽,千古风流人物,故垒西边,人道是那三国周郎赤壁②...!Error
    .text:00404A8F rep stosd
    .text:00404A91 push    edx             ; ekeyX
    .text:00404A92 mov     ecx, esi
    .text:00404A94 xor     ebp, ebp
    .text:00404A96 call    Hi_odd_even_switch_sub_405100
(5.3.2)然后进行 splitxor 运算得到cmpKeyX
(5.3.3)最后由StepX功能号调用 00405010 call Hi_set_check_sub_405240,在(4.4)中提及。
该函数对于功能号StepX=1,2,3,4,5,6,7会进入最终的校验比对函数 00405379 call Hi_check2_cmpCheck_sub_401060
(5.3.4)在 Hi_check2_cmpCheck_sub_401060 中就会根据StepX的不同比对相应的cmpKeyX

6.于是逆向得到xkey的思路就是
从 cmpKeyX 经过 splitxor可逆运算解密 oe_keyX
从 oe_keyX 反洗牌提取 ekeyX
从所有ekeyX组合得到 ekey
从 ekey 经过 splitxor可逆运算解密 xykey
从 xykey 反洗牌提取 xkey

(6.1)从Hi_check2_cmpCheck_sub_401060 中提取到的cmpKeyX
直接断在00402004处,然后从栈空间dump出这些字节即可
后面的函数中。
hstr2bs 函数会自动将这些数据转为二进制数组
bs2hstr 执行相反的功能,
splitxor 为我们的分段可逆异或加解密实现

cmpKey4 = """
50 2F 66 76 4C 71 E4 BA BC C4 A1 AD DF 4D C6 A4
86 96 FE DA 8C F5 FB 84 19 FE F5 F2 F2 C8 CF E9
FC D5 EF EF 1B 14 4F 51 6F 67 75 81 88 83 FD 19
FE E1 94 F2 8C 82 96 9F 99 7B 9D 8A A0 88 9F E2
9D 93 9F EE 81 97 E4 E6 F5 79 79 57 57 00 00 50
"""
cmpKey1 = """
43 63 20 62 64 18 E4 BA BC C4 A1 AD DF 4D C6 A4
86 96 FE DA 8C F5 FB 84 19 FE F5 F2 F2 C8 CF E9
FC D5 EF EF 1B 14 4C 23 02 4C 06 81 88 83 FD 19
FE E1 94 F2 8C 82 96 9F 99 7B 9D 8A A0 88 9F E2
9D 93 9F EE 81 97 E4 E6 F5 79 79 57 57 00 00 00
"""
cmpKey5 = """
2F 75 4A 4D 02 02 E4 BA BC C4 A1 AD DF 4D C6 A4
86 96 FE DA 8C F5 FB 84 19 FE F5 F2 F2 C8 CF E9
FC D5 EF EF 1B 14 49 75 72 6E 0A 81 88 83 FD 19
FE E1 94 F2 8C 82 96 9F 99 7B 9D 8A A0 88 9F E2
9D 93 9F EE 81 97 E4 E6 F5 79 79 57 57 00 00 00
"""
cmpKey3 = """
50 02 0C 56 03 4C E4 BA BC C4 A1 AD DF 4D C6 A4
86 96 FE DA 8C F5 FB 84 19 FE F5 F2 F2 C8 CF E9
FC D5 EF EF 1B 14 61 44 32 59 6F 81 88 83 FD 19
FE E1 94 F2 8C 82 96 9F 99 7B 9D 8A A0 88 9F E2
9D 93 9F EE 81 97 E4 E6 F5 79 79 57 57 FF F0 00
"""
cmpKey6 = """
25 29 34 2D 25 F8 E4 BA BC C4 A1 AD DF 4D C6 A4
86 96 FE DA 8C F5 FB 84 19 FE F5 F2 F2 C8 CF E9
FC D5 EF EF 1B 14 43 03 42 46 0E 81 88 83 FD 19
FE E1 94 F2 8C 82 96 9F 99 7B 9D 8A A0 88 9F E2
9D 93 9F EE 81 97 E4 E6 F5 79 79 57 57 00 00 00
"""
cmpKey2 = """
19 06 13 13 47 EF E4 BA BC C4 A1 AD DF 4D C6 A4
86 96 FE DA 8C F5 FB 84 19 FE F5 F2 F2 C8 CF E9
FC D5 EF EF 1B 14 18 37 74 32 6D 81 88 83 FD 19
FE E1 94 F2 8C 82 96 9F 99 7B 9D 8A A0 88 9F E2
9D 93 9F EE 81 97 E4 E6 F5 79 79 57 57 00 00 00
"""
cmpKey7 = """
33 60 2F 40 40 40 E4 BA BC C4 A1 AD DF 4D C6 A4
86 96 FE DA 8C F5 FB 84 19 FE F5 F2 F2 C8 CF E9
FC D5 EF EF 1B 14 0F 0B 62 62 62 81 88 83 FD 19
FE E1 94 F2 8C 82 96 9F 99 7B 9D 8A A0 88 9F E2
9D 93 9F EE 81 97 E4 E6 F5 79 79 57 57 00 00 00
"""

def hstr2bs(hstr = None):
  hstr = hstr.replace("\n"," ")
  hstr = hstr[1:hstr.__len__()-1]
  #print hstr
  bs = [int(hch,0x10) for hch in hstr.split(" ")]
  return bs

def bs2hstr(bs):
  for i in xrange(0,bs.__len__()):
    print "{:02X}".format(bs[i]),
    if (i+1) % 16 == 0:
      print ""
  print ""

def splitxor(ikey = None):
  okey = [i for i in xrange(0,0x50)]
  okey[0x4e] = 0
  okey[0x4f] = 0
  edx = 0;ebp = 0;
  while True:
    edi = 0
    while True:
      esi = 0
      while True:
        eax = 0
        while True:
          idv = None
          idx = ((edi+ebp)*3+esi)*3+eax
          #print edx,ebp,esi,eax,idx
          if edx < 9:
            idv = ikey[idx] ^ 0x17
          elif edx < 0x12:
            idv = ikey[idx] ^ 0x61
          elif edx < 0x36:
            idv = ikey[idx] ^ 0x35
          elif edx <= 0x4d:
            idv = ikey[idx] ^ 0x57
          else:
            return okey
            #idv = 0
            #okey[idx] = idv
            break
          okey[idx] = idv
          edx += 1
          eax += 1
          if eax >= 3:
            break
        esi += 1
        if esi >= 3:
          break
      edi += 1
      if edi >= 3:
        break
    ebp += 3
    if ebp >= 9:
      break
  return okey

(6.2)解密得到 oe_keyX , 从 oe_keyX 我们来个大跃进,直接得到 ekey
因为各个oe_keyX都跟Hi_poem_408168洗牌,这里我们用清除 Hi_poem_408168的方式
观察下清理 Hi_poem_408168后的结果。
oe_key1 = splitxor(hstr2bs(cmpKey1))
oe_key2 = splitxor(hstr2bs(cmpKey2))
oe_key3 = splitxor(hstr2bs(cmpKey3))
oe_key4 = splitxor(hstr2bs(cmpKey4))
oe_key5 = splitxor(hstr2bs(cmpKey5))
oe_key6 = splitxor(hstr2bs(cmpKey6))
oe_key7 = splitxor(hstr2bs(cmpKey7))

ta = []
for i in xrange(0,oe_key1.__len__()):
  oe_key1i = oe_key1[i]
  bcom = True
  for axi in [oe_key2[i],oe_key3[i],oe_key4[i],oe_key5[i],oe_key6[i],oe_key7[i]]:
    if axi != oe_key1i:
      bcom = False
      break
  if bcom:
    ta.append(0)
  else:
    ta.append(oe_key1i)

bs2hstr(ta)
54 74 37 75 73 0F 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 79 16 37 79 33 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

即每个oe_keyX提取出的ekeyX顺序如下(奇偶错位开来)
54 74 37 75 73 0F
79 16 37 79 33
>> 54 79 74 16 37 37 75 79 73 33 0F

于是我们可以直接从oe_key1...oe_key7直接提取到ekey
ekey = []
for a in [oe_key1,oe_key2,oe_key3,oe_key4,oe_key5,oe_key6,oe_key7]:
  for i in [0,0+0x26,1,1+0x26,2,2+0x26,3,3+0x26,4,4+0x26,5]:
     ekey.append(a[i])

(6.3)有ekey解密得到xykey
ekey.append(0) #配合splitxor的0x4d长度
xykey = splitxor(ekey)
xykey 如下
43 6E 63 01 | 20 20 62 6E | 64 52 6E 6F | 4C 70 63 65 | Cnc.  bndRnoLpce
20 65 32 65 | 6D CD 72 61 | 20 44 2E 32 | 74 59 21 6F |  e2em.ra D.2tY!o
6E 72 4F 0D | 51 44 6F 54 | 67 6E 75 53 | 0D 49 57 75 | nrO.QDoTgnuS.IWu
68 72 6F 6E | 20 0A 42 65 | 21 69 61 74 | 20 6D 24 65 | hron .Be!iat m$e
6C B8 73 6D | 20 69 6F 00 | 00 00 00 00 | 00 57 00 00 | l.sm io......W..

(6.4)从xykey提取xkey.
ykey取值如下,这里只取参与运算的有效值,参与计算部分会在0x00处截断。有效长度为0x31。
21 B8 01 4C CD 21 54 68 69 73 20 70 72 6F 67 72  !?L?This progr
61 6D 20 63 61 6E 6E 6F 74 20 62 65 20 72 75 6E  am cannot be run
20 69 6E 20 44 4F 53 20 6D 6F 64 65 2E 0D 0D 0A   in DOS mode....
24 00                                            $.

根据字符串的截断特征,xykey的有效长度为0x47。
所以xkey的长度为 0x47 - 0x31 = 22。这里
#odd_even_switch为正向洗牌操作,这里用不上,dxkey为其反操作
def odd_even_switch(xkey,ykey):
  xykey = xkey+ykey
  switchCnt = 3
  oebytes = xykey
  while switchCnt != 0:
    oddbytes = []
    evenbytes = []
    for i in xrange(0,oebytes.__len__()):
      if i % 2:
        oddbytes.append(xykey[i])
      else:
        evenbytes.append(xykey[i])
    oebytes = oddbytes + evenbytes
    switchCnt -= 1

def dxkey(xykey,xykey_valid_len):
  mi = xykey_valid_len/2+1
  xkey = xykey
  cnt = 3
  while cnt != 0:
    m_xkey = []
    for i in xrange(0,mi):
      m_xkey.append(xkey[i])
      m_xkey.append(xkey[i+mi])
    xkey = m_xkey
    cnt -= 1
  return xkey

xkey_ykey = dxkey(xykey,0x47)
43 52 32 32 | 51 49 42 65 | 6E 6E 65 74 | 44 57 65 6C | CR22QIBennetDWel
63 6F 6D 59 | 6F 75 21 B8 | 01 4C CD 21 | 54 68 69 73 | comYou!..L.!This
20 70 72 6F | 67 72 61 6D | 20 63 61 6E | 6E 6F 74 20 |  program cannot
62 65 20 72 | 75 6E 20 69 | 6E 20 44 4F | 53 20 6D 6F | be run in DOS mo
64 65 2E 0D | 0D 0A 24 00 | 31 00 0A 00 | AD 00 08 03 | de....$.1.......

>>> b"".join([chr(xkey_ykey[i]) for i in xrange(0,22)])
'CR22QIBennetDWelcomYou'
即我们得到xkey = CR22QIBennetDWelcomYou

(6.5)More?
(6.5.1)在最终校验Hi_check2_cmpCheck_sub_401060函数中,
如果匹配成功,各线程会设置线程参数的CheckResult.stepResult[7]
  .04hww 1 step1
  .08hww 2 step2
  .0Chww 3 step3
  .10hww 4 step4
  .14hww 5 step5
  .18hww 6 step6
  .1Chww 7 step7

(6.5.2)在检验线程Hi_Thread7_cmpStep5_sub_403DB0函数中,
除了匹配stepX校验外,其还会检测 CheckResult.stepResult 的状态
如果都匹配,则会调用下述函数,设置成功信息
00403F4B call Hi_set_success_info_sub_404220
其主要将加密的Hi_success_byte_408144[0x24]成功信息解密出来设置到static控件中。
#"看雪CrackMe攻防大赛 2016"
tipstr = """
BF B4 D1 A9 43 72 61 63 6B 4D 65 B9 A5 B7 C0 B4
F3 C8 FC 20 32 30 31 36
"""
#Hi_success_byte_408144[0x24]
sucstr = """
FC DB BF CE 31 13 15 16 07 2C 11 D0 CA D9 EC 94
A0 AD 92 44 12 49 5E 43 71 30 71 37 3D 3E 26 34
23 0F 0E 0F
"""
btipstr = hstr2bs(tipstr)
bsucstr = hstr2bs(sucstr)
bdstr = []
for i in xrange(0,0x24):
  if i >= btipstr.__len__():
    bdstr.append(bsucstr[i] ^ 0x51)
  else:
    bdstr.append(btipstr[i] ^ bsucstr[i])

>>> b"".join([chr(ch) for ch in bdstr])
'Congratulation, Send you a flower^_^'

收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 9
活跃值: (175)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
2
点赞!!!
2016-12-3 22:49
0
游客
登录 | 注册 方可回帖
返回
//