黑客字典II算法分析

来源:互联网 发布:淘宝手机排行怎么看 编辑:程序博客网 时间:2024/04/30 02:36

http://hi.baidu.com/hacksign/blog/item/08c65a55ffca13c5b645aea9.html

http://www.hacksign.cn

                                                                                     Hacksign
       一、起因
       拿到新的一期“黑防”要做的第一件事情是什么?答案是——拆开包装袋!(哎呀,谁拿砖头扔我?)呵呵,开个玩笑。拿到新一期杂志的第一件事情当然是看一下光盘上有没有什么好东东啦。这不,我就在06年第7期的杂志光盘的漏天剑中找到了《黑客字典II》这个好东西。虽然说以前用过这款软件,不过那时刚刚开始接触网络安全,也就是说——当时自己根本不知道这款软件是干什么用的:)可是随着技术的慢慢积累,越来越多的和各种密码打交道(最近就是这样,所以才会注意到这个软件)才意识到有一个好的字典是多么的重要。另外,还要在这里感谢负责光盘这一板块的小编能想的这么周到,为我们提供注册码。不过一想到软件在注册时用的是别人的信息,鄙人就感到浑身不爽。于是乎,破解的念头就这样产生了。
二、破解
       废话说了这么多,下面我们进入本文的主题。看一下丫穿的什么马甲先:


看来并没有加任何的壳,程序是用visual c++编译的。下面开始破解。
      在破解之前,建议大家先熟悉一下程序的注册流程,养成好的习惯是很重要的!程序是用的用户名+注册码的注册方式,并没有用到当下很流行的重启验证的注册方式,这样省去不少麻烦。考虑到程序是用visual c++编译的,所以在取得用户名和密码的时候用的无外乎是GetWindowText或者GetDlgItem函数,而由我们向程序提供的注册信息来看,程序很有可能是由我们提供的用户名得到注册码,然后再与我们提供的注册码比较,复杂一点的话就将注册码处理一下再比较(这样的好处是可以避免明码比较从而使注册信息不在内存中以可辨认的形式出现)这样破解者就不能通过WinHex之类的软件搜索内存得到注册码了。
      那么,程序用的是什么方式来注册呢?考虑到此程序的完成时间,猜测程序使用的应该是明码比较的方式。添上注册信息,然后点注册,这时软件弹出注册错误的对话框,这时不要点确定,用WinHex打开黑客字典的主要内存来搜索添入的假的注册码(这里是188990139),找到后注意观察内存的数据,在不远处就可以看到一个可疑的字符串U8v866zG,没错,这就是注册码了:


其实这并不是什么新的技术,在这里加以说明是想让大家明白,为什么有的程序可以在内存搜索到注册码,而有的却不可以。研究一个问题,做到透彻才对噢。
     三、算法分析
     前面我带领大家在内存中搜索到了真正的注册码,不过总感觉还是不爽。那么,就让我继续带领大家找到这个程序的注册算法吧!
     前面我说过程序在取得用户名和注册码时会使用GetWindowText或者GetDlgItem函数,这个就是破解黑客字典II的突破口了。首先用OllyDGB载入黑客字典II,F9让程序运行,然后按快捷键crtl+n呼出程序的函数导入表,随便的激活一行然后输入GETWINDOWTEXT(不分大小写)OD就会自动为我们定位这个函数了,找到这个函数后单击鼠标右键选择“在每个参考设置断点”、用同样的方法在函数GetDlgItem设置断点(后来证明在此函数上设置断点是没有必要的,不过小心点总是没有错的)注册,输入以下信息:
用户名:Hacksign
注册码:188990139
点注册,程序马上被断下这时,观察一下堆栈:


