安全运营内刊—威胁分析与响应能力—Shellcode分析与检测技巧

攻击溯源
1年前

01

shellcode简介


shellcode是一段用于利用软件漏洞而执行的代码,shellcode为16进制的机器码,因为经常让攻击者获得shell而得名。shellcode常常使用机器语言编写。可在暂存器eip溢出后,塞入一段可让CPU执行的shellcode机器码,让电脑可以执行攻击者的任意指令。


1.1  依赖项


    因Shellcode有一些依赖关系,所以编写Shellcode存在一定难度。

    • 长度 - 因为 shellcode 利用内存中的特定漏洞,所以序列需要尽可能的高效。这意味着攻击者必须使其长度适合于缓冲区的大小,所有的指令都将在内存空间中运行。如果shellcode的长度大于缓冲区的大小,可能造成程序崩溃或甚至被非法利用(如缓冲区溢出等)。

    • 不允许的字符 - 当shellcode 中出现‘\r’, ‘\n’,0x00等字符时,代码将终止运行。例如,当'Null Bytes'(如0x00值)被读取时,CPU会将它识别为字符串的结束(Null Terminator)。


    1.2  Unix Shellcode


    Unix操作系统提供了直接访问通信和管理内核的途径—指令int 0x80。因此,当系统调用跟随该指令设置时,shellcode 将直接被赋予以高权限执行的能力。


    1.3  Windows Shellcode


    在基于Windows的操作系统中,由于内存保护机制,如ASLR(地址空间布局随机化)等,创建shellcode比较困难。


    02

    内存中查找 API 函数的地址


    一般情况下,shellcode会被注入到一个正在运行的进程中,没有任何关于内存状态以及 API 函数地址的预先知识,据此可以得出结论:shellcode不能以静态地址为基础。

    如上所述,shellcode不能使用Call CreateProcessA或jmp sub_40100000这样的指令,必须独立运行才能找到所需的 API 函数并手动解析地址。换句话说,要找到使用的DLL需要基于内存中对象的结构。这也是它被称为位置无关代码(PIC)的原因。

    示例:要调用GetProcAddress,shellcode 需要找到Kernel32.dllDLL 的地址。

    操作流程如下:


    2.1  PEB - 过程环境块


    因PEB 对象总是被加载到内存的同一个地址FS:[0x30],所以 shellcode 可以使用。


    执行步骤:

    1. 转到 PEB 对象。

    2. 传递PEB_LDR_DATA对象到InMemoryOrderModuleList链表,其中包含有关进程加载模块的信息。在此之后,shellcode 将保存所需模块(通常Kernel32.dll是第三个对象)。

    3. DllBase 使用该字段查找所需 DLL 的基地址。

    4. 找到它的导出表。

    5. 找到想要的功能(如GetProcAddress)。

    6. 使用函数获取函数的地址GetProcAddress。

    7. 最终,使用合适的参数运行想要的功能。


    2.2  使用SEH—结构化异常处理


    该技术是通过输入TIB(线程信息块)的第一个属性来访问SEH链的底部,它有一个常数地址--FS:[0x0]。该地址包含Kernel32.dll模块的默认异常处理程序。要定位所需模块的地址,可以返回内存地址,直到找到模块的入口点为止。(使用MZ签名或0x5A4D为例)。


    2.3  使用TEB——线程环境块


    与 SEH 技术一样,可以访问 TEB 对象,该对象在内存中有固定的位置 — FS:[0x18]. 通过这个对象可以进入 SEH 链,如上所述。


    2.4  TopStack


    该方法依赖于在堆栈中具有所需 API 函数的所需 DLL 的地址,所以并不常见。


    2.5  通过哈希查找API 函数


    这种技术也被称为SFHA(Stephen Fewer’s Hash API)。它使用4个字节来表示DAX寄存器中的DLL!WinAPI内部函数的Hash值。然后向该地址发送JMP调用该函数。需要注意哪个函数从EAX被调用,以及得到了哪些参数(可以在MSDN中查看)。接下来比较内存中的实际值。例如,如果一个shellcode调用Win_Exec函数,将通过每一个DLL!WinApi使用其他技术之一,并带有其他参数(可以通过寄存器调用前的PUSH指令识别)。当输入参数的内存地址时就可以知道该地址的内容是什么(也许是PS1脚本或编码命令)。


    03

    shellcode检测


    对shellcode有所了解之后,下面探讨如何检测 shellcode。

    首先注意去掉混淆,解密一个混淆或加密的脚本,以暴露出shellcode真实代码。


    3.1  使用行为模式


    如上所述,shellcode 是表示指令的 Opcodes 序列。因此可以找到这个序列并对其进行检查。

    如果有恶意程序的源代码运行 shellcode(如 PowerShell 或 JavaScript),可以寻找可能代表Opcodes的值/变量/字符串,该方式有利于找到shellcode本身。

    下面来看这条指令:mov ebp, esp。shellcode使用的字符序列可以有多种格式:

    • 十六进制值:8B EC

    • 反斜杠十六进制值:\x8B\EC

    • Unicode 百分比: %u8B%EC

    • 反斜杠 Unicode:\u8B\uEC

    • Array:[0x8B, 0xEC]

    重点关注这些字符的序列可以代表 CPU 指令。


    04

    转储 shellcode


    在检测到潜在的 shellcode 后,将其转储到二进制文件是第一步。

    • base64dump.py工具 — 允许查找可能包含 shellcode 的部分并将其转储到二进制文件 ( .bin) 中,以便对其进行分析

    • 参数:

    1. pu — 允许搜索用于百分比 Unicode 编码 ( %u) 的字符串。

    2. bu — 允许搜索用于反斜杠 Unicode 编码 ( \u) 的字符串。

    3. hex — 允许搜索用作十六进制值的字符串。

    4. base64 — 允许使用 Base64 编码搜索字符串。


    base64dump.py -e {param} {file} -s {sectionID} -d > {outFile.bin}

    允许通过 ID 定位想要的部分,并将其转储到二进制文件中。大多数情况下将选择最大的部分


    base 64 dump . py { param }{ file }- s { section ID }- a

    显示所选部分的十六进制视图。

    • objdump.pytool — 允许在提取 shellcode 时转储。

    • Using a Hex-Editor — Hex-Editor 可用于删除不相关的部分,剩下的 shellcode代码可以保存到.bin文件中。


    05

    如何分析shellcode


    可以通过两种主要方式分析 shellcode :静态分析和动态分析。


    5.1  静态分析——代码分析


    通过静态分析,包含 shellcode 的二进制文件将被转换为可执行文件,并被加载到反汇编程序(如 IDA PRO)中。然后被分析为可执行文件。

    用法:



    shellcode2exe.py {file.bin}shellcode2exe.bat {32\64} {file.bin} {file.exe}


    5.2  动态分析


    scdbg工具--允许模拟shellcode的执行,以便找到它所使用的API函数,这可以表明shellcode的许多行为。


    scdbg -s {steps} -f {file.bin}
    • 运行GUI scdbg工具并向其启动二进制文件。

    • 使用FindSC将在加载的二进制文件中找到shellcode的开头。

    jmp2it工具--允许在一个专门的进程中执行shellcode,该进程作为可执行文件的 "外壳"。因此可以将调试器附加到运行的进程中,便于分析人员调试shellcode。

    • jmp2it {file.bin} {offset}—offset表示 shellcode 从二进制入口点的偏移量(0x0用于开始)。

    shellcode_launcher.exe--与之前的工具类似。