欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

转贴:ddcrack的APISPY32 2.4的破解 (25千字)

转贴:ddcrack的APISPY32 2.4的破解 (25千字)

APISPY32 V2.4完全破解实战
  破解过程中你一定遇到过这样的情况:设置了好多断点,可就是拦截不住想要破解的应用程序。现在一切已成过去,有了APISPY32的帮助,我们就能清楚地知道应用程序调用了哪些API函数,设置断点就是轻而易举的事情了。
程序名 :APISPY32
版本   :V2.4
大小   :164KB
运行平台:Windows 9x/NT/2000
保护方式:注册码
破解方式:注册码破解
破解难度:难
程序api32-24.zip
注册机及其C语言源程序

笨冬瓜 :ddcrack.yeah.net
破解步骤:
1. 用softice载入windows(通过CTRL+D来检查softice是否已经准备好,按F5退出);
2. 运行APIS32;
3. 选择“Register”进入注册框;
4. 在“Registration Name:”中输入:ddcrack(随意),“Registration Key:”中输入:12345678(随意);
5. 用CTRL+D呼出softice,下万能断点:bpx hmemcpy,按F5返回APIS32;
6. 在APIS32注册框中点击“OK”,很快程序就被softice拦截下来;
7. 用 bd * 暂停断点 bpx hmemcpy ;
8. 按F12键9次,返回到APIS32的领空,程序停留在下面的地方:
。。。
0167:00404859 TEST EAX,EAX         <-- 程序停在这里
0167:0040485B JZ 004048D3
0167:0040485D MOV EDI,0040CF00
0167:00404862 OR ECX,-01
0167:00404865 XOR EAX,EAX
0167:00404867 REPNZ SCASB          <-- EDI指向我们输入的注册码“12345678”
0167:00404869 NOT ECX
0167:0040486B DEC ECX
0167:0040486C PUSH ECX
0167:0040486D PUSH 0040CF00         <-- 内存地址0040CF00中是输入的注册码“12345678”
0167:00404872 PUSH 00409608         <-- 内存地址00409608中是字符串“UserKey”
0167:00404877 CALL 00404A50
0167:0040487C ADD ESP,0C
0167:0040487F PUSH 4E
0167:00404881 PUSH 0040CF00
0167:00404886 PUSH 000003F0
0167:0040488B PUSH ESI
0167:0040488C CALL EBX
0167:0040488E TEST EAX,EAX
0167:00404890 JZ 004048D3
0167:00404892 MOV EDI,0040CF00
0167:00404897 OR ECX,-01
0167:0040489A XOR EAX,EAX
0167:0040489C REPNZ SCASB          <-- EDI指向我们输入的用户名“ddcrack”
0167:0040489E NOT ECX
0167:004048A0 DEC ECX
0167:004048A1 PUSH ECX
0167:004048A2 PUSH 0040CF00         <-- 内存地址0040CF00中是输入的用户名“ddcrack”
0167:004048A7 PUSH 004095F8
0167:004048AC CALL 00404A50         <-- 内存地址00404A50中是字符串“UserName”
0167:004048B1 ADD ESP,0C
0167:004048B4 PUSH 01
0167:004048B6 PUSH ESI
0167:004048B7 CALL [USER32!EndDialog]    <-- 关闭注册框
0167:004048BD POP EDI
0167:004048BE POP ESI
0167:004048BF POP EBX
0167:004048C0 RET
。。。
9. 刚返回到APIS32的领空,我们就看见指令0167:00404859 TEST EAX,EAX和0167:0040485B JZ 004048D3,该不会是注册码已经判断过了吧?不太可能,首先是一般情况下应用程序不会这么快就会判断注册码的正确性(因为要进行很多处理和转换嘛),再者EAX既不等于1也不等于0,而是等于8。让我们往这条指令的前面看去,你会发现以下一些指令:
。。。
0167:00404844 MOV EBX,[USER32!GetDlgItemTextA]
0167:0040484A PUSH 4E
0167:0040484C PUSH 0040CF00
0167:00404851 PUSH 000003F1
0167:00404856 PUSH ESI
0167:00404857 CALL EBX
0167:00404859 TEXT EAX,EAX
0167:0040485B JZ 004048D3
  现在明白怎么回事了吧^_^——程序先将获取对话框文本的API函数GetDlgItemTextA的地址赋予EBX,下面用CALL EBX指令来调用它。所以这段程序是取得我们输入的文本,因为输入的注册码是8个字符的“12345678”,所以EAX才会等于8嘛,如果输入的字符为空,则EAX=0,程序跳到004048D3去,肯定会让你重新输入注册信息了;
10. 按F10几次走到0167:00404877 CALL 00404A50停下,然后分别用 D 0040CF00 和 D 00409608,你会看到内存地址0040CF00中是我们输入的注册码“12345678”,而内存地址00409608中有“UserKey.SOFTWARE\APIS32”的字符串,好象应该是跟注册表有关的东西;
11. 按F8进入这个CALL 00404A50里面去,你会看到有很多的注册表操作API函数,主要是在注册表中写入信息,显然此处不会去判断注册码。按F12一下走出这个CALL 00404A50来到它的下一句0167:0040487C ADD ESP,0C。先不用着急走,看看下面的程序段,跟上面走过的差不多,你可以一边按F10走一边观察内存数据,可以发现接着是用0167:0040488C的CALL EBX取得我们输入的用户名“ddcrack”,然后0167:004048AC CALL 00404A50将其写入注册表中,最后0167:004048B7 CALL [USER32!EndDialog]关闭注册信息输入框;
12. 按F10走完上面的程序之后继续往后走,你会发现再次进入系统区域(首先进入KERNEL32,然后是USER),按F12数次(多少次我也不清楚,反正挺多的,仔细看好,不要走过头了^_^)直到又重新返回APIS32的领空:
。。。
0167:0040172C CALL [USER32!DialodBoxParamA] <-- 获取对话框参数
0167:00401732 TEST EAX,EAX         <-- 程序停在这里
0167:00401734 JZ 00401873
0167:0040173A PUSH 00409020         <-- 地址00409020中是“APIS32”
0167:0040173F PUSH 0040D1E0         <-- 地址0040D1E0中是“The registration information you provided is incorrect”
0167:00401744 CALL [KERNEL32!lstrcpy]
0167:0040174A PUSH 00409038         <-- 地址00409038中是“V 2.4”
0167:0040174F PUSH 0040D1E0
0167:00401754 CALL [KERNEL32!lstrcat]
0167:0040175A PUSH 00409328         <-- 地址00409328中是“ - ”
0167:0040175F PUSH 0040D1E0
0167:00401764 CALL [KERNEL32!lstrcat]
0167:0040176A CALL 004046A0
0167:0040176F MOV [0040CE74],EAX
0167:00401774 JMP 00401777
0167:00401776 MOV EAX,0274C00A
0167:0040177B JMP 004017A9
。。。
13. 上面的程序都在对字符串进行复制和追加的操作,肯定和注册码的判断无关。按F10往下走,然后用 D 内存地址 可以看到上面注释中的内容。当走到0167:00401774 JMP 00401777时暂停,再按一次F10,你会看到下面的指令突然变掉了:
。。。
0167:00401774 JMP 00401777
0167:00401776 MOV EAX,0274C00A
0167:00401777 OR AL,AL           <-- 程序停在这里
0167:00401779 JZ 0040177D
0167:0040177B JMP 004017A9
。。。
14. 平白无故多了两条指令:0167:00401777 OR AL,AL和0167:00401779 JZ 0040177D,很可疑噢!再按一下F10,后面的指令又变掉了:
。。。
0167:00401774 JMP 00401777
0167:00401776 MOV EAX,0274C00A
0167:00401779 JZ 0040177D          <-- 程序停在这里
0167:0040177B JMP 004017A9
。。。
15. 走了两步,程序指令连续变了两次,上面的0167:0040176A CALL 004046A0肯定有问题(你也许会问:为什么程序的指令会动态改变呢?——其实这是APIS32设置的一个圈套,它并没有动态去修改执行指令代码,只是在0167:00401776处用伪指令 BYTE B8 在此放置了单字节数据B8,因为程序运行时并不知道这是个数据,只是运行到相关的地方才会跳过去(注意Softice一直是将B8作为一个指令代码来反汇编程序代码),所以我们看到的指令在不停的变来变去。需要注意的是:在跟踪很多狡猾的应用程序时你都会发现同样的伎俩,不要理它就是了,因为再狡猾的指令到了该执行的时候终归会原形必露,WINDOWS下的任何程序都没有办法逃过动态跟踪的)。首先将鼠标移到CALL 004046A0这行并点击一下,然后按F9在此设置断点。做好了准备工作,接着用 RFL Z 改变零标志位的状态,这样程序就不会按预定的路线跳到0040177D去,而是执行其后的指令0167:0040177B JMP 004017A9跳到004017A9,按F5让程序自己跑,看见了什么^_^——APIS32出现注册成功的信息“Thanks for registering APIS32!”。显然CALL 004046A0就是注册码检测子程序,只要EAX返回值不为0就表示注册码合法,让我们杀进去看看;
16. 返回到APIS32的注册框,重新输入用户名“ddcrack”和注册码“12345678”,按“OK”键,马上APIS32就被Softice拦截停在0167:0040176A CALL 004046A0上(因为刚才在这里设置了断点),然后按F8进入到这个CALL里去:
。。。
0167:004046A0 PUSH ECX
0167:004046A1 PUSH EBX
0167:004046A2 PUSH EBP
0167:004046A3 PUSH ESI
0167:004046A4 PUSH EDI
0167:004046A5 PUSH 50
0167:004046A7 PUSH 0040C6A0         <-- 内存地址0040C6A0中是输入的注册码“12345678”
0167:004046AC PUSH 00409608         <-- 内存地址00409608中是字符串“UserKey”
0167:004046B1 CALL 004049D0
0167:004046B6 ADD ESP,0C
0167:004046B9 CMP EAX,10          <-- 此时EAX=8
0167:004046BC JGE 004046C6
0167:004046BE XOR EAX,EAX
0167:004046C0 POP EDI
0167:004046C1 POP ESI
0167:004046C2 POP EBP
0167:004046C3 POP EBX
0167:004046C4 POP ECX
0167:004046C5 RET
0167:004046C6 PUSH 2F
0167:004046C8 PUSH 0040CF00         <-- 内存地址0040CF00中是输入的用户名“ddcrack”
0167:004046CD PUSH 004095F8         <-- 内存地址004095F8中是字符串“UserName”
0167:004046D2 CALL 004049D0
0167:004046D7 ADD ESP,0C
0167:004046DA CMP EAX,05          <-- 此时EAX=7
0167:004046DD JGE 004046E7
0167:004046DF XOR EAX,EAX
0167:004046E1 POP EDI
0167:004046E2 POP ESI
0167:004046E3 POP EBP
0167:004046E4 POP EBX
0167:004046E5 POP ECX
0167:004046E6 RET
。。。
17. 按F10走到0167:004046B1 CALL 004049D0停下,分别用 D 0040C6A0 和 D 00409608 命令,可以看到内存地址0040C6A0中是输入的注册码“12345678”,而内存地址00409608中是字符串“UserKey”,按F10两次停在0167:004046B9 CMP EAX,10上,此时EAX=8,很容易就能想到EAX中的8就是注册码“12345678”的字符数,而此处程序的作用就是判断输入的注册码字符数是否大于等于16进制值10,即字符数是否大于等于16,如果小于16个字符,下面的程序0167:004046BE XOR EAX,EAX就会将EAX清零,这样子程序返回后就完蛋了(因为步骤15是我们已经知道EAX的返回值不能等于0);
18. 按一次F10走到0167:004046BC JGE 004046C6处,用 RFL S 改变程序的执行方向(因为我们输入的注册码“12345678”字符数为8,小于16,如果让程序自己走下去就没戏唱了),按F10走下去,和刚才差不多,指令0167:004046DA CMP EAX,05会判断输入的用户名字符数是否大于5(我们输入的“ddcrack”是7个字符,自然没有问题了);
19. 按F5返回APIS32,进入注册框,输入用户名“ddcrack”和注册码“1234567890ABCDEF0”(17位,随意但要大于16位),按“OK”键,APIS32再次被Softice拦截停在0167:0040176A CALL 004046A0上。然后按F8进入CALL 004046A0,这下我们可以大踏步的按F10走到上面程序段的后面程序去了:
。。。
0167:004046E7 MOV EDI,0040C6A0
0167:004046EC OR ECX,-01
0167:004046EF XOR EAX,EAX
0167:004046F1 MOV BYTE PTR [0040C6B1],00
0167:004046F8 REPNZ SCASB
0167:004046FA NOT ECX
0167:004046FC SUB EDI,ECX
0167:004046FE MOV EAX,ECX
0167:00404700 MOV ESI,EDI
0167:00404702 MOV EDI,0040C6B4
0167:00404707 SHR ECX,02
0167:0040470A REPZ MOVSD
0167:0040470C MOV ECX,EAX
0167:0040470E XOR EAX,EAX
0167:00404710 AND ECX,03
0167:00404713 REPZ MOVSB
0167:00404715 MOV EDI,0040C6A9
0167:0040471A OR ECX,-01
0167:0040471D REPNZ SCASB
0167:0040471F NOT ECX
0167:00404721 SUB EDI,ECX
0167:00404723 MOV EDX,ECX
0167:00404725 MOV ESI,EDI
0167:00404727 MOV EDI,0040C6BC
0167:0040472C SHR ECX,02
0167:0040472F REPZ MOVSD
0167:00404731 MOV ECX,EDX
0167:00404733 AND ECX,03
0167:00404736 XOR BL,BL
0167:00404738 REPZ MOVSB
0167:0040473A MOV ESI,0040C6A1       <-- 内存地址0040C6A1中是字符串“234567890ABCDEF0”
0167:0040473F MOV EDI,0040C6B4       <-- 内存地址0040C6A1中是字符串“123456780ABCDEF0”
0167:00404745 CALL 00404930
0167:0040474A MOV CL,BL
。。。
20. 上面的程序段前大部分主要是字符串操作指令,将数据移来移去的,我们可以不用管它,。按F10走到0167:00404745 CALL 00404930停下,然后分别用 D ESI 和 D EDI ,你会看到ESI指向的内存中是字符串“234567890ABCDEF0”,EDI指向的内存地址中是字符串“123456780ABCDEF0”。什么意思呢?不清楚,按F8进入CALL 00404930里去查个究竟:
。。。
0167:00404930 MOV ECX,[ESP+04]       <-- 将字符串“123456780ABCDEF0”的地址赋予ECX
0167:00404934 MOV AL,[ECX]         <-- 取出ECX指向的内存地址中的第一个字符“1”
0167:00404936 CMP AL,39           <-- 判断字符“1”是否小于等于“9”
0167:00404938 JLE 0040493E
0167:0040493A ADD AL,C9           <-- 字符“1”大于“9”则将其ASCII码值加上C9
0167:0040493C JMP 00404940
0167:0040493E ADD AL,D0           <-- 字符“1”小于等于“9”则将其ASCII码值加上D0
0167:00404940 MOV CL,[ECX+01]        <-- 取出ECX指向的内存地址中的第二个字符“2”
0167:00404943 CMP CL,39           <-- 判断字符“2”是否小于等于“9”
0167:00404946 JLE 00404951
0167:00404948 SHL AL,04           <-- AL左移4位,即乘以16
0167:0040494B SUB CL,37           <-- 字符“2”大于“9”则将其ASCII码值减去37
0167:0040494E OR AL,CL           <-- 合并AL和CL,高位为AL,低位为CL
0167:00404950 RET
0167:00404951 SHL AL,04           <-- AL左移4位,即乘以16
0167:00404954 SUB CL,30           <-- 字符“2”小于等于“9”则将其ASCII码值减去30
0167:00404957 OR AL,CL           <-- 合并AL和CL,高位为AL(对应的字符是“1”),低位为CL(对应的字符是“2”)
0167:00404959 RET
。。。
  按一次F10走过0167:00404930 MOV ECX,[ESP+04],用 D ECX 可以看到ECX此时指向的内存区域中是字符串“123456780ABCDEF0”(以后可以看到ECX会递增加2,也就是依次取出后面的字符),仔细看清楚,我们输入的注册码是“1234567890ABCDEF0”,现在已经被APIS32将字符“9”剔除掉了。而上面的子程序其作用实际上是将字符串两个为一组(如“12”)合并成同样形式的16进制数(与“12”对应的是0x12),结果保存在AL中。程序假设输入的注册码只能有数字“1”到“9”和大写字母“A”到“Z”,若是数字,将其ASCII码值(0x30到0x39)加上D0或减去30得到对应的16进制值(0x00到0x09),若是大写字母则将其ASCII码值(0x41到0x46)加上C9或减去37得到其对应的16进制值(0x0A到0x0F),然后将两个16进制值拼装在一起;
21. 按F10走出子程序CALL 00404930,来到它的下一句(为了便于讲解,将其前面的几条指令也列出来):
。。。
0167:00404736 XOR BL,BL           <-- 寄存器BL清零
0167:00404738 REPZ MOVSB
0167:0040473A MOV ESI,0040C6A1
0167:0040473F MOV EDI,0040C6B4        <-- EDI指向字符串“123456780ABCDEF0”的某一位置,
                                初始时指向字符串“123456780ABCDEF0”的开始位置
0167:00404745 CALL 00404930          <-- 两个字符为一组(如“12”)转换为相同形式的16进制值(如 0x12)
0167:0040474A MOV CL,BL           <-- 将BL的值赋予CL
0167:0040474C ADD ESP,04
0167:0040474F ADD CL,50           <-- CL=BL+50
0167:00404752 ADD EDI,02           <-- 指针EDI递增加2,即依次顺序(两个字符为一组)指向字符串“123456780ABCDEF0”中的字符
0167:00404755 XOR AL,CL           <-- 将字符串对应形式的16进制值和CL相异或
0167:00404757 INC BL             <-- BL递增加1
0167:00404759 MOV [ESI-01],AL        <-- 将异或结果AL存入内存地址[ESI-01](即地址0040C6A0开始的内存)中
0167:0040475C MOV BYTE PTR [ESI],00     <-- 其后面补0作为结束标志
0167:0040475F INC ESI             <-- 地址指针ESI递增加1
0167:00404760 CMP BL,08           <-- 循环次数为8次,因为两个字符为一组,所以实际上注册码应为17位数(别忘了被剔除的那1位“9”噢^_^)
0167:00404763 JB 00404744
0167:00404765 PUSH 0040C6B4           <-- 内存地址0040C6B4中是字符串“123456780ABCDEF0”
0167:0040476A PUSH 0040C6A0           <-- 内存地址0040C6A0中是字符串“123456780ABCDEF0”经过上面的程序处理之后的结果,
                          “12 34 56 78 0A BC DE F0”对应的结果为16进制值“42 65 04 2B 5E E9 88 A7”
0167:0040476F CALL 00404960
。。。
22. 按F10继续走到0167:0040476F CALL 00404960时发现其入口参数为字符串“123456780ABCDEF0”和其转换后结果的内存地址,所以我们必须按F8跟进去看看:
。。。
0167:00404960 PUSH EBX
0167:00404961 PUSH EBP
0167:00404962 MOV EBP,[ESP+10]
0167:00404966 PUSH ESI
0167:00404967 PUSH EDI
0167:00404968 MOV EDI,[ESP+14]
0167:0040496C XOR ECX,ECX
0167:0040496E SUB EDI,EBP
0167:00404970 MOV [ESP+18],EDI
0167:00404974 JMP 0040497A
0167:00404976 MOV EDI,[ESP+18]
0167:0040497A LEA ESI,[ECX+EBP]
0167:0040497D XOR EDX,EDX           <-- EDX清零
0167:0040497F MOV EAX,00000001        <-- EAX初始值为1
0167:00404984 MOV DWORD PTR [ESP+14],00000007 <-- 循环次数为7次,放在内存[ESP+14]中
0167:0040498C MOV DL,[EDI+ESI]        <-- EDI+ESI指向刚才字符串的处理结果“42 65 04 2B 5E E9 88 A7”
0167:0040498F MOV EDI,EDX
0167:00404991 MOV EDX,EDI           <-- 将取出的值放在EDX中
0167:00404993 IMUL EAX,EDX          <-- EAX乘以EDX
0167:00404996 CMP EAX,00008899        <-- 乘法结果和00008899比较
0167:0040499B JLE 004049A7
0167:0040499D CDQ                 <-- 大于00008899则执行这里开始的程序
0167:0040499E MOV EBX,00008899        <-- EBX=00008899
0167:004049A3 IDIV EBX             <-- EAX除以00008899
0167:004049A5 MOV EAX,EDX           <-- 除法余数放回EAX中
0167:004049A7 MOV EDX,[ESP+14]       <-- 循环次数放入EDX
0167:004049AB DEC EDX             <-- 循环记数值减1
0167:004049AC MOV [ESP+14],EDX
0167:004049B0 JNZ 00404991          <-- 判断循环次数是否已满7次
0167:004049B2 CDQ               <-- 循环完毕将EAX的值扩展到EDX中
0167:004049B3 MOV EDI,000000BB        <-- EDI=000000BB
0167:004049B8 IDIV EDI             <-- EAX除以EDI
0167:004049BA INC ECX              <-- 外循环记数值递增加1
0167:004049BB CMP ECX,00000008        <-- 是否已将“42 65 04 2B 5E E9 88 A7”处理完毕
0167:004049BE MOV [ESI],DL          <-- 取除法余数作为结果并存在地址[ESI]中,
                   “42 65 04 2B 5E E9 88 A7”对应的处理结果为“6E 54 73 57 13 07 AA 28”
