詳細APCメカニズム

APCの自然

スレッドは、他の人が彼をコントロールすることができるか、CPUを占有中断し、自分自身の実装でスレッドを再開殺害することはできませんか?あなたがAPIを呼び出さない場合は、極端な例を挙げれば、割り込みマスク、およびコードが異常な表示されないことを保証するために、スレッドは永続的にCPUを占有されます。あなたは、スレッドの終了をしたい場合は他の人が、スレッドの終了が存在しないのであれば、あなたは、自分自身を殺すためにコードを実行されている必要があります。

あなたがスレッドの動作を変更したいのであれば、どのように行うには?彼が呼び出すために聞かせて、彼のための機能を提供することができ、この機能は、非同期プロシージャ・コールであるAPC、あります

APCキュー

私たちが今必要なのは、議論のある私は、特定のスレッドに機能を提供する場合、この関数はどこにハングアップしますかその答えは、APCキュー、現在のスレッドの構造で最初の外観です

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

スレッド構造KTHREAD部材位置0x40のは、APCキューであるサブ構造ApcState、あります

kd> dt _KAPC_STATE
nt!_KAPC_STATE
   +0x000 ApcListHead			//2个APC队列 用户APC和内核APC 
   +0x010 Process				//线程所属进程或者所挂靠的进程
   +0x014 KernelApcInProgress	//内核APC是否正在执行
   +0x015 KernelApcPending		//是否有正在等待执行的内核APC
   +0x016 UserApcPending		//是否有正在等待执行的用户APC

_KAPC_STATEAPCキューの最初の二つのメンバーは、各メンバーが二重にリンクされたリストで、二重にリンクされたリストは、APCキューです。

キューは、カーネルモードAPC、APC機能が格納されている内部で、2 APC、APCのユーザーモードがキューであります。

あなたは、リンクリストにリンクされている機能を提供することができる特定の瞬間に、現在のスレッドが時間の関数がある場合、我々は、通話に行きます、機能の現在のリストをチェックしたときに、スレッドが特定の操作を実行します。これは、スレッドの動作を変更することと同じです。

今、私たちはあなたがスレッドの動作を変更したい場合、正確には、APCキューのスレッドにリンクされている機能を提供することが必要であることを知っていることはAPCを提供することで、APCの構造を理解するために年かかりました。

APC構造

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機能により求めることができます。

今、私たちは、フォーマットが従ってAPCを提供することを知っているだけでなく、スレッドを保存する場所が、別の問題があり、それはAPCが提供する機能を実行する現在のスレッドを

あなたはこの問題を解決したい場合は、コア機能を知っておく必要があります。KiServiceExit

APC相関関数

KiServiceExit

この関数は、システムコール例外であるか、ユーザ空間に戻るための唯一の方法を中断します

KiDeliveApc

APC機能の実装を担当

APCバックアップキュー

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

スレッド構造では0x40のAPCキューの位置で、0x170 APCの位置は、これら二つの部材のキュー構造を持っているとまったく同じです

ApcState意味

APC機能のスレッドキューがプロセスに関連している、ことを特定点:Tは、アクセス・メモリ・アドレスに、APC機能の全ての処理をスレッドが処理されています。

しかし、スレッドは他のプロセスにリンクすることができます。たとえば、プロセスAスレッドTは、CR3を変更することによって、あなたはBのプロセスのアドレス空間にアクセスすることができ、いわゆるプロセスが停泊しました。

スレッドがT B工程を固定されたときに、キューに格納されたAPCは、まだ元のAPCです。読み取るために、この時間は、読み出しアドレス空間Bのプロセスになる場合、具体的に、APC機能のようなデータ・アドレス0x12345678のを読み取るために、そのような論理は間違っています。

スレッドB Tプロセスを呼び出すときに回避の混乱に、値が一時的にその後、APCキュー回復ApcState SavedApcState、オリジナルのプロセスAにアイソクロナスバック、中に保存されます

したがって、SavedApcStateも知られているAPCバックアップキュー

つまりアンカー環境下でApcState

環境を呼び出すことではなく、この場合には、の使用はAPCがそれをキューイングすることを、キューのスレッドAPC APCに挿入することができますか?

TのTスレッドA処理アンカープロセスB、Aが所有されているプロセスは、B Tは、プロセスとリンクされています

  • ApcState:APC機能の処理に関連するB
  • SavedApcState:プロセス関連のAPC機能

通常の状況下では、現在のプロセスがあり、あなたのプロセス Aは、ケースがリンクされている場合、現在のプロセスがされ、プロセスとリンク B

APCのその他の関連するメンバー

ApcStatePointer

