本帖最后由 solly 于 2019-5-6 23:22 编辑
160个CrackMe之89--PhrekTech是一个16位的NE格式的win16执行程序,这个程序只能在Win3.x和Win9x系统下运行,需要装一个Win98SE来跟踪。
我是在VMWare中装了一个Win98SE的虚拟机来跟踪这个程序的,并且是通过Win9x下经典的调试工具 TRW2000 来完成跟踪和追码。
为了在虚拟机中调试该CrackMe,需要进行以下设置或操作,不然无法调出TRW2k的调试界面,但这样设置后就无法使用鼠标了,只能通过键盘操作。
1、修改虚拟机vmx配置文件:
vmmouse.present = "FALSE"
svga.maxFullscreenRefreshTick = "5"
2、将TRW2000的所有文件及子目录放置到C:\盘的根目录下。如下图所示:
由于只能键盘操作,有几个快捷键要用到:
1、ALT+SpaceBar:调用出系统菜单,可以用方向键调整窗口大小,以及移动窗口位置;
2、ALT+F4:关闭窗口;
3、ALT+TAB:切换窗口;
4、TAB键:在窗口内各个控件中移动焦点。
5、CTRL + ALT:从虚拟机中切回到Window10主机;
6、TRW2000加载后,CTRL+N和CTRL+M,调出TRW2000的调试界面。
下面简单介绍在TRW中需要用到了几个调试命令:
1、s 0,FFFFFFFF ’solly‘ 在内存中搜索字符串”solly"
2、bpm address 内存下断点,内存地址格式:segment:offset,如 es:0528
3、bl 查看断点
4、bd 编号 禁用某个编号的断点,如 bd 04 禁用04断点,编号可以通过上面的bl查到
5、be 编号 启用某个编号的断点,如 be 04 启用04断点,编号可以通过前面的bl查到
6、bpx 代码下断点,如 bpx cs:0208 ,在代码段中偏移0208处下断点。
7、g 运行,退出调试界面
8、按F8键是跟入,可以进入函数,F10是单步跟踪(不进入函数)
注意,有时从TRW2000退回Win98后,键盘不响应,按任何键虚拟机都是发出“嘀”声时,可以先按"CTRL+ALT"返回Window10,再点击虚拟机的桌面进去,就可以再次使用键盘了。
16位的NE程序与DOS程序一样,是分段的,所有的代码和数据的访问是通过 segment:offset 的地址格式来访问的,如 cs:0208 是访问代码,ds:0528、ss:sp、es:si、es:di 是访问数据等
该CrackMe程序是Delphi 1.x编写的,通过IDA反汇编后,可以看到其资源和内部函数,我们可以通过IDA找到内部函数,在TRW2000中就不需要进入这些内部函数跟踪了。
启动TRW2000的Loader程序 trw2000.exe:
点击“Browse”找到我们要跟踪的CrackMe程序。
然后再点击“Load"加载程序,TRW加载程序后,会弹出调试界面,停留在 INITTASK 位置。
不管它,输入 g, 加回车,返回桌面:
继续运行程序,如下:
TRW2000的 Message Output 内显示加载成功,并且CrackMe也正常进入界面,如下:
,
按钮上面的标签显示”Not tested“,表示还没有进行过注册码验证。
输入假码,如下图:
然后按CTRL+N调出TRW2000调试界面,如果没有出来,则多按几次。
输入” S 0, ffffffff 'solly' “,搜索刚才输入的用户名,如果搜到,则会在后面显示地址,同时,数据窗口会显示搜索到的数据,如上图所示。
搜到地址,可以下内存断点:bpm 80386d68,这样下完断点后,只要程序读取这个用户名,就会断下来。
如果没有断下来,説明这个数据在内存中有多个,如上图,我们可以重新搜索,如 S 80386d69,ffffffff 'solly' 来重新指定搜索范围,搜索刚才没有搜索过的地址空间,直到搜索到能够断下来的地址,如果搜索结果太多,一直断不下来,则可以下g关闭调试窗口,重新输入一个没有使用过的用户名再来重新搜索。如下图,我就把用户用重新改成了”username9977",再重新搜索。
下完新的断点,再g回到windows,按CrackMe的 Test 按钮。这回断了下来,如上图,TRW2000显示“Break on BP1",代码区显示,我们断在了 KERNEL.DLL中的 HMEMCPY 函数中,这是一个内存拷贝系统函数,这里 es:si指向源字符串,为系统空间,es:di为目的字符串,指向了应用程序的空间,如下图:
在指令区下 d es:di-4,就可以看到,系统正向程序传送用户名数据,如上图,已经传送了一部分,下图则是全部传送完成了,代码也返回了应用空间的状态:
目的地址为: es:5128,这是应用程序保存用户名的缓冲区。
这个时候为了我们可以下命令 bd 01,禁用刚才的内存中断,也可以下 bc * ,禁用所有的断点,并重新下一个新的断点:bpx HMEMCPY, 这样,等一下读取序列号时就可以断下来了。
一路按F10,返回多个 retf 后,我们来到了CrackMe的指令空间,如下图:
这里就是CrackMe进行序列号验证的地方。同时,我们用IDA反编译程序,按地址3907:0237在IDA找到相应代码位置,另外,段地址3907不用管,只要在IDA搜索偏移地址0237就可以,然后跟据TRW2000中显示的汇编代码就可以在IDA中找到相应的汇编代码范围。
这样,我们可以在IDA中看到Delphi的内部函数,如下所示,是上图对应的汇编代码:
[Asm] 纯文本查看 复制代码 cseg01:0237 call @TControl@GetText$qv ; 读取用户名:username9977
cseg01:023C call @Concat$qm6Stringt1 ; 连接字符串:"g3df" + "username9977"
cseg01:0241 lea di, [bp+var_100]
cseg01:0245 push ss
cseg01:0246 push di ; es:di 输出结果缓冲区
cseg01:0247 push 0FFh
cseg01:024A call @$basg$qm6Stringt14Byte ; 生成Delphi字符串:"g3dfusername9977"
cseg01:024F lea di, [bp+var_300] ; es:di ===> 用户名:username9977
cseg01:0253 push ss
cseg01:0254 push di
cseg01:0255 lea di, [bp+var_100] ; es:di ===>连接后的字符串:"g3dfusername9977"
cseg01:0259 push ss
cseg01:025A push di
cseg01:025B call @$basg$qm6Stringt1 ; 生成字符串:"g3dfusername9977"
cseg01:0260 mov di, offset aH9cf8 ; "h9cf8"
cseg01:0263 push cs
可以看到刚才执行了Delphi 控件的 GetText 函数,取得了用户名。
一路往下跟踪执行,来到了 3907:02D9 处,执行完这条指令,就生成了一个序列号,如下图所示:
使用 d es:4F28 就会在数据区显示出来,这个地址是前面push到栈中的[sp] = 0x4F28,在TRW2000中的Stack窗口可以看到。
同时在IDA中可以看到,马上要执行一个字符比较函数: bsub(),这是一个delphi的字符串比较函数,如果相等则返回0,不相等则返回非0,按F8进入函数内部,其进行字符串比较的代码细节如下图所示:
先比较长度,再比较字符串。
执行到 0FA7:2DCD LODSB 时,可以通过 d es:si 和 d es:di 来显示进行比较的两个字符串,如下图:
到这里,真正的序列号已经出来了,其生成规则是:
1、输入的用户名不能为空;
2、在用户名"username9977"前面加上"g3df",形成字符串1:"g3dfusername9977";
3、在前面生成的字符串1的后面加上"h9cf8",形成字符串2:"g3dfusername9977h9cf8";
4、再把前面生成的两个字符串连接起来,形成序列号:"g3dfusername9977g3dfusername9977h9cf8";
这样,只要我们返回CrackMe,输入以上序列号就可以注册通过了。
先禁用断点:
禁用断点后,输入 g 返回应用。输入正确的序列号,再按“Test”:
这时,按钮上面的标签显示“YEAH,Good work!”,表示注册成功!
下面是完整的汇编代码的验证过程,我加了注释:
[Asm] 纯文本查看 复制代码 cseg01:0208 sub_208 proc far ; DATA XREF: cseg01:010E↑o
cseg01:0208
cseg01:0208 var_500 = byte ptr -500h
cseg01:0208 var_400 = byte ptr -400h
cseg01:0208 var_300 = byte ptr -300h
cseg01:0208 var_200 = byte ptr -200h
cseg01:0208 var_100 = byte ptr -100h
cseg01:0208 arg_0 = dword ptr 6
cseg01:0208
cseg01:0208 push bp
cseg01:0209 mov bp, sp
cseg01:020B mov ax, 500h
cseg01:020E call @__StackCheck$q4Word ; __StackCheck(Word)
cseg01:0213 sub sp, 500h
cseg01:0217 lea di, [bp+var_400]
cseg01:021B push ss
cseg01:021C push di
cseg01:021D mov di, offset aG3df ; "g3df"
cseg01:0220 push cs
cseg01:0221 push di
cseg01:0222 call @$basg$qm6Stringt1 ; 生成Delphi字符串:"g3df"
cseg01:0227 lea di, [bp+var_300] ; es:di 保存用户名的缓冲区
cseg01:022B push ss
cseg01:022C push di
cseg01:022D les di, [bp+arg_0]
cseg01:0230 les di, es:[di+18Ch]
cseg01:0235 push es
cseg01:0236 push di ; this
cseg01:0237 call @TControl@GetText$qv ; 读取用户名:username9977
cseg01:023C call @Concat$qm6Stringt1 ; 连接字符串:"g3df" + "username9977"
cseg01:0241 lea di, [bp+var_100]
cseg01:0245 push ss
cseg01:0246 push di ; es:di 输出结果缓冲区
cseg01:0247 push 0FFh
cseg01:024A call @$basg$qm6Stringt14Byte ; 生成Delphi字符串:"g3dfusername9977"
cseg01:024F lea di, [bp+var_300] ; es:di ===> 用户名:username9977
cseg01:0253 push ss
cseg01:0254 push di
cseg01:0255 lea di, [bp+var_100] ; es:di ===>连接后的字符串:"g3dfusername9977"
cseg01:0259 push ss
cseg01:025A push di
cseg01:025B call @$basg$qm6Stringt1 ; 生成字符串:"g3dfusername9977"
cseg01:0260 mov di, offset aH9cf8 ; "h9cf8"
cseg01:0263 push cs
cseg01:0264 push di
cseg01:0265 call @Concat$qm6Stringt1 ; 连接字符串:"g3dfusername9977" + "h9cf8"
cseg01:026A lea di, [bp+var_200] ; es:di ===> 字符串缓冲区
cseg01:026E push ss
cseg01:026F push di
cseg01:0270 push 0FFh
cseg01:0273 call @$basg$qm6Stringt14Byte ; 生成Delphi字符串:"g3dfusername9977h9cf8"
cseg01:0278 lea di, [bp+var_300] ; es:di ==> delphi字符串数据区
cseg01:027C push ss
cseg01:027D push di
cseg01:027E les di, [bp+arg_0]
cseg01:0281 les di, es:[di+18Ch]
cseg01:0286 push es
cseg01:0287 push di ; this
cseg01:0288 call @TControl@GetText$qv ; 再次取用户名
cseg01:028D add sp, 4
cseg01:0290 cmp [bp+var_300], 0 ; 字符串长度不等于0?
cseg01:0295 jnz short loc_2AD
cseg01:0297 mov di, offset aNaahWrong ; "Naah! Wrong!"
cseg01:029A push cs
cseg01:029B push di ; __int32
cseg01:029C les di, [bp+arg_0]
cseg01:029F les di, es:[di+184h]
cseg01:02A4 push es ; int
cseg01:02A5 push di ; TControl *
cseg01:02A6 call @TControl@SetText$q8TCaption ; TControl::SetText(TCaption)
cseg01:02AB jmp short locret_30F
cseg01:02AD ; ---------------------------------------------------------------------------
cseg01:02AD
cseg01:02AD loc_2AD: ; CODE XREF: sub_208+8D↑j
cseg01:02AD lea di, [bp+var_400] ; es:di ==> delphi字符串数据区,准备保存序列号
cseg01:02B1 push ss
cseg01:02B2 push di
cseg01:02B3 les di, [bp+arg_0]
cseg01:02B6 les di, es:[di+17Ch]
cseg01:02BB push es
cseg01:02BC push di ; this
cseg01:02BD call @TControl@GetText$qv ; 读取序列号
cseg01:02C2 lea di, [bp+var_500]
cseg01:02C6 push ss
cseg01:02C7 push di
cseg01:02C8 lea di, [bp+var_100] ; es:di ===> "g3dfusername9977"
cseg01:02CC push ss
cseg01:02CD push di
cseg01:02CE call @$basg$qm6Stringt1 ; 生成字符串:"g3dfusername9977"
cseg01:02D3 lea di, [bp+var_200] ; es:di ===> "g3dfusername9977h9cf8"
cseg01:02D7 push ss
cseg01:02D8 push di
cseg01:02D9 call @Concat$qm6Stringt1 ; 连接字符:"g3dfusername9977" + "g3dfusername9977h9cf8"
cseg01:02DE call @$bsub$qm6Stringt1 ; 比较上面连接的字符串是否与输入的序列号相等,不相等返回非0,相等返回0
cseg01:02E3 jnz short loc_2FB
cseg01:02E5 mov di, offset aYeahGoodWork ; "YEAH! Good work!"
cseg01:02E8 push cs
cseg01:02E9 push di ; __int32
cseg01:02EA les di, [bp+arg_0]
cseg01:02ED les di, es:[di+184h]
cseg01:02F2 push es ; int
cseg01:02F3 push di ; TControl *
cseg01:02F4 call @TControl@SetText$q8TCaption ; TControl::SetText(TCaption)
cseg01:02F9 jmp short locret_30F
cseg01:02FB ; ---------------------------------------------------------------------------
cseg01:02FB
cseg01:02FB loc_2FB: ; CODE XREF: sub_208+DB↑j
cseg01:02FB mov di, offset aNaahWrong ; "Naah! Wrong!"
cseg01:02FE push cs
cseg01:02FF push di ; __int32
cseg01:0300 les di, [bp+arg_0]
cseg01:0303 les di, es:[di+184h]
cseg01:0308 push es ; int
cseg01:0309 push di ; TControl *
cseg01:030A call @TControl@SetText$q8TCaption ; TControl::SetText(TCaption)
cseg01:030F
cseg01:030F locret_30F: ; CODE XREF: sub_208+A3↑j
cseg01:030F ; sub_208+F1↑j
cseg01:030F leave
cseg01:0310 retf 8
cseg01:0310 sub_208 endp
分析完华!
|