0167:004049C0 MOV BYTE PTR [ECX+EBP],00
0167:004049C4 JL 00404976
0167:004049C6 POP EDI
0167:004049C7 POP ESI
0167:004049C8 POP EBP
0167:004049C9 POP EBX
0167:004049CA RET
。。。
23. 上面的子程序再次对字符串“123456780ABCDEF0”的结果“42 65 04 2B 5E E9 88 A7”进行了处理,得到“6E 54 73 57 13 07 AA 28”。一直按F10走出这个CALL,回到它的下面一条指令:
。。。
0167:0040476F CALL 00404960
0167:00404774 MOV EDI,0040CF00        <-- 从子程序CALL 00404960返回停在这里
0167:00404779 OR ECX,-01
0167:0040477C XOR EAX,EAX
0167:0040477E ADD ESP,08
0167:00404781 REPNZ SCASB           <-- EDI指向输入的用户名“ddcrack”
0167:00404783 NOT ECX
0167:00404785 SUB EDI,ECX
0167:00404787 XOR EBP,EBP
0167:00404789 MOV EDX,ECX
0167:0040478B MOV ESI,EDI
0167:0040478D MOV EDI,0040C6BE
0167:00404792 SHR ECX,02
0167:00404795 REPZ MOVSD
0167:00404797 MOV ECX,EAX
0167:00404799 AND ECX,03
0167:0040479C REPZ MOVSB
0167:0040479E MOV EDI,0040CF00
0167:004047A3 OR ECX,-01
0167:004047A6 REPNZ SCASB           <-- EDI指向输入的用户名“ddcrack”
0167:004047A8 NOT ECX
0167:004047AA DEC ECX
0167:004047AB CMP CL,08            <-- 用户名字符数和8比较
0167:004047AE MOV [ESP+10],CL
0167:004047B2 JAE 004047E4          <-- 大于等于8则直接跳到004047E4去
0167:004047B4 MOV EDX,DOWRD PTR [ESP+10]   <-- 小于8则进行其它处理
0167:004047B8 MOV EDI,0040CF00
0167:004047BD AND EDX,000000FF
0167:004047C3 OR ECX,-01
0167:004047C6 ADD EDX,0040C6BE
0167:004047CC REPNZ SCASB
0167:004047CE NOT ECX
0167:004047D0 SUB EDI,ECX
0167:004047D2 MOV EAX,ECX
0167:004047D4 MOV ESI,EDI
0167:004047D6 MOV EDI,EDX
0167:004047D8 SHR ECX,02
0167:004047DB REPZ MOVSD
0167:004047DD MOV ECX,EAX
0167:004047DF AND ECX,00000003
0167:004047E2 REPZ MOVSB
0167:004047E4 MOV BYTE PTR [0040C6C6],00
。。。
24. 上面又是一段长长的字符串字符操作程序,我们不用仔细去跟踪它。首先按F10走到0167:00404781 REPNZ SCASB处,用 D EDI 命令,将会看到EDI指向输入的用户名“ddcrack”,说明这段程序在对用户名进行处理。走到0167:00404795 REPZ MOVSD时停下来,先用 D ESI ,可以看到相应的内存中是用户名“ddcrack”,再用 D EDI ,然后按F10,发现“ddcrack”已经被搬到EDI指向的内存中了。继续按F10走到0167:004047AB CMP CL,08,显然这条指令在判断用户名字符数是否大于等于8,这里CL=7(“ddcrack”是7位),所以程序接着往下走,当走过0167:004047DB REPZ MOVSD这条指令后你会发现原来内存中的“ddcrack”已经变成了 “ddcrackd”,正好是8位字符;
25. 现在我们知道程序将不足8位的用户名补足8位:规则就是顺序将用户名往后补足8位,例如“ddcrack”补足8位后成为“ddcrackd”。其实刚才我们只是碰巧发现了程序段的功能,如果你仔细去跟踪程序,一样能知道其具体作用,只是费点时间罢了。按F10继续走到上面程序段的后面去:
。。。
0167:004047E4 MOV BYTE PTR [0040C6C6],00
0167:004047EB MOV ECX,0040C6B4          <-- ECX指向注册码处理结果“6E 54 73 57 13 07 AA 28”
0167:004047F0 MOV ESI,00000008          <-- 分别对8个值进行处理
0167:004047F5 MOV AL,[ECX]            <-- 取出某个值
0167:004047F7 CMP AL,20              <-- 和20比较
0167:004047F9 JAE 00404809
0167:004047FB XOR EDX,EDX             <-- 如果小于20
0167:004047FD AND EAX,000000FF          <-- 将AL的值扩展到EAX中
0167:00404802 MOV DL,[ECX+0A]          <-- ECX+0A指向用户名处理结果“ddcrackd”
0167:00404805 OR EDX,EAX             <-- 注册码处理结果和用户名处理的结果对应位进行或运算
0167:00404807 JMP 00404815
0167:00404809 XOR EDX,EDX             <-- 如果大于20
0167:0040480B AND EAX,000000FF          <-- 将AL的值扩展到EAX中
0167:00404810 MOV DL,[ECX+0A]          <-- ECX+0A指向用户名处理结果“ddcrackd”
0167:00404813 XOR EDX,EAX             <-- 注册码处理结果和用户名处理的结果对应位进行异或运算
0167:00404815 ADD EBP,EDX             <-- 运算结果累加到EBP中
0167:00404817 INC ECX
0167:00404818 DEC ESI
0167:00404819 JNZ 004047F5
0167:0040481B XOR EAX,EAX
0167:0040481D POP EDI
0167:0040481E TEST EBP,EBP             <-- 测试EBP的值
0167:00404820 POP ESI
0167:00404821 POP EBP
0167:00404822 SETZ AL                 <-- 如果EBP=0,则AL=1,那么就表示注册码正确了^_^
0167:00404825 POP EBX
0167:00404826 POP ECX
0167:00404827 RET
。。。
26. 程序已经走完,相信你对注册码算法也有了一定的概念,现在让我们来清理一下大脑:
⑴  首先,用户名字符个数必须大于等于5,注册码为17位字符串且必须为数字1到9或者大写字母A到Z;
⑵  将输入的注册码字符串(以“1234567890ABCDEF0”为例)去掉第9位(变成“123456780ABCEDF0),也即表示注册码第9位是任意的;
⑶  将处理过的注册码字符两个为一组,转换成相同形式的16进制值Ri,i=0,1,2,3,4,5,6,7,(“12”“ 34”“ 56 ”“78”“ 0A ”“BC”“ DE”“ F0”分别变成 0x12 0x34 0x56 0x78 0x0A 0xBC 0xDE 0xF0);
⑷  Ri = Ri XOR (0x50+i);
⑸  EAX初值为1:
   FOR(J=0;J<7;J++)
    { EAX=EAX*Ri
      IF(EAX>00008899) EAX=EAX MOD 00008899
    }
   Ri = EAX MOD 000000BB
