日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

APC机制详解

發(fā)布時間:2025/3/21 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 APC机制详解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

    • APC的本質(zhì)
      • APC隊(duì)列
      • APC結(jié)構(gòu)
      • APC相關(guān)函數(shù)
        • KiServiceExit
        • KiDeliveApc
    • 備用APC隊(duì)列
      • ApcState的含義
      • 掛靠環(huán)境下的ApcState的含義
      • 其他APC相關(guān)成員
        • ApcStatePointer
        • ApcStateIndex
        • ApcStatePointer與ApcStateIndex組合尋址
        • ApcQueueable
    • APC掛入過程
      • KAPC結(jié)構(gòu)
      • 掛入流程
      • KeInitializeApc
      • ApcStateIndex
      • KiInsertQueueApc
    • 內(nèi)核APC的執(zhí)行過程
      • 執(zhí)行點(diǎn):線程切換
      • 執(zhí)行點(diǎn):系統(tǒng)調(diào)用 中斷或者異常(_KiServiceExit)
      • KiDeliverApc函數(shù)分析
      • 總結(jié)
    • 用戶APC的執(zhí)行過程
      • 執(zhí)行用戶APC時的堆棧操作
      • KiDeliverApc函數(shù)分析
      • KiInitializeUserApc函數(shù)分析:備份CONTEXT
      • KiInitializeUserApc函數(shù)分析:堆棧圖
      • KiInitializeUserApc函數(shù)分析:準(zhǔn)備用戶層執(zhí)行環(huán)境
      • ntdll.KiUserApcDispatcher函數(shù)分析
      • 總結(jié)

APC的本質(zhì)

線程是不能被殺死 掛起和恢復(fù)的,線程在執(zhí)行的時候自己占據(jù)著CPU,別人怎么可能控制他呢?舉個極端的例子,如果不調(diào)用API,屏蔽中斷,并保證代碼不出現(xiàn)異常,線程將永久占據(jù)CPU。所以說線程如果想結(jié)束,一定是自己執(zhí)行代碼把自己殺死,不存在別人把線程結(jié)束的情況。

那如果想改變一個線程的行為該怎么辦?可以給他提供一個函數(shù),讓他自己去調(diào)用,這個函數(shù)就是APC,即異步過程調(diào)用

APC隊(duì)列

我們現(xiàn)在需要討論是的,如果我給某一個線程提供一個函數(shù),那么這個函數(shù)掛在哪里?答案是APC隊(duì)列,先來看一下當(dāng)前線程的結(jié)構(gòu)體

kd> dt _KTHREAD ntdll!_KTHREAD +0x040 ApcState : _KAPC_STATE

線程結(jié)構(gòu)體KTHREAD0x40的位置的成員是一個子結(jié)構(gòu)體ApcState,也就是APC隊(duì)列

kd> dt _KAPC_STATE nt!_KAPC_STATE+0x000 ApcListHead //2個APC隊(duì)列 用戶APC和內(nèi)核APC +0x010 Process //線程所屬進(jìn)程或者所掛靠的進(jìn)程+0x014 KernelApcInProgress //內(nèi)核APC是否正在執(zhí)行+0x015 KernelApcPending //是否有正在等待執(zhí)行的內(nèi)核APC+0x016 UserApcPending //是否有正在等待執(zhí)行的用戶APC

_KAPC_STATE的第一個成員是兩個APC隊(duì)列,每一個成員都是一個雙向鏈表,這個雙向鏈表就是APC隊(duì)列。

APC一共有兩個,一個是用戶態(tài)APC隊(duì)列,一個是內(nèi)核態(tài)的APC隊(duì)列,里面存儲的都是APC函數(shù)。

你想讓線程執(zhí)行某些操作的時候,就可以提供一個函數(shù),掛到這個鏈表里,在某一個時刻,當(dāng)前線程會檢查當(dāng)前的函數(shù)列表,當(dāng)里面有函數(shù)的時候,就會去調(diào)用。這樣就相當(dāng)于改變了線程的行為。

現(xiàn)在我們知道了如果想改變線程的行為,需要提供一個函數(shù)掛到線程的APC隊(duì)列里,準(zhǔn)確的說是提供一個APC,接下里需要了解APC的結(jié)構(gòu)。

APC結(jié)構(gòu)