+0x168 ApcStatePointer  : [2] Ptr32 _KAPC_STATE

0x168 KTHREAD構造の位置に部材がApcStateへの2つのポインタ、各ポインタ点で、ポインタの配列であります

操作を容易にするために、KTHREAD構造は、ポインタアレイApcStatePointer、2の長さを規定します。

通常の状況下では:

ApcStatePointer [0] ApcStateポイント

ApcStatePointer [1]指向SavedApcState

ケースに固定:

ApcStatePointer [0]指向SavedApcState

ApcStatePointer [1] ApcStateポイント

ApcStateIndex

+0x134 ApcStateIndex    : UChar

0通常1つの傘下の状態:ApcStateIndexは、現在のスレッドを述べるものを識別するために使用されます

アドレッシングApcStatePointer ApcStateIndexとの組み合わせで

通常の状況下では、APCキュー挿入ApcState:

ApcStatePointer [0]ポイントApcState、このときApcStateIndexで0の値

ApcStatePointer [ApcStateIndex]ポイントApcState

アンカーの場合は、APCは、キューApcStateに挿入されています。

ApcStatePointer [1] ApcState、このときの値1 ApcStateIndexポイント

ApcStatePointer [ApcStateIndex]ポイントApcState

要約:

状況は、ApcStatePointer [ApcStateIndex]ポイントはApcState、ApcStateは常にAPC使用するスレッドの現在の状態を表しているものに関係なく

ApcQueueable

+0x0b8 ApcQueueable     : Pos 5, 1 Bit

APC APCは、キュースレッド挿入することができるかどうかを示すApcQueueable。

コードは、スレッドが終了を実行している場合、この値は0に設定され、それが0である場合、この時点で挿入APCコード場合、挿入機能は、状態の値を決定し、挿入は失敗。

APCは、プロセスにリンク

それが正常な状態か、アンカー状態であるかどうか、それは2つのAPCキュー、キューのカーネル、ユーザーキューを持っている必要があります。APC機能が係合するたびに関係なく、ユーザまたはカーネルキューキューの、カーネルはKAPCデータ構造準備する必要があり、この構造KAPC APCは、対応するキューにリンクされています。

KAPC構造

kd> dt _KAPC
nt!_KAPC
   +0x000 Type		//类型  APC类型为0x12
   +0x002 Size		//本结构体的大小  0x30
   +0x004 Spare0    	//未使用                             
   +0x008 Thread 		//目标线程                                  
   +0x00c ApcListEntry	//APC队列挂的位置
   +0x014 KernelRoutine	//指向一个函数(调用ExFreePoolWithTag 释放APC)
   +0x018 RundownRoutine//略 
   +0x01c NormalRoutine	//用户APC总入口  或者 真正的内核apc函数
   +0x020 NormalContext	//内核APC:NULL  用户APC:真正的APC函数
   +0x024 SystemArgument1//APC函数的参数	
   +0x028 SystemArgument2//APC函数的参数
   +0x02c ApcStateIndex	//挂哪个队列,有四个值:0 1 2 3
   +0x02d ApcMode	//内核APC 用户APC
   +0x02e Inserted	//表示本apc是否已挂入队列 挂入前:0  挂入后  1
  • タイプ:タイプを。Windowsでは、任意のカーネルオブジェクトは、番号を持って、この数は、あなたが属するタイプを識別するために使用され、APC自体がカーネルオブジェクトである、それはまた、番号を持っているの0x12であります
  • サイズ:これはKAPCの構造の現在のメンバーのサイズを参照
  • スレッド:各スレッドは、独自のAPCキューを持ち、このメンバーは、スレッドに属してAPCを指定します
  • ApcListEntry:APCキュー吊り位置、二重にリンクされたリストは、次のAPC二重リンクリストで見つけることができています
  • KernelRoutine:関数(コールExFreePoolWithTagリリースAPC)を指します。我々はAPCを終了したら、このメモリは、リリースにKernelRoutine現在KAPC自体で指定された機能します
  • NormalRoutine:値FOUNDによって現在のカーネルAPCは、実際のカーネルAPC機能である場合に、現在のAPCユーザAPC場合、ユーザAPC総エントリこの位置点は、総入口によってすべてのユーザーにAPCを見出すことができます機能
  • NormalContext:この値によって、現在のカーネルAPCは、空の場合、現在のユーザーは、APC APCであれば、実際のユーザーのAPC機能には、この値のポイント
  • SystemArgument1 SystemArgument2 APC関数パラメータ
  • ApcStateIndex:ハングアップすると、現在のAPCキュー
  • ApcMode:現在のAPCのユーザーまたはカーネルAPC APC
  • 挿入:APC APCの現在の構造は、キューに挿入されています

