从样本分析中学习病毒隐蔽启动过程
杰歪 发表于 浙江 技术文章 1083浏览 · 2024-11-13 09:17

进程注入

DLL注入

基本原理:将自定义DLL的路径传递给目标进程,并使目标进程调用 LoadLibrary 函数加载该DLL。

大致步骤:

获取远程进程的句柄:使用OpenProcess打开目标进程
分配内存空间:使用VirtualAllocEx在目标进程中分配内存
写入DLL路径:使用WriteProcessMemory将DLL路径写入目标进程的内存
加载DLL:使用CreateRemoteThread创建远程线程,调用LoadLibrary或CreateThread执行代码

代码示例:

#include <windows.h>

BOOL InjectDLL(DWORD dwProcessId, const char* szDllPath) {
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess == NULL) {
        return FALSE;
    }

    LPVOID lpRemoteMemory = VirtualAllocEx(hProcess, NULL, strlen(szDllPath) + 1, MEM_COMMIT, PAGE_READWRITE);
    if (lpRemoteMemory == NULL) {
        CloseHandle(hProcess);
        return FALSE;
    }

    if (!WriteProcessMemory(hProcess, lpRemoteMemory, szDllPath, strlen(szDllPath) + 1, NULL)) {
        VirtualFreeEx(hProcess, lpRemoteMemory, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }

    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, lpRemoteMemory, 0, NULL);
    if (hThread == NULL) {
        VirtualFreeEx(hProcess, lpRemoteMemory, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }

    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    VirtualFreeEx(hProcess, lpRemoteMemory, 0, MEM_RELEASE);
    CloseHandle(hProcess);
    return TRUE;
}

OpenProcess函数:

HANDLE OpenProcess(
  [in] DWORD dwDesiredAccess,  //对进程对象的访问
  [in] BOOL  bInheritHandle,  //是否继承进程的HANDLE
  [in] DWORD dwProcessId  //要打开的本地进程的标志符
);

VirtualAllocxEx函数:

LPVOID VirtualAllocEx(
  [in]           HANDLE hProcess,  //进程的句柄
  [in, optional] LPVOID lpAddress,  //为要分配的页面区域指定所需起始地址的指针
  [in]           SIZE_T dwSize,  //要分配的内存区域的大小
  [in]           DWORD  flAllocationType,  //内存分配的类型
  [in]           DWORD  flProtect  //内存保护
);

WriteProcessMemory函数:

BOOL WriteProcessMemory(
  [in]  HANDLE  hProcess,  //要修改的进程内存的句柄
  [in]  LPVOID  lpBaseAddress, //指向将数据写入到的指定进程中基址的指针
  [in]  LPCVOID lpBuffer, //指向缓冲区的指针,包含要写入指定进程的地址空间中的数据
  [in]  SIZE_T  nSize,  //要写入指定进程的字节数
  [out] SIZE_T  *lpNumberOfBytesWritten //指向变量的指针,该变量接收传输到指定进程的字节数
);

CreateRemoteThread函数:

HANDLE CreateRemoteThread(
  [in]  HANDLE                 hProcess,  //要在其中创建线程的进程句柄
  [in]  LPSECURITY_ATTRIBUTES  lpThreadAttributes, 
  [in]  SIZE_T                 dwStackSize, 
  [in]  LPTHREAD_START_ROUTINE lpStartAddress,
  [in]  LPVOID                 lpParameter,
  [in]  DWORD                  dwCreationFlags,
  [out] LPDWORD                lpThreadId
);

实验示例:

典型的DLL注入代码的模式,我们的主要任务主要是通过这些Windows API的参数获取到重要字符串:

  1. 注入了哪个目标进程
  2. 注入了哪些DLL的信息
  3. 注入的DLL要干什么

确定了我们的分析目标,就可以进行接下来的分析,

首先,尝试在OpenProcess函数中找到对进程对象的访问dwDesiredAccess

可以看到这个参数的值是ecx寄存器的值,而ecx的值又来自于上一条mov指令,向上追踪找到这块流程,

深入分析sub_401000函数,发现explorer.exe痕迹,仔细分析这段函数逻辑,主要实现了打开一个指定进程ID的进程,获取进程的某些信息,并进行字符串比较。如果字符串比较结果为 explorer.exe,则返回 1,否则返回 0。

回答第一个问题,目标进程就是explorer.exe

这里有个问题,我尝试直接通过OD在目标寄存器上打断点读取数值,但不知道为什么读取不到。

要回答第二个问题,主要是读取WriteProcessMemory函数的lpBuffer缓冲区的数值,

对eax寄存器追根溯源,

