介绍
Qiling Framework 基于Unicorn,能够在一个平台上模拟多个OS和架构的二进制文件,包括Linux、MacOS、Windows、FreeBSD、DOS、UEFI和MBR。它支持x86(16、32和64位)、ARM、ARM64和MIPS。因此,我们几乎不需要担心因为环境搭建困难及手头设备不足导致无法进行分析工作,尤其像基于ARM或MIPS的IoT固件,想要进行逆向分析和漏洞挖掘尤其不易。
在澪同学的帖子用麒麟框架深入分析实模式二进制文件中提到了Qiling能够进行gdb远程调试,这实际上是我为Qiling所贡献的第一个功能。将调试能力赋予Qiling,我们就能够在完全可控的环境中对二进制文件进行动态分析,不必担心病毒样本对主机造成影响,能够绕过检测,甚至能够只执行其中的一部分。
但这样的调试还不够优雅,在这里我想要为大家介绍的是Qiling调试功能的全新模块:Qiling IDA插件。与当今最强大的反编译器结合,我们可以做很多很棒的事。不需要IDA remote server和虚拟机就能够轻松地在IDA上进行动态调试和反编译,实时查看寄存器,堆栈和内存,绘制代码的执行路径等。此外,结合自定义脚本,我们能够使用Qiling提供的全部功能。我们还有更高级的功能:自动分析并反ollvm控制流平坦化,这将在今后进行单独介绍。
文章同步发表于我的Blog
安装
安装Qiling
Qiling的安装方式在这里,由于Qiling更新速度较快,想要获取最新版本请git clone https://github.com/qilingframework/qiling/tree/dev
注意 请将Qiling安装在IDA所使用的Python3环境内。
安装插件
插件的安装方式有两种
作为一个IDA插件
只需要建立一个Qiling插件与IDA plugin目录的符号链接。
# Linux
ln -s /absolute/path/to/qiling/extensions/idaplugin/qilingida.py /path/to/your/ida/plugins/
# Macos
ln -s /absolute/path/to/qiling/extensions/idaplugin/qilingida.py /Applications/<Your IDA>/ida.app/Contents/MacOS/plugins/
# Windows
mklink C:\absolute\path\to\IDA\plugins\qilingida.py D:\absolute\path\to\qiling\extensions\idaplugin\qilingida.py
IDA在启动时会自动加载Qiling插件。
作为一个脚本文件
运行IDA,点击File/Script file...
,定位插件所在位置(也可以在此处下载最新版本),选择qilingida.py即可。
加载脚本后可以在Edit->Plugins->Qiling Emulator
和右键菜单找到按钮。
插件的具体用法请访问 https://docs.qiling.io/en/latest/ida/
支持状态
插件支持IDA7.x && Python3.6+, 当前建议使用环境macOS及Linux
插件支持环境如下:
|
8086 |
x86 |
x86-64 |
ARM |
ARM64 |
MIPS |
Windows (PE) |
- |
☑ |
☑ |
- |
☐ |
- |
Linux (ELF) |
- |
☑ |
☑ |
☑ |
☑ |
☑ |
MacOS (MachO) |
- |
☐ |
☑ |
- |
☐ |
- |
BSD (ELF) |
- |
☐ |
☑ |
☐ |
☐ |
☐ |
UEFI |
- |
☑ |
☑ |
- |
- |
- |
DOS (COM) |
☑ |
- |
- |
- |
- |
- |
MBR |
☑ |
- |
- |
- |
- |
- |
使用Qiling IDA插件解密Mirai
配置好插件后我们就可以开始分析本次的示例Mirai了。
视频展示
为此我录制了视频来展示整体流程:
bilibili版本
https://www.bilibili.com/video/BV1FK4y1a7SH
Youtube版本
样本分析
本次使用的样本是ARM架构的IoT僵尸网络病毒Mirai。
通过分析我们可以在0xFC50处发现一些被加密的字符串,程序会把它们添加到一个table,而这个table事实上是病毒用来爆破ssh的字典。
定位到0x12A20,我们会发现该函数正是用来加解密table的。
使用F5进行反编译,可以看到加解密算法为异或key,因此我们只需要将加密数据输入该函数即可得到解密后的明文。
分析解密循环的反汇编,我们可以得知,LR寄存器存放了待解密buffer的内存地址,因此我们只需要将加密数据写入该内存区域然后运行到循环结束就可以得到结果。
由于读写寄存器及内存的操作需要循环多次,我们手工操作会非常繁琐,因此Qiling IDA插件的自定义脚本就派上了用场。
编写插件自定义脚本
自定义脚本的基本结构如下:
class QILING_IDA():
def __init__(self):
pass
def custom_prepare(self, ql): # Qiling初始化时调用
pass
def custom_continue(self, ql:Qiling): # 点击“Continue”按钮时调用
hook = []
return hook
def custom_step(self, ql:Qiling, stepflag): # 点击“Step”按钮时调用
hook = []
return hook
对于本样例,脚本如下,脚本分析在注释中说明
import struct
from qiling import *
def show_encode_string_memory_address(ql:Qiling):
# 显示待解密buffer所在地址
memory_address_bytes = bytes(ql.mem.read(ql.reg.read('LR'), 0x4))
memory_address = hex(struct.unpack('<I', memory_address_bytes)[0])
print('encode_string_memory_address at: '+memory_address)
def hook_LR(ql:Qiling, encoded_message_ascii):
encode_string_length=len(encoded_message_ascii)
encode_bytes_length=encode_string_length.to_bytes(length=1, byteorder='big')
# 在内存中覆写待解密buffer的长度
ql.mem.write(ql.reg.read('LR')+0x4, encode_bytes_length)
memory_address_bytes = bytes(ql.mem.read(ql.reg.read('LR'), 0x4))
memory_address = hex(struct.unpack('<I', memory_address_bytes)[0])
memory_address = int(memory_address, 16)
# 将待解密buffer中的值替换为我们需要解密的数据
new_encode=b''
for x in encoded_message_ascii:
new_encode += x.to_bytes(length=1, byteorder='big')
ql.mem.write(memory_address, new_encode)
print('Encode: ', bytes(ql.mem.read(memory_address, len(encoded_message_ascii))))
def decode_show(ql, encoded_message_ascii):
# 读取待解密buffer中的值
encode_string_length=len(encoded_message_ascii)
memory_address_bytes = bytes(ql.mem.read(ql.reg.read('LR'), 0x4))
memory_address = hex(struct.unpack('<I', memory_address_bytes)[0])
memory_address = int(memory_address, 16)
print('Decode: ', bytes(ql.mem.read(memory_address, encode_string_length)).replace(b'T', b' '))
class QILING_IDA():
def __init__(self):
pass
def custom_prepare(self, ql):
ql.patch(0xF58C, b'\x90\x90\x90\x90\x90') # 将该地址的clock函数nop,否则无法继续模拟
# 读取加密数据,转换格式
encoded_message_bytes = bytes(ql.mem.read(0x1393C, 0x1395B-0x1393C))
encoded_message_ascii = []
for i in encoded_message_bytes:
encoded_message_ascii.append(i)
# 显示待解密buffer所在地址,便于在内存中查看结果
ql.hook_address(show_encode_string_memory_address, 0x12A68)
# 将加密数据写入LR指向的内存地址
ql.hook_address(hook_LR, 0x12A70, user_data=encoded_message_ascii)
def custom_continue(self, ql:Qiling):
# 读取加密数据,转换格式
encoded_message = bytes(ql.mem.read(0x1393C, 0x1395B-0x1393C))
encoded_message_ascii = []
for i in encoded_message:
encoded_message_ascii.append(i)
# 显示每循环一次后当前内存的解密状态
decode_show_hook = ql.hook_address(decode_show, 0x12AC8, user_data=encoded_message_ascii)
# 返回该hook的handle,插件将自动处理
hook = [decode_show_hook]
return hook
def custom_step(self, ql:Qiling, stepflag):
hook = []
return hook
初始化插件
右键-Qiling Emulator-Setup
设置rootfs(即环境根目录,Qiling会加载其中的lib,一般建议使用qiling/example/rootfs下对应环境的文件夹路径作为rootfs),本次分析样本还需要将ld-uClibc.so.0
放置在rootfs/lib目录下。
- 设置自定义脚本文件的路径。
如果Qiling初始化和加载自定义脚本成功,则IDA输出窗口显示如下内容
提示 在IDA 反汇编界面的基址位置会显示该文件所依赖的lib。
运行插件进行解密
回到加解密函数,我们在解密循环前设置断点,用于访问内存地址,并在循环结束后设置断点,因为不必继续模拟。
点击右键-Qiling Emulator-Continue,Qiling将模拟到第一个断点
我们得到了待解密buffer起始地址为0x1E258
右键-Qiling Emulator-View Memory,输入地址及要查看的内存大小,即可查看实时内存
同样的,在右键-Qiling Emulator中点击 View Register和View Stack可以查看寄存器及堆栈。整理窗口位置后如下
可以看出当前内存中已经写入了我们要解密的数据,在寄存器R2中则存储着将要异或的key。
再次点击Continue,进行解密
内存窗口中黄色字符串即为发生变化的内存,右侧输出窗口也实时显示了解密结果(动态效果参见视频)。
总结
本次我使用了一个较为简单的例子向大家演示了Qiling IDA插件的功能,但它的潜力远不仅此。未来我们也会继续添加更多可用功能,使其更加完整。
当然,在开发过程中也不可避免会出现bug,请到https://github.com/qilingframework/qiling/issues提issue。
同时我们也欢迎各位参与到开发中,提出pr。
如果有更多问题和想法,欢迎到gitter或QQ群: 486812017 进行交流。
觉得项目不错的话就请给个star吧~
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2020-9-15 00:12
被kabeor编辑
,原因: