天天看点

“金山杯2007逆向分析挑战赛”第一阶段第一题分析

  题目来自于如下网址:

  

  第13篇 论坛活动 \

金山杯2007逆向分析挑战赛 \ 第一阶段 \ 第一题 \ 题目 \ [第一阶段 第一题];

  现将此题目概述粘贴如下:

  crackme.exe 是一个简单的注册程序,见附件,请写一个注册机: 要求:   1. 注册机是keygen,不是内存注册机或文件patch   2. 注册机可以使用asm,vc,bc,vb,delphi等语言书写,其他谢绝使用。   3. 注册机必须可以运行在windows系统上。   ......

  昨天我偶然浏览到这里,看到这个题目,于是看了下,题目早就已经是过去时了,但今天依然可以看看,大概用了半天时间求解此题目(本来以为挺简单,但实际上还是少有点难度的)。把附件下载下来可以看到,crackme.exe

是一个仅有 1.85 kb 的 windows

对话框程序。界面也非常简单,只有两个文本框用于获取用户名,注册码,和一个注册按钮,当点击注册按钮时,如果注册码正确,会弹出 messagebox 显示

"ok!!",否则会显示 "fail!" 。

“金山杯2007逆向分析挑战赛”第一阶段第一题分析

  很显然,如果我们直接修改这个 exe,跳过其注册码检测或者修改里面的跳转逻辑,修改调用 decrypttext 的参数,修改栈上的密文数据等等,有

无数种方法可以直接令这个程序显示 “ok!!”

消息框。但是,这显然不是这个题目的目的(因为这太简单了,也无需理解那些验证用的汇编代码)。根据题目要求,可以看出出题者的要求是,原程序是不能修改的,解题的人通过阅读和分析验证注册码的汇编代码,得出其验证思路,然后根据此写出注册机(keygen),实现给出任意的

username,得出 serialno = f (username) ,也就是要求找出 f 关系,并用编程语言实现注册机。

  因此我们用 ida 汇编这个极小的程序,可以看到它的组成非常简单,下面给出一些主要的汇编代码,并部分的翻译成 c 语言。

  首先是,当点击“注册按钮”,程序将会检测注册码是否正确。在对话框的窗口过程中,可以找到一段重要代码,现在把它提取出来作为一个注册机要用到的重要函数,即根据用户名字符串得到一个整数特征值,汇编代码省略,这里把这个函数翻译为

c 语言如下:

  接下来,程序将会调用一个校验注册码的函数,在这个函数中同时会弹出

messagebox,这个函数由几块功能组成,也是此题目必须要分析的重点,这个函数的原型可以推测出为形如:

  void checkserialno(int nuserval, char* pserialno);

  通过这个函数,我们可以很容易看到它是如何弹出显示着 “fail!!"

的消息框的,该函数首先在栈上放置成功和失败的文本密文,然后通过检测结果,把相应的密文地址传送到解密函数(这里称之为

decrypttext),解密函数把解密后的明文放入栈上的缓冲区,然后以此调用

messagebox,非常明显,这是模拟软件的常规保护方法,在实际软件中都会将关键和敏感信息进行隐藏,当然这也不是这道题目的重点,因为很容易就找到密文的位置,为紧靠

ebp 附近的两个字节数组。

  在代码段(.text) 的第一个函数就是解密函数,其原型形如:void decrpttext(const byte* psecret, char*

pplaintextbuffer);  这个加解密非常简单,只是把一个数组用另一个事先拟定好的 key

数组线性的异或了一下而已,所以这不是本题重点,省略不提。

  下面给出的是这个程序的关键汇编代码,也就是 checkserialno

的完整代码,此题目的本意正是要求读懂这个函数的逻辑,并找出注册机算法。这个函数较长,但分开割裂不太好,所以完整粘贴如下(前面有一大段花里胡哨的稀奇古怪的指令,一些变量赋值操作也通过隔开少许的

push / pop 来完成,最恶劣的是 dword 指针竟然地址不对齐,仿佛是人为故意设置的障碍):

  在汇编代码右侧,我已经大致写了一定的注释,下面把这个校验注册码的函数翻译到 c 语言如下,然后再推断注册机算法。

  在高级语言版本中,我使用了一些语言方法,来避免在高级语言代码把汇编中的那些相对跳转直译为

goto,大体上将是等效的。从上面的代码中可以看出检查注册码的重要标准,如果我们把注册码看做输入,实际上在扫描注册码的过程中,就是 nuserbits

这个元素为二元的数组变化的过程,nuserbits

是根据用户填写的用户名计算得到的数组,所以它的元素和用户名相关,是不确定的,注册码扫描结束后,这个数组必须所有元素都为 0。因此,相当于以注册码为驱动。

  从上面的代码逻辑中我们还能看到很重要的一点,要修改 nuserbits 的某一位(假设为

nwhichbit),那么必须满足以下条件,nuserbits 必须处于如下状态:

index:

1

2

3

...

5

6

7

8

9

value:

nwhichbit

x

  也就是说,当我们要切换某一位的状态时,从这一位向低位方向(左侧)看去,应该是 【0】* n + 【1】 的组合(紧邻的低位为 1, 其余均为

0)。最低位(索引 1 )可以随时修改,因为左边已经没有位了,当然就没必要向左看了。

  所以这样就提示注册机算法,可以用递归函数 ( 下面代码中的 setbit 函数 ) 来求解。注册码是由一系列的 nwhichbit

(要切换状态的位索引)组成,这个索引是根据注册码的当前位得到的。换句话说,求注册码相当于找出这样一个有序序列,{ b1, b2, b3, ..., 

} ,每一步都是合法切换,处理后 nuserbits 所有位都为 0。

  这里给出一个例子来说明,以校验的索引范围为从 1 到 4 ,假设 nuserbits 的初始状态为 { 0, 0, 0, 1 } (索引从 1

开始),则如何经过一系列上述规则允许的元素切换,把它变为 { 0, 0, 0, 0 },参考下表(整个过程让我想起了汉诺塔):

index

[1]

[2]

[3]

[4]

nuserbits:

initial state

  把最右侧一列的索引值连在一起,就是一个我们需要设计的位置序列 { 121312141213121 },依次对这些位置进行开关切换(把

nuserbits 每一个元素想象成一个开关)后,就可以让 nuserbits 数组变为全为 0

的状态(注册码正确的条件)。如果不需要和用户特征值关联到一起的话,那这这个序列就是注册码。但原程序中是通过注册码和用户特征值(nuserval)混编后得到这个调整序列,所以我们只需要对这个调整序列做个逆运算,“剔除”其中的用户特征值成分,即可得到实际注册码。

  为此我们给出注册机的如下多个函数,即为题目要求的注册机,显然,注册码如果不限长度,可以有无数多个,但我们当然选择生成简短的。

  这里,附上原题目程序,我写的注册机的源码以及可执行文件的压缩包下载:

  注册机截图,用 vc 写的一个对话框程(当然写成 console 程序更容易,但 windows

程序用户更熟悉):

“金山杯2007逆向分析挑战赛”第一阶段第一题分析

  最后,随便给出一个注册机计算出的注册码作为结束(由于注册码太长,所以插入了换行和缩进):

  user: hoodlum1980

serial:

  32991678634237933710214174007422371074154242840289212146582658628926588332475377

  00312194908492670084956232944239113126781668321916686352376337402131741074023730

  74054252845289812136583658428946587332575347006121849094924700049552320442991171

  26681678329916886342377337102161740074123710742542428462892121865826585289265893

  32475357003121149084925700849572329442091131260816683209166863623763372021317430

  740237207405426284528931213658

上一篇: 焦距
下一篇: LCX端口转发