首页 > 其他分享 >APC挂靠

APC挂靠

时间:2024-05-18 17:29:58浏览次数:24  
标签:__ 挂靠 KAPC 线程 apc APC PVOID OUT

5.APC挂靠

用户态apc

和上一课的内核apc几乎一致,唯一的变动就是这个

//插入当前线程
	KeInitializeApc(pKapc, eThread, OriginalApcEnvironment, KernelAPCRoutineFunc, NULL, 0x4011d0, UserMode, NULL);

改成了UserMode函数地址改成了进程的地址0x4011d0

完整代码

Driver-main.c

#include <ntifs.h>
#include "struct.h"
//特殊apc
VOID KernelAPCRoutineFunc(
	IN struct _KAPC* Apc,
	IN OUT PKNORMAL_ROUTINE* NormalRoutine,
	IN OUT PVOID* NormalContext,
	IN OUT PVOID* SystemArgument1,
	IN OUT PVOID* SystemArgument2
)
{
	DbgPrint("----target1:%d---\r\n", PsGetCurrentProcessId());
	//打印一句话然后释放内存
	DbgPrint("KernelAPCRoutineFunc insert\r\n");
	ExFreePool(Apc);
}


VOID Unload(PDRIVER_OBJECT pDriver)
{
	DbgPrint("unload\r\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
	//定义一个apc申请内存,因为要进到dpc等级上,所以不能分页
	//不同版本的vs的`KAPC`结构体会有变化,所以需要增加一些大小
	PKAPC pKapc = ExAllocatePool(NonPagedPool, sizeof(KAPC) + 0x100);
	//清空内存
	memset(pKapc, 0, sizeof(KAPC) + 0x100);

	//插入外部线程apc
	PETHREAD eThread = NULL;
	PsLookupThreadByThreadId(3000, &eThread);
	if (!eThread)
	{
		DbgPrint("获取线程失败\r\n");
		ExFreePool(pKapc);
		return STATUS_UNSUCCESSFUL;
	}
	DbgPrint("----main:%d---\r\n", PsGetCurrentProcessId());
	//初始化apc
	//插入当前线程
	KeInitializeApc(pKapc, eThread, OriginalApcEnvironment, KernelAPCRoutineFunc, NULL, 0x4011d0, UserMode, NULL);
	//插入apc
	BOOLEAN isRet = KeInsertQueueApc(pKapc, NULL, NULL, 0);
	//如果插入失败,释放内存
	if (!isRet)
	{
		ExFreePool(pKapc);
	}

	pDriver->DriverUnload = Unload;
	//DbgBreakPoint();
	DbgPrint("TEST_Entry\r\n");
	return STATUS_SUCCESS;
}

Driver-struct.h

#pragma once
#pragma once
#include <ntifs.h>
//定义和原型

//内核中最常用第一个,其他几个基本用不到,因为无论怎么样,线程最终都会回归原始的环境
typedef enum _KAPC_ENVIRONMENT {
	OriginalApcEnvironment,
	AttachedApcEnvironment,
	CurrentApcEnvironment,
	InsertApcEnvironment
} KAPC_ENVIRONMENT;

typedef VOID(*PKNORMAL_ROUTINE) (
	IN PVOID NormalContext,
	IN PVOID SystemArgument1,
	IN PVOID SystemArgument2
	);

typedef VOID(*PKKERNEL_ROUTINE) (
	IN struct _KAPC* Apc,
	IN OUT PKNORMAL_ROUTINE* NormalRoutine,
	IN OUT PVOID* NormalContext,
	IN OUT PVOID* SystemArgument1,
	IN OUT PVOID* SystemArgument2
	);

typedef VOID(*PKRUNDOWN_ROUTINE) (
	IN struct _KAPC* Apc
	);
//初始化apc函数
VOID KeInitializeApc(
	__out PRKAPC Apc,
	__in PRKTHREAD Thread,
	__in KAPC_ENVIRONMENT Environment,
	__in PKKERNEL_ROUTINE KernelRoutine,
	__in_opt PKRUNDOWN_ROUTINE RundownRoutine,
	__in_opt PKNORMAL_ROUTINE NormalRoutine,
	__in_opt KPROCESSOR_MODE ApcMode,
	__in_opt PVOID NormalContext
);
//插入apc函数
BOOLEAN KeInsertQueueApc(
	__inout PRKAPC Apc,
	__in_opt PVOID SystemArgument1,
	__in_opt PVOID SystemArgument2,
	__in KPRIORITY Increment
);

测试程序

main.c

#include <windows.h>
#include <stdio.h>
//三个参数
VOID test(PVOID parm1, PVOID parm2, PVOID parm3)
{
	printf("apc被执行了\r\n");
}

int main()
{
	//打印函数地址
	printf("Test Func = %x\r\n", test);
	//打印线程地址
	printf("Local Thread = %d\r\n", GetCurrentThreadId());
	system("pause");
	while (1)
	{
		printf("----1min----\r\n");
		//可以唤醒的等待
		SleepEx(1000, TRUE);
	}
	return 0;
}

一定要在程序程序执行等待后再apc插入(执行到while循环里面),否则会蓝屏!

实验

测试程序代码改成如下

#include <windows.h>
#include <stdio.h>
//三个参数
VOID test(PVOID parm1, PVOID parm2, PVOID parm3)
{
	printf("apc被执行了\r\n");
}

int main()
{
	//打印函数地址
	printf("Test Func = %x\r\n", test);
	//打印线程地址
	printf("Local Thread = %d\r\n", GetCurrentThreadId());
	system("pause");
	while (1)
	{
		printf("----1min----\r\n");
		//不可以唤醒的等待(死等)
		Sleep(1000);
	}

	return 0;
}

不使用可以唤醒的SleepEx而是使用Sleep

可以看到,此时再用上面的apc插入代码会失败

先windbg找到对应的进程!process 0 0

dt _kthread 872c2d48查看一下ethread结构体中的警惕标志

 +0x03c Alertable        : 0y0

发现可警惕标志为0,代表不可以被唤醒

接下来我们改一下标志位

+0x03c KernelStackResident : 0y1             1
+0x03c ReadyTransition  : 0y0                2
+0x03c ProcessReadyQueue : 0y0               3
+0x03c WaitNext         : 0y0                4
+0x03c SystemAffinityActive : 0y0            1
+0x03c Alertable        : 0y0                2
+0x03c GdiFlushActive   : 0y0                3
+0x03c UserStackWalkActive : 0y0             4
+0x03c ApcInterruptRequest : 0y0
+0x03c ForceDeferSchedule : 0y0

因为Alertable是第二个0的第二个,所以给那一位改成了2

想不明白就将每一位16进制转换成2进制对照一下

然后继续运行尝试插入apc

发现还是没有反应,失败

此时重新dt _kthread 872c2d48查看一下结构

发现竟然被改回去了,原因是sleep是循环执行的,所以每次都会被重新执行的时候标志位都会被改回去

此时我们改一下代码,把sleep的时间改长一些

Sleep(100000);

重新运行程序改一下标志位

可以看到可警惕已经被改成了1

再次加载驱动可以发现已经被唤醒了

这证明了我们插入apc的时候需要把这个标志改成1

用处:可以执行程序内部的代码(远程call)

代码实现思路

可以看一下WRK的KeAlertThread具体代码

BOOLEAN
KeAlertThread (
    __inout PKTHREAD Thread,
    __in KPROCESSOR_MODE AlertMode
    )

/*++

Routine Description:

    This function attempts to alert a thread and cause its execution to
    be continued if it is currently in an alertable Wait state. Otherwise
    it just sets the alerted variable for the specified processor mode.

Arguments:

    Thread  - Supplies a pointer to a dispatcher object of type thread.

    AlertMode - Supplies the processor mode for which the thread is
        to be alerted.

Return Value:

    The previous state of the alerted variable for the specified processor
    mode.

--*/

{

    BOOLEAN Alerted;
    KLOCK_QUEUE_HANDLE LockHandle;

    ASSERT_THREAD(Thread);
    ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    //
    // Raise IRQL to SYNCH_LEVEL, acquire the thread APC queue lock, and lock
    // the dispatcher database.
    //

    KeAcquireInStackQueuedSpinLockRaiseToSynch(&Thread->ApcQueueLock, &LockHandle);
    KiLockDispatcherDatabaseAtSynchLevel();

    //
    // Capture the current state of the alerted variable for the specified
    // processor mode.
    //

    Alerted = Thread->Alerted[AlertMode];

    //
    // If the alerted state for the specified processor mode is Not-Alerted,
    // then attempt to alert the thread.
    //

    if (Alerted == FALSE) {

        //如果可警惕位是0的话
        // If the thread is currently in a Wait state, the Wait is alertable,
        // and the specified processor mode is less than or equal to the Wait
        // mode, then the thread is unwaited with a status of "alerted".
        //

        if ((Thread->State == Waiting) && (Thread->Alertable == TRUE) &&
            (AlertMode <= Thread->WaitMode)) {
            KiUnwaitThread(Thread, STATUS_ALERTED, ALERT_INCREMENT);

        } else {
            Thread->Alerted[AlertMode] = TRUE;//将可警惕位置为1
        }
    }

    //
    // Unlock the dispatcher database from SYNCH_LEVEL, release the thread
    // APC queue lock, exit the scheduler, and return the previous alerted
    // state for the specified mode.
    //
    //接下来是切换线程,必然会触发apc
    KiUnlockDispatcherDatabaseFromSynchLevel();
    KeReleaseInStackQueuedSpinLockFromDpcLevel(&LockHandle);
    KiExitDispatcher(LockHandle.OldIrql);
    return Alerted;
}

由于这个是未文档化的函数,所以需要提前声明

完整代码 x86

struct.h

#pragma once
#pragma once
#include <ntifs.h>
//定义和原型

//内核中最常用第一个,其他几个基本用不到,因为无论怎么样,线程最终都会回归原始的环境
typedef enum _KAPC_ENVIRONMENT {
	OriginalApcEnvironment,
	AttachedApcEnvironment,
	CurrentApcEnvironment,
	InsertApcEnvironment
} KAPC_ENVIRONMENT;

typedef VOID(*PKNORMAL_ROUTINE) (
	IN PVOID NormalContext,
	IN PVOID SystemArgument1,
	IN PVOID SystemArgument2
	);

typedef VOID(*PKKERNEL_ROUTINE) (
	IN struct _KAPC* Apc,
	IN OUT PKNORMAL_ROUTINE* NormalRoutine,
	IN OUT PVOID* NormalContext,
	IN OUT PVOID* SystemArgument1,
	IN OUT PVOID* SystemArgument2
	);

typedef VOID(*PKRUNDOWN_ROUTINE) (
	IN struct _KAPC* Apc
	);
//初始化apc函数
VOID KeInitializeApc(
	__out PRKAPC Apc,
	__in PRKTHREAD Thread,
	__in KAPC_ENVIRONMENT Environment,
	__in PKKERNEL_ROUTINE KernelRoutine,
	__in_opt PKRUNDOWN_ROUTINE RundownRoutine,
	__in_opt PKNORMAL_ROUTINE NormalRoutine,
	__in_opt KPROCESSOR_MODE ApcMode,
	__in_opt PVOID NormalContext
);
//插入apc函数
BOOLEAN KeInsertQueueApc(
	__inout PRKAPC Apc,
	__in_opt PVOID SystemArgument1,
	__in_opt PVOID SystemArgument2,
	__in KPRIORITY Increment
);

//更改线程的可警惕
BOOLEAN
KeAlertThread(
	__inout PKTHREAD Thread,
	__in KPROCESSOR_MODE AlertMode
);

main.c

#include <ntifs.h>
#include "struct.h"
//特殊apc
VOID KernelAPCRoutineFunc(
	IN struct _KAPC* Apc,
	IN OUT PKNORMAL_ROUTINE* NormalRoutine,
	IN OUT PVOID* NormalContext,
	IN OUT PVOID* SystemArgument1,
	IN OUT PVOID* SystemArgument2
)
{
	DbgPrint("----target1:%d---\r\n", PsGetCurrentProcessId());
	//打印一句话然后释放内存
	DbgPrint("KernelAPCRoutineFunc insert\r\n");
	ExFreePool(Apc);
}


VOID Unload(PDRIVER_OBJECT pDriver)
{
	DbgPrint("unload\r\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
	//定义一个apc申请内存,因为要进到dpc等级上,所以不能分页
	//不同版本的vs的`KAPC`结构体会有变化,所以需要增加一些大小
	PKAPC pKapc = ExAllocatePool(NonPagedPool, sizeof(KAPC) + 0x100);
	//清空内存
	memset(pKapc, 0, sizeof(KAPC) + 0x100);

	//插入外部线程apc
	PETHREAD eThread = NULL;
	PsLookupThreadByThreadId(2732, &eThread);
	if (!eThread)
	{
		DbgPrint("获取线程失败\r\n");
		ExFreePool(pKapc);
		return STATUS_UNSUCCESSFUL;
	}
	DbgPrint("----main:%d---\r\n", PsGetCurrentProcessId());
	//初始化apc
	//插入当前线程
	KeInitializeApc(pKapc, eThread, OriginalApcEnvironment, KernelAPCRoutineFunc, NULL, 0x4011c0, UserMode, NULL);

	/* 将Alertable置为1,让他可警惕,才可以apc插入*/
	*((PUCHAR)eThread + 0x3c) |= 0x20;

	//插入apc
	BOOLEAN isRet = KeInsertQueueApc(pKapc, NULL, NULL, 0);

	//更改线程的apc并且通过切换线程唤醒apc
	KeAlertThread(eThread, UserMode);
    
	//如果插入失败,释放内存
	if (!isRet)
	{
		ExFreePool(pKapc);
	}

	pDriver->DriverUnload = Unload;
	//DbgBreakPoint();
	DbgPrint("TEST_Entry\r\n");
	return STATUS_SUCCESS;
}

x64

因为x64下线程是加密了的,所以我们需要使用一个为文档化函数进行解密

不过这里我这个函数可以直接使用了。。。

这个我感觉有点怪,驱动要编译成x64的,但是测试程序需要用x86的,也就是wow64

这个要注意几个点

第一个就是KeInitializeApc初始化的时候需要填上一个解密前的函数地址

第二个就是需要在线程内部才可以使用PsWrapApcWow64Thread否则没法使用,获取不到当前线程,没法解密

完整代码

main.c

#include <ntifs.h>
#include "struct.h"
//特殊apc
VOID KernelAPCRoutineFunc(
	IN struct _KAPC* Apc,
	IN OUT PKNORMAL_ROUTINE* NormalRoutine,
	IN OUT PVOID* NormalContext,
	IN OUT PVOID* SystemArgument1,
	IN OUT PVOID* SystemArgument2
)
{
	/*在指定线程里面后才可以取到当前线程的id,才可以进行地址转化*/
	ULONG64 addr = 0x4011c0;
	PsWrapApcWow64Thread(NULL, &addr);
	/*跳转到我们需要执行的代码位置*/
	*NormalRoutine = addr;

	DbgPrint("----target1:%d---\r\n", PsGetCurrentProcessId());
	//打印一句话然后释放内存
	DbgPrint("KernelAPCRoutineFunc insert\r\n");
	ExFreePool(Apc);
}


VOID Unload(PDRIVER_OBJECT pDriver)
{
	DbgPrint("unload\r\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
	//定义一个apc申请内存,因为要进到dpc等级上,所以不能分页
	//不同版本的vs的`KAPC`结构体会有变化,所以需要增加一些大小
	PKAPC pKapc = ExAllocatePool(NonPagedPool, sizeof(KAPC) + 0x100);
	//清空内存
	memset(pKapc, 0, sizeof(KAPC) + 0x100);

	//插入外部线程apc
	PETHREAD eThread = NULL;
	PsLookupThreadByThreadId(2496, &eThread);
	if (!eThread)
	{
		DbgPrint("获取线程失败\r\n");
		ExFreePool(pKapc);
		return STATUS_UNSUCCESSFUL;
	}
	DbgPrint("----main:%d---\r\n", PsGetCurrentProcessId());



#ifdef _WIN64
	/* 将Alertable置为1,让他可警惕,才可以apc插入*/
	* ((PUCHAR)eThread + 0x4c) |= 0x20;

#else
	/* 将Alertable置为1,让他可警惕,才可以apc插入*/
	* ((PUCHAR)eThread + 0x3c) |= 0x20;
	ULONG addr = 0x10000;
#endif
	//初始化apc
	//插入当前线程
	/* x64下那个NormalRoutine要填解密前的地址 */
	KeInitializeApc(pKapc, eThread, OriginalApcEnvironment, KernelAPCRoutineFunc, NULL, 0x4011c0, UserMode, NULL);
	//插入apc
	BOOLEAN isRet = KeInsertQueueApc(pKapc, NULL, NULL, 0);
	//更改线程的apc并且通过切换线程唤醒apc
	KeAlertThread(eThread, UserMode);

	//如果插入失败,释放内存
	if (!isRet)
	{
		ExFreePool(pKapc);
	}

	pDriver->DriverUnload = Unload;
	//DbgBreakPoint();
	DbgPrint("TEST_Entry\r\n");
	return STATUS_SUCCESS;
}

struct.h

#pragma once
#pragma once
#include <ntifs.h>
//定义和原型

//内核中最常用第一个,其他几个基本用不到,因为无论怎么样,线程最终都会回归原始的环境
typedef enum _KAPC_ENVIRONMENT {
	OriginalApcEnvironment,
	AttachedApcEnvironment,
	CurrentApcEnvironment,
	InsertApcEnvironment
} KAPC_ENVIRONMENT;

typedef VOID(*PKNORMAL_ROUTINE) (
	IN PVOID NormalContext,
	IN PVOID SystemArgument1,
	IN PVOID SystemArgument2
	);

typedef VOID(*PKKERNEL_ROUTINE) (
	IN struct _KAPC* Apc,
	IN OUT PKNORMAL_ROUTINE* NormalRoutine,
	IN OUT PVOID* NormalContext,
	IN OUT PVOID* SystemArgument1,
	IN OUT PVOID* SystemArgument2
	);

typedef VOID(*PKRUNDOWN_ROUTINE) (
	IN struct _KAPC* Apc
	);
//初始化apc函数
VOID KeInitializeApc(
	__out PRKAPC Apc,
	__in PRKTHREAD Thread,
	__in KAPC_ENVIRONMENT Environment,
	__in PKKERNEL_ROUTINE KernelRoutine,
	__in_opt PKRUNDOWN_ROUTINE RundownRoutine,
	__in_opt PKNORMAL_ROUTINE NormalRoutine,
	__in_opt KPROCESSOR_MODE ApcMode,
	__in_opt PVOID NormalContext
);
//插入apc函数
BOOLEAN KeInsertQueueApc(
	__inout PRKAPC Apc,
	__in_opt PVOID SystemArgument1,
	__in_opt PVOID SystemArgument2,
	__in KPRIORITY Increment
);

//更改线程的可警惕
BOOLEAN
KeAlertThread(
	__inout PKTHREAD Thread,
	__in KPROCESSOR_MODE AlertMode
);

//x64下解密用的函数
EXTERN_C NTSTATUS PsWrapApcWow64Thread(PVOID* ApcContext, PVOID* ApcRoutine);

挂靠

理论

原本的apc里面是有值的,如果是第一次挂靠对方线程,那么会将原本有值的APC_STATE里面的值复制到SAVE_APC_STATE链表,然后将APC_STATE清空

如果是第二次挂靠,那么会将原本有值的APC_STATE里面的值复制到PARAM_APC_STATE链表,然后将APC_STATE清空

本质上就是切换cr3,但是因为对方的进程可能被放到了磁盘上,所以需要经过一系列的判断

挂靠检测

使用这个函数进行检测,对当前线程的ApcStateIndex进行检测,如果是1那么就是被挂靠了

但是这个检测不准,第一是因为你挂靠的速度很快,这个值可能一会是1一会是2,第二是因为系统也会挂靠

标签:__,挂靠,KAPC,线程,apc,APC,PVOID,OUT
From: https://www.cnblogs.com/murkuo/p/18199541

相关文章

  • 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存下其中一个集合的所......
  • .net core,.net 6使用SoapCore开发webservice接口,以及使用HttpClientFactory动态访问we
    1.使用soapCorenuget包 2.新建接口及实现2.1新建接口 2.2新建实现 2.3新建接收实体 2.4返回实体 3.接口注入使用  4.启动程序,直接访问对应的asmx地址  ......
  • hitcontraining_heapcreator
    [BUUCTF]hitcontraining_heapcreatorUAF|Off-By-One|堆溢出对应libc版本libc6_2.23-0ubuntu9_amd64[*]'/home/bamuwe/heapcreator/heapcreator'Arch:amd64-64-littleRELRO:PartialRELROStack:CanaryfoundNX:NXenabled......
  • P9433 [NAPC-#1] Stage5 - Conveyors
    P9433[NAPC-#1]Stage5-Conveyorslca维护树上路径但是这题不是难在这里,考察的是分析问题答案构成的能力。我们可以从数据范围出发。\(s=t,k=n\)每条边都要走两遍,显然是树上所有边权和\(\times2\)。\(k=n\)可以构造一种走法,使得\(t\)先到\(s\),按照上面的走法走完......
  • mybatis :sqlmapconfig.xml配置 ++++Mapper XML 文件(sql、insert、update、delete)
    sqlmapconfig.xml配置MyBatis的配置文件包含了会深深影响MyBatis行为的设置(settings)和属性(properties)信息。文档的顶层结构如下:configuration配置properties属性settings设置typeAliases类型别名typeHandlers类型处理器objectFactory对象工厂plugins插件environ......
  • 随机APC决斗
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device......
  • 软件工程师证书可以像建筑行业的证书一样给其他公司挂靠赚取收入吗?
    软件工程师证书可以像建筑行业的证书一样给其他公司挂靠赚取收入吗?建筑行业证书大部分都可以挂靠每个月也有点收入软件行业的证书可以挂靠吗到什么地方挂靠 软件设计师证书如何挂靠?如果想要把自己的软件设计师证书挂靠出去,可以自己找所需证书的企业,也可以找中介......
  • Vulkan学习苦旅05:马不停蹄地渲染(创建交换链VkSwapchainKHR)
    通俗地说,渲染图像就是为图像的每个像素选择合适的颜色。例如,如果图像的分辨率为1920x1080,表示图像中有1920x1080个像素,渲染的过程,就是为每个位置的像素计算出合适的颜色。具体来说,假设每种颜色具有RGBA四个通道,且每个通道用1个字节表示(可以表示255种不同的情况),那么图像应当占据192......
  • Istio从入门到精通—— 安装 —— Kubernetes 删除 istio-system namesapce 时候,出现
    Kubernetes删除istio-systemnamesapce时候,出现Terminating解决办法当你在Kubernetes中遇到无法删除处于Terminating状态的命名空间时,可能是由于该命名空间中仍有活跃的资源或服务。要解决这个问题,你可以尝试以下几个步骤:一、常规方法检查命名空间中的活跃资源:......
  • 最高法-1. 挂靠关系下挂靠人向被挂靠人主张挂靠费用的,不予支持;2. 在相对人不知晓挂靠
    (2020)最高法民终576号  河南东方建设集团发展有限公司、黄建国建设工程施工合同纠纷二审民事判决书【经典判例】上诉人主张:【东方公司】(一)黄建国系东方公司内部工作人员,东方公司一直为其正常缴纳社会保险。《工程施工内部承包协议书》是东方公司与黄建国之间签订的独立合同,东......