kd> dt _KAPC ntdll!_KAPC+0x000 Type : UChar+0x001 SpareByte0 : UChar+0x002 Size : UChar+0x003 SpareByte1 : UChar+0x004 SpareLong0 : Uint4B+0x008 Thread : Ptr32 _KTHREAD+0x00c ApcListEntry : _LIST_ENTRY+0x014 KernelRoutine : Ptr32 void +0x018 RundownRoutine : Ptr32 void +0x01c NormalRoutine : Ptr32 void +0x020 NormalContext : Ptr32 Void+0x024 SystemArgument1 : Ptr32 Void+0x028 SystemArgument2 : Ptr32 Void+0x02c ApcStateIndex : Char+0x02d ApcMode : Char+0x02e Inserted : UChar

其中最重要的是+0x01c NormalRoutine的這個成員,通過這個成員可以找到你提供的APC函數(shù)。

現(xiàn)在我們知道了提供APC需要遵循的格式,以及存到線程的位置,但是還有另外的問題,當(dāng)前的線程什么時候會執(zhí)行所提供的APC函數(shù)

如果想要解決這個問題,需要知道一個內(nèi)核函數(shù):KiServiceExit

APC相關(guān)函數(shù)

KiServiceExit

這個函數(shù)是系統(tǒng)調(diào)用 異常或中斷返回用戶空間的必經(jīng)之路

KiDeliveApc

負(fù)責(zé)執(zhí)行APC函數(shù)

備用APC隊(duì)列

kd> dt _KTHREAD ntdll!_KTHREAD +0x040 ApcState : _KAPC_STATE +0x170 SavedApcState : _KAPC_STATE

在線程結(jié)構(gòu)體0x40的位置是APC隊(duì)列,在0x170的位置也有一個APC隊(duì)列,這兩個成員的結(jié)構(gòu)是完全一樣的

ApcState的含義

線程隊(duì)列中的APC函數(shù)都是與進(jìn)程相關(guān)聯(lián)的,具體點(diǎn)說:A進(jìn)程的T線程中所有的APC函數(shù),要訪問的內(nèi)存地址都是A進(jìn)程的。

但線程是可以掛靠到其他的進(jìn)程:比如A進(jìn)程的線程T,通過修改CR3,就可以訪問B進(jìn)程的地址空間,即所謂的進(jìn)程掛靠。

當(dāng)T線程掛靠B進(jìn)程后,APC隊(duì)列中存儲的仍然是原來的APC。具體點(diǎn)說,比如某個APC函數(shù)要讀取地址為0x12345678的數(shù)據(jù),如果此時進(jìn)行讀取,讀到的將是B進(jìn)程的地址空間,這樣邏輯就錯誤了。

為了避免混亂,在T線程掛靠B進(jìn)程時,會將ApcState中的值暫時存儲到SavedApcState中,等回到原進(jìn)程A時,再將APC隊(duì)列恢復(fù)

所以,SavedApcState又稱為備用APC隊(duì)列

掛靠環(huán)境下的ApcState的含義

在掛靠環(huán)境下,也是可以將線程APC隊(duì)列插入APC的,那這種情況下,使用的是哪個APC隊(duì)列呢?

A進(jìn)程的T線程掛靠B進(jìn)程,A是T的所屬進(jìn)程,B是T的掛靠進(jìn)程

  • ApcState:B進(jìn)程相關(guān)的APC函數(shù)
  • SavedApcState:A進(jìn)程相關(guān)的APC函數(shù)

在正常情況下,當(dāng)前進(jìn)程就是所屬進(jìn)程A,如果是掛靠情況下,當(dāng)前進(jìn)程就是掛靠進(jìn)程B

其他APC相關(guān)成員

ApcStatePointer

+0x168 ApcStatePointer : [2] Ptr32 _KAPC_STATE

在KTHREAD結(jié)構(gòu)體的0x168的位置的成員是一個指針數(shù)組,有兩個指針,每一個指針都指向一個ApcState

為了操作方便,KTHREAD結(jié)構(gòu)體中定義了一個指針數(shù)組ApcStatePointer,長度為2。

正常情況下:

? ApcStatePointer[0]指向ApcState

? ApcStatePointer[1]指向SavedApcState

掛靠情況下:

? ApcStatePointer[0]指向SavedApcState

? ApcStatePointer[1]指向ApcState

ApcStateIndex

