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

本帖最后由 solly 于 2019-6-2 13:53 编辑

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),停了下来,出现异常:



上图对应代码如下:
[Asm] 纯文本查看 复制代码

004016E0    55                       push    ebp
004016E1    8BEC                     mov     ebp, esp
004016E3    8B45 08                  mov     eax, dword ptr [ebp+8]
004016E6    64:8B15 04000000         mov     edx, dword ptr fs:[4]           ; StackBase
004016ED    8B4A F8                  mov     ecx, dword ptr [edx-8]
004016F0    83C1 30                  add     ecx, 30
004016F3    890D 000013A8            mov     dword ptr [A8130000], ecx
004016F9    C705 0000138C 00001478   mov     dword ptr [8C130000], 78140000
00401703    C705 00001390 00001468   mov     dword ptr [90130000], 68140000
0040170D    C700 49737282            mov     dword ptr [eax], 82727349
00401713    C740 04 00001380         mov     dword ptr [eax+4], 80130000
0040171A    5D                       pop     ebp
0040171B    C3                       retn

这里的指令地址在OEP(00401000)的节范围内,属于  CrackMe 的代码,并且,变量地址还没有修正,指向了一个非法地址,被 OD 断下来了。
到这里后,程序已经无法再运行了。只有重新来了,但指令地址是正确的了,请记住,后面要用到。
重新加载到OD,我们一路F8+F4组合使用,来到这里:



具体代码如下:
[Asm] 纯文本查看 复制代码

00409135   . |50               push    eax
00409136   . |83C7 08          add     edi, 8
00409139   . |FF96 64700000    call    dword ptr [esi+7064]                  ;  KERNEL32.LoadLibraryA

这里就是我在最前面説的,壳代码通过LoadLibraryA()动态加载库文件"CW3220.DLL"的地方,而且这个 CW3220.DLL 也不会出现在 CrackMe 的导入表中,脱壳后需要修正导入表才可以正常运行。
壳代码加载完 CW3220.DLL 后,再通过 GetProcAddress() 取得导入函数入口并填充到代码中的地址变量中,如下:



可以看到,这里并没有对两个函数的调用结果进行检查,而是直接保存填充,因此,如果没有 CW3220.DLL 时,程序也不会报错,就算脱壳后也不能运行,就是这个没有填充或填充不对的原因。如果填充不对,则API入口代码如下:
[Asm] 纯文本查看 复制代码

00401776    FF25 B8304000   jmp     dword ptr [4030B8]
0040177C    FF25 BC304000   jmp     dword ptr [4030BC]
00401782    FF25 C0304000   jmp     dword ptr [4030C0]
00401788    FF25 C4304000   jmp     dword ptr [4030C4]
0040178E    FF25 C8304000   jmp     dword ptr [4030C8]
00401794    FF25 CC304000   jmp     dword ptr [4030CC]
0040179A    FF25 D0304000   jmp     dword ptr [4030D0]
004017A0    FF25 D4304000   jmp     dword ptr [4030D4]
004017A6    FF25 D8304000   jmp     dword ptr [4030D8]
004017AC    FF25 DC304000   jmp     dword ptr [4030DC]
004017B2    FF25 E0304000   jmp     dword ptr [4030E0]
004017B8    FF25 E4304000   jmp     dword ptr [4030E4]
004017BE  - FF25 EC304000   jmp     dword ptr [4030EC]   ; KERNEL32.lstrlenA
004017C4  - FF25 F0304000   jmp     dword ptr [4030F0]   ; KERNEL32.lstrcmpA
004017CA  - FF25 F4304000   jmp     dword ptr [4030F4]   ; KERNEL32.GetModuleHandleA
004017D0  - FF25 F8304000   jmp     dword ptr [4030F8]   ; KERNEL32.lstrcatA
004017D6  - FF25 FC304000   jmp     dword ptr [4030FC]   ; KERNEL32.CreateFileA
004017DC  - FF25 00314000   jmp     dword ptr [403100]   ; KERNEL32.GetProcAddress
004017E2  - FF25 04314000   jmp     dword ptr [403104]   ; KERNEL32.CloseHandle
004017E8  - FF25 0C314000   jmp     dword ptr [40310C]   ; USER32.MessageBoxA
004017EE  - FF25 10314000   jmp     dword ptr [403110]   ; USER32.GetDlgItemTextA
004017F4  - FF25 14314000   jmp     dword ptr [403114]   ; USER32.EndDialog
004017FA  - FF25 18314000   jmp     dword ptr [403118]   ; USER32.DialogBoxParamA

可以看到,前面有10多个函数地址是没有的。正确的结果如下:
[Asm] 纯文本查看 复制代码

