Loadlibrary:将Windows的动态链接库移植到Linux下

Loadlibrary在干什么?

loadlibrar运行在linux上,它可以调用windows上dll文件中的函数,实现windows下的程序在linux上运行。目前我们介绍的这部分实现的是对windows Denfender反病毒组件中的mpengine.dll的移植,使得我们可以在linux上进行对文件进行检测以确定文件是否携带病毒。

loadlibrary有什么意义?

简单来说,它的意义就是在linux上更加容易进行fuzz测试,耗费更少的资源、时间以及能够更加方便地进行fuzz测试。windows上的安全产品使用了非常复杂的组件,这些组件往往会跨内核跨用户空间进行互联,如果我们要对他们进行测试,将要调用到整个虚拟化windows环境。举个例子,我们可以使用afl-fuzz在linux上对该程序其进行测试,并能够方便地得到相关数据,但在windows下想要实现这一目的就会非常麻烦。

Windows Defender

从原理上来说,我们现在的loadlibrary所做的事与windows Defender是相同的,这是因为我们将要导入的组件就是Windows Defender恶意软件保护服务中的核心组件–mpengine.dll

准备

由于该dll文件是32位dll文件,你的linux系统上需要安装一些依赖包来在64位机器上来解析它。(非常重要)

我们需要的32位的反恶意软件文件下载链接:

mpengine

原作者说下载下来的名字叫mpam-fe.exe,但是我在ubuntu上下载下来并不叫这个名字而是一个.com文件。不过这个无所谓,我们只要利用cabextract命令提取就可以了。注意下载下来的文件要放在engine目录下再执行cabextract命令。

$ cabextract mpam-fe.exe

Extracting cabinet: mpam-fe.exe

extracting MPSigStub.exe

extracting mpavdlta.vdm

extracting mpasdlta.vdm

extracting mpavbase.vdm

extracting mpasbase.vdm

extracting mpengine.dll

All done, no errors.

查看版本

在engine目录下输入如下指令可以查看当前dll文件的版本。

$ exiftool mpengine.dll | grep 'Product Version Number'

Product Version Number : 1.1.13701.0

运行

先在loadlibrary目录下输入make构建可执行程序。

$make

完成之后得到一个可执行程序mpclient。该程序就是工具的入口。我们构建一个文件,用vi打开,输入欧洲计算机防病毒协会开发的病毒代码,保存为a.exe。将文件作为参数传给程序就可以进行验证。

