【S2E】分析恶意软件

S2E – 分析恶意软件

实际操作了一下。原文参考 【S2E】【翻译】用S2E分析“基于触发的”恶意软件

在 S2E 下构建 Windows 7 镜像

S2E 很明确地要求镜像名为 en_windows_7_professional_with_sp1_x86_dvd_u_677056.iso,MSDN 下载需要订阅,所以在 Google 上随便找了个 ed2k 链接下载的。SHA-1 值是:d89937df3a9bc2ec1a1486195fd308cd3dade928 ,下载完后可以用 certutil -hashfile en_windows_7_professional_with_sp1_x86_dvd_u_677056.iso SHA1 来计算 SHA-1 。

1
2
3
4
PS G:\> certutil -hashfile C:\Users\zhehuiliu\Desktop\en_windows_7_professional_with_sp1_x86_dvd_u_677056.iso SHA1
SHA1 的 C:\Users\zhehuiliu\Desktop\en_windows_7_professional_with_sp1_x86_dvd_u_677056.iso 哈希:
d89937df3a9bc2ec1a1486195fd308cd3dade928
CertUtil: -hashfile 命令成功完成。

然后将镜像放在某个目录下,运行:

1
s2e image_build windows-7sp1pro-i386 --iso-dir ${path_to_iso_dir}

等个三四个小时就好了。

在 VS 中构建 malware-inject 工程

我安装了 VS2017 专业版。

按照博客中的指示,打开 $S2EDIR/source/s2e/guest/windows/s2e.sln ,可以看到解决方案中包含 7 个项目:

image-20211101105858551

创建一个新项目:(选择 添加到解决方案)

image-20211101110134915

在 malware-inject 项目中,使用 nuget 安装 EasyHookNativePackage:

image-20211101110748677

image-20211101110850429

在 malware-inject 项目中创建 inject.c ,写入下列代码

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
#include <stdio.h>
#include <string.h>

#include <Shlwapi.h>
#include <Windows.h>

#include <easyhook.h>

// We must add this header file to support writing to S2E's logs. s2e.h resides
// in the libcommon project, so the libcommon project must be added as a
// dependency to the malware-inject project
#define USER_APP
#include <s2e/s2e.h>

#define S2E_MSG_LEN 512
#define MAX_PATH_LEN 256

static INT s2eVersion = 0;

... ...

... ...


if (hProcess) {
WaitForSingleObject(hProcess, timeout);
GetExitCodeProcess(hProcess, &exitCode);
CloseHandle(hProcess);
} else {
Message("Unable to open process 0x%x: 0x%X\n", pid, GetLastError());
}

return exitCode;
}

此时发现,IDE 无法找到 s2e/s2e.h ,根据下面注释中的描述,将 libcommon 添加到 malware-inject 项目的依赖。malware-inject -> 右键 -> 生成依赖项 -> 项目依赖项

1
2
3
// We must add this header file to support writing to S2E's logs. s2e.h resides
// in the libcommon project, so the libcommon project must be added as a
// dependency to the malware-inject project
image-20211101113750201

然后,在 malware-inject -> 右键 -> 属性,在包含目录中添加 :$(SolutionDir)libcommon\include; ,引入 s2e/s2e.h 文件。

image-20211101152806732

此外,还需要把 libcommon.lib 导入 malware-inject 工程。这个参考 Visual Studio 2017 项目中引用lib静态库_轻舞飞扬-CSDN博客

最后编译 malware-inject 工程。我遇到报错 LNK2019 无法解析的外部符号 __imp__PathFileExistsA@ ,所以添加了 #pragma comment(lib, "Shlwapi.lib")

最后编译成功:

image-20211101163016895

