------- 軟體調試——挫敗 QQ.exe 的內核模式保護機制 -------

来源:https://www.cnblogs.com/flying-shark/archive/2018/02/05/8419853.html
-Advertisement-
Play Games

———————————————————————————————————————————————————————————————————————— QQ 是一款熱門的即時通信(IM)類工具,在安裝時刻會向系統分區的 \..\windows\system32\drivers 路徑下生成兩個驅動程式文件: ...


 

————————————————————————————————————————————————————————————————————————

QQ 是一款熱門的即時通信(IM)類工具,在安裝時刻會向系統分區的 \..\windows\system32\drivers 路徑下生成兩個驅動程式文件:

QQProtect.sys 與 QQFrmMgr.sys ,前者是 QQProtect.exe(QQ 安全防護進程,又稱 Q 盾)的內核模式組件;後者是一種過濾型驅動。

同時還會向註冊表位置 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\ 創建對應名稱的鍵,其中有重要的兩個子鍵控制這兩個驅動的載入

式:“type”與“start”——對於 QQProtect.sys ,其鍵值分別為 1 和 2,這意味著由 services.exe(服務控制管理器)自動將 QQProtect.sys 載入內核

間;對於 QQFrmMgr.sys,其鍵值分別為 1 和 1,這意味著在內核初始化期間,由 ntoskrnl.exe 將 QQFrmMgr.sys 載入內核空間,就載入

的順序而言,QQFrmMgr.sys 早於 QQProtect.sys ,如下圖所示:

 

儘管這兩個驅動並非 Rootkit 或者惡意軟體,但它們確實會 hook 系統服務調度表/Shadow、在 System 進程中註入內核線程、註

冊一些通知回調。。。。

所以也算是更改了系統的一些關鍵數據結構來進行非正當活動。

本文探討如何使用內核調試器 WinDbg.exe 來檢查諸如此類為保護 QQ 進程而採取的內核空間手段,然後把系統還原至“乾凈”狀態。

測試環境是兩台真實的電腦(雙機物理調試)——運行 Windows 7 的 宿主機(調試機),以及運行 Windows 8.1 的目標機(被調試機,已安裝了 QQ)

兩者通過乙太網線連接進行調試。

註意,通過乙太網線執行雙機物理調試時,對調試機的網卡無特殊要求;但是被調試機的網卡必須被 Debugging Tools for Windows 所支持(亦即 Kd.exe

與 WinDbg.exe),而且被調試機上的操作系統版本需要是 windows 8 或者更後面的版本;調試機上的操作系統需要是 windows xp 或更後面的版本。

 

使用乙太網調試的一大好處就是,通信介質獲取方便——相較於老舊的串口線(RS-232)以及主板上基本被淘汰的 COM 模塊而言,Cat5 標準以上的

網路線隨便在電腦城就能買到,而且主板上絕不可能沒有網路介面卡使用的 RJ-45 埠。。。。想必乙太網調試一定會成為日後的標準!

另一方面,我也實施了物理-虛擬機調試,虛擬機作為被調試機,其上運行 Windows 7,這樣不但能夠對比出,QQ 驅動針對不同內核版本

(Windows 7 是內核版本 6.1 ;Windows 8.1 是內核版本 6.3)所表現出來的邏輯差異,還能夠明確 QQ 驅動是否採取了“反虛擬機”技術,並且揭示它在

真實機器上的行為!

 

因此下麵的調試過程中,所有與真實機器上不同的結果我都會另行說明。在開始之前,來過目一下我配置的雙機物理調試參數:

1 cd "d:\Windows Kits\10\Debuggers\x86" && d: && windbg.exe -n -v -logo d:\networking_physical_host-target_debugging.txt -y SRV*E:\windows8_1_retail_symbols*http://msdl.microsoft.com/download/symbols -k net:port=60111,key=shayi.1983.gmail.com

 

 

其中,

❶ 我將 Windows Kits 驅動開發工具包安裝到了“d:\Windows Kits”目錄下;

❷ 輸出調試信息到指定的日誌文件;

❸ 指定微軟的符號伺服器 URL,這樣調試器就可以通過 HTTP GET 請求,按需從伺服器下載並解析特定內核模塊中的函數符號;

