首页
社区
课程
招聘
[原创]对前辈一个破解程序的补全(申请邀请码)
发表于: 2010-12-18 20:40 9665

[原创]对前辈一个破解程序的补全(申请邀请码)

2010-12-18 20:40
9665

前辈的文章链接:http://bbs.pediy.com/showthread.php?t=11222

破解声明:

在学校图书馆借到了《加密与解密3》,这是第5章练习题:pediy crackme 的第一道题。
自己先尝试做了一遍,弄一有段时间了,最后终于弄出来了。前辈用了很短的时候就做出来了,
虽然自己做的比较慢,但是也发现了前辈在分析中的一些漏洞。由于前辈对有些细节没有考虑详细
,导致写出来的注册机有部分bug,现在我来尝试完善一下。

大部分分析和前辈是一样的:

发现加了ASPack 壳,用OD加载,SFX自动脱壳之后停在了真正的入口点。

发现时用GetDlgItemTextA函数取字符串的,当然是下断点,然后F9,之后再按F2,回到了代码领空。

我输入的
账号是:zhangyudi
密码是:loong

用于前辈给的注释不太详细,我再来把代码贴出来
总共分为三大部分吧:
第一部分
00401528  |.  68 00010000   PUSH 100                                 ; /Count = 100 (256.)
0040152D  |.  8D85 00FFFFFF LEA EAX,DWORD PTR SS:[EBP-100]           ; |
00401533  |.  50            PUSH EAX                                 ; |Buffer
00401534  |.  6A 65         PUSH 65                                  ; |ControlID = 65 (101.)
00401536  |.  FF75 08       PUSH DWORD PTR SS:[EBP+8]                ; |hWnd
00401539  |.  E8 FA010000   CALL <JMP.&USER32.GetDlgItemTextA>       ; \GetDlgItemTextA
0040153E  |.  89C3          MOV EBX,EAX
00401540  |.  09DB          OR EBX,EBX                               ;  判断输入的账号是否为空
00401542  |.  75 04         JNZ SHORT unpacked.00401548
00401544  |.  31C0          XOR EAX,EAX                              ;  最后是以eax是否为空来判断注册是否成功
00401546  |.  EB 50         JMP SHORT unpacked.00401598
00401548  |>  BF BC020000   MOV EDI,2BC
0040154D  |.  BE 30000000   MOV ESI,30
00401552  |.  B8 48000000   MOV EAX,48
00401557  |.  99            CDQ
00401558  |.  F7FB          IDIV EBX
0040155A  |.  29C6          SUB ESI,EAX                              ;  30减去48h除以账号长度的商
0040155C  |.  8D34B6        LEA ESI,DWORD PTR DS:[ESI+ESI*4]         ;  上一步的结果乘以5
0040155F  |.  29F7          SUB EDI,ESI                              ;  edi减去上一步的结果
00401561  |.  6BFF 6B       IMUL EDI,EDI,6B
00401564  |.  81EF 6CCF0000 SUB EDI,0CF6C
0040156A  |.  81FF 00230000 CMP EDI,2300                             ;  大于就失败
00401570  |.  7F 08         JG SHORT unpacked.0040157A
00401572  |.  81FF 90010000 CMP EDI,190
00401578  |.  7D 04         JGE SHORT unpacked.0040157E              ;  大于等于才行
0040157A  |>  31C0          XOR EAX,EAX
0040157C  |.  EB 1A         JMP SHORT unpacked.00401598
0040157E  |>  8D85 00FFFFFF LEA EAX,DWORD PTR SS:[EBP-100]
00401584  |.  50            PUSH EAX
00401585  |.  53            PUSH EBX
00401586  |.  FF75 08       PUSH DWORD PTR SS:[EBP+8]
00401589  |.  E8 77FDFFFF   CALL unpacked.00401305                   ;  关键点,必须跟进去
0040158E  |.  83C4 0C       ADD ESP,0C
00401591  |.  09C0          OR EAX,EAX                               ;  返回值必须不是0啊
这是对输入的账号进行判断:
输入为空的话,程序失败,
kong = (0x2bc -(0x30 - 0x48 / (signed int)(strlen(账号字符串))) * 5) * 0x6b - 0x0cf6c;最后kong的范围在[0x190,0x2300)

