首页
社区
课程
招聘
[旧帖] [邀请已发,结帖]新手也写注册机 0.00雪花
发表于: 2009-6-8 11:32 1424

[旧帖] [邀请已发,结帖]新手也写注册机 0.00雪花

2009-6-8 11:32
1424
既然是新手就只能玩简单的。
调试的程序是《加密与解密》第三版的第二章CrackMe例子,虽然附有源程序不过计算序列号的算法很简单所以新手也能逆向推出来;书本里对源码部分只有正向流程的解说,我自己试了一下,花了点时间把汇编代码逆向翻译成c 函数,这里给出过程,供新手参考用,同时欢迎各位同好挑错。


[课程]FART 脱壳王!加量不加价!FART作者讲授!

收藏
免费 0
支持
分享
最新回复 (12)
雪    币: 69
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
首先在ollydbg里定位到计算序列号的函数,给GetDlgItemText下断点、给check按钮下鼠标消息断点、找出出错提示字符串、在引用串的地方下断点,都很容易推断出计算序列号函数的位置,书里有前两种的描述这里就不多说。

下面是olldbg里截出的代码:

00401340  /$  55            push    ebp
00401341  |.  8B6C24 0C     mov     ebp, dword ptr [esp+C]
00401345  |.  56            push    esi
00401346  |.  57            push    edi
00401347  |.  8B7C24 18     mov     edi, dword ptr [esp+18]
0040134B  |.  B9 03000000   mov     ecx, 3
00401350  |.  33F6          xor     esi, esi
00401352  |.  33C0          xor     eax, eax
00401354  |.  3BF9          cmp     edi, ecx
00401356  |.  7E 21         jle     short TraceMe.00401379                                ;跳转1
00401358  |.  53            push    ebx
00401359  |>  83F8 07       /cmp     eax, 7
0040135C  |.  7E 02         |jle     short TraceMe.00401360                                ;跳转2
0040135E  |.  33C0          |xor     eax, eax
00401360  |>  33D2          |xor     edx, edx
00401362  |.  33DB          |xor     ebx, ebx
00401364  |.  8A1429        |mov     dl, byte ptr [ecx+ebp]
00401367  |.  8A98 30504000 |mov     bl, byte ptr [eax+405030]                        ;取全局变量的值1
0040136D  |.  0FAFD3        |imul    edx, ebx
00401370  |.  03F2          |add     esi, edx
00401372  |.  41            |inc     ecx
00401373  |.  40            |inc     eax
00401374  |.  3BCF          |cmp     ecx, edi
00401376  |.^ 7C E1         \jl      short TraceMe.00401359                                ;跳转3
00401378  |.  5B            pop     ebx
00401379  |>  56            push    esi                              ; /<%ld>
0040137A  |.  68 78504000   push    TraceMe.00405078                 ; |%ld                        ;取全局变量的值2
0040137F  |.  55            push    ebp                              ; |s
00401380  |.  FF15 9C404000 call    near dword ptr [<&USER32.wsprint>; \wsprintfA
00401386  |.  8B4424 1C     mov     eax, dword ptr [esp+1C]
0040138A  |.  83C4 0C       add     esp, 0C
0040138D  |.  55            push    ebp                              ; /String2
0040138E  |.  50            push    eax                              ; |String1
0040138F  |.  FF15 04404000 call    near dword ptr [<&KERNEL32.lstrc>; \lstrcmpA
00401395  |.  F7D8          neg     eax
00401397  |.  1BC0          sbb     eax, eax
00401399  |.  5F            pop     edi
0040139A  |.  5E            pop     esi
0040139B  |.  40            inc     eax
0040139C  |.  5D            pop     ebp
0040139D  \.  C3            retn

我们就是要把这段代码搞到自己写的注册机程序里。
5个我加了中文注释的地方是首先需要处理的地方,它们引用的内存地址都是针对原程序来说的,移植到新程序里要做处理;其实3个跳转都是相对地址跳转,用一串db指令把机器码写到新程序里也能用,但可读性很差,所以换个简单的方式,给跳转的目的地址加上标签,jmp label即可;另一个是取全局变量的值的地方,如下:
mov     bl, byte ptr [eax+405030]
这种一看地址不在堆栈范围的就是全局变量,eax稍后再解释,这里我们在ollydbg里查看地址405030的值,发现是8个字节的数值,用C语言语法描述就是:
BYTE byData[] = { 0x0C, 0x0A, 0x13, 0x09,  0x0C, 0x0B, 0x0A, 0x08};
另一个是push    TraceMe.00405078,查看地址指向的值,用C表达就是 char *szData = "%ld";
2009-6-8 11:45
0
雪    币: 69
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
加上C风格的函数头,改写如下:

BYTE byData[] = { 0x0C, 0x0A, 0x13, 0x09,  0x0C, 0x0B, 0x0A, 0x08};
char *szData = "%ld";
int __declspec(naked) CalRegCode(char *szCode1,char *szName,int nNameLen)
{
   __asm
   {
                push    ebp;
                mov     ebp, dword ptr [esp+C]
                push    esi;
                push    edi;
                mov     edi, dword ptr [esp+18];
                mov     ecx, 0x03;
                xor     esi, esi;
                xor     eax, eax;
                cmp     edi, ecx;
                jle     short L1;                                ;跳转1
                push    ebx;

L3:
                cmp     eax, 0x07;
                jle     short L2;                                ;跳转2
                xor     eax, eax;

L2:
                xor     edx, edx;
                xor     ebx, ebx;
                mov     dl, byte ptr [ecx+ebp];
                mov     bl, byte ptr [eax+byData];                        ;取全局变量的值1
                imul    edx, ebx;
                add     esi, edx;
                inc     ecx;
                inc     eax;
                cmp     ecx, edi;
                jl      short L3;                                ;跳转3
                pop     ebx;

L1:
                push    esi;               
                push    szData;                                          ;取全局变量的值2
                push    ebp;                  
                call    near dword ptr wsprint;
                mov     eax, dword ptr [esp+0x01C];
                add     esp, 0x00C;
                push    ebp;                     
                push    eax;                     
                call    near dword ptr lstrc;
                neg     eax;
                sbb     eax, eax;
                pop     edi;
                pop     esi;
                inc     eax;
                pop     ebp;
                retn;
        }
}

代码搬过来时别忘了给里面的数值加上0x前缀,因为在原来是16进制代码,放到C编译器里要加说明,当然自己手工转换成十进制数值也没问题。
关键字__declspec(naked)是让C编译器(我用vs2005)不要给函数生成入栈出栈的预处理后处理代码,普通的函数编译器肯定要生成这些代码的,但我们现在是搬来的代码已经做了这部分工作所以让编译器跳过这一步;这里有个要注意的地方是看看调用函数后栈怎么清理,Cdecl调用是由调用者清理,但是我们基本把asm代码限制在一个函数里,所以后面还要加一条 add  esp,0xXX的指令才行,这就相当于把Cdecl调用改成Stdcall调用了,否则你就需要在调用CalRegCode函数的后面加这句 add esp的指令。
这里我没加,因为最后我发现这个函数需要改写。

函数原型的确定,主要由跟踪原程序,看调用前它压什么值入栈、进函数后取哪些堆栈的值、怎么用这些值来确定,有经验的可以不必调试程序,直接看汇编代码也能大致确定。

这个函数已经可以在新程序里运行,完成的功能和它在原程序里是一致的;这种方法是最简单的注册机编写方法,不需要深入分析代码逻辑、翻译成高级语言,把相关计算步骤的汇编代码拷贝过来、改造一下就可以给自己用。
2009-6-8 12:08
0
雪    币: 50
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
00000000000000
2009-6-8 12:08
0
雪    币: 69
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
int  CalRegCode(char *szCode1,char *szName,int nNameLen);

这个函数,在原程序里是传入界面输入的用户名、序列号,然后计算出序列号,和输入的序列号对比,判断是否正确,而正确的序列号只存在这个函数里,没保存下来,出去后就没有了。

但我们现在需要的是函数返回一个根据用户名计算出来的、正确的序列号,函数声明应该是这么样的:
int CalRegCode_asm(char *szName,int nNameLen);

因为它计算出来的肯定是一个数值,所以可以把这个值直接返回,由调用者输出到界面上。

那么该怎么改呢,首先我们省略了一个参数,可以砍去和这个参数有关的代码;然后我们需要声明一个变量来保存计算出来的序列号,全局变量也可以挪到函数里面当局部变量用,这种情况下采用C函数里嵌套一段asm代码比较合适。
改造后如下:

int CalRegCode_asm(char *szName,int nNameLen)
{
        BYTE byData[] = { 0x0C, 0x0A, 0x13, 0x09,  0x0C, 0x0B, 0x0A, 0x08};
        int nResult = 0;

        _asm
        {
                mov     edi, nNameLen;  
                mov     ecx, 0x03;      
                xor     esi, esi;      
                xor     eax, eax;               
                cmp     edi, ecx;      
                jle     short L1;               
                push    ebx;

L3:
                cmp     eax, 0x07;     
                jle     short L2;               
                xor     eax, eax;               

L2:
                xor     edx, edx;      
                xor     ebx, ebx;               
               
                //mov     dl, byte ptr [szName+ecx];
                mov     ebx, szName;       
                add     ebx, ecx;
                mov     dl, byte ptr [ebx];
                xor     ebx,ebx;
                //

                mov     bl, byte ptr byData[eax];  
                imul    edx, ebx;                                       
                add     esi, edx;                                       
                inc     ecx;                                               
                inc     eax;
                cmp     ecx, edi;                  
                jl      short L3;                  
                pop     ebx;
                //result in esi;

                mov   dword ptr nResult,esi;
        }

L1:
        return nResult;
}

原来的这两行代码说明一下:
    mov     dl, byte ptr [ecx+ebp];
    mov     bl, byte ptr [eax+byData];
在ollydbg里调试、或者直接看上下代码可以知道ebp,byData的值是固定的,而ecx、eax是变化的,这其实是高级语言的数组在汇编中的实现方式,用一个C数组来举例:
int  nData[3] = {1,2,3};
nData是数组的首地址,相当于[ecx+ebp]里的ebp,然后我们引用一个数组里的值是用nData[1]的方式,数组下标就相当于ecx;一维数组大致如此,表达式里不变的部分就是数组首地址,变的是数组下标,有些变化比如 [ecx+ebp+20],不变的部分是ebp+20,变的是ecx;二维数组与此类似,不过变化的有两个,比如[ebp+ecx+eax],多维数组由此类推。

此外,我用 mov     dl, byte ptr [szName+ecx] 这一句达不到预想中的效果,只好做如下转换:

//mov     dl, byte ptr [szName+ecx];
mov     ebx, szName;       
add     ebx, ecx;
mov     dl, byte ptr [ebx];
xor     ebx,ebx;
//

请各位高手说明一下正确的语句该怎么写,距离当年在学校里学汇编语法已经很久,实在想不起来了。

省去一个参数(用户输入的序列号),同时把计算出来的数值返回(不必转换成字符串),就可以把后面的两个调用去掉(wsprintf、lstrc),代码一下子少了很多,就算新手也能看出程序逻辑、转换成C代码了。
2009-6-8 12:28
0
雪    币: 69
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
上面的函数已经符合我们的需求,可以直接用了。

要转换成C程序需要进一步分析程序逻辑,这时候在汇编代码旁用注释加上对应的C代码是个不错的做法:

int CalRegCode_asm(char *szName,int nNameLen)
{
        BYTE byData[] = { 0x0C, 0x0A, 0x13, 0x09,  0x0C, 0x0B, 0x0A, 0x08};
        int nResult = 0;

        _asm
        {
                mov     edi, nNameLen;  // int vedi = nNameLen;
                mov     ecx, 0x03;      // int vecx = 3;
                xor     esi, esi;       // int vesi = 0;
                xor     eax, eax;                // int veax = 0;
                cmp     edi, ecx;       // if (vedi > vecx)  ; if (nNameLen > 3)
                jle     short L1;                //{
                push    ebx;

L3:
                cmp     eax, 0x07;     // if (veax > 7)
                jle     short L2;                // {
                xor     eax, eax;                //   veax = 0;} else

L2:
                xor     edx, edx;      //{ vedx =0;
                xor     ebx, ebx;                // vebx = 0;
               
                //mov     dl, byte ptr [szName+ecx];
                mov     ebx, szName;        // char c1 = szName[vecx];
                add     ebx, ecx;
                mov     dl, byte ptr [ebx];
                xor     ebx,ebx;
                //

                mov     bl, byte ptr byData[eax];  //char c2 = byData[veax];
                imul    edx, ebx;                                        // long nTmp = c1 * c2;
                add     esi, edx;                                        // vesi += nTmp;
                inc     ecx;                                                // vecx++;  veax++;
                inc     eax;
                cmp     ecx, edi;                   //if (vecx > nNameLen)
                jl      short L3;                   //跳回去,明显是个循环
                pop     ebx;
                //result in esi;

                mov   dword ptr nResult,esi;
        }

L1:
        return nResult;
}

需要说明的是原汇编代码里的几处跳转,对应到高级语言里无非就是条件语句(if..else、switch)和循环语句(for、while等),比如这一段:

cmp     eax, 0x07;     // if (veax > 7)
jle     short L2;                // {
xor     eax, eax;                //   veax = 0;} else

先拿eax的值和7比较,小于或等于就跳转,否则(大于)就往下,对应的C代码就是
if (veax > 7)
{
    veax = 0;
}
else
{
.....

注意C代码的比较条件和汇编代码相反,这是为了让C代码和汇编代码在顺序结构上保持一致而特意做的,否则如果按照汇编代码直接翻译成 if (veax <= 7)的话,下面的指令xor  eax, eax就得挪到后面,jmp到的代码就得挪上来,这样也不是不行但在对比汇编、C代码时很别扭,可读性不好。

还有这一段:
cmp     ecx, edi;                   //if (vecx > nNameLen)
jl      short L3;                   //跳回去,明显是个循环

当发现要跳回去的汇编代码,往往就是对应高级语言里的循环,具体循环条件就需要结合上下的代码来分析。

这些汇编代码和高级语言代码的对应关系,大致都有几种比较固定的模式,看多了就知道了;
平时用vc等编译器写程序,调试时把汇编代码打开,看看局部变量、全局变量、数组、结构等在汇编代码里怎么表示的,if、while、for等在汇编代码里怎么实现的,看熟了等你跟踪别人写的程序心理就有底了。
另外要注意不同的编译器对同一段高级语言编译出的机器码有时候区别很多,比如delphi和vc,这时候先用peid看看目标程序是用什么编译的比较好下手。
2009-6-8 12:47
0
雪    币: 69
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
最后翻译出来的C函数如下:

int CalRegCode(char *szName,int nNameLen)
{
        BYTE byData[] = { 0x0C, 0x0A, 0x13, 0x09,  0x0C, 0x0B, 0x0A, 0x08};
        int nResult = 0;

        if (nNameLen > 3)
        {
                for (int i=3, j=0; i<= nNameLen; i++,j++)
                {
                        nResult += (long)szName[i] * (long)byData[j%8];
                }
        }

        return nResult;
}

此时我们完全理解了程序的逻辑,开头的这几句,

0040134B  |.  B9 03000000   mov     ecx, 3
。。。
00401354  |.  3BF9          cmp     edi, ecx
00401356  |.  7E 21         jle     short TraceMe.00401379

一开始不知道为什么它为什么小于等于3就跳转,然后看到下面:
00401364  |.  8A1429        |mov     dl, byte ptr [ecx+ebp]
。。。。
00401372  |.  41              inc     ecx

就知道它取输入的用户名是从字符串的第四位(下标从0开始)开始的,前三个忽略不计,也就是说输入123rrr和890rrr的效果完全没区别。
此外,函数外面已经限制输入的用户名长度必须大于5个字符,所以这几行代码可以认为没有起作用。

这一段:
00401359  |>  83F8 07    cmp     eax, 7
0040135C  |.  7E 02         jle     short TraceMe.00401360                                ;跳转2
0040135E  |.  33C0          xor     eax, eax

为什么它大于7就清0呢?往下看:
00401367  |.  8A98 30504000 |mov     bl, byte ptr [eax+405030]
。。。
00401373  |.  40            inc     eax

原来[405030]这里的数值的数目有限制,具体是多少个呢,它每次加1,>7就是8了,也就是说byData有8个值(byte类型),从头到尾依次取值计算,到后面后又折回来,所以我用一个byData[j%8]完成这个判断。
2009-6-8 13:17
0
雪    币: 69
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
完毕,请新手一起学习,请高手指点,特别是上面我说的这一段:

用 mov     dl, byte ptr [szName+ecx] 这一句达不到预想中的效果,只好做如下转换:

//mov     dl, byte ptr [szName+ecx];
mov     ebx, szName;  
add     ebx, ecx;
mov     dl, byte ptr [ebx];
xor     ebx,ebx;
//

请各位高手说明一下正确的语句该怎么写,距离当年在学校里学汇编语法已经很久,实在想不起来了。
2009-6-8 13:22
0
雪    币: 53
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
呵呵厉害啊,我到现在还是不明白
2009-6-8 13:30
0
雪    币: 39
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
我是菜鸟,学习了。
2009-6-8 23:05
0
雪    币: 53
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
我来看看,不过不太明白
2009-6-9 18:18
0
雪    币: 88
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
有点明白,需要继续整理,谢谢楼主了,不过注册机这块不是这里的新手能学的吧?呵呵
楼主可以的话还是写出怎么追注册码的方式吧,呵呵
2009-6-9 19:01
0
雪    币: 69
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
这个过程在书里有啊,你去买一本看就知道了。

另外,mov dl, byte ptr [szName+ecx] 这一句达不到预想中的效果知道怎么回事了。
其实如果szName是在函数这么声明 char szName[] = "adfdf" 的话,就可以取到正确的值,这时候串是放在栈里,是局部变量;
当声明成 char *szName= "adfdf" 时这个串是全局变量,而szName是传入的参数的话效果和这个一样,此时 mov dl, byte ptr [szName+ecx] 或者
mov dl, byte ptr szName[ecx] 取到的就是szData的地址(指针)。

而在C语法里,函数内部的
char *szData = "abcd"  和当参数传进来的 char *szData或char szData[]
以及函数内部声明的 char szData[] = "abcd"
效果没什么区别, 按szData[i]都能取到想要的值。

这个地方以前没注意到
2009-6-10 02:58
0
游客
登录 | 注册 方可回帖
返回
//