プロセス従事

ここに画像を挿入説明

Keinitialiseapc

VOID KeInitializeApc
(
	IN PKAPC Apc,//KAPC指针
	IN PKTHREAD Thread,//目标线程
	IN KAPC_ENVIRONMENT TargetEnvironment,//0 1 2 3四种状态
	IN PKKERNEL_ROUTINE KernelRoutine,//销毁KAPC的函数地址
	IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,
	IN PKNORMAL_ROUTINE NormalRoutine,//用户APC总入口或者内核apc函数
	IN KPROCESSOR_MODE Mode,//要插入用户apc队列还是内核apc队列
	IN PVOID Context//内核APC:NULL  用户APC:真正的APC函数
) 

KeInitializeApc機能の役割は、現在の構造の割り当てを与えることですKAPC

ApcStateIndex

そして、同じ名前KTHREAD(+ 0x134)ではなく、同じ意味のプロパティ:

ApcStateIndexは、4つの値があります。

  • 0元の環境では - >挿入プロセスは、現在のスレッドに属しているにかかわらず、プロセスに挿入されているアンカーかどうかの、現在のスレッドのAPCキューに属します。
  • 1つのアンカー環境
  • リンクされている場合、親プロセスは、現在のプロセスは、プロセスとリンクされている> APCキューの現在のプロセスの中に、固定されていない場合、現在のプロセスがされる - 2現在の環境
  • 3現在の環境APC挿入 - 挿入APCは、現在のスレッドは、次にAPC挿入固定状態であるか否かを判断する前に、スイッチング状態の任意の時点3の値に>スレッド

KiInsertQueueApc

  1. KAPC ApcStateIndex APCキューに基づいて対応する構造を探します
  2. 構造はKAPC ApcModeは、ユーザまたはカーネルキューキューを決定し、その後で
  3. KAPCはKAPCでハングApcListEntryの対応するキューにリンクされています
  4. 構造は、現在の状態がKAPCとして挿入されている識別し、次いでKAPC挿入されたセットであります
  5. KAPC_STATE修正構造KernelApcPending / UserApcPending

APCのカーネル実行

挿入およびAPC機能の実装は、同じスレッド、特定のポイントことはありません。

スレッドは、アクションがスレッドで行われる挿入、APCにスレッドBに挿入されるが、スレッドBの決定によって実行されたとき。だから、非同期プロシージャ・コールと呼ばれます。

実行と実装時間カーネルAPC APC機能およびユーザー機能が異なります。私たちは、カーネルAPCの実装プロセスを理解するために開始します

実行のポイント:スレッド切り替え

IDAオープンntkrnlpaは、SwapContext機能を見つけます

ここに画像を挿入説明

この完了したときに実行される約関数、現在のカーネルAPCを実行するかどうかを決定する、EAXを維持する決意のその結果、その後リターン

ここに画像を挿入説明

次に、関数KiSwapContext機能をフォローアップしていき層を見つけます

ここに画像を挿入説明

この関数は、処理のためのAPCはありませんが、戻って、親機能に従うことを継続していき
ここに画像を挿入説明

ここに戻り、現在のカーネルAPCが処理されるかどうかを判断するために、ある値KiSwapContextを判断するために戻ります、そうであれば、KiDeliverApc処理を呼び出します。

这个函数有三个参数,第一个参数如果是0,就意味着KiDeliverApc在执行的时候只会处理内核APC,第一个参数如果是1,KiDeliverApc除了处理内核APC以外,还会处理用户APC

流程总结:

  1. SwapContext 判断是否有内核APC
  2. KiSwapThread 切换线程
  3. KiDeliverApc

执行点:系统调用 中断或者异常(_KiServiceExit)

ここに画像を挿入説明

找到_KiServiceExit函数,这里会判断是否有要执行的用户APC,如果有的话则会调用KiDeliverApc函数进行处理,此时KiDeliverApc第一个参数为1,代表执行用户APC和内核APC。

当要执行用户APC之前,先要执行内核APC

KiDeliverApc函数分析

无论是执行内核APC还是执行用户APC都会调用KiDeliverApc函数,接下来分析KiDeliverApc函数主要做了什么事情
ここに画像を挿入説明

首先这里会取出内核APC列表,然后执行跳转

ここに画像を挿入説明

接着判断第一个链表是否为空(内核APC队列),如果不为空则跳转

ここに画像を挿入説明

跳转以后,首先得到KACP的首地址,然后取出KACP结构体的各个参数,放到局部变量里