踩坑

  1. 编译项目需要在 Windows 7 32bit 下运行,所以我直接编译了 winxp 版本。我下载好平台工具集 Visual Studio 2017 - Windows XP (v141_xp) 并切换之后,显示找不到 Windows.h 等头文件。网上都是说没安装工具集,反复装了好几遍,最后在 [StackOverflow](c++ - How to compile code for Windows XP in Visual Studio 2017 - Stack Overflow) 上看到了:

    This happens when you have customized include/library paths in legacy projects. If you added your own additional paths in project properties, VisualStudio 2017 can’t automatically figure out base paths when switching between platforms/toolsets - normally it automatically puts correct paths there, but if you added customizations, VS won’t touch them.

    This is legitimate problem which I ran into myself recently when migrating old project targeted for Windows XP into VS2017. None of the answers or comments listed/linked here so far are helpful. I have all legacy SDKs in VisualStudio 2017 installer, and none of that fixed VS not finding essential includes such as <windows.h>. In my case the project was using v120 toolset from VS2013, which is superseded by v140_xp in newer VS.

    After setting correct platform and toolset understood by VS2017, I did the following to resolve the problem:

    • Open project properties, go to VC++ Directories, for ‘Include Directories’ and for ‘Library Directories’, choose <Inherit from parent or project defaults>. This will remove your additional paths.
    • Click ‘Apply’. This will reset include path to something like $(VC_IncludePath_x86);$(WindowsSdk_71A_IncludePath_x86) (will vary for SDKs).
    • Re-add your extra paths here, or better yet - under C/C++/General -> Additional Include Directories and Linker/General -> Additional Library Directories.
  2. 切换到 v141_xp 后,报错 无法解析外部符号 __imp__RtlGetLastErrorString@0 referenced in function _main ,在 Accessing EasyHook FROM Visual Studio - C++ · Issue #95 · EasyHook/EasyHook · GitHub 中找到,是 EasyHook32.lib 没有加载,我手动把 EasyHook32.lib 拷贝到当前项目下就好了。

学习用例1:GetLocalTime-hook 项目

构建 GetLocalTime-hook 项目

新建 GetLocalTime-test 项目。创建 GetLocalTime-hook.cpp ,写入下面代码:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
///
/// Copyright (C) 2018, Adrian Herrera
/// All rights reserved.
///

#include <Windows.h>
#include <strsafe.h>

#include <easyhook.h>

// We need this header file to make things symbolic and to write to the S2E log
#define USER_APP
extern "C" {
#include <s2e/s2e.h>
}

/// Maximum message length to write to S2E debug log
#define S2E_MSG_LEN 512

/// S2E version number, or 0 if not running in S2E mode
static INT s2eVersion = 0;

//////////////////////
// Helper functions //
//////////////////////

///
/// Write a message to the S2E log (or stdout).
///
static void Message(LPCSTR fmt, ...) {
CHAR message[S2E_MSG_LEN];
va_list args;

va_start(args, fmt);
vsnprintf(message, S2E_MSG_LEN, fmt, args);
va_end(args);

if (s2eVersion) {
S2EMessageFmt("[malware-hook] %s", message);
} else {
printf("[malware-hook] %s", message);
}
}

////////////////////////
// GetLocalTime hooks //
////////////////////////

static VOID WINAPI GetLocalTimeHook(LPSYSTEMTIME lpSystemTime) {
Message("Intercepted GetLocalTime\n");

// Call the original GetLocalTime to get a concrete value
GetLocalTime(lpSystemTime);

// Make the value concolic
S2EMakeSymbolic(lpSystemTime, sizeof(*lpSystemTime), "SystemTime");
}

////////////////////
// Initialisation //
////////////////////

///
/// The names of the functions to hook (and the library that function belongs
/// to)
///
static LPCSTR functionsToHook[][2] = {
{"kernel32", "GetLocalTime"},
{NULL, NULL},
};

/// The function hooks that we will install
static PVOID hookFunctions[] = {
GetLocalTimeHook,
};

/// The actual hooks
static HOOK_TRACE_INFO hooks[] = {
{NULL},
};

// EasyHook will be looking for this export to support DLL injection. If not
// found then DLL injection will fail
extern "C" void __declspec(dllexport) __stdcall NativeInjectionEntryPoint(REMOTE_ENTRY_INFO *);