X5O!P%@AP[4PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

这段代码只是为了验证反病毒软件的功能,本身没有任何危害。

# ./mpclient a.exe

main(): Scanning a.exe...

EngineScanCallback(): Scanning input

EngineScanCallback(): Threat Virus:DOS/EICAR_Test_File identified.

除此之外,类似的工具还有mpstreamfuzz和mpscript。

覆盖

在该项目中存在一个目录:coverage。这个目录包含了一个基本的pintool工具以收集覆盖数据,并且也包含了工具和脚本以便于去处理这些数据。这些工具可以支持语料库蒸馏与反语料库审查。

在最简单的操作模块中,这个工具提供了从dll文件中价值的可执行基本块的列表。

但是经过一些简单的处理我们就可以生成带注解的IDB文件或生成一个已蒸馏的语料库来进行fuzz测试。

下载pintool并将其解压在coverage目录下:

https://software.intel.com/en-us/articles/pintool-downloads

$ tar -zxvf pin-3.2-81205-gcc-linux.tar.gz

$ make

如果你想进行语料库蒸馏,那么执行如下命令。(需要glib-2.0的支持)

$ make coverage_parse_min

运行

执行如下命令,将会显示执行信息。

$ ./coverage/pin -t coverage/deepcover.so -- ./mpclient a.exe

main(): Scanning a.exe

EngineScanCallback(): Scanning input

EngineScanCallback(): Threat Virus:DOS/EICAR_Test_File identified.

----- COVERAGE ANALYSIS -----

58590 Unique Instructions Executed

10271 Unique Basic Blocks Executed

3193230 Total Instructions Executed

590362 Total Basic Blocks Executed

Hottest Basic Block (0x00000000003918e7)

11731 Executes

3 Instructions

这个报告默认名称为coverage.txt,你可以重写它的名称通过设置环境变量COVERAGE_REPORT_FILE

如果你有很多的输入文件,你可以把它们放在一个目录下,利用xarg:

$ ls samples

sample1.exe

sample2.exe

sample3.zip

...

$ find samples -type f | xargs -t -I{} -P8 -n1 env COVERAGE_REPORT_FILE={}.txt ./coverage/pin -t coverage/deepcover.so -- ./mpclient {}

-P参数用来标识你能够并行处理的进程数量。把它设置成你的内核支持的数目。

语义库蒸馏:

$ find samples -type f | xargs -t -I{} -P8 -n1 env COVERAGE_REPORT_FILE={}.txt ./coverage/pin -t coverage/deepcover.so -- ./mpclient {}

构建coverage_parse_min工具

$ make -C coverage coverage_parse_min

你可以用如下命令找到蒸馏过的语义库

$ grep -H ^ samples/*.txt | awk -F: '{print $2FS$1}' | ./coverage_parse_min | cut -d: -f2 | sort -u

反语义库审查

经验告诉我们,我们的fuzz测试的覆盖率是不够高的。这个工作能够帮助我们构建出已经覆盖的数据,并标识出来我们没有覆盖到的部分。

如下的脚本可以简单地产生带注解的idb文件,处理你上述产生的覆盖报告。

$ grep -H ^ *.txt | awk -F: '{print $2FS$1}' | ./coverage_parse_min | sed -e 's#:.*/#:#g' -e 's/.txt$//' > minimal.txt

$ bash genidc.sh minimal.txt > coverage.idc

之后,你可以在IDA中加载产生的coverage.idc文件通过File->Script File....

注意事项

只有当你调用了InstrumentationCallback().这个pintool工具才会开始收集覆盖数据。

你可以通过把你不希望加载的块放置在blacklist.h上来把他们加入黑名单。

调试

如果这个程序出现了崩溃需要进行调试,你可以按照如下步骤:

首先需要安装好gdb的环境。

然后,你需要用mpengine.dll生成一个.map文件。这个操作可以在Linux上进行也可以在window下进行。mpengine.dll在之前的步骤中已经生成的engine文件夹下。

linux执行如下命令:

> idaw -A -P+ -S"createmap.idc mpengine.map"mpengine.dll

windows执行如下步骤:

选择mpengine.dll,一路yes点过去。

产生一个map文件。

将该文件放在你的linux上,进行处理。在控制台输入如下命令使得文件可以在Linux上被识别:

$ dos2unix mpengine.map

使用gdb进行调试:

$ gdb -q ./mpclient

(gdb) r a.exe

添加符号文件:

(gdb) add-symbol-file engine/mpengine.dll 0xf6af4008+0x1000

add symbol table from file "engine/mpengine.dll"at

.text_addr=0xf6af5008

Reading symbols from engine/mpengine.dll...done.

(gdb) shell bash genmapsym.sh 0xf6af4008+0x1000 symbols_19009.o 0

add symbol table from file "symbols_19009.o" at

.text_addr = 0x0

Reading symbols from symbols_19009.o...done.

(gdb) p as3_parsemetadata_swf_vars_t

$1 = {void (void)} 0xf6feb842

(gdb) c

如果你想要在gdb中使用硬件断点的话,你可以使用hb或hbreak命令来代替原本的break命令

(gdb) b as3_parsemethodinfo_swf_vars_t

Breakpoint 1 at 0xf6feb8da

(gdb) c

Continuing.

main(): Scanning test/input.swf...

EngineScanCallback(): Scanning input

Breakpoint 1, 0xf6feb8da in as3_parsemethodinfo_swf_vars_t()

(gdb) bt

#0 0xf6feb8da inas3_parsemethodinfo_swf_vars_t ()

#1 0xf6dbad7f inSwfScanFunc ()

#2 0xf6d73ec3 inUfsScannerWrapper__ScanFile_scanresult_t ()

#3 0xf6d6c9e3 inUfsClientRequest__fscan_SCAN_REPLY ()

#4 0xf6d6a818 inUfsNode__ScanLoopHelper_wchar_t ()

#5 0xf6d6a626 inUfsNode__Analyze_UfsAnalyzeSetup ()

#6 0xf6d71f7f inUfsClientRequest__AnalyzeLeaf_wchar_t ()

#7 0xf6d71bb9 inUfsClientRequest__AnalyzePath_wchar_t ()

#8 0xf6dbbd88 instd___String_alloc_std___String_base_types_char_std__allocator_char______Myptr_void_()

#9 0xf6d75e72 inUfsCmdBase__ExecuteCmd__lambda_c80a88e180c1f4524a759d69aa15f87e____lambda_c80a88e180c1f4524a759d69aa15f87e__()

Backtrace stopped: previous frame inner to this frame(corrupt stack?)

(gdb) x/3i $pc

=> 0xf6feb8da 7>: lea ebx,[edx+0x1c]

0xf6feb8dd10>: push esi

0xf6feb8de11>: mov edx,ebx