[原创] 精准型消息断点 | 宜武汇-ag真人国际厅网站

[副标题]:从应用层到mcu,看windows处理键盘输入 [2.a.1.传球手user32.dll]

引言

前文作为系列的开篇,我们站在notepad.exe的视角,看它接过系统传来的消息,交由notepad的窗口处理函数(wndproc)进行处理的过程。user32.dll!dispatchmessage api是前面"系统传来"中的一环,也是最靠近应用层的一环,将窗口消息传递给应用。本文从该api切入,逐渐远离熟悉的应用层。

正文

[原本本文我想结合ollydbg的消息断点演示,奈何消息断点存在各种限制,因此我在dispatchmessage上下条件断点,更精准的达到相同目的]

用过ollydbg的读者一定对ollydbg中的条件断点并不陌生,我先演示使用条件断点的标准流程。


1.1 查找registerclassex并下断

1.2 notepad被断点中断后,分析调用registerclassexw时传入的窗口类:

上图ollydbg的堆栈区窗口中:

a.esp指向调用user32.registerclassexw api的指令的返回地址 ;

b.esp 4存放registerclassexw的参数wndclassex结构的地址,其结构定义如下,其中域变量lpfnwndproc位于结构体中的offset 8 byte处:

typedef struct tagwndclassexw {     uint        cbsize; // offset 0     /* win 3.x */     uint        style; //  offset 4     wndproc     lpfnwndproc; //offset 8 窗口过程地址     int         cbclsextra;     int         cbwndextra;     hinstance   hinstance;     hicon       hicon;     hcursor     hcursor;     hbrush      hbrbackground;     lpcwstr     lpszmenuname;     lpcwstr     lpszclassname;     /* win 4.0 */     hicon       hiconsm; } wndclassexw, *pwndclassexw, near *npwndclassexw, far *lpwndclassexw;

ps:虽然lpfnwndproc标注为窗口过程地址,但是只有少数crackme在lpfnwndproc指向的函数中进行消息处理。因此,只在此处未必是解决crackme的银弹。这才引出使用dispatchmessage追踪窗口函数的必要性。


ollydbg的堆栈区窗口中"esp 4"处右键"follow in dump",即可在数据区查看wndclassex各个域变量的值。图中0x19fb64(wndclassex 0x08)处存放notepad窗口过程(wndproc)地址(0x401b90):

在ollydbg数据区0x19fb64处右键"follow dword in disassembler",即可在指令窗口显示notepad窗口过程的反汇编。(我用loadmapex加载了notepad的map文件,因此comment区域会显示窗口过程的符号名_npwndproc)notepad的窗口过程位于0x0401b90(很明显该过程位于notepad.exe 代码段内),先记录这个地址并在下断点

通过分析registerclassex的参数,我们认为notepad的窗口过程位于0x0401b90,然而,ollydbg工具栏中"w"给出的窗口过程/clsproc都没给出这个地址

1.3 给notepad.exe窗口下条件断点.

对于crackme练习,一般下一步是在edit窗口clsproc上下消息断点,奈何消息断点在单文档窗口程序上似乎失效了(百度搜索"消息断点失效",提问者不少解答者寥寥),于是变通为给dispatchmessagea/w下条件断点。dispatchmessage的唯一参数为msg,结构如下:

typedef struct tagmsg {     hwnd        hwnd;     uint        message;     wparam      wparam;     lparam      lparam;     dword       time;     point       pt; #ifdef _mac     dword       lprivate; #endif } msg, *pmsg, near *npmsg, far *lpmsg;


dispatchmessagea/w下条件断点的步骤如下:

a. 先给dispatchmessage下个普通断点(bp disaptchmessagea/w),断下后结合堆栈来拼凑成条件断点;

简单说明以下,在ollydbg堆栈区中: "esp ==>":指向返回地址; "esp 4":指向参数msg的地址。 在esp 4处右键"follow in dump"将在ollydbg数据区解析msg各个成员: msg 0x00 (0x19fcd0):0x00307e4,为窗口句柄 msg 0x04 (0x19fcd4):0x60,为消息值

据此,得到dispatchmessagew条件断点的表达式: 

1.4.设置内存访问断点

当notepad收到按键抬起消息时,ollydbg会中断在条件断点处。一般为了追踪处理消息的窗口过程中,cracker此时会在ollydbg中对代码段下内存访问断点。对于本文就是对notepad的代码段下内存访问断点。

点击ollydbg工具栏的"m",显示模块窗口:

再次运行ollydbg,程序马上会暂定在notepad的代码段中,暂定处大概率是窗口过程。对于crackme,剩下的是分析注册码算法了,但是此处,我提出2个调试过程中遇到的值得深思的问题:

a.如果notepad.exe的代码量极大,窗口过程恰好位于其他dll中,那么通过下内存访问断点来定位窗口过程的方式是不是失效了?

b.有别于练手的crackme程序,对于多线程程序,就如notepad.exe,设置内存访问断点后,其他线程也会访问代码段(如访问网络读取数据),如何从中挑选出窗口过程?这无异于引入了大量的噪声,增加的分析的难度。(简单如notepad.exe也有15个线程)

如何解决上述2个问题?让我们深入dispatchmessage函数。

2.user32._internalcallwinproc

借助前面registerclassexw给窗口过程下的断点,继续运行ollydbg,ollydbg中断。点击ollydbg工具栏"k",查看函数调用堆栈:

第一第四栈帧有点眼熟,它显示了窗口程序在user32模块中从dispatchmessage api进入窗口过程的全过程。借助ida为user32.dll生成map文件/ollydbg loadmap插件加载新生成的map文件,可以获得相对友好的调用堆栈:

2.1. ida生成user32.map

ida加载user32.dll,点击file–produce file–create map file,生成user32.map:

2.2. ollydbg加载map

ollydbg–plugins–loadmapex–loadmapex加载ida生成的map:

(注:一定要在user32.dll模块的地址空间中加载user32.map,否则会干扰ollydbg的分析功能.一旦干扰ollydbg的分析功能,只能通过移除分析结果来恢复)

 再次打开调用堆栈,得到较为友好的调用堆栈:

虽然图中有部分函数地址没有解析出来,但是通过单步跟踪可以得到如下调用链:

dispatchmessagew |-->dispatchmessageworker     |-->usercallwinproccheckwow         |-->internalcallwinproc

同时借助泄露的win xp源码一探究竟:usercallwinproccheckwow的实现位于nt\windows\core\ntuser\client\clmsg.c

lresult usercallwinproccheckwow(     pactivation_context pactctx,     wndproc pfn,     hwnd hwnd,     uint msg,     wparam wparam,     lparam lparam,     pvoid pww,     bool fenablelitehooks) {     bool finsidehook;     lresult lret = 0;       begin_callwinproc(finsidehook, lret)           bool foverride = finsidehook && fenablelitehooks && ismsgoverride(msg, &guah.uoiwnd.mm);           pfn = mapkernelclientfntoclientfn(pfn);           if (foverride) {             /*              * note: it is important that the same lret is passed to all three              * calls, allowing the before and after owp's to examine the value.              */             void * pvcookie = null;             if (guah.uoiwnd.pfnbeforeowp(hwnd, msg, wparam, lparam, &lret, &pvcookie)) {                 goto donecalls;             }               lret = (iswowproc(pfn) ? (*pfnwowwndprocex)(hwnd, msg, wparam, lparam, ptrtoulong(pfn), kpvoid_to_pvoid(pww)) :                 internalcallwinproc((wndproc)kpvoid_to_pvoid(pfn), hwnd, msg, wparam, lparam));               if (guah.uoiwnd.pfnafterowp(hwnd, msg, wparam, lparam, &lret, &pvcookie)) {                 // fall through and exit normally             } donecalls:             ;         } else {             lret = (iswowproc(pfn) ? (*pfnwowwndprocex)(hwnd, msg, wparam, lparam, ptrtoulong(pfn), kpvoid_to_pvoid(pww)) :                 internalcallwinproc((wndproc)kpvoid_to_pvoid(pfn), hwnd, msg, wparam, lparam));         }     end_callwinproc(finsidehook)       return lret; #ifdef _win64     unreferenced_parameter(pww); #endif // _win64 }

internalcallwinproc是宏,定义于nt\windows\core\ntuser\client\callproc.h 

#define internalcallwinproc(winproc, hwnd, message, wparam, lparam)    \     (winproc)(hwnd, message, wparam, lparam)

其中winproc是函数指针,随之猜想,winproc可能会有机会指向notepad.exe代码段中的某个地址。


3.精准型消息断点

先给前面猜想的结论,当usercallwinproccheckwow函数调用internalcallwinproc时,winproc有机会(winproc还会指向ntdll中的回调函数)会指向notepad的窗口过程:

如上图,调用internalcallwinproc,ebx指向notepad.npwndproc。既然如此,我可以在调用internalcallwinproc时下条件断点(我就称它为精准型消息断点)

图中ebx的取值需要根据代码段的起始/结束位置做调整。

当ollydbg中断时,结合internalcallwinproc的函数原型可知:

ebx指向真正的窗口过程;–>这就是我提出的精准型消息断点,可以解决前面提出的问题

esi指向接受消息的窗口句柄;

edi指向消息类型

当然,我们还能进一步编辑条件断点的条件,过滤出特定的窗口消息wm_keyup消息。

原文链接:https://bbs.kanxue.com/thread-277392.htm

网络摘文,本文作者:15h,如若转载,请注明出处:https://www.15cov.cn/2023/08/27/原创-精准型消息断点/

发表评论

邮箱地址不会被公开。 必填项已用*标注

网站地图