首页
社区
课程
招聘
7
[原创]ccfer-crackme02破解(含keygen)
发表于: 2008-6-2 00:45 12632

[原创]ccfer-crackme02破解(含keygen)

2008-6-2 00:45
12632

ccfer-crackme02破解(含keygen)
by bood

ccfer说这个是“一个简单的crackme”,但是从我的破解过程来看,我还是很汗这句话的,可能是说爆破简单?个人觉得算法还是蛮复杂的……

1. 定位算法代码
这个就不详细说了,随便断GetDlgItemTextA或者MessageBoxA都可以。容易发现0x401110是整个检查函数的入口。

2. 算法分析
通过跟踪发现,第一段检查代码如下(关键函数调用已经被我用自定义函数标识):

00401168  |> /0FBE9434 2801>/movsx   edx, byte ptr [esp+esi+128]             ; serial的一位
00401170  |. |52            |push    edx                                     ; /ch
00401171  |. |E8 6E020000   |call    <checkChar>                             ; \crackme.checkChar
00401176  |. |83C4 04       |add     esp, 4
00401179  |. |85C0          |test    eax, eax                                ;  '0'-'9' 'A'-'F' 'a'-'f'
0040117B  |. |0F84 3C010000 |je      004012BD                                ;  check failed, goodbye
00401181  |. |46            |inc     esi
00401182  |. |3BF5          |cmp     esi, ebp
00401184  |.^\7C E2         \jl      short 00401168

checkChar函数如下:

004013E4 >/$  833D 6C824000>cmp     dword ptr [<checkCharFlag>], 1           ;  这个检查不知道干吗的,好像总是1,没管
004013EB  |.  7E 11         jle     short 004013FE
004013ED  |.  68 80000000   push    80
004013F2  |.  FF7424 08     push    dword ptr [esp+8]
004013F6  |.  E8 B00C0000   call    004020AB
004013FB  |.  59            pop     ecx
004013FC  |.  59            pop     ecx
004013FD  |.  C3            retn
004013FE  |>  8B4424 04     mov     eax, dword ptr [esp+4]                   ;  跳到这里必然,eax里就是serial的一个字符
00401402  |.  8B0D 60804000 mov     ecx, dword ptr [<checkCharTable>]        ;  一个表格……
00401408  |.  8A0441        mov     al, byte ptr [ecx+eax*2]
0040140B  |.  25 80000000   and     eax, 80                                  ;  最高位必须是1
00401410  \.  C3            retn

checkCharTable表格的内容如下:


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

收藏
免费 7
支持
分享
赞赏记录
参与人
雪币
留言
时间
Youlor
为你点赞~
2024-1-3 03:57
伟叔叔
为你点赞~
2023-10-30 00:00
QinBeast
为你点赞~
2023-8-5 00:36
PLEBFE
为你点赞~
2023-8-4 01:01
shinratensei
为你点赞~
2023-7-12 02:52
心游尘世外
为你点赞~
2023-7-2 00:57
飘零丶
为你点赞~
2023-6-22 03:04
最新回复 (22)
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
注册机代码
作为习题,请修改打印出所有注册码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <iostream>
#include <iomanip>
#include <iterator>
using namespace std;
int table[8*9]={     //表格,根据调试时内存中数据所得
  0,0,0,0,0,0,0,0,0, //no use
  0,0,0,1,1,1,0,0,0,
  0,0,0,1,1,1,0,0,0,
  0,1,1,1,1,1,1,1,0,
  0,1,1,1,3,1,1,1,0,
  0,1,1,1,1,1,1,1,0,
  0,0,0,1,1,1,0,0,0,
  0,0,0,1,1,1,0,0,0
};
const int MAX=32;
int tablePath[MAX][8*9];
int serial[MAX];
bool searchLevel(int level);
 
//复制当前表格状态,以便修改后不影响回溯
void copyTableTo(int level)
{
  if(level>=MAX) return; //最后一个禁止拷贝,不然就溢出了,当然,也可以给tablePath加一维
  memcpy(tablePath[level], tablePath[level-1], 8*9*sizeof(int));
}
 
//用x匹配注册码中第level个数值(level从0开始)
//核心算法
bool doSearch(int level, int x)
{
  int ecx=(x/4)%7+1;
  int edx=((x/4)/7)%7+1;
  int edi=x&3;
  int offset=ecx+9*edx;
  bool ok=false;
  switch(edi) {
  case 0:
    if(tablePath[level][offset-9]==1
       && tablePath[level][offset-0x12]==3) {
      serial[level]=x;
      copyTableTo(level+1);
      tablePath[level+1][offset]=3;
      tablePath[level+1][offset-9]=3;
      tablePath[level+1][offset-0x12]=1;
      ok=true;
    }
    break;
  case 1:
    if(tablePath[level][offset+1]==1
       && tablePath[level][offset+2]==3) {
      serial[level]=x;
      copyTableTo(level+1);
      tablePath[level+1][offset]=3;
      tablePath[level+1][offset+1]=3;
      tablePath[level+1][offset+2]=1;
      ok=true;
    }
    break;
  case 2:
    if(tablePath[level][offset+9]==1
       && tablePath[level][offset+0x12]==3) {
      serial[level]=x;
      copyTableTo(level+1);
      tablePath[level+1][offset]=3;
      tablePath[level+1][offset+9]=3;
      tablePath[level+1][offset+0x12]=1;
      ok=true;
    }
    break;
  case 3:
    if(tablePath[level][offset-1]==1
       && tablePath[level][offset-2]==3) {
      serial[level]=x;
      copyTableTo(level+1);
      tablePath[level+1][offset]=3;
      tablePath[level+1][offset-1]=3;
      tablePath[level+1][offset-2]=1;
      ok=true;
    }
    break;
  }
  if(ok)
      return searchLevel(level+1);
  else
      return false;
}
 
//搜索序列号第level个数值(level从0开始)
bool searchLevel(int level)
{
  if(level>=MAX) return true;
  cout<<"Searching on level: "<<level<<endl;
  for(int x=0;x<=0xff;x++) {
    if(doSearch(level, x))
        return true;
  }
  cout<<"Failed on level:"<<level<<endl;
  return false;
}
int main()
{
  /* 我是据此发现x/7跟代码中HIGHDWORD的等价性的
  unsigned long long m=0x92492493;
  for(unsigned long long i=0;i<=0x3f;i++) {
    unsigned long long r = m*i;
    cout << i << ":" << (r>>32)/4 << ":" << i/28<< endl;
  }
  */
  memcpy(tablePath[0], table, 8*9*sizeof(int));
  if(searchLevel(0)) {
    cout<<"Serial found!\n";
    //copy(serial, serial+32, ostream_iterator<int>((cout<<setw(2)<<setfill('0')<<setiosflags(ios::hex)), ""));
    for(int i=0;i<MAX;i++) {
        cout<<hex<<setw(2)<<setfill('0')<<serial[i];
    }
  }
  return 0;
}
2008-6-2 00:50
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
厉害
!!!是很晚了,应该休息了
2008-6-2 02:08
0
雪    币: 8209
活跃值: (4559)
能力值: ( LV15,RANK:2473 )
在线值:
发帖
回帖
粉丝
4
如果楼主用ida辅助分析的话就会少走一些弯路了,有些其实是vc提供的库函数
.text:00401171                 call    _isxdigit
...
.text:0040128A                 call    _sscanf
...

还有这个游戏的名字叫“独立钻石”,网上可以搜一下
2008-6-2 09:26
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
哦,原来是这样,我还以为你故意的,呵呵
机器上没装IDA,这个东西od没有办法搞定么?
是VC以源代码方式提供的?exe里似乎没有看到VC相关的dll
2008-6-2 10:00
0
雪    币: 8209
活跃值: (4559)
能力值: ( LV15,RANK:2473 )
在线值:
发帖
回帖
粉丝
6
如果od能解决所有问题,ida还能卖的出去吗
2008-6-2 10:43
0
雪    币: 2317
活跃值: (129)
能力值: (RANK:410 )
在线值:
发帖
回帖
粉丝
7
是递归搜索吗?
2008-6-3 08:49
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
8
恩,是递归,也是回溯,同时也是DFS
2008-6-3 10:18
0
雪    币: 2317
活跃值: (129)
能力值: (RANK:410 )
在线值:
发帖
回帖
粉丝
9
oh.这就是传说的DFS。学习了。是个好方法。
不过得不到最优解吧。
2008-6-3 18:18
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
恩……不知道你说的最优解是指什么?因为这里所有的解都是得到1000分,无所谓最优吧?
是指搜索速度么?
2008-6-3 21:37
0
雪    币: 2317
活跃值: (129)
能力值: (RANK:410 )
在线值:
发帖
回帖
粉丝
11
恩,最少步数。你的算法已经简介高效的完成了任务。
我只是想了解有没有最优解的算法。
2008-6-4 00:33
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
12
其实这个独立钻石游戏的步数显然是固定的,游戏每步移掉一个棋子,一共32个棋子,因此步数肯定是31步
他的算法还根据x&3限定了跳的方向,我不知道为什么
而且我怀疑他是否漏了一个检查,即检查原处是否有棋子,看这个if判断:
1
2
if(tablePath[level][offset-9]==1 //被跳处是否有棋
   && tablePath[level][offset-0x12]==3) { //对面是否是空位

似乎还应该加上tablePath[level][offset]的判断

造成的影响就是有时候棋子会增加……-_-

对于真正的独立钻石游戏的搜索,我想可以通过下面两条来加快搜索速度:
1. 只搜索空位附近的棋子(前期应该可以剪掉很多搜索分支)
2. 用hash保存棋盘状态,防止重复搜索(类似DP,即动态规划)

你有兴趣的话可以做做:)
2008-6-4 10:16
0
雪    币: 2317
活跃值: (129)
能力值: (RANK:410 )
在线值:
发帖
回帖
粉丝
13
根据网上查的关于“独钻”的资料,只剩一字的为大师,只剩一子且该子居中,则为天才。天才又根据步数分等。最早有人发现19步解法。后又发现18步的2种解法。
所以我猜想该crack应该是有很多种解法的,但最优解只有两种。这个crack没有仔细跟(我看的太慢,所以没跟),不知他做了什么限定没有。有空去研究下。
2008-6-4 11:12
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
14
哦,原来真正的独钻是可以跳过多个棋子的……怪不得步数会不一样
2008-6-4 11:53
0
雪    币: 8209
活跃值: (4559)
能力值: ( LV15,RANK:2473 )
在线值:
发帖
回帖
粉丝
15
只有上下左右4个方向啊,为什么不可以限制方向?


应该是漏掉了这个检查
2008-6-6 17:48
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
16
但是你的坐标是根据x得出来的阿
而任何一个坐标处的棋子应该可以有四个自由度把,你这样的话一个坐标上的棋子只能往一个方向跳了
2008-6-6 23:45
0
雪    币: 8209
活跃值: (4559)
能力值: ( LV15,RANK:2473 )
在线值:
发帖
回帖
粉丝
17
难道 &3不是表示4个自由度吗?
2008-6-8 09:41
0
雪    币: 123
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
算法啊,头疼,头疼,我还是走爆爆的路吧,学不会啊
2008-6-8 09:44
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
19
但是对于某个特定坐标上的棋子不是阿,始终是往x&3那个方向跳的,对么?
当然,也可以认为是特殊的限制条件,但是如果加上上面那个检查,很可能是无解的……
btw: 加上那个检查可以增加很多计算量,搜索算法需要做优化,我搜了很久都没搜完……
2008-6-8 20:50
0
雪    币: 6075
活跃值: (2236)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
20
and 3d = and 11b 保留2bit,表示范围0~2^2-1=0-3=0 1 2 3,相当于mod 4, 四个方向
2008-6-10 13:34
0
雪    币: 314
活跃值: (10)
能力值: ( LV12,RANK:570 )
在线值:
发帖
回帖
粉丝
21
顶,我也下去破了一下。蛮有趣的,绕一个大弯子然后再……其乐无穷。作者写的真的很好,我顶。
2008-6-10 16:14
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
22
哦,你说得对,是我搞错了,可以是从0-0xff的,方向信息已经包含在其中了。
另外给出针对ccfer本意的注册机(即加上棋子检查后):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <iostream>
#include <iomanip>
#include <iterator>
#include <map>
#include <vector>
using namespace std;
const int STEP=31;
const int SIZE=7;
int _table[SIZE*SIZE]={     //表格,根据调试时内存中数据所得
  0,0,1,1,1,0,0,
  0,0,1,1,1,0,0,
  1,1,1,1,1,1,1,
  1,1,1,3,1,1,1,
  1,1,1,1,1,1,1,
  0,0,1,1,1,0,0,
  0,0,1,1,1,0,0
};
vector<int> table(_table, _table+SIZE*SIZE);
vector<int> tablePath[STEP+1];
map<vector<int>, bool> failedTables[STEP];
int serial[STEP];
 
bool searchLevel(int level);
 
//复制当前表格状态,以便修改后不影响回溯
void copyTableTo(int level)
{
  tablePath[level]=tablePath[level-1];
}
 
//用x匹配注册码中第level个数值(level从0开始)
//核心算法
bool doSearch(int level, int pos)
{
  int row=((pos/4)/SIZE)%SIZE;
  int col=(pos/4)%SIZE;
  int d=pos&3;
  int offset=SIZE*row+col;
  bool ok=false;
  if(tablePath[level][offset]!=1) return false;
 
  switch(d) {
      case 0:
          //jump up
          if(row>2 && tablePath[level][offset-7]==1 && tablePath[level][offset-14]==3) {
              copyTableTo(level+1);
              tablePath[level+1][offset]=3;
              tablePath[level+1][offset-7]=3;
              tablePath[level+1][offset-14]=1;
              ok=true;
          }
          break;
          //jump right
      case 1:
          if(col<5 && tablePath[level][offset+1]==1 && tablePath[level][offset+2]==3) {
              copyTableTo(level+1);
              tablePath[level+1][offset]=3;
              tablePath[level+1][offset+1]=3;
              tablePath[level+1][offset+2]=1;
              ok=true;
          }
          break;
      case 2:
          //jump down
          if(row<5 &&tablePath[level][offset+7]==1 && tablePath[level][offset+14]==3) {
              copyTableTo(level+1);
              tablePath[level+1][offset]=3;
              tablePath[level+1][offset+7]=3;
              tablePath[level+1][offset+14]=1;
              ok=true;
          }
          break;
      case 3:
          //jump left
          if(col>2 && tablePath[level][offset-1]==1 && tablePath[level][offset-2]==3) {
              copyTableTo(level+1);
              tablePath[level+1][offset]=3;
              tablePath[level+1][offset-1]=3;
              tablePath[level+1][offset-2]=1;
              ok=true;
          }
          break;
  }
  if(ok) {
      serial[level]=pos;
      return searchLevel(level+1);
  }
  return false;
}
 
//搜索序列号第level个数值(level从0开始)
bool searchLevel(int level)
{
    if(level>=STEP) return (tablePath[level][3*7+3]==1);
    if(failedTables[level].find(tablePath[level])!=failedTables[level].end()) return false;
 
    for(int pos=0;pos<0xff;pos++) {
        if(doSearch(level, pos)) {
            return true;
        }
    }
 
    //纪录失败状态(包括因对称性而存在的等价状态),防止重复搜索
    vector<int> tempTable(tablePath[level]);
    failedTables[level][tempTable] = false;
    for(int x=0;x<SIZE;x++)
        for(int y=0;y<SIZE;y++)
            tempTable[SIZE*x+y]=tablePath[level][SIZE*y+x];
    failedTables[level][tempTable] = false;
    for(int x=0;x<SIZE;x++)
        for(int y=0;y<SIZE;y++)
            tempTable[SIZE*x+y]=tablePath[level][SIZE*x+(6-y)];
    failedTables[level][tempTable] = false;
    for(int x=0;x<SIZE;x++)
        for(int y=0;y<SIZE;y++)
            tempTable[SIZE*x+y]=tablePath[level][SIZE*(6-x)+y];
    failedTables[level][tempTable] = false;
    for(int x=0;x<SIZE;x++)
        for(int y=0;y<SIZE;y++)
            tempTable[SIZE*x+y]=tablePath[level][SIZE*(6-y)+6-x];
    failedTables[level][tempTable] = false;
    return false;
}
int main()
{
  tablePath[0]=table;
  if(searchLevel(0)) {
    cout<<"Serial found!\n";
    for(int i=0;i<STEP;i++) {
        cout<<hex<<setw(2)<<setfill('0')<<serial[i];
    }
    cout<<endl;
  } else {
      cout<<"No serial found...\n";
  }
  return 0;
}
2008-6-10 22:00
0
雪    币: 208
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
OD 里面菜单 调试 - 加入库文件,把MFC或用到的DLL的 lib文件加进去就可以识别调用的函数名了
2009-7-6 17:10
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册