从应用层到MCU看Windows处理键盘输入

VSole2023-04-10 09:48:04

几年前,想写一个关于ACPI协议的系列文章,并归档在,但由于各种原因(最主要的原因是没有合适的笔记本)断更了。

断更的这些年,我对驱动/ACPI/Bios有了更多的理解;又逢微软/Intel助攻。2020年微软泄露了部分WinXP源码(>75%), Intel泄露了KabyLake设计文档;最重要的,这个月淘宝帮我克服了最大的写作障碍----我买到了二手的dynabook笔记本。

鉴于以上原因,我打算重开一个新的系列,在不泄密的前提下,以键盘输入为例,介绍从应用层到驱动层,再到Bios,最后到MCU的处理流程。用生产者/消费者模型来看,就是应用层通过什么途径,才能消费由键盘生产的按键。如果有时间,我也会尝试补全>系列。

文本编辑器/文本编辑框是应用层常见的键盘处理程序。微软泄露的WinXP源码下有文本编辑器Notepad的实现:

Microsoft_leaked_source_codet5src\Source\XPSP1\NT\shell\osshell\accesoryotepad

(notepad源码结构)

实现一个文本编辑器并不复杂,微软又(被迫)提供了Sample,因此我就不重复造轮子了。本文从调试器的角度观察Notepad.exe如何消费(使用)键盘按键。

1.Notepad接收按键消息/WM_CHAR

首先评估一下调试Notepad.exe的难易程度(虽然有源码,我还是装作没有):Windows窗体程序,在我可以折腾的范围。

查壳,无壳且看着像是C++编译器生成

(Spy++查看窗体,Notepad.exe属于标准的Windows窗口程序,如果遇到C#窗体,我直接放弃了)

既然(猜测)Notepad.exe是标准的窗口程序,那它一定按窗口程序的模板(如下)处理窗口消息,WM_CHAR等按键消息的处理亦包含其中:

    //窗口消息循环模板    while (GetMessage((LPMSG)&msg, (HWND)NULL, 0, 0))    {        if (TranslateAccelerator(hwndNP, hAccel, (LPMSG)&msg) == 0)        {           TranslateMessage ((LPMSG)&msg);           DispatchMessage ((LPMSG)&msg);        }    }

在跟踪按键消息的消费者WM_CHAR的行为前,先要从茫茫众消息(窗体消息中有大量的鼠标移动的消息干扰分析)中筛选出WM_CHAR,思路如下:

① 搜索并定位GetMessage API;

② 分析GetMessage返回的消息,筛选出WM_CHAR消息。

下文分步实现上述思路:

搜索并定位GetMessage API

先用IDA查找并定位Notepad.exe调用GetMessage API的位置,再用windbg下断点:

(根据IDA分析,Notepad.exe在WinMain中进行消息循环)

0:001> x notepad!*main* ;先找符号winMain,再找GetMessage调用处,最后下断点00007ff7`e4d0ad6c NOTEPAD!wWinMain ()  0:001> uf NOTEPAD!wWinMain ;对wWinMain函数进行反汇编00007ff7`e4d0b010 488d4d0f        lea     rcx,[rbp+0Fh] ;<--获得窗体消息msg变量的地址00007ff7`e4d0b014 4533c0          xor     r8d,r8d00007ff7`e4d0b017 33d2            xor     edx,edx00007ff7`e4d0b019 48ff1500bc0100  call    qword ptr [NOTEPAD!_imp_GetMessageW (00007ff7`e4d26c20)]00007ff7`e4d0b020 0f1f440000      nop     dword ptr [rax+rax]00007ff7`e4d0b025 85c0            test    eax,eax

简单说明注记一下上面windbg的输出的:

L5处:GetMessage需要4个参数,参数1传入窗体消息MSG msg的地址。而我的OS是64位系统,所以Notepad.exe也是64位程序。而64位程序依次通过rcx/rdx/r8/r9传入函数的前4个参数,lea rcx是传入窗体栈变量msg的地址。

L9处:运行到L9处时,GetMessage调用结束。在此处下断点,查看变量MSG msg就可以获得窗体消息。

分析GetMessage返回的消息,筛选出WM_CHAR消息

为了使windbg能正确解析各个成员变量,需要明确告知windbg从GetMessage返回的窗体消息是个MSG结构体。