ここに画像を挿入説明
在这里,因为我们要处理的是内核APC,所以NormalRoutine代表是内核APC函数地址,这里会判断内核APC函数地址是否为空,不为空的话则进行跳转

ここに画像を挿入説明

跳转以后,先判断是否有正在执行内核APC,然后判断是否禁用内核APC,接着将APC从内核队列中摘除。

接着先调用KAPC.KernelRoutine指定的函数 释放KAPC结构体占用的空间

ここに画像を挿入説明

然后将ApcState.KernelApcInProgress 设置为1 标识正在执行内核APC。

接着将三个参数压入栈里,开始执行真正的内核APC函数

执行完毕以后,将ApcState.KernelApcInProgress 置0,接着再次判断内核APC队列,开始下一轮循环

内核APC执行流程总结:

  1. 判断第一个链表(内核APC队列)是否为空
  2. 判断KTHREAD.ApcState.KernelApcInProgress(是否正在执行内核APC)是否为1
  3. 判断是否禁用内核APC(KTHREAD.KernelApcDisable是否为1)
  4. 将当前KAPC结构体从链表中摘除
  5. 执行KAPC.KernelRoutine指定的函数 释放KAPC结构体占用的空间
  6. 将KTHREAD.ApcState.KernelApcInProgress设置为1 标识正在执行内核APC
  7. 执行真正的内核APC函数(KAPC.NormalRoutine)
  8. 执行完毕 将KernelApcInProgress改为0

总结

  1. 内核APC在线程切换的时候就会执行,这也就意味着,只要插入内核APC很快就会被执行
  2. 在执行用户APC之前会先执行内核APC
  3. 内核APC在内核空间执行,不需要换栈,一个循环全部执行完毕

用户APC的执行过程

当产生系统调用 中断或者异常,线程在返回用户空间前都会调用_KiServiceExit函数,在_KiServiceExit函数里会判断是否有要执行的用户APC,如果有则调用KiDeliverApc函数进行处理

执行用户APC时的堆栈操作

处理用户APC要比处理内核APC复杂的多,因为用户APC函数要在用户空间执行,这里涉及到大量的换栈操作:

当线程从用户层进入内核层时,要保留原来的运行环境,比如各种寄存器 栈的位置等等,然后切换成内核的堆栈,如果正常返回,恢复堆栈环境即可

但如果有用户APC要执行的话,就意味着线程要提前返回到用户空间去执行,而且返回的位置不是线程进入内核时的位置,而是返回到真正执行APC的位置

每处理一个用户APC就会涉及到:内核—>用户空间—>再回到内核空间

执行用户APC最为关键的就是理解堆栈操作的细节

KiDeliverApc函数分析

ここに画像を挿入説明

KiDeliverApc函数会push三个参数,第一个参数如果为0,代表只处理内核APC,如果为1,代表处理用户APC和内核APC。

也就是说内核APC是无论如何都会执行的。

ここに画像を挿入説明

取出内核APC队列之后会再次取出用户APC队列,并判断用户APC队列是否为空

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

接着判断KiDeliverApc第一个参数是否为1 如果不是1 说明不处理用户APC,直接返回

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

+0x4A=UserApcPending 表示是否正在执行用户APC,为0说明正在执行的用户APC,继续往下走

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

先将UserApcPending置0,表示当前正在执行用户APC

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

-0xC 得到KPCR首地址

.text:00426076                 mov     ecx, [edi+1Ch]  ; +0x1C=NormalRoutine 用户APC总入口
.text:00426079                 mov     ebx, [edi+14h]  ; +0x14=KernelRoutine 释放APC的函数
.text:0042607C                 mov     [ebp+var_4], ecx
.text:0042607F                 mov     ecx, [edi+20h]  ; +0x20 NormalContext  用户APC:真正的APC函数
.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结构体的成员,放到局部变量里保存

.text:00426091                 mov     ecx, [eax]      ; --------------------------
.text:00426093                 mov     eax, [eax+4]
.text:00426096                 mov     [eax], ecx      ; 链表操作 将用户APC从链表中移除
.text:00426098                 mov     [ecx+4], eax    ; --------------------------

然后将当前的用户APC从链表中摘除

.text:004260B7                 push    eax
.text:004260B8                 push    edi
.text:004260B9                 call    ebx             ; 调用KAPC.KernelRoutine 释放KAPC结构体内存

接着调用调用KAPC.KernelRoutine指定的函数, 释放KAPC结构体内存

到这里为止,用户APC和内核APC的处理方式就发生了变化。

如果是内核APC这里会直接调用APC入口函数,执行内核APC,但是用户APC执行的方式不一样。当前的堆栈处于0环,而用户APC需要在三环执行。