+0x134 ApcStateIndex : UChar

ApcStateIndex用來標(biāo)識當(dāng)前線程處于什么狀態(tài):0正常狀態(tài) 1掛靠狀態(tài)

ApcStatePointer與ApcStateIndex組合尋址

正常情況下,向ApcState隊(duì)列插入APC時:

? ApcStatePointer[0]指向ApcState,此時ApcStateIndex的值為0

? ApcStatePointer[ApcStateIndex]指向ApcState

掛靠情況下,向ApcState隊(duì)列中插入APC時:

? ApcStatePointer[1]指向ApcState,此時ApcStateIndex的值為1

? ApcStatePointer[ApcStateIndex]指向ApcState

總結(jié):

無論什么環(huán)境下,ApcStatePointer[ApcStateIndex]指向的都是ApcState,ApcState則總是表示線程當(dāng)前使用的APC狀態(tài)

ApcQueueable

+0x0b8 ApcQueueable : Pos 5, 1 Bit

ApcQueueable用于表示是否可以向線程的APC隊(duì)列中插入APC。

當(dāng)線程正在執(zhí)行退出的代碼時,會將這個值設(shè)置為0,如果此時執(zhí)行插入APC的代碼,在插入函數(shù)中會判斷這個值的狀態(tài),如果為0,則插入失敗。

APC掛入過程

無論是正常狀態(tài)還是掛靠狀態(tài),都要有兩個APC隊(duì)列,一個內(nèi)核隊(duì)列,一個用戶隊(duì)列。每當(dāng)要掛入一個APC函數(shù)時,不管是用戶隊(duì)列還是內(nèi)核隊(duì)列,內(nèi)核都要準(zhǔn)備一個KAPC的數(shù)據(jù)結(jié)構(gòu),并且將這個KAPC結(jié)構(gòu)掛到相應(yīng)的APC隊(duì)列中。

KAPC結(jié)構(gòu)

kd> dt _KAPC nt!_KAPC+0x000 Type //類型 APC類型為0x12+0x002 Size //本結(jié)構(gòu)體的大小 0x30+0x004 Spare0 //未使用 +0x008 Thread //目標(biāo)線程 +0x00c ApcListEntry //APC隊(duì)列掛的位置+0x014 KernelRoutine //指向一個函數(shù)(調(diào)用ExFreePoolWithTag 釋放APC)+0x018 RundownRoutine//略 +0x01c NormalRoutine //用戶APC總?cè)肟? 或者 真正的內(nèi)核apc函數(shù)+0x020 NormalContext //內(nèi)核APC:NULL 用戶APC:真正的APC函數(shù)+0x024 SystemArgument1//APC函數(shù)的參數(shù) +0x028 SystemArgument2//APC函數(shù)的參數(shù)+0x02c ApcStateIndex //掛哪個隊(duì)列,有四個值:0 1 2 3+0x02d ApcMode //內(nèi)核APC 用戶APC+0x02e Inserted //表示本apc是否已掛入隊(duì)列 掛入前:0 掛入后 1
  • Type :類型。在Windows里,任何一種內(nèi)核對象都有一個編號,這個編號用來標(biāo)識你是屬于哪一種類型,APC本身也是一種內(nèi)核對象,它也有一個編號,是0x12
  • Size:這個成員指的是當(dāng)前的KAPC的結(jié)構(gòu)體的大小
  • Thread:每一個線程都有自己的APC隊(duì)列,這個成員指定了APC屬于哪一個線程
  • ApcListEntry:APC隊(duì)列掛的位置,是一個雙向鏈表,通過這個雙向鏈表可以找到下一個APC
  • KernelRoutine:指向一個函數(shù)(調(diào)用ExFreePoolWithTag 釋放APC)。當(dāng)我們的APC執(zhí)行完畢以后,當(dāng)前的KAPC本身的這塊內(nèi)存,會由KernelRoutine指定的函數(shù)來釋放
  • NormalRoutine:如果當(dāng)前是內(nèi)核APC,通過這個值找到的就是真正的內(nèi)核APC函數(shù);如果當(dāng)前的APC是用戶APC,那么這個位置指向的是用戶APC總?cè)肟?#xff0c;通過這個總?cè)肟诳梢哉业剿杏脩籼峁┑腁PC函數(shù)
  • NormalContext:如果當(dāng)前是內(nèi)核APC,通過這個值為空;如果當(dāng)前的APC是用戶APC,那么這個值指向的是真正的用戶APC函數(shù)
  • SystemArgument1 SystemArgument2 APC函數(shù)的參數(shù)
  • ApcStateIndex:當(dāng)前的APC要掛到哪個隊(duì)列
  • ApcMode:當(dāng)前的APC是用戶APC還是內(nèi)核APC
  • Inserted:當(dāng)前的APC結(jié)構(gòu)體是否已經(jīng)插入到APC隊(duì)列