第二部分:
0040139E  |.  68 00010000   PUSH 100                                 ; /Count = 100 (256.)
004013A3  |.  8D85 E1FCFFFF LEA EAX,DWORD PTR SS:[EBP-31F]           ; |
004013A9  |.  50            PUSH EAX                                 ; |Buffer
004013AA  |.  6A 66         PUSH 66                                  ; |ControlID = 66 (102.)
004013AC  |.  FF75 08       PUSH DWORD PTR SS:[EBP+8]                ; |hWnd
004013AF  |.  E8 84030000   CALL <JMP.&USER32.GetDlgItemTextA>       ; \GetDlgItemTextA
004013B4  |.  09C0          OR EAX,EAX
004013B6  |.  0F84 48010000 JE unpacked.00401504                     ;  如果输入的serial为空,则失败
004013BC  |.  B8 CF110000   MOV EAX,11CF
004013C1  |.  0FB68D E1FCFF>MOVZX ECX,BYTE PTR SS:[EBP-31F]          ;  密码首位的asc码
004013C8  |.  99            CDQ
004013C9  |.  F7F9          IDIV ECX
004013CB  |.  83FA 17       CMP EDX,17                               ;  余数必须等于0x17
004013CE  |.  74 07         JE SHORT unpacked.004013D7
004013D0  |.  31C0          XOR EAX,EAX
004013D2  |.  E9 2D010000   JMP unpacked.00401504
004013D7  |>  31DB          XOR EBX,EBX
004013D9  |.  EB 0B         JMP SHORT unpacked.004013E6
004013DB  |>  8B45 10       /MOV EAX,DWORD PTR SS:[EBP+10]
004013DE  |.  0FBE0418      |MOVSX EAX,BYTE PTR DS:[EAX+EBX]
004013E2  |.  0145 FC       |ADD DWORD PTR SS:[EBP-4],EAX            ;  0012F8F0最后存放账号的ASC码的和
004013E5  |.  43            |INC EBX
004013E6  |>  3B5D 0C        CMP EBX,DWORD PTR SS:[EBP+C]
004013E9  |.^ 7C F0         \JL SHORT unpacked.004013DB
004013EB  |.  31DB          XOR EBX,EBX
004013ED  |.  E9 83000000   JMP unpacked.00401475
004013F2  |>  8B55 10       /MOV EDX,DWORD PTR SS:[EBP+10]           ;  edx始终指向账号字符串的地址
004013F5  |.  0FBE3C1A      |MOVSX EDI,BYTE PTR DS:[EDX+EBX]         ;  edi指向每一位账号的ASC值
004013F9  |.  8B75 FC       |MOV ESI,DWORD PTR SS:[EBP-4]            ;  账号每位ASC的和
004013FC  |.  89D9          |MOV ECX,EBX
004013FE  |.  C1E1 02       |SHL ECX,2                               ;  账号的数组下标左移2位
00401401  |.  89DA          |MOV EDX,EBX
00401403  |.  42            |INC EDX
00401404  |.  29D1          |SUB ECX,EDX                             ;  上一步的结果减去对应数组下标加1的和
00401406  |.  0FB68C0D E1FE>|MOVZX ECX,BYTE PTR SS:[EBP+ECX-11F]     ;  0012F7D4的一串字符串
0040140E  |.  89FA          |MOV EDX,EDI
00401410  |.  31CA          |XOR EDX,ECX                             ;  edx为账号某些特定位的asc值与ecx对应的asc值异或
00401412  |.  89F1          |MOV ECX,ESI
00401414  |.  0FAFCB        |IMUL ECX,EBX                            ;  账号字符asc码和乘以当前读取字符下标
00401417  |.  29F1          |SUB ECX,ESI                             ;  上面的结果在减去一个账号ASC的和
00401419  |.  89CE          |MOV ESI,ECX
0040141B  |.  83F6 FF       |XOR ESI,FFFFFFFF                        ;  上面的结果与-1异或
0040141E  |.  8DB432 4D0100>|LEA ESI,DWORD PTR DS:[EDX+ESI+14D]      ;  esi加上14D加上账号asc
00401425  |.  8B4D 0C       |MOV ECX,DWORD PTR SS:[EBP+C]            ;  账号的长度
00401428  |.  89DA          |MOV EDX,EBX                             ;  当前下标
0040142A  |.  83C2 03       |ADD EDX,3                               ;  当前下标加3
0040142D  |.  0FAFCA        |IMUL ECX,EDX                            ;  长度乘以上面的结果
00401430  |.  0FAFCF        |IMUL ECX,EDI                            ;  上面的结果乘以对应asc值
00401433  |.  89F0          |MOV EAX,ESI
00401435  |.  01C8          |ADD EAX,ECX                             ;  加上之前esi的结果
00401437  |.  B9 0A000000   |MOV ECX,0A
0040143C  |.  31D2          |XOR EDX,EDX
0040143E  |.  F7F1          |DIV ECX                                 ;  每次除以10,即求模10
00401440  |.  83C2 30       |ADD EDX,30                              ;  余数加30h
00401443  |.  88941D FCFEFF>|MOV BYTE PTR SS:[EBP+EBX-104],DL        ;  要余数的低byte位
0040144A  |.  0FB6BC1D FCFE>|MOVZX EDI,BYTE PTR SS:[EBP+EBX-104]
00401452  |.  81F7 ACAD0000 |XOR EDI,0ADAC                           ;  与0adac异或
00401458  |.  89DE          |MOV ESI,EBX                             ;  当前下标
0040145A  |.  83C6 02       |ADD ESI,2                               ;  加2
0040145D  |.  89F8          |MOV EAX,EDI
0040145F  |.  0FAFC6        |IMUL EAX,ESI
00401462  |.  B9 0A000000   |MOV ECX,0A
00401467  |.  99            |CDQ
00401468  |.  F7F9          |IDIV ECX
0040146A  |.  83C2 30       |ADD EDX,30
0040146D  |.  88941D FCFEFF>|MOV BYTE PTR SS:[EBP+EBX-104],DL
00401474  |.  43            |INC EBX
00401475  |>  3B5D 0C        CMP EBX,DWORD PTR SS:[EBP+C]            ;  ebx做指针
00401478  |.^ 0F8C 74FFFFFF \JL unpacked.004013F2
0040147E  |.  8D85 FCFEFFFF LEA EAX,DWORD PTR SS:[EBP-104]
00401484  |.  50            PUSH EAX
00401485  |.  6A 54         PUSH 54
00401487  |.  8D85 DCFBFFFF LEA EAX,DWORD PTR SS:[EBP-424]
0040148D  |.  50            PUSH EAX                                 ; |Format
0040148E  |.  8D85 E1FBFFFF LEA EAX,DWORD PTR SS:[EBP-41F]           ; |
00401494  |.  50            PUSH EAX                                 ; |s
00401495  |.  E8 CE020000   CALL <JMP.&USER32.wsprintfA>             ; \wsprintfA
0040149A  |.  8B7D 0C       MOV EDI,DWORD PTR SS:[EBP+C]             ;  zhangyudi账号得到的是T61258670(0012F4D5)
0040149D  |.  89F8          MOV EAX,EDI
0040149F  |.  0FAF45 FC     IMUL EAX,DWORD PTR SS:[EBP-4]            ;  账号的长度乘以每位账号ASC码的和
004014A3  |.  B9 64000000   MOV ECX,64
004014A8  |.  99            CDQ
004014A9  |.  F7F9          IDIV ECX                                 ;  上面得到的结果除以64
004014AB  |.  89D7          MOV EDI,EDX
004014AD  |.  83C7 30       ADD EDI,30                               ;  商加上30
004014B0  |.  57            PUSH EDI
004014B1  |.  8DBD E1FBFFFF LEA EDI,DWORD PTR SS:[EBP-41F]
004014B7  |.  57            PUSH EDI                                 ;  上一个wsprintfA得到的结果T61258670
004014B8  |.  8DBD D6FBFFFF LEA EDI,DWORD PTR SS:[EBP-42A]           ;  %s-%d
004014BE  |.  57            PUSH EDI                                 ; |Format
004014BF  |.  8DBD E1FDFFFF LEA EDI,DWORD PTR SS:[EBP-21F]           ; |
004014C5  |.  57            PUSH EDI                                 ; |s
004014C6  |.  E8 9D020000   CALL <JMP.&USER32.wsprintfA>             ; \wsprintfA
004014CB  |.  83C4 20       ADD ESP,20                               ;  得到序列T612582670-59,59应该是后面计算(0012F6D5)
004014CE  |.  8D8D E1FDFFFF LEA ECX,DWORD PTR SS:[EBP-21F]
004014D4  |.  83C8 FF       OR EAX,FFFFFFFF
004014D7  |>  40            /INC EAX
004014D8  |.  803C01 00     |CMP BYTE PTR DS:[ECX+EAX],0
004014DC  |.^ 75 F9         \JNZ SHORT unpacked.004014D7             ;  得到第二次的序列长度
004014DE  |.  50            PUSH EAX                                 ; /Arg3
004014DF  |.  8D85 E1FCFFFF LEA EAX,DWORD PTR SS:[EBP-31F]           ; |指向输入密码的地址
004014E5  |.  50            PUSH EAX                                 ; |Arg2
004014E6  |.  8D85 E1FDFFFF LEA EAX,DWORD PTR SS:[EBP-21F]           ; |指向第二次得到的序列的地址
004014EC  |.  50            PUSH EAX                                 ; |Arg1
004014ED  |.  E8 D0FDFFFF   CALL unpacked.004012C2                   ; \unpacked.004012C2
004014F2  |.  83C4 0C       ADD ESP,0C                               ;  上面那个函数是关键算法
004014F5  |.  83F8 00       CMP EAX,0
004014F8  |.  75 07         JNZ SHORT unpacked.00401501              ;  返回值必须不是0
004014FA  |.  B8 00000000   MOV EAX,0
004014FF  |.  EB 03         JMP SHORT unpacked.00401504
00401501  |>  31C0          XOR EAX,EAX
00401503  |.  40            INC EAX
00401504  |>  5F            POP EDI
00401505  |.  5E            POP ESI
00401506  |.  5B            POP EBX
00401507  |.  C9            LEAVE
00401508  \.  C3            RETN
这部分是比较关键的代码了,它根据输入的账号进行了一系列的运算,得到了一个中间字符串,
其中还有一点是,输入的密码的第一位的asc码是有限制的,它必须被0x11cf - 0x17整除。
至于得到中间序列的算法请看注册机代码。