.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

接着这里调用了KiInitializeUserApc函数,接下来就要研究一下这个函数是如何实现的

用户APC执行流程总结:

  1. 判断用户APC链表是否为空
  2. 判断第一个参数是为1,为1说明处理用户APC和内核APC
  3. 判断ApcState.UserApcPending(是否正在执行用户APC)是否为1
  4. 将ApcState.UserApcPending设置为0,表示正在处理用户APC
  5. 链表操作 将当前APC从用户队列中拆除
  6. 调用函数(KAPC.KernelRoutine)释放KAPC结构体内存空间
  7. 调用KiInitializeUserApc函数

KiInitializeUserApc函数分析:备份CONTEXT

线程进0环时,原来的运行环境(寄存器栈顶等)保存到_Trap_Frame结构体中,如果要提前返回3环去处理用户APC,就必须修改_Trap_Frame结构体,因为此时Trap_Frame中存储的EIP是从三环进零环时保存的EIP,而不是用户APC函数的地址

比如:进0环时的位置存储在EIP中,现在要提前返回,而且返回的并不是原来的位置,那就意味着必须要修改EIP为新的返回位置,还有堆栈ESP也要修改为处理APC需要的堆栈。那原来的值怎么办?处理完APC后该如何返回原来的位置呢?

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

将原来_Trap_Frame的值备份到一个新的结构体中(CONTEXT),这个功能由其子函数KeContextFromKframes来完成

ここに画像を挿入説明

找到KiInitializeUserApc函数,首先调用了KeContextFromKframes,将Trap_Frame备份到Context

第一个参数ebx是Trap_Frame结构体首地址,第三个参数ecx是CONTEXT结构体首地址

那么问题在于CONTEXT结构体存到哪?肯定不能存到当前函数的局部变量里。Windows想了一个办法,把这个结构体和APC需要的参数,直接存到三环的堆栈里

KiInitializeUserApc函数分析:堆栈图

.text:00429EFC                 mov     esi, [ebp+var_224] ; 2E8-224=C4 刚好是CONTEXT结构体ESP的偏移
.text:00429F02                 and     esi, 0FFFFFFFCh ; 进行4字节对齐
.text:00429F05                 sub     esi, eax        ; 在0环直接修改3环的栈 将用户3环的栈减0x2DC个字节

首先esi是CONTEXT结构体里ESP的偏移,也就是三环的堆栈,然后进行4字节对齐。

接着将用户3环的栈减0x2DC个字节,此时三环的堆栈被拉伸,为什么是2DC个字节呢?

因为CONTEXT结构体的大小加上用户APC所需要的4个参数正好是2DC个字节,如下图:

ここに画像を挿入説明

.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复制到了三环的堆栈

.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环堆栈栈顶

接着这几行代码就是将APC函数执行时需要的4个值压入到3环的堆栈

KiInitializeUserApc函数分析:准备用户层执行环境

当KiInitializeUserApc将CONTEXT和执行用户APC所需要的4个值备份到3环的堆栈时,就开始准备用户层的执行环境了

ここに画像を挿入説明

.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就是执行用户APC时返回到3环的位置。

这个位置是固定的,是一个全局变量:KeUserApcDispatcher。这个值在系统启动的时候已经赋值好了,是3环的一个函数:ntdll.KiUserApcDispatcher

然后回到3环,由KiUserApcDispatcher执行用户APC

总结:

  1. 段寄存器 SS DS FS GS
  2. 修改EFLAGS寄存器
  3. 修改ESP
  4. 修改EIP->ntdll.KiUserApcDispatcher

ntdll.KiUserApcDispatcher函数分析

ここに画像を挿入説明

找到KiUserApcDispatcher函数,结合上面的堆栈图我们可以得知,esp+0x10的位置就是CONTEXT指针

此时的ESP指向的是NormalRoutine,pop eax将NormalRoutine赋值给了eax,然后call eax开始处理用户APC的总入口

处理完用户的APC函数之后,会调用ZwContinue,这个函数的意义在于:

  1. 返回内核,如果还有用户APC,重复上面的执行过程
  2. 如果没有需要执行的用户APC,会将CONTEXT赋值给Trap_Frame结构体,回到0环

总结

  1. 内核APC在线程切换时执行,不需要换栈,比较简单,一个循环执行完毕
  2. 用户APC在系统调用、中断或异常返回3环前会进行判断,如果有要执行的用户APC,再执行。
  3. 用户APC执行前会先执行内核APC
发布了99 篇原创文章 · 获赞 89 · 访问量 7万+

おすすめ

転載: blog.csdn.net/qq_38474570/article/details/104326170