掛入流程

KeInitializeApc

VOID KeInitializeApc (IN PKAPC Apc,//KAPC指針I(yè)N PKTHREAD Thread,//目標(biāo)線程IN KAPC_ENVIRONMENT TargetEnvironment,//0 1 2 3四種狀態(tài)IN PKKERNEL_ROUTINE KernelRoutine,//銷毀KAPC的函數(shù)地址IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,IN PKNORMAL_ROUTINE NormalRoutine,//用戶APC總?cè)肟诨蛘邇?nèi)核apc函數(shù)IN KPROCESSOR_MODE Mode,//要插入用戶apc隊(duì)列還是內(nèi)核apc隊(duì)列IN PVOID Context//內(nèi)核APC:NULL 用戶APC:真正的APC函數(shù) )

KeInitializeApc函數(shù)的作用就是給當(dāng)前的KAPC結(jié)構(gòu)體賦值

ApcStateIndex

與KTHREAD(+0x134)的屬性同名,但含義不一樣:

ApcStateIndex有四個值:

  • 0 原始環(huán)境->插入到當(dāng)前線程的所屬進(jìn)程APC隊(duì)列,不管是否掛靠都插入到當(dāng)前線程的所屬進(jìn)程。
  • 1 掛靠環(huán)境
  • 2 當(dāng)前環(huán)境->插入到當(dāng)前進(jìn)程的APC隊(duì)列,如果沒有掛靠,當(dāng)前進(jìn)程則是父進(jìn)程,如果掛靠了,當(dāng)前進(jìn)程就是掛靠進(jìn)程
  • 3 插入APC時的當(dāng)前環(huán)境->線程隨時處于切換狀態(tài) 當(dāng)值為3時,在插入APC之前會判斷當(dāng)前線程是否處于掛靠狀態(tài) 再進(jìn)行APC插入

KiInsertQueueApc

  • 根據(jù)KAPC結(jié)構(gòu)中的ApcStateIndex找到對應(yīng)的APC隊(duì)列
  • 再根據(jù)KAPC結(jié)構(gòu)中的ApcMode確定是用戶隊(duì)列還是內(nèi)核隊(duì)列
  • 將KAPC掛到對應(yīng)的隊(duì)列中,掛到KAPC的ApcListEntry處
  • 再根據(jù)KAPC結(jié)構(gòu)中的Inserted置1,標(biāo)識當(dāng)前的KAPC為已插入狀態(tài)
  • 修改KAPC_STATE結(jié)構(gòu)中的KernelApcPending/UserApcPending
  • 內(nèi)核APC的執(zhí)行過程

    APC函數(shù)的插入和執(zhí)行并不是同一個線程,具體點(diǎn)說:

    在A線程中向B線程插入一個APC,插入的動作是在A線程中完成的,但什么時候執(zhí)行則由B線程決定。所以叫異步過程調(diào)用。

    內(nèi)核APC函數(shù)與用戶APC函數(shù)的執(zhí)行時間和執(zhí)行方式也有區(qū)別。我們先來了解內(nèi)核APC的執(zhí)行過程

    執(zhí)行點(diǎn):線程切換

    IDA打開ntkrnlpa,找到SwapContext函數(shù)

    在這個函數(shù)即將執(zhí)行完成的時候,會判斷當(dāng)前是否有要執(zhí)行的內(nèi)核APC,接著將判斷的結(jié)果存到eax,然后返回

    接著找到上一層函數(shù)KiSwapContext函數(shù)繼續(xù)跟進(jìn)

    這個函數(shù)也沒有對APC進(jìn)行處理,而是繼續(xù)返回,繼續(xù)跟進(jìn)父函數(shù)

    返回到這里,會判斷KiSwapContext的返回值,也就是判斷當(dāng)前是否有要處理的內(nèi)核APC,如果有,則調(diào)用KiDeliverApc進(jìn)行處理。

    這個函數(shù)有三個參數(shù),第一個參數(shù)如果是0,就意味著KiDeliverApc在執(zhí)行的時候只會處理內(nèi)核APC,第一個參數(shù)如果是1,KiDeliverApc除了處理內(nèi)核APC以外,還會處理用戶APC

    流程總結(jié):

  • SwapContext 判斷是否有內(nèi)核APC
  • KiSwapThread 切換線程
  • KiDeliverApc
  • 執(zhí)行點(diǎn):系統(tǒng)調(diào)用 中斷或者異常(_KiServiceExit)

    找到_KiServiceExit函數(shù),這里會判斷是否有要執(zhí)行的用戶APC,如果有的話則會調(diào)用KiDeliverApc函數(shù)進(jìn)行處理,此時KiDeliverApc第一個參數(shù)為1,代表執(zhí)行用戶APC和內(nèi)核APC。

    當(dāng)要執(zhí)行用戶APC之前,先要執(zhí)行內(nèi)核APC

    KiDeliverApc函數(shù)分析

    無論是執(zhí)行內(nèi)核APC還是執(zhí)行用戶APC都會調(diào)用KiDeliverApc函數(shù),接下來分析KiDeliverApc函數(shù)主要做了什么事情

    首先這里會取出內(nèi)核APC列表,然后執(zhí)行跳轉(zhuǎn)

    接著判斷第一個鏈表是否為空(內(nèi)核APC隊(duì)列),如果不為空則跳轉(zhuǎn)

    跳轉(zhuǎn)以后,首先得到KACP的首地址,然后取出KACP結(jié)構(gòu)體的各個參數(shù),放到局部變量里


    在這里,因?yàn)槲覀円幚淼氖莾?nèi)核APC,所以NormalRoutine代表是內(nèi)核APC函數(shù)地址,這里會判斷內(nèi)核APC函數(shù)地址是否為空,不為空的話則進(jìn)行跳轉(zhuǎn)

    跳轉(zhuǎn)以后,先判斷是否有正在執(zhí)行內(nèi)核APC,然后判斷是否禁用內(nèi)核APC,接著將APC從內(nèi)核隊(duì)列中摘除。

    接著先調(diào)用KAPC.KernelRoutine指定的函數(shù) 釋放KAPC結(jié)構(gòu)體占用的空間

    然后將ApcState.KernelApcInProgress 設(shè)置為1 標(biāo)識正在執(zhí)行內(nèi)核APC。

    接著將三個參數(shù)壓入棧里,開始執(zhí)行真正的內(nèi)核APC函數(shù)

    執(zhí)行完畢以后,將ApcState.KernelApcInProgress 置0,接著再次判斷內(nèi)核APC隊(duì)列,開始下一輪循環(huán)

    內(nèi)核APC執(zhí)行流程總結(jié):

  • 判斷第一個鏈表(內(nèi)核APC隊(duì)列)是否為空
  • 判斷KTHREAD.ApcState.KernelApcInProgress(是否正在執(zhí)行內(nèi)核APC)是否為1
  • 判斷是否禁用內(nèi)核APC(KTHREAD.KernelApcDisable是否為1)
  • 將當(dāng)前KAPC結(jié)構(gòu)體從鏈表中摘除
  • 執(zhí)行KAPC.KernelRoutine指定的函數(shù) 釋放KAPC結(jié)構(gòu)體占用的空間
  • 將KTHREAD.ApcState.KernelApcInProgress設(shè)置為1 標(biāo)識正在執(zhí)行內(nèi)核APC
  • 執(zhí)行真正的內(nèi)核APC函數(shù)(KAPC.NormalRoutine)
  • 執(zhí)行完畢 將KernelApcInProgress改為0
  • 總結(jié)

  • 內(nèi)核APC在線程切換的時候就會執(zhí)行,這也就意味著,只要插入內(nèi)核APC很快就會被執(zhí)行
  • 在執(zhí)行用戶APC之前會先執(zhí)行內(nèi)核APC
  • 內(nèi)核APC在內(nèi)核空間執(zhí)行,不需要換棧,一個循環(huán)全部執(zhí)行完畢
  • 用戶APC的執(zhí)行過程

    當(dāng)產(chǎn)生系統(tǒng)調(diào)用 中斷或者異常,線程在返回用戶空間前都會調(diào)用_KiServiceExit函數(shù),在_KiServiceExit函數(shù)里會判斷是否有要執(zhí)行的用戶APC,如果有則調(diào)用KiDeliverApc函數(shù)進(jìn)行處理

    執(zhí)行用戶APC時的堆棧操作

    處理用戶APC要比處理內(nèi)核APC復(fù)雜的多,因?yàn)橛脩鬉PC函數(shù)要在用戶空間執(zhí)行,這里涉及到大量的換棧操作:

    當(dāng)線程從用戶層進(jìn)入內(nèi)核層時,要保留原來的運(yùn)行環(huán)境,比如各種寄存器 棧的位置等等,然后切換成內(nèi)核的堆棧,如果正常返回,恢復(fù)堆棧環(huán)境即可

    但如果有用戶APC要執(zhí)行的話,就意味著線程要提前返回到用戶空間去執(zhí)行,而且返回的位置不是線程進(jìn)入內(nèi)核時的位置,而是返回到真正執(zhí)行APC的位置

    每處理一個用戶APC就會涉及到:內(nèi)核—>用戶空間—>再回到內(nèi)核空間

    執(zhí)行用戶APC最為關(guān)鍵的就是理解堆棧操作的細(xì)節(jié)

    KiDeliverApc函數(shù)分析

    KiDeliverApc函數(shù)會push三個參數(shù),第一個參數(shù)如果為0,代表只處理內(nèi)核APC,如果為1,代表處理用戶APC和內(nèi)核APC。

    也就是說內(nèi)核APC是無論如何都會執(zhí)行的。

    取出內(nèi)核APC隊(duì)列之后會再次取出用戶APC隊(duì)列,并判斷用戶APC隊(duì)列是否為空

    .text:00426063 cmp [ebp+arg_0], 1

    接著判斷KiDeliverApc第一個參數(shù)是否為1 如果不是1 說明不處理用戶APC,直接返回

    .text:00426069 cmp byte ptr [esi+4Ah], 0 ;

    +0x4A=UserApcPending 表示是否正在執(zhí)行用戶APC,為0說明正在執(zhí)行的用戶APC,繼續(xù)往下走

    .text:0042606F mov byte ptr [esi+4Ah], 0

    先將UserApcPending置0,表示當(dāng)前正在執(zhí)行用戶APC

    .text:00426073 lea edi, [eax-0Ch]

    -0xC 得到KPCR首地址

    .text:00426076 mov ecx, [edi+1Ch] ; +0x1C=NormalRoutine 用戶APC總?cè)肟?.text:00426079 mov ebx, [edi+14h] ; +0x14=KernelRoutine 釋放APC的函數(shù) .text:0042607C mov [ebp+var_4], ecx .text:0042607F mov ecx, [edi+20h] ; +0x20 NormalContext 用戶APC:真正的APC函數(shù) .text:00426082 mov [ebp+var_10], ecx .text:00426085 mov ecx, [edi+24h] ; +0x24 SystemArgument1 .text:00426088 mov [ebp+var_C], ecx .text:0042608B mov ecx, [edi+28h] ; SystemArgument2

    接著取出KAPC結(jié)構(gòu)體的成員,放到局部變量里保存

    .text:00426091 mov ecx, [eax] ; -------------------------- .text:00426093 mov eax, [eax+4] .text:00426096 mov [eax], ecx ; 鏈表操作 將用戶APC從鏈表中移除 .text:00426098 mov [ecx+4], eax ; --------------------------

    然后將當(dāng)前的用戶APC從鏈表中摘除

    .text:004260B7 push eax .text:004260B8 push edi .text:004260B9 call ebx ; 調(diào)用KAPC.KernelRoutine 釋放KAPC結(jié)構(gòu)體內(nèi)存

    接著調(diào)用調(diào)用KAPC.KernelRoutine指定的函數(shù), 釋放KAPC結(jié)構(gòu)體內(nèi)存

    到這里為止,用戶APC和內(nèi)核APC的處理方式就發(fā)生了變化。

    如果是內(nèi)核APC這里會直接調(diào)用APC入口函數(shù),執(zhí)行內(nèi)核APC,但是用戶APC執(zhí)行的方式不一樣。當(dāng)前的堆棧處于0環(huán),而用戶APC需要在三環(huán)執(zhí)行。

    .text:004260CA push [ebp+var_8] .text:004260CD push [ebp+var_C] .text:004260D0 push [ebp+var_10] .text:004260D3 push [ebp+var_4] .text:004260D6 push [ebp+arg_8] .text:004260D9 push [ebp+arg_4] .text:004260DC call _KiInitializeUserApc

    接著這里調(diào)用了KiInitializeUserApc函數(shù),接下來就要研究一下這個函數(shù)是如何實(shí)現(xiàn)的

    用戶APC執(zhí)行流程總結(jié):

  • 判斷用戶APC鏈表是否為空
  • 判斷第一個參數(shù)是為1,為1說明處理用戶APC和內(nèi)核APC
  • 判斷ApcState.UserApcPending(是否正在執(zhí)行用戶APC)是否為1
  • 將ApcState.UserApcPending設(shè)置為0,表示正在處理用戶APC
  • 鏈表操作 將當(dāng)前APC從用戶隊(duì)列中拆除
  • 調(diào)用函數(shù)(KAPC.KernelRoutine)釋放KAPC結(jié)構(gòu)體內(nèi)存空間
  • 調(diào)用KiInitializeUserApc函數(shù)
  • KiInitializeUserApc函數(shù)分析:備份CONTEXT

    線程進(jìn)0環(huán)時,原來的運(yùn)行環(huán)境(寄存器棧頂?shù)?保存到_Trap_Frame結(jié)構(gòu)體中,如果要提前返回3環(huán)去處理用戶APC,就必須修改_Trap_Frame結(jié)構(gòu)體,因?yàn)榇藭rTrap_Frame中存儲的EIP是從三環(huán)進(jìn)零環(huán)時保存的EIP,而不是用戶APC函數(shù)的地址

    比如:進(jìn)0環(huán)時的位置存儲在EIP中,現(xiàn)在要提前返回,而且返回的并不是原來的位置,那就意味著必須要修改EIP為新的返回位置,還有堆棧ESP也要修改為處理APC需要的堆棧。那原來的值怎么辦?處理完APC后該如何返回原來的位置呢?

    KiInitializeUserApc要做的第一件事就是備份:

    將原來_Trap_Frame的值備份到一個新的結(jié)構(gòu)體中(CONTEXT),這個功能由其子函數(shù)KeContextFromKframes來完成

    找到KiInitializeUserApc函數(shù),首先調(diào)用了KeContextFromKframes,將Trap_Frame備份到Context

    第一個參數(shù)ebx是Trap_Frame結(jié)構(gòu)體首地址,第三個參數(shù)ecx是CONTEXT結(jié)構(gòu)體首地址

    那么問題在于CONTEXT結(jié)構(gòu)體存到哪?肯定不能存到當(dāng)前函數(shù)的局部變量里。Windows想了一個辦法,把這個結(jié)構(gòu)體和APC需要的參數(shù),直接存到三環(huán)的堆棧里

    KiInitializeUserApc函數(shù)分析:堆棧圖

    .text:00429EFC mov esi, [ebp+var_224] ; 2E8-224=C4 剛好是CONTEXT結(jié)構(gòu)體ESP的偏移 .text:00429F02 and esi, 0FFFFFFFCh ; 進(jìn)行4字節(jié)對齊 .text:00429F05 sub esi, eax ; 在0環(huán)直接修改3環(huán)的棧 將用戶3環(huán)的棧減0x2DC個字節(jié)

    首先esi是CONTEXT結(jié)構(gòu)體里ESP的偏移,也就是三環(huán)的堆棧,然后進(jìn)行4字節(jié)對齊。

    接著將用戶3環(huán)的棧減0x2DC個字節(jié),此時三環(huán)的堆棧被拉伸,為什么是2DC個字節(jié)呢?

    因?yàn)镃ONTEXT結(jié)構(gòu)體的大小加上用戶APC所需要的4個參數(shù)正好是2DC個字節(jié),如下圖:

    .text:00429F16 lea edi, [esi+10h]

    此時的esi指向的是-2DC的位置,也就是上圖的NormalRoutine,+10降低堆棧,將指針指向SystemArgument2

    .text:00429F19 mov ecx, 0B3h .text:00429F1E lea esi, [ebp+var_2E8] .text:00429F24 rep movsd

    這幾行代碼將CONTEXT復(fù)制到了三環(huán)的堆棧

    .text:00429FAC push 4 .text:00429FAE pop ecx .text:00429FAF add eax, ecx .text:00429FB1 mov [ebp+var_2EC], eax .text:00429FB7 mov edx, [ebp+arg_C] .text:00429FBA mov [eax], edx .text:00429FBC add eax, ecx .text:00429FBE mov [ebp+var_2EC], eax .text:00429FC4 mov edx, [ebp+arg_10] .text:00429FC7 mov [eax], edx .text:00429FC9 add eax, ecx .text:00429FCB mov [ebp+var_2EC], eax .text:00429FD1 mov edx, [ebp+arg_14] .text:00429FD4 mov [eax], edx .text:00429FD6 add eax, ecx .text:00429FD8 mov [ebp+var_2EC], eax ; 修正3環(huán)堆棧棧頂

    接著這幾行代碼就是將APC函數(shù)執(zhí)行時需要的4個值壓入到3環(huán)的堆棧

    KiInitializeUserApc函數(shù)分析:準(zhǔn)備用戶層執(zhí)行環(huán)境

    當(dāng)KiInitializeUserApc將CONTEXT和執(zhí)行用戶APC所需要的4個值備份到3環(huán)的堆棧時,就開始準(zhǔn)備用戶層的執(zhí)行環(huán)境了

    .text:00429F2D push 23h .text:00429F2F pop eax ; eax=0x23 .text:00429F30 mov [ebx+78h], eax ; 修改Trap_Frame中的SS .text:00429F33 mov [ebx+38h], eax ; 修改Trap_Frame中的DS .text:00429F36 mov [ebx+34h], eax ; 修改Trap_Frame中的ES .text:00429F39 mov dword ptr [ebx+50h], 3Bh ; 修改Trap_Frame中的FS .text:00429F40 and dword ptr [ebx+30h], 0 ; 修改Trap_Frame中的GS

    首先修改段寄存器 SS DS FS GS

    .text:00429F78 mov [ebx+70h], eax ; 修改Trap_Frame中的EFLAGS

    接著修改EFLAGS寄存器

    .text:00429F97 mov [ebx+74h], eax ; 修改Trap_Frame中的ESP .text:00429F9A mov ecx, _KeUserApcDispatcher .text:00429FA0 mov [ebx+68h], ecx ; 修改Trap_Frame中的EIP

    然后修改ESP和EIP。這個EIP就是執(zhí)行用戶APC時返回到3環(huán)的位置。

    這個位置是固定的,是一個全局變量:KeUserApcDispatcher。這個值在系統(tǒng)啟動的時候已經(jīng)賦值好了,是3環(huán)的一個函數(shù):ntdll.KiUserApcDispatcher

    然后回到3環(huán),由KiUserApcDispatcher執(zhí)行用戶APC

    總結(jié):

  • 段寄存器 SS DS FS GS
  • 修改EFLAGS寄存器
  • 修改ESP
  • 修改EIP->ntdll.KiUserApcDispatcher
  • ntdll.KiUserApcDispatcher函數(shù)分析

    找到KiUserApcDispatcher函數(shù),結(jié)合上面的堆棧圖我們可以得知,esp+0x10的位置就是CONTEXT指針

    此時的ESP指向的是NormalRoutine,pop eax將NormalRoutine賦值給了eax,然后call eax開始處理用戶APC的總?cè)肟?/p>

    處理完用戶的APC函數(shù)之后,會調(diào)用ZwContinue,這個函數(shù)的意義在于:

  • 返回內(nèi)核,如果還有用戶APC,重復(fù)上面的執(zhí)行過程
  • 如果沒有需要執(zhí)行的用戶APC,會將CONTEXT賦值給Trap_Frame結(jié)構(gòu)體,回到0環(huán)
  • 總結(jié)

  • 內(nèi)核APC在線程切換時執(zhí)行,不需要換棧,比較簡單,一個循環(huán)執(zhí)行完畢
  • 用戶APC在系統(tǒng)調(diào)用、中斷或異常返回3環(huán)前會進(jìn)行判斷,如果有要執(zhí)行的用戶APC,再執(zhí)行。
  • 用戶APC執(zhí)行前會先執(zhí)行內(nèi)核APC
  • 總結(jié)

    以上是生活随笔為你收集整理的APC机制详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。