首页 > 其他分享 >APC初始化

APC初始化

时间:2024-05-18 17:32:08浏览次数:25  
标签:初始化 函数 插入 线程 内核 KAPC APC apc

1.APC初始化 R3APC插入

简述

线程出现等待的情况下sleep WaitForSingleObject线程的资源再利用

apc--异步过程调用本质上就是一个异步call,线程本身是一直走,直到出现等待。但是有的时候需要当线程的某一个函数执行完,产生一个通知,但是如果是采用等待的方式的话,那么对于一些UI相关的不够友好,因为卡在那里了。异步call就是为了解决这个需求,当执行了需要通知的函数,不会一直卡在那里而是会一直向下走且不创建新的线程。线程会在某个时刻触发这个异步call,不过因为触发的地方很多,所以基本上是插入就执行。

apc都是插入到线程的,在线程结构中有两个链表,一个是内核链表(在上面),一个是用户链表(在下面),apc就是插在这里面,很多线程在R3->R0或者R0中的时候都会检查自身有没有apc,有的话会优先执行。

apc是插入进去后,线程中会有某些函数会进行检查是否有apc,有的话调用,所以称为异步

同步是等待的,异步是不等待。同步就是等事情做完后再执行(一直等待直到触发那个事件),是顺序的。异步是不等待,一直执行,同时注意有没有触发指定事件,如果有的话,就先停下之前的事情,执行触发后需要执行的动作

与apc相关的字段

线程中

详细信息看4.3的线程结构

Windbg执行dt _kthread查看线程结构

其中与apc有关的如下

+0x03a Alerted          : [2] UChar //警惕,0内核1,三环
+0x03c Alertable        : Pos 5, 1 Bit//可警惕,是否可以唤醒,只有填是,警惕才有意义
+0x040 ApcState         : _KAPC_STATE//r0下和警惕性无关,r3下必须是可警惕
+0x0b8 ApcQueueable     : Pos 5, 1 Bit//是否允许apc插入队列,为0的话代表调用api无法插入,默认1
+0x168 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
+0x170 SavedApcState    : _KAPC_STATE
+0x134 ApcStateIndex    : UChar//索引值

ApcStatePointer

本质上是存了两个指针,代表+0x040 ApcState+0x170 SavedApcState

+0x040 ApcState当前上下文环境

+0x170 SavedApcState备用的上下文环境

+0x134 ApcStateIndexapc索引

我们的线程在内核环境下是可以脱离进程的(切换进程上下文),保留原始环境表,为还原使用

没有挂靠的时候,原始apc链表是有值的,备用是空的,ApcStatePointer[0] = ApcStateApcStatePointer[1] = SavedApcState同时+0x134 ApcStateIndex索引值是0,这个索引代表ApcStatePointer[index]是这个的索引号

已挂靠的时候,将ApcState中的值复制到SavedApcState中,之后清空ApcState的值,将ApcStateIndex的改成1

KAPC_STATE

kd> dt _kapc_state
ntdll!_KAPC_STATE
   +0x000 ApcListHead      : [2] _LIST_ENTRY//1->R3,0->R0
   +0x010 Process          : Ptr32 _KPROCESS//当前环境的进程,挂靠后就是挂靠的进程
   +0x014 KernelApcInProgress : UChar//内核apc是否正在执行
   +0x015 KernelApcPending : UChar//内核apc是否可以执行,1的时候`ApcListHead`有值,反之没有
   +0x016 UserApcPending   : UChar//用户apc是否可以执行,1的时候`ApcListHead`有值,反之没有

kapc

可以理解为KAPC_STATEapc管理器,kapc才是真正的具体的apc结构

_kapc_state+0x000 ApcListHead 指向的是_kapc

kd> dt _kapc
ntdll!_KAPC
   +0x000 Type             : UChar//apc类型
   +0x001 SpareByte0       : UChar//保留
   +0x002 Size             : UChar//apc结构大小
   +0x003 SpareByte1       : UChar//保留
   +0x004 SpareLong0       : Uint4B//保留
   +0x008 Thread           : Ptr32 _KTHREAD//当前apc的结构是挂在那个线程上的
   +0x00c ApcListEntry     : _LIST_ENTRY//和KAPC_STATE串联,减去0x0c才可以得到kapc
   +0x014 KernelRoutine    : Ptr32     void //内核函数
   +0x018 RundownRoutine   : Ptr32     void //特殊的函数
   +0x01c NormalRoutine    : Ptr32     void //正常的apc的函数表,如果是用户态apc必须写,内核apc可写可不写
   +0x020 NormalContext    : Ptr32 Void//正常的apc的函数表对应的参数1
   +0x024 SystemArgument1  : Ptr32 Void//正常的apc的函数表对应的参数2
   +0x028 SystemArgument2  : Ptr32 Void//正常的apc的函数表对应的参数3
   +0x02c ApcStateIndex    : Char//和线程中的ApcStateIndex没有关系
   +0x02d ApcMode          : Char//模式,用户apc节点还是用户apc节点,0代表内核,1代表用户
   +0x02e Inserted         : UChar//插入完是1,记录自己是否被插入过

