修改现有程序成KEYGEN
dOsKey[Nuke Group]
做过KEYGEN的CRACKER可以都遇到过这种情况:生成注册码的算法十分复杂。虽然可以逆向分析出整个算法,但是需要很长时间。而程序内部已经包含了算注册码的函数的时候,我们可以使用程序内部的算码函数来实现我们自己的KEYGEN,以达到四两拨千斤的效果。
目标程序是某个国产手机写码软件。第一次运行的时候程序要求输入根据机器码得来的用户名和注册码。用FI扫描后得知目标软件无壳,用VC6写的,目前很少国产软件不加壳了,看来我们很幸运,省略了脱壳的步骤。接下来我们用OllyDbg载入目标软件,F9运行。在目标程序中输入用户名“DOSKEY”和注册码U20-111111111111,点OK,弹出有“注册失败!”的提示窗口。不管它,切换回OllyDbg按F12暂停在主线程。然后按CTRL+F12直到OllyDbg状态为“Till return”,切换回目标程序点消息框的确定按钮。目标程序的进程再次被OllyDbg暂停住,一直按CTRL+F12返回到主线程我们就来到这里:
代码:
0042675F /$ 55 PUSH EBP 00426760 |. 8BEC MOV EBP, ESP 00426762 |. E8 91140000 CALL 2210.00427BF8 00426767 |. 8B40 04 MOV EAX, DWORD PTR DS:[EAX+4] 0042676A |. 85C0 TEST EAX, EAX 0042676C |. 74 15 JE SHORT 2210.00426783 0042676E |. FF75 10 PUSH DWORD PTR SS:[EBP+10] 00426771 |. 8B10 MOV EDX, DWORD PTR DS:[EAX] 00426773 |. 8BC8 MOV ECX, EAX 00426775 |. FF75 0C PUSH DWORD PTR SS:[EBP+C] 00426778 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] 0042677B |. FF92 8C000000 CALL DWORD PTR DS:[EDX+8C] 00426781 |. EB 10 JMP SHORT 2210.00426793 <= 返回到这里,上面就是调用MessageBox显示消息框的CALL 00426783 |> FF75 10 PUSH DWORD PTR SS:[EBP+10] ; /Arg3 00426786 |. 33C9 XOR ECX, ECX ; | 00426788 |. FF75 0C PUSH DWORD PTR SS:[EBP+C] ; |Arg2 0042678B |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |Arg1 0042678E |. E8 E5FEFFFF CALL 2210.00426678 ; \2210.00426678 00426793 |> 5D POP EBP 00426794 \. C2 0C00 RETN 0C
很显然这只是一个显示间接消息框的子函数,CTRL+F12返回到这个地方:
代码:
00403815 > 6A 00 PUSH 0 ; /Arg3 = 00000000 00403817 . 6A 00 PUSH 0 ; |Arg2 = 00000000 00403819 . 68 14844300 PUSH 2210.00438414 ; |Arg1 = 00438414 0040381E . E8 3C2F0200 CALL 2210.0042675F ; \2210.0042675F 00403823 . 8BCD MOV ECX, EBP 00403825 . E8 36000000 CALL 2210.00403860 0040382A > 8BCD MOV ECX, EBP <= 返回到这里 0040382C . E8 9AC40100 CALL 2210.0041FCCB 00403831 . 8D4C24 10 LEA ECX, DWORD PTR SS:[ESP+10]
看403819h处压入的438414h就是“注册失败!”,看来我们已经离比较注册码的地方不远了。看403815h处前面的“>”说明有语句跳转到这里。选择403815h这个语句在这个语句前面上,OllyDbg的Information栏目显示“Jump from 004037E2”,我们来到4037E2h处:
代码:
004037DB . E8 5AFB0100 CALL 2210.0042333A 004037E0 . 84DB TEST BL, BL 004037E2 . 74 31 JE SHORT 2210.00403815 <= 把这里NOP掉就爆破了
这个CALL就是比较注册码的地方。我们把4037E2h的语句NOP掉就爆破了。可是我们要的不是这个结果。是该祭出CRACKER的杀手工具IDA的时候了。
用IDA打开目标程序,待IDA分析完毕后按G跳转到4037DBh处。向上翻页到这个函数的首部4036A0h处,这里就是我们要分析的地方:
代码:
.text:004036A0 sub_4036A0 proc near ; DATA XREF: .rdata:0042DA8Co .text:004036A0 .text:004036A0 var_1C = dword ptr -1Ch .text:004036A0 var_18 = dword ptr -18h .text:004036A0 var_14 = dword ptr -14h .text:004036A0 var_10 = dword ptr -10h .text:004036A0 var_C = dword ptr -0Ch .text:004036A0 var_4 = dword ptr -4 .text:004036A0 .text:004036A0 000 mov eax, large fs:0 .text:004036A6 000 push 0FFFFFFFFh .text:004036A8 004 push offset loc_42ADE8 .text:004036AD 008 push eax .text:004036AE 00C mov large fs:0, esp .text:004036B5 00C sub esp, 10h .text:004036B8 01C push ebx .text:004036B9 020 push ebp .text:004036BA 024 push esi .text:004036BB 028 push edi .text:004036BC 02C mov ebp, ecx .text:004036BE 02C push 1 .text:004036C0 030 call FUN_UpdateData() ; 刷新数据,CDialog::UpdateData() .text:004036C5 02C mov eax, [ebp+60h] .text:004036C8 02C lea edi, [ebp+60h] .text:004036CB 02C cmp dword ptr [eax-8], 10h ;比较注册码长度是否为16个字符,是就跳,否则提示并结束 .text:004036CF 02C jz short loc_4036F2 .text:004036D1 02C push 0 .text:004036D3 030 push 0 .text:004036D5 034 push offset aVSIDA ; "注册码长度错误!" .text:004036DA 038 call FUN_MessageBox ;提示注册码长度错误 .text:004036DF 02C mov ecx, [esp+2Ch+var_C] .text:004036E3 02C mov large fs:0, ecx .text:004036EA 02C pop edi .text:004036EB 028 pop esi .text:004036EC 024 pop ebp .text:004036ED 020 pop ebx .text:004036EE 01C add esp, 1Ch .text:004036F1 000 retn .text:004036F2 ; ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ .text:004036F2 .text:004036F2 loc_4036F2: ; CODE XREF: sub_4036A0+2Fj .text:004036F2 02C mov ecx, [ebp+64h] .text:004036F5 02C push 0 .text:004036F7 030 cmp dword ptr [ecx-8], 5 ; 比较用户名长度是否大于5个字符, 大于跳, 否则显示提示并结束 .text:004036FB 030 jg short loc_40371C .text:004036FD 030 push 0 .text:004036FF 034 push offset aVSZDJ5 ; "注册用户名长度应大于5位!" .text:00403704 038 call FUN_MessageBox .text:00403709 02C mov ecx, [esp+2Ch+var_C] .text:0040370D 02C mov large fs:0, ecx .text:00403714 02C pop edi .text:00403715 028 pop esi .text:00403716 024 pop ebp .text:00403717 020 pop ebx .text:00403718 01C add esp, 1Ch .text:0040371B 000 retn ; time_t * ======================================== 看了上面的代码可能你以为没什么,但是不知道你发现没有,[EBP+60]里面就是注册码的地址,然后注册码的地址减8就是注册码的长度。 用户名是[EBP+64]是用户名的字符串地址,用户名的地址减8就是用户名的长度。同理可以取得机器码的地址[EBP+5C]。这几个是非常关键的数据。 ======================================== .text:0040371C ; ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ .text:0040371C .text:0040371C loc_40371C: ; CODE XREF: sub_4036A0+5Bj .text:0040371C 030 call _time ;取得系统时间 .text:00403721 030 push eax ; unsigned int .text:00403722 034 call _srand ;将系统时间做SEED .text:00403727 034 add esp, 8 .text:0040372A 02C call _rand ;生成随机数 .text:0040372F 02C mov bl, al ;AL里面是随机数 .text:00403731 02C push edi .text:00403732 030 lea ecx, [esp+30h+var_1C] .text:00403736 030 mov byte ptr [esp+30h+var_14], bl ; 保存随机数到临时变量中 .text:0040373A 030 call FUN_GetBuf ; 取缓冲 .text:0040373F 02C mov eax, [edi] ; EDI=注册码地址 .text:00403741 02C mov esi, 4 ; 初始化for循环控制变量,不运算“U20-” .text:00403746 02C mov [esp+2Ch+var_4], 0 .text:0040374E 02C cmp [eax-8], esi ; 进入循环的第一次比较,[eax-8]中是注册码长度 .text:00403751 02C jle short loc_403789 .text:00403753 .text:00403753 loc_403753: ; CODE XREF: sub_4036A0+E7j .text:00403753 02C mov al, [eax+esi] ; 取注册码中位置为esi的字符 .text:00403756 02C mov dl, bl ; bl为随机数,dl为运算用变量 .text:00403758 02C and dl, 7Fh ; 保留低7位 .text:0040375B 02C mov ecx, edi .text:0040375D 02C xor dl, al ; 注册码中的字符和随机数进行异或运算,结果在dl中 .text:0040375F 02C add dl, 20h ; 运算结果加20h .text:00403762 02C mov byte ptr [esp+2Ch+var_18], dl ; 保存运算结果到临时变量中 .text:00403766 02C mov eax, [esp+2Ch+var_18] ; 取回来准备压栈 .text:0040376A 02C push eax ; 压入运算后的结果 .text:0040376B 030 push esi ; 压入位置 .text:0040376C 034 call ?SetAt@CString@@QAEXHD@Z ; CString::SetAt(int,char) ; 调用CString::SetAt()来替换掉原先注册码中的字符 .text:00403771 02C inc bl ; 随机数加1 .text:00403773 02C test bl, 1 ; 如果随机数等于一跳转到40377F处 .text:00403776 02C jz short loc_40377F .text:00403778 02C shr bl, 1 ; 右移 .text:0040377A 02C or bl, 80h ; 逻辑加80h .text:0040377D 02C jmp short loc_403781 .text:0040377F ; ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ .text:0040377F .text:0040377F loc_40377F: ; CODE XREF: sub_4036A0+D6j .text:0040377F 02C shr bl, 1 ; 右移 .text:00403781 .text:00403781 loc_403781: ; CODE XREF: sub_4036A0+DDj .text:00403781 02C mov eax, [edi] .text:00403783 02C inc esi ; for循环的第三部分,控制变量加一 .text:00403784 02C cmp esi, [eax-8] ; for循环的第二部分,控制变量是否小于注册码长度,小就继续循环,否则离开循环 .text:00403787 02C jl short loc_403753 .text:00403789 .text:00403789 loc_403789: ; CODE XREF: sub_4036A0+B1j ======================================== 这里的for循环是将输入的注册码编码。为了后面的比较不是直接的明码,但是这样做并不高明。看后面离就知道了 ======================================== .text:00403789 02C mov ecx, [esp+2Ch+var_14] ; 取回刚才保护的随机数 .text:0040378D 02C lea edx, [ebp+5Ch] ; 取机器码 .text:00403790 02C push ecx .text:00403791 030 push ecx ; 压入随机数 .text:00403792 034 mov ecx, esp ; .text:00403794 034 mov [esp+34h+var_14], esp .text:00403798 034 push edx .text:00403799 038 call FUN_GetBuf ; 取机器码缓冲 .text:0040379E 034 push ecx ; 压入机器码 .text:0040379F 038 lea esi, [ebp+64h] .text:004037A2 038 mov ecx, esp .text:004037A4 038 mov [esp+38h+var_10], esp .text:004037A8 038 push esi ; 压入用户名 .text:004037A9 03C mov byte ptr [esp+3Ch+var_4], 1 .text:004037AE 03C call FUN_GetBuf .text:004037B3 038 lea eax, [esp+38h+var_18] .text:004037B7 038 mov ecx, ebp .text:004037B9 038 push eax ; 压入保存正确的经过编码的注册码的临时空间 .text:004037BA 03C mov byte ptr [esp+3Ch+var_4], 0 .text:004037BF 03C call FUN_Main ======================================== 这个CALL是计算正确的经过编码的注册码的。这里是本问所要利用到的算码函数!我们看看进入CALL之前堆栈中的内容: 0012F904 0012F928 |Arg1 = 0012F928 <= 临时空间 0012F908 00936F38 |Arg2 = 00936F38 ASCII "DOSKEY" <= 用户名 0012F90C 00936EE8 |Arg3 = 00936EE8 ASCII "0000E87C1C92" <= 机器码 0012F910 000000CE \Arg4 = 000000CE <= 随机数,编码用的 ======================================== .text:004037C4 02C mov eax, [eax] ; eax中保存的是正确的经过编码的注册码的地址 .text:004037C6 02C mov edi, [edi] ; 我们输入的注册码,经过编码的 .text:004037C8 02C push eax ; 压入正确的 .text:004037C9 030 push edi ; 压入我们输入的 .text:004037CA 034 call __mbscmp ; 比较 .text:004037CF 034 add esp, 8 .text:004037D2 02C lea ecx, [esp+2Ch+var_18] .text:004037D6 02C test eax, eax .text:004037D8 02C setz bl .text:004037DB 02C call sub_42333A .text:004037E0 02C test bl, bl ; 如果不相同就跳,显示“注册失败” .text:004037E2 02C jz short loc_403815 .text:004037E4 02C push ecx .text:004037E5 030 mov ecx, esp .text:004037E7 030 mov [esp+30h+var_10], esp .text:004037EB 030 push esi .text:004037EC 034 call FUN_GetBuf .text:004037F1 030 push ecx .text:004037F2 034 lea edx, [esp+34h+var_1C] .text:004037F6 034 mov ecx, esp .text:004037F8 034 mov [esp+34h+var_14], esp .text:004037FC 034 push edx .text:004037FD 038 mov byte ptr [esp+38h+var_4], 2 .text:00403802 038 call FUN_GetBuf .text:00403807 034 mov ecx, ebp .text:00403809 034 mov byte ptr [esp+34h+var_4], 0 .text:0040380E 034 call sub_402950 ; 将注册码保存到注册表中,下次启动的时候检查 .text:00403813 02C jmp short loc_40382A .text:00403815 ; ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ .text:00403815 .text:00403815 loc_403815: ; CODE XREF: sub_4036A0+142j .text:00403815 02C push 0 .text:00403817 030 push 0 .text:00403819 034 push offset aVSZ ; "注册失败!" .text:0040381E 038 call FUN_MessageBox .text:00403823 02C mov ecx, ebp .text:00403825 02C call sub_403860 .text:0040382A .text:0040382A loc_40382A: ; CODE XREF: sub_4036A0+173j .text:0040382A 02C mov ecx, ebp .text:0040382C 02C call ?OnOK@CDialog@@MAEXXZ ; CDialog::OnOK(void) .text:00403831 02C lea ecx, [esp+2Ch+var_1C] .text:00403835 02C mov [esp+2Ch+var_4], 0FFFFFFFFh .text:0040383D 02C call sub_42333A .text:00403842 02C mov ecx, [esp+2Ch+var_C] .text:00403846 02C pop edi .text:00403847 028 pop esi .text:00403848 024 pop ebp .text:00403849 020 mov large fs:0, ecx .text:00403850 020 pop ebx .text:00403851 01C add esp, 1Ch .text:00403854 000 retn .text:00403854 sub_4036A0 endp .text:00403854 .text:00403854 ; ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
好了,我们已经分析完比较的过程。大概是这样:
1、生成随机数
2、将输入的注册码利用随机数进行编码
3、根据输入的注册码、用户名、机器码和随机数生成正确的经过编码的注册码。
4、比较编码后的注册码是否相同。如果不同就提示错误。否则就注册成功。
我们现在来看看4037BFh处的生成注册码的函数。我们跳转到402A30h处。汗。变量就一百多个,代码有1.8K字节之多。我们只能找捷径了。向下翻页到这里:
代码:
.text:004030B8 loc_4030B8: ; CODE XREF: FUN_Main+6ACj .text:004030B8 18C mov cl, byte ptr [esp+esi+18Ch+var_158] .text:004030BC 18C mov dl, al .text:004030BE 18C and dl, 7Fh .text:004030C1 18C xor dl, cl .text:004030C3 18C add dl, 20h .text:004030C6 18C inc al .text:004030C8 18C test al, 1 .text:004030CA 18C mov byte ptr [esp+esi+18Ch+var_158], dl .text:004030CE 18C jz short loc_4030D6 .text:004030D0 18C shr al, 1 .text:004030D2 18C or al, 80h .text:004030D4 18C jmp short loc_4030D8 .text:004030D6 ; ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ .text:004030D6 .text:004030D6 loc_4030D6: ; CODE XREF: FUN_Main+69Ej .text:004030D6 18C shr al, 1 .text:004030D8 .text:004030D8 loc_4030D8: ; CODE XREF: FUN_Main+6A4j .text:004030D8 18C inc esi .text:004030D9 18C cmp esi, 0Ch .text:004030DC 18C jl short loc_4030B8
发现没有?和我们前面分析的那个for循环如出一辙,只是这段代码是直接操作的内存。我们用OllyDbg跟踪到4030B8h处看看。esp+esi+18Ch+var_158中就是没有“U20-”的正确注册码。然后:
代码:
.text:004030F9 18C push ecx .text:004030FA 190 push offset aU20 ; "U20-" .text:004030FF 194 push edx .text:00403100 198 call sub_4235CF
这个函数将“U20-”和编码后的注册码连接成正确的经过编码的注册码。最后函数直接返回了这个字符串的地址。用OllyDbg跟踪到4030B8h处,把4030B8h到4030DCh的代码全部填充成NOP。我们再继续运行,这个函数就返回的正确的未编码的注册码了。
好了。到这里我们的准备工作已经都做好了。我们要开始DIY这个程序了首先,备份原始文件,以防万一。然后我们要修改对话框可以接受输入机器码。用Resource Hacker打开目标程序,找到对话框资源1160的2052代码页,点显示对话框,选择机器码的EDIT控件,在编辑器里面去掉ES_READONLY属性,保存退出。修改算注册码的函数然它可以自己生成未编码的注册码。用HIEW打开目标程序,将4030B8h到4030DCh的数据填充成90h,保存退出。用LordPE打开目标文件,在目录中点击引入表后面的“..”,点右键添加引入表,添加两个我们要使用的API函数,一个是USER32的FindWindowA和USER32的SetDlgItemTextA。最后我们开始做主要的修改,然目标程序可以自己显示注册码。
用OllyDbg打开刚才修改过后的目标程序。CTRL+G跳转到4036C5h(4036C5h是刷新数据的地方),F2下断点,然后F9运行目标程序。随便输入用户名和注册码,点OK后被OllyDbg中断。点击右键,在上下文菜单中选择Search for\Name(label) in current module,查找FindWindowA、SetDlgItemTextA、VirtualAlloc、VirtualFree的地址。下面我要调用这些API的时候要这个写:call [API 地址],这些地址是在引入表中的地址,如果不这样调用,程序就无法跨平台。问为什么?看看PE格式的详细解释吧。
选择4036C5h到40371Bh的代码,点右键,在上下问菜单中选择Binary\Fill With NOPs,将这个函数后面的代码填充为NOP。然后我们逐行输入下面的代码:
代码:
004036A0 . 64:A1 00000000 MOV EAX, DWORD PTR FS:[0] 004036A6 . 6A FF PUSH -1 004036A8 . 68 E8AD4200 PUSH 复件_221.0042ADE8 004036AD . 50 PUSH EAX 004036AE . 64:8925 0000000>MOV DWORD PTR FS:[0], ESP 004036B5 . 83EC 10 SUB ESP, 10 004036B8 . 53 PUSH EBX 004036B9 . 55 PUSH EBP 004036BA . 56 PUSH ESI 004036BB . 57 PUSH EDI 004036BC . 8BE9 MOV EBP, ECX 004036BE . 6A 01 PUSH 1 004036C0 . E8 B4E80100 CALL 复件_221.00421F79 ================================上面是原有的代码========================================== 004036C5 . 8B45 64 MOV EAX, DWORD PTR [EBP+64] ;取得用户名地址 004036C8 . 8B58 F8 MOV EBX, DWORD PTR [EAX-8] ;取得用户名长度 004036CB . 83FB 05 CMP EBX, 5 ;比较长度是否大于5,否则就利用原先代码中的42675Fh函数显示提示字符串,并终止本函数 004036CE . 7F 21 JG SHORT 复件_221.004036F1 004036D0 . 6A 00 PUSH 0 ; /Arg3 = 00000000 004036D2 . 6A 00 PUSH 0 ; |Arg2 = 00000000 004036D4 . 68 20844300 PUSH 复件_221.00438420 ; |Arg1 = 00438420 004036D9 . E8 81300200 CALL 复件_221.0042675F ; \复件_221.0042675F 004036DE . 8B4C24 20 MOV ECX, DWORD PTR [ESP+20] 004036E2 . 64:890D 0000000>MOV DWORD PTR FS:[0], ECX 004036E9 . 5F POP EDI 004036EA . 5E POP ESI 004036EB . 5D POP EBP 004036EC . 5B POP EBX 004036ED . 83C4 1C ADD ESP, 1C 004036F0 . C3 RETN ==============================用户名长度OK就跳转到下面来================================= 在这里为我们算出的序列号分配临时内存空间。按道理来说可以直接写入会原先保存注册码的内存,然后使用UpdateData(FALSE)写会到界面,但是我测试过好多次都没有通过,所以使用比较低级一点的方法:P 004036F1 > 6A 04 PUSH 4 ; /Protect = PAGE_READWRITE 可读写 004036F3 . 68 00100000 PUSH 1000 ; |AllocationType = MEM_COMMIT 提交 004036F8 . 6A 11 PUSH 11 ; |Size = 11 (17.) 长度17,不要忘记字符串后面的0 004036FA . 6A 00 PUSH 0 ; |Address = NULL 地址NULL,不指定地址 004036FC . FF15 B4D24200 CALL DWORD PTR [<&KERNEL32.VirtualAlloc>] ; \VirtualAlloc 分配内存,地址在EAX中 00403702 . 8BD0 MOV EDX, EAX 00403704 . 52 PUSH EDX ;把分配后的地址保存压栈保护起来,为了后面的释放 00403705 . 6A 00 PUSH 0 ; /Arg4 = 00000000 ;压入参数4,随机数,由于我们已经在上面去掉随机数的处理,所以我们可以随便压入一个值 00403707 . 8B45 5C MOV EAX, DWORD PTR [EBP+5C] ; | 0040370A . 50 PUSH EAX ; |Arg3 ;压入参数3,机器码 0040370B . 8B45 64 MOV EAX, DWORD PTR [EBP+64] ; | 0040370E . 50 PUSH EAX ; |Arg2 ;压入参数2,用户名 0040370F . 52 PUSH EDX ; |Arg1 ;压入参数1,我们分配的内存 00403710 . E8 1BF3FFFF CALL 复件_221.00402A30 ; \复件_221.00402A30 ;算码的关键函数,注册码的地址在EAX中 00403715 . 8B00 MOV EAX, DWORD PTR [EAX] ;返回了指向注册码的字符串地址,我们取回来 ===================================下面是我们为了将注册码显示回编辑框==================================== 00403717 . 50 PUSH EAX ; /Text ;刚才取回来的序列号 00403718 . 68 32040000 PUSH 432 ; |ControlID = 432 (1074.) ;注册码编辑框的ID,这个ID可以用OllyDbg或者是ResHacker取得 0040371D . 68 53374000 PUSH 复件_221.00403753 ; |/Title = "SW Register" ;查找主窗口的标题,这个字符串是我们自己修改的,放在这个函数最后面 00403722 . 6A 00 PUSH 0 ; ||Class = 0 ;类不用,压0 00403724 . FF15 2B904400 CALL DWORD PTR [<&user32.FindWindowA>] ; |\FindWindowA ;找主窗口句柄保存在EAX中 0040372A . 50 PUSH EAX ; |hWnd ;压入主窗口句柄 0040372B . FF15 27904400 CALL DWORD PTR [<&user32.SetDlgItemTextA>] ; \SetDlgItemTextA ;调用这个API设置对话框项目的文本 00403731 . 58 POP EAX ;弹出我们刚才入栈的,我们自己分配的内存空间的地址,准备释放 00403732 . 68 00400000 PUSH 4000 ; /FreeType = MEM_DECOMMIT ;一个对一个 00403737 . 6A 11 PUSH 11 ; |Size = 11 (17.) ;长度 00403739 . 50 PUSH EAX ; |Address ;地址 0040373A . FF15 B4D14200 CALL DWORD PTR [<&KERNEL32.VirtualFree>] ; \VirtualFree ;释放 ==================================下面是照抄403842h到403854h的代码======================================= 00403740 . 8B4C24 20 MOV ECX, DWORD PTR [ESP+20] 00403744 . 64:890D 0000000>MOV DWORD PTR FS:[0], ECX 0040374B . 5F POP EDI 0040374C . 5E POP ESI 0040374D . 5D POP EBP 0040374E . 5B POP EBX 0040374F . 83C4 1C ADD ESP, 1C 00403752 . C3 RETN 00403753 . 53 57 20 52 65 >ASCII "SW Register",0 ;窗口标题字符串
到这里我们已经初步将程序修改好了。然后我们将其保存。点右键,在上下文菜单中选择Copy to Executable\All Modifications,然后在弹出从窗口中选择Save File保存。测试一下,感觉还不错。做一下修饰,改下标题名,放个宣传文本就成下图这样了。
很久没有写文章了。如果有错误或者不足之处,请拼命的提。