void __stdcall NativeInjectionEntryPoint(REMOTE_ENTRY_INFO *inRemoteInfo) {
// Unused
(void *) inRemoteInfo;

// Used by the Message function to decide where to write output to
s2eVersion = S2EGetVersion();

for (unsigned i = 0; functionsToHook[i][0] != NULL; ++i) {
LPCSTR moduleName = functionsToHook[i][0];
LPCSTR functionName = functionsToHook[i][1];

// Install the hook
NTSTATUS result = LhInstallHook(GetProcAddress(GetModuleHandleA(moduleName), functionName), hookFunctions[i],
NULL, &hooks[i]);

if (FAILED(result)) {
Message("Failed to hook %s.%s: %S\n", moduleName, functionName, RtlGetLastErrorString());
} else {
Message("Successfully hooked %s.%s\n", moduleName, functionName);
}

// Ensure that all threads _except_ the injector thread will be hooked
ULONG ACLEntries[1] = {0};
LhSetExclusiveACL(ACLEntries, 1, &hooks[i]);
}

// The process was started in a suspended state. Wake it up...
RhWakeUpProcess();
}

编译选项设置可以参考前面的,包括:

  1. 使用 Visual Studio 2017 - Windows XP (v141_xp) 工具集;
  2. 把输出类型设置为 动态链接库 .dll;
  3. 安装 EasyHookNativePackage,并把 EasyHook32.lib 拷贝到当前目录;
  4. 把 libcommon 作为当前项目的依赖项目,并把 libcommon/include 目录添加到当前项目的包含目录;最后引入 libcommon.lib。

到这一步,编译成功后,我们会得到两个文件:

  • malware-inject.exe
  • GetLocalTime-hook.dll

使用 S2E 测试 GetLocalTime-test

先编写 GetLocalTime-test 测试程序。我不想在 VS 下新建一个工程了,直接在 Dev C++ 下编译下面代码:

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
#include <stdio.h>
#include <Windows.h>

void ddos(LPCSTR target) {
// DDOS code goes here :)
printf("DDOS'ing %s\n", target);
}

int main() {
// The following code is adapted from the paper "Automatically Identifying
// Trigger-based Behaviour in Malware" by Brumley et al.
SYSTEMTIME systime;
LPCSTR site = "www.usenix.org";

GetLocalTime(&systime);

if (9 == systime.wDay) {
if (10 == systime.wHour) {
if (11 == systime.wMonth) {
if (6 == systime.wMinute) {
ddos(site);
}
}
}
}

return 0;
}

将其命名为 test-time.exe。然后将 test-time.exe 拷贝到 S2E 中。

先创建 S2E 工程:

1
s2e new_project -i windows-7sp1pro-i386 /home/ubuntu/workspace/malware/test-time.exe

然后将 malware-inject.exe,GetLocalTime-hook.dll 连同 EasyHook32.dll 一起拷贝到刚生成的 S2E 环境下,并在刚生成的项目中创建软链接。

修改 bootstrap.sh:

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
# ...

# This function executes the target program.
# You can customize it if your program needs special invocation,
# custom symbolic arguments, etc.
function execute_target {
local TARGET
TARGET="$1"

./malware-inject.exe --dll "./GetLocalTime-hook" --app ${TARGET}
#run_cmd "$@" > /dev/null 2> /dev/null
}


# ...

# We also need to download the files required for hooking

# Download the target file to analyze
${S2EGET} "GetLocalTime-test.exe"

${S2EGET} "EasyHook32.dll"
${S2EGET} "GetLocalTime-hook.dll"
${S2EGET} "malware-inject.exe"

# ...

这里因为我改了 dll 的名字,所以在 execute_target 使用的地方和导入的地方都要改一下名字。我的项目中 bootstrap.sh 和作者博客有些不同,应该是版本不同导致的。不过还是按照作者写的改了。

最后启动 S2E:

image-20211102161318736

可以看到最后有 4 个 state,说明 fork 了 4 次。在 debug.txt 中也能看到符号变量:

image-20211102163244578

总结

复现这个例子说明了,使用 S2E 分析 Windows 下的进程是可行的。原文中作者提到,因为在 Windows 下没有类似 s2e.so 类似的内容,所以不能直接将输入符号化。通过 DLL 注入和 hook Windows API 的方式向程序注入符号变量,进行进一步的分析。

WannaCry 的部分暂时没有实践分析,等到后面有机会再进行(挖坑)。