APC插入过程分析
APC不论从3环插入还是从0环插入都会调用内核层函数:
QueueUserAPC(Kernel32.dll) ↓
↓ → 用户层调用
NtQueueApcThread(ntosker.exe) ↑
↓
KeInitializeApc(分配空间,初始化KAPC结构体) ↓
↓ ↓
KeInsertQueueApc → 很多内核函数调用
↓ ↑
KiInsertQueueApc(将KAPC插入指定APC队列) ↑
APC函数的执行与插入并不是同一个线程,即在A线程中向B线程插入一个APC,插入的动作是在A线程完成的,但什么时候执行则是由B线程决定的,所以叫“一部过程调用”。
NtQueueApcThread(ntosker.exe)
NTSTATUS __stdcall NtQueueApcThread(HANDLE ThreadHandle, PKNORMAL_ROUTINE ApcRoutine, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
_NtQueueApcThread@20 proc near ; DATA XREF: .text:0040B978↑o
AccessMode = byte ptr -4
ThreadHandle = dword ptr 8
ApcRoutine = dword ptr 0Ch
NormalContext = dword ptr 10h
SystemArgument1 = dword ptr 14h
SystemArgument2 = dword ptr 18h
; FUNCTION CHUNK AT PAGE:00523983 SIZE 00000025 BYTES
//初始化堆栈
mov edi, edi
push ebp
mov ebp, esp
push ecx
push ebx
push esi
mov eax, large fs:124h //_KPCR.CurrentThread 获取当前线程的_KTHREAD
mov al, [eax+140h] //获取先前模式
mov [ebp+AccessMode], al //给参数赋值
xor esi, esi //清零esi
push esi //参数
lea eax, [ebp+ThreadHandle] //要插入APC线程句柄
push eax
push dword ptr [ebp+AccessMode] //传参
push _PsThreadType
push 10h
push [ebp+ThreadHandle]
call _ObReferenceObjectByHandle@24 ; ObReferenceObjectByHandle(x,x,x,x,x,x) //根据提供的 Handle 值得到 Object!
mov ebx, eax //返回值保存到eax,解析返回一个 _KTHREAD对象
cmp ebx, esi
jl short loc_4BA123 //判断 _KTHREAD对象 是否为空
mov eax, [ebp+ThreadHandle] //获取线程句柄
xor ebx, ebx //清空ebx
test byte ptr [eax+248h], 10h // eax + 248 = _ETHREAD.ThreadListEntry.CrossThreadFlags
jnz loc_523983 //判断 _ETHREAD.ThreadListEntry.CrossThreadFlags 标志位是否为 10H
push edi
push 70617350h ; Tag
push 30h //分配的空间大小,_KAPC 大小为30h
push 8 ; PoolType
call _ExAllocatePoolWithQuotaTag@12 ; ExAllocatePoolWithQuotaTag(x,x,x) //函数分配了一个_KAPC结构体大小
mov edi, eax //函数返回一个指向已分配池的指针,即_KAPC结构体指针。
cmp edi, esi
jz loc_52398D //判断指针是否为空
push [ebp+NormalContext] //内核APC: NULL, 用户APC:真正的APC函数
push 1
push [ebp+ApcRoutine] //先前模式
push esi //0
push offset _IopDeallocateApc@20 ; IopDeallocateApc(x,x,x,x,x) //获取释放APC函数地址
push esi //0
push [ebp+ThreadHandle] //要插入线程句柄
push edi //指向已分配池的指针
call _KeInitializeApc@32 ; KeInitializeApc(x,x,x,x,x,x,x,x)
push esi //0
push [ebp+SystemArgument2] //APC函数的参数
push [ebp+SystemArgument1] //APC函数的参数
push edi //初始化好的_KAPC 结构体
call _KeInsertQueueApc@16 ; KeInsertQueueApc(x,x,x,x)
test al, al
jz loc_523997
loc_4BA11A: ; CODE XREF: NtQueueApcThread(x,x,x,x,x)+69907↓j
; NtQueueApcThread(x,x,x,x,x)+69918↓j
pop edi
loc_4BA11B: ; CODE XREF: NtQueueApcThread(x,x,x,x,x)+698FD↓j
mov ecx, [ebp+ThreadHandle] ; Object
call @ObfDereferenceObject@4 ; ObfDereferenceObject(x)
loc_4BA123: ; CODE XREF: NtQueueApcThread(x,x,x,x,x)+35↑j
pop esi
mov eax, ebx
pop ebx
leave
retn 14h
_NtQueueApcThread@20 endp
KeInitializeApc(ntosker.exe)–初始化_KAPC结构体
__stdcall KeInitializeApc(x, x, x, x, x, x, x, x)
public _KeInitializeApc@32
_KeInitializeApc@32 proc near ; CODE XREF: NtSetTimer(x,x,x,x,x,x,x)+10D↓p
; IopCompleteRequest(x,x,x,x,x)+277↓p ...
arg_0 = dword ptr 8
arg_4 = dword ptr 0Ch
arg_8 = dword ptr 10h
arg_C = dword ptr 14h
arg_10 = dword ptr 18h
arg_14 = dword ptr 1Ch
arg_18 = byte ptr 20h
arg_1C = dword ptr 24h
FUNCTION CHUNK AT .text:0040DE44 SIZE 0000000B BYTES
FUNCTION CHUNK AT .text:0040E465 SIZE 00000011 BYTES
mov edi, edi
push ebp
mov ebp, esp //初始化堆栈
mov eax, [ebp+arg_0] //获取第一个参数 : 指向已分配池的指针,_KAPC结构体指针
mov edx, [ebp+arg_8] //获取第三个参数 : 0
cmp edx, 2
mov ecx, [ebp+arg_4] //获取第二个参数 : //要插入线程句柄
mov word ptr [eax], 12h //设置_KAPC.Type = 12h, APC默认类型为 12h
mov word ptr [eax+2], 30h //设置结构体大小 _KAPC.Size = 30h
jz loc_40DE44
loc_40E2A9: ; CODE XREF: KeInitializeApc(x,x,x,x,x,x,x,x)-43D↑j
mov [eax+8], ecx //保存要插入线程句柄
mov ecx, [ebp+arg_C] //获取第四的参数:释放APC函数地址
mov [eax+14h], ecx //设置释放APC的函数地址: _KAPC.KernelRoutine
mov ecx, [ebp+arg_10] //获取第五的参数:0
mov [eax+2Ch], dl //设置挂在哪个队列 _KAPC.ApcStateIndex = 0
mov [eax+18h], ecx //_KAPC.RundownRoutine = 0
mov ecx, [ebp+arg_14] //获取参数先前模式
xor edx, edx //清零 edx
cmp ecx, edx //判断先前是否是在0环
mov [eax+1Ch], ecx
jnz loc_40E465 //先前模式 != 0,则运行在3环跳转
//先前运行环境为内核时
mov [eax+2Dh], dl //_KAPC.ApcMode = 0, 标志当前是内核APC为1, 用户为0
mov [eax+20h], edx //设置 _KAPC.NormalContext = 0 内核APC: NULL, 用户APC:真正的APC函数
loc_40E2D1: ; CODE XREF: KeInitializeApc(x,x,x,x,x,x,x,x)+1EA↓j
mov [eax+2Eh], dl //_KAPC.Inserted = 0, 表示当前APC是否已经挂入队列。挂入前:0
pop ebp
retn 20h //返回,并平衡堆栈
_KeInitializeApc@32 endp
//先前运行环境为用户时
loc_40E465: ; CODE XREF: KeInitializeApc(x,x,x,x,x,x,x,x)+3E↑j
mov cl, [ebp+arg_18] //获取参数1
mov [eax+2Dh], cl //_KAPC.ApcMode = 1, 标志当前是内核APC为1, 用户为0
mov ecx, [ebp+arg_1C] //获取参数 NormalContext
mov [eax+20h], ecx //设置 _KAPC.NormalContext的值为真正的APC函数 内核APC: NULL, 用户APC:真正的APC函数
jmp loc_40E2D1 //返回
KeInsertQueueApc
; __stdcall KeInsertQueueApc(x, x, x, x)
public _KeInsertQueueApc@16
_KeInsertQueueApc@16 proc near ; CODE XREF: ExpTimerDpcRoutine(x,x,x,x)+26↓p
; IopCompleteRequest(x,x,x,x,x)+21E↓p ...
LockHandle = _KLOCK_QUEUE_HANDLE ptr -0Ch
arg_0 = dword ptr 8
arg_4 = dword ptr 0Ch
arg_8 = dword ptr 10h
arg_C = dword ptr 14h
mov edi, edi
push ebp
mov ebp, esp
sub esp, 0Ch
push ebx
push esi
push edi
// 初始化堆栈
mov edi, [ebp+arg_0] //初始化好的_KAPC 结构体指针
mov esi, [edi+8] //_KAPC + 8 = _KAPC.Thread 目标线程
lea ecx, [esi+0E8h] //esi + 0E8 = _KTHREAD + 0E8 = _KTHREAD.ApcQueueLock APC队列锁
lea edx, [ebp+LockHandle]
call ds:__imp_@KeAcquireInStackQueuedSpinLockRaiseToSynch@8 ; KeAcquireInStackQueuedSpinLockRaiseToSynch(x,x)
xor bl, bl //清零 bl
cmp [esi+166h], bl //esi+166h = _KTHREAD.ApcQueueable,用于表示是否可以向线程的APC队列中插入APC,0则不能插入
jz short loc_40E453
mov eax, [ebp+arg_4] //第二个参数,APC函数的参数 SystemArgument1
mov edx, [ebp+arg_C] //第四个参数,0
mov [edi+24h], eax //_KAPC.SystemArgument1 = eax = SystemArgument1
mov eax, [ebp+arg_8] //第三个参数,APC函数的参数 SystemArgument2
mov ecx, edi //ecx = _KAPC 结构体指针
mov [edi+28h], eax //_KAPC.SystemArgument2 = eax = SystemArgument2
call @KiInsertQueueApc@8 ; KiInsertQueueApc(x,x)
mov bl, al //al 返回是否成功
loc_40E453: ; CODE XREF: KeInsertQueueApc(x,x,x,x)+28↑j
lea ecx, [ebp+LockHandle] ; LockHandle
call ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x) //释放锁
pop edi
pop esi
mov al, bl
pop ebx
leave
retn 10h
_KeInsertQueueApc@16 endp
KiInsertQueueApc
KiInsertQueueApc函数说明:
1)根据KAPC结构中的ApcStateIndex找到对应的APC队列
2)再根据KAPC结构中的ApcMode确定是用户队列还是内核队列
3)将KAPC挂到对应的队列中(挂到KAPC的ApcListEntry)
4)再根据KAPC结构中的Inserted置1,标识当前的KAPC为已插入状态
修改KAPC_STATE(_KTHREAD)结构中的KernelApcPending/UserApcPending :
若插入用户的:
6)若UserApcPending = 1, 则说明当前线程正在等待状态,并且可以被APC唤醒,调用KiUnwaitThread函数唤醒线程。
6)若UserApcPending != 1, 则说明当前线程不能被唤醒或者未在等待状态等,则直接返回成功。
若插入内核的:
7)KernelApcPending = 1
8)若线程处在就绪状态则结束。
9)线程不在就绪状态,则判断是否在等待状态,若在等待状态并且 WaitIrql = 0,则调用KiUnwaitThread函数唤醒线程。
注:当线程处在由用户引起的等待状态下,并且线程可以被唤醒会将 UserApcPending = 1,否则为0,但是APC是插入成功了。若UserApcPending = 0时当前APC并不会立即被执行只能等待其他插入的APC使UserApcPending = 1时,就可一并执行。
KiUnwaitThread函数:将当前线程从等待链表中挂到就绪链表。
KiInsertQueueApc:
; __fastcall KiInsertQueueApc(x, x)
@KiInsertQueueApc@8 proc near ; CODE XREF: KeInsertQueueApc(x,x,x,x)+3B↓p
; KeSuspendThread(x)+57↓p ...
var_4 = dword ptr -4
mov edi, edi
push ebp
mov ebp, esp
push ecx
mov eax, ecx //ecx = eax = _KAPC结构体指针
cmp byte ptr [eax+2Eh], 0 //eax+2Eh = _KAPC.Inserted,表示当前APC是否已经挂入队列。挂入前:0,挂入后 1
mov ecx, [eax+8] //ecx = _KAPC.Thread = 目标线程结构体
mov [ebp+var_4], edx //将edx = 0,保存到临时变量
jnz loc_446A65 // _KAPC.Inserted != 0,则跳转,即已经挂入队列了,直接返回
cmp byte ptr [eax+2Ch], 3 //_KAPC.ApcStateIndex 判断将_KAPC挂到那个队列
jz loc_446A69 //等于3时跳转, 3为:插入的时候,当前进程ApcState。 中途ApcState的值可能会发生变化
//初始化的时候,当前进程的ApcState:
loc_40E39B: ; CODE XREF: KiInsertQueueApc(x,x)+386F9↓j
cmp dword ptr [eax+1Ch], 0 //判断_KAPC.NormalRoutine 是否为空
movsx edx, byte ptr [eax+2Ch] //eax+2Ch = _KAPC.ApcStateIndex
push ebx
push esi
push edi //保存值
mov edi, [ecx+edx*4+138h] //_KTHREAD.ApcStatePointer + 4 * edx = _KTHREAD.ApcStatePointer[ApcStateIndex] = ApcState
//ApcState则总是表示线程当前使用的apc状态。
mov dl, [eax+2Dh] //获取_KAPC.ApcMode的值,用来判断是内核APC还是用户APC
jnz loc_40F034 //_KAPC.NormalRoutine != 0 则跳转,进行APC插入
movsx esi, dl //esi = dl = _KAPC.ApcMode
lea edi, [edi+esi*8] //ApcState + esi * 8 = ApcState.ApcListHead[0或1],获取APC队列地址
mov esi, [edi+4] //ApcState.ApcListHead[0或1].Blink
loc_40E3BF: ; CODE XREF: KiInsertQueueApc(x,x)+1D554↓j
cmp esi, edi
jnz loc_42B8C0
loc_40E3C7: ; CODE XREF: KiInsertQueueApc(x,x)+1D54B↓j
//挂入APC队列
mov ebx, [esi]
lea edi, [eax+0Ch]
mov [edi], ebx
mov [edi+4], esi
mov [ebx+4], edi
mov [esi], edi
loc_40E3D6: ; CODE XREF: KiInsertQueueApc(x,x)+CE2↓j
; KiInsertQueueApc(x,x)+185D0↓j
movsx edi, byte ptr [eax+2Ch] //获取 eax+2Ch = _KAPC.ApcStateIndex,挂入那个队列(即apc插入状态)
mov byte ptr [eax+2Eh], 1 //eax+2Eh = _KAPC.Inserted = 1 设置apc为已经挂入队列
movzx esi, byte ptr [ecx+165h] //ecx+165h = _KTHREAD.ApcStateIndex,获取当前线程处于正常状态还是挂靠状态
cmp edi, esi //cmp edi= _KAPC.ApcStateIndex(挂入哪个对列用户、内核), _KTHREAD.ApcStateIndex(挂入哪个ApcState,正常、挂靠)
pop edi
pop esi
pop ebx //保存寄存器
jnz short loc_40E408 //不相等则跳转,返回成功
test dl, dl
jnz loc_40F060 //dl != 0则跳转,dl = _KAPC.ApcMode = 1 ,为用户APC
//内核
mov dl, [ecx+2Dh] //_KTHREAD.State
cmp dl, 2 //判断是否为 _KTHREAD.State 是否为 2 就绪状态
mov byte ptr [ecx+49h], 1 //_KTHREAD._KAPC_STATE.KernelApcPending = 1
jnz short loc_40E476 //_KTHREAD.State != 2,
mov cl, 1
call ds:__imp_@HalRequestSoftwareInterrupt@4 ; HalRequestSoftwareInterrupt(x)
//成功返回
loc_40E408: ; CODE XREF: KiInsertQueueApc(x,x)+71↑j
; KiInsertQueueApc(x,x)+100↓j ...
mov al, 1
leave
retn
@KiInsertQueueApc@8 endp
//返回失败
loc_446A65: ; CODE XREF: KiInsertQueueApc(x,x)+12↑j
xor al, al
leave
retn
//插入的时候,当前线程的ApcState。 中途ApcState的值可能会发生变化
loc_446A69: ; CODE XREF: KiInsertQueueApc(x,x)+1C↑j
mov dl, [ecx+165h] //ecx+165h = _KTHREAD.ApcStateIndex 的值,获取目标线程状态
mov [eax+2Ch], dl //设置APC状态,eax+2Ch = _KAPC.ApcStateIndex = _KTHREAD.ApcStateIndex,
//再将值设为用于表示是内核APC还是用户APC
jmp loc_40E39B
loc_40F034: ; CODE XREF: KiInsertQueueApc(x,x)+37↑j
test dl, dl
jz short loc_40F045 //dl = 0则跳转,dl = _KAPC.ApcMode = 0 ,为内核APC
//用户APC
cmp dword ptr [eax+14h], offset _PsExitSpecialApc@20 ; PsExitSpecialApc(x,x,x,x,x) //_KAPC.KernelRoutine 判断是否存在
jz loc_426930 //_KAPC.KernelRoutine 已经赋值过了则跳转,去插入APC
//否则则继续执行:loc_40F045,插入到内核队列
//将初始化好的APC结构体插入到内核队列
loc_40F045: ; CODE XREF: KiInsertQueueApc(x,x)+CBD↑j
movsx ebx, dl //ebx = dl = _KAPC.ApcMode = 0
lea edi, [edi+ebx*8] //ApcState + 0 * 8 = ApcState.ApcListHead[0],获取内核APC队列地址
mov ebx, [edi+4] //ApcState.ApcListHead.Blink
lea esi, [eax+0Ch] //获取_KAPC.ApcListEntry(初始化的KAPC)地址
//将初始化好的APC插入到指定线程内核Apc队列中最前面:
mov [esi], edi //_KAPC.ApcListEntry.Flink = _KAPC.ApcListEntry.Flink赋值为APC链表开始节点
mov [esi+4], ebx //_KAPC.ApcListEntry.Blink赋值为APC链表之前的第一个节点
mov [ebx], esi // ApcState.ApcListHead.Blink(新节点) = _KAPC.ApcListEntry.Flink(原APC队列第一个节点)
//将原本第一个节点放到新插入节点的后面
mov [edi+4], esi //将头节点的下个节点地址赋值为,新插入节点的地址
jmp loc_40E3D6
//将初始化好的APC结构体插入到用户队列
loc_426930: ; CODE XREF: KiInsertQueueApc(x,x)+CC6↑j
movsx ebx, dl //ebx = dl = _KAPC.ApcMode = 1
mov byte ptr [ecx+4Ah], 1 //ecx+4Ah = _KTHREAD.ApcState.UserApcPending 设置为1:存在未执行的APC函数
lea edi, [edi+ebx*8] //ApcState + 1 * 8 = ApcState.ApcListHead[1],获取用户APC队列地址
mov ebx, [edi]
lea esi, [eax+0Ch] //获取_KAPC.ApcListEntry(初始化的KAPC)地址
//将初始化好的APC插入到指定线程用户Apc队列中最前面:
mov [esi], ebx
mov [esi+4], edi
mov [ebx+4], esi
mov [edi], esi
jmp loc_40E3D6
loc_40F060: ; CODE XREF: KiInsertQueueApc(x,x)+75↑j
cmp byte ptr [ecx+2Dh], 5 //_KTHREAD.State 5 , 判断线程是否为等待状态
jnz loc_40E408 //_KTHREAD.State != 5
cmp byte ptr [ecx+59h], 1 //_KTHREAD.WaitMode //判断线程是否是用户引起的等待
jnz loc_40E408 //_KTHREAD.WaitMode != 1
cmp byte ptr [ecx+164h], 0 //_KTHREAD.Alertable //判断线程是否可以被吵醒
jz loc_430284 //_KTHREAD.Alertable != 0
//继续走 loc_40F081
loc_40F081: ; CODE XREF: KiInsertQueueApc(x,x)+21F15↓j
mov byte ptr [ecx+4Ah], 1 //_KTHREAD._KAPC_STATE.UserApcPending = 1, 存在可以执行的用户函数
push 0
mov edx, 0C0h
jmp loc_40E492
loc_430284: ; CODE XREF: KiInsertQueueApc(x,x)+D02↑j
cmp byte ptr [ecx+4Ah], 0 //_KTHREAD._KAPC_STATE.UserApcPending, 没有可以执行的用户函数
jz loc_40E408 //没有可以执行的用户函数, 成功返回
jmp loc_40F081
loc_40E492: ; CODE XREF: KiInsertQueueApc(x,x)+D13↓j
push [ebp + var_4] //[ebp + var_4] = 0
call @KiUnwaitThread@16 ; KiUnwaitThread(x,x,x,x) //唤醒线程函数
jmp loc_40E408 //返回成功
//内核会调
loc_40E476: ; CODE XREF: KiInsertQueueApc(x,x)+85↑j
cmp dl, 5 //_KTHREAD.State 5 判断线程是否为等待状态
jnz short loc_40E408 //_KTHREAD.State != 5 返回成功
cmp byte ptr [ecx+58h], 0 //_KTHREAD.WaitIrql
jnz short loc_40E408 //_KTHREAD.WaitIrql != 0 返回成功
xor edx, edx //清零 edx
cmp [eax+1Ch], edx //_KTHREAD.StackLimit, 判断堆栈范围是否为零
jnz loc_41A8F8 //_KTHREAD.StackLimit != 0, 跳转
//继续走 loc_40E48C
loc_40E48C: ; CODE XREF: KiInsertQueueApc(x,x)+C58E↓j
push edx
mov edx, 100h
//继续走 loc_40E492
//内核和用户都会调过来用来唤醒线程
loc_40E492: ; CODE XREF: KiInsertQueueApc(x,x)+D13↓j
push [ebp+var_4]
call @KiUnwaitThread@16 ; KiUnwaitThread(x,x,x,x)
jmp loc_40E408 //返回成功
//内核会调
loc_41A8F8: ; CODE XREF: KiInsertQueueApc(x,x)+10D↑j
cmp [ecx+0D4h], edx //_KTHREAD.KernelApcDisable, 判断内核ACP是否被禁用,1则禁用,edx = 0
jnz loc_40E408 //内核ACP被禁用则返回
cmp [ecx+48h], dl //_KTHREAD._KAPC_STATE.KernelApcInProgress, 内核APC的程序是否正在执行, 0就是没执行。
jz loc_40E48C //_KTHREAD._KAPC_STATE.KernelApcInProgress == 0, 则去执行一下,将线程加入到就绪队列
jmp loc_40E408 //返回成功
调用APC函数分析
在进行线程切换、系统调用、中断或者异常时都会调用执行APC,内核APC直接在0环处理,而3环的APC要返回3环处理。
KiDeliverApc内核APC函数执行流程:
1)判断第一个链表是否为空(第一个链表为内核APC链表)
2)判断KTHREAD.ApcState.KernelApcInProgress是否为1,即是否存在正在执行的APC函数
3)判断是否禁用内核APC(esi+0D4h = _KTHREAD.KernelApcDisable, 判断内核ACP是否被禁用,1则禁用)
4)将当前KAPC结构体从链表中摘除
5)执行KAPC.KernelRoutine指定的函数,释放KAPC结构体占用的空间
6)将KTHREAD.ApcState.KernelApcInProgress 设置为1,表示正在执行内核APC
7)执行真正的内核APC函数(KAPC.NormalRputine)
8)执行完毕将KTHREAD.ApcState.KernelApcInProgress改为1
9)循环判断是否存在下一个,继续执行,直到链表为空。
注:若KAPC.NormalRputine为空,则会将当前KAPC结构体从链表中摘除,执行KAPC.KernelRoutine指定的函数,释放KAPC结构体占用的空间,后循环下一个内核APC。
调用用户APC时,会先执行内核APC,但是调用内核APC时不会执行用户APC。
KiDeliverApc用户APC函数执行流程:
1)判断用户APC链表是否为空
2)判断第一个参数是为1,为1说明处理用户APC和内核APC
3)判断ApcState.UserApcPending(是否正在执行用户APC)是否为1
4)将ApcState.UserApcPending设置为0,表示正在处理用户APC链表操作 将当前APC从用户队列中拆除
5)调用函数(KAPC.KernelRoutine)释放KAPC结构体内存空间
6) 调用KiInitializeUserApc函数
KiServiceExit中调用KiDeliverApc (内核中)— 系统调用、中断或者异常中调用执行APC函数:
push eax //eax保存提供CR3的进程结构体_KPROCESS,挂靠即挂靠进程的CR3,否则即自己的
sti
push ebx
push 0
push 1 //0处理内核, 1处理内核和用户APC
call _KiDeliverApc@12 ; KiDeliverApc(x,x,x)
pop ecx ; NewIrql
call ds:__imp_@KfLowerIrql@4 ; KfLowerIrql(x)
KiSwapThread中调用KiDeliverApc (内核中) — 线程切换中调用执行APC函数:
loc_415ADB: ; CODE XREF: KiSwapThread()+46↑j
mov cl, 1 ; NewIrql
call esi ; KfLowerIrql(x) ; KfLowerIrql(x)
xor eax, eax
push eax
push eax
push eax //0处理内核, 1处理内核和用户APC
call _KiDeliverApc@12 ; KiDeliverApc(x,x,x)
xor cl, cl
jmp loc_40510B
KiDeliverApc 解析
__stdcall KiDeliverApc(x, x, x)
public _KiDeliverApc@12
_KiDeliverApc@12 proc near ; CODE XREF: KiUnlockDispatcherDatabase(x)+A3↑p
; _KiServiceExit+53↑p ...
LockHandle = _KLOCK_QUEUE_HANDLE ptr -28h
var_1C = dword ptr -1Ch
BugCheckParameter1= dword ptr -18h
var_10 = dword ptr -10h
var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
arg_0 = byte ptr 8
arg_4 = dword ptr 0Ch
arg_8 = dword ptr 10h
; FUNCTION CHUNK AT .text:0040F091 SIZE 00000086 BYTES
; FUNCTION CHUNK AT .text:00446A2D SIZE 00000008 BYTES
; FUNCTION CHUNK AT .text:00446A3A SIZE 0000000C BYTES
; FUNCTION CHUNK AT .text:00446A4B SIZE 0000001A BYTES
mov edi, edi
push ebp
mov ebp, esp
sub esp, 28h //初始化函数堆栈
mov ecx, [ebp+arg_8] //函数接受的第3个参数, ecx = _Trap_Frame
//ebp + 10H = ebg + 返回地址 + 第一个参数 + 第二个参数 + 第三个参数
test ecx, ecx //判断DbgEbp是否为空
jz short loc_40E304 //为零则跳转
mov edx, [ecx+68h] //获取Eip的值,_Trap_Frame.Eip (三环的)
mov eax, offset _ExpInterlockedPopEntrySListResume@0 ; ExpInterlockedPopEntrySListResume() //获取当前函数地址
cmp edx, eax //判断Eip是否等于ExpInterlockedPopEntrySListResume的地址
jb short loc_40E304
cmp edx, offset _ExpInterlockedPopEntrySListEnd@0 ; ExpInterlockedPopEntrySListEnd()
//判断Eip是否等于ExpInterlockedPopEntrySListEnd的地址
jbe loc_446A2D
loc_40E304: ; CODE XREF: KiDeliverApc(x,x,x)+D↑j
; KiDeliverApc(x,x,x)+19↑j ...
push ebx //保存ebx
push esi //保存esi
push edi //保存edi
mov eax, large fs:124h //KPCR.CurrentThread 获取当前线程结构体
mov esi, eax
//esi = _KTHREAD
mov eax, [esi+134h] //eax = _KTHREAD + 134H = _KTHREAD.TrapFrame 当前线程3环的堆栈环境。
mov [ebp+var_1C], eax //临时保存
mov eax, [esi+44h] //_KTHREAD.ApcState.UserApcPending ,APC队列中是否存在未执行的用户函数,为1则存在。
mov [esi+134h], ecx //将新的TrapFrame 写入 _KTHREAD.TrapFrame
lea ecx, [esi+0E8h] //获取_KTHREAD.ApcQueueLock 的地址
lea edx, [ebp+LockHandle] //保存到临时变量
mov [ebp+BugCheckParameter1], eax
call ds:__imp_@KeAcquireInStackQueuedSpinLock@8 ; KeAcquireInStackQueuedSpinLock(x,x) //获取一个排队的自旋锁
mov byte ptr [esi+49h], 0 //_KTHREAD.ApcState.KernelApcPending 将KernelApcPending置为0,即没有未执行的内核APC函数
lea ebx, [esi+34h] //获取 _KTHREAD.ApcState.ApcListHead[0].ApcListEntry
loc_40E33A: ; CODE XREF: IopCompleteRequest(x,x,x,x,x)+218↓j
; IopCompleteRequest(x,x,x,x,x)+4E95↓j ...
cmp [ebx], ebx //判断链表是否为空
jnz loc_415A54 //ebx 不相等则跳转,内核APC列表不为空,跳转去执行内核API
lea ecx, [esi+3Ch] //ecx = _KTHREAD.ApcListHead.[1].ApcListEntry.Flink , 获取用户内核链表的地址
mov eax, [ecx]
cmp eax, ecx //判断链表是否为空
jnz loc_40F091 //用户APC列表不为空,则跳转
loc_40E34F: ; CODE XREF: KiDeliverApc(x,x,x)+DB8↓j
; KiDeliverApc(x,x,x)+DC2↓j ...
lea ecx, [ebp+LockHandle] ; LockHandle
call ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)
loc_40E358: ; CODE XREF: KiDeliverApc(x,x,x)+E35↓j
; KiDeliverApc(x,x,x)+38764↓j ...
mov ecx, [ebp+BugCheckParameter1]
cmp [esi+44h], ecx
jnz loc_446A4B
mov eax, [ebp+var_1C]
pop edi
mov [esi+134h], eax
pop esi
pop ebx
leave
retn 0Ch
_KiDeliverApc@12 endp
loc_446A2D: ; CODE XREF: KiDeliverApc(x,x,x)+21↑j
mov [ecx+68h], eax //修改 _Trap_Frame.Eip = ExpInterlockedPopEntrySListResume
jmp loc_40E304
loc_415A54
内核APC链表不为空跳转:
loc_415A54: ; CODE XREF: KiDeliverApc(x,x,x)+5F↑j
此时 : ebx = _KTHREAD.ApcState.ApcListHead[0]
mov eax, [ebx] //eax = [_KTHREAD.ApcState.ApcListHead[0]] = _KAPC.ApcListEntry
lea edi, [eax-0Ch] //获取 _KAPC 开始的位置
//此时 edi = _KAPC,下面用来读取APC的值,并保存到局部变量中
mov ecx, [edi+14h] //获取用来销毁APC的函数地址,edi+14h(指向一个函数地址) = _KAPC.KernelRoutine
mov [ebp+ms_exc.exc_ptr], ecx //保存
mov ecx, [edi+1Ch] //内核APC函数(这是内核APC队列),edi+1Ch(指向一个函数地址) = _KAPC.NormalRoutine
test ecx, ecx //这是内核apc,所以_KAPC.NormalRoutine保存真正的内核APC函数,判断当前函数是否为空
mov [ebp+ms_exc.registration.TryLevel], ecx //保存
mov edx, [edi+20h] //内核APC: NULL, 用户APC:真正的APC函数 edi+20h(指向一个函数地址) = _KAPC.NormalContext
mov [ebp+ms_exc.registration.Next], edx //保存
mov edx, [edi+24h] //获取APC函数的参数 _KAPC.SystemArgument1
mov [ebp+ms_exc.registration.ExceptionHandler], edx //保存
mov edx, [edi+28h] //获取APC函数的参数 _KAPC.SystemArgument2
mov [ebp+ms_exc.registration.ScopeTable], edx //保存
jnz loc_41A6B8 //_KAPC.NormalRoutine内核APC函数不为空则跳转
//当内核APC函数 为空时:
//(注:下面是断链操作)
mov ecx, [eax] //[eax] = [_KAPC.ApcListEntry.Flink] 获取前一个节点(指向_KAPC)的位置 (_LIST_ENTRY)
mov eax, [eax+4] //[eax + 4] = [_KAPC.ApcListEntry.Blink] 获取后一个节点(指向_KAPC)的位置 (_LIST_ENTRY)
mov [eax], ecx //此时eax是后一个节点的_KAPC.ApcListEntry,[eax] = [_KAPC.ApcListEntry.Flink]
//将后一个节点的 Flink 赋值为 前一个节点的值(_KAPC.ApcListEntry)
mov [ecx+4], eax //此时ecx是上一个节点的_KAPC.ApcListEntry,[ecx+4] = [_KAPC.ApcListEntry.Blink]
//将前一个节点的 Blink 赋值为 后一个节点的值(_KAPC.ApcListEntry)
//上面4步将内核APC函数为空的_KAPC结构体在链表中断掉。
lea ecx, [ebp+LockHandle] //从临时变量中获取 _KTHREAD.ApcQueueLock 的地址
mov byte ptr [edi+2Eh], 0 //edi+2Eh = _KAPC.Inserted 修改为 0, 即挂入前
call ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)
//调用函数,Push参数
lea eax, [ebp+ms_exc.registration.ScopeTable] //获取APC函数的参数 _KAPC.SystemArgument2
push eax
lea eax, [ebp+ms_exc.registration.ExceptionHandler] //获取APC函数的参数 _KAPC.SystemArgument1
push eax
lea eax, [ebp+ms_exc.registration] //临时变量地址
push eax
lea eax, [ebp+ms_exc.registration.TryLevel] //用户APC总入口 或者 真正的内核APC函数,edi+1Ch(指向一个函数地址) = _KAPC.NormalRoutine
push eax
push edi //当前线程的 _KTHREAD
call [ebp+ms_exc.exc_ptr] //调用用来销毁APC的函数
lea edx, [ebp+LockHandle] //从临时变量中获取 _KTHREAD.ApcQueueLock 的地址
lea ecx, [esi+0E8h] // esi+0E8h = _KTHREAD.ApcQueueLock
call ds:__imp_@KeAcquireInStackQueuedSpinLock@8 ; KeAcquireInStackQueuedSpinLock(x,x)
jmp loc_40E33A //再次去判断内核链表
loc_41A6B8 执行内核APC函数
loc_41A6B8: ; CODE XREF: IopCompleteRequest(x,x,x,x,x)+1D8↑j
; __unwind { // __SEH_prolog
//此时 esi = _KTHREAD
cmp byte ptr [esi+48h], 0 //判断 内核APC的程序是否正在执行 KTHREAD.ApcState.KernelApcInProgress
jnz loc_40E34F //横在执行则跳转
cmp dword ptr [esi+0D4h], 0 //esi+0D4h = _KTHREAD.KernelApcDisable, 判断内核ACP是否被禁用
jnz loc_40E34F //被禁用则跳转
//(注:下面是断链操作)
mov ecx, [eax] //[eax] = [_KAPC.ApcListEntry.Flink] 获取前一个节点(指向_KAPC)的位置 (_LIST_ENTRY)
mov eax, [eax+4] //[eax + 4] = [_KAPC.ApcListEntry.Blink] 获取后一个节点(指向_KAPC)的位置 (_LIST_ENTRY)
mov [eax], ecx //此时eax是后一个节点的_KAPC.ApcListEntry,[eax] = [_KAPC.ApcListEntry.Flink]
//将后一个节点的 Flink 赋值为 前一个节点的值(_KAPC.ApcListEntry)
mov [ecx+4], eax //此时ecx是上一个节点的_KAPC.ApcListEntry,[ecx+4] = [_KAPC.ApcListEntry.Blink]
//将前一个节点的 Blink 赋值为 后一个节点的值(_KAPC.ApcListEntry)
//上面4步将当前内核APC函数_KAPC结构体在链表中断掉。
lea ecx, [ebp+LockHandle] //从临时变量中获取 _KTHREAD.ApcQueueLock 的地址
//此时 edi = _KAPC
mov byte ptr [edi+2Eh], 0 //edi+2Eh = _KAPC.Inserted 修改为 0, 即挂入前
call ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)
//调用销毁内核API函数,Push参数
lea eax, [ebp+ms_exc.registration.ScopeTable] //获取APC函数的参数 _KAPC.SystemArgument2
push eax
lea eax, [ebp+ms_exc.registration.ExceptionHandler] //获取APC函数的参数 _KAPC.SystemArgument1
push eax
lea eax, [ebp+ms_exc.registration] //临时变量地址
push eax
lea eax, [ebp+ms_exc.registration.TryLevel] //用户APC总入口 或者 真正的内核APC函数,edi+1Ch(指向一个函数地址) = _KAPC.NormalRoutine
push eax
push edi //当前线程的 _KTHREAD
call [ebp+ms_exc.exc_ptr] //调用用来销毁APC的函数
cmp [ebp+ms_exc.registration.TryLevel], 0 //判断函数地址是否为空
jz short loc_41A723 //函数地址为空则跳转
xor cl, cl
mov byte ptr [esi+48h], 1 //将内核API程序设置为正在执行状态
call ds:__imp_@KfLowerIrql@4 ; KfLowerIrql(x)
//调用内核API函数
push [ebp+ms_exc.registration.ScopeTable] //Push APC函数的参数 _KAPC.SystemArgument2
push [ebp+ms_exc.registration.ExceptionHandler] //Push APC函数的参数 _KAPC.SystemArgument1
push [ebp+ms_exc.registration.Next] //内核APC: NULL, 用户APC:真正的APC函数 edi+20h(指向一个函数地址) = _KAPC.NormalContext
call [ebp+ms_exc.registration.TryLevel] //执行 真正的内核APC函数,_KAPC.NormalRoutine
mov cl, 1 ; NewIrql
call ds:__imp_@KfRaiseIrql@4 ; KfRaiseIrql(x)
mov [ebp+LockHandle.OldIrql], al
loc_41A723: ; CODE XREF: IopCompleteRequest(x,x,x,x,x)+4E5D↑j
lea edx, [ebp+LockHandle] ; LockHandle
lea ecx, [esi+0E8h] ; SpinLock
call ds:__imp_@KeAcquireInStackQueuedSpinLock@8 ; KeAcquireInStackQueuedSpinLock(x,x)
mov byte ptr [esi+48h], 0 // KTHREAD.ApcState.KernelApcInProgress 置为 0 即没有正在执行的
jmp loc_40E33A //再次去判断内核链表
loc_40F091 执行用户APC函数
loc_40F091: ; CODE XREF: KiDeliverApc(x,x,x)+6C↑j
cmp [ebp+arg_0], 1 //Ebp减8即push进函数的第一个参数,所以判断是否要调用用户APC
jnz loc_40E34F
//esi = _KTHREAD
cmp byte ptr [esi+4Ah], 0 //esi+4Ah = _KTHREAD.ApcState.UserApcPending, 判断APC队列中是否存在未执行的用户函数,为1则存在。
jz loc_40E34F //不存在未执行的用户函数则结束
mov byte ptr [esi+4Ah], 0 //将esi+4Ah = _KTHREAD.ApcState.UserApcPending 设置为0, 防止再次进来
//此时eax为_KAPC结构体的_KAPC.ApcListEntry
lea edi, [eax-0Ch] //_KAPC结构体的开头位置
//此时edi 保存 _KAPC结构体的开头位置
mov ecx, [edi+1Ch] //_KAPC的_KAPC.NormalRoutine,用户APC总入口,因为这是用户调用(这是用户APC队列)
mov ebx, [edi+14h] //_KAPC的_KAPC.KernelRoutine ,用于获取释放Apc的函数
mov [ebp+var_4], ecx //保存到临时变量
mov ecx, [edi+20h] //_KAPC的_KAPC.NormalContext 用户APC:真正的APC函数(这是用户APC队列)
mov [ebp+var_10], ecx //保存到临时变量
mov ecx, [edi+24h] //_KAPC的_KAPC.SystemArgument1 APC函数参数
mov [ebp+var_C], ecx //保存到临时变量
mov ecx, [edi+28h] //_KAPC的_KAPC.SystemArgument2 APC函数参数
mov [ebp+var_8], ecx //保存到临时变量
//将当前_KAPC结构体从队列中断去,即断链
mov ecx, [eax]
mov eax, [eax+4]
mov [eax], ecx
mov [ecx+4], eax
lea ecx, [ebp+LockHandle] //从临时变量中获取_KTHREAD.ApcQueueLock 的地址
mov byte ptr [edi+2Eh], 0
call ds:__imp_@KeReleaseInStackQueuedSpinLock@4 ; KeReleaseInStackQueuedSpinLock(x)
//销毁APC函数调用
lea eax, [ebp+var_8] //_KAPC的_KAPC.SystemArgument2 APC函数参数
push eax
lea eax, [ebp+var_C] //_KAPC的_KAPC.SystemArgument1 APC函数参数
push eax
lea eax, [ebp+var_10] //_KAPC的_KAPC.NormalContext 用户APC:真正的APC函数(这是用户APC队列)
push eax
lea eax, [ebp+var_4] //_KAPC的_KAPC.NormalRoutine,用户APC总入口,因为这是用户调用(这是用户APC队列)
push eax //_KAPC.ApcListEntry
push edi //_KAPC结构体
call ebx //调用用于释放_KAPC的函数
cmp [ebp+var_4], 0 //判断KiDeliverApc函数的返回地址
jz loc_446A3A
push [ebp+var_8] //_KAPC的_KAPC.SystemArgument2 APC函数参数
push [ebp+var_C] //_KAPC的_KAPC.SystemArgument1 APC函数参数
push [ebp+var_10] //_KAPC的_KAPC.NormalContext 用户APC:真正的APC函数(这是用户APC队列)
push [ebp+var_4] //_KAPC的_KAPC.NormalRoutine,用户APC总入口,因为这是用户调用(这是用户APC队列)
push [ebp+arg_8] //KiDeliverApc函数接受的第3个参数, _Trap_Frame
push [ebp+arg_4] //KiDeliverApc函数接受的第4个参数, 保存提供CR3的进程结构体_KPROCESS,挂靠即挂靠进程的CR3,否则即自己的
call _KiInitializeUserApc@24 ; KiInitializeUserApc(x,x,x,x,x,x)
jmp loc_40E358
用户: _KiInitializeUserApc 解析
线程进0环时,原来的运行环境(寄存器栈顶等)保存到_Trap_Frame结构体中,如果要提前返回3环去处理用户APC,就必须要修改_Trap_Frame结构体
比如:进0环时的位置存储在EIP中,现在要提前返回,而且返回的并不是原来的位置,那就意味着必须要修改EIP为新的返回位置。还有堆栈ESP,也要修改为处理APC需要的堆栈。因为处理完APC后要返回原来的位置继续执行,即处理完APC后若要重新返回三环所在的位置,就要从之前_Trap_Frame结构体中保存的EIP返回,所以KiInitializeUserApc要做的第一件事就是备份:将原来的_Trap_Frame结构体的值备份到一个新的结构体中(CONTEXT),这个功能有其子函数KeContextFromKframes完成。
注:_Trap_Frame结构体中EIP所在位置,即返回三环执行的位置。
准备用户层执行环境:
Windows想了一个办法,把Context结构体和APC需要的参数,直接存到三环的堆栈里如图(即context + APC4个参数 = 2DC):
2DC: NormalRoutione 用户APC总入口
2D8: NormalContext 真正的APC函数
2D4: SystemArgument1 参数
2D0: SystemArgument2 参数
2CC: 0x00000000 //context结构体
0x00000000
0x00000000
0x00000000
0x00000000
0x00000000
...
0x11111111 //此为原用户空间堆栈
0x11111111
0x11111111
0x11111111
0x11111111
0x11111111
...
windows之所以将Context保存到3环,是方便三环去读写Context的值,在返回三环时会将context的值原封不动的还给0环。
_KiInitializeUserApc:
__stdcall KiInitializeUserApc(x, x, x, x, x, x)
_KiInitializeUserApc@24 proc near ; CODE XREF: KiDeliverApc(x,x,x)+E30↑p
ExceptionRecord = _EXCEPTION_RECORD ptr -34Ch
var_2FC = dword ptr -2FCh
var_2F8 = dword ptr -2F8h
var_2F4 = dword ptr -2F4h
BugCheckParameter3= dword ptr -2F0h
var_2EC = dword ptr -2ECh
var_2E8 = dword ptr -2E8h
var_228 = dword ptr -228h
var_224 = dword ptr -224h
var_1C = dword ptr -1Ch
ms_exc = CPPEH_RECORD ptr -18h
arg_0 = dword ptr 8
arg_4 = dword ptr 0Ch
arg_8 = dword ptr 10h
arg_C = dword ptr 14h
arg_10 = dword ptr 18h
arg_14 = dword ptr 1Ch
; FUNCTION CHUNK AT .text:0042EBAF SIZE 00000009 BYTES
; FUNCTION CHUNK AT .text:00446D2F SIZE 0000001F BYTES
; FUNCTION CHUNK AT .text:00446D53 SIZE 00000012 BYTES
; FUNCTION CHUNK AT .text:00446D6A SIZE 0000002E BYTES
__unwind { // __SEH_prolog
push 33Ch
push offset stru_40F338
call __SEH_prolog
mov eax, ds:___security_cookie
mov [ebp+var_1C], eax //eax保存到临时变量
mov eax, [ebp+arg_0] //eax = 保存提供CR3的进程结构体_KPROCESS,挂靠即挂靠进程的CR3,否则即自己的
//此时 eax = _KPROCESS
mov [ebp+var_2F4], eax //保存到临时变量
mov ebx, [ebp+arg_4] //第3个参数, _Trap_Frame
mov [ebp+BugCheckParameter3], ebx //保存到临时变量
test byte ptr [ebx+72h], 2
jnz loc_40F328
mov [ebp+var_2E8], 10017h //10017h保存到临时变量 context地址,即向下占用C8h个字节
lea ecx, [ebp+var_2E8] //获取当前临时变量的地址
push ecx //将地址当作参数
push eax //参数 _KPROCESS
push ebx //参数 _Trap_Frame
call _KeContextFromKframes@12 ; KeContextFromKframes(x,x,x) //将_Trap_Frame保存到context中
and [ebp+ms_exc.registration.TryLevel], 0
mov eax, 2DCh
mov [ebp+var_2F8], eax
mov esi, [ebp+var_224] //获取 context中的变量,
//var_2E8 - var_224 = 2E8 - 224 = C4 = _CONTEXT.Esp 这个ESP是指向三环原来栈顶的位置
and esi, 0FFFFFFFCh //对Esp进行4字节对齐
sub esi, eax //提升三环堆栈2DCh,用来保存Context和APC需要的4个参数的地址
//esi保存三环提升后堆栈栈顶
mov [ebp+var_2EC], esi //保存到临时变量中 var_2E8 向下 C8h个字节为 context结构体
push 4 ; Alignment //对齐
push eax ; Length //2DCh 大小
push esi ; Address //三环提升后堆栈栈顶
call _ProbeForWrite@12 ; ProbeForWrite(x,x,x)
//赋值context到三环
lea edi, [esi+10h] //esi + 10 = 三环Context的位置
mov ecx, 0B3h
lea esi, [ebp+var_2E8] //临时变量Context的位置,也是0环准备给三环赋值的context
rep movsd //esi移动到edi,即给三环Context赋值
mov dword ptr [ebx+6Ch], 1Bh //_KTrap_Frame.SegCs
push 23h
pop eax //eax = 23h
mov [ebx+78h], eax //赋值_KTrap_Frame.HardwareSegSs
mov [ebx+38h], eax //赋值_KTrap_Frame.SegDs
mov [ebx+34h], eax //赋值_KTrap_Frame.SegEs
mov dword ptr [ebx+50h], 3Bh //赋值_KTrap_Frame.SegFs
and dword ptr [ebx+30h], 0 //赋值_KTrap_Frame.SegGs
mov ecx, [ebp+var_228] //获取var_2E8 - var_228 = 2E8 - 228 = C0 = _CONTEXT.EFlag
test ecx, 20000h
jnz loc_446D2F
loc_40F2B0: ; CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+37B3A↓j
and ecx, 3E0DD7h
or ecx, 200h
mov eax, ecx
loc_40F2BE: ; CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+37B4D↓j
mov [ebx+70h], eax //前面修正了Flage的值,赋值_KTrap_Frame.EFlags
mov eax, large fs:124h //fs:[124] = _KPCR._KPCB + 4 = _KPCR._KPCB._KTHREAD
mov [ebp+var_2FC], eax //临时保存
cmp byte ptr [eax+30h], 0 //eax+30h = _KTHREAD.Iopl, 判断特权级别
jnz loc_42EBAF
loc_40F2D7: ; CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+1F9B7↓j
mov eax, [ebp+var_2EC] //ebp+var_2EC 保存三环栈顶
mov [ebx+74h], eax //_KTrap_Frame.HardwareEsp 赋值栈顶
mov ecx, ds:_KeUserApcDispatcher //全局变量,值在系统启动的时候已经赋值好了,是3环的一个函数:
//ntdll.KiUserApcDispatcher,回到3环,由KiUserApcDispatcher执行用户APC
修改EIP mov [ebx+68h], ecx //修改_Trap_Frame.EIP,让其返回三环执行点为ntdll.KiUserApcDispatcher
and dword ptr [ebx+64h], 0 //修改_Trap_Frame.ErrCode 为 0
//将NormalRoutione、NormalContext、SystemArgument1 、SystemArgument2 四个值放到三环堆栈中
mov ecx, [ebp+arg_8] //_KAPC的_KAPC.NormalRoutine,用户APC总入口,因为这是用户调用(这是用户APC队列)
mov [eax], ecx //将当前值保存到三环栈顶的位置,ESP0 = _KAPC.NormalRoutine
push 4
pop ecx //ecx = 4
add eax, ecx //eax 指向 三环栈顶的下一个元素,即ESP3 + 4
mov [ebp+var_2EC], eax //保存到临时变量
mov edx, [ebp+arg_C] //_KAPC的_KAPC.NormalContext 用户APC:真正的APC函数(这是用户APC队列
//_KiInitializeUserApc参数
mov [eax], edx //保存到 ESP3 + 4 = _KAPC.NormalContext
add eax, ecx //ESP3 + 8
mov [ebp+var_2EC], eax //保存到临时变量
mov edx, [ebp+arg_10] //_KAPC的_KAPC.SystemArgument1 APC函数参数
mov [eax], edx //保存到 ESP3 + 8 = _KAPC.SystemArgument1
add eax, ecx //ESP3 + 0Ch
mov [ebp+var_2EC], eax //保存到临时变量
mov edx, [ebp+arg_14] //_KAPC的_KAPC.SystemArgument2 APC函数参数
mov [eax], edx //保存到 ESP3 + 12 = _KAPC.SystemArgument2
add eax, ecx //ESP3 + 10H
mov [ebp+var_2EC], eax //保存到临时变量
loc_40F324: ; CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+37B97↓j
or [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh
loc_40F328: ; CODE XREF: KiInitializeUserApc(x,x,x,x,x,x)+2D↑j
mov ecx, [ebp+var_1C]
call sub_40D361
call __SEH_epilog
retn 18h
; } // starts at 40F1FC
_KiInitializeUserApc@24 endp
__SEH_epilog
__SEH_epilog proc near ; CODE XREF: CcWorkerThread(x):loc_40E060↓p
; CcLazyWriteScan()+1B8↓p ...
mov ecx, [ebp-10h]
mov large fs:0, ecx
pop ecx
pop edi
pop esi
pop ebx
leave
push ecx
retn
__SEH_epilog endp
ntdll.KiUserApcDispatcher(三环函数)
当用户在3环调用QueueUserAPC函数来插入APC时,不需要提供 NormalRoutione,这个参数是在QueueUserAPC内部指定的: kernel32.BaseDispatchAPC,即eax = kernel32.BaseDispatchAPC。
; __stdcall KiUserApcDispatcher(x, x, x, x, x)
public _KiUserApcDispatcher@20
_KiUserApcDispatcher@20 proc near ; DATA XREF: .text:off_7C923428↑o
arg_C = byte ptr 10h
lea edi, [esp+arg_C] //esp + 10h = context结构体, 这是3环的堆栈
pop eax //获取 NormalRoutione 用户APC总入口
call eax //执行 NormalRoutione
push 1
push edi
call _ZwContinue@8 ; ZwContinue(x,x)
nop
_KiUserApcDispatcher@20 endp ; sp-analysis failed
ntdll.ZwContinue(三环函数)
1)返回内核,在内核中再次判断是否还有用户APC,重复上面修改堆栈切换到用户执行的过程。
2)如果没有需要执行的用户APC,会将CONTEXT赋值给Trap_Frame结构体。即相当于没有改过。ZwContinue后面的代码不会执行,线程从哪进0环仍然会从哪里回去。
__stdcall ZwContinue(x, x)
public _ZwContinue@8
_ZwContinue@8 proc near ; CODE XREF: KiUserApcDispatcher(x,x,x,x,x)+A↓p
; KiUserExceptionDispatcher(x,x)+17↓p ...
mov eax, 20h ; ' ' ; NtContinue
mov edx, 7FFE0300h
call dword ptr [edx]
retn 8
_ZwContinue@8 endp
Context结构体
typedef struct _CONTEXT
{
DWORD ContextFlags // -| +00h
DWORD Dr0 // | +04h
DWORD Dr1 // | +08h
DWORD Dr2 // >调试寄存器 +0Ch
DWORD Dr3 // | +10h
DWORD Dr6 // | +14h
DWORD Dr7 // -| +18h
FLOATING_SAVE_AREA FloatSave; //浮点寄存器区 +1Ch~~~88h
DWORD SegGs //-| +8Ch
DWORD SegFs // |\段寄存器 +90h
DWORD SegEs // |/ +94h
DWORD SegDs //-| +98h
DWORD Edi //________ +9Ch
DWORD Esi // | 通用 +A0h
DWORD Ebx // | 寄 +A4h
DWORD Edx // | 存 +A8h
DWORD Ecx // | 器 +ACh
DWORD Eax //_|___组_ +B0h
DWORD Ebp //++++++ +B4h
DWORD Eip // |控制 +B8h
DWORD SegCs // |寄存 +BCh
DWORD EFlag // |器组 +C0h
DWORD Esp // | +C4h
DWORD SegSs //++++++ +C8h
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;