eax寄存器的值,即传入buffer中的值,就是在这调用了GetCurrentDirectoryA函数获取当前路径,并与Lab12-01.dll拼接传入。

后面就是将dll注入到本地进程中,并进行远程启动的过程。

可以在Procmon找到痕迹,用火绒剑更好。

第三个问题通过静态分析DLL或者直接运行EXE能得到答案,

直接载入DLL文件,直接查看IAT表,点进去看流程逻辑,这里省略了,

就是一个固定时间内循环弹出弹窗的小恶作剧了啦哈哈哈~

直接注入

基本原理:通过操作远程进程的内存,直接将代码(通常是shellcode)写入到目标进程的地址空间中,并且在目标进程中执行这些代码。

大致步骤:

获取远程进程的句柄:使用OpenProcess获取目标进程的句柄
分配内存空间:使用VirtualAllocEx在目标进程中分配内存
写入代码:使用WriteProcessMemory将代码写入内存空间
执行代码:使用 CreateRemoteThread 来在目标进程中创建新线程,并让这个线程执行注入的代码

tips:这里和DLL注入主要的区别在于代码执行的部分,DLL注入需要LoadLibrary等函数去导入并执行,主要依赖DLL文件,直接注入的代码可以直接运行,主要依赖于写入的内存。

实验示例:

以一段shellcode的直接注入为例,

我是直接使用交叉引用查找可疑API 函数,

这里跟前文一样分析,很快发现是想注入到iexplore.exe浏览器进程中,

我的目的是知道注入的什么东西,直接断到缓冲区的指针,发现shellcode缓冲区基址

查看00407030的汇编指令,IDA中反汇编失败,全是数据,这里存在Shellcode编码,只能动态调试,

单步调试几条指令后,发现这里存在一个循环,是shellcode解码的过程,

跳过循环,程序会进入到00407048攻击载荷部分,同时也可以发现,循环过程中这一段解码完成,

将解码完成的ShellCode尝试dump下来,

结构很简单,但是shellcode数据和代码混写的特性让IDA反汇编失败,这不影响我们分析,重点在于loc_4071B6sub_4070E3

loc_4071B6:

将单字节数据转换为双字节数据,得到ShellCode的哈希数据,

sub_4070E3:

这个函数将哈希数据传入以后,有一个明显的循环,怀疑是哈希处理函数,

ShellCode自包含特性:

1.寻找系统DLL文件基地址

2.自动解析符号标志

sub_4070C2和sub_407076函数就是完成了这两个过程,

sub_4070C2:

这个函数主要是找到了kernel32.dll文件的基地址,

具体的ShellCode如何在内存中找到Kernel32.dll过程,可以参考在网上找到的重要结构体神图。

简化版:

sub_407076:

重点也在循环部分,

jecxz: 若 ecx 为零,则跳转到 loc_4070B8,这是循环终止的条件。

dec ecx: 每次迭代减小 ecx 的值,直到它为零。

mov esi, [ebx+ecx\*4]: 从导入表中获取函数地址并储存在 esi 中。

add esi, ebp: 将 RVA 转为实际地址。

这个函数主要是完成了根据哈希数组去查找DLL文件中特定的导出表函数,并返回该函数的地址。

打断点调试,调试打断点,

可以看到ShellCode主要是找到Kernel32.dllWS2_32.dll文件,

主要解析这两个文件中的LoadLibraryACreateProcessATerminateProcessGetCurrentProcessWSAStartupWSASocketAconnet函数。

可以对ShellCode做一个行为分析:它想要创建一个新的线程,并且尝试创建套接字跟远程进程进行连接。

createprocess函数细节:

这里创建了一个cmd.exe进程,

connect函数细节:

02 00:协议类型。

34 12:源端口号:13330。

C0 A8 C8 02:IP 地址,可以转换为 192.168.200.2

总结:这个EXE可执行文件尝试将shellcode直接注入到浏览器进程中,进行反弹shell连接。

DLL劫持

基本原理:通过将DLL文件放在被目标程序加载的路径中,从而诱使程序加载并执行代码。

DLL文件的加载顺序:

一、Windows XP SP2之前 Windows查找DLL的目录以及对应的顺序:

1. 进程对应的应用程序所在目录;
2. 当前目录(Current Directory);
3. 系统目录(通过 GetSystemDirectory 获取);
4. 16位系统目录;
5. Windows目录(通过 GetWindowsDirectory 获取);
6. PATH环境变量中的各个目录;

二、在Windows xp sp2之后

Windows查找DLL的目录以及对应的顺序(SafeDllSearchMode 默认会被开启):

默认注册表为:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode, //其键值为1

dll文件加载顺序:

