使用LLVM Obfuscator / constexpr / PEB调用API 进行程序混淆
一直用这个东西编译WG, 现在想想, 用来免杀应该也不错
Obfuscation
先看一下效果
使用LLVM Obfuscator进行混淆
1. 介绍
地址: llvm-obfuscator
llvm-obfuscator编译比较复杂了, 可以参考llvm官网的 [编译教程](https://llvm.org/docs/GettingStartedVS.html), 测试VS2017下编译通过 (CPU一直100%跑了两小时....2. 使用方法
llvm-obfuscator的话, 主要是在生成的时候做一个混淆和花指令处理,
相应的参数如下
-mllvm -bcf -mllvm -bcf_loop=4 -mllvm -bcf_prob=100 -mllvm -sub -mllvm -sub_loop=2
已经做成VS配置了, 直接复制 Platforms 到
YOUR_PATH\Microsoft Visual Studio\2017\Community\Common7\IDE\VC\VCTargets\Platforms
目录下面,然后在VisualStudio里面编译器选择LLVM
3. 坑
使用的时候还是有不少坑的,首先需要关闭 "符合模式",
然后新建的所有 源文件和头文件, 记得设置成UTF-8-BOM格式(VS默认UTF-16-LE)
使用 constexpr 加密字符串
1. 介绍
关于 constexpr 的介绍可以看这里:
http://zh.cppreference.com/w/cpp/language/constexpr
我们的目标是使用 constexpr 将所有的字符串在编译的时候进行加密,保证程序中看不到丝毫的字符串
2. 使用方法
首先引入这个头文件
obfuscation.h
然后将你的字符串用这种形式标注
MessageBoxA(0, XorString("World!"), XorString("Hello"), MB_OK);
MessageBoxW(0, XorStringW(L"World!"), XorStringW(L"Hello"), MB_OK);
我们来对比一下源码和ida的结果
加密好像有点简单?
再来看一下 llvm + constexpr 的结果
ida还是能看到MessageBoxA的调用啊,没关系,还有下一步
通过PEB调用API
1. 介绍
这个方法可以说是非常 "邪门歪道" 了
先介绍一下相关概念,
线程信息块(TIB)
TIB是一个内部未记录/非官方的Windows数据结构,其中包含有关进程当前正在运行的线程的信息,并且该信息位于用户空间内存中。对于一般布局,请看维基百科。在32位可执行文件中, 具有一定偏移量的选择器fs用于访问TIB,而在64位可执行文件中则使用gs选择器。如果想要一些其他操作,找到一个指向TIB的指针会方便一些。指针可以通过读取fs:[0x18]获取(x86)。可以通过TIB访问其他信息,比如当前线程ID,当前语言环境以及GetLastError()的值等(每个线程都有一个TIB) 。我们只需要TIB里面的一个字段,那就是指向Process Environment Block的指针,并且TIB中的PEB指针对于当前进程所有TIB都是相同的,因为每个进程只有一个PEB。可以通过读取fs:[0x30]获得(x86)。
// TIB:
mov eax, [fs:0x18]
// PEB:
mov eax, [fs:0x30]
下面的代码可能比读取fs寄存器好一些:
#if defined(_M_X64) // x64
static PTEB tebPtr = reinterpret_cast<PTEB>(__readgsqword(reinterpret_cast<DWORD_PTR>(&static_cast<NT_TIB*>(nullptr)->Self)));
#else // x86
static PTEB tebPtr = reinterpret_cast<PTEB>(__readfsdword(reinterpret_cast<DWORD_PTR>(&static_cast<NT_TIB*>(nullptr)->Self)));
#endif
现在介绍完TIB和PEB了,在介绍PEB结构体的一个成员:Ldr,Ldr包含一个 InMemoryOrderModuleList 链表,
我们需要遍历 InMemoryOrderModuleList 找到我们需要加载的dll名称,比如user32.dll,
// peb pointer
PPEB pebPtr = tebPtr->ProcessEnvironmentBlock;
// Reference point / tail to compare against, since the list is circular
PLIST_ENTRY moduleListTail = &pebPtr->Ldr->InMemoryOrderModuleList;
PLIST_ENTRY moduleList = moduleListTail->Flink;
//Traverse the list until moduleList gets back to moduleListTail
do {
char* modulePtrWithOffset = (char*)moduleList;
PLDR_DATA_TABLE_ENTRY module = (PLDR_DATA_TABLE_ENTRY)modulePtrWithOffset;
void *funcPtr = nullptr;
void* DllBase = module->Reserved2[0];
if (!dllName || _wcsicmp(module->FullDllName.Buffer, dllName) == 0)
// find !
}
之后,有了DLL的基地址,可以解析Image的DOS和PE头来获取DLL导出表,然后遍历导出表,获取函数的地址。
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)moduleBase;
PIMAGE_NT_HEADERS headers32 = (PIMAGE_NT_HEADERS)((char*)moduleBase + dosHeader->e_lfanew);
DWORD EdtOffset = headers32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
PEXPORT_DIRECTORY_TABLE EdtPtr = (PEXPORT_DIRECTORY_TABLE)((char*)moduleBase + EdtOffset);
PVOID OrdinalTable = (PBYTE)moduleBase + EdtPtr->OrdinalTableRVA;
PVOID NamePointerTable = (PBYTE)moduleBase + EdtPtr->NamePointerTableRVA;
PVOID ExportAddressTable = (PBYTE)moduleBase + EdtPtr->ExportAddressTableRVA;
// 导出表
for (DWORD i = 0; i < EdtPtr->NamePointerTableSize; i++) {
DWORD NameRVA = ((PDWORD)NamePointerTable)[i];
const char* NameAddr = (char*)moduleBase + NameRVA;
// 比较API名称, 比如MessageBoxA等等
}
完整代码可以参考
obfuscation.h
2. 使用
使用起来就比较简单了,只需要引入第二步的头文件,然后像这个样子调用API
// 方式1
IFN(MessageBoxA)(0, XorString("hello"), XorString("msg"), MB_OK);
// 方式2
IFN_DLL(XorStringW(L"user32.dll"), MessageBoxA)(0, XorString("hello"), XorString("msg"), MB_OK);
为什么要有方式二呢,因为这个调用API方式原理是用函数的名称当做HASH(防止函数名称泄露), 遍历DLL寻找导入表比较HASH,
但是部分DLL里面会错误,
比如 Kernel32.dll 里面有一个 InitializeProcThreadAttributeList,但是是错误的,必须指定DLL名称为 KernelBase.dll
IFN_DLL(XorStringW(L"KernelBase.dll"), InitializeProcThreadAttributeList)(pAttr, 1, 0, &cbSize);
还有一种方式,比如你需要一个API函数指针而不是直接调用,可使用IFN_PTR获取指针
void *_NtSetInformationObject = IFN_PTR(NtSetInformationObject);
同样可以指定DLL
void *_NtSetInformationObject2 = IFN_PTR_DLL(XorStringW(L"ntdll.dll"),NtSetInformationObject);
最后看一下ida的结果(未使用llvm-obfuscator)
int main() {
IFN(LoadLibraryA)(XorString("user32.dll"));
IFN(MessageBoxA)(0, XorString("World!"), XorString("Hello"), MB_OK);
return 0;
}
3. 坑
这个方法同样有坑,程序怎么崩溃了啊???
再次回顾方法原理 TIB -> PEB -> Ldr -> InMemoryOrderModuleList
MessageBoxA在 user32.dll 里面,我们的程序没有依赖user32.dll,所以运行的时候 InMemoryOrderModuleList 里面是没有user32.dll的!
需要手动在程序最开始的时候加载一下
IFN(LoadLibraryA)(XorString("user32.dll"));
总结
当然是 LLVM + constexpr + PEB 全都用上啊!!!
参考:
https://stackoverflow.com/questions/7270473/compile-time-string-encryption
http://pimpmycode.blogspot.no/2015/01/win32-hacks-loading-api-functions-from.html
http://pimpmycode.blogspot.no/2015/01/win32-hacks-loading-api-functions-from_4.html
llvm-obfuscator编译 完了以后,怎么配置呢?
编译完成之后需要在VS里面选择自定义的编译器, 我已经写成配置了
https://github.com/Tai7sy/vs-obfuscation/tree/master/vs_config
有没有详细点儿llvm的编译步骤啊,一直编译失败,编译一次几个小时,有点儿累。尝试了两次,未成功编译完,所有的。
https://llvm.org/docs/GettingStartedVS.html
"Unknown endianness of the compilation platform, check this header aes_encrypt.h"
每次都报这个错误。不知道博主有解决方法没有。
在LLVMipo 和 LLVMObfuscation里添加预定义 ENDIAN_LITTLE 编译过了
我遇到了同样的问题,请问大佬如何设置预定义宏的?谢谢~
大佬,话说我添加完config之后只有平台LLVM-vs2017,没有工具集
加错目录了...
现在有工具集了,但是编译的时候llvm的选项都被忽略了...
https://i.imgur.com/12mKMmT.png
另外我编译的llvm-obfuscator,只有bin和lib,没有include
llvm-obfuscator编译后只有bin和lib,跟楼上问题一样....
都好..就是编译速度??..hello world 很快,但是别的项目 就慢的要命.. 半个小时都不行..
出问题了么
一个for循环,编译了1个多小时,没有结束,正常么,始终编译不成功