0:001> dt combase!MSG   +0x000 hwnd             : Ptr64 HWND__   +0x008 message          : Uint4B   +0x010 wParam           : Uint8B   +0x018 lParam           : Int8B   +0x020 time             : Uint4B   +0x024 pt               : tagPOINT

在GetMessage返回地址处下断点,当windbg断下后,开始解析MSG内容:

0:001> bp 00007ff7`e4d0b0200:001> gBreakpoint 0 hit  0:000> dt combase!MSG [rbp+f]   +0x000 hwnd             : 0x00000000`001001fe HWND__   +0x008 message          : 0xf   +0x010 wParam           : 0   +0x018 lParam           : 0n0   +0x020 time             : 0xaa1dbe   +0x024 pt               : tagPOINT

上面的片段中:

L1处 在GetMessage API的返回地址处下断点。

L6处 从GetMessage API中获得的窗口句柄,这和前面Spy++获得的窗口句柄值一致。

L7处 从GetMessage API中获得的消息类型,值0x0f对应WM_PAINT (我调试时,notepad.exe正好被windbg窗口挡住)。

在此,我截取了WinUser.h中部分消息的定义:

#define WM_SETTEXT                      0x000C#define WM_GETTEXT                      0x000D#define WM_GETTEXTLENGTH                0x000E#define WM_PAINT                        0x000F#define WM_CLOSE                        0x0010

窗口程序上会接收到大量消息,这些消息跟噪音一样影响分析。因此需要修改一下前面的断点,让它变为条件断点(条件断点略复杂,请移步windbg设置条件断点),每当Notepad.exe中按键,windbg打印一串字符(WM_CHAR Enter):

0:001> bp 00007ff7`e4d0b020 ".block{r @$t0=poi(rbp+0xf+0x08);.if(@$t0==0x102){.printf @\"WM_CHAR enter\";gc;};.else{gc;}}"0:000> g

看下效果,左边的红框是我在Notepad中随意按键输入,右边是windbg相应输出 (作为演示效果挺好的,除了记事本按键半天才给个显示):

2.Notepad处理WM_CHAR/显示输出

Notepad.exe以内存映射的方式实现文件读写,它将收到的按键值暂存在所映射内存中,通过某种机制(哪种机制?)将这段内存内容显示在文本(文本编辑框)上。

如果修改这段内存,是否导致最终文本内容被修改?以修改如下文本为例,自问自答吧:

用windbg在内存中搜索Unicode String,例如State:

0:003> s -u 0x20181900000 L?100000 "State" #windbg 搜索指定Unicode string "State"

#搜索acpi.h文件中第一行文字,确定所在的内存起始地址:0:001> s -u 0x20181900000 L?100000 "typedef struct _GAS_20 {"00000201`81967050  0074 0079 0070 0065 0064 0065 0066 0020  t.y.p.e.d.e.f. .#以Unicode字符串形式打印起始地址的内容:0:001> du 00000201`8196705000000201`81967050  "typedef struct _GAS_20 {..    UI"00000201`81967090  "NT8...AddrSpcID;          //The "00000201`819670d0  "address space where the data str"00000201`81967110  "ucture or register exists...    "00000201`81967150  "                                "00000201`81967190  "//Defined values are above      "00000201`819671d0  "                                "00000201`81967210  "      ..    UINT8...RegBitWidth;"00000201`81967250  "..//The size in bits of the give"00000201`81967290  "n register. ...........//When ad"00000201`819672d0  "dressing a data structure, this "00000201`81967310  "field must be zero...    UINT8.."

通过windbg搜索结果,可以确定文本内容所在的内存地址:0000020181967050。

确定文本所在的起始地址后,准备尝试修改该内存块。如果修改内存后直接会反应到文本上(根据测试结果,需要让窗口重绘才能使得修改生效),那么可以证明Notepad确实通过内存映射的方式访问文件。修改前我们再核对一下acpi.h开头的内容,因为待会马上要整容了:

#以Unicode string方式修改内存0:004> eu 0x20181967050    "I don't know what to write"#查看修改结果0:004> du 0x20181967050    00000201`81967050  "I don't know what to write    UI"00000201`81967090  "NT8...AddrSpcID;          //The "00000201`819670d0  "address space where the data str"00000201`81967110  "ucture or register exists...    "

下图是Notepad的显示输出,看着acpi.h的变化证明了我的猜想。

3.链接键盘输入和显示输出过程

上一节提出了一个问题:Notepad通过某种机制将这段内存内容显示在文本(文本编辑框)上。