00401776  - FF25 B8304000   jmp     dword ptr [4030B8]   ; cw3220.__startup
0040177C  - FF25 BC304000   jmp     dword ptr [4030BC]   ; cw3220.__unlockDebuggerData
00401782  - FF25 C0304000   jmp     dword ptr [4030C0]   ; cw3220._close
00401788  - FF25 C4304000   jmp     dword ptr [4030C4]   ; cw3220._abort
0040178E  - FF25 C8304000   jmp     dword ptr [4030C8]   ; cw3220.___debuggerDisableTerminateCallback
00401794  - FF25 CC304000   jmp     dword ptr [4030CC]   ; cw3220.__ExceptionHandler
0040179A  - FF25 D0304000   jmp     dword ptr [4030D0]   ; cw3220._itoa
004017A0  - FF25 D4304000   jmp     dword ptr [4030D4]   ; cw3220._flushall
004017A6  - FF25 D8304000   jmp     dword ptr [4030D8]   ; cw3220._open
004017AC  - FF25 DC304000   jmp     dword ptr [4030DC]   ; cw3220._read
004017B2  - FF25 E0304000   jmp     dword ptr [4030E0]   ; cw3220._CatchCleanup
004017B8  - FF25 E4304000   jmp     dword ptr [4030E4]   ; cw3220.__lockDebuggerData
004017BE  - FF25 EC304000   jmp     dword ptr [4030EC]   ; KERNEL32.lstrlenA
004017C4  - FF25 F0304000   jmp     dword ptr [4030F0]   ; KERNEL32.lstrcmpA
004017CA  - FF25 F4304000   jmp     dword ptr [4030F4]   ; KERNEL32.GetModuleHandleA
004017D0  - FF25 F8304000   jmp     dword ptr [4030F8]   ; KERNEL32.lstrcatA
004017D6  - FF25 FC304000   jmp     dword ptr [4030FC]   ; KERNEL32.CreateFileA
004017DC  - FF25 00314000   jmp     dword ptr [403100]   ; KERNEL32.GetProcAddress
004017E2  - FF25 04314000   jmp     dword ptr [403104]   ; KERNEL32.CloseHandle
004017E8  - FF25 0C314000   jmp     dword ptr [40310C]   ; USER32.MessageBoxA
004017EE  - FF25 10314000   jmp     dword ptr [403110]   ; USER32.GetDlgItemTextA
004017F4  - FF25 14314000   jmp     dword ptr [403114]   ; USER32.EndDialog
004017FA  - FF25 18314000   jmp     dword ptr [403118]   ; 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 Import" 按钮,取得当前进程的导入数据:



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



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



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

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



就是一个消息框,通过 IDA 就可以看到,在 WinMain() 最前面就是显示这个消息框的代码。如下:
[Asm] 纯文本查看 复制代码

00401378    68 00100000          push    1000
0040137D    8D83 29020000        lea     eax, dword ptr [ebx+229]
00401383    50                   push    eax
00401384    8D93 EC010000        lea     edx, dword ptr [ebx+1EC]
0040138A    52                   push    edx
0040138B    6A 00                push    0
0040138D    E8 56040000          call    004017E8                                          ; MessageBox(), 显示未注册NAG

将上面的代码全部"NOP"后,就可以去除 NAG 的显示。

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

在 WinMain() 最后一段代码,就有对注册文件校验的调用:
[Asm] 纯文本查看 复制代码

004013DC    E8 1BFDFFFF          call    004010FC                                          ; 注册文件校验
004013E1    85C0                 test    eax, eax
004013E3    74 15                je      short 004013FA
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 [ebp+8]
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 [ebx+304]
00401405    50                   push    eax
00401406    8D93 AF020000        lea     edx, dword ptr [ebx+2AF]
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
校验文件的内容为:
[HTML] 纯文本查看 复制代码

Why didn't n0p3x use a more difficult keyfile method?

如果 keyfile 校验通过,则会显示主界面:



看看 About:



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



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

因为是基于对话框的程序,通过 WinMain()中以下代码,可以找到  DlgProc:
[Asm] 纯文本查看 复制代码

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 [ebp+8]
004013F3 E8 02040000 call 004017FA ; jmp 到 USER32.DialogBoxParamA
004013F8 EB 1A jmp short 00401414

其 DlgProc 地址为 0x00401301,通过DlgProc中的事件处理过程,很快就可以定位到注册码校验过程,主要代码如下:
[Asm] 纯文本查看 复制代码

00401194    6A 14                push    14
00401196    8D45 EC              lea     eax, dword ptr [ebp-14]
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 [ebp-28]
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 [ebp-3C]
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 [esi+56]                           ; eax ==> null, 空串
004011C1    50                   push    eax
004011C2    8D55 EC              lea     edx, dword ptr [ebp-14]                           ; 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 [esi+6F]
004011D7    51                   push    ecx
004011D8    8D46 57              lea     eax, dword ptr [esi+57]
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 [esi+78]                           ; edx ===> null,空串
004011EB    52                   push    edx
004011EC    8D4D D8              lea     ecx, dword ptr [ebp-28]                           ; 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 [esi+90]
00401204    50                   push    eax
00401205    8D56 79              lea     edx, dword ptr [esi+79]
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 [esi+99]                           ; ecx ===> null, 空串
0040121B    51                   push    ecx
0040121C    8D45 C4              lea     eax, dword ptr [ebp-3C]                           ; 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 [esi+B0]
00401234    52                   push    edx
00401235    8D8E 9A000000        lea     ecx, dword ptr [esi+9A]
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 [ebp-3C]                           ; 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 [esi+F9]
00401261    52                   push    edx
00401262    8D8E B9000000        lea     ecx, dword ptr [esi+B9]
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 [ebp-14]                           ; 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 [ebp-28]                           ; 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 [ebp-70]                           ; 转换结果缓冲区, 结果为"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 [esi+102]                          ; ecx ===> ":-)"
004012B0    51                   push    ecx
004012B1    8D45 90              lea     eax, dword ptr [ebp-70]                           ; 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 [ebp-3C]                           ; 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 [esi+12C]
004012D0    51                   push    ecx
004012D1    8D86 06010000        lea     eax, dword ptr [esi+106]
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 [esi+18D]
004012EC    52                   push    edx
004012ED    8D8E 36010000        lea     ecx, dword ptr [esi+136]
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“后:

分析完毕!!!

THE END
喜欢就支持以下吧
点赞0
分享
评论 抢沙发
管埋员的头像-小北的自留地

昵称

取消
昵称