第三部分

004012C2  /$  55            PUSH EBP
004012C3  |.  89E5          MOV EBP,ESP
004012C5  |.  53            PUSH EBX
004012C6  |.  56            PUSH ESI
004012C7  |.  57            PUSH EDI
004012C8  |.  8B5D 10       MOV EBX,DWORD PTR SS:[EBP+10]            ;  ebx为序列的长度
004012CB  |.  31F6          XOR ESI,ESI
004012CD  |.  46            INC ESI                                  ;  跳过第一个字符
004012CE  |.  EB 29         JMP SHORT unpacked.004012F9
004012D0  |>  8B55 08       /MOV EDX,DWORD PTR SS:[EBP+8]
004012D3  |.  0FBE3C32      |MOVSX EDI,BYTE PTR DS:[EDX+ESI]
004012D7  |.  89F8          |MOV EAX,EDI
004012D9  |.  83F0 20       |XOR EAX,20                              ;  序列号第二位之后的ASC码与20h异或
004012DC  |.  B9 0A000000   |MOV ECX,0A
004012E1  |.  99            |CDQ
004012E2  |.  F7F9          |IDIV ECX                                ;  异或之后的结果除以A
004012E4  |.  89D7          |MOV EDI,EDX
004012E6  |.  83C7 30       |ADD EDI,30                              ;  余数加30
004012E9  |.  8B55 0C       |MOV EDX,DWORD PTR SS:[EBP+C]
004012EC  |.  0FBE1432      |MOVSX EDX,BYTE PTR DS:[EDX+ESI]
004012F0  |.  39D7          |CMP EDI,EDX                             ;  与输入的账号逐位比较,必须相等才行
004012F2  |.  74 04         |JE SHORT unpacked.004012F8
004012F4  |.  31C0          |XOR EAX,EAX
004012F6  |.  EB 08         |JMP SHORT unpacked.00401300
004012F8  |>  46            |INC ESI
004012F9  |>  39DE           CMP ESI,EBX
004012FB  |.^ 7C D3         \JL SHORT unpacked.004012D0
004012FD  |.  31C0          XOR EAX,EAX
004012FF  |.  40            INC EAX
00401300  |>  5F            POP EDI
00401301  |.  5E            POP ESI
00401302  |.  5B            POP EBX
00401303  |.  5D            POP EBP
00401304  \.  C3            RETN

这段代码应该比较简单:

上面得到的中间字符串从第二位开始:
asc码值与0x20异或,然后余数加上0x30,得到的结果应该就是输入的密码对应的字符的asc
值。

注册机的代码如下(vc6.0通过):
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void)
{
        int i, j;
        int sum;
        char tep[10];
        int str;

        int ch;
        int kong;
        int loong;
        //char arr1[] = {"zhangyudi"};
        char arr1[50];
        char arr[] = {"ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
        char temp[50];

        sum = 0;
        j = 0;
        temp[j] = 'T';
        j++;

        printf("请输入要注册的账号!\n");
        scanf("%s", arr1);
                        
        if (strlen(arr1) == 0) //账号长度为0,则失败
        {
                printf("账号无效!\n");
                return 0;
        }
        kong = (0x2bc -(0x30 - 0x48 / (signed int)(strlen(arr1))) * 5) * 0x6b - 0x0cf6c;
        if (kong < 0x190 || kong >= 0x2300)
        {
                printf("账号无效!\n");
                return 0;
        }
        /**********上面是对账号的验证**************/
        for (i = 0; i < (signed int)strlen(arr1); i++)
        {
                sum = sum + arr1[i];
        }
        //printf("%x\n", sum); //输入的账号总和
        i = 0;
        ch = ((((((arr1[i] ^ 0) + ((sum * i - sum) ^ 0xffffffff) + 0x14D +
                        (i + 3) * strlen(arr1) * arr1[i]) % 0xA + 0x30) ^ 0x0ADAC)
                        * (i + 2) % 0xA) + 0x30) % 16;
        //printf("%d ", ch); //首位需要单独考虑
        temp[j]  = ch + 48;
        j++;
        for (i = 1; i < (signed int)strlen(arr1); i++, j++)
        {
                ch = ((((((arr1[i] ^ arr[i * 4 - i - 1]) + ((sum * i - sum) ^ 0xffffffff) + 0x14D +
                        (i + 3) * strlen(arr1) * arr1[i]) % 0xA + 0x30) ^ 0x0ADAC)
                        * (i + 2) % 0xA) + 0x30) % 16;
                //printf("%d ", ch);
                temp[j] = ch + 48;
        }
        /********上面各种复杂的取得中间码的运算*******/
        str = j;
        printf("\n");
        printf("\n中间运算变量1:\n");
        for (j = 0; j < str; j++)
        {
                printf("%c", temp[j]);
        }
        temp[str] = '\0';
        ultoa((sum * strlen(arr1) % 0x64 + 0x30), tep, 10);
        strcat(temp, "-");
        strcat(temp, tep);
        temp[strlen(temp) + 1] = '\0';
        printf("\n中间运算变量2:\n");
        printf("\n%s", temp);
        for (i = 1; i < (signed int)strlen(temp); i++)
        {
                temp[i] = (temp[i] ^ 0x20) % 0xA + 0x30;
        }
        printf("\n最终的注册码\n");
        printf("\n%s", temp);

        /**************以下是对密码首位的验证*************/
        loong = 0x11cf - 0x17;
        printf("其实密码的首位有很多种情况,考虑到用户的输入,只列出下面的情况!\n");
        for (i = 2; i < (loong + 1) / 2; i++)
        {
                if ((loong % i) == 0 && (i >= 32) && (i <= 126) )
                {
                        printf("%c\n", i);
                }
        }
        return 0;
}
PS: 如果发现有bug,请指出。
经过数学计算,发现输入的的账号如果超过10位(含10位)的话,就肯定不行,因为字母AB...Z,总共只有26位, (26 + 1) / 3 = 9,这点程序设计者在之前貌似通过第一部分已经过滤掉了
(输入验证没有发现bug,但是没有从理论上证明)。

整个的过程大致如此吧,感觉做逆向非常需要静得下心来,这样才能有所突破。
谈谈算法本身吧,感觉还行,只是虽然没有直接用账号和密码进行简单的比较,但是
账号的运算毕竟产生的中间字符串明文的出现在了内存之中,貌似可以直接将那一部分的反汇编
代码内嵌的C语言中,这样好像可以不管你具体的算法,而直接跳过那一部分,弱化了算法的复杂性。

大致如此吧,菜鸟的第一次发破解贴,请大家多多指教


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 7
支持
分享
最新回复 (9)
雪    币: 1015
活跃值: (235)
能力值: ( LV12,RANK:440 )
在线值:
发帖
回帖
粉丝
2
忘发程序了
上传的附件:
2010-12-18 20:44
0
雪    币: 1844
活跃值: (35)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
内容不敢看,太累,顶一下
2010-12-18 21:14
0
雪    币: 1015
活跃值: (235)
能力值: ( LV12,RANK:440 )
在线值:
发帖
回帖
粉丝
4
谢谢楼上的大牛
2010-12-18 21:39
0
雪    币: 114
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
又长见识了,谢谢分享
2010-12-19 02:54
0
雪    币: 24
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
辛苦了,顶一个
2010-12-19 10:20
0
雪    币: 1015
活跃值: (235)
能力值: ( LV12,RANK:440 )
在线值:
发帖
回帖
粉丝
7
谢谢哈
2010-12-19 15:10
0
雪    币: 680
活跃值: (68)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
8
kankan
2013-11-28 13:48
0
雪    币: 32
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
2010年10月注册。膜拜下,大大的第一贴
2014-5-9 14:21
0
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
很不错了,继续加油!!!
2014-5-11 09:30
0
游客
登录 | 注册 方可回帖
返回
//