首页
社区
课程
招聘
[原创]看雪CTF2016-5-CrackMe-冰怜泯灭.rar破解报告
发表于: 2016-11-10 19:47 3798

[原创]看雪CTF2016-5-CrackMe-冰怜泯灭.rar破解报告

2016-11-10 19:47
3798
【文章标题】: 看雪CTF2016-5-CrackMe-冰怜泯灭.rar破解报告
【文章作者】: SilentGamb@pediy
【作者主页】: http://bbs.pediy.com/member.php?u=492631
【软件名称】: 5-CrackMe-冰怜泯灭.rar
【软件大小】: 40.0 KB (40,960 字节)
【下载地址】: http://ctf.pediy.com/?game-fight-6.htm
【加壳方式】: 无
【保护方式】: 无
【编写语言】: VC6
【使用工具】: OD1.1 + IDA6.1
【操作平台】: Win7X64
【软件介绍】: 冰怜泯灭给大家做的cm
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
  <<5-CrackMe-冰怜泯灭.rar>>破解调试记录
  侦查
  注册码输入错误之后没提示
  VC6编译器生成.
  
  单步跟一下
  00401172   .  FFD3          call ebx                                         ; |\GetDlgItem
  00401174   .  8B2D A4504000 mov ebp,dword ptr ds:[<&USER32.SendMessageA>]    ; |user32.SendMessageA
  0040117A   .  50            push eax                                         ; |hWnd = 0x9
  0040117B   .  FFD5          call ebp                                         ; \SendMessageA
  0040117D   .  33C9          xor ecx,ecx                                      ;  取用户输入
  0040117F   .  85C0          test eax,eax
  00401181   .  76 17         jbe short CrackMe.0040119A
  00401183   >  8A540C 20     mov dl,byte ptr ss:[esp+ecx+0x20]
  00401187   .  80FA 30       cmp dl,0x30
  0040118A   .  7C 0C         jl short CrackMe.00401198
  0040118C   .  80FA 39       cmp dl,0x39
  0040118F   .  7F 07         jg short CrackMe.00401198                        ;  判断是否是合规字符集(0~9)
  00401191   .  41            inc ecx
  00401192   .  3BC8          cmp ecx,eax
  00401194   .^ 72 ED         jb short CrackMe.00401183
  00401196   .  EB 02         jmp short CrackMe.0040119A
  00401198   >  33FF          xor edi,edi
  0040119A   >  83F8 06       cmp eax,0x6
  0040119D   .  75 56         jnz short CrackMe.004011F5                       ;  注册码是6个数字
  0040119F   .  85FF          test edi,edi
  004011A1   .  74 52         je short CrackMe.004011F5
  
  启用测试输入 987654
  
  004010DE                   |.  81FF 79290000 cmp edi,0x2979                                   ;  注册码结果比对
  
  
  sub_401000 将用户输入进行运算A, 结果为B
  sub_4010C0 将B做校验,看是否正确, 结果为bRc
  
  如果bRc正确,用sub_4010C0的运算结果去做任务,显示正确注册信息。
  如果爆破,程序崩了.
  
  
            sub_401000((int)szBuf_len_0x3f, 6);
            LOBYTE(v9) = sub_4010C0();
  
  将 sub_401000, sub_4010C0反推出正确的注册码,就可以了.
  
  确认运算过程
  0018f85c = 0x100 缓冲区长度
  0018f864长度0x100, 初始化为0x00~0xff
  0018F854  FE 71 6A 75 5A 37 6A 75 00 01 00 00 04 00 34 00  juZ7ju....4.
  0018F864  00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F  .0018F874  10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F  
  ...
  0018F944  E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF  噌忏溴骁栝觌祉铒
  0018F954  F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF  瘃蝮趱鲼?
  
  制作穷举注册机
  sub_401000是更新Key.
  sub_4010C0 是注册码比对.
  IDA翻译的内容摘出来直接用,有问题, 表达不了cm的算法,只能参考。
  人肉翻译了这2个函数,整理出了注册机.
  
  注册机实现
  // KeyGen.cpp : Defines the entry point for the console application.
  //
  
  #include "stdafx.h"
  #include <windows.h>
  
  int g_iRetryCnt = 0;
  
  const char ucAryPwdCharSet[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
  BYTE g_ucAryKey[128] = {'\0'};
  
  BYTE g_ucAryKeyBk[128] = {
      0xF4, 0x12, 0x9D, 0x60, 0x45, 0xF8, 0x20, 0x6A, 0x6F, 0x67, 0x04, 0x71, 0xC0, 0x9B, 0x0C, 0x5A,
      0x1D, 0x18, 0x6C, 0x96, 0x69, 0x01, 0x1C, 0xF4, 0x7F, 0x28, 0x5A, 0xFB, 0x29, 0x07, 0x40, 0x8B,
      0xD3, 0xE1, 0xB1, 0x12, 0xFB, 0xCA, 0x7C, 0x89, 0xB9, 0x5A, 0x30, 0x70, 0x9D, 0x95, 0x2B, 0x95,
      0x3C, 0x8D, 0x2E, 0x45, 0xEF, 0x70, 0xC6, 0xA3, 0xB9, 0xB2, 0x5A, 0x63, 0x5F, 0x03, 0x33, 0xB8,
      0x64, 0x4A, 0x8F, 0xBC, 0xF7, 0x91, 0x69, 0x6A, 0x56, 0x2E, 0xD4, 0x6E, 0x82, 0x93, 0xE9, 0x76,
      0xDC, 0xA3, 0x6C, 0x5E, 0x6B, 0x72, 0x64, 0x37, 0xE7, 0x15, 0x17, 0xAC, 0x64, 0x78, 0xD5, 0x4A,
      0x60, 0x2D, 0xF0, 0x54, 0xA6, 0xF3, 0xE8, 0xE0, 0xE0, 0xB9, 0x8F, 0x85, 0x90, 0xE4, 0xEA, 0xD6,
      0xBB, 0xB7, 0x15, 0x9E, 0x2A, 0x44, 0xE7, 0x31, 0x63, 0xAC, 0x80, 0x6C, 0x34, 0x82, 0xE9, 0xCF
  };
  
  int fnUpdateKeyByUserInput(char* pcSnInput, int iLenSn)
  {
      int iLoopCnt = 0;
      BYTE ucIndex = 0;
      int iIndex = 0;
      BYTE ucTotal = 0;
      BYTE ucTmp = 0;
      char szBuf[0x100] = {'\0'};
      BYTE* pBufCur = (BYTE*)&szBuf[0];
      BYTE* pBufBegin = (BYTE*)&szBuf[0];
      BYTE* pBufEnd = (BYTE*)&szBuf[0xff];
      
      memcpy(g_ucAryKey, g_ucAryKeyBk, sizeof(g_ucAryKeyBk)); // !
      for (iIndex = 0; iIndex < sizeof(szBuf); iIndex++) {
          szBuf[iIndex] = iIndex;
      }
      
      // 根据输入计算索引交换缓冲区字节内容
      for (iLoopCnt = sizeof(szBuf); iLoopCnt > 0; iLoopCnt--) {
          ucTotal += (*pBufCur + * ((BYTE*)pcSnInput + ucIndex++));
         
          if (ucIndex >= 6) {
              ucIndex = 0;
          }
         
          ucTmp = *pBufCur;
          *pBufCur = szBuf[ucTotal];
          szBuf[ucTotal] = ucTmp;
          pBufCur++;
      }
      
      // 用计算后的缓冲区内容更新预留密钥
      // szBuf size的一半是0x128
      for (iIndex = 0; iIndex < 128; iIndex++) {
          g_ucAryKey[iIndex] ^= (*pBufBegin++ + *pBufEnd--);
      }
      
      return iIndex;
  }
  
  bool RegSnVerify()
  {
      int iSum = 0;
      int iIndex = 0;
      
      do {
          iSum += g_ucAryKey[iIndex++];
      } while (iIndex < 128);
      
      return (iSum == 10617);
  }
  
  int main(int argc, char* argv[])
  {
      // 6位的口令, 用接口穷举吧^_^
      int iLenAryPwdCharSet = sizeof(ucAryPwdCharSet);
      int iLoop1 = 0;
      int iLoop2 = 0;
      int iLoop3 = 0;
      int iLoop4 = 0;
      int iLoop5 = 0;
      int iLoop6 = 0;
      char szBuf[7] = {'\0'};
      
      for (iLoop1 = 0; iLoop1 < iLenAryPwdCharSet; iLoop1++) {
          for (iLoop2 = 0; iLoop2 < iLenAryPwdCharSet; iLoop2++) {
              for (iLoop3 = 0; iLoop3 < iLenAryPwdCharSet; iLoop3++) {
                  for (iLoop4 = 0; iLoop4 < iLenAryPwdCharSet; iLoop4++) {
                      for (iLoop5 = 0; iLoop5 < iLenAryPwdCharSet; iLoop5++) {
                          for (iLoop6 = 0; iLoop6 < iLenAryPwdCharSet; iLoop6++) {
                              szBuf[0] = ucAryPwdCharSet[iLoop1];
                              szBuf[1] = ucAryPwdCharSet[iLoop2];
                              szBuf[2] = ucAryPwdCharSet[iLoop3];
                              szBuf[3] = ucAryPwdCharSet[iLoop4];
                              szBuf[4] = ucAryPwdCharSet[iLoop5];
                              szBuf[5] = ucAryPwdCharSet[iLoop6];
                              
                              if (g_iRetryCnt++ > 10000) {
                                  g_iRetryCnt = 0;
                                  OutputDebugString("find... 注册码 : ");
                                  OutputDebugString(szBuf);
                                  OutputDebugString("\r\n");
                              }
                              
                              fnUpdateKeyByUserInput(szBuf, 6);
                              
                              if (true == RegSnVerify()) {
                                  // 注册码是 : 771535 ^_^
                                  ::MessageBox(NULL, szBuf, "找到注册码!", MB_OK);
                                  OutputDebugString("找到注册码!\r\n");
                                  OutputDebugString(szBuf);
                                  OutputDebugString("\r\n");
                              }
                          }
                      }
                  }
              }
          }
      }
      
      ::MessageBox(NULL, "END", "穷举结束", MB_OK);
      return 0;
  }
  
  得到注册码
  注册机大概跑了10秒,得到注册码”771535”
  因为只有9位数字的注册码,又验证了还有没有多解,符合题意,只有这一个解.
  
  cm实际验证
  输入注册码 771535, 成功了
  
--------------------------------------------------------------------------------
【经验总结】
  逆向分析出注册接口,然后使用针对性的穷举,这招还蛮好使.
  如果是蛮力穷举,穷举的效果就不可预测了.
  
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                                                       2016年11月10日 19:39:08

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 6
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
爆破的时候程序异常, 直接将4010C0后面的eax 判断跳转修改爆破,本以为会显示破解成功信息,但是结果却异常了,跟进去到call 6030的时候, 发现跟进去的地址变成了603x,而不是6030 ,并且正确的密码和错误的密码,call 6030 那里f7进去的时候,汇编不一样 ,
   请问作者是在这里进行了混淆么?为什么出现 call 6030的地方 f7进去却停在6032处?
2017-2-17 15:17
0
游客
登录 | 注册 方可回帖
返回
//