ExtractRes的破解
kflnig
一不小心就把黑客手册中的ExtractRes给破了。谁叫它躺在了《黑客手册》两周年生日的那张光盘上。《黑客手册》两周年了,也拖了我4篇文章的稿费了。心中感到由衷的高兴,于是产生了本文来给《黑客手册》贺寿。好像迟到了一点,但是不要紧,只要有这份心就行。
  本文适合想学习破解知识的小鸟。很简单。本文中若无特殊说明都是16进制运算。
我用下列这些工具:OLLYDBG。假使你也使用这些。
输入用户名和伪码之后出现了,图1
 
名称:  image001.png
查看次数: 183
文件大小:  8.2 KB
图1
它都这么说了,我还有什么可说呢?典型的重启验证。
但是大家也要注意一点。不是所有的软件这样之后都会是在重启之后验证。有一部分是先处理好是否正确注册之后,然后写一个true或者false的标志到某个地方,重启的时候只是读取一下这个值而已。当然这个软件不是特例。
很不好意思的是这个软件又是半个明码比较。我们看看:
既然是重启验证,那么当然会有特定的手段了。
第一,它把注册信息写到了哪里?我猜多数是注册表。
验证,OLLYDBG载入。下断RegQueryValueA。然后F9,眼睛紧盯堆栈窗口。
 
名称:  image003.png
查看次数: 179
文件大小:  6.4 KB
图2

名称:  image005.png
查看次数: 176
文件大小:  4.6 KB
 
图3
看到图2,图3,你还有什么话说。

第二,亲爱的注册过程你在哪里。
如果有人愿意一步一步F7(步入)+F8(步过)等一步一步爬到关键的地方,我只能说佩服你的毅力。我没有毅力。
所以我要走捷径。
思考:因为它在取用户名和注册码,那么注册过程肯定在这个之后。
于是乎,在见到图3之后,粗跟踪一番。可是不顺,对注册过程的处理一无所获。
怎么办?但是得到一点有用的信息。
给大家看一下,我在粗跟踪中的成果。
0040F714   .  E8 CD3A0300   CALL ExtractR.004431E6
0040F719   .  A1 286B4800   MOV EAX,DWORD PTR DS:[486B28]
0040F71E   .  C68424 CC0100>MOV BYTE PTR SS:[ESP+1CC],10
0040F726   .  85C0          TEST EAX,EAX
0040F728   .  75 0E         JNZ SHORT ExtractR.0040F738
0040F72A   .  68 2C0E4800   PUSH ExtractR.00480E2C     ;  ASCII " [unregistered]"
0040F72F   .  8D4C24 2C     LEA ECX,DWORD PTR SS:[ESP+2C]
如果你要说0040F714处的call是关键call的话,那么我告诉你错了。关键是
0040F719   .  A1 286B4800   MOV EAX,DWORD PTR DS:[486B28]
0040F726   .  85C0          TEST EAX,EAX
0040F728   .  75 0E         JNZ SHORT ExtractR.0040F738
0040F72A   .  68 2C0E4800   PUSH ExtractR.00480E2C     ;  ASCII " [unregistered]"
这里我们看到假如在正常情况下,eax不为0那么我们就不用破解了,也就是说要是我们不擅自改,那么只要注册成功,这里的eax就是0。因为这个[unregistered]是图3中的软件标题的一部分。
 
名称:  image007.png
查看次数: 177
文件大小:  2.6 KB
图3
^_^那么eax又来自哪里呢?MOV EAX,DWORD PTR DS:[486B28]这句话明确的告诉了我们它的来历!
到这里我们就好办了。
OD其实有很多功能,我们平时没有用到。现在我们就用“硬件访问断点”伺候。
OD从新载入。在命令框中输入d    486B28。然后如图4操作
 
名称:  image009.png
查看次数: 177
文件大小:  10.8 KB
图4
我们此时看“调试——>硬件断点”可以看到如图5所示
 
名称:  image011.png
查看次数: 175
文件大小:  8.5 KB
图5
假设你现在下好了断点。我们就可以运行了。
在RegQueryValueA和刚才的硬件访问断点,我们可以很容易的确定关键地点。我们可以忽略RegQueryValueA断下之前的硬件断点,只是这个软件这种情况没有发生。当然也可以忽略0040F719   .  A1 286B4800   MOV EAX,DWORD PTR DS:[486B28]这个之后的硬件断点。
这个软件第一次断下后不远处就到了关键的地点。真善良,^_^
断在此处:0040F4B3   .  E8 AEC30400   CALL ExtractR.0045B866
这是关键:0040F509   .  E8 D2090000   CALL ExtractR.0040FEE0  ; \ExtractR.0040FEE0
进入……

第三,在第二步大功告成之后,我们就得真的和它硬碰了。这是考功力的一关。分析代码。
进入之后,显示100万行无用代码。众小鸟不要被这个气势压倒。之后是关键的东西。
先要讲不少预备知识
esp的值的改变:
push一次
esp=esp-4
pop一次
esp=esp+4

cdq和idiv指令
CDQ是符号扩展指令 ,D是dword(4字节),Q是qword(8字节) 。CDQ把EAX寄存器中的数视为有符号的数,将其符号位(即EAX的最高位)扩展到EDX寄存器,即若EAX的最高位是1,则执行后EDX的每个位都是1,结果EDX = FFFFFFFF;若EAX的最高位是0,则执行后EDX的每个位都是0,结果EDX = 00000000。这样就把EAX中的32位带符号的数变成了EDX:EAX中的64位带符号的数,以满足64位运算指令的需要,但转换后的值没变。 
若是idiv  ebp就是
eax=eax div ebp,edx=eax mod ebp。
<1>
0040FFD8  |> \8B9424 E40000>MOV EDX,DWORD PTR SS:[ESP+E4];edx指向用户名
0040FFDF  |.  33C9          XOR ECX,ECX;这里ecx清零,在下面的循环中控制次数。
0040FFE1  |.  53            PUSH EBX
0040FFE2  |.  C64424 08 68  MOV BYTE PTR SS:[ESP+8],68;注意这些初始化的值
0040FFE7  |.  8B72 F8       MOV ESI,DWORD PTR DS:[EDX-8];用户名长度
0040FFEA  |.  C64424 09 75  MOV BYTE PTR SS:[ESP+9],75
0040FFEF  |.  85F6          TEST ESI,ESI 
0040FFF1  |.  C64424 0A 79  MOV BYTE PTR SS:[ESP+A],79 
0040FFF6  |.  C64424 0B 64  MOV BYTE PTR SS:[ESP+B],64 
0040FFFB  |.  C64424 0C 6F  MOV BYTE PTR SS:[ESP+C],6F
00410000  |.  C64424 0D 6E  MOV BYTE PTR SS:[ESP+D],6E
00410005  |.  C64424 0E 67  MOV BYTE PTR SS:[ESP+E],67
0041000A  |.  C64424 0F 00  MOV BYTE PTR SS:[ESP+F],0
0041000F  |.  7E 3F         JLE SHORT ExtractR.00410050;这是和0040FFEF  TEST ESI,ESI联系的,除非你没有输入用户名,否则就不会跳走
00410011  |.  55            PUSH EBP
00410012  |.  57            PUSH EDI
00410013  |.  8D7C34 17     LEA EDI,DWORD PTR SS:[ESP+ESI+17]
<2>
00410017  |>  8B8424 F00000>/MOV EAX,DWORD PTR SS:[ESP+F0];指向用户名的首地址
0041001E  |.  BD 07000000   |MOV EBP,7
00410023  |.  8A1C01        |MOV BL,BYTE PTR DS:[ECX+EAX];ecx将依次递增,所以就是依次指向用户名的一个个字符。
00410026  |.  8BC1          |MOV EAX,ECX
00410028  |.  99            |CDQ
00410029  |.  F7FD          |IDIV EBP
0041002B  |.  0FBEC3        |MOVSX EAX,BL
0041002E  |.  8BD9          |MOV EBX,ECX
00410030  |.  0FBE5414 10   |MOVSX EDX,BYTE PTR SS:[ESP+EDX+10]
00410035  |.  03DA          |ADD EBX,EDX
00410037  |.  03C3          |ADD EAX,EBX
00410039  |.  BB 09000000   |MOV EBX,9
0041003E  |.  03C6          |ADD EAX,ESI
00410040  |.  99            |CDQ
00410041  |.  F7FB          |IDIV EBX
00410043  |.  80C2 30       |ADD DL,30
00410046  |.  41            |INC ECX
00410047  |.  8817          |MOV BYTE PTR DS:[EDI],DL;edi中最后指向真码
00410049  |.  4F            |DEC EDI
0041004A  |.  3BCE          |CMP ECX,ESI
0041004C  |.^ 7C C9         \JL SHORT ExtractR.00410017
<3>
0041004E  |.  5F            POP EDI
0041004F  |.  5D            POP EBP
00410050  |>  8D46 4D       LEA EAX,DWORD PTR DS:[ESI+4D]
00410053  |.  B9 09000000   MOV ECX,9
00410058  |.  99            CDQ
00410059  |.  F7F9          IDIV ECX
0041005B  |.  8B8424 EC0000>MOV EAX,DWORD PTR SS:[ESP+EC]
00410062  |.  80C2 30       ADD DL,30
00410065  |.  885434 10     MOV BYTE PTR SS:[ESP+ESI+10],DL
00410069  |.  C64434 11 00  MOV BYTE PTR SS:[ESP+ESI+11],0
很长,但是可以分为三部分。前面在初始化一些有用的值,中间是用用户名生成序列号,后来是解决最后一位的取值问题。
再作一点疑难知识讲解:
0040FFE2和0041004C之间只有两条push语句。
所以第一部分的esp相当于循环中的esp+8。所以循环中的[ESP+EDX+10]事实上,相当于前面的[ESP+EDX+8]。从中我们就可以找出两部分之间的对应关系。至于其中的运算,希望你自己注意。慢慢分析。如果有必要自己可以列一张表,分析寄存器的值的变化。
最后第三部分

00410065  |.  885434 10     MOV BYTE PTR SS:[ESP+ESI+10],DL
00410069  |.  C64434 11 00  MOV BYTE PTR SS:[ESP+ESI+11],0
很容易知道DL的来历,通过对ESP+ESI+10值的分析知道它是把DL的值添加到code尾。
比如说我原来生成的值是38 31 33 33 31 31 ,执行过00410065之后就是38 31 33 33 31 31 32。我们输入的用户名和注册码它都是当字符串在处理。但是你知道下面的一个0有什么意思吗?因为c字符串是以00结尾的。这里就是把用户名通过处理构建一个c字符串的解。cracker最终还是要看代码的,最后让我们用高级语言来实现一下上面我所说的。
#include <iostream.h>
#include <string.h>
void main()
{
  int sz[8]={0x68,0x75,0x79,0x64,0x6F,0x6E,0x67,0x0};
  char name[]="kflnig";
  char code[10];//这个大小是我随便写的。
  int zc=0,len,n,eax,edx,bl;
  len=strlen(name);
  for(n=0;n<len;n++)
  {
    bl=name[n];
    edx=n % 7;
    edx=sz[edx];
    eax=bl;
    eax=eax+edx+n+len;
    edx=eax %9;
    eax=eax /9;
    edx=edx+0x30;
    code[n+1]=edx;
  }
  eax=0x4d+len;
  edx=eax % 9;
  edx=edx+0x30;
  code[0]=edx;
  for (n=len;n>=0;n--)cout<<code[n];//不知道算不算是cout太聪明了,这里会输出的就是ancsii码值。
}
所以我
用户名:kflnig
序列号:8133112
下面就是一段比较。
00410072  |> /8A10          /MOV DL,BYTE PTR DS:[EAX];eax是伪码
00410074  |. |8A1E          |MOV BL,BYTE PTR DS:[ESI];esi是真码
00410076  |. |8ACA          |MOV CL,DL
00410078  |. |3AD3          |CMP DL,BL
0041007A  |. |75 1E         |JNZ SHORT ExtractR.0041009A;绝命的跳
0041007C  |. |84C9          |TEST CL,CL
0041007E  |. |74 16         |JE SHORT ExtractR.00410096
00410080  |. |8A50 01       |MOV DL,BYTE PTR DS:[EAX+1]
00410083  |. |8A5E 01       |MOV BL,BYTE PTR DS:[ESI+1]
00410086  |. |8ACA          |MOV CL,DL
00410088  |. |3AD3          |CMP DL,BL
0041008A  |. |75 0E         |JNZ SHORT ExtractR.0041009A;绝命的跳
0041008C  |. |83C0 02       |ADD EAX,2
0041008F  |. |83C6 02       |ADD ESI,2
00410092  |. |84C9          |TEST CL,CL
00410094  |.^\75 DC         \JNZ SHORT ExtractR.00410072
我之所以把这个代码贴出来是因为这个代码有点怪,用这种奇怪的方式一下子比较两个byte。
行文到此结束了,小鸟要努力看懂上面循环中一段运算的代码。找到注册码花了我2分钟,写这篇文章花了5个小时,值得!
最后,祝《黑客手册》生日快乐。
浙江省绍兴县柯桥中学高二(3)班李宁