⑹  格式化输入的用户名为8位字符Ni,i=0,1,2,3,4,5,6,7:多余8位的将超过8位的字符截掉,不足8位的用用户名顺序补齐(如“ddcrack”补足8位后变成“ddcrackd”);
⑺  Ui = Ri XOR Ni(如果Ri大于等于20)或 Ri OR Ni(如果Ri小于20);
⑻  T = U0 + U1 + U2 + U3 + U4 + U5 + U6 + U7 ;
⑼  如果T=0表示注册码合法,否则显示错误信息;
⑽  若要 T = 0,则 U0 = U1 = U2 = U3 = U4 = U5 = U6 = U7 = 0 即可;但是 Ui = Ri XOR Ni 或者 Ri OR Ni,如果 Ri 恒定大于等于20,则 Ui = Ri XOR Ni;此时只要 Ri = Ni ,则 Ui == 0,那么 T == 0 ,就得到了正确的注册码。
27. 下面以我们输入的用户名“ddcrack”和注册码“1234567890ABCDEF0”为例将注册码的算法演示一遍:
注册码:              1 2 3 4 5 6 7 8 9 0 A B C D E F 0
两个一组转变成对应形式的16进制值:  12  34  56  78    0A  BC  DE  F0    (第9位被剔除掉)
⑴  12 XOR 50 = 42;
   34 XOR 51 = 65;
   56 XOR 52 = 04;
   78 XOR 53 = 2B;
   0A XOR 54 = 5E;
   BC XOR 55 = E9;
   DE XOR 56 = 88;
   F0 XOR 57 = A7;