RundownRoutine

特殊的情况下执行这里面的函数,例如线程挂起/线程结束

普通的派发情况下不执行

NormalRoutine

用户态必须写这个函数

内核态没写这个函数的话叫做特殊内核apc,写叫做普通内核apc

特殊内核apc是插入在ApcListHead这个链表的头部

普通内核apc是插入在ApcListHead这个链表的尾部

也就是特殊的优先级比较高

ApcStateIndex

和线程中的ApcStateIndex没有关系

挂靠apc的四种环境

  1. 无论挂不挂靠,apc都插入到原始环境(插入到创建者的上下文中),本质上就是插入到ApcState表中
  2. 无论挂不挂靠,apc都插入到挂靠环境(插入到创建者的上下文中),本质上就是插入到SavedApcState表中
  3. 选择插入,在初始化apc函数中,看线程中的ApcStateIndex,如果等于0,就插入到原始环境,否则插入到挂靠环境
  4. 选择插入,初始化函数不插入,在插入apc函数的时候选择插入,判断原理同上一个

这四种环境对应了ApcStateIndex0,1,2,3

函数声明

WRK中的KeInitializeApc初始化apc函数声明

VOID
KeInitializeApc (
    __out PRKAPC Apc,//APC结构
    __in PRKTHREAD Thread,//apc要插入哪个线程
    __in KAPC_ENVIRONMENT Environment,//插入的环境
    __in PKKERNEL_ROUTINE KernelRoutine,//内核回调,必须写
    __in_opt PKRUNDOWN_ROUTINE RundownRoutine,//填NULL就行
    __in_opt PKNORMAL_ROUTINE NormalRoutine,//挂在`ApcState`链表中还是`SavedApcState`链表中
    __in_opt KPROCESSOR_MODE ApcMode,
    __in_opt PVOID NormalContext
    )

KAPC_ENVIRONMENT环境和上面的ApcStateIndex中所说的4种环境一致

typedef enum _KAPC_ENVIRONMENT {
    OriginalApcEnvironment,//原始环境
    AttachedApcEnvironment,//挂靠环境
    CurrentApcEnvironment,//当前环境
    InsertApcEnvironment//插入后选择环境
} KAPC_ENVIRONMENT;

R3下APC注入代码

注入器main.c

#include <windows.h>
#include <stdio.h>
int main()
{
	//打开进程
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 1044);
	if (!hProcess)
	{
		printf("打开进程失败\r\n");
		return -1;
	}
	//打开线程
	HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, 1896);
	if (!hThread)
	{
		printf("打开线程失败\r\n");
		return -1;
	}
	//获取loadlibraryA
	HMODULE hModule = GetModuleHandleA("kernel32.dll");
	PVOID func = (PVOID)GetProcAddress(hModule, "LoadLibraryA");
	printf("%x\r\n", func);
	//system("pause");
	//给目标进程申请内存,存dll路径
	PUCHAR targetMemory = (PUCHAR)VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	if (!targetMemory)
	{
		printf("申请内存失败\r\n");
		return -1;
	}
	printf("targetMemony :%x\r\n", targetMemory);
	//system("pause");
	//返回大小
	SIZE_T proc = NULL;
	//dll路径
	char* dllpath = "C:\\Users\\Administrator\\Desktop\\testDll.dll";
	//给目标进程写内容 
	//写+1的原因,因为`WriteProcessMemory`函数到`\0`就结束了,所以这个`dllpath`没有写`\0`
	//又因为`VirtualAllocEx`申请的内存没有初始化,所以里面可能有很多的0xcc所以我们需要将dllpath和`\0`一起写进去,所以要+1
	if (!WriteProcessMemory(hProcess, targetMemory, dllpath, strlen(dllpath) + 1, NULL))
	{
		printf("写入失败\r\n");
		return -1;
	}
	/* 接下来是apc注入*/

	//插入apc
	//3个参数,第一个是要执行的函数,第二个是要插入的线程,第三个是带进去的参数
	QueueUserAPC((PAPCFUNC)func, hThread, (ULONG_PTR)targetMemory);
	system("pause\r\n");
	return 0;
}