1. 进程对应的应用程序所在目录(可理解为程序安装目录比如C:ProgramFilesuTorrent);
2. 系统目录(即%windir%system32);
3. 16位系统目录(即%windir%system);
4. Windows目录(即%windir%);
5. 当前目录(运行的某个文件所在目录,比如C:DocumentsandSettingsAdministratorDesktoptest);
6. PATH环境变量中的各个目录;

三、Windows7以上

系统没有了SafeDllSearchMode 而采用KnownDLLs,那么凡是此项下的DLL文件就会被禁止从EXE自身所在的目录下调用,而只能从系统目录即SYSTEM32目录下调用,其注册表位置:

计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\KnownDLLs

根据dll文件的路径,去注册表里面找。

DLL劫持 vs DLL注入:

特点 DLL 劫持 DLL 注入
工作原理 利用目标程序的 DLL 加载路径漏洞,强制程序加载恶意 DLL 文件。 通过调用 API(如 CreateRemoteThread),将恶意 DLL 加载到目标进程的内存中,并执行其中的代码。
执行时机 恶意 DLL 会在目标程序启动时自动加载并执行。 恶意 DLL 可以在目标程序运行时随时加载并执行。
依赖条件 依赖于目标程序对 DLL 文件路径的搜索顺序漏洞,通常不需要修改目标程序的代码。 需要通过 API 操作目标进程的内存,通常需要更复杂的代码和攻击手段。
隐蔽性 相对隐蔽,因为恶意 DLL 会在程序启动时自动加载,不需要直接操作目标进程的内存。 相对显著,因为恶意代码需要通过注入手段操作目标进程的内存,可能更容易被检测。
控制能力 对目标程序的控制有限,通常只能够替换或篡改某些函数的行为。 可以完全控制目标进程,进行全面的行为修改、数据窃取等操作。
适用场景 适用于没有正确处理 DLL 路径的程序,攻击者通过将恶意 DLL 文件放置在程序可搜索到的位置来实现劫持。 适用于需要直接修改目标进程行为、执行恶意代码的场景,攻击者可以直接操控进程。

DLL劫持实现的方法多种多样,比如第三方软件劫持等等,我这里主要是对恶意软件的行为做分析,不详细介绍攻击手法。

了解了恶意程序做DLL劫持的基本原理,有助于我们遇到这种情况时去监控DLL加载路径、检查不匹配的DLL版本、分析程序行为和实时监控注册表及文件系统的变化。

进程替换

基本原理:直接用恶意代码替换目标进程的内存空间。

大致步骤:

挂起进程:利用CreateProcess设置进程CREATE_SUSPENDED挂起
释放内存:利用ZwUnmapViewOfSection或NtUnmapViewOfSection来取消映射目标进程的内存
分配内存:利用VirtualAllocEx为恶意软件分配新内存
写入内存:使用WriteProcessMemory将每个恶意软件的部分写入目标进程空间
恢复进程:使用SetThreadContext将entrypoint指向恶意代码,最后ResumeThread函数恢复进程

实验示例:

Sha256:eae72d803bf67df22526f50fc7ab84d838efb2865c27aef1a61592b1c520d144

在沙箱中的分析报告显示存在进程替换行为,

文件注入

PE注入

基本原理:将恶意代码或数据注入到PE文件的头部空白区域,例如DOS头、PE头或节表中,涉及到对PE文件结构的修改。

大致步骤:

打开PE文件:使用GetFileType函数找到目标PE文件
获取PE头:使用RtlImageNtHeader函数在PE图像中找到IMAGE_NT_HEADERS
插入代码:使用SetThreadContext函数设置代码上下文,并用WriteProcessMemory写入
修改PE头:使用SetFilePointer函数修改 PE 头的 AddressOfEntryPoint 字段,使其指向插入的代码

实验示例:

Sha256:ce8d7590182db2e51372a4a04d6a0927a65b2640739f9ec01cfd6c143b1110da


这个样本只有Antiy可以检测出DLLinject,

反射式注入

基本原理:将反射式加载器代码注入目标进程的内存,加载器负责将恶意代码映射到内存然后进行执行操作。

大致步骤:

打开进程、分配内存以及写入内存:OpenProcess,VirtualAllocEx,WriteProcessMemory
映射操作:CreateFileMapping,Nt/MapViewOfFile,memcpy,Nt/MapViewOfSection
执行代码:CreateThread,NtQueueApcThread,CreateRemoteThread,RtlCreateUserThread

总结

  • 完成了病毒启动时进程注入、文件注入以及进程替换的部分理论学习;
  • 详细分析了一段DLL注入的恶意代码和一段直接注入的恶意shellcode;
  • 用沙箱完成了进程替换和PE注入的分析;
0 条评论
某人
表情
可输入 255