⑵  对于16进制数42,其计算过程为:
     1 * 42 =  42 <= 00008899
    42 * 42 = 1104 <= 00008899
   1104 * 42 = 46308 > 00008899,46308 MOD 8899 = 1E40
   1E40 * 42 = 7CC80 > 00008899,7CC80 MOD 8899 = 5422
   5422 * 42 = 15B0C4 > 00008899,15B0C4 MOD 8899 = 58DC
   58DC * 42 = 16E8B8 > 00008899,16E8B8 MOD 8899 = 7E9E
   7E9E * 42 = 20E6BC > 00008899,20E6BC MOD 8899 = 5A47
省略其它数的计算过程,结果汇总:
   42 -> 5A47
   65 -> 1F02
   04 -> 4000
   2B -> 6DE9
   5E -> 3566
   E9 -> 5CCC
   88 -> 79EC
   A7 -> 0A62
⑶  5A47 MOD BB = 6E
   1F02 MOD BB = 54
   4000 MOD BB = 73
   6DE9 MOD BB = 57
   3566 MOD BB = 13
   5CCC MOD BB = 07
   79EC MOD BB = AA
   0A62 MOD BB = 28
输入用户名:   ddcrack
格式化为8位:   d  d  c  r  a  c  k  d
对应的16进制值: 64  64  63  72 61  63  6B  64
⑷  64 XOR 6E = 0A
   64 XOR 54 = 30
   63 XOR 73 = 10
   72 XOR 57 = 25
   61 OR 13 = 73
   63 OR 07 = 67
   6B XOR AA = C1
   64 XOR 28 = 4C
