[PYG]算法分析入门第二课的算法分析在【
http://bbs.chinapyg.com/viewthread.php?tid=843&extra=page%3D1】已经由总坛主说明了。
写该文目的并不是再谈里面的算法,而是看了该文后的读后感以及该Crackme的程序是如何执行的。
【开始】:
在上面的文章中,分析是从
00401132 . E8 41020000 call <jmp.&USER32.SendDlgItemMessage>; 取得用户名位数
这条指令开始分析的。那么在上面应该还有一部分指令,那就是它的参数。
用C32Asm查看下,补上缺少指令,看下是如何执行的。
::00401127:: 6A 00 PUSH 0 \:BYJMP JmpBy:004011FF, 这边的跳转应该是按钮按下后跳过来的
::00401129:: 6A 00 PUSH 0
::0040112B:: 6A 0E PUSH E
::0040112D:: 6A 03 PUSH 3
::0040112F:: FF75 08 PUSH DWORD PTR [EBP+8]
::00401132:: E8 41020000 CALL 00401378 \:JMPDOWN >>>: USER32.DLL:SendDlgItemMessageA
也就是在下面的SendDlgItemMessage执行之前,进行了5个参数的入参。他们分别是
push 0
push 0
push E
push 3
push DWORD PTR [EBP+8]
问题1:这几个参数有什么用呢?
问题2:为什么执行【call <jmp.&USER32.SendDlgItemMessage>; 】就是取得用户名位数呢?而不是取得用户名呢?
这个时候我们就需要查看SendDlgItemMessage的参数情况。
00401132 . E8 41020000 call <jmp.&USER32.SendDlgItemMessage>; 取得用户名位数
00401137 . A3 AF214000 mov dword ptr ds:[4021AF],eax
0040113C . 83F8 00 cmp eax,0 是否为0
0040113F . 0F84 D5000000 je chap203.0040121A 是则over
00401145 . 83F8 08 cmp eax,8 是否大于8位
00401148 . 0F8F CC000000 jg chap203.0040121A 大于则出错!
0040114E . 8BF0 mov esi,eax 位数送到esi
查看SendDlgItemMessage的参数情况:
问题1回答:
LONG SendDlgItemMessage(
HWND hDlg, // 窗口句柄
int nIDDlgItem, // 控件ID
UINT Msg, // 消息类型
WPARAM wParam, // 参数wParam
LPARAM lParam // 参数lParam
);
这时我们就可以知道,调用该函数前需要进行这5个函数的入参过程。那么第一个push 0是传入窗口句柄吗?
回答:NO。第一个传入的参数应该是【参数lParam】,为什么?
回答:push指令是对堆栈操作。堆栈是【先进后出】方式的,也压栈的,后出来。所以窗口句柄是最后一个传入的。
好,回到第2个问题:
问题2:为什么执行【call <jmp.&USER32.SendDlgItemMessage>; 】就是取得用户名位数呢?而不是取得用户名呢?
回答:
这时,我们就要查看SendDlgItemMessage传入的参数情况:
push 0 // 参数lParam
push 0 // 参数wParam
push E // 消息类型
push 3 // 控件ID =====>我们现在知道存放用户名的控件ID是3
push DWORD PTR [EBP+8] // 窗口句柄
关键点是知道消息类E是代表是意思:
可以查看到:
#define WM_GETTEXTLENGTH 0x000E
这是Windows已经定义好的。好,这个时候,我们已经知道此时调用该函数时,就是获取用户名的字符长度了。并且,还可以查到,使用WM_GETTEXTLENGTH该消息类型时,参数wParam,lParam都需要为0.
这时调用该函数时,字符长度是存放在EAX。现在下面的用户长度比较都应该好理解了。并且下面的【取得假码位数】的一些操作也应该好理解了。
=====================================================================================================================
用户名长度,注册码长度的获取的一系列操作,我想应该可以明白了。下面开始讲如何获取注册码和用户名的。
在总坛主的文章中是这样分析的,如下
00401192 . E8 E1010000 call <jmp.&USER32.SendDlgItemMessage>
00401197 . B9 FFFFFFFF mov ecx,-1 初始化ecx
0040119C > 41 inc ecx
0040119D . 0FBE81 60214000 movsx eax,byte ptr ds:[ecx+402160] 逐位取用户名ascii值
004011A4 . 83F8 00 cmp eax,0 是否为0
004011A7 . 74 32 je short chap203.004011DB
问题3:为什么【movsx eax,byte ptr ds:[ecx+402160]】该指令是逐位获取用户名ascii值?而不是逐位注册码ascii值?
这些虽然在使用OD动态调试的时候可以一眼看出来是什么,但是,我们还是从程序代码的角度去分析,这样可能对自己阅读代码的能力可以提高一些把。
文章中如下:
004011E1 . 68 94214000 push chap203.00402194 真码入栈
004011E6 . 68 79214000 push chap203.00402179 假码入栈
问题4:这里为什么【push chap203.00402194】是真码入栈,为什么【push chap203.00402179】是假码入栈?
【分析开始】
看总坛主文章的代码:
00401171 . 68 60214000 push chap203.00402160
00401176 . 6A 08 push 8
00401178 . 6A 0D push 0D
0040117A . 6A 03 push 3
0040117C . FF75 08 push dword ptr ss:[ebp+8]
0040117F . E8 F4010000 call <jmp.&USER32.SendDlgItemMessage>
00401184 . 68 79214000 push chap203.00402179
00401189 . 6A 10 push 10
0040118B . 6A 0D push 0D
0040118D . 6A 04 push 4
0040118F . FF75 08 push dword ptr ss:[ebp+8]
00401192 . E8 E1010000 call <jmp.&USER32.SendDlgItemMessage>
00401197 . B9 FFFFFFFF mov ecx,-1 初始化ecx
0040119C > 41 inc ecx
0040119D . 0FBE81 60214000 movsx eax,byte ptr ds:[ecx+402160] 逐位取用户名ascii值
004011A4 . 83F8 00 cmp eax,0 是否为0
004011A7 . 74 32 je short chap203.004011DB
可以看到代码中执行了2次SendDlgItemMessage,这2次操作是一样的吗?肯定不一样。
先看第一次发送该消息是执行什么?
00401171 . 68 60214000 push chap203.00402160
00401176 . 6A 08 push 8
00401178 . 6A 0D push 0D
0040117A . 6A 03 push 3
0040117C . FF75 08 push dword ptr ss:[ebp+8]
0040117F . E8 F4010000 call <jmp.&USER32.SendDlgItemMessage>
这次调用SendDlgItemMessage前,入参是不一样了哦。根据给的参数情况,看下每个参数的意义。
push 00402160 // 参数lParam
push 8 // 参数wParam
push 0D // 消息类型
push 3 // 控件ID ==========>上面已经提过一次了,控件ID=3就是存放用户名控件ID,那么此次操作肯定是用户名的
push dword ptr ss:[ebp+8] // 窗口句柄
这次消息ID是0x0D了。这个又是什么消息呢?
查看资料
#define WM_GETTEXT 0x000D
这也是Windows已经定义好的,从字面意思我们基本可以猜到是获取字符,我们还是从函数参数的形式给分析下,怎末获取字符的。
当使用WM_GETTEXT消息类型的时候,2个附加参数的含义是不一样的,从push进去的值我们也可以看到是不一样的。
WM_GETTEXT
wParam = (WPARAM) cchTextMax;
lParam = (LPARAM) lpszText;
当使用WM_GETTEXT时,lParam存放的是获取下来的字符数据,wParam存放的是要获取字符的最大长度。
这时我们就清楚了,这时调用SendDlgItemMessage函数,是获取用户名字符,并且长度最大为8,当然前面已经被验证过用户名长度不能大于8了,如果没有验证的话,这边即使输入10个字符,获取下来的也只有8个字符。
并且还可以知道获取的字符存放在****【00402160】****这个地址。
下面再次调用SendDlgItemMessage就迎刃而解了,就是获取输入的注册码字符,并且注册码字符存放在****【00402179】****这个地址。并且存放注册码的控件ID=4
问题3到这基本已经解决,movsx eax,byte ptr ds:[ecx+402160],这边402160就是存放用户名的起始地址了,在上面还有一条指令inc ecx,这样就可以逐位取用户名ascii值了。
问题4:
004011E1 . 68 94214000 push chap203.00402194 真码入栈
004011E6 . 68 79214000 push chap203.00402179 假码入栈
回答:首先,【push chap203.00402179 假码入栈】这个在问题3中已经提到,获取输入的注册码就是存放在【00402179】这个地址,所以很容易理解。
真码可以从
004011D3 . 8981 94214000 mov dword ptr ds:[ecx+402194],eax 保存到内存中
这条指令可以看出来,数据是保存在【402194】这个起始地址的。
好,我在看了总坛主的【算法分析入门第二课】之后,就这么点体会和获取到的知识,提供给像我这样的菜鸟共享,可能有很多错误和不足之处,还请多多包涵。
PS:我想,我们还可以通过该函数将真正的注册码直接显示的存放注册码的控件上显示,只是调用的消息类型不同而已。下次有机会再写写把,把注册码通过其他一些途径显示出来或者......
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课