首页
论坛
专栏
课程

[原创]PEDIY CTF 2016 第一题破解说明

2016-11-2 04:38 2785

[原创]PEDIY CTF 2016 第一题破解说明

2016-11-2 04:38
2785
【文章标题】: 1-Crack_Me-凉飕飕.zip 破解说明
【文章作者】: SilentGamb@pediy.com
【作者主页】: http://bbs.pediy.com/member.php?u=492631
【软件名称】: 1-Crack_Me-凉飕飕.zip
【软件大小】: 223 KB (228,352 字节)
【下载地址】: http://ctf.pediy.com/?game-fight-2.htm
【加壳方式】: 无
【保护方式】: 反调试
【编写语言】: vs2008
【使用工具】: 52pjOD1.1 + IDA6.1
【操作平台】: win7X64
【软件介绍】: 看雪CTF 2016 - 第一题
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
  破解过程,我写了调试记录, 调试过程是一个凌乱试错的过程,调试记录的连贯性不强。
  本破文摘自调试记录.
  
  给作者的建议
  * 即使程序被调试,也不要让程序报错. 容易让攻击者看出来. 让程序逻辑错乱好些. e.g. 程序被调试,就不走正常的注册码比对流程.
  * 发布的程序不要编译成DEBUG版程序, 程序的体积会小很多. Debug版程序没有优化,比Release版容易看一些.
  * 分段逐个判断注册码,只会增加攻击者的工作量,因为有一条正确的路引导攻击者找到注册码,根本不用写公式,在每个判断注册码的点上,只有唯一正确的选择.
  
  
  动态基地址
  cm用vs2008编译的, 打开了动态基地址选项.
  后4位是相同的,可以和IDA对在一起.
  
  发现反调试
  00EE2BC9   .  E8 43950000   call Crack_Me.00EEC111
  过了这步挂了.
  
  00EEC151  |.  FF55 F8       |call dword ptr ss:[ebp-0x8]             ;  Crack_Me.00EE2B41
  
  00EEC164  |.  E8 5C6BFFFF   call Crack_Me.00EE2CC5
  
  00EE4A37   .  E8 D5760000   call Crack_Me.00EEC111
  
  00EE4A5C   .  E8 2A000000   call Crack_Me.00EE4A8B
  
  00214A90  |.  E8 FC910000   call Crack_Me.0021DC91
  
  00964A5C   .  E8 2A000000   call Crack_Me.00964A8B // 这里就直接退出了
  
  退出的处理
  00134A8B  /$  8BFF          mov edi,edi
  00134A8D  |.  55            push ebp
  00134A8E  |.  8BEC          mov ebp,esp
  00134A90  |.  E8 FC910000   call Crack_Me.0013DC91
  00134A95  |.  84C0          test al,al
  00134A97  |.  74 20         je short Crack_Me.00134AB9
  00134A99  |.  64:A1 3000000>mov eax,dword ptr fs:[0x30]
  00134A9F  |.  8B40 68       mov eax,dword ptr ds:[eax+0x68]
  00134AA2  |.  C1E8 08       shr eax,0x8
  00134AA5  |.  A8 01         test al,0x1
  00134AA7  |.  75 10         jnz short Crack_Me.00134AB9
  00134AA9  |.  FF75 08       push dword ptr ss:[ebp+0x8]              ; /ExitCode = 7EFDE000 (2130567168.)
  00134AAC  |.  FF15 A4801400 call dword ptr ds:[<&KERNEL32.GetCurrent>; |[GetCurrentProcess
  00134AB2  |.  50            push eax                                 ; |hProcess = 766D3358
  00134AB3  |.  FF15 A8801400 call dword ptr ds:[<&KERNEL32.TerminateP>; \TerminateProcess
  00134AB9  |>  FF75 08       push dword ptr ss:[ebp+0x8]
  00134ABC  |.  E8 0B000000   call Crack_Me.00134ACC
  00134AC1  |.  59            pop ecx                                  ;  kernel32.766D336A
  00134AC2  |.  FF75 08       push dword ptr ss:[ebp+0x8]              ; /ExitCode = 0x7EFDE000
  00134AC5  \.  FF15 E8801400 call dword ptr ds:[<&KERNEL32.ExitProces>; \ExitProcess
  00134ACB      CC            int3
  
  异常处理
  .text:0040322A sub_40322A      proc near               ; CODE XREF: sub_402B41p
  .text:0040322A                 push    offset TopLevelExceptionFilter ; lpTopLevelExceptionFilter
  .text:0040322F                 call    ds:SetUnhandledExceptionFilter
  .text:00403235                 retn
  .text:00403235 sub_40322A      endp
  .text:00403235
  .text:00403236
  .text:00403236 ; =============== S U B R O U T I N E =======================================
  .text:00403236
  .text:00403236 ; Attributes: bp-based frame
  .text:00403236
  .text:00403236 ; LONG __stdcall TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *)
  .text:00403236 TopLevelExceptionFilter proc near       ; DATA XREF: sub_40322Ao
  .text:00403236
  .text:00403236 arg_0           = dword ptr  8
  安装异常处理
  011B322A  /$  68 36321B01   push Crack_Me.011B3236                   ; /pTopLevelFilter = Crack_Me.011B3236
  011B322F  |.  FF15 94801C01 call dword ptr ds:[<&KERNEL32.SetUnhandl>; \SetUnhandledExceptionFilter
  
  退出处理注释掉
  sub_404972 一共有4处调用
  1
  .text:00404B45                 push    1
  .text:00404B47                 push    2
  .text:00404B49                 push    0
  .text:00404B4B                 call    sub_404972
  .text:00404B50                 add     esp, 0Ch
  2
  .text:00404B54                 push    1
  .text:00404B56                 push    0
  .text:00404B58                 push    0
  .text:00404B5A                 call    sub_404972
  .text:00404B5F                 add     esp, 0Ch
  3
  .text:00404B68                 push    0
  .text:00404B6A                 push    2
  .text:00404B6C                 push    [ebp+arg_0]
  .text:00404B6F                 call    sub_404972
  .text:00404B74                 add     esp, 0Ch
  4
  .text:00404BB6                 push    0
  .text:00404BB8                 push    0
  .text:00404BBA                 push    [ebp+arg_0]
  .text:00404BBD                 call    sub_404972
  .text:00404BC2                 add     esp, 0Ch
  
  挂了
  01322BC9   .  E8 43950000   call Crack_Me.0132C111
  
  在这挂了
  .text:0040C14B                 call    ds:off_41818C
  .text:0040C151                 call    [ebp+var_8]
  
  0132C14B  |.  FF15 8C813301 |call dword ptr ds:[0x133818C]           ;  Crack_Me.01323720
  0132C151  |.  FF55 F8       |call dword ptr ss:[ebp-0x8]             ;  Crack_Me.01322B41
  
  0132C14B  |.  FF15 8C813301 |call dword ptr ds:[0x133818C]           ;  Crack_Me.01323720
  0132C151  |.  FF55 F8       |call dword ptr ss:[ebp-0x8]             ;  Crack_Me.01321000
  
  01321530 NOP掉
  想先打掉反调试,让调试器带程序跑起来.
  
  0122C151  |.  FF55 F8       |call dword ptr ss:[ebp-0x8]             ;  Crack_Me.01221010
  
  0122C151  |.  FF55 F8       |call dword ptr ss:[ebp-0x8]             ;  Crack_Me.01221010
  
  0122C164  |.  E8 5C6BFFFF   call Crack_Me.01222CC5
  
  报错了
  .text:00402C44                 call    loc_401560
  
  建立窗口前有插入的汇编代码进行检测流程
  003A15AB  |.  83F8 02       cmp eax,0x2                              ; |
  003A15AE  |.  0F8F DA000000 jg Crack_Me.003A168E                     ; |
  必须打掉,窗口流程才能出来
  
  进行时间差检测的函数
  调用的参考还挺多, 必须让这个函数返回的时间差 < 2
  在里面改不太好改,直接抹掉调用sub_4048DE后,使用结果的判断吧.
  
  int __cdecl sub_4048DE()
  {
    int v0; // ecx@3
    signed __int64 v1; // qax@5
    LARGE_INTEGER PerformanceCount; // [sp+4h] [bp-8h]@2
  
    if ( (HIDWORD(qword_41FC18) & (unsigned int)qword_41FC18) == -1
      || !QueryPerformanceCounter(&PerformanceCount)
      || (v0 = (unsigned __int64)(*(_QWORD *)&PerformanceCount - qword_41FC18) >> 32, v0 <= 0) && v0 < 0
      || (LODWORD(v1) = sub_404817(
                          PerformanceCount.s.LowPart - qword_41FC18,
                          (unsigned __int64)(*(_QWORD *)&PerformanceCount - qword_41FC18) >> 32),
          v1 > 2147483647) )
      LODWORD(v1) = -1;
    return v1;
  }
  
  打掉时间差检测
  NOP掉检测点 jg xx
  比对点1
  .text:004015A2                 call    sub_4048DE
  .text:004015A7                 sub     eax, edi
  .text:004015A9                 push    0
  .text:004015AB                 cmp     eax, 2
  .text:004015AE                 jg      loc_40168E
  比对点2
  .text:00401DC9                 call    sub_4048DE
  .text:00401DCE                 sub     eax, [ebp-0D0h]
  .text:00401DD4                 cmp     eax, 2
  .text:00401DD7                 jg      short loc_401E3E
  比对点3
  .text:00401EC6                 call    sub_4048DE
  .text:00401ECB                 sub     eax, esi
  .text:00401ECD                 cmp     eax, 2
  .text:00401ED0                 jg      loc_4020D4
  
  已经过了所有的反调试
  Crack_Me5.exe 可以用调试器带起来正常运行.
  
  GetWindowTextW下断
  
  点击按钮的处理
  00E722C4                                                  .  A1 1803E900   mov eax,dword ptr ds:[0xE90318]          ;  Case 3EB of switch 00E72296
  
  
  起线程
  .text:004020E0 fnThreadProc    proc near               ; DATA XREF: sub_402120+24Co
  .text:004020E0
  .text:004020E0 arg_0           = dword ptr  8
  
  取输入的文本
  // 这里要F9, 才能到1C3E
  01231C2C                                                 |.  8D85 34FFFFFF lea eax,dword ptr ss:[ebp-0xCC] // 接收注册码的缓冲区
  01231C32                                                 |.  6A 64         push 0x64                                ; /Count = 64 (100.)
  01231C34                                                 |.  50            push eax                                 ; |Buffer = 00000006
  01231C35                                                 |.  FF76 0C       push dword ptr ds:[esi+0xC]              ; |hWnd = 00100A94 (class='Edit',parent=00180AA6)
  01231C38                                                 |.  FF15 4C812401 call dword ptr ds:[<&USER32.GetWindowTex>; \GetWindowTextW
  
  一个字符必须是’b‘
  01231C65                                                 |.  BA 62000000   mov edx,0x62
  01231C6A                                                 |.  66:0f1f4400 0>nop word ptr ds:[eax+eax]
  01231C70                                                 |>  66:3BD1       /cmp dx,cx
  01231C73                                                 |.  74 1E         |je short Crack_Me.01231C93
  
  过了一个正确的判断了
  01231CDD                                                 |. /74 54         je short Crack_Me.01231D33
  01231CDF                                                 |. |68 C8000000   push 0xC8
  
  call之后发送了消息
  01231D0F                                                 |.  E8 3C0D0000   call Crack_Me.01232A50
  
  01232A50是关键CALL.
  01232A50 call后,如果发送了消息, 注册码又不对,就出现失败对话框
  
  窗口处理过程
  01232120                                                  .  55            push
  ebp
  
  WM_COMMAND处理
  
  01232290                                                  > \8B4D 10       mov ecx,dword ptr ss:[ebp+0x10]          ;  Case 111 (WM_COMMAND) of switch 0123214D
  
  关键CALL分析
  signed int __fastcall sub_402A50(int a1, int a2, __int16 a3)
  {
  // a1 is ecx = 76E41EE3
  // a2 is edx = 输入的注册码 “b1111111”
  // a3 is ebp + 8 = 0x70
  
  
    signed int result; // eax@2
    __int16 v4; // ax@3
    int v5; // ecx@3
  
    if ( a2 )
    {// 首先:注册码必须不是空字符串
      v4 = *(_WORD *)a2;// 注册码是宽字符输入的
      v5 = 0;
      if ( *(_WORD *)a2 )
      {
        while ( a3 != v4 ) // 第个字母必须是’p’
        {
          v4 = *(_WORD *)(a2 + 2 * v5++ + 2);
          if ( !v4 )
            goto LABEL_6;
        }
        result = 1; // 走到这才是正确的注册码
      }
      else
      {
  LABEL_6:
        result = 0;
      }
    }
    else
    {
      result = 0;
    }
    return result;
  }
  已经有2个字母正确了
  01231D1F                                                 |.  E8 8CFFFFFF   call Crack_Me.01231CB0
  
  猜密码
  从程序资源看,这个作者是15pb的学员.
  有2个字符是bp,正好是15pb反着写
  尝试输入bp51xxx, 得到口令必须是7位. 猜测是bp51 + 3个其他字符
  拿bp5115p作为输入的注册码,继续调试. 从Crack_Me.01231CB0开始.
  
  算了一次输入的注册码长度
  01231DAA                                                 |> \33C9          xor ecx,ecx                              ;  user32.76E41EE3
  
  01231DBF                                                 |.  E8 120F0000   call Crack_Me.01232CD6
  
  开始比对字符串长度是否正确
  0123C980                                                 |.  83FE E0       cmp esi,-0x20
  
  Crack_Me.0123C393 里面在堆上分配了0x10字节的缓冲区.
  看到下面有SendMessage
  01231DC4                                                 |.  83C4 04       add esp,0x4
  
  怀疑是算字符串长度的代码是宏
  01231B42                                                 |> /8D40 02       /lea eax,dword ptr ds:[eax+0x2]
  
  将异或后的用户密文压入CALL
  01231BDB                                                 |.  E8 90FCFFFF   call Crack_Me.01231870(ecx,edx)
  ecx = 1
  edx = 异或加密后的注册码输入
  
  看到了,这就是算注册码的CALL, 因为算完后,会发送2个消息,wParam不同,
  wParam = 1034,
  wParam = 1035,
  看看这是2个啥消息
  
  在一个缓冲区内填入了宽字符L’1’~L’9’
  012318A2                                                 |> /66:8901       /mov word ptr ds:[ecx],ax
  012318A5                                                 |. |8D49 02       |lea ecx,dword ptr ds:[ecx+0x2]
  012318A8                                                 |. |40            |inc eax
  012318A9                                                 |. |83F8 39       |cmp eax,0x39
  012318AC                                                 |.^\7E F4         \jle short Crack_Me.012318A2
  在一个缓冲区内填入了宽字符L’a’~L’z’
  012318B6     |> /66:8901       /mov word ptr ds:[ecx],ax
  012318B9     |. |8D49 02       |lea ecx,dword ptr ds:[ecx+0x2]
  012318BC     |. |40            |inc eax
  012318BD     |. |83F8 7A       |cmp eax,0x7A
  012318C0     |.^\7E F4         \jle short Crack_Me.012318B6
  
  将刚填入的小写a~z变成大些A~Z
  012318E8     |. /72 0D         |jb short Crack_Me.012318F7
  012318EA     |. |83F8 7A       |cmp eax,0x7A
  012318ED     |. |77 08         |ja short Crack_Me.012318F7
  012318EF     |. |83C0 E0       |add eax,-0x20
  
  异或加密后的用户输入
  edi = 00570468
  又开始算加密后的串长度
  01231910     |> /8D40 02       /lea eax,dword ptr ds:[eax+0x2]
  01231913     |. |41            |inc ecx
  01231914     |. |66:8338 00    |cmp word ptr ds:[eax],0x0
  01231918     |.^\75 F6         \jnz short Crack_Me.01231910
  开始还原加密后的用户输入
  01231920     |> /83F8 02       /cmp eax,0x2
  01231923     |. /73 07         |jnb short Crack_Me.0123192C
  01231925     |. |66:833447 0F  |xor word ptr ds:[edi+eax*2],0xF
  0123192A     |. |EB 11         |jmp short Crack_Me.0123193D
  
  第1个字符异或0xf
  第2个字符异或0xf
  第3个字符异或0x50
  第4个字符异或0x42
  第5个字符异或0x42
  第6个字符异或0x42
  
  将解密后的用户输入变成大写
  01231960     |> /0FB7044F      /movzx eax,word ptr ds:[edi+ecx*2]
  01231964     |. |83F8 61       |cmp eax,0x61
  01231967     |. |72 0C         |jb short Crack_Me.01231975
  01231969     |. |83F8 7A       |cmp eax,0x7A
  0123196C     |. |77 07         |ja short Crack_Me.01231975
  0123196E     |. |83C0 E0       |add eax,-0x20
  01231971     |. |66:89044F     |mov word ptr ds:[edi+ecx*2],ax
  01231975     |> |41            |inc ecx
  01231976     |. |3BCA          |cmp ecx,edx
  01231978     |.^\72 E6         \jb short Crack_Me.01231960
  
  在操作注册码
  为啥我看到的合规字符集,是A~Z ?
  01231996     |> /66:85C9       /test cx,cx
  01231999     |. |74 2C         |je short Crack_Me.012319C7
  0123199B     |. |0FB710        |movzx edx,word ptr ds:[eax]
  0123199E     |. |8D4D B0       |lea ecx,dword ptr ss:[ebp-0x50]
  
  头2个字母必须是bp
  现在用的注册码是 “bp12345”
  可以过下面的判断,否则去失败处了.
  001C19EA     |.  83F9 02       cmp ecx,0x2
  001C19ED     |.  75 4A         jnz short Crack_Me.001C1A39
  
  001C1A39     |> \6A 00         push 0x0                                                 ;  发送失败消息
  
  向内存写15PB了
  001C19F1     |.  C745 F0 31003>mov dword ptr ss:[ebp-0x10],0x350031
  001C19F8     |.  C745 F4 50004>mov dword ptr ss:[ebp-0xC],0x420050
  001C19FF     |.  8D77 04       lea esi,dword ptr ds:[edi+0x4]
  开始比较15PB
  001C1A10     |> /66:8B444D F0  /mov ax,word ptr ss:[ebp+ecx*2-0x10]
  001C1A15     |. |66:3B06       |cmp ax,word ptr ds:[esi]
  001C1A18     |. |75 1F         |jnz short Crack_Me.001C1A39
  
  启用新的注册码
  条件:
  *  bp在注册码中只能有2个字符
  * 第3个字符开始的字符序列必须是15pb
  * 总字符数为7
  * 第一个字符和第2个字符加起来为0x63, 即第一个字符必须为’1’, 第2个字符必须为’2’
  
  
  离胜利又进了一步
  00F41832                                             |.  8B45 B4       mov eax,dword ptr ss:[ebp-0x4C]
  
  00F41841       |.  3BC1          cmp eax,ecx                                              ;  最后一个字符必须是'8' 即 0x38
  00F41843       |.  75 16         jnz short Crack_Me.00F4185B                              ;  错误分支
  
  胜利
  00F41847       |.  B8 01000000   mov eax,0x1                                              ;  这里是正确的分支,必须走到这里
  
  
  得到正确的注册码
  拿下面的字符串可以进入最后的比对
  1215pb8
  
  
  窗体还有个定时器
  011D2285      .  FF15 3C811E01 call dword ptr ds:[<&USER32.SetTimer>]                   ; \SetTimer
  进入最后的注册码判断
  
  01151740 <Crack_Me.fnLastRegJudge>    /$  55            push ebp
  
  
--------------------------------------------------------------------------------
【经验总结】
  * 先打掉反调试,让调试器能带着cm跑起来。
  * cm用vs2008编写,开了活动基地址,但是低16位地址和IDA能对应起来。e.g. 0100ABC0地址在IDA中为0040ABC0
  * 不断的试错,只有一条通向正确注册码的大路,开心
  * 我的正式破解处女篇成功了,感谢凉飕飕的cm.
  
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                                                       2016年11月02日 4:28:43

[招聘]欢迎市场人员加入看雪学院团队!

上传的附件:
最新回复 (2)
MKHoshi 2016-11-6 14:09
2
0
没想到是逐个字符比较,
wx_疾如风 2018-6-17 17:52
3
0
  00964A5C    这个地址写错了,应该是  00964AC5
游客
登录 | 注册 方可回帖
返回