Qiling 模拟执行xwings在ZeroNights 2019中曾提及,Qiling模拟执行框架开发出来的目的是为了模拟执行各种操作系统中的shellcode,以应对复杂度不断增长的shellcode。作为一个二进制分析和模拟执行框架,它支持跨平台(如Windows 和Linux),多架构(如ARM和x86),和多种文件格式(如ELF和Portable Executables)。 PE代码覆盖率1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py
index b6e46c8e..3375bbf2 100644
--- a/qiling/loader/pe.py
+++ b/qiling/loader/pe.py
@@ -87,15 +87,20 @@ class Process():
self.ql.nprint("[+] Cached %s" % path)
dll_base = self.dll_last_address
+
dll_len = self.ql.os.heap._align(len(bytes(data)), 0x1000)
self.dll_size += dll_len
self.ql.mem.map(dll_base, dll_len, info=dll_name)
self.ql.mem.write(dll_base, bytes(data))
self.dll_last_address += dll_len
+
# add dll to ldr data
self.add_ldr_data_table_entry(dll_name)
+ # add DLL to coverage images
+ self.images.append(self.QlImage(dll_base, dll_len, path))
+
self.ql.nprint("[+] Done with loading %s" % path)
return dll_base
@@ -328,6 +333,8 @@ class QlLoaderPE(QlLoader, Process):
self.pe_image_address = self.pe_image_address = self.pe.OPTIONAL_HEADER.ImageBase
self.pe_image_address_size = self.pe_image_address_size = self.pe.OPTIONAL_HEADER.SizeOfImage
+ self.images.append(self.QlImage(self.pe_image_address, self.pe_image_address + self.pe_image_address_size, self.path))
|
这部分代码可以为任意loader加载起来的PE内存镜像提供代码覆盖率信息(也包括DLL依赖),当追踪开启时,可通过block_callback这一函数回调打印出每个基本块的执行日志。 1
2
3
4
5
6
| def block_callback(ql, address, size, self):
for mod_id, mod in enumerate(ql.loader.images):
if mod.base <= address <= mod.end:
ent = bb_entry(address - mod.base, size, mod_id)
self.basic_blocks.append(ent)
break
| 接触Dragon(Dance)在IDA Pro和二进制Ninja中的可视化代码覆盖率功能,其事实标准是一个叫作Lighthouse的插件。如果你使用上述两种反汇编器,我强烈推荐你使用它。在我的业余时间,我一直在尝试使用Ghidra,它有一个很棒的反汇编器和反编译器,并且它还提供了大量IDA Pro缺少的功能。一旦你习惯了它的用法,你会发现它是一个非常棒的自由开源的逆向工具。我偶然间发现了一个叫做Dragon Dance的插件,它能在Ghidra中提供和Lighthouse相似的代码覆盖率可视化功能,更重要的是,它支持本文中提到的DRCOV追踪格式。它支持大量的内置引用,能够让你做到诸如在高亮语法中进行diff追踪这样的功能。下面是从README中摘录的一段文字,介绍如何用这些函数来实现脚本接口。
利用Qiling框架实现带有代码覆盖率信息的PE文件模拟执行
除此之外,Dragon Dance支持还有修复功能,它本质上是将二进制文件中的代码覆盖率完整性与Ghidra反汇编出的指令进行比较。如果这两者不同,插件会提示用户修复Ghidra缺失的部分。下面也是从README中摘录的一段介绍:
利用Qiling框架实现带有代码覆盖率信息的PE文件模拟执行
组合所有功能现在我们已经能够追踪PE覆盖率,而且能够在逆向工程软件中将它可视化。接下来看一个例子,Qiling附带了一个WannaCry的二进制文化,我们用它来进行举例。首先,要想安装它,我需要依照Dragon Dance中的编译步骤将它编译出来,编译步骤非常简单。编译并安装之后,就可以在Ghidra中使用它了。我运行qltool并启用追踪功能来对WannaCry进行分析。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| # python qltool run --disasm -c wannacry-trace.cov -f examples/rootfs/x86_windows/bin/wannacry.bin --rootfs examples/rootfs/x86_windows/
[+] Initiate stack address at 0xfffdd000
[+] Loading examples/rootfs/x86_windows/bin/wannacry.bin to 0x400000
[+] PE entry point at 0x409a16
[+] TEB addr is 0x6000
[+] PEB addr is 0x6044
[+] Loading examples/rootfs/x86_windows/Windows/SysWOW64/ntdll.dll to 0x10000000
[+] Done with loading examples/rootfs/x86_windows/Windows/SysWOW64/ntdll.dll
[+] Loading examples/rootfs/x86_windows/Windows/SysWOW64/kernel32.dll to 0x10141000
[+] Done with loading examples/rootfs/x86_windows/Windows/SysWOW64/kernel32.dll
[+] Loading examples/rootfs/x86_windows/Windows/SysWOW64/advapi32.dll to 0x10215000
[+] Done with loading examples/rootfs/x86_windows/Windows/SysWOW64/advapi32.dll
[+] Loading examples/rootfs/x86_windows/Windows/SysWOW64/ws2_32.dll to 0x102b6000
[+] Done with loading examples/rootfs/x86_windows/Windows/SysWOW64/ws2_32.dll
[+] Loading examples/rootfs/x86_windows/Windows/SysWOW64/msvcp60.dll to 0x102eb000
[+] Done with loading examples/rootfs/x86_windows/Windows/SysWOW64/msvcp60.dll
[+] Loading examples/rootfs/x86_windows/Windows/SysWOW64/iphlpapi.dll to 0x10351000
[+] Done with loading examples/rootfs/x86_windows/Windows/SysWOW64/iphlpapi.dll
[+] Loading examples/rootfs/x86_windows/Windows/SysWOW64/wininet.dll to 0x1036d000
[+] Done with loading examples/rootfs/x86_windows/Windows/SysWOW64/wininet.dll
[+] Loading examples/rootfs/x86_windows/Windows/SysWOW64/msvcrt.dll to 0x10462000
[+] Done with loading examples/rootfs/x86_windows/Windows/SysWOW64/msvcrt.dll
-snip-
[+] 0x408171 50 push eax
[+] 0x408172 50 push eax
[+] 0x408173 50 push eax
[+] 0x408174 6a 01 push 1
[+] 0x408176 50 push eax
[+] 0x408177 88 44 24 6b mov byte ptr [esp + 0x6b], al
[+] 0x40817b ff 15 34 a1 40 00 call dword ptr [0x40a134]
[+] 0x1039c18e 8b ff mov edi, edi
InternetOpenA(lpszAgent = 0x0, dwAccessType = 0x1, lpszProxy = 0x0, lpszProxyBypass = 0x0, dwFlags = 0x0)
[+] 0x408181 6a 00 push 0
[+] 0x408183 68 00 00 00 84 push 0x84000000
[+] 0x408188 6a 00 push 0
[+] 0x40818a 8d 4c 24 14 lea ecx, [esp + 0x14]
[+] 0x40818e 8b f0 mov esi, eax
[+] 0x408190 6a 00 push 0
[+] 0x408192 51 push ecx
[+] 0x408193 56 push esi
[+] 0x408194 ff 15 38 a1 40 00 call dword ptr [0x40a138]
[+] 0x103b00f1 8b ff mov edi, edi
InternetOpenUrlA(hInternet = 0x0, lpszUrl = "http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com", lpszHeaders = 0x0, dwHeadersLength = 0x0, dwFlags = 0x84000000, dwContext = 0x0)
[+] 0x40819a 8b f8 mov edi, eax
[+] 0x40819c 56 push esi
[+] 0x40819d 8b 35 3c a1 40 00 mov esi, dword ptr [0x40a13c]
[+] 0x4081a3 85 ff test edi, edi
[+] 0x4081a5 90 nop
[+] 0x4081a6 90 nop
[+] 0x4081a7 ff d6 call esi
[+] 0x10387b49 8b ff mov edi, edi
InternetCloseHandle(hInternet = 0x0) = 0x1
-snip-
|
传递给qltool的几个选项功能如下: - --disasm对所有执行的指令进行反汇编
- -c显示DRCONV输出的trace文件路径
如你所见,在我们模拟执行的过程中,这个臭名昭著的开关URLhxxp://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea[.]com被传给了InternetOpenUrlA。接下来,我们使用Dragon Dance把trace文件导入Ghidra
中,看看它的结果,如果已经安装了插件,可以从Ghidra的Window下拉菜单中打开插件窗口。
利用Qiling框架实现带有代码覆盖率信息的PE文件模拟执行
打开Dragon Dance窗口之后,使用绿色的+按键添加一个trace文件。
利用Qiling框架实现带有代码覆盖率信息的PE文件模拟执行
文件添加好后,在文件上右键,点击Switch To进入这个文件。
利用Qiling框架实现带有代码覆盖率信息的PE文件模拟执行
经过上述操作后,所有可执行/模拟的区域都已经高亮显示了,在这里我们能看到WannaCry调用的开关函数。
利用Qiling框架实现带有代码覆盖率信息的PE文件模拟执行
如果上述步骤你都跟过来了,你会发现WannaCry最后结束在sprintf函数中。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| [+] 0x4080a5 ff 15 2c a1 40 00 call dword ptr [0x40a12c]
[+] 0x104b5aa9 b8 e4 30 ff 6f mov eax, 0x6fff30e4
__p___argc() = 0x5053d44
[+] 0x4080ab 83 38 02 cmp dword ptr [eax], 2
[+] 0x4080ae 7d 09 jge 0x4080b9
[+] 0x4080b0 e8 6b fe ff ff call 0x407f20
[+] 0x407f20 e8 1b fd ff ff call 0x407c40
[+] 0x407c40 81 ec 04 01 00 00 sub esp, 0x104
[+] 0x407c46 8d 44 24 00 lea eax, [esp]
[+] 0x407c4a 57 push edi
[+] 0x407c4b 68 60 f7 70 00 push 0x70f760
[+] 0x407c50 68 30 13 43 00 push 0x431330
[+] 0x407c55 50 push eax
[+] 0x407c56 ff 15 0c a1 40 00 call dword ptr [0x40a10c]
[+] 0x1047f354 8b ff mov edi, edi
[!] sprintf Exception Found
[!] Emulation Error
-snip-
File "/qiling/qiling/os/windows/windows.py", line 115, in hook_winapi
raise QlErrorSyscallError("[!] Windows API Implementation Error")
qiling.exception.QlErrorSyscallError: [!] Windows API Implementation Error
|
我们可以看到,在msvcrt.dll中调用sprintf的代码后面,指令都没有高亮了:
利用Qiling框架实现带有代码覆盖率信息的PE文件模拟执行
我们可以用Dragon Dance可视化msvcrt.dll的依赖,也就是它本身的代码覆盖率。以此找到异常发生的地方:
利用Qiling框架实现带有代码覆盖率信息的PE文件模拟执行
这是我碰到的Qiling的一个局限,因为Qiling使用Unicorn执行机器码的基本块。当执行某个基本块时发生了异常(也就是本例中的场景),它不会触发日志回调。尽管如此,你还是可以用这个可视化功能来寻找问题发生的地方。如果我们想要程序继续执行,有两种方案: - 找到msvcrt.dll中的sprintf执行失败并触发Windows API Implementation Error的原因。
- 使用Qiling自己实现这个API
前者要求我们逆向一个Windows DLL,后者需要我们弄清楚sprintf的原理并用Qiling实现它。
总结这是一个调试模拟输出的基本例子,因为从qltool的输出中我们能够很明显地看出到底发生了什么,然而,想要可视化一些复杂例子中的执行路径,其开销是非常大的,因为很多分支可能会进入一个特定的函数中,从而触发错误或未定义行为。Qiling框架正在积极开发中,我几天每天都能在Github上看到它的更新。核心开发人员在维护项目方面做得很好,我建议大家使用这些技术来调试API,并为这个项目贡献一些代码。
逆向快乐! |