solly 发表于 2019-6-2 12:37

160 个 CrackMe 之 087 - n0p3x.9 手动脱壳及修复、注册分析和去NAG。

本帖最后由 solly 于 2019-6-2 18:00 编辑

160 个 CrackMe 之 087 - n0p3x.9 是一个比较老的 CrackMe,由于其加壳的原因,不能直接在目前的系统上运行,但脱壳后是可以在最新系统上运行的。
该 CrackMe 需要一个 borland 公司的运行库支持,库文件是 CW3220.DLL ,在网络上有下载。如果没有这个库,壳代码可以正常解码,但是所有库函数入口地址填充的是0,这个时候可以脱壳,但没有导入函数,也运行不了。
也由于这个库的原因,导致没有脱壳前也不能正常运行,这是因为没有脱壳前,壳代码会动态加载CW3220.DLL,并取得各个API入口,填充到原CrackMe代码中。但是这个时候,原有代码并没有解码完成,还有一些全局变量的地址没有解析成可用地址,这个时候调用该库,该库的 DllMain() 初始化时会反过来调用 CrackMe 的一个导出函数“__GetExceptDLLinfo”,导致内存访问非法的异常,系统会关闭 CrackMe,具体解决办法有几个,后面我们采用跳过执行该段代码的方法来避免异常。
先看看文件信息:

信息不对,按“Scan /t”再次扫描:

这个信息也不对,也脱不了壳,只有手动脱壳了,载入OD中,如下:

第一条指令就是一个 PUSHAD,第二条指令是一个CALL (E8 00000000),用来取得第三条指令的地址。于是往下拖,寻找 POPAD,一路向下,来到这里(0x00409197处):

一条 POPAD 指令,加上一个 JMP 00401000,这个 00401000 就是 OEP 了,于是在 POPAD 下一个断点,如下图:

然后,直接一个 F9 ,看看能不能脱壳,但是,没有达成愿望,代码来到下面位置(0x004016F3),停了下来,出现异常:

上图对应代码如下:
004016E0    55                     push    ebp
004016E1    8BEC                     mov   ebp, esp
004016E3    8B45 08                  mov   eax, dword ptr
004016E6    64:8B15 04000000         mov   edx, dword ptr fs:         ; StackBase
004016ED    8B4A F8                  mov   ecx, dword ptr
004016F0    83C1 30                  add   ecx, 30
004016F3    890D 000013A8            mov   dword ptr , ecx
004016F9    C705 0000138C 00001478   mov   dword ptr , 78140000
00401703    C705 00001390 00001468   mov   dword ptr , 68140000
0040170D    C700 49737282            mov   dword ptr , 82727349
00401713    C740 04 00001380         mov   dword ptr , 80130000
0040171A    5D                     pop   ebp
0040171B    C3                     retn
这里的指令地址在OEP(00401000)的节范围内,属于CrackMe 的代码,并且,变量地址还没有修正,指向了一个非法地址,被 OD 断下来了。
到这里后,程序已经无法再运行了。只有重新来了,但指令地址是正确的了,请记住,后面要用到。
重新加载到OD,我们一路F8+F4组合使用,来到这里:

具体代码如下:
00409135   . |50               push    eax
00409136   . |83C7 08          add   edi, 8
00409139   . |FF96 64700000    call    dword ptr                   ;KERNEL32.LoadLibraryA
这里就是我在最前面説的,壳代码通过LoadLibraryA()动态加载库文件"CW3220.DLL"的地方,而且这个 CW3220.DLL 也不会出现在 CrackMe 的导入表中,脱壳后需要修正导入表才可以正常运行。
壳代码加载完 CW3220.DLL 后,再通过 GetProcAddress() 取得导入函数入口并填充到代码中的地址变量中,如下:

可以看到,这里并没有对两个函数的调用结果进行检查,而是直接保存填充,因此,如果没有 CW3220.DLL 时,程序也不会报错,就算脱壳后也不能运行,就是这个没有填充或填充不对的原因。如果填充不对,则API入口代码如下:
00401776    FF25 B8304000   jmp   dword ptr
0040177C    FF25 BC304000   jmp   dword ptr
00401782    FF25 C0304000   jmp   dword ptr
00401788    FF25 C4304000   jmp   dword ptr
0040178E    FF25 C8304000   jmp   dword ptr
00401794    FF25 CC304000   jmp   dword ptr
0040179A    FF25 D0304000   jmp   dword ptr
004017A0    FF25 D4304000   jmp   dword ptr
004017A6    FF25 D8304000   jmp   dword ptr
004017AC    FF25 DC304000   jmp   dword ptr
004017B2    FF25 E0304000   jmp   dword ptr
004017B8    FF25 E4304000   jmp   dword ptr
004017BE- FF25 EC304000   jmp   dword ptr    ; KERNEL32.lstrlenA
004017C4- FF25 F0304000   jmp   dword ptr    ; KERNEL32.lstrcmpA
004017CA- FF25 F4304000   jmp   dword ptr    ; KERNEL32.GetModuleHandleA
004017D0- FF25 F8304000   jmp   dword ptr    ; KERNEL32.lstrcatA
004017D6- FF25 FC304000   jmp   dword ptr    ; KERNEL32.CreateFileA
004017DC- FF25 00314000   jmp   dword ptr    ; KERNEL32.GetProcAddress
004017E2- FF25 04314000   jmp   dword ptr    ; KERNEL32.CloseHandle
004017E8- FF25 0C314000   jmp   dword ptr    ; USER32.MessageBoxA
004017EE- FF25 10314000   jmp   dword ptr    ; USER32.GetDlgItemTextA
004017F4- FF25 14314000   jmp   dword ptr    ; USER32.EndDialog
004017FA- FF25 18314000   jmp   dword ptr    ; USER32.DialogBoxParamA
可以看到,前面有10多个函数地址是没有的。正确的结果如下:
00401776- FF25 B8304000   jmp   dword ptr    ; cw3220.__startup
0040177C- FF25 BC304000   jmp   dword ptr    ; cw3220.__unlockDebuggerData
00401782- FF25 C0304000   jmp   dword ptr    ; cw3220._close
00401788- FF25 C4304000   jmp   dword ptr    ; cw3220._abort
0040178E- FF25 C8304000   jmp   dword ptr    ; cw3220.___debuggerDisableTerminateCallback
00401794- FF25 CC304000   jmp   dword ptr    ; cw3220.__ExceptionHandler
0040179A- FF25 D0304000   jmp   dword ptr    ; cw3220._itoa
004017A0- FF25 D4304000   jmp   dword ptr    ; cw3220._flushall
004017A6- FF25 D8304000   jmp   dword ptr    ; cw3220._open
004017AC- FF25 DC304000   jmp   dword ptr    ; cw3220._read
004017B2- FF25 E0304000   jmp   dword ptr    ; cw3220._CatchCleanup
004017B8- FF25 E4304000   jmp   dword ptr    ; cw3220.__lockDebuggerData
004017BE- FF25 EC304000   jmp   dword ptr    ; KERNEL32.lstrlenA
004017C4- FF25 F0304000   jmp   dword ptr    ; KERNEL32.lstrcmpA
004017CA- FF25 F4304000   jmp   dword ptr    ; KERNEL32.GetModuleHandleA
004017D0- FF25 F8304000   jmp   dword ptr    ; KERNEL32.lstrcatA
004017D6- FF25 FC304000   jmp   dword ptr    ; KERNEL32.CreateFileA
004017DC- FF25 00314000   jmp   dword ptr    ; KERNEL32.GetProcAddress
004017E2- FF25 04314000   jmp   dword ptr    ; KERNEL32.CloseHandle
004017E8- FF25 0C314000   jmp   dword ptr    ; USER32.MessageBoxA
004017EE- FF25 10314000   jmp   dword ptr    ; USER32.GetDlgItemTextA
004017F4- FF25 14314000   jmp   dword ptr    ; USER32.EndDialog
004017FA- FF25 18314000   jmp   dword ptr    ; USER32.DialogBoxParamA
我们先不要执行这个 LoadLibraryA()调用,先在该指令下面设置一个断点,便于后面快速返回。
还要再去我们前面出错的代码处设置另外一个断点,用到代码中右键“转到”功能,如下图:

输入我们前面记住的过程地址,来到这里:

我们在 0x004016E1 处下一个断点,阻止代码对几个寄存器的值的修改。
然后,按F9 执行,程序断在我们的断点上,然后我们修改 EIP 的值,跳过几个指令的执行,如下图:

重新设置EIP 为 0x0040171A 。如果出现下面提示,按“是”:

下图就是EIP变动后的值,跳过了中间9条指令:

然后,直接 F9 运行,返回到壳代码,回到了我们前面设置的断点处:


这个时候,EAX中才是正确的 DLL 的 hInstance 值了。
后面的 GetProcAddress() 也可以取到正确的API地址入口了。
这里正常后,脱壳也就没有问题了。
直接按 F9 来到我们前面在 POPAD 处设置的断点处,然后几个 F8 来到 OEP(00401000) 处,就可以开始脱壳了。如下图:

选择“Dump debugged process"即可:

全部默认设置,直接点”Dump"并保存好 Dump 后的文件,这时候还有一个要处理的问题,导入表的问题,dump出来的 crackme 的导入表是没有 CW3220.DLL 的。需要我们手动修正。


以管理员身份启动“ ImportREC v1.6F",如下图:


在"Attach to an Active Process"列表中选择 crackme.exe 进程。

OEP 显示的是 00009000,这个是不对的,我们改成正确的 00001000,如下图:

改好后,点击按钮”IAT AutoSearch",出现下面的提示:

提示我们试一下“Get Import”,我们按“确定”关闭提示,然后点击最下面那个“Get Imports" 按钮,取得当前进程的导入数据:

中间的文本框显示有3个导入库,这时,包括了我们需要的 cw3220.dll 在内。
这个时候,可以点一下右边的”Show Invalid",看看有没有无效的导入,如果有,我们还要删除这些无效导入数据。这里我们没有发现无效数据。
然后直接点击最下面的按钮“Fix Dump",选择我们前面在 OD 中Dump 出来的那个文件,如下图:

选择好文件后,按“打开”即可。回到ImportREC的主界面,显示修正成功:

并且,生成一个新文件,就是在原文件名后加了一个下划线“_",这个就是修正导入表后的文件,也就是可以正常运行的脱壳文件了。至此,脱壳才算完成。

下一步就是对 crackme 的自有代码进行处理了。
这个 CrackMe 带有一个 NAG,运行显示如下:

就是一个消息框,通过 IDA 就可以看到,在 WinMain() 最前面就是显示这个消息框的代码。如下:
00401378    68 00100000          push    1000
0040137D    8D83 29020000      lea   eax, dword ptr
00401383    50                   push    eax
00401384    8D93 EC010000      lea   edx, dword ptr
0040138A    52                   push    edx
0040138B    6A 00                push    0
0040138D    E8 56040000          call    004017E8                                          ; MessageBox(), 显示未注册NAG
将上面的代码全部"NOP"后,就可以去除 NAG 的显示。

这个Crackme还有对注册文件(keyfile)的检查,注册文件验证通过后,才会显示主界面,否则会退出 CrackMe。如果没有注册文件或文件内容不正确,则提示:


在 WinMain() 最后一段代码,就有对注册文件校验的调用:
004013DC    E8 1BFDFFFF          call    004010FC                                          ; 注册文件校验
004013E1    85C0               test    eax, eax                                          ; eax 不等于 0, 则校验成功!
004013E3    74 15                je      short 004013FA                                    ; eax == 0 就转去显示文件校验失败,并退出CrackMe
004013E5    6A 00                push    0
004013E7    68 01134000          push    00401301                                          ; DialogProc,回调过程
004013EC    6A 00                push    0
004013EE    6A 01                push    1
004013F0    FF75 08            push    dword ptr
004013F3    E8 02040000          call    004017FA                                          ; jmp 到 USER32.DialogBoxParamA
004013F8    EB 1A                jmp   short 00401414
004013FA    68 00100000          push    1000                                              ; 这里开始提示注册文件不成确
004013FF    8D83 04030000      lea   eax, dword ptr
00401405    50                   push    eax
00401406    8D93 AF020000      lea   edx, dword ptr
0040140C    52                   push    edx
0040140D    6A 00                push    0
0040140F    E8 D4030000          call    004017E8                                          ; MessageBoxA(),显示注册文件不正确
00401414    33C0               xor   eax, eax
00401416    5B                   pop   ebx
00401417    5D                   pop   ebp
00401418    C2 1000            retn    10
其中的 ” call    004010FC   " 就是调用keyfile 校验的子过程,keyfile 的文件名为:Register.dat。
keyfile 的文件内容为:
Why didn't n0p3x use a more difficult keyfile method?
keyfile 校验的子过程就是将文件读入局部量后与一条明文常量字符串进行比较。而且,其并没有初始化局部变量的内存空间,所以读入后有可能字符串不是“null”结尾,会导致比较不成功!(跟踪过程中碰到过一次,大多情况下好象没有问题)
如果 keyfile 校验通过,则会显示主界面:

看看 About:

输入一组假码,测试一下:

点击OK,得到如下错误提示:



因为是基于对话框的程序,通过 WinMain()中以下代码,可以找到DlgProc:
004013E5    6A 00                push    0
004013E7    68 01134000          push    00401301                                          ; DialogProc,回调过程
004013EC    6A 00                push    0
004013EE    6A 01                push    1
004013F0    FF75 08            push    dword ptr
004013F3    E8 02040000          call    004017FA                                          ; jmp 到 USER32.DialogBoxParamA
004013F8    EB 1A                jmp   short 00401414
其 DlgProc 地址为 0x00401301,通过DlgProc中的事件处理过程,很快就可以定位到注册码校验过程,主要代码如下:
00401194    6A 14                push    14
00401196    8D45 EC            lea   eax, dword ptr
00401199    50                   push    eax
0040119A    6A 67                push    67                                                ; 用户名
0040119C    53                   push    ebx
0040119D    E8 4C060000          call    004017EE                                          ; jmp 到 USER32.GetDlgItemTextA
004011A2    6A 14                push    14
004011A4    8D55 D8            lea   edx, dword ptr
004011A7    52                   push    edx
004011A8    6A 66                push    66                                                ; 公司名
004011AA    53                   push    ebx
004011AB    E8 3E060000          call    004017EE                                          ; jmp 到 USER32.GetDlgItemTextA
004011B0    6A 14                push    14
004011B2    8D4D C4            lea   ecx, dword ptr
004011B5    51                   push    ecx
004011B6    6A 65                push    65                                                ; 注册码
004011B8    53                   push    ebx
004011B9    E8 30060000          call    004017EE                                          ; jmp 到 USER32.GetDlgItemTextA
004011BE    8D46 56            lea   eax, dword ptr                            ; eax ==> null, 空串
004011C1    50                   push    eax
004011C2    8D55 EC            lea   edx, dword ptr                            ; edx ===> 用户名:"solly"
004011C5    52                   push    edx
004011C6    E8 F9050000          call    004017C4                                          ; jmp 到 KERNEL32.lstrcmpA
004011CB    85C0               test    eax, eax                                          ; 检查用户名是否为空
004011CD    75 19                jnz   short 004011E8
004011CF    68 00100000          push    1000
004011D4    8D4E 6F            lea   ecx, dword ptr
004011D7    51                   push    ecx
004011D8    8D46 57            lea   eax, dword ptr
004011DB    50                   push    eax
004011DC    6A 00                push    0
004011DE    E8 05060000          call    004017E8                                          ; jmp 到 USER32.MessageBoxA
004011E3    E9 13010000          jmp   004012FB
004011E8    8D56 78            lea   edx, dword ptr                            ; edx ===> null,空串
004011EB    52                   push    edx
004011EC    8D4D D8            lea   ecx, dword ptr                            ; ecx ===> 公司名:"who am i"
004011EF    51                   push    ecx
004011F0    E8 CF050000          call    004017C4                                          ; jmp 到 KERNEL32.lstrcmpA
004011F5    85C0               test    eax, eax                                          ; 检查公司名是否为空
004011F7    75 1C                jnz   short 00401215
004011F9    68 00100000          push    1000
004011FE    8D86 90000000      lea   eax, dword ptr
00401204    50                   push    eax
00401205    8D56 79            lea   edx, dword ptr
00401208    52                   push    edx
00401209    6A 00                push    0
0040120B    E8 D8050000          call    004017E8                                          ; jmp 到 USER32.MessageBoxA
00401210    E9 E6000000          jmp   004012FB
00401215    8D8E 99000000      lea   ecx, dword ptr                            ; ecx ===> null, 空串
0040121B    51                   push    ecx
0040121C    8D45 C4            lea   eax, dword ptr                            ; eax ===> 注册码:"787878787878"
0040121F    50                   push    eax
00401220    E8 9F050000          call    004017C4                                          ; jmp 到 KERNEL32.lstrcmpA
00401225    85C0               test    eax, eax                                          ; 检查注册码是否为空
00401227    75 1F                jnz   short 00401248
00401229    68 00100000          push    1000
0040122E    8D96 B0000000      lea   edx, dword ptr
00401234    52                   push    edx
00401235    8D8E 9A000000      lea   ecx, dword ptr
0040123B    51                   push    ecx
0040123C    6A 00                push    0
0040123E    E8 A5050000          call    004017E8                                          ; jmp 到 USER32.MessageBoxA
00401243    E9 B3000000          jmp   004012FB
00401248    8D45 C4            lea   eax, dword ptr                            ; eax ===> 注册码:"787878787878"
0040124B    50                   push    eax
0040124C    E8 6D050000          call    004017BE                                          ; __lstrlen(),计算注册码的长度
00401251    83F8 09            cmp   eax, 9
00401254    7D 1F                jge   short 00401275                                    ; 注册码是否大于或等9个字符
00401256    68 00100000          push    1000
0040125B    8D96 F9000000      lea   edx, dword ptr
00401261    52                   push    edx
00401262    8D8E B9000000      lea   ecx, dword ptr
00401268    51                   push    ecx
00401269    6A 00                push    0
0040126B    E8 78050000          call    004017E8                                          ; jmp 到 USER32.MessageBoxA
00401270    E9 86000000          jmp   004012FB
00401275    53                   push    ebx                                             ; hDlg
00401276    E8 D9FEFFFF          call    00401154                                          ; 取注册码长度
0040127B    59                   pop   ecx                                             ; hDlg
0040127C    8D45 EC            lea   eax, dword ptr                            ; eax ===> 用户名
0040127F    50                   push    eax
00401280    E8 39050000          call    004017BE                                          ; __lstrlen(),计算用户名的长度
00401285    8BD8               mov   ebx, eax                                          ; ebx == 5, 用户名的长度
00401287    8D45 D8            lea   eax, dword ptr                            ; eax ===> 公司名
0040128A    50                   push    eax
0040128B    E8 2E050000          call    004017BE                                          ; __lstrlen(),计算公司名的长度
00401290    0FAFD8               imul    ebx, eax                                          ; ebx = 用户名长度 * 公司名长度 = 0x28 = 40
00401293    81C3 A93E0F00      add   ebx, 0F3EA9                                       ; ebx = ebx(40) + 0x000F3EA9(999081)
00401299    8BC3               mov   eax, ebx                                          ; eax == ebx = 999121
0040129B    6A 0A                push    0A                                                ; 转换成10进制字符串
0040129D    8D55 90            lea   edx, dword ptr                            ; 转换结果缓冲区, 结果为"999121"
004012A0    52                   push    edx
004012A1    50                   push    eax
004012A2    E8 F3040000          call    0040179A                                          ; _itoa(eax)
004012A7    83C4 0C            add   esp, 0C
004012AA    8D8E 02010000      lea   ecx, dword ptr                         ; ecx ===> ":-)"
004012B0    51                   push    ecx
004012B1    8D45 90            lea   eax, dword ptr                            ; eax ===> "999121"
004012B4    50                   push    eax
004012B5    E8 16050000          call    004017D0                                          ; __Lstrcat(),在上面转换结果"999121"后加上":-)"
004012BA    50                   push    eax                                             ; eax ==> "999121:-)"
004012BB    8D55 C4            lea   edx, dword ptr                            ; edx ===> "787878787878",输入的假码
004012BE    52                   push    edx
004012BF    E8 00050000          call    004017C4                                          ; __lstrcmp(str1, str2), 注册码比较
004012C4    85C0               test    eax, eax
004012C6    75 19                jnz   short 004012E1
004012C8    6A 40                push    40
004012CA    8D8E 2C010000      lea   ecx, dword ptr
004012D0    51                   push    ecx
004012D1    8D86 06010000      lea   eax, dword ptr
004012D7    50                   push    eax
004012D8    6A 00                push    0
004012DA    E8 09050000          call    004017E8                                          ; jmp 到 USER32.MessageBoxA
004012DF    EB 1A                jmp   short 004012FB
004012E1    68 00100000          push    1000
004012E6    8D96 8D010000      lea   edx, dword ptr
004012EC    52                   push    edx
004012ED    8D8E 36010000      lea   ecx, dword ptr
004012F3    51                   push    ecx
004012F4    6A 00                push    0
004012F6    E8 ED040000          call    004017E8                                          ; jmp 到 USER32.MessageBoxA
004012FB    5E                   pop   esi
004012FC    5B                   pop   ebx
004012FD    8BE5               mov   esp, ebp
004012FF    5D                   pop   ebp
00401300    C3                   retn
从上面代码分析可以看出注册码规则如下:
1、注册码的长度必须大于或等于 9。
2、计算用户名的长度,记为 int a = strlen(username);
3、计算公司名的长度,记为 int b = strlen(companyname);
4、计算 a 和 b 的 积, 记为 int c = a * b;
5、将 c 加上 999081,记为 int d = c + 999081;
6、将上面的 d 转换成10进制字符串,然后在后面加上一个笑脸“:-)”,就是最后的注册码。
根据我前面输入的注册信息,注册码计算如下:
d = 5*8 + 999081 = 999121
注册码则是 "999121:-)"
因此,口算就可以了,也就不需要写注册机了。
在界面输入上面计算的注册码,得到提示如下:

注册成功!!!!!!

脱壳后的文件信息如下,”scan /t“后:


分析完毕!!!

solly 发表于 2019-6-11 08:55

888066yjg 发表于 2019-6-11 01:51
加我2176847672 帮我破解一个 价格可以谈

你好,这个只是一点爱好,而且年纪大了,近来只是发点水贴,刷刷存在感。不接破解需求。

pk8900 发表于 2019-6-2 14:59

帖子写的真详细,可以跟着操作,是我们学习的最佳教程。

UID:888888 发表于 2019-6-3 08:56

young24 发表于 2019-6-3 10:02

感谢分享

Light紫星 发表于 2019-6-3 11:57

楼主加油

yptk 发表于 2019-6-3 14:47

Ars 发表于 2019-6-3 22:41

感谢楼主分享,支持一下!

focusos 发表于 2019-6-3 22:45

感谢了,学习案例。

血色天空 发表于 2019-6-4 08:50

学习喽,谢谢楼主分享

Raohz520 发表于 2019-6-4 19:01

感谢楼主写的这么清晰!!!
页: [1] 2
查看完整版本: 160 个 CrackMe 之 087 - n0p3x.9 手动脱壳及修复、注册分析和去NAG。