❹ 以及預先存儲在本地的符號文件(可以從 MSDN 站點下載,整個 MSI 封裝的符號包大小約為五、六百 MB)所在路徑;

 (註意,宿主機上內核版本的不同導致需要分別下載對應的符號文件,並指定為調試參數

❺ 指定通過乙太網調試(net),宿主機上開啟調試埠為 UDP 的 60111;

❻ 最後的 key 可以任意指定,但其中的 4 個子域之間需要用點號分隔開。

關於目標機上的對應配置,請各位參見 MSDN 文檔,這裡就不再贅述。

——————————————————————————————————————————————————————————

首先在 Windows 8.1 目標機上通過 Process Explorer 瀏覽到 System 進程中的系統線程,其中有一個 QQ 內核線程是由

QQFrmMgr.sys 創建的,該線程的啟動地址距離所屬模塊被載入基址的偏移量為 0x5e34 :

 

我們的目標是結束該線程的執行,通常的做法是用系統內置的 APC(非同步過程調用)機制來實現。APC 就是運行在特定線程上下文

中的常式。

從編程角度來講,調用 KeInitializeApc() 初始化一個 nt!_KAPC 結構,並將其關聯到該 QQ 內核線程的 nt!_KTHREAD 結構,設定

該 APC 常式回調為 PspExitThread()然後利用 KeInsertQueueApc() 通過這個 nt!_KTHREAD 結構來排入該 QQ 內核線程的

APC 隊列,如此一來,當該 APC 被交付時,就會在該 QQ 內核線程的執行上下文中調用 PspExitThread(),從而終止掉該 QQ 內

核線程。

而在調試環境下,沒有對應的內核 API 可用,所以我們必須手工構造 APC、指定回調函數、關聯線程、以及排入隊列,如下步驟所

示:

第一步:查詢 System 進程的 nt!_EPROCESS 結構地址;

第二步:定位到其中的線程雙向鏈表頭部,然後開始遍歷這個鏈表中的每一個 nt!_ETHREAD 結構,找出那些啟動地址位於

QQFrmMgr.sys 模塊空間內的線程:

 

 

 相應的 WinDbg 命令如下:

1 !list "-t nt!_LIST_ENTRY.FLink -e -x \"r @$t3=@$extret-@$t1; 
2 r @$t4= @$t3+@$t2;
3 r @$t5=poi(@$t4);
4 .if(@@((unsigned long)@$t5>(unsigned long)0x82000000 && (unsigned long)@$t5<(unsigned long)0x82017b00)){r @$t3;dt -b nt!_ETHREAD Cid. @$t3; dds @$t4 l1;}; 
5 \" 8565e5c0+@$t0"

 

 

第三步:手工構建一個 nt!_KAPC 結構,指定回調函數、關聯線程、以及排入隊列:

(3-1):查詢 QQFrmMgr.sys 模塊內部的 section 信息,註意到其中的 .data section 後緊接 INIT section:

 

 

從上圖可知,.data section 起始 RVA 為 11800,大小 3C80,結束 RVA 為 15480,這剛好是 INIT section 的起始 RVA。

INIT section 的屬性中,Discardable”與“Execute Read Write”完美匹配了手工構建 APC 需要的寫屬性,以及回調函數需要

執行屬性,所以它是理想的目標 section。

(3-2):從 INIT section 起始地址初始化 0x200 位元組記憶體,此塊區域用於 nt!_KAPC 結構和回調函數(nt!_KAPC 的

KernelRoutine 欄位)。如下圖所示,

我們在地址 82015480 處構造的回調函數調用 PspExitThread() 來結束當前線程的運行;然後在地址 82015500 處構造一個

nt!_KAPC 結構;

 

 

 1 r @$t0=82015500; 
 2 r @$t1=8b9ccbc0; 
 3 r@$t2=82015480;
 4 ?? ((nt!_KAPC*)@$t0)->Type=18;
 5 ?? ((nt!_KAPC*)@$t0)->Size=sizeof(nt!_KAPC);
 6 ?? ((nt!_KAPC*)@$t0)->Thread=@$t1;
 7 ?? ((nt!_KAPC*)@$t0)->KernelRoutine=@$t2;
 8 ?? ((nt!_KAPC*)@$t0)->Inserted=1;
 9 r @$t3=@@(&(((nt!_ETHREAD*)@$t1)->Tcb.ApcState.ApcListHead[0]));
10 r @$t4=@@(&(((nt!_KAPC*)@$t0)->ApcListEntry));
11 r @$t5=@@(((nt!_LIST_ENTRY*)@$t3)->Flink);
12 ?? ((nt!_LIST_ENTRY*)@$t4)->Flink=@$t5;
13 ?? ((nt!_LIST_ENTRY*)@$t4)->Blink=@$t3;
14 ?? ((nt!_LIST_ENTRY*)@$t5)->Blink=@$t4;
15 ?? ((nt!_LIST_ENTRY*)@$t3)->Flink=@$t4;
16 ?? ((nt!_ETHREAD*)@$t1)->Tcb.ApcState.KernelApcPending=1;

 

 

變數“t1”的值是前面查詢到的 QQ 內核線程的 nt!_ETHREAD 結構;

變數“t3”用來定位到 nt!_ETHREAD 結構中的第一個 APC 隊列頭部(Tcb.ApcState.ApcListHead[0]);這個隊列頭部

的“Flink”欄位(指向下一個 nt!_KAPC 結構)由變數“t5”存儲;

變數“t4”亦即我們構建的 nt!_KAPC 結構中的“ApcListEntry”欄位,它被用來初始化“t5”;

這種初始化邏輯類似於下麵的 C 代碼:

1  KTHREAD.ApcState.ApcListHead[0]->Flink = KAPC->ApcListEntry;

 

驗證我們的操作是否正確:

 

整個過程的形象圖示:

為了理解 APC 交付的機制,我們在回調函數入口處設置一個斷點,然後就能夠通過棧回溯信息得知該回調是如何被調用的,按

下“g”鍵恢複目標機器的執行,等待 APC 交付時觸發斷點:

 

 

從上圖可以看到,這種 APC 交付機制其實並不神秘—— 傳遞給 PspSystemThreadStartup() 的首個參數就是 QQFrmMgr.sys 創

建的 QQ 內核線程的啟動地址,表明它被調度運行了;經過一系列調用後,KiSwapThread() 從它接收到的首個參數

(0x8b9ccbc0,亦即 QQ 內核線程的 nt!_ETHREAD 結構地址)中,定位到其 APC 隊列頭部,然後調用鏈表中第一個 nt!_KAPC

結構的“KernelRoutine”回調,從而觸發我們先前設置的斷點。

按下“g”鍵繼續運行,導致 PspExitThread() 把 QQ 內核線程終止掉然後返回,現在通過 Process Explorer 瀏覽目標機器上,

System 進程中的系統線程們,已經找不到 QQFrmMgr.sys+0x5e34 那個線程了,另一方面,也可以在調試機器上驗證:

 

1 r @$t0=@@(#FIELD_OFFSET(nt!_EPROCESS, ThreadListHead));
2 r @$t1= @@(#FIELD_OFFSET(nt!_ETHREAD, ThreadListEntry));
3 r @$t2=@@(#FIELD_OFFSET(nt!_ETHREAD, StartAddress));
4 !list "-t nt!_LIST_ENTRY.FLink -e -x \"r @$t3=@$extret-@$t1; 
5 r @$t4= @$t3+@$t2; 
6 r @$t5=poi(@$t4);
7 .if(@@((unsigned long)@$t5>(unsigned long)0x82000000 && (unsigned long)@$t5<(unsigned long)0x82017b00)){r @$t3;dt -b nt!_ETHREAD Cid. ExitStatus @$t3; dt -b nt!_KTHREAD Header. @$t3; }; 
8 \" 8565e5c0+@$t0"

 

 

——————————————————————————————————————————————————————————————————————————————————

小結:本篇討論瞭如何利用內核提供的基礎設施——APC——來挫敗 QQ 過濾驅動向內核空間註入的可執行代碼,併在基於 Windows 8.1(NT 6.3 版內

核)的真實機器上成功實踐,限於篇幅,後續博文將介紹如何檢測並還原 QQ 驅動修改的其它內核數據結構,以及清除它安裝的鉤子常式!

 

——————————————————————————————————————————————————————————————————————————————————

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、dir內置函數 二、類 1、定義 類名 大駝峰命名法 2、創建 3、self參數(哪一個對象調用方法,self就是哪一個對象的引用 類似於this) (1)、增加屬性 (2)、 調用屬性 self.訪問對象的屬性 self.調用其他的對象方法 (PS 這種在外部賦值屬性的方法 不建議使用哦) ( ...
  • 登錄失敗信息回顯不會的新的一個頁面,而是顯示在登錄頁面 一種方法是: 登錄頁面表單中每個欄位後添加<span>標簽寫入失敗信息,利用ajax技術 通過改變<span>標簽的display:none屬性添加失敗信息 這裡用的類似的方法: 資料庫準備略, 註意寫好對應的user類供BeanHandler ...
  • package com.swift; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileInputStream; import java.io.FileOutputStream; impo... ...
  • lambda x: x * x,實際上就是 def f(x): retrun x * x 關鍵字lambda表示匿名函數,冒號前的x表示函數參數。 匿名函數只能有一個表達式,不用寫return,返回值就是該表達式的結果。 用匿名函數有個好處,因為函數沒有名字,不必擔心函數名衝突。此外,匿名函數也是一 ...
  • 微服務,通常都是用複雜的、大規模分散式集群來實現的。微服務構建在不同的軟體模塊上,這些軟體模塊,有可能是由不同的團隊開發、可能使用不同的編程語言來實現、有可能布在了幾千台伺服器,橫跨多個不同的數據中心。因此,就需要一些可以幫助理解系統行為、用於分析性能問題的工具。 API網關Ocelot 作為微服務 ...
  • 重寫 class 的 ToString() 來簡化獲取 enum 的 DescriptionAttribute 值 目錄 一、常見的 enum 類型版本 二、演變:class 版本的 enum 類型 三、演進:class 和 enum 兩者共存的版本 一、常見的 enum 類型版本 新建一個 Alg ...
  • 1. 前言 Fall Creators Update中提供了一個新得ColorPicker控制項,解決了以前選擇顏色只能用Combo Box的窘境。 2. 一個簡單的例子 如上所示,ColorPiker可以通過在光譜或色輪上拖動滑塊,或者在RGB/HSV及十六進位的TextBox中直接輸入顏色的數值改 ...
  • 一開始,思路是這樣的: 遺憾的是,將會拋出一個異常: 修改後的實現方式: 或者 : 最後附上SqlHelper: static class SqlHelper { private static readonly string ConnectionString = ConfigurationManag ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...