注意程序在进行函数调用时向堆栈压入的Buffer参数,这可是函数保存得到的对话框内容的内存的地址哦,在内存中找到这个地址后按ctrl+f9让GetWindowText执行到返回就会看到内存地址0012ee04已经填充上了我们输入的用户名。下面就要耐心一点了,f8单步跟踪,在此期间程序会再一次的调用GetWindowText函数,这一次是得到注册码,离胜利不远了,继续前进。f8继续单步跟踪,直到看到如下代码:
00401BFA      . 8D7C24 20         LEA EDI,DWORD PTR SS:[ESP+20];用户名地址
00401BFE      . 83C9 FF           OR ECX,FFFFFFFF
00401C01      . 33C0              XOR EAX,EAX
00401C03      . F2:AE             REPNE SCAS BYTE PTR ES:[EDI]
00401C05      . F7D1              NOT ECX
00401C07      . 49                DEC ECX
00401C08      . 0F84 16020000     JE UltraDic.00401E24
00401C0E      . 8DBC24 8400000>LEA EDI,DWORD PTR SS:[ESP+84];注册码地址
00401C15      . 83C9 FF           OR ECX,FFFFFFFF
00401C18      . F2:AE             REPNE SCAS BYTE PTR ES:[EDI]
00401C1A      . F7D1              NOT ECX
00401C1C      . 49                DEC ECX
00401C1D      . 0F84 01020000     JE UltraDic.00401E24
…………
上面的代码的作用是计算用户名和注册码的长度,如果两者有一个为空的话就跳到00401E24处理,下面重头戏开始了(很奇怪小榕没有把算法封装在一个函数中,而是直接在代码中):
00401C33      > 0FBE7C34 20       MOVSX EDI,BYTE PTR SS:[ESP+ESI+20]          ;     依次取用户名中的每一位
00401C38      . 8BC7              MOV EAX,EDI                                 ;     eax初始化为用户名相应的值
00401C3A      . B9 0A000000       MOV ECX,0A                                  ;     ecx初始化为0x0A
00401C3F      . 99                CDQ
00401C40      . F7F9              IDIV ECX                                    ;     eax/ecx
00401C42      . 8BCA              MOV ECX,EDX                                 ;     余数存入ecx
00401C44      . 81E2 01000080     AND EDX,80000001                            ;     余数and0x80000001
算法小结:1、设用户名为数组RegName[],RegName[i]为用户名的第i+1位
            2、用户名中的每一位和0x0a相除,设于数为YuShu,Yushu=RegName[i]%0x0a
       3、令余数与0x80000001做“与”运算,temp=YuShu&0x80000001
---------------------------------------------------------------------------------------------------
00401C4A      . 79 05             JNS SHORT UltraDic.00401C51                 ;     结果不是负数就跳
00401C4C      . 4A                DEC EDX
00401C4D      . 83CA FE           OR EDX,FFFFFFFE
00401C50      . 42                INC EDX
算法小结:1、如果temp小于0就将余数减一后与0xFFFFFFFE做“或”运算然后再加一,temp=((temp--)|0xFFFFFFFE)++
---------------------------------------------------------------------------------------------------
00401C51      > 75 16             JNZ SHORT UltraDic.00401C69                 ;     结果不为0就跳
00401C53      . 8BC7              MOV EAX,EDI                                 ;     为零就将此位赋值给eax
00401C55      . B9 1A000000       MOV ECX,1A                                  ;     ecx=0x1A
00401C5A      . 99                CDQ
00401C5B      . F7F9              IDIV ECX                                    ;     eax/ecx
00401C5D      . 80C2 41           ADD DL,41                                   ;     余数加上0x41
00401C60      . 889434 4801000>MOV BYTE PTR SS:[ESP+ESI+148],DL            ;     作为注册码存入地址
00401C67      . EB 2E             JMP SHORT UltraDic.00401C97
算法小结:1、如果temp等于0就用RegName[i]的值除以0x1a,得到的余数再加上0x41,temp=RegName[i]%0x1a+0x41
       2、temp的值就做为注册码的第i位保存在ESP+ESI+148地址
