首页
社区
课程
招聘
[原创]160个CrackMe_024破解思路
发表于: 2016-10-12 12:52 6731

[原创]160个CrackMe_024破解思路

2016-10-12 12:52
6731
发一下自己破解的思路总结
之所以发一下这个,是觉得这个CM很有意思,程序用自身的机器码作为校验方式,Name和Serial会改变程序内部代码,挺好玩的。
不过这个CM拖了一天多的时间才搞定,算法不难,但是就是不成功,原因在结尾


====================以下就是我边破边记录的思路了====================

根据CM023的经验,仍然是下获取编辑框文本的断点(GetWindowText或GetDlgItemText)。
代码的主要逻辑还是比较好懂的。
仍然是获取密码框的int值,压栈保存,获取用户名框的值,
0x58455443循环加用户名DWORD   然后用户名地址+1
弹出密码框int值,和上边循环相加的值相加,先认为这个结果为eax,
eax和0x584554异或保存在全局变量中,然后全局变量减去eax右移0x10的word值(ax)。

注意这个全局变量是在代码段,改变就会改变对应的代码。这是个唯一的去正确提示的突破点。
jmp是个完美的跳转,当然其他也可以,就是得看看当时的标志位满足不满足。
所以用反汇编试了试jmp到成功的提示CALL处(需要调到push那个位置),结果opcode是 EB 26,OK,
这个就是突破点。要让这个全局变量结果是0x26EB,继续分析。

仅仅这样改了跳转还不够,还需要让程序走到我们的跳转处,这就需要达到作者的第二重验证。发现把0x3E给了ecx当做计数器。然后调用lods dword ptr ds:[esi],这作者太坑了,把自己的程序opcode当做加密方法,还一下来了3E次,理论上如果我们写注册机的话,就得保存3E个它的DWORD值.....

程序经过3E次循环异或后,得数与0xAFFCCFFB比较,相等就会跳到我们刚才的jmp处。
经过分析,发现妈的那个esi指向的位置循环3E次,1次4字节,到最后那这一大段代码
中包括了我们要改的代码....这一处代码用两处验证,有点棘手。

程序思路就是代码opcode经过异或后需要等于0xAFFCCFFB才能跳到需要的地方,变动也就4个字节的opcode,逆着把无关紧要的代码异或掉,留下需要改变的那4个字节XXXXXX04  D833ADXX.。看似不连在一起,无从下手,这里就要用到异或的特性:
------------------------------------------------
1、 123456     XOR  D8219956   D833AD00
------------------------------------------------
2、 123456     XOR  D8219900    D833AD56
    D833AD56   XOR        56    D833AD00
------------------------------------------------


想了解异或的特性的朋友可以看看这篇文章:http://www.cnblogs.com/suoloveyou/archive/2012/04/25/2470292.html

上方1和2可以看出(123456 xor D8219956)结果和((123456 xor D8219900) xor 56)一样那么就可以把那4个字节的opcode全填成0让他异或,也就是不会变的数据经过异或后得到的数据A记下来。
004012DD    AD              lods dword ptr ds:[esi]
004012DE    33D8            xor ebx,eax
004012E0    49              dec ecx

它存在ebx中,让那4个字节用00填充,经过循环异或后得到的ebx记下来。
用00填充后代码就是这样的
004012D7   EB 04           jmp XChafe_2.004012DD
004012D9   0000            add byte ptr ds:[eax],al
004012DB   0000            add byte ptr ds:[eax],al
004012DD   AD              lods dword ptr ds:[esi]
004012DE   33D8            xor ebx,eax
004012E0   49              dec ecx
004012E1   75 FA           jnz XChafe_2.004012DD

==========================此处往下思路有误=======================
在内存中的4字节数据就是00000004和D833AD00。ebx经过循环后得到的结果是
0xDCDA243F,与正确结果0xAFFCCFFB差很多,差的这些数据就需要从4个00处下手了。
因为第一个DWORD中的04和第二个DWORD中的D833AD是死数据,不会改变,并且刚才已经经过了异或运算,所以把他们填0,然后写个程序遍历。
填0后的这两个DWORD是xxxxxx00和000000xx,其中xxx是需要遍历的数据,让他们异或以后的结果等于程序需要的0xAFFCCFFB。
int main(void)
{
   for (DWORD i = 0x0; i <= 0xFFFFFF00; i++)//第一个4字节xxxxxx00的范围
  {
    if ((i & 0xFF)==0)//因为低两位的04已经运算过了,所以
              //这里只的低两位必须是00才符合需要的结果
    {    
      for (DWORD j = 0; j <= 0xFF; j++)//第二个4字节的范围
      {
        if (0xAFFCCFFB == ((0xB2DA24A3 ^ i) ^ j))
          printf("\n%08x\t%02x\n\n", i, j);
      }
    }
  }
  return 0;
}

