欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

速成QQ群成员提取器破解

程序员文章站 2022-06-28 11:30:16
本人新手一名,一直都跟自己的好基友学习逆向。之前基友给了我一个功课练练手,小的我也终于在昨天给弄完了。然后好基友就建议,作为第一次实战的经验可以在看雪分享下,顺便也做个总结。于是我就来到了这里...
本人新手一名,一直都跟自己的好基友学习逆向。之前基友给了我一个功课练练手,小的我也终于在昨天给弄完了。然后好基友就建议,作为第一次实战的经验可以在看雪分享下,顺便也做个总结。于是我就来到了这里了。
    第一次写破文,难免不会出错,至此还希望各位多多见谅。不对的地方,也希望各位指出。本文偏向新手分享经验,所以大牛们没有兴趣的话,还请飘过。
     
     此文只供仅供学习,切勿拿来做坏事哈!

     好了就不多扯闲话了,进入正题吧!
     软件的名字叫“速成QQ群成员号提取器V7.0”,界面图下图:
速成QQ群成员提取器破解
     功能就不做介绍了,这不是我们所关心的重点。

     点击右下的“注册购买”弹出对话框了。速成QQ群成员提取器破解
随便输一串注册码吧 点击注册就会见到这个了速成QQ群成员提取器破解
     都是大家很亲切的东西。
     看到这些,大致的思路就已经出来了,就是去找获取对话框文本的api函数,如GetWindowText,看他是如何处理我们输入的注册码了然后来逆向做出我们的注册机了
     又或是找输出结果的MessageBox了,看看我们是如何被跳入“错误”这个选项的,然后再看看能不能采取爆破的手段直接过掉。

     我先剧透下,第二种方法是在这个程序中是不行的,所以下文采取的是第一个思路。至于为什么不行,在下文会有点到。

     既然思路出来了,那就动手吧。
  
     用OD加载我们的程序,然后按下F9,让程序先运行起来。
     诶诶,纳尼!What the Fuck!跑不动了!非法指令!
速成QQ群成员提取器破解
     在多次shift+F9跳过非法指令,我们进入badend——程序被终止了 。

     看来对方明显不想让我们破解他的程序,在其中加了点料,来防止我们用调试工具来加载。
     不过不怕,他有张良计,我们也有过桥梯。正面不行,我们就走侧面。
     我们先让程序跑起来,然后在用OD来附加该程序的进程就行了(打开OD,文件->附加,找到我们要附加程序的进程)然后F9。好,跑起来了,而且没有错误。
     
     哟西,开始去找获取EdIt文本的api函数吧。之后随便填入注册码(我用的是12345678),点击注册。
     好,断下了。
速成QQ群成员提取器破解
     
     然后在堆栈窗口查看函数的几个参数,Buffer参数,转到数据窗口中。这就是保存我们填写注册码的地址了。F8单步步过,注册码就填进去了
速成QQ群成员提取器破解
大家已经看到在我们注册码上面还有3个int型的变量,这应该是一个结构体。
     有经验的人肯定会在此设下硬件断点,但作为新手的我们还是一步一步慢慢来。切记,作为新手耐心是必备的!
     之后继续单步走,我们遇到在程序中第一个函数,这个函数是用来获取我们这注册码长度,然后放在上面的第二个int变量中。这变量就是长度了。
     之后就是强制跳转,然后retn返回了。
速成QQ群成员提取器破解
我们可以看到经过两个函数,就会遇到一个switch case语句 参数是ebp切且初始值被设为-14(0xFFFFFFEC) 然后与0x8c比较 高于则跳转到【0040B18B】那里,也就是关键字default那里。
速成QQ群成员提取器破解
ebp在这里被累加,然后又回到switch开头。同时我们也注意到了每个case语句都有jmp跳到那里。
看到这里我们可以清楚意识,不出意外,所有的case语句都会遍历一边。那我们就在每个case设下断点。至于前面的两个函数,它们后面都没有条件跳转,那我们可以先尝试F8一个一个的跳过,如果在此期间跳到了其他的地方 那说明这个函数有问题,然后返回来再看就行了。第一个函数的意图我不清楚,第二个函数则是防止粘贴注册码前面多出空格,这里就不贴图介绍了。有耐心的朋友也可以自己跳进去看看。

     那我们直接F9来到第一个case语句,有个函数。函数之后有条件跳转
0040B0EB   .  85C0          test    eax, eax
0040B0ED   .  0F84 A8000000 je      速成QQ群.0040B19B
而且有些眼熟,如果细心的话,我们就会发现很多case后面都有一个条件跳转而且都是同一个地址。注册码肯定不会满足一个条件就会跳到我们想要的结果,这显然是一个bad路线。
速成QQ群成员提取器破解
     这个函数是用来生成字符串“注册码错误”和“注意”的,也就是在错误的情况下MessageBox的两个参数。在函数内倒数第4个函数就是用来调用MessageBox的。有兴趣的朋友可以自己跟进去看看,逆向一下,这里就不在讨论它了。

好了,我们继续!
既然知道了,那就想方设法不跳出去。到了第二个case语句:
0040B107   > \8B17          mov     edx, dword ptr ds:[edi]       ;ds:[0012f42c] =00D34338    ;  Case 28 of switch 0040B0C0
0040B109   .  837A F8 0B    cmp     dword ptr ds:[edx-8], 0B         ;  注册码应该要11位以上
0040B10D   .  0F8C 88000000 jl      速成QQ群.0040B19B
edi存有一个栈地址,而这个栈地址存的刚好是我们注册码存的地址。回到之前注册码地址的图,我们发现-8刚好是注册码的长度。也就是说这个 注册码应该要11位以上。显然我们之前8位的是通不过的。那我们重新输一个11位的,再来一次。
继续往后走,我们来到第三个case语句:
速成QQ群成员提取器破解
这次的条件是al非零就被跳出。
我们跟进第二个函数里,发现里面有个循环:
0040AB64  |> /8A0411        /mov     al, byte ptr ds:[ecx+edx]       ;  ds:[0+00D34338]
0040AB67  |. |884424 04     |mov     byte ptr ss:[esp+4], al
0040AB6B  |. |8B4424 04     |mov     eax, dword ptr ss:[esp+4]
0040AB6F  |. |25 FF000000   |and     eax, 0FF
0040AB74  |. |83F8 41       |cmp     eax, 41                         
0040AB77  |. |7C 2D         |jl      short 速成QQ群.0040ABA6      ;小于‘A’跳出
0040AB79  |. |83F8 5A       |cmp     eax, 5A
0040AB7C  |. |7F 28         |jg      short 速成QQ群.0040ABA6      ;大于‘Z’跳出
0040AB7E  |. |41            |inc     ecx
0040AB7F  |. |3BCE          |cmp     ecx, esi
0040AB81  |.^\7C E1         \jl      short 速成QQ群.0040AB64
0040AB83  |> \8D4C24 18     lea     ecx, dword ptr ss:[esp+18]
0040AB87  |.  C74424 10 FFF>mov     dword ptr ss:[esp+10], -1
0040AB8F  |.  E8 A7D20100   call    速成QQ群.00427E3B
0040AB94  |.  32C0          xor     al, al                ;循环正常结束 al清零
0040AB96  |.  5E            pop     esi
0040AB97  |.  8B4C24 04     mov     ecx, dword ptr ss:[esp+4]
0040AB9B  |.  64:890D 00000>mov     dword ptr fs:[0], ecx
0040ABA2  |.  83C4 10       add     esp, 10
0040ABA5  |.  C3            retn
0040ABA6  |>  8D4C24 18     lea     ecx, dword ptr ss:[esp+18]      ;跳出的地方
0040ABAA  |.  C74424 10 FFF>mov     dword ptr ss:[esp+10], -1
0040ABB2  |.  E8 84D20100   call    速成QQ群.00427E3B
0040ABB7  |.  8B4C24 08     mov     ecx, dword ptr ss:[esp+8]
0040ABBB  |.  B0 01         mov     al, 1                                         ;跳出后al被赋值为1
0040ABBD  |.  5E            pop     esi
0040ABBE  |.  64:890D 00000>mov     dword ptr fs:[0], ecx
0040ABC5  |.  83C4 10       add     esp, 10
0040ABC8  \.  C3            retn
也就是说,我们的注册码必须是大写字母。
继续往后走,来到第4个case,虽然不知道他检测什么,但貌似并没有出错,我们继续F9来到第5个case
速成QQ群成员提取器破解
进去发现只有一句xor al,al语句,同样对我们来说没有任何问题继续往下走。来到了倒数第二个case了。其实我们往下看最后一个case语句的时候就会发现,这个第5个是一样的。也就是说我们现在断下来的地方就是就应该是最后的阶段了。
速成QQ群成员提取器破解
第一函数跳过 对我们来说没用,第二个函数进去
速成QQ群成员提取器破解
同样越过两个函数,来到最关键的地方了。进去之后 我们就看到我们想要的加密算法了。其中一个参数为0x1500,这在后面有用到
加密有两个循环来完成
第一个加密:
0040BCD4  |.  8B4C24 2C     mov     ecx, dword ptr ss:[esp+2C]      ;ecx 为注册码地址
0040BCD8  |.  33F6          xor     esi, esi                                 ;esi初始值为0
0040BCDA  |.  8B41 F8       mov     eax, dword ptr ds:[ecx-8]
0040BCDD  |.  99            cdq
0040BCDE  |.  2BC2          sub     eax, edx
0040BCE0  |.  D1F8          sar     eax, 1                           ;  将注册码长度除2
0040BCE2  |.  85C0          test    eax, eax
0040BCE4  |.  7E 6A         jle     short 速成QQ群.0040BD50
0040BCE6  |.  57            push    edi
0040BCE7  |> /8A0471        /mov     al, byte ptr ds:[ecx+esi*2]      
0040BCEA  |. |8A4C71 01     |mov     cl, byte ptr ds:[ecx+esi*2+1]      ;一次取两个字符来加密
0040BCEE  |. |884424 10     |mov     byte ptr ss:[esp+10], al
0040BCF2  |. |884C24 14     |mov     byte ptr ss:[esp+14], cl
0040BCF6  |. |8B4424 10     |mov     eax, dword ptr ss:[esp+10]
0040BCFA  |. |8B4C24 14     |mov     ecx, dword ptr ss:[esp+14]
0040BCFE  |. |25 FF000000   |and     eax, 0FF
0040BD03  |. |81E1 FF000000 |and     ecx, 0FF
0040BD09  |. |68 5C444400   |push    速成QQ群.0044445C
0040BD0E  |. |8D1440        |lea     edx, dword ptr ds:[eax+eax*2]   
0040BD11  |. |8D0490        |lea     eax, dword ptr ds:[eax+edx*4]
0040BD14  |. |8DBC41 25F9FF>|lea     edi, dword ptr ds:[ecx+eax*2-6DB>  ;加密算法,将(第一个字符*(3*4+1)*2+第二字符 -6DB
0040BD1B  |. |8D4C24 10     |lea     ecx, dword ptr ss:[esp+10]
0040BD1F  |. |E8 A0C20100   |call    速成QQ群.00427FC4                  ;
0040BD24  |. |57            |push    edi
0040BD25  |. |6A 00         |push    0
0040BD27  |. |8D4C24 14     |lea     ecx, dword ptr ss:[esp+14]
0040BD2B  |. |E8 1FC60100   |call    速成QQ群.0042834F                    ;将加密后的密文存到一个存储区00D34388
0040BD30  |. |8D5424 0C     |lea     edx, dword ptr ss:[esp+C]
0040BD34  |. |8D4C24 08     |lea     ecx, dword ptr ss:[esp+8]
0040BD38  |. |52            |push    edx
0040BD39  |. |E8 15C50100   |call    速成QQ群.00428253
0040BD3E  |. |8B4C24 30     |mov     ecx, dword ptr ss:[esp+30]
0040BD42  |. |46            |inc     esi
0040BD43  |. |8B41 F8       |mov     eax, dword ptr ds:[ecx-8]
0040BD46  |. |99            |cdq
0040BD47  |. |2BC2          |sub     eax, edx
0040BD49  |. |D1F8          |sar     eax, 1
0040BD4B  |. |3BF0          |cmp     esi, eax
0040BD4D  |.^\7C 98         \jl      short 速成QQ群.0040BCE7
第二个加密:
0040BD64  |.  8B42 F8       mov     eax, dword ptr ds:[edx-8]      ;eax为长度的地址
0040BD67  |.  85C0          test    eax, eax
0040BD69  |.  7E 48         jle     short 速成QQ群.0040BDB3
0040BD6B  |.  53            push    ebx
0040BD6C  |.  8B5C24 34     mov     ebx, dword ptr ss:[esp+34]
0040BD70  |> /8A1416        /mov     dl, byte ptr ds:[esi+edx]       ; edx指向第一次加密的密文地址,每次取一个字符
0040BD73  |. |33C9          |xor     ecx, ecx
0040BD75  |. |8ACF          |mov     cl, bh                                    ;第一次ebx存有之前提到过的参数0x1500,bh = 0x15
0040BD77  |. |32CA          |xor     cl, dl                                       ;
0040BD79  |. |51            |push    ecx
0040BD7A  |. |56            |push    esi
0040BD7B  |. |8D4C24 10     |lea     ecx, dword ptr ss:[esp+10]
0040BD7F  |. |E8 CBC50100   |call    速成QQ群.0042834F              ;将加密后的继续一个一个地保存起来。
0040BD84  |. |8B5424 30     |mov     edx, dword ptr ss:[esp+30]      
0040BD88  |. |66:0FB60416   |movzx   ax, byte ptr ds:[esi+edx]      ;注意eax的高字段还保留了长度地址的高字段0x00D30000,而且这里取得是注册码的字符!
0040BD8D  |. |03C3          |add     eax, ebx
0040BD8F  |. |BB BF580000   |mov     ebx, 58BF
0040BD94  |. |8D0C40        |lea     ecx, dword ptr ds:[eax+eax*2]
0040BD97  |. |C1E1 04       |shl     ecx, 4
0040BD9A  |. |2BC8          |sub     ecx, eax
0040BD9C  |. |8D0C49        |lea     ecx, dword ptr ds:[ecx+ecx*2]
0040BD9F  |. |8D0C89        |lea     ecx, dword ptr ds:[ecx+ecx*4]
0040BDA2  |. |8D0CC9        |lea     ecx, dword ptr ds:[ecx+ecx*8]
0040BDA5  |. |8D0448        |lea     eax, dword ptr ds:[eax+ecx*2]
0040BDA8  |. |2BD8          |sub     ebx, eax                                    ;这里是算出下一轮要用ebx的值
0040BDAA  |. |8B42 F8       |mov     eax, dword ptr ds:[edx-8]
0040BDAD  |. |46            |inc     esi
0040BDAE  |. |3BF0          |cmp     esi, eax
0040BDB0  |.^\7C BE         \jl      short 速成QQ群.0040BD70
如今算法清楚了,接下来就是去找用到我们加密后的密文地地方,这个时候我们也发现机械码一次都还没用上。那最后肯定是两者之间进行比较了。
     那我们可以去找同时使用这两个地址为参数的函数,也可以在两个地址设下硬件断点F9直奔过去。一步一步的跟的话就会发现就是case语句中第3个函数。(如果是硬件断点,第一次并不会断到我们想要的地方);
0041516C  |.  8B75 0C       mov     esi, dword ptr ss:[ebp+C]      ;密文地址
0041516F  |.  8B7D 08       mov     edi, dword ptr ss:[ebp+8]      ;机器码地址
00415172  |.  59            pop     ecx
00415173  |>  66:0FB60F     /movzx   cx, byte ptr ds:[edi]           ;循环开始,一次取一个字节
00415177  |.  0FB6C1        |movzx   eax, cl
0041517A  |.  47            |inc     edi
0041517B  |.  894D 0C       |mov     dword ptr ss:[ebp+C], ecx
0041517E  |.  F680 41D64400>|test    byte ptr ds:[eax+44D641], 4
00415185  |.  74 16         |je      short 速成QQ群.0041519D
00415187  |.  8A07          |mov     al, byte ptr ds:[edi]
00415189  |.  84C0          |test    al, al
0041518B  |.  75 06         |jnz     short 速成QQ群.00415193
0041518D  |.  8365 0C 00    |and     dword ptr ss:[ebp+C], 0
00415191  |.  EB 0A         |jmp     short 速成QQ群.0041519D
00415193  |>  33D2          |xor     edx, edx
00415195  |.  47            |inc     edi
00415196  |.  8AF1          |mov     dh, cl
00415198  |.  8AD0          |mov     dl, al
0041519A  |.  8955 0C       |mov     dword ptr ss:[ebp+C], edx ;这个栈每次存机器码
0041519D  |>  66:0FB61E     |movzx   bx, byte ptr ds:[esi]      ;取密文的一个字节
004151A1  |.  0FB6C3        |movzx   eax, bl
004151A4  |.  46            |inc     esi
004151A5  |.  F680 41D64400>|test    byte ptr ds:[eax+44D641], 4
004151AC  |.  74 13         |je      short 速成QQ群.004151C1
004151AE  |.  8A06          |mov     al, byte ptr ds:[esi]
004151B0  |.  84C0          |test    al, al
004151B2  |.  75 04         |jnz     short 速成QQ群.004151B8
004151B4  |.  33DB          |xor     ebx, ebx
004151B6  |.  EB 09         |jmp     short 速成QQ群.004151C1
004151B8  |>  33C9          |xor     ecx, ecx
004151BA  |.  46            |inc     esi
004151BB  |.  8AEB          |mov     ch, bl
004151BD  |.  8AC8          |mov     cl, al
004151BF  |.  8BD9          |mov     ebx, ecx
004151C1  |>  66:395D 0C    |cmp     word ptr ss:[ebp+C], bx
004151C5  |.  75 09         |jnz     short 速成QQ群.004151D0      ;机器码和密文取出的字节比较 相同继续,不同弹出。[/COLOR]
004151C7  |.  66:837D 0C 00 |cmp     word ptr ss:[ebp+C], 0
004151CC  |.  74 16         |je      short 速成QQ群.004151E4
004151CE  |.^ EB A3         \jmp     short 速成QQ群.00415173
004151D0  |>  6A 19         push    19
004151D2  |.  E8 14450000   call    速成QQ群.004196EB
004151D7  |.  66:3B5D 0C    cmp     bx, word ptr ss:[ebp+C]
004151DB  |.  59            pop     ecx
004151DC  |.  1BC0          sbb     eax, eax
004151DE  |.  83E0 02       and     eax, 2
004151E1  |.  48            dec     eax                                    ;goodending 赋值1
004151E2  |.  EB 0A         jmp     short 速成QQ群.004151EE
004151E4  |>  6A 19         push    19
004151E6  |.  E8 00450000   call    速成QQ群.004196EB
004151EB  |.  59            pop     ecx
004151EC  |.  33C0          xor     eax, eax                          ;badending 清零
如上,我们已经清楚了密文一定要和机器码相同才是正解。之后返回到case语句上,
速成QQ群成员提取器破解
最后的条件跳转的地址已经不是之前的badend的地址了。大家是不是已经跃跃欲试了,只要将bl的值不为零,就是我们想要的结果了!
找到爆破点不是我们的目的,写出注册机才是我们最终目的。既然算法已经知道了,需要的结果。那就动手吧
     先来终结下算法,首先取注册码两个相邻字符(用x,y表示),x*26+y-6DB 然后与bh异或,除了第一次bh是固定的0x15 其他次bh都是通过一个个注册码字节值+0x00D30000来算出来的,这对我们来说是个问题。
     不过第一次bh是已知的 而且我们也知道xor的结果是机械码的第一个字节,且X,Y满足大于0x41小于0x5A就可以求出X,Y了。然后在用得出的XY来求bh就行了。而且算法可以从代码中扣取出来。
    
     参考:

char code[128];//注册码
char Mcode[64];//机器码

const int _A =0x41;
const int _Z=0x5A;
const int  encry1 = 0x06DB;
char *pc ;
int number = 0x1500;
/*算ebx*/
int _encry3(int sum){
     _asm{
          push eax
          push ebx
          push ecx
          mov eax,sum
          add eax,0x0D30000
          mov ebx,number
          add eax,ebx                                                        
          mov ebx,0x58BF
          lea     ecx, dword ptr ds:[eax+eax*2]
          shl     ecx, 4
          sub     ecx, eax
          lea     ecx, dword ptr ds:[ecx+ecx*2]
          lea     ecx, dword ptr ds:[ecx+ecx*4]
          lea     ecx, dword ptr ds:[ecx+ecx*8]
          lea     eax, dword ptr ds:[eax+ecx*2]
          sub     ebx, eax
          mov          number,ebx                                    ;偷懒 扣取的代码

          pop          ecx
          pop          ebx
          pop          eax
     }
     return 0;
}
/*用来逆算出需要的第一次密文*/
int _encry2(int num,int i)
{
     _asm{
          push     ebx
          push     eax
          push     ecx
          mov          ecx,i
          mov          ebx, num
          movzx     eax,byte ptr [Mcode+ecx]
          xor          al,bh
          mov          num, eax
          pop     ecx
          pop     eax
          pop          ebx
     }
     return num;
}
/*用逆算出的值推出注册码相邻字节*/
BOOL _encry1(int num,int i)
{
     int sum;
     sum = _encry2(num, i);
     num =sum + encry1;
     int j =0;
     int n ;
     for(n =_A;n <= _Z ; ++n)
     {
          j = num - n*26;
          if (_A <= j&& j<= _Z )
          {
               *pc = n;
               ++pc;
               *pc = j;
               ++pc;
               _encry3(sum);
               return 0;
          }
     }
     return 1;
}

int _encry()
{
     pc =code;
     int i = 0;
     int num =number;
     for( ; i<=16;++i)
     {
          _encry1(num,i);
          num = number;
     }
     return 0;
}

这个还不能完全验证是否正确,因为关于注册码相邻字节的计算毕竟是个f(x,y)2元函数,即使限制在大写字母范围内,也难免不会出现多个解,从而导致后续计算有出错的情况 。如果发现问题,请及时告诉我,谢谢!    
ok 打完收工!
如果有什么地方说错,还请指出来!谢谢!