本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com
前言
杀软的无奈是我准备开始写的一个新的专题文章,主要用来分享恶意代码分析过程用到的一些方法和工具,以及 shellcode 编写, shellcode 分析,metasploit 中的 shellcode 编码器的实现,编码器特征码定位,编码 shellcode 绕过杀软检测,基于 unicorn 检测 shellcode 编码器等相关的知识。文章中讲的案例会主要集中在 linux 平台中的 ELF 文件,但是由于个人的精力和知识水平有限,文章更新的频率和质量不太敢保证。如果有地方写的不太对,希望大佬们能够积极斧正,给与一些指导和帮助。
这是这个系列的第一篇文章,俗话说万丈高楼平地起,我们第一篇主要说一下我觉得非常实用的工具,在之后的文章中我的脚本会基于这些工具开发,并不会再介绍这些基础工具的使用。
相关工具的介绍
-
Capstone, 全能的反编译框架
-
Keystone, 全能的编译框架
-
IDAPython, 给 ida 神器再插上翅膀
-
unicorn, 基于 qemu 的模拟执行框架 (unicorn 官方版本不支持 SMC,我 patch 了一下相关代码 https://github.com/wonderkun/unicorn, 建议安装这个版本)
-
flare-emu, 基于 unicorn 的 ida 插件,能够快速帮你获取你不想读的代码的执行结果。
全能反汇编引擎 Capstone
Capstone 是一个非常优秀的反汇编框架,支持多种 CPU 架构的,而且提供多种语言的 api 接口,使用起来非常的简单方便,IDA,Radare2,Qemu 等著名项目都使用了 Capstone Engine。
源码地址:https://github.com/aquynh/capstone.git, 官方文档: http://www.capstone-engine.org/lang_python.html
一个简单的例子如下:
from capstone import *
CODE = b"\x55\x48\x8b\x05\xb8\x13\x00\x00"
md = Cs(CS_ARCH_X86, CS_MODE_64)
for i in md.disasm(CODE, 0x1000):
print("%d\t0x%x:\t%s\t%s\t%s" %(i.id,i.address, i.mnemonic, i.op_str,i.bytes.hex()))
初始化一个 Cs 类,需要有两个参数,分别是平台和架构模式
md.disasm 函数需要提供两个参数,第一个参数是需要分析的 bytes,第二个参数是基地址。返回一个生成器,遍历就可以得到每条指令的对象 CsInsn,它导出了与此条指令相关的很多属性,详细的解释如下:
全能的编译引擎 Keystone
与 Capstone 相对应的,那必然是 keystone 了,keystone 与 capstone 功能恰好恰好相反,是一个全能的支持多种架构的编译框架。源代码地址 https://github.com/keystone-engine/keystone, 官方文档地址 https://www.keystone-engine.org/docs/tutorial.html。
CODE = b"INC ecx; DEC edx" # separate assembly instructions by ; or \n
try:
# Initialize engine in X86-32bit mode
ks = Ks(KS_ARCH_X86, KS_MODE_32)
encoding, count = ks.asm(CODE)
print("%s = %s (number of statements: %u)" %(CODE, encoding, count))
except KsError as e:
print("ERROR: %s" %e)
可以看到,跟 Capstone 的使用方法非常类似。
IDAPython
ida 是逆向分析的神器,但是再加上 idapython 那就是给神器安装上翅膀,非常好用,关于 idapython 的 api 使用说明,可以读一下我的学习记录,里面有比较好的学习资料推荐。
flare-emu
是 fireEye 开源的一款基于 unicorn,并且直接可以再 ida 导入使用的代码模拟执行工具,这个工具对于我们利用 ida 分析恶意代码或者 shellcode 都非常的有用,特别是复杂的加密算法,或者是恶心的自解密代码。
关于这款工具的使用说明可以参考这篇翻译文章 https://wonderkun.cc/2020/03/02/%E7%94%A8%E6%A8%A1%E6%8B%9F%E6%89%A7%E8%A1%8C%E5%AE%9E%E7%8E%B0Objective-C%E4%BB%A3%E7%A0%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%88%86%E6%9E%90/
, 或者直接看源代码 https://github.com/fireeye/flare-emu,我当时修改了一个 python3 的版本用于支持 ida7.4,
详情见我的 githubhttps://github.com/wonderkun/flare-emu。
注意: 在 mac 平台上,ida 默认使用的 python 并不是是用 brew 安装的 python3,需要手工切换一下,切换方法可以参考 https://github.com/wonderkun/flare-emu#intall-on-mac。
pip 安装的 unicorn 可能不支持 python3,需要自己编译安装一下 unicorn。
unicorn
Unicorn 是一款基于 qemu 模拟器的模拟执行框架,支持 Arm, Arm64 (Armv8), M68K, Mips, Sparc, & X86 (include X86_64) 等指令集,为多种语言提供编程接口比如 C/C++、Python、Java 等语言。Unicorn 的 DLL 可以被更多的语言调用,比如易语言、Delphi,前途无量。它的设计之初就考虑到线程安全问题,能够同时并发模拟执行代码,极大的提高了实用性。
在后续分析 shellcode 的过程中,会遇到大量的 self-modify-code,unicorn 官方提供的版本是不支持 SMC 代码的,unicorn-engine/unicorn#820, 所以我参照网上的方法 patch 了一个版本 https://github.com/wonderkun/unicorn, 建议安装这个版本。就目前来看是够用的,但是官方还没有接受我的 pr,具体原因未知。
Unicorn 采用虚拟内存机制,使得虚拟 CPU 的内存与真实 CPU 的内存隔离。Unicorn 使用如下 API 来操作内存:
-
mem_map
-
mem_read
-
mem_write
使用 uc_mem_map 映射内存的时候,address 与 size 都需要与 0x1000 对齐,也就是 0x1000 的整数倍,否则会报 UC_ERR_ARG 异常。如何动态分配管理内存并实现 libc 中的 malloc 功能将在后面的课程中讲解。
Unicorn 的 Hook 机制为编程控制虚拟 CPU 提供了便利。
Unicorn 支持多种不同类型的 Hook。
大致可以分为 (hook_add 第一参数,Unicorn 常量):
- 指令执行类
UC_HOOK_INTR
UC_HOOK_INSN
UC_HOOK_CODE
UC_HOOK_BLOCK
- 内存访问类
UC_HOOK_MEM_READ
UC_HOOK_MEM_WRITE
UC_HOOK_MEM_FETCH
UC_HOOK_MEM_READ_AFTER
UC_HOOK_MEM_PROT
UC_HOOK_MEM_FETCH_INVALID
UC_HOOK_MEM_INVALID
UC_HOOK_MEM_VALID
- 异常处理类
UC_HOOK_MEM_READ_UNMAPPED
UC_HOOK_MEM_WRITE_UNMAPPED
UC_HOOK_MEM_FETCH_UNMAPPED
调用 hook_add 函数可添加一个 Hook。Unicorn 的 Hook 是链式的,而不是传统 Hook 的覆盖式,也就是说,可以同时添加多个同类型的 Hook,Unicorn 会依次调用每一个 handler。hook callback 是有作用范围的(见 hook_add begin 参数)。
python 包中的 hook_add 函数原型如下
def hook_add(self, htype, callback, user_data=None, begin=1, end=0, arg1=0):
pass
htype 就是 Hook 的类型,callback 是 hook 回调用;
callback 是 Hook 的处理 handler 指针。请注意!不同类型的 hook,handler 的参数定义也是不同的。
user_data 附加参数,所有的 handler 都有一个 user_data 参数,由这里传值。
begin hook 作用范围起始地址
end hook 作用范围结束地址,默认则作用于所有代码。
不同类型的 hook,对应的 callback 的参数也是不相同的,这里只给出 C 语言定义。
Python 编写 callback 的时候参考 C 语言即可(看参数)。
typedef void (*uc_cb_hookcode_t)(uc_engine *uc, uint64_t address, uint32_t size, void *user_data);
-
address: 当前执行的指令地址
-
size: 当前指令的长度,如果长度未知,则为 0
-
user_data: hook_add 设置的 user_data 参数
typedef void (*uc_cb_hookmem_t)(uc_engine *uc, uc_mem_type type,
uint64_t address, int size, int64_t value, void *user_data);
-
type: 内存操作类型 READ, or WRITE
-
address: 当前指令地址
-
size: 读或写的长度
-
value: 写入的值(type = read 时无视)
-
user_data: hook_add 设置的 user_data 参数
typedef bool (*uc_cb_eventmem_t)(uc_engine *uc, uc_mem_type type,
uint64_t address, int size, int64_t value, void *user_data);
-
type: 内存操作类型 READ, or WRITE
-
address: 当前指令地址
-
size: 读或写的长度
-
value: 写入的值(type = read 时无视)
-
user_data: hook_add 设置的 user_data 参数
-
返回值
返回真,继续模拟执行
返回假,停止模拟执行
- End -
精彩推荐
【技术分享】借助DefCon Quals 2021的mooosl学习
JBS工厂因网络攻击关停,真凶系REvil 勒索软件
【技术分享】vCenter Server CVE-2021-21985 RCE分析
【技术分享】MIPS架构—堆栈缓冲区溢出调试与利用
戳“阅读原文”查看更多内容