
幾年前,想寫一個關于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 "http://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 ); //獲得文本
看雪學苑
雷石安全實驗室
看雪學苑
RacentYY
合天網安實驗室
LemonSec
重生信息安全
看雪學苑
看雪學苑
黑客技術和網絡安全
雷石安全實驗室
看雪學苑