这一节简单的回答这个问题:

a.输入端:Notepad接收到WM_CHAR消息后,通过DispatchMessage,将消息传给文本编辑框句柄hwndEdit(为什么hwndEdit就是文本编辑框的句柄?这个可以参考张银奎老师的《格蠹汇编》一书)。

b.hwndEdit所在窗体的Callback处理WM_CHAR,将键盘消息插入到内存映射所对应的Unicode String的恰当位置。

c.输出端:由hwndEdit调用SetDlgItemText将Unicode String显示到Notepad.exe对应的文本编辑框。

Notepad源码中通过下列方式,从hwndEdit窗口句柄获得文本内容:

    hEText= (HANDLE) SendMessage( hwndEdit, EM_GETHANDLE, 0, 0 ); //获得文本句柄    if( !hEText )  // silently return if we can't get it    {        return( bStatus );    }    pStart= LocalLock( hEText ); //获得文本
char函数windbg
本作品采用《CC 协议》,转载必须注明作者和本文链接
由于RPC的规范非常复杂,且相关内容很少,所以我也只是根据文档和调式尽可能地将我的理解贴上来,如果有误欢迎指正。MyRPCServer向外只导出一个函数,并打印出4个传入的参数内容.MyRPCClient是一个最简单的RPC客户端,它调用MyRPCServer向外导出的HelloProc函数,并传入4个参数。
返回到内核继续执行的时候,会将用户层函数中指定的地址保存到窗口对象偏移0x128的pExtraBytes成员中。通过劫持用户层函数的执行,可以让SetWindowLongPtr函数对不合法地址进行写入会产生BSOD,也可以通过计算来扩大其他窗口的cbwndExtra,从而实现任意地址读写,最终实现提权
前言1.漏洞描述在win32k!xxxMNEndMenuState函数中,函数会调用MNFreePopup函数释放tagPOPUPMENU对象,但是函数释放对象以后,没有清空指针。而在弹出窗口过程中,用户可以劫持相应的处理函数来实现两次调用xxxMNEndMenuState函数,因为双重释放导致BSOD的产生。通过内存布局,可以伪装tagPOPUPMENU对象在释放的内存空间中,通过解引用修改窗口对象的关键的标志位,可以通过SendMessage函数让窗口在内核态执行指定的处理函数实现提权操作。
漏洞描述该漏洞存在与win32k模块中的SetImeInfoEx函数,在该函数中未对tagWINDOWSTATION结构偏移0x14的spkiList进行有效性验证就对其进行解引用操作,而spkList可以为NULL,此时就会对地址0x14进行解引用操作,导致系统崩溃。
本系列将以官网资料为基础主要通过动态跟踪来解析DynamoRIO的源代码。因为如果不结合实例只是将各函数的作用写出来,实在无法很好的说明问题,我们将以代码覆盖工具drcov为例,分析DynamoRIO的执行流程。
可在其中找受影响的版本复现,在受影响版本的系统中找到win32k.sys导入IDA。漏洞函数位于win32k.sys的SetImeInfoEx()函数,该函数在使用一个内核对象的字段之前并没有进行是否为空的判断,当该值为空时,函数直接读取零地址内存。如果在当前进程环境中没有映射零页面,该函数将触发页面错误异常,导致系统蓝屏发生。tagWINDOWSTATIONspklList对象的结构为:漏洞触发验证查看SSDT表dd KeServiceDescriptorTabledds Address L11C 显示地址里面值指向的地址. 以4个字节显示。
看雪论坛作者ID:LarryS
而在xxxSBTrackInit和xxxFreeWindow中都存在用户层的回调,通过对函数的劫持可以在回调中释放掉xxxSBTrackInit函数中使用的tagSBTRACK结构体,这样当xxxSBTrackInit释放该结构体的时候就会因为双重释放导致BSOD的产生
内核漏洞学习-HEVD-NullPointerDereference
CS-Shellcode分析(一)
2021-10-18 08:21:57
本文是CS的shellcode分析的第一篇文章,该系列文章旨在帮助具有一定二进制基础的朋友看懂cs的shellcode的生成方式,进而可以达到对shellcode进行二进制层面的改变与混淆,用于免杀相关的研究。执行后可以得到shellcode的原始文件然后我们再用IDA打开这个文件进行分析:先看第一个call的内容这里pop rbp?
VSole
网络安全专家