---------------------------------------------------------------------------------------------------
00401C69      > 8BC1              MOV EAX,ECX                                 ;     余数传递给eax
00401C6B      . BB 03000000       MOV EBX,3                                   ;     EBX=0x03
00401C70      . 99                CDQ
00401C71      . F7FB              IDIV EBX                                    ;     eax/ebx
00401C73      . 85D2              TEST EDX,EDX                                ;     余数是0吗?
00401C75      . 75 16             JNZ SHORT UltraDic.00401C8D                 ;     不是0就跳
00401C77      . 8BC7              MOV EAX,EDI                                 ;     如果是0.....
00401C79      . B9 1A000000       MOV ECX,1A                                  ;     ecx=0x1a
00401C7E      . 99                CDQ
00401C7F      . F7F9              IDIV ECX                                    ;     就将这一位除以0x1a
00401C81      . 80C2 61           ADD DL,61                                   ;     然后余数+0x61
00401C84      . 889434 4801000>MOV BYTE PTR SS:[ESP+ESI+148],DL            ;     存入内存作为注册码
00401C8B      . EB 0A             JMP SHORT UltraDic.00401C97
算法小结:1、如果YuShu&0x80000001的值大于0就让YuShu除以0x03然后判断所得余数是否为0
      2、如果不是0就跳到00401C8D
      3、如果是零就用用户名中该位除以0x1a后所得的余数加上0x61,temp=RegName[i]%0x1a+0x61
      4、temp作为注册码的第i位存入内存
---------------------------------------------------------------------------------------------------
00401C8D      > 80C1 31           ADD CL,31                                   ;     余数加上0x31
00401C90      . 888C34 4801000>MOV BYTE PTR SS:[ESP+ESI+148],CL            ;     作为注册码保存
算法小结:1、如果YuShu除以0x03后余数为0
       2、就将该余数加上0x31,temp=YuShu%0x03+0x31
       3、temp作为注册码的第i位存入内存
---------------------------------------------------------------------------------------------------
00401C97      > 8D7C24 20         LEA EDI,DWORD PTR SS:[ESP+20]               ;     用户名地址传递给edi
…………
00401CA0      . 46                INC ESI                                     ;     指向用户名的下一位
00401CA1      . F2:AE             REPNE SCAS BYTE PTR ES:[EDI]
00401CA3      . F7D1              NOT ECX
00401CA5      . 49                DEC ECX                                     ;     计算用户名长度
00401CA6      . 3BF1              CMP ESI,ECX                                 ;     用户名取完了吗?
00401CA8      .^72 89             JB SHORT UltraDic.00401C33                  ;     小于,也就是没取完,跳回继续循环
      四、编写注册机
      这部分没有什么好说的,如果大家明白了程序的注册算法,那么写出注册机就是很简单的事情了,如果大家还没有明白的话,希望在看过注册机的代码之后可以对程序的注册算法有进一步的理解。下面就是注册机的源代码:
#include "stdio.h"
#include "string.h"

void main()
{
     char RegCode[20],RegName[20];
     int i=0,count,temp,YuShu;
     scanf ("%s",RegName);
  
     count=strlen(RegName);     //得到用户名的长度
     while (i<=count)
     {
      temp=(RegName[i]%0x0a)&0x80000001;
      YuShu=RegName[i]%0x0a;
      if (temp<0)
{
      YuShu--;
      temp=YuShu|0xFFFFFFFE;
      YuShu++;
}
if (temp==0)
     {
      temp=RegName[i]%0x1a+0x41;
         RegCode[i]=temp;
      }
else
{
      temp=YuShu%0x03;
       if (temp==0)
        {
      RegCode[i]=RegName[i]%0x1A+0x61;
      }
       else
        {
      temp=YuShu+0x31;
      RegCode[i]=temp;
      }
}   
i++;
     }
RegCode[--i]='\0';
printf ("Your RegCode is:\n%s\nHave a good time ;)\n",RegCode);
}
该注册机在Win XP SP2、VC6下编译成功:

注册成功后程序会在c盘下面生成regbanyet.dat文件,该文件具有隐藏和只读属性,把这个文件删除掉就又可以再玩一边啦。

原创粉丝点击