经过遍历,只跑出来一个结果 7326EB00  C4,放在内存中是这样的04 EB 26 73 | C4 AD 33 D8,DWORD数据是0xC47326EB
因为之前在OD里试着改过反汇编代码,看见EB 26就比较敏感,没错,他就是jmp到正确提示。
有了这些数据,剩下的工作就是让用户名和序列号经过运算得到这些数据就OK了。现在去看它的用户名&序列号校验
算法。
经过分析以后,它的算法完全明了了
00401269   .  C705 D9124000>mov dword ptr ds:[<四字节代码>],0x584554   ;  初始化代码数据
00401273   .  6A 00         push 0x0                                   ; /IsSigned = FALSE
00401275   .  8D45 FC       lea eax,dword ptr ss:[ebp-0x4]             ; |
00401278   .  50            push eax                                   ; |pSuccess
00401279   .  6A 64         push 0x64                                  ; |ControlID = 64 (100.)
0040127B   .  FF35 50314000 push dword ptr ds:[0x403150]               ; |hWnd = 00050612 ('TEXme v2.0',class='CTEX')
00401281   .  E8 BC010000   call <jmp.&USER32.GetDlgItemInt>           ; \GetDlgItemInt
00401286   .  837D FC 00    cmp dword ptr ss:[ebp-0x4],0x0
0040128A   .  74 5F         je XChafe_2.004012EB
0040128C   .  50            push eax                                   ;  密码框的int值
0040128D   .  6A 14         push 0x14                                  ; /Count = 14 (20.)
0040128F   .  68 6C314000   push Chafe_2.0040316C                      ; |用户名的ASCII
00401294   .  FF35 54314000 push dword ptr ds:[0x403154]               ; |hWnd = 00050614 (class='Edit',parent=00050612)
0040129A   .  E8 AF010000   call <jmp.&USER32.GetWindowTextA>          ; \GetWindowTextA
0040129F   .  85C0          test eax,eax
004012A1   .  74 48         je XChafe_2.004012EB
004012A3   .  A1 0B304000   mov eax,dword ptr ds:[<全局变量0>]
004012A8   .  BB 6C314000   mov ebx,Chafe_2.0040316C                   ;  存放用户名地址
004012AD   .  0303          add eax,dword ptr ds:[ebx]
004012AF   .  43            inc ebx
004012B0   .  81FB 7C314000 cmp ebx,Chafe_2.0040317C
004012B6   .  75 F5         jnz XChafe_2.004012AD                      ;  58455443循环加用户名DWORD
004012B8   .  5B            pop ebx                                   
004012B9   .  03C3          add eax,ebx                          ;  密码框的int值相加
004012BB   .  3105 D9124000 xor dword ptr ds:[<四字节代码>],eax        ;  原始代码与运算后的值异或
004012C1   .  C1E8 10       shr eax,0x10
004012C4   .  66:2905 D9124>sub word ptr ds:[<四字节代码>],ax          ;  减去右移16次的word值
004012CB   .  BE EC114000   mov esi,Chafe_2.004011EC
004012D0   .  B9 3E000000   mov ecx,0x3E                               ;  算法2的计数器
004012D5   .  33DB          xor ebx,ebx
004012D7   .  EB 04         jmp XChafe_2.004012DD                      ;  -----jmp算法2-----
004012D9   .  54            push esp                                   ;  这里的代码每次都会初始化为
004012DA      45            db 45                                      ;  0x00584554
004012DB      58            db 58                                     
004012DC      00            db 00
004012DD   .  AD            lods dword ptr ds:[esi]                    ;  ---算法2---
注释已经写的很明白了,接下来就是下个注册机。
=============================有误解除===========================
想法挺好的,然后注册机没搞出来。这就是为什么说思路有误。因为想法和现实有些出入。
注册机确实写出来了,但是跑出来的密码只能在调试的时候成功一次,然后就不成功了,一直想不通是为什么。看了断点分析才知道,原来软件断点是在opcode第一个字节被替换成了0xCC,这也是单步的原理。这就是为什么调试状态下可以成功,然后取消断点或者不调试运行莫名其妙的就不成功了,因为0xCC断点,所以它的代码opcode异或值在调试的时候一直就不是原原本本的opcede。
如下图:本来的opcode读入寄存器应该是83EC8B55,但图中读入寄存器eax的值却是83ECCC55,原因就是软件断点,第一个字节填充成了0xCC。这就很坑爹了,因为调试过程中,会下N个断点来检测当时数据是否为我们猜想的数据,把这好几个断点带的0xCC参与校验,偏差恐怕要大很多很多。
     