用来测试的dll

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		MessageBox(NULL, L"1", L"TEST", MB_OK);
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

被注入的进程

#include <windows.h>
#include <stdio.h>
int main()
{
	while (1)
	{
		//TRUE代表可以唤醒,在插入apc的时候可以运行起来
		SleepEx(1000, TRUE);
		printf("-----test-----\r\n");
	}
	return 0;
}

效果

标签:初始化,函数,插入,线程,内核,KAPC,APC,apc
From: https://www.cnblogs.com/murkuo/p/18199533

相关文章

  • 内核APC执行过程
    3.内核APC执行过程说明未文档化但是导出,所以需要提前声明具体看下面的代码中的struct.hKeInitializeApc参数//初始化apc函数VOIDKeInitializeApc( __outPRKAPCApc,//使用`PKAPCpKapc`初始化 __inPRKTHREADThread,//内核中填当前线程即可 __inKAPC_ENVIRONMENTEnv......
  • APC插入
    2.APC插入介绍WRK中的KeInsertQueueApc函数原型BOOLEANKeInsertQueueApc(__inoutPRKAPCApc,//apc结构__in_optPVOIDSystemArgument1,//参数1(可选)__in_optPVOIDSystemArgument2,//参数2(可选)__inKPRIORITYIncrement//优先级,R3下自动传值)......
  • APC挂靠
    5.APC挂靠用户态apc和上一课的内核apc几乎一致,唯一的变动就是这个//插入当前线程 KeInitializeApc(pKapc,eThread,OriginalApcEnvironment,KernelAPCRoutineFunc,NULL,0x4011d0,UserMode,NULL);改成了UserMode函数地址改成了进程的地址0x4011d0完整代码Driver-main......
  • C++ 初始化列表(Initialization List)
    请注意以下继承体系中各class的constructors写法:1classCPoint2{3public:4CPoint(floatx=0.0)5:_x(x){}67floatx(){return_x;}8voidx(floatxval){_x=xval;}9protected:10float_x;11};1213classCPoint2d:......
  • "Bios"是计算机系统中的基本输入输出系统(Basic Input/Output System),负责在计算机启动
    "Bios"是计算机系统中的基本输入输出系统(BasicInput/OutputSystem),负责在计算机启动时初始化硬件设备、检测系统资源,并启动操作系统。Bios开发人员是负责设计、开发和维护计算机系统的Bios软件的专业人员。工作内容:软件设计和开发:Bios开发人员负责设计和编写Bios软件,包......
  • 单例模式的两种实现方式(初始化时创建以及运行时创建)
    单例模式删除析构函数通常意味着单例对象应该一直存活直到程序结束。在单例模式中,这通常是可取的,因为单例对象的生命周期通常与应用程序的生命周期相同。但是这样的话需要有一个函数来回收资源。以下例子:使用双重检查锁实现(线程安全)实现模板来创建单例#include<iostream>......
  • 2022 Benelux Algorithm Programming Contest (BAPC 22) A 、I、J、L
    A.AdjustedAverage(暴力枚举+二分查找)分析读完题目可以发现k很小,那么考虑暴力做法的时间复杂度为\(O(C_n^k)\),对于\(k\leq3\)的其实可以直接暴力创过去,但对于\(k=4\)的情况显然不适用。那么对应\(k=4\)的情况考虑优化,可以选择将数分为两个集合,先用一个set存下其中一个集合的所......
  • 使用selenium时,用webdriver初始化浏览器时间过久并伴随报错 operation timed out 的处
    我使用的系统是archlinux,在日常更新软件包时系统自动将selenium更新到目前(2024年5月)最新的版本,即:python-selenium4.20.0-1。在运行我的爬虫时报错缺少依赖包,经过一番查询得知现在的驱动由webdriver_manger统一接管了。webdrivermanger来对浏览器驱动和浏览器版本进行管理,但aur......
  • Spring SpringMVC——前端控制器初始化过程
     创建完DispatcherServlet对象时,会执行类中的init方法 如果不配置load-on-startup,那么DispatcherServlet将在第一次收到请求时才会被实例化和初始化。这意味着DispatcherServlet不会在服务器启动时立即执行创建和初始化的操作。当第一个请求到达时,Servlet容器(如Tomca......
  • 10.3顺序表的初始化以及插入实战(早期学习笔记记录)
    命名规范1.下划线命名法list_insert不同的单词用下划线连接2.驼峰命名法ListInsert每个单词首字母大写一切数据结构考的都是增(插入)删查改插入思路1、判断插入位置是否合法1<=i<=lenthif(i<1||i>lenth){returnfalse;}2、判断储存空间是否已满(插入数据后......