APC挂靠

news/2025/1/12 1:02:06/文章来源:https://www.cnblogs.com/murkuo/p/18199541

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);//插入外部线程apcPETHREAD 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);//插入apcBOOLEAN 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 tobe continued if it is currently in an alertable Wait state. Otherwiseit 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 isto be alerted.Return Value:The previous state of the alerted variable for the specified processormode.--*/{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.////接下来是切换线程,必然会触发apcKiUnlockDispatcherDatabaseFromSynchLevel();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);//插入外部线程apcPETHREAD 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;//插入apcBOOLEAN isRet = KeInsertQueueApc(pKapc, NULL, NULL, 0);//更改线程的apc并且通过切换线程唤醒apcKeAlertThread(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);//插入外部线程apcPETHREAD 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);//插入apcBOOLEAN isRet = KeInsertQueueApc(pKapc, NULL, NULL, 0);//更改线程的apc并且通过切换线程唤醒apcKeAlertThread(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,第二是因为系统也会挂靠

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/708313.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java(7)-Maven抽取公共模块构建jar包

前提假设:项目中有两个Moudle,分别是Moudle1和Moudle2,它们有些代码是相同的,比如相同的类和API,重复书写当然可以正常运行,但是我们可以用maven打包成jar包,其他Moudle直接引用即可。 步骤 1.新建一个Module-common pox.xml 中配置 Module1 和 Moudle2 同样使用的依赖:…

KPCR进程概念

1.KPCR进程概念 KPCR 介绍 KPCR 是CPU的控制结构 FS段寄存器在R0(FS=0x30)的时候指向KPCR结构 FS 段寄存器在R3(FS=0x3b)的时候指向 当前线程的TEB(线程) 线程结构是运行在CPU上面,所以线程结构是放在CPU上的 kd> dt _KPCR ntdll!_KPCR+0x000 NtTib : _NT_TIB…

驱动断链

03驱动断链获取驱动信息的途径是从Driver和FileSystem两个目录下获得的正常情况下我们自己做驱动遍历只能便利Driver下的所有驱动,也就是驱动链表里的驱动 分析 这里我们F12进去找一下DRIVER_OBJECT的结构 typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT; WinDbg里dt一下 kd&…

蓝屏分析

04蓝屏分析 直接上蓝屏代码 #include <ntifs.h>VOID Unload(PDRIVER_OBJECT pDriver) {DbgPrint("Driver Unload\r\n"); }NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg) {pDriver->DriverUnload = Unload;//DbgBreakPoint();DbgPrin…

Qt学习第二篇(基本小组件的使用)

Qt_2 小部件是 GUI 的基本元素。 它也称为UI 控件。 它接受来自底层平台的不同用户事件,如鼠标和键盘事件(以及其他事件)。 我们使用不同的小部件创建 UI。 曾经有一段时间,所有的 GUI 控件都是从头开始编写的。 Qt 小部件通过开发具有现成的 GUI 控件的桌面 GUI 来缩短时间,…

Weblogic T3反序列化漏洞(CVE-2018-2628)

在Weblogic中RMI通信的实现是使用T3协议,并且在T3的传输过程中,和RMI一样,会进行序列化和反序列化的操作。目录前言T3协议概述漏洞复现修复方案 前言 WebLogic Server 是一个企业级的应用服务器,由Oracle公司开发,支持完整的Java EE规范,包括EJB、JSP、Servlet、JMS等,适…

方方方的数据结构

总算给我看懂到底是什么意思了。。。 首先我们来考虑按照时间+扫描线进行处理,假设操作如下黑色是加操作,黄色是乘操作,绿色是加操作,对于红色那条线所代表的点,随着时间的流逝,首先在刚刚进入黑色的时候,这一点的值就被加上了一个数,然后刚刚进入黄色的时候,这一点的…

手机硬件检测:-DeviceTest

手机硬件检测:Z-DeviceTest官方版是款针对手机硬件所打造的检测工具。手机硬件检测:Z-DeviceTest能够检测硬件和OS,硬件上不仅仅是电池、cpu、内存、OS,甚至连usb、扬声器、指南针、摄像头、GPS、听筒等都能检测。并且手机硬件检测:Z-DeviceTest还能够对市面众多的机型进行检…

OutOfMemoryError

以下的这段代码应该是报错的才对,但是我在运行了之后,程序一直卡在那里。最后请教老师了解到,原来jvm如果不指定运行参数,是会进行自动扩容的。 package com.coding.jvm.oom;public class NativeErrorDemo {public static void main(String[] args) {for (; ; ) {new Threa…

【工具使用】【Shell脚本】【gitlab】下拉所有的仓库代码

1 前言 电脑重置了或者新的项目代码,仓库里二三十个,一个一个拉属实有点拉跨,今儿空了整了个脚本,可以拉下所有的仓库代码。 2 前置 需要装一个解析 json的,windows 的话可以直接下载:下载,mac的话可以再官网下载:官网地址。 然后加入到 PATH 下,效果如下:3 脚本 …

app测试工具monkey

Monkey是Android中的一个命令行工具,可以运行在模拟器或者实际设备中向系统发送伪随机的用户事件流(按键输入、触摸屏输入、手势输入等) 实现对正在开发的应用程序进行测试; Monkey测试之一种为了测试软件的稳定性、健壮性的快速有效的方式 一、什么是Monkey 顾名思义,M…

Markdown的学习笔记

Markdown (#+空格输入后回车自动形成大标题) 字体 (##+空格输入回车形成二级标题,以此类推,最多6级) hello world!(两边加两个*好为粗体) hello world!(两边一个*号为斜体) hello world!(三个*号斜体加粗) hello world!(两边两个~号删除线) 引用(大于>+空格形…