sergio-echigo/malware_analysis_cp3
GitHub: sergio-echigo/malware_analysis_cp3
一个恶意软件分析的逆向工程教学项目,使用 x64dbg 对 Windows 二进制程序进行调试,解析其命令行参数验证、反调试检测及文件操作逻辑。
Stars: 0 | Forks: 0
# 程序整体运行机制
在使用 `xdbg32` 应用程序对程序进行了长时间的分析后,可以观察到该程序需要两个或更多的参数,具体取决于传递的第二个参数(第一个是应用程序的执行路径)。如果第二个参数是 "/start",则需要再输入一个参数 "A",应用程序才能正常运行——它会在 "C:\Users\Public\" 目录下创建一个名为 "cp03.txt" 的文件,其中包含文件创建的日期和时间,接着是一个换行符,最后是字符 "A"。然而,如果参数是 "/read",则会显示该文件的内容(前提是它存在),此外还有一条消息 "CheckPoint#03 Done! Congratz! Show me the result!",该消息起初在程序的文本引用中是以 base 64 编码的。这就是该应用程序的工作方式。此外,还有一种环境检查,用于验证应用程序是否在 "Debug" 模式下运行,这是 Windows 的一个自带函数,名为 `IsDebuggerPresent`。
# 执行流程
一切都从获取命令行参数开始,如下图带有注释的指令所示:

紧接着,会调用一个特定的函数;正如稍后将看到的,该函数负责对传递的参数以及应用程序运行的执行环境进行各种验证和检查。该函数被昵称为 `EnvArgsCheck`,理解其正在执行的大部分条件判断对于程序的正确运行至关重要。此外,它还接收另外三个参数,这些参数也已经在代码中描述过:程序中传递的参数数量、内存中参数 *array*(数组)的第一个地址,最后是一个与特定目录相关的 *string*(字符串),该目录是通过调用 `get_initial_narrow_environment` 函数获取的。前面展示的图像也显示了这些值被添加到程序的堆栈中,以及对该函数的调用。
## 执行环境检查
尽管重点是环境验证,但首先会观察参数数量是否小于两个;如果为真,程序将被终止。否则,下一次验证将通过 `IsDebuggerPresent` 函数进行,根据 MSDN 规范,该函数返回一个与程序是否通过 *debugger*(调试器)执行相关的布尔值。例如,由于分析完全是通过 `xdbg` 进行的,该函数返回 *true*,这意味着将二进制值 **一** 赋予寄存器 `eax`。以至于在该函数调用之后立即执行了一条操作 `eax` 值的指令 `test`,仅使用该值。下一条指令是条件跳转 `jne`,只要 `ZF` 为零它就会发生,而只要 `eax` 也为零(根据 `test` 指令),即来自 *debugger* 验证函数的调用,这就成立。换言之,**如果存在 debugger,程序将再次被终止。** 这样就更直观了。同样,由于分析完全是通过 **debugger** 进行的,因此在运行时需要更改 *flag*(标志位) `ZF` 的值。下图展示了与这些验证相关的所有指令,并且它们旁边还有更多注释。

## 程序参数验证
接下来的检查实际上将与程序执行期间传递的参数有关。在此处的指令和彼处的调用等等之间,可以分析出一些函数,尽管它们存在,但并不是验证或可能导致程序终止的点,例如 `StrangeFunc1`,它通常将寄存器 `ecx` 的值复制到 `eax`。无论如何,发生的下一次验证是关于参数数量(再次!),将其与 2 进行比较。然而,在该验证和紧随其后的条件跳转中有一个重要细节:如果参数数量确实等于两个——即不发生 `jle` 跳转——下一个“代码块”将负责获取第二个参数的字符数量,并将其与六进行比较。细节是:如果不发生 `jle` 跳转,即参数数量等于或更小——尽管考虑到这种验证之前已经做过,它永远不可能更小——它只会跳过这个字符计数,并且程序的执行很快再次进入一个单一路径,即“与六的比较”。不,如果第二个参数的字符数量不等于六,程序并不会终止。接下来的两个小节将负责解释程序的逻辑,根据最后测试的条件,程序逻辑现在一分为二。此外,以下图像展示了整个执行流程。

### 第二个参数的字符数量等于六
这可能属实,但不能保证该参数的值将与 "/start" 相同,这是对应下一次主要检查的条件。起初,比较的是整个参数,但这并不妨碍(至少根据汇编指令来看)发生逐个字符的验证,无论如何这都会发生。此外,还发生了各种加法和减法操作,可能是为了确保根据第二个参数值的正确长度正确进行比较。回到比较中,该值必须等于 "/start";否则,最终会发生条件跳转,到达一个“第二个参数的字符数量不等于六”的比较点。就好像没有回调一样,就好像对于下一次验证没有 *else*,只有验证本身。由于在这个最终点上验证变成了字符数量与五的比较,结果将为假,最后程序将被终止。这种比较将在下一个小节中进行更好的解释,除此之外,下图展示了与常量 "/start" 的比较以及“逐个字符”的比较:

幸运的是,代码并没有在这里结束。还有很多值得探索的地方。毕竟,还有更多条件需要验证。下一个基本条件是命令行传递的 **第三个** 参数的字符数量为 1。第三个参数?没错。毕竟,请记住要到达这里,参数数量必须大于二,因为第二个参数的字符数量等于六,而这后一个断言是在条件跳转 `jle` 发生之后做出的。细节:第三个参数的长度是在调用指令 `call cp03.ap1ec30fc4ke.C52740` 期间在本地分配的,这可以在图像稍微靠上的位置看到。现在,可以声明如下:第三个参数的字符数量必须等于一。这是因为,即使进行了新的比较(基本上是相同的),程序也会再次陷入第二个参数的字符数量与五的比较中,然后再次被终止。此外,作为参数传递的这唯一字符的值必须是 "A"——否则,再一次,代码将跳出直到这里构建的巨大 *if* 块。与这些验证相关的所有指令都展示在下面唯一的截图中:

这本身就已经足够了。在完成所有这些验证之后,可以观察到代码正在运行,并且非常具体:它会在 `C:\Users\Public\cp03.fiap` 目录中创建一个文件,其内容已在整个分析的开头解释过。而且,文件是否已经被创建并不重要;也就是说,如果已创建,该文件将被修改以包含新数据。完成这一切后,程序将终止。


### 第二个参数的字符数量不等于六
(...) 但它强制必须为五。这就是那个不存在的 *else*,它根据特定条件在验证后立即终止程序。而这些就是现在将要分析的条件。除了必须是五个字符外,其值 **必须** 等于 "/read"。就像在字符数为六的“第一个 *if*”中发生的那样:验证字符数、参数值、动作等等。同样,至少在汇编中,会进行逐个字符的比较。至少这次没有对某种形式的第三个命令行参数进行验证。由于执行的整个逻辑与上面已经解释的非常相似,下图展示了该逻辑的摘要:




上图旨在表示(与 alt 中写的相同):
- 检查第二个参数为 /read;
- 获取包含项目的目录及先前写入的文件;
- 读取文件并解码 base64;
- 最终结果。
标签:DAST, DNS 反向解析, DOM解析, xdbg32, 云资产清单, 反调试, 恶意软件分析, 端点可见性, 网络信息收集, 逆向工程