混淆(隐藏)PE导入,使其不被静态/动态分析工具检测到。
这相当直接,假设我使用了VirtualProtect,并想用Sleep来混淆它,该工具会操纵IAT,使得指向VirtualProtect的thunk转而指向Sleep,这样在文件执行时,Windows加载程序会加载Sleep而不是VirtualProtect,并将执行权转移到入口点,从那里执行将被重定向到工具之前放置的shellcode,以找到VirtualProtect的地址,并用它替换之前由加载程序分配的Sleep的地址。
#include <cobf.hpp>
int main() {
cobf obf_file = cobf("sample.exe");
obf_file.load_pe();
obf_file.obf_sym("kernel32.dll", "SetLastError", "Beep");
obf_file.obf_sym("kernel32.dll", "GetLastError", "GetACP");
obf_file.generate("sample_obfuscated.exe");
obf_file.unload_pe();
return 0;
};
config.ini)。cobf.exe <输入文件> <输出文件> [配置文件]; 配置文件模板:
; * 节可以写成:
; [dll名称]
; 旧符号=新符号
; * dll名称不区分大小写,但
; 旧符号和新符号是区分大小写的。
; * 可以在dll名称和旧符号上使用通配符。
; * 可以在旧符号或新符号的开头使用'#'来标记
; 序号。
; * 新符号必须由dll导出,以便Windows加载程序可以解析它。
; 例如:
; * 将user32.dll中所有导入的符号混淆为序号1600。
[user32.dll]
*=#1600
; * 将kernel32.dll和kernelbase.dll中导入的符号都混淆为Sleep。
[kernel*.dll]
*=Sleep
; * 将fprintf混淆为exit。
[*]
fprintf=exit
编译以下代码示例
#include <windows.h>
#include <stdio.h>
int main() {
SetLastError(5);
printf("Last error is %d\n", GetLastError());
return 0;
};
编译后,kernel32的导入看起来是这样的

现在让我们将SetLastError和GetLastError分别混淆为Beep和GetACP(实际上kernel32中的任何API都可以,即使它根本没有被导入)。
使用的配置如下
[kernel32.dll]
SetLastError=Beep
GetLastError=GetACP
以下是输出结果(你也可以直接使用上面的库)。

再来看一下kernel32的导入

不再有SetLastError或GetLastError的身影
确认两个文件都能正常工作

IDA HexRays反编译器

IDA调试器

Ghidra

ApiMonitor

这是因为所有静态分析工具都依赖于IAT中写入的API名称,而它就像展示的那样可以被操纵。
对于ApiMonitor,由于使用了IAT挂钩,也存在同样的问题。
另一方面,对于x64dbg这样的工具,显示的API名称仅取决于实际调用的内容(而不是IAT中写的内容)。

.cobf的新rwx节,用于存放shellcode和其他所需数据。git clone https://github.com/d35ha/CallObfuscator获取源代码。