⑸  累加和为:0A + 30 + 10 + 25 +73 + 67 +C1 + 4C = 256不等于0,所以注册码不正确。
27. 虽然我们已经掌握了注册码的算法过程,但是我们的目标还是没有达到——仍然没有取得正确的注册码?显然我们是不可能根据用户名“ddcrack”推导出正确的注册码的,尽管我们已经非常清楚地知道了注册码的算法(算法过程太复杂,除非你的大脑像电脑^_^),所以必须要换个角度,从现有对程序跟踪的结果来推导出一个合法的注册码;
28. 让我们回头看看:输入的注册码注册码“12 34 56 78 (9) 0A BC DE F0”经过运算最终分别得到16进制数“6E 54 73 57 13 07 AA 28”。前面提到只有注册码处理后的数值分别等于相应的用户名的ASCII码值即可通过注册算法,既然我们已经知道“12 34 56 78 0A BC DE F0”计算后可以得到“6E 54 73 57 13 07 AA 28”,而其中的“6E”“ 54”“ 73 ”“57”分别对应字母“n”“T”“s”“W”,所以我们何不如假设用户名就是“TTTTTTTT”呢?因为从前面的算法演示中可知65经过步骤⑵和步骤⑶得到54,即字母“T”。也就是说假如某一注册码经过处理后得到16进制数值“65 65 65 65 65 65 65 65”,然后经过步骤⑵和步骤⑶后就可以得到“54 54 54 54 54 54 54 54”,分别对应字符“T T T T T T T T”,现在假设用户名是“TTTTTTTT”,那么经过步骤⑷和步骤⑸计算后累加和肯定为零,说明这个注册码是正确的;
29. 假设某一注册码经过拼装后分别为16进制值Si,i=0,1,2,3,4,5,6,7,根据步骤⑴可知:
   S0 XOR 50 = 65
   S1 XOR 51 = 65
   S2 XOR 52 = 65
   S3 XOR 53 = 65
   S4 XOR 54 = 65
   S5 XOR 55 = 65
   S6 XOR 56 = 65
   S7 XOR 57 = 65
根据异或运算的可逆性,我可以求得Si:
   S0 = 65 XOR 50 = 35
   S1 = 65 XOR 51 = 34
   S2 = 65 XOR 52 = 37
   S3 = 65 XOR 53 = 36
   S4 = 65 XOR 54 = 31
   S5 = 65 XOR 55 = 30
   S6 = 65 XOR 56 = 33
   S7 = 65 XOR 57 = 32
30. 我们已知 35 34 37 36 31 30 33 32 分别是注册码的字符两个一组拼装起来的,而注册码的第9位没有进行任何处理,也就是说是任意的,不妨假设它为“5”,由此可得到用户名“TTTTTTTT”对应的正确注册码为“35343736531303332”,赶紧拿去试一试,怎么样啊?是不是很管用呢^_^!
31. 哇噻!终于讲完了,是不是很累啊!为了能得到符合自己大名的正确注册码,只能去写注册机,让电脑帮我们推算了。我自己也写了一个注册机,大家可以参考参考,不过因为前面注册码算法已经讲得很详细,所以注册机的C语言源代码中没有注释信息,这里可以APISPY32注册机和C语言源程序;
32. 最后别忘了用 BC * 清除所有断点,祝你好运^_^!

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功