滴水-day40-ESP寻址-定位回调函数
ESP寻址
函数的调用总会伴随着传递参数,A函数调用B函数给B函数传递了两个INT参数,那么在CALL B函数的时候,B函数内部一定会有堆栈提升的操作给这个函数使用,通常情况下都是使用的PUSH EBP的方式进行堆栈的提升,但还有一种方式比PUSH EBP更方便快捷,就是ESP提升堆栈。
我们都知道CALL 指令其实就相当于一个PUSH MOV 的结合体,当CALL指令执行的时候会往堆栈中压入一个数据,该数据是当前CALL指令的下一条指令的地址,俗称函数返回地址。
例如说A函数调用B函数传递两个参数,且AB函数都是_stdcall调用约定,且CALL下一条指令地址为:0x12345678,那么就会有以下操作
PUSH 2 PUSH 1 CALL B
此时堆栈如下
0x12345680 0x12345678
0x1234567C 0x00000001
0x12345678 0x00000002
0x12345680就是目前的栈顶,当CALL进入函数的时候堆栈就是这个样子,那么在函数内部肯定会有一个提升堆栈的操作,这里使用ESP。
SUB ESP 0x40
那么此时堆栈如下
0x12345640 0x00000000
……………………………………
0x12345680 0x12345678
0x1234567C 0x00000001
0x12345678 0x00000002
此时新的栈顶就是0x12345640,而原来的ESP成了这个函数的栈底。
此时通过ESP+40可以取到函数的返回地址,ESP+44可以取到第一个参数,ESP+48可以取到第二个参数。
当然ESP寻址也是有缺点的,就是在函数内部如果还有PUSH操作的话那么堆栈就会提升,在想通过ESP取值的时候也要跟着提升,例如在函数内部PUSH EAX,此时栈顶就是0x1234563C
那么想取到函数的返回地址就是ESP+0x44.
回调函数
练习
WIN32入口函数识别
1.WIN32入口函数有4个参数,且该函数是_stdcall调用约定,那么在外层肯定会有4个传参的操作。
0078119A > /E9 211A0000 jmp DiShui_A.wWinMainCRTStartupmodeure_nopopacheledptr
00782BC3 E8 68FCFFFF call DiShui_A.__scrt_common_mainrt_uninitialize_criticaldefaultnmentnt
00782838 E8 13000000 call DiShui_A.__scrt_common_main_sehermination_complete_defaultnmentnt
007829A2 E8 79010000 call DiShui_A.invoke_mainain_policy::set_app_typeledptr
00782B39 8B4D FC mov ecx,dword ptr ss:[ebp-0x4]
00782B3C 51 push ecx
00782B3D 8B55 F8 mov edx,dword ptr ss:[ebp-0x8]
00782B40 52 push edx
00782B41 6A 00 push 0x0
00782B43 68 00007700 push DiShui_A.00770000
00782B48 E8 8AE5FFFF call DiShui_A.007810D7
EBP-4获取到局部变量的数据存放到ecx中。
ECX压入栈中,观察寄存器ECX发现其值是0xA,就是WinMain最后一个参数的值。
EBP-8获取到局部变量的数据存放到EDX中。
EDX压入栈中,这个是WinMain函数的第三个参数,是命令行参数的地址。
PUSH 0,是WinMain函数的第二个函数,此参数始终为NULL。
PUSH 0x770000,此程序的ImageBase,如果在数据窗口跟随会发现,就是这个程序在内存中的状态。
结合以上分析,call DiShui_A.007810D7就是要找的WinMain函数了
007810D7 /E9 640E0000 jmp DiShui_A.wWinMainode_Defaultin_usee_modetfail_fptr
窗口回调函数定位
00592161 8B45 08 mov eax,dword ptr ss:[ebp+0x8] ; DiShui_A.00580000
00592164 50 push eax ; DiShui_A.00580000
00592165 E8 17F0FFFF call DiShui_A.00591181
ebp+0x8,取WinMain第一个参数存放到eax中,push eax后call了一个地址,在我的程序中我是把窗口注册放到了一个函数中,所以我要传递一个此程序的句柄。
00591181就是我地自定义的函数
00591181 /E9 BA090000 jmp DiShui_A.MyRegisterClassyfailurere_os_handled_fptr
…………………………………………
00591BF8 8D45 C8 lea eax,dword ptr ss:[ebp-0x38]
00591BFB 50 push eax
00591BFC FF15 FCC05900 call dword ptr ds:[<&USER32.RegisterClassExW>] ; user32.RegisterClassExW
当给WNDCLASSEXW结构体的成员赋值完毕之后,会调用RegisterClassExW函数进行窗口的注册。
此函数需要一个WNDCLASSEXW结构体参数。eax是该结构体的首地址
那么既然知道了WNDCLASSEXW的首地址,就可以直接定位到窗口回调函数了
WNDCLASSEXW结构的第三个参数就是处理窗口消息的函数地址。
直接在堆栈中取到eax+0x8地址保存的数据就是WndProc函数的地址了
0059110E /E9 8D0B0000 jmp DiShui_A.WndProcllPathFromFilePathitialize_criticaldefa>
麻了,在DeBug版本中我找不到WndProc具体的回调函数在哪里
消息具体事件函数定位
但是在Release中可以轻松定位到具体消息的事件函数
00E910A7 |. 50 push eax ; /pWndClassEx = 00BEF94C
00E910A8 |. FF15 6430E900 call near dword ptr ds:[<&USER32.RegisterClassExW>] ; \\RegisterClassEx
堆栈跟踪eax的数据,取到第三个参数就是WndProc函数的地址
00BEF954 00E911A0 DiShui_A.WndProcfastfailstartup_lockializexception_default
跟踪这个地址
00E911B9 >|. 8B75 0C mov esi, dword ptr ss:[ebp+0xC]
在这里下一个断点运行程序会发现程序不断的在暂停,这是应为ebp+0xc取的是WndProc函数的第二个参数,该参数是消息类型,所以会不断的停在这里。
右击断点设置条件:[ebp+c] == WM_LBUTTONDOWN
当ebp+c的值等于WM_LBUTTONDOWN也就是201的时候再暂停,这个是鼠标左键的消息。
ReverseTraining_1.exe
三个字符是AFg
00401120 . 8B4424 30 mov eax, dword ptr ss:[esp+0x30] ; Case 100 (WM_KEYDOWN) of switch 004010F7
00401124 . 83F8 41 cmp eax, 0x41 ; Switch (cases 41..67)
00401127 . 74 49 je short ReverseT.00401172
00401129 . 83F8 46 cmp eax, 0x46
0040112C . 74 33 je short ReverseT.00401161
0040112E . 83F8 67 cmp eax, 0x67
00401131 . 74 1D je short ReverseT.00401150