首页
论坛
课程
招聘
[翻译]利用Qiling框架实现带有代码覆盖率信息的PE文件模拟执行
2020-9-4 19:45 3780

[翻译]利用Qiling框架实现带有代码覆盖率信息的PE文件模拟执行

2020-9-4 19:45
3780

Qiling 模拟执行

xwings在ZeroNights 2019中曾提及,Qiling模拟执行框架开发出来的目的是为了模拟执行各种操作系统中的shellcode,以应对复杂度不断增长的shellcode。作为一个二进制分析和模拟执行框架,它支持跨平台(如Windows 和Linux),多架构(如ARM和x86),和多种文件格式(如ELF和Portable Executables)。

PE代码覆盖率

当试图为Windows系统API贡献代码,提高其覆盖率时,我发现难以追踪到那些调用了特定API的代码的执行流,而我想要模拟执行的代码正是这部分.万幸的是,@assaf_carlsbad提交了一个PR,这个PR使用了DRCOV-compatible代码覆盖率,刚好能实现我想要的功能。但它原本的代码只支持PE UEFI Loader,以我决定为它添加PE Loader的支持.修改如下所示:

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这一函数回调打印出每个基本块的执行日志。

    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

我把这篇博客搁置了一段时间,后来发现xwings在6月份已经把这一功能添加到PE中了,所以我提交了一个PR以支持获取DLL的代码覆盖率

接触Dragon(Dance)

在IDA Pro和二进制Ninja中的可视化代码覆盖率功能,其事实标准是一个叫作Lighthouse的插件。如果你使用上述两种反汇编器,我强烈推荐你使用它。在我的业余时间,我一直在尝试使用Ghidra,它有一个很棒的反汇编器和反编译器,并且它还提供了大量IDA Pro缺少的功能。一旦你习惯了它的用法,你会发现它是一个非常棒的自由开源的逆向工具。我偶然间发现了一个叫做Dragon Dance的插件,它能在Ghidra中提供和Lighthouse相似的代码覆盖率可视化功能,更重要的是,它支持本文中提到的DRCOV追踪格式。它支持大量的内置引用,能够让你做到诸如在高亮语法中进行diff追踪这样的功能。下面是从README中摘录的一段文字,介绍如何用这些函数来实现脚本接口。

除此之外,Dragon Dance支持还有修复功能,它本质上是将二进制文件中的代码覆盖率完整性与Ghidra反汇编出的指令进行比较。如果这两者不同,插件会提示用户修复Ghidra缺失的部分。下面也是从README中摘录的一段介绍:

组合所有功能

现在我们已经能够追踪PE覆盖率,而且能够在逆向工程软件中将它可视化。接下来看一个例子,Qiling附带了一个WannaCry的二进制文化,我们用它来进行举例。首先,要想安装它,我需要依照Dragon Dance中的编译步骤将它编译出来,编译步骤非常简单。编译并安装之后,就可以在Ghidra中使用它了。我运行qltool并启用追踪功能来对WannaCry进行分析。

# 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下拉菜单中打开插件窗口。

 


打开Dragon Dance窗口之后,使用绿色的+按键添加一个trace文件。

 

文件添加好后,在文件上右键,点击Switch To进入这个文件。

 

经过上述操作后,所有可执行/模拟的区域都已经高亮显示了,在这里我们能看到WannaCry调用的开关函数。

 


如果上述步骤你都跟过来了,你会发现WannaCry最后结束在sprintf函数中。

[+] 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的代码后面,指令都没有高亮了:

我们可以用Dragon Dance可视化msvcrt.dll的依赖,也就是它本身的代码覆盖率。以此找到异常发生的地方:

 


这是我碰到的Qiling的一个局限,因为Qiling使用Unicorn执行机器码的基本块。当执行某个基本块时发生了异常(也就是本例中的场景),它不会触发日志回调。尽管如此,你还是可以用这个可视化功能来寻找问题发生的地方。如果我们想要程序继续执行,有两种方案:

  1. 找到msvcrt.dll中的sprintf执行失败并触发Windows API Implementation Error的原因。
  2. 使用Qiling自己实现这个API
    前者要求我们逆向一个Windows DLL,后者需要我们弄清楚sprintf的原理并用Qiling实现它。

总结

这是一个调试模拟输出的基本例子,因为从qltool的输出中我们能够很明显地看出到底发生了什么,然而,想要可视化一些复杂例子中的执行路径,其开销是非常大的,因为很多分支可能会进入一个特定的函数中,从而触发错误或未定义行为。Qiling框架正在积极开发中,我几天每天都能在Github上看到它的更新。核心开发人员在维护项目方面做得很好,我建议大家使用这些技术来调试API,并为这个项目贡献一些代码。

 

逆向快乐!

 

原文链接

 

翻译:看雪翻译小组 梦野间
校对:看雪翻译小组 lumou


[招聘] 欢迎你加入看雪团队!

收藏
点赞0
打赏
分享
最新回复 (1)
雪    币: 1396
活跃值: 活跃值 (331)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 活跃值 2020-11-26 15:34
2
0
牛批。支持下 
游客
登录 | 注册 方可回帖
返回