我们已经见识过了各种各样的
注册算法,今天要为
大家介绍的是一种古老而又经典的算法类型:“查表计算”。什么是查表计算呢?简单说明就是,
软件作者在编写
软件的时候会在内部设定一张数据表,这个数据表有些密码表的味道,除了
软件作者,我们根本就不知道这张表是什么样子的,也不知道它的内容是什么;在平时使用
软件的过程中也不会用到这个表,在不涉及到与注册有关的操作时,这张表就一直默默无闻地待在程序里。然而,当我们输入注册名等与注册相关的信息时,这张数据表就开始发挥它的作用了。这个时候,程序会根据我们输入的注册信息,按照一定的规律或是顺序从数据表中取出相对应的内容,然后将这些内容在进行一些处理或是直接组合成注册码。由此可见,这张数据表与
软件的注册算法的关系是非常密切的,也可以说是算法的核心内容。
其实,查表计算的原理非常简单,我就举例说明一下吧。想象一下我们正在上课,刚开学老师需要叫一些同学来打扫卫生,老师拿着名单说“所有双号的同学扫地,单号的同学拖地”,大家听到了命令便纷纷干起活来。就是这样一个简单的过程,恰恰反映了查表计算这一类项算法的最基本的原理。我们把程序中的那张数据表比作老师手里的名单,老师根据学号的不同来给大家安排不同的任务。同样,在
软件中,程序也会根据注册信息的不同,而从数据表中取出不同的内容。
听起来原理还是挺简单的,具体到程序又是通过怎样的一种形式表现出来的呢?下面我们就以一个Crackme为例,详细介绍一下“查表计算”在程序中的实际应用。
对于用OD载入寻找关键代码等过程在这里就省略了,因为Crackme没有加壳,通过字符串查找很快就可以找到对应的关键代码。详细代码如下,我们来一一进行分析。
00401127 > /6A 00 push 0; /我们在这里设置断点00401129 . |6A 00 push 0;|wParam = 00040112B . |6A 0E push 0E;|Message = WM_GETTEXTLENGTH0040112D . |6A 03 push 3;|ControlID = 30040112F . |FF75 08 push d<a href="http://www.at
cpu.com/bbs/thread-htm-fid-125.html" target="_blank">word</a> ptr [ebp+8];|hWnd00401132 . |E8 41020000 call <jmp.;USER32.SendDlgItemMessageA>;SendDlgItemMessageA00401137 . |A3 AF214000 mov [4021AF], eax;取注册名的位数0040113C . |83F8 00 cmp eax, 0;注册名位数与0相比0040113F . |0F84 D5000000 je 0040121A;没有输入注册名就跳向失败00401145 . |83F8 08 cmp eax, 8;注册名位数与8相比00401148 . |0F8F CC000000 jg 0040121A;大于8位也跳向失败
首先,程序对注册名进行检查。在这里,程序要求注册名的长度必须小于8位,这是第一个要求。
0040114E . |8BF0 mov esi, eax00401150 . |6A 00 push 0;/lParam = 000401152 . |6A 00 push 0;|wParam = 000401154 . |6A 0E push 0E;|Message = WM_GETTEXTLENGTH00401156 . |6A 04 push 4; |ControlID = 400401158 . |FF75 08 push d<a href="http://www.atcpu.com/bbs/thread-htm-fid-125.html" target="_blank">word</a> ptr [ebp+8];|hWnd0040115B . |E8 18020000 call <jmp.;USER32.SendDlgItemMessageA>;SendDlgItemMessageA00401160 . |83F8 00 cmp eax, 0;将注册码的位数与0相比00401163 . |0F84 B1000000 je 0040121A;没有输入注册码就失败00401169 . |3BF0 cmp esi, eax;注册名位数与注册码位数相比0040116B . |0F85 A9000000 jnz 0040121A;不相等就跳向失败
检验完了注册名,自然就是对注册码的检验。程序要求注册名与注册码的位数必须相同才能过关。检验完这两个主要的信息,下面就开始算法计算了。究竟查表计算是怎样进行的呢?我们分析看看。
0040119C > |41 inc ecx0040119D . |0FBE81 602140>movsx eax, byte ptr [ecx+402160];取注册名每一位的ASCII码004011A4 . |83F8 00 cmp eax, 0;是否为0; 004011A7 . |74 32 je short 004011DB004011A9 . |BE FFFFFFFF mov esi, -1004011AE . |83F8 41 cmp eax, 41;是否小于41004011B1 . |7C 67 jl short 0040121A;小于就跳向失败004011B3 . |83F8 7A cmp eax, 7A;是否大于7A004011B6 . |77 62 ja short 0040121A;大于就跳向失败004011B8 . |83F8 5A cmp eax, 5A;与5A比较004011BB . |7C 03 jl short 004011C0;小于就跳走004011BD . |83E8 20 sub eax, 20;ASCII码减去0x20 004011C0 > |46 inc esi
在计算之前,程序又对注册名进行了更进一步的检验。第一检验是对注册名的长度作了限制,这里则是对注册名的内容进行检验。我们注意到,程序首先取出了注册名每一位的ASCII码,然后将ASCII码分别与41和7A比较,如果小于41或者大于7A都会直接导致失败。所以,注册名每一位的ASCII码必须在41与7A这个范围之间。现在大家可能就有疑问了,这里的41与7A到底是什么呢?其实,在ASCII码表中41对应的字母是A而7A对应的字母则是字母z。所以,这段代码检验注册名的作用就是,要求注册名的每一位都是字母而不能是数字或者其他符号。当注册名满足是字母的条件后,我们看到,如果注册名某一位的ASCII码大于5A这个值,那么就将这个ASCII码减去20,这又是在干什么呢?我们先看看5A对应的是哪个字母,原来5A对应的是字母Z,也就是大写字母中的最后一位。现在我们就明白了,因为Z是大写字母的最后一位,如果ASCII码大于Z的ASCII码的话,那么就可以判断,这个字母应该是小写字母中的某一个了。既然将大写字母的ASCII码减去0x20,那么这个问题就清楚了,就是将注册名中的小写字母转换成大写字母。例如,注册名是tcxb,那么经过这里的处理就变成了TCXB。
将注册名转换完成之后,下面就开始正式的计算了。我们继续看看这个传说中的“查表计算”是怎么计算的呢?经过了一些无关紧要的代码,我们来到了以下代码处。
004011C1 . |0FBE96 172040>movsx edx, byte ptr [esi+402017];这里我们需要注意004011C8 . |3BC2 cmp eax, edx;是否相等004011CA .^|75 F4 jnz short 004011C0;不相等就继续寻找
注意这里的第一句代码,它指向了一个地址。我们去这个地址看看,在OD的命令行中输入这个命令:“D esi+402017”就可以在数据窗口中看到以下的内容。
00402017 41 31 4C 53 4B 32 44 4A 46 34 48 47 50 33 51 57 A1LSK2DJF4HGP3QW
00402027 4F 35 45 49 52 36 55 54 59 5A 38 4D 58 4E 37 43 O5EIR6UTYZ8MXN7C
00402037 42 56 39 BV9
这是一个什么东西呢?经过对代码的跟踪与分析我们发现,这些数据其实是一张数据表:A1LSK2DJF4HGP3QWO5EIR6UTYZ8MXN7CBV9,程序会将注册名中的字母在这里一一找到,并且记下它们各自的位置。为了方便大家理解,我们给数据表中的这些字符按照顺序标上1~35的序号,例如TCXB这四个字母,在这个数据表中对应的位置就分别是24、32、29、33。保存了对应的位置后,程序开始进行下一步计算。
004011CC . |0FBE86 3C2040>movsx eax, byte ptr [esi+40203C];这里出现了第二张表004011D3 . |8981 94214000 mov [ecx+402194], eax;将每一步的结果保存004011D9 .^|EB C1 jmp short 0040119C;循环计算
这里的代码又指向了一个
内存中的地址,我们还是采用命令的方法到内存中去看看里面到底有些什么内容。输入命令“D ESI+40203C”便看到了以下内容。
00402033 53 55 37 43 53 4A 4B SU7CSJK
00402043 46 30 39 4E 43 53 44 4F 39 53 44 46 30 39 53 44 F09NCSDO9SDF09SD
00402053 52 4C 56 4B 37 38 30 39 53 34 4E 46 RLVK7809S4NF
由此,我们就又可以整理出来一张数据表了:SU7CSJKF09NCSDO9SDF09SDRLVK7809S4NF。这个表又是干什么用的呢?还是按照1~35为它们分别标上序号吧。
原来,在这里程序按照从第一张表中得到的位置信息,在这里寻找对应位置上的字符。呵呵,这么说大家可能有些听不懂了,还是举例来说吧。联系上一步的计算,注册名TCXB在数据表1中对应的位置分别是:23、31、28、32,那么在这里程序就将在表2中寻找对应位置的字母,即将表2中第23、31、28、32位字符取出来。在SU7CSJKF09NCSDO9SDF09SDRLVK7809S4N中,对应位置的字母分别是RS84,这个字符串就很重要了