在此思路有二:  1、手动计算opceode的异或值 2、硬件断点试试。
为了懒省事,先用硬件断点试试。
004012D7   . /EB 04         jmp XChafe_2.004012DD         ; 硬件断点
004012D9 >   |0000          add byte ptr ds:[eax],al      ; 这里填00
004012DB     |0000          add byte ptr ds:[eax],al      ; 这里填00
004012DD   > \AD            lods dword ptr ds:[esi]                  
004012DE   .  33D8          xor ebx,eax
004012E0      49            dec ecx
004012E1    ^ 75 FA         jnz XChafe_2.004012DD
004012E3      81FB FBCFFCAF cmp ebx,0xAFFCCFFB            ; 硬件断点   

jmp处的硬件断点断下以后,把后边4字节用00填充,然后F9运行到cmp处的硬件断点,记下ebx的值。这个值就是缺少那4个字节的值,要用到他去穷举那4个字节可能出现的值。
新的穷举算法:
int main(void)
{
  for (DWORD i = 0x0; i <= 0xFFFFFF00; i++)//第一个4字节xxxxxx00的范围
  {
    if ((i & 0xFF) == 0)//因为低两位的04已经运算过了,所以
      //这里只的低两位必须是00才符合需要的结果
    {
      for (DWORD j = 0; j <= 0xFF; j++)//第二个4字节的范围
        //因为前3个字节已经异或过了,这里只有最后一个字节被我们填00了
        //所以范围就是0-0xFF。
      {    //正确的值    //ebx的值
        if (0xAFFCCFFB == (((0xFBDA24A3 ^ i)) ^ j))
          printf("\n%08x\t%02x\n\n", i, j);
      }
    }
  }
  return 0;
}

跑出来的结果是 5426eb00   58,在OD里的值是这样的。
那接下来就可以写真•注册机了。
int main(void)
{
  CHAR szName[20] = { 0 };
  cout << "Name:";
  cin >> szName;
  DWORD dwNum = 0x58455443;//程序实现填入的全局变量
  for (int i = 0; i < strlen(szName); i++)//用户名循环相加DWORD
  {
    void *p = &szName[i];
    _asm
    {
      mov edi, p;
      mov eax, dword ptr[edi];
      add dwNum, eax;
    }
  }
//////////////////////////////////////////////////////////////////////////
  DWORD dwPass = 0;
  while (1)
  {
    DWORD temp = (dwNum + dwPass) ^ 0x584554;
    temp -= (WORD)((dwNum + dwPass) >> 0x10);
    if (dwPass % 0x100000 == 0)  //这里if语句可以不要只是用来看看它在跑密码还是死机了
      cout << dwPass << endl;
    if (temp == 0x585426EB)//这就是上一个算法得到的正确的opcode
      break;
    dwPass++;
  }
  cout << "Pass:"<<dwPass<<endl;
}

跑出来的结果非调试下也可以提示正确了。
提供一组用于测试:
Name:pediy.com
Serial:2920336344

那总结下来原因就是因为CC断点了,经验不够丰富,一直没往这个地方想,不过收获还是挺大了。

CM单包下载: Chafe.2.zip

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

上传的附件:
收藏
免费 0
支持
分享
最新回复 (2)
雪    币: 16418
活跃值: (1665)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享 思路过程更重要。
2016-10-12 16:22
0
雪    币: 193
活跃值: (1215)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
茅厕顿开啊,难怪试了好几次结果都不对
2016-10-22 06:29
0
游客
登录 | 注册 方可回帖
返回
//