kaibasc 发表于 2014-8-19 13:21

【原创】一个不错的CM的算法分析

本帖最后由 kaibasc 于 2014-8-19 15:00 编辑

前言:学了破解学了有一段时间了,但是一直没有发帖,最近在网上找到了一个算法比较巧妙的CM,于是乎自己就拿来了分析分析,把自己得学习心得和大家分享分享~

打开程序要求输入name以及serial,那么就丢进OD随便输入吧,name输入aaaaaa,serial输入bbbbbb;
在getwindowsTextA处设下一个断点,之后F9让程序运行起来,断点处暂停之后,单步运行,一直运行到
004010A7|.E8 5A020000   call <jmp.&user32.GetDlgItemTextA>       ; \GetDlgItemTextA
在这里下一个断点,这里的函数是为了获得用户名,即aaaaaa,然后我们往下看看,可以看到
0040110C|.E8 F5010000   call <jmp.&user32.GetDlgItemTextA>       ; \GetDlgItemTextA
这里的函数是为了获取我们输入的密码,即bbbbbb,果断的在这里下一个断点再说~
F9让程序运行起来,会发现有个弹窗,显示“Wrong serial."
重新运行程序,搜索ASCII码,找到"Wrong serial."的字符串,双击找到字符串出现的位置
004011D7|> \6A 10         push 0x10                              ; /Style = MB_OK|MB_ICONHAND|MB_APPLMODAL
004011D9|.68 00304000   push Crackme.00403000                  ; |Crackme
004011DE|.68 08304000   push Crackme.00403008                  ; |Wrong serial.
004011E3|.FF75 08       push                            ; |hOwner
004011E6|.E8 21010000   call <jmp.&user32.MessageBoxA>         ; \MessageBoxA
分析可以发现,004011D7处的跳转来自三个地方,那么我们就分别在这3处设下断点,让程序运行起来。
程序停在第一个GetDlgItemTextA函数这里,我们分析一下程序是怎么对name进行变化的。
004010A7|.E8 5A020000   call <jmp.&user32.GetDlgItemTextA>       ; \GetDlgItemTextA
004010AC|.BE B0304000   mov esi,Crackme.004030B0               ;ASCII "aaaaaa"
004010B1|.33C0          xor eax,eax
004010B3|.33D2          xor edx,edx
004010B5|>8A16          /mov dl,byte ptr ds:         ;求出name中的每一个数字之和,令其为sum
004010B7|.03C2          |add eax,edx
004010B9|.46            |inc esi
004010BA|.85D2          |test edx,edx
004010BC|.^ 75 F7         \jnz XCrackme.004010B5
004010BE|.A3 78304000   mov dword ptr ds:,eax          ;将sum存在0x403078处
004010C3|.48            dec eax
004010C4|.6BC0 03       imul eax,eax,0x3                         ;sum=(sum-1)*3
004010C7|.A3 7C304000   mov dword ptr ds:,eax          ;将变化过的sum存放在0x40307C处
004010CC|.BE B0304000   mov esi,Crackme.004030B0               ;ASCII "aaaaaa"
004010D1|.B8 78563412   mov eax,0x12345678         ;eax=0x12345678
004010D6|.33D2          xor edx,edx
004010D8|>8A16          /mov dl,byte ptr ds:
004010DA|.33C2          |xor eax,edx                           ;eax=0x61 ^ eax
004010DC|.C1C0 05       |rol eax,0x5                           ;eax转化为2进制数左移5位(貌似说的有点不准确)
004010DF|.46            |inc esi
004010E0|.85D2          |test edx,edx
004010E2|.^ 75 F4         \jnz XCrackme.004010D8
004010E4|.33D2          xor edx,edx             ;edx清零
004010E6|.B9 697A0000   mov ecx,0x7A69
004010EB|.F7F1          div ecx                                  ;eax=eax/0x7a69    edx=eax%0x7a69
004010ED|.8915 80304000 mov dword ptr ds:,edx          ;余数放在0x403080处
004010F3|.25 FF0F0000   and eax,0xFFF                            ;eax=eax | 0xFFF
004010F8|.A3 84304000   mov dword ptr ds:,eax          ;商的值存在0x403084处
分析到这里都感觉比较常规,F9运行程序后,程序停在了第二个GetDlgItemTextA函数处,我们继续往下进行分析
0040110C|.E8 F5010000   call <jmp.&user32.GetDlgItemTextA>       ; \GetDlgItemTextA
00401111|.83F8 23       cmp eax,0x23                           ;序列号长度必须为35位(说明我序列号错了,重新输入01234567890123456789012345678901234作为密码)
00401114|.0F85 BD000000 jnz Crackme.004011D7                     ;密码存放在4030b0--4030d2处
0040111A|.BF B0304000   mov edi,Crackme.004030B0               ;ASCII "01234567890123456789012345678901234"
0040111F|.C647 08 00    mov byte ptr ds:,0x0            ;密码第9位变成0
00401123|.57            push edi
00401124|.E8 9C010000   call Crackme.004012C5                  ;函数004012C5(这里暂且缓缓,稍后分析,暂且不跟进去分析)
00401129|.A3 88304000   mov dword ptr ds:,eax
0040112E|.83C7 09       add edi,0x9
00401131|.C647 08 00    mov byte ptr ds:,0x0            ;密码第9位为0
00401135|.57            push edi
00401136|.E8 8A010000   call Crackme.004012C5
0040113B|.A3 8C304000   mov dword ptr ds:,eax
00401140|.83C7 09       add edi,0x9
00401143|.C647 08 00    mov byte ptr ds:,0x0            ;密码第18位为0
00401147|.57            push edi
00401148|.E8 78010000   call Crackme.004012C5
0040114D|.A3 90304000   mov dword ptr ds:,eax
00401152|.83C7 09       add edi,0x9
00401155|.C647 08 00    mov byte ptr ds:,0x0            ;密码第27位为0
00401159|.57            push edi
0040115A|.E8 66010000   call Crackme.004012C5
0040115F|.A3 94304000   mov dword ptr ds:,eax
我们继续往下进行分析
00401164|.33C9          xor ecx,ecx
00401166|.BA 214E0000   mov edx,0x4E21                           ;这里开始验证账户和密码是否对应
0040116B|>83C1 03       /add ecx,0x3                           ;ecx=ecx+3
0040116E|.83EA 02       |sub edx,0x2                           ;edx=edx-2
00401171|.890D 98304000 |mov dword ptr ds:,ecx         ;0x403098=ecx
00401177|.8915 9C304000 |mov dword ptr ds:,edx         ;0x40309c=edx
0040117D|.68 98304000   |push Crackme.00403098         ;
00401182|.68 A0304000   |push Crackme.004030A0
00401187|.E8 65000000   |call Crackme.004011F1                   ;
0040118C|.68 98304000   |push Crackme.00403098
00401191|.68 A8304000   |push Crackme.004030A8
00401196|.E8 96000000   |call Crackme.00401231                   ;
0040119B|.A1 A0304000   |mov eax,dword ptr ds:
004011A0|.3305 A8304000 |xor eax,dword ptr ds:         ;0x4030A0处的值与0x4030A8处的值不相等则跳转失败
004011A6|.75 2F         |jnz XCrackme.004011D7                   ;
004011A8|.A1 A4304000   |mov eax,dword ptr ds:
004011AD|.3305 AC304000 |xor eax,dword ptr ds:
004011B3|.75 22         |jnz XCrackme.004011D7                   ;0x4030a0与0x4030ac的值不相等则跳转失败
004011B5|.81F9 214E0000 |cmp ecx,0x4E21                        ;ecx==0x4e21的跳转成功
004011BB|.^ 75 AE         \jnz XCrackme.0040116B
004011BD|.6A 40         push 0x40                              ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
004011BF|.68 00304000   push Crackme.00403000                  ; |Crackme
004011C4|.68 16304000   push Crackme.00403016                  ; |Roeoeoeoechtoeoeoeoech!
004011C9|.FF75 08       push                            ; |hOwner
004011CC|.E8 3B010000   call <jmp.&user32.MessageBoxA>         ; \MessageBoxA
004011D1|.33C0          xor eax,eax
004011D3|.C9            leave
004011D4|.C2 1000       retn 0x10
004011D7|>6A 10         push 0x10                              ; /Style = MB_OK|MB_ICONHAND|MB_APPLMODAL
004011D9|.68 00304000   push Crackme.00403000                  ; |Crackme
004011DE|.68 08304000   push Crackme.00403008                  ; |Wrong serial.
004011E3|.FF75 08       push                            ; |hOwner
004011E6|.E8 21010000   call <jmp.&user32.MessageBoxA>         ; \MessageBoxA
004011EB|.33C0          xor eax,eax
004011ED|.C9            leave
004011EE\.C2 1000       retn 0x10
看到这里之后就发现程序的思路还是比较简单的,接下来,我们就需要开始细入的分析程序了,首先从0x004012C5的函数开始分析,这个函数在name的变化中调用了
很多次,可以判定为比较重要的函数。
004012C5/$55            push ebp
004012C6|.8BEC          mov ebp,esp
004012C8|.60            pushad
004012C9|.33C0          xor eax,eax             ;eax清零
004012CB|.33D2          xor edx,edx             ;edx清零
004012CD|.B9 08000000   mov ecx,0x8             ;ecx=8
004012D2|.8B75 08       mov esi,                        ;密码的8位放在esi
004012D5|>8A16          /mov dl,byte ptr ds:
004012D7|.84D2          |test dl,dl                              ;Switch (cases 0..39)
004012D9|.74 14         |je XCrackme.004012EF
004012DB|.80EA 30       |sub dl,0x30                           ;edx=edx-0x30
004012DE|.80FA 0A       |cmp dl,0xA             ;dl的值与0xA比较,小于的跳到004012E6
004012E1|.72 03         |jb XCrackme.004012E6
004012E3|.80EA 07       |sub dl,0x7                              ;dl=dl-0x7;
004012E6|>C1E0 04       |shl eax,0x4                           ;eax=eax*0x4
004012E9|.0BC2          |or eax,edx                              ;eax=ax   eax | edx
004012EB|.46            |inc esi
004012EC|.49            |dec ecx
004012ED|.^ 75 E6         \jnz XCrackme.004012D5
004012EF|>8945 08       mov ,eax                        ;将计算过后的结果放在中
004012F2|.61            popad
004012F3|.8B45 08       mov eax,
004012F6|.C9            leave
004012F7\.C2 0400       retn 0x4
到此为止都还算比较简单,OK,我们继续耐着性子往下继续分析,跳出来之后,我们要来分析,004012C5处的函数一共调用了四次,那么我们可以
猜测,序列号的形式大概是xxxxxxxx0xxxxxxxx0xxxxxxxx0xxxxxxxx一共35位。
我们仔细观察可以看到name与serial的验证过程中出现了call 004011F1与call 00401231这样的调用,这是很重要的函数,我们跟进去看看的说
先从004011F1处的开始~~~
004011F1/$55            push ebp
004011F2|.8BEC          mov ebp,esp
004011F4|.83C4 F0       add esp,-0x10
004011F7|.60            pushad
004011F8|.8B7D 08       mov edi,                        ;arg1
004011FB|.8B75 0C       mov esi,                        ;arg2
004011FE|.8D5D F8       lea ebx,                        ;local1
00401201|.8D4D F0       lea ecx,                        ;local2
00401204|.56            push esi
00401205|.56            push esi
00401206|.53            push ebx
00401207|.E8 7E000000   call Crackme.0040128A                  ;
0040120C|.68 88304000   push Crackme.00403088                  ;Q1
00401211|.56            push esi
00401212|.51            push ecx
00401213|.E8 72000000   call Crackme.0040128A                  ;loacl2=Q1 * arg2
00401218|.51            push ecx
00401219|.53            push ebx
0040121A|.57            push edi
0040121B|.E8 49000000   call Crackme.00401269                  ;
00401220|.68 90304000   push Crackme.00403090                  ;Q2
00401225|.57            push edi
00401226|.57            push edi
00401227|.E8 3D000000   call Crackme.00401269                  ;arg1=arg1 + Q2
0040122C|.61            popad
0040122D|.C9            leave
0040122E\.C2 0800       retn 0x8
呵呵。。。。里面还有两处不明了的CALL,call Crackme.0040128A与call Crackme.00401269,恩。。。貌似开始有点复杂了。。好了,我们耐着
性子听着歌加把油继续向下分析~
从call Crackme.0040128A 开始分析
0040128A/$55            push ebp
0040128B|.8BEC          mov ebp,esp
0040128D|.60            pushad
0040128E|.8B75 0C       mov esi,                        ;x=a + bi
00401291|.8B5D 10       mov ebx,                        ;y=c + ci
00401294|.8B7D 08       mov edi,                        ;z=e + fi
00401297|.8B16          mov edx,dword ptr ds:               ;edx=a   a为x的实部
00401299|.8B03          mov eax,dword ptr ds:               ;c为y的实部
0040129B|.0FAFD0      imul edx,eax                           ;edx=a*c
0040129E|.8B46 04       mov eax,dword ptr ds:         ;b位x的虚部
004012A1|.8B4B 04       mov ecx,dword ptr ds:         ;d为y的虚部
004012A4|.0FAFC1      imul eax,ecx                           ;eax=b*d
004012A7|.2BD0          sub edx,eax                              ;e=a*c-b*d
004012A9|.8917          mov dword ptr ds:,edx               ;结果存在0013faf4
004012AB|.8B16          mov edx,dword ptr ds:               ;a
004012AD|.8B43 04       mov eax,dword ptr ds:         ;d
004012B0|.0FAFD0      imul edx,eax                           ;edx=a*d
004012B3|.8B46 04       mov eax,dword ptr ds:         ;b
004012B6|.8B0B          mov ecx,dword ptr ds:               ;c
004012B8|.0FAFC1      imul eax,ecx                           ;eax=b*c
004012BB|.03D0          add edx,eax                              ;f=a*d+b*c
004012BD|.8957 04       mov dword ptr ds:,edx         ;结果存放在0013faf8
004012C0|.61            popad
004012C1|.C9            leave
004012C2\.C2 0C00       retn 0xC
这里出现了,我把它们分别比作z,x,y看起来更加的直观,这段代码乍一看看不出什么名堂,定睛一看也还是看不出东西
但是分析的过程中大伙儿是否一种很熟悉的感觉呢?尤其是遇到了
00401297|.8B16          mov edx,dword ptr ds:               ;令edx=a
00401299|.8B03          mov eax,dword ptr ds:               ;令eax=c
0040129B|.0FAFD0      imul edx,eax                           ;edx=a*c
0040129E|.8B46 04       mov eax,dword ptr ds:         ;令eax=b
004012A1|.8B4B 04       mov ecx,dword ptr ds:         ;令ecx=d
004012A4|.0FAFC1      imul eax,ecx                           ;eax=b*d
004012A7|.2BD0          sub edx,eax                              ;edx=a*c-b*d
注释这样标大家就差不多看的比较明白了,这样的运算规则不就是两个复数乘积的实部么?
这个函数实际上实现了两个复数的乘法运算
这样的话大家就应该明白为何我要把x,y,z设成复数的形式了
只要跟复数的运算挂上钩接下来的分析就豁然明朗了,那么我们开始分析call Crackme.00401269
00401269/$55            push ebp
0040126A|.8BEC          mov ebp,esp
0040126C|.60            pushad
0040126D|.8B75 0C       mov esi,                        ;x(a,b)
00401270|.8B5D 10       mov ebx,                        ;y(c,d)
00401273|.8B7D 08       mov edi,                        ;z(e,f)
00401276|.8B06          mov eax,dword ptr ds:               ;a
00401278|.0303          add eax,dword ptr ds:               ;eax=a+c
0040127A|.8907          mov dword ptr ds:,eax               ;0x4030a0的值等于a+c
0040127C|.8B46 04       mov eax,dword ptr ds:         ;eax=b
0040127F|.0343 04       add eax,dword ptr ds:         ;eax=b+d
00401282|.8947 04       mov dword ptr ds:,eax         ;004030a4存放b+d的值
00401285|.61            popad
00401286|.C9            leave
00401287\.C2 0C00       retn 0xC
这个函数实际上就是实现了两个复数的加法运算
我们再次回到004011F1处,细节分析完之后我们就需要从大方向继续分析下面这一大块代码到底能够得出一些什么东东
004011F1/$55            push ebp
004011F2|.8BEC          mov ebp,esp
004011F4|.83C4 F0       add esp,-0x10
004011F7|.60            pushad
004011F8|.8B7D 08       mov edi,                        ;arg1
004011FB|.8B75 0C       mov esi,                        ;arg2
004011FE|.8D5D F8       lea ebx,                        ;local1
00401201|.8D4D F0       lea ecx,                        ;local2
00401204|.56            push esi
00401205|.56            push esi
00401206|.53            push ebx
00401207|.E8 7E000000   call Crackme.0040128A                  ;虚数的乘法运算local1=arg2 * arg2
0040120C|.68 88304000   push Crackme.00403088                  ;Q1
00401211|.56            push esi
00401212|.51            push ecx
00401213|.E8 72000000   call Crackme.0040128A                  ;loacl2=Q1 * arg2
00401218|.51            push ecx
00401219|.53            push ebx
0040121A|.57            push edi
0040121B|.E8 49000000   call Crackme.00401269                  ;虚数的加法运算arg1=local2 + loacl1
00401220|.68 90304000   push Crackme.00403090                  ;Q2
00401225|.57            push edi
00401226|.57            push edi
00401227|.E8 3D000000   call Crackme.00401269                  ;arg1=arg1 + Q2
0040122C|.61            popad
0040122D|.C9            leave
0040122E\.C2 0800       retn 0x8
那么最终得出的表达式即为F1 = Arg1 = Arg2*Arg2 + (Arg2 * Q1) + Q2
00401196|.E8 96000000   |call Crackme.00401231                  
这里我们依旧跟进去看看情况
00401231/$55            push ebp
00401232|.8BEC          mov ebp,esp
00401234|.83C4 F0       add esp,-0x10
00401237|.60            pushad
00401238|.8B7D 08       mov edi,                        ;arg1
0040123B|.8B75 0C       mov esi,                        ;arg2
0040123E|.8D5D F8       lea ebx,                        ;local1
00401241|.8D4D F0       lea ecx,                        ;local2
00401244|.68 78304000   push Crackme.00403078                  ;N1
00401249|.56            push esi
0040124A|.53            push ebx
0040124B|.E8 19000000   call Crackme.00401269                  ;local1=arg2+N1
00401250|.68 80304000   push Crackme.00403080                  ;N2
00401255|.56            push esi
00401256|.51            push ecx
00401257|.E8 0D000000   call Crackme.00401269                  ;loacl2=arg2+N2
0040125C|.51            push ecx
0040125D|.53            push ebx
0040125E|.57            push edi
0040125F|.E8 26000000   call Crackme.0040128A                  ;arg1=loacl1 * local2
00401264|.61            popad
00401265|.C9            leave
00401266\.C2 0800       retn 0x8
里面多次调用的函数我们已经分析过了,这样的话分析起来就十分方便了。
0040119B|.A1 A0304000   |mov eax,dword ptr ds:       ;取0x4030A0处的值放入eax
004011A0|.3305 A8304000 |xor eax,dword ptr ds:         ;0x4030a0与0x4030a8处的值进行异或运算
004011A6|.75 2F         |jnz XCrackme.004011D7                   ;两个值不相等则跳转失败
004011A8|.A1 A4304000   |mov eax,dword ptr ds:      
004011AD|.3305 AC304000 |xor eax,dword ptr ds:      ;0x4030a4的值与0x4030ac的值进行异或运算
004011B3|.75 22         |jnz XCrackme.004011D7                   ;0x4030a0与0x4030ac的值不相等则跳转失败
004011B5|.81F9 214E0000 |cmp ecx,0x4E21                        ;ecx==0x4e21的跳转成功
004011BB|.^ 75 AE         \jnz XCrackme.0040116B
这一段代码是关键的关键,因为这里有两次跳转的验证,那么现在的关键就是需要来看看0x4030A0,0X4030A4,0X4030A8,0X4030AC
这四个地址处的值分别为多少,是怎么运算并且存入进去的~
我们回顾一下程序,会发现
0040127A|.8907          mov dword ptr ds:,eax               ;0x4030a0的值等于a+c
00401282|.8947 04       mov dword ptr ds:,eax         ;0x004030a4存放b+d的值
004012A9|.8917          mov dword ptr ds:,edx               ;e=a*c-b*d结果存在0013faf4
004012BD|.8957 04       mov dword ptr ds:,edx         ;f=a*d+b*c结果存放在0013faf8
既然已经知道了这四个未知量的位置,并且我们已经分析过了他们的计算方法,那么用name来计算serial的方式就非常的直观明了了~
004011A0|.3305 A8304000 |xor eax,dword ptr ds:         ;F1!=F2则跳转失败
这里我们来分析一下数学问题
00401187|.E8 65000000   |call Crackme.004011F1                   ;F1 = Arg2 * Arg2 + (Arg2 * Q1) + Q2
从这个函数调用完之后我们知道F1 = Arg2 * Arg2 + (Arg2 * Q1) + Q2
00401196|.E8 96000000   |call Crackme.00401231                   ;F2 = (Arg2 + N1) * (Arg2 + N2)
这个函数调用完之后知道F2 = (Arg2 + N1) * (Arg2 + N2)
F1=F2,建立等式,两边化简,最终得到的结果为:Q2 + Arg2 * Q1 = (N1 + N2) * Arg2 + N1 * N2
进而可以推出Q1=N1 +N2 , Q2=N1 * N2
又因为前面我们已经分析过,Q1,Q2,N1,N2全部都为复数,那么我们进而可以得到以下的等式
设N1=a + bi,N2=c + di,则
Q1s=a + c
Q1x=b + d
Q2s=a * c - b * d
Q2x=a * d + b * c
现在离成功仅仅只有一步之遥了,我们只需要知道N1与N2的值便大功告成了,shall we continue
004010BE|.A3 78304000   mov dword ptr ds:,eax          ;0x403078=name's sum
004010ED|.8915 80304000 mov dword ptr ds:,edx          ;余数放在0x403080
这样便于序列号的运算结果全部都联系起来了,这样便不难得出
a,b,c,d的具体表达式了,好了,我们开始写注册机吧~
比如我输入的aaaaaa,那么
a=00000246
b=000006CF
c=000034A2
d=00000CFE
serial1=a+c
serial2=b+d
serial3=a*c-b*d
serial4=a*d+b*c
那么serial就是000036E8-000013CD-001F32EA-0183E472
有程序中的分析可以知道,序列号的第9,18,27位是任意的,因为从name的计算中发现与
这几位的值完全没有关系。
PS:因为注册机写的不太完美,给出的serial里面的小写字母一定要用大写形式写入CM当中才行。。。编程水平不够。。。


