Shellcode是现代恶意软件的主要组件之一,它最初是为了利用漏洞并在目标进程上运行代码而发明的。最近,它被更多地用作恶意软件组件,以击败简单的检测和分析。在APT攻击中使用的多阶段,高度混淆的shellcode是非常常见的。
即使有很多好的静态和动态分析工具和服务可用于观察恶意软件的行为,但某些恶意软件可能具有一些仅在特殊条件下才会表现出来的隐藏行为。某些恶意软件可能会检查虚拟环境的存在,需要使用工具来更深入地分析这些威胁。
在本文中,我们想讨论一种使用仿真框架分析Shellcode的方法。
内存工具仿真
这里介绍的方法是使用内存工具作为Shellcode仿真的基础。Shellcode本质上是独立的,并且在大多数情况下对于每个进程的特定环境都是中立的,许多模拟shellcode的实现都依赖于内置的内存结构。但是,从理论上讲,所有必需的内存组件都可以通过进程转储映像获得。诸如TEB / PEB,已加载的模块映像列表甚至DLL代码之类的相关内存结构应驻留在过程映像中。
ShellCodeEmulator是使用Unicorn框架进行仿真并使用Windows进程转储图像作为内存工具来源的框架。
目标Shellcode
为了演示此方法的工作原理,下面一个非常简单的Windows x64 shellcode示例,其SHA1哈希为33312f916c5904670f6c3b624b43516e87ebb9e3。
访问 PEB 结构
shellcode最重要的部分是访问PEB结构的部分。PEB是存储过程相关信息的过程环境块,可通过“ gs:[rdx]”存储位置访问PEB,“ rdx”设置为0x60,GS:60是PEB指针所在的位置。
seg000:0000000000000015 65 48 8B 32 mov rsi, gs:[rdx] seg000:0000000000000019 48 8B 76 18 mov rsi, [rsi+18h] seg000:000000000000001D 48 8B 76 10 mov rsi, [rsi+10h] seg000:0000000000000021 48 AD lodsq seg000:0000000000000023 48 8B 30 mov rsi, [rax] seg000:0000000000000026 48 8B 7E 30 mov rdi, [rsi+30h] seg000:000000000000002A 03 57 3C add edx, [rdi+3Ch] seg000:000000000000002D 8B 5C 17 28 mov ebx, [rdi+rdx+28h] seg000:0000000000000031 8B 74 1F 20 mov esi, [rdi+rbx+20h] seg000:0000000000000035 48 01 FE add rsi, rdi seg000:0000000000000038 8B 54 1F 24 mov edx, [rdi+rbx+24h]
x64平台的PEB结构的开始如下所示。偏移量为0x19的指令“ mov rsi,[rsi + 18h]”将从“ + 0x018 Ldr”指针中检索指针。
0:000> dt _PEB @$peb ntdll!_PEB +0x000 InheritedAddressSpace : 0 '' +0x001 ReadImageFileExecOptions : 0 '' +0x002 BeingDebugged : 0 '' +0x003 BitField : 0x4 '' +0x003 ImageUsesLargePages : 0y0 +0x003 IsProtectedProcess : 0y0 +0x003 IsImageDynamicallyRelocated : 0y1 +0x003 SkipPatchingUser32Forwarders : 0y0 +0x003 IsPackagedProcess : 0y0 +0x003 IsAppContainer : 0y0 +0x003 IsProtectedProcessLight : 0y0 +0x003 IsLongPathAwareProcess : 0y0 +0x004 Padding0 : [4] "" +0x008 Mutant : 0xffffffff`ffffffff Void +0x010 ImageBaseAddress : 0x00007ff6`5b530000 Void +0x018 Ldr : 0x00007fff`a2f253c0 _PEB_LDR_DATA +0x020 ProcessParameters : 0x00000250`36573480 _RTL_USER_PROCESS_PARAMETERS +0x028 SubSystemData : 0x00007fff`9d6b4440 Void +0x030 ProcessHeap : 0x00000250`36570000 Void +0x038 FastPebLock : 0x00007fff`a2f24fc0 _RTL_CRITICAL_SECTION +0x040 AtlThunkSListPtr : (null) ...
“ Ldr”指针具有以下数据结构,并且包含有关已加载的DLL模块的信息。通过这种结构,你可以访问DLL的基地址。该结构的“ InLoadOrderModuleList”成员具有已加载模块的链接列表。
0:000> dt _PEB_LDR_DATA ntdll!_PEB_LDR_DATA +0x000 Length : Uint4B +0x004 Initialized : UChar +0x008 SsHandle : Ptr64 Void +0x010 InLoadOrderModuleList : _LIST_ENTRY +0x020 InMemoryOrderModuleList : _LIST_ENTRY +0x030 InInitializationOrderModuleList : _LIST_ENTRY +0x040 EntryInProgress : Ptr64 Void +0x048 ShutdownInProgress : UChar +0x050 ShutdownThreadId : Ptr64 Void
基本上,shellcode依赖于遍历PEB.ldr结构来查找API。在这种情况下,它将检索第一个模块的基地址(通常是kernel32),并通过比较API哈希值找到WinExec API的位置。最终,shellcode将通过调用检索到的API指针来运行外部进程(calc.exe)。
GDT 和Unicorn框架
为shellcode提供执行环境的第一个挑战是构建虚拟FS / GS分段。在Unicorn框架上,你需要构建虚拟GDT整体。每个条目的选择器值需要写入每个段寄存器。
以下显示了GDT条目的结构,你需要使用适当的值为每个细分创建该条目。
在gdt.py中,GDT条目构建代码如下所示。
class Layout: def create_gdt_entry(self, base, limit, access, flags): gdt_entry = limit & 0xffff gdt_entry |= (base & 0xffffff) << 16 gdt_entry |= (access & 0xff) << 40 gdt_entry |= ((limit >> 16) & 0xf) << 48 gdt_entry |= (flags & 0xff) << 52 gdt_entry |= ((base >> 24) & 0xff) << 56 return struct.pack('<Q',gdt_entry)
完整的GDT构建代码如下所示。基本上,它使用create_gdt_entry来构建每个GDT条目并将GDT条目索引值分配给每个段,并将选择器值写入每个段寄存器。
def setup(self, gdt_addr = 0x80043000, gdt_limit = 0x1000, gdt_entry_size = 0x8, fs_base = None, fs_limit = None, gs_base = None, gs_limit = None, segment_limit = 0xffffffff): gdt_entries = [self.create_gdt_entry(0,0,0,0) for i in range(0x34)] if fs_base != None and fs_limit != None: gdt_entries[self.fs_index] = self.create_gdt_entry(fs_base, fs_limit , A_PRESENT | A_DATA | A_DATA_WRITABLE | A_PRIV_0 | A_DIR_CON_BIT, F_PROT_32) else: gdt_entries[self.fs_index] = self.create_gdt_entry(0, segment_limit, A_PRESENT | A_DATA | A_DATA_WRITABLE | A_PRIV_3 | A_DIR_CON_BIT, F_PROT_32) if gs_base != None and gs_limit != None: gdt_entries[self.gs_index] = self.create_gdt_entry(gs_base, gs_limit, A_PRESENT | A_DATA | A_DATA_WRITABLE | A_PRIV_3 | A_DIR_CON_BIT, F_PROT_32) else: gdt_entries[self.gs_index] = self.create_gdt_entry(0, segment_limit, A_PRESENT | A_DATA | A_DATA_WRITABLE | A_PRIV_3 | A_DIR_CON_BIT, F_PROT_32) gdt_entries[self.ds_index] = self.create_gdt_entry(0, segment_limit, A_PRESENT | A_DATA | A_DATA_WRITABLE | A_PRIV_3 | A_DIR_CON_BIT, F_PROT_32) gdt_entries[self.cs_index] = self.create_gdt_entry(0, segment_limit, A_PRESENT | A_CODE | A_CODE_READABLE | A_PRIV_3 | A_EXEC | A_DIR_CON_BIT, F_PROT_32) gdt_entries[self.ss_index] = self.create_gdt_entry(0, segment_limit, A_PRESENT | A_DATA | A_DATA_WRITABLE | A_PRIV_0 | A_DIR_CON_BIT, F_PROT_32) self.emulator.memory.map(gdt_addr, gdt_limit) for idx, value in enumerate(gdt_entries): offset = idx * gdt_entry_size self.emulator.memory.write_memory(gdt_addr + offset, value) self.emulator.register.write_register(UC_X86_REG_GDTR, (0, gdt_addr, len(gdt_entries) * gdt_entry_size-1, 0x0)) self.emulator.register.write_register(UC_X86_REG_FS, self.create_selector(self.fs_index, S_GDT | S_PRIV_0)) self.emulator.register.write_register(UC_X86_REG_GS, self.create_selector(self.gs_index, S_GDT | S_PRIV_3)) self.emulator.register.write_register(UC_X86_REG_DS, self.create_selector(self.ds_index, S_GDT | S_PRIV_3)) self.emulator.register.write_register(UC_X86_REG_CS, self.create_selector(self.cs_index, S_GDT | S_PRIV_3)) self.emulator.register.write_register(UC_X86_REG_SS, self.create_selector(self.ss_index, S_GDT | S_PRIV_0))
流程图
现在已经完成了Shellcode仿真的基本配置,下一步是从进程转储映像中提供适当的内存数据。只需你可以从notepad.exe进行内存转储。如果shellcode检查特定进程的进程名称或进程环境,则可能要对这些进程进行转储。它将为仿真提供更具体的内存环境。例如,使用Process Explorer从64位notepad.exe进行内存转储,并将其另存为notepad64.dmp。
ShellcodeEmulator使用PyKD解析并从进程转储映像中提取适当的组件。提取的组件包括PEB和LDR结构以及加载的DLL。当shellcode从DLL调用某些API时,将模拟提取的内存中的代码。你可以为潜在的API放置代码执行hook,shellcode将运行这些API来观察和修改行为。如果你不拦截任何API调用,则最终当满足syscall指令时,仿真将停止运行。目前,ShellcodeEmulator尚未为syscall指令提供仿真层。
ShellcodeEmulator
你需要使用Python 3.x在系统上进行git安装。
pip install git+https://github.com/ohjeongwook/ShellCodeEmulator
ShellCodeEmulator依赖windbgtool,可以使用以下命令进行安装。
pip install git+https://github.com/ohjeongwook/windbgtool --upgrade
使用方法
安装后,你可以提供'-d '选项提供进程转储映像文件名。
> python -m shellcode_emulator.run Usage: run.py [options] args Options: -h, --help show this help message and exit -b IMAGE_BASE, --image_base=IMAGE_BASE Image base to load the shellcode inside process memory -d DUMP_FILENAME, --dump_filename=DUMP_FILENAME A process dump file from normal Windows process -l LIST_FILENAME, --list_filename=LIST_FILENAME A list filename generated by IDA (this can be used instead of shellcode filename)
以下命令显示如何使用64位记事本过程映像运行33312f916c5904670f6c3b624b43516e87ebb9e3.bin shellcode文件。
python -m shellcode_emulator.run 33312f916c5904670f6c3b624b43516e87ebb9e3.bin -d notepad64.dmp
模拟仿真
当你模拟shellcode时,它将显示shellcode执行“ kernel32!WinExec” API。
* Setting up gs: 754d475000 (len=2000) Writing shellcode to 7ff65b54ac50 (len=6a) notepad!WinMainCRTStartup: 7FF65B54AC50: 50 push rax rax: 00000000 ebx: 00000000 ecx: 00000000 edx: 00000000 rsp: 754D87F000 rbp: 754D87F000 rsi: 00000000 rdi: 00000000 rip: 7FF65B54AC50 fs: 00000070 gs: 0000007B cs: 0000008B ds: 00000083 es: 00000000 notepad!WinMainCRTStartup+0x1: +00000001: 51 push rcx rax: 00000000 ebx: 00000000 ecx: 00000000 edx: 00000000 rsp: 754D87EFF8 rbp: 754D87F000 rsi: 00000000 rdi: 00000000 rip: 7FF65B54AC51 fs: 00000070 gs: 0000007B cs: 0000008B ds: 00000083 es: 00000000 kernel32!WinExec: 7FFFA2D2F0E0: 48 8b c4 mov rax, rsp kernel32!WinExec: 7FFFA2D2F0E0: 48 8b c4 mov rax, rsp kernel32!WinExec: 7FFFA2D2F0E0: 48 8b c4 mov rax, rsp kernel32!memset: 7FFFA2CF2E67: ff 25 db 7c 05 00 jmp qword ptr [rip + 0x57cdb] kernel32!memset: 7FFFA2CF2E67: ff 25 db 7c 05 00 jmp qword ptr [rip + 0x57cdb] kernel32!memset: 7FFFA2CF2E67: ff 25 db 7c 05 00 jmp qword ptr [rip + 0x57cdb] ntdll!memset: 7FFFA2E65380: 48 8b c1 mov rax, rcx ntdll!memset: 7FFFA2E65380: 48 8b c1 mov rax, rcx ntdll!memset: 7FFFA2E65380: 48 8b c1 mov rax, rcx KERNELBASE!CreateProcessA: 7FFF9FA0C170: 4c 8b dc mov r11, rsp KERNELBASE!CreateProcessA: 7FFF9FA0C170: 4c 8b dc mov r11, rsp KERNELBASE!CreateProcessA: 7FFF9FA0C170: 4c 8b dc mov r11, rsp KERNELBASE!CreateProcessInternalA: 7FFF9FA0C1F0: 4c 89 4c 24 20 mov qword ptr [rsp + 0x20], r9 KERNELBASE!CreateProcessInternalA: 7FFF9FA0C1F0: 4c 89 4c 24 20 mov qword ptr [rsp + 0x20], r9 KERNELBASE!CreateProcessInternalA: 7FFF9FA0C1F0: 4c 89 4c 24 20 mov qword ptr [rsp + 0x20], r9
ShellcodeEmulator的当前实现侧重于用于Windows Shellcode的非常常见的API,但是可以通过修改代码轻松地对其进行扩展。
https://github.com/ohjeongwook/ShellCodeEmulator/blob/35904fc088cba7b8246d11e5def49675f58ae25c/shellcode_emulator/api.py#L85
分析总结
ShellcodeEmulator是一个基本框架,可以轻松扩展以支持许多不同种类的Shellcode仿真。因为它不依赖于硬编码的PEB或模型结构,所以可以轻松地为不同的shellcode设置不同的内存环境。某些shellcode可能需要特殊的环境,你只需提供与配置文件匹配的适当内存转储即可轻松提供环境。广泛的API仿真仍在进行中,但是作为一种研究工具,它易于使用并且可以作为仿真的一个很好的例子,Unicorn框架可以应用于现实生活中的防御性分析工作。
本文翻译自:https://darungrim.com/research/2020-06-04-UsingMemoryArtifactsAsShellcodeEmulationEnvironment.html如若转载,请注明原文地址: