------- 軟體調試——挫敗 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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...