感言:从头到尾分析一个CM,从中能获得学习到很多的东西,分析小软件确实很培养的一个人的耐心,现在自己越来越感觉到浮躁不得,欲速则不达,学习还是的应该脚踏实地扎扎实实的学才行,平时也应该多多练练手,不然就会忘掉。学校里所学的东西应该还是要和实践紧紧的联系在一起才行,编写注册机的时候,才发现自己C语言的编程存在不小的问题,写完了以后自己确实不是很满意http://bbs.pediy.com/images/smilies/redface.gifhttp://bbs.pediy.com/images/smilies/redface.gifhttp://bbs.pediy.com/images/smilies/redface.gif
自己应该加把的努力才行,希望能和论坛里的大牛多多指点,提出宝贵的意见,自己也希望能和论坛里的人多多交流,相互的提高~http://bbs.pediy.com/images/smilies/tongue.gif


crackme:http://pan.baidu.com/s/1kTFVe6b
keygen:http://pan.baidu.com/s/1eQBv6CE

798880914 发表于 2014-8-19 13:34

沙发???

798880914 发表于 2014-8-19 13:35

新手能分析成这样已经很ok了。

吾爱扣扣 发表于 2014-8-19 14:34

膜拜算法牛
页: [1]
查看完整版本: 【原创】一个不错的CM的算法分析