该篇文章是Hikari源码分析的第二篇,第一篇在这里(https://bbs.kanxue.com/thread-280139.htm ), 该篇主要针对AntiDebug功能的实现进行分析,源码位置: https://github.com/61bcdefg/Hikari-LLVM15-Core/blob/main/AntiDebugging.cpp, 话不多说,我们开始。
如何反debug的原理不再赘述,我们主要针对PASS的具体实现进行分析。该PASS旨在增加编译后程序的抵抗调试能力,先梳理它的整体实现逻辑有个第一印象,后续我们会展开详解。它通过两个主要方式实现反调试功能:
代码中尝试从指定的路径(由adbextirpath
选项提供)加载预编译的反调试IR文件。如果文件加载成功,它会通过Linker::linkModules
函数被链接到当前的模块中。这个预编译的IR可能包含了一系列用于反调试的函数(ADBCallBack
和InitADB
)和结构,例如:
对于Darwin操作系统的AArch64架构,如果没有找到ADBCallBack
和InitADB
函数,pass会尝试直接注入内联汇编代码。采用了一种基于概率的方法,通过cryptoutils->get_range(2)
随机选择一种内联汇编代码注入:
我们先对配置进行简单的解读,代码开始位置定义了两个静态全局命令行选项:
PreCompiledIRPath
命令行选项:
ProbRate
命令行选项:
总的来说,允许用户在命令行中通过-adbextirpath
选项指定预编译反调试IR文件的路径,以及用-adb_prob
选项指定每个函数被混淆的概率。
接下来我们详细地解析initialize
函数的代码,并梳理它的整体逻辑。
首先判断了PreCompiledIRPath
是否为空。如果是,就尝试构建一个默认的路径。它假定有一个名为"Hikari"的文件夹在用户的home_directory
目录下,然后根据当前模块的目标架构和操作系统类型来构建文件名称。
在这个部分,首先使用一个ifstream
对象f
来检查文件是否存在。如果存在,就尝试链接预编译的IR文件。如果文件不存在或不可读,就输出一条错误信息。
如果找到了ADBCallBack
函数,就断言它不是一个声明(即它已经被定义了),然后改变它的可见性、链接属性以及函数属性,保证它在优化和链接期间的行为。
在成功链接预编译IR之后,设置了initialized
标志为true
,并且将模块的triple
信息存储起来。
最终,initialize
方法在完成它的任务后返回true
。通过这种方式,如果在程序编译时包含了这个LLVM Pass,它会为每个模块提供一个初始化和链接预编译IR的过程,从而植入防调试代码。如果初始化失败,它将输出错误,并且可能停止Pass的进一步执行。
runOnModule
函数比较简单,整体逻辑也很清晰,通过使用一个设定的概率值来决定是否对模块中的各个函数应用反调试混淆。它首先确保用户输入的概率值在合理范围内(0到100),然后遍历模块的所有函数,并通过toObfuscate
函数和概率判断来决定是否对非特定函数(即非ADBCallBack
和InitADB
)应用混淆。如果是,则进行相应的混淆处理,并在处理过程中初始化必要的数据结构。
该函数为整个PASS的核心函数,我们也将针对该函数进行详细解析。
获取了函数的第一个基本块,通常用于插入初始化代码或其他前置逻辑。
尝试从当前函数所在的模块(F.getParent()
)中获取名为ADBCallBack
和InitADB
的函数。
如果找到这两个函数的处理,则在入口基本块中创建对InitADB
的调用。
如果ADBCallBack
或InitADB
没有找到,则输出错误消息,并且如果函数F
的返回类型不是void
,则返回false
。
如果目标系统是 Darwin(例如 macOS 或 iOS)且架构是 AArch64(ARM64),则继续执行,初始化一个空的字符串,用于后续构建内联汇编代码。
通过一个随机函数get_range(2)
来选择不同的代码路径。
使用循环和随机选择的方法,确保每组指令都至少使用一次,然后拼接到antidebugasm
字符串。
创建一个内联汇编对象,其中包含了字符串antidebugasm
中的汇编代码。
遍历函数中的所有基本块,并在每个基本块的终止指令前插入内联汇编调用,并在内部进行了版本适配。
如果不是预期的操作系统和架构,输出一个错误消息。
通过上述代码,大致流程主要是先进行ADBCallBack
和InitADB
函数的获取以及调用,之后针对Darwin系统ARM64架构进行了内联汇编的插入,通过汇编实现svc ptrace的调用,在过程中采用了随机数填充等安全手段。
在上面的分析中我们可知,代码逻辑通过PreCompiledIRPath
参数设置了包含ADBCallBack
和InitADB
函数的IR文件,在此文件中进行了一下反调试的逻辑。所以接下来我们针对该文件进行分析。该IR文件Hikari原作者已经提供,地址为:https://github.com/HikariObfuscator/Resources ,文件结构如下:
我们仅针对PrecompiledAntiDebugging-aarch64-ios.bc
文件进行分析,.bc
文件是LLVM bitcode文件格式,它包含了LLVM的中间表示的编译后的二进制形式。要查看.bc
文件的内容,需要将其转换成文本形式的LLVM IR。使用LLVM工具链中的llvm-dis
工具来完成这个转换。转换后的文件通常具有.ll
扩展名,这是一个可读的LLVM IR文件。
读者可以自行去转换一下,由于代码量较大,在此处就不提供对应代码,我们接下来针对该IR文件进行分析。
代码的开头定义了多个结构体,其中包括 %struct.kinfo_proc
、%struct.extern_proc
、%union.anon
、%struct.itimerval
、%struct.timeval
、%struct.eproc
、%struct._pcred
、%struct._ucred
、%struct.vmspace
和 %struct.ios_execp_info
。
ADBCallBack
函数比较简单,调用 abort()
函数终止程序,然后执行一个无法到达的指令(unreachable
)。
这个函数包含了多个系统调用和检查,主要逻辑如下:
函数声明部分包含了多个系统调用,例如:
函数属性在代码末尾通过 attributes 关键字定义:
模块的编译器标志和识别信息在代码末尾给出:
以上 IR 代码设计用来检测和防止调试。一旦它检测到某些条件符合调试器运行或者与正常运行程序的预期不符,它会通过调用 ADBCallBack
来终止程序。我们做一下代码的总体分析:
结构体定义:代码以多个结构体的定义开始,这些结构体可能用于与 iOS 操作系统的交互和内存数据的组织。
全局声明:@.str
是一个私有的、未命名的地址常量,用于存储字符串 "ptrace"。@mach_task_self_
是一个外部全局变量,它可能表示当前任务的标识。
函数 ADBCallBack:这个函数非常简单,它调用 abort()
函数终止程序,然后执行一个无法到达的指令(unreachable
),这通常是反调试逻辑的一部分。
函数 InitADB:这个函数是反调试逻辑的核心。它进行了一系列的系统调用和检查:
系统调用和声明:代码中声明了一系列系统函数,如 getpid
、sysctl
、dlopen
、dlsym
、dlclose
、syscall
、malloc
、task_get_exception_ports
、isatty
和 ioctl
。这些函数用于执行各种系统级别的操作,很多与防止调试有关。
属性:这些定义了函数的编译器优化属性,如不内联(noinline
)、不抛出异常(nounwind
)等。
模块标志和标识:声明了一些编译器相关的元数据,比如 wchar_size
和 PIC(位置无关代码)等级。
这篇文章我们通过详细的代码分析以及IR文件解读了解了基于LLVM PASS的AntiDebug是如何实现的,最后我们总结一下相较于源代码实现AntiDebug采用PASS的形式两者之间有什么不同。
在项目中直接实现AntiDebug通常意味着在源代码层面增加检测调试器的逻辑,而基于LLVM Pass实现AntiDebug则是在编译器优化阶段插入这类逻辑。两者的优势可以从以下几个方面进行比较:
隐蔽性:
可移植性:
灵活性和复用性:
维护性:
性能:
混淆程度:
总而言之,基于LLVM Pass实现AntiDebug可以提供更好的隐蔽性、可移植性、灵活性、维护性,同时可能带来性能和混淆程度方面的优势。然而,这种方法需要对LLVM框架有深入的了解,并且可能需要面对更复杂的构建和调试过程。
static
cl::opt<std::string> PreCompiledIRPath(
"adbextirpath"
,
cl::desc(
"External Path Pointing To Pre-compiled AntiDebugging IR"
),
cl::value_desc(
"filename"
), cl::init(
""
));
static
cl::opt<std::string> PreCompiledIRPath(
"adbextirpath"
,
cl::desc(
"External Path Pointing To Pre-compiled AntiDebugging IR"
),
cl::value_desc(
"filename"
), cl::init(
""
));
static
cl::opt<uint32_t> ProbRate(
"adb_prob"
,
cl::desc(
"Choose the probability [%] For Each Function To Be "
"Obfuscated By AntiDebugging"
),
cl::value_desc(
"Probability Rate"
), cl::init(40), cl::Optional);
static
cl::opt<uint32_t> ProbRate(
"adb_prob"
,
cl::desc(
"Choose the probability [%] For Each Function To Be "
"Obfuscated By AntiDebugging"
),
cl::value_desc(
"Probability Rate"
), cl::init(40), cl::Optional);
if
(PreCompiledIRPath ==
""
) {
SmallString<32> Path;
if
(sys::path::home_directory(Path)) {
sys::path::append(Path,
"Hikari"
);
Triple tri(M.getTargetTriple());
sys::path::append(Path,
"PrecompiledAntiDebugging-"
+
Triple::getArchTypeName(tri.getArch()) +
"-"
+ Triple::getOSTypeName(tri.getOS()) +
".bc"
);
PreCompiledIRPath = Path.c_str();
}
}
if
(PreCompiledIRPath ==
""
) {
SmallString<32> Path;
if
(sys::path::home_directory(Path)) {
sys::path::append(Path,
"Hikari"
);
Triple tri(M.getTargetTriple());
sys::path::append(Path,
"PrecompiledAntiDebugging-"
+
Triple::getArchTypeName(tri.getArch()) +
"-"
+ Triple::getOSTypeName(tri.getOS()) +
".bc"
);
PreCompiledIRPath = Path.c_str();
}
}
std::ifstream f(PreCompiledIRPath);
if
(f.good()) {
errs() <<
"Linking PreCompiled AntiDebugging IR From:"
<< PreCompiledIRPath <<
"\n"
;
SMDiagnostic SMD;
std::unique_ptr<Module> ADBM(
parseIRFile(StringRef(PreCompiledIRPath), SMD, M.getContext()));
Linker::linkModules(M, std::move(ADBM), Linker::Flags::LinkOnlyNeeded);
}
else
{
errs() <<
"Failed To Link PreCompiled AntiDebugging IR From:"
<< PreCompiledIRPath <<
"\n"
;
}
std::ifstream f(PreCompiledIRPath);
if
(f.good()) {
errs() <<
"Linking PreCompiled AntiDebugging IR From:"
<< PreCompiledIRPath <<
"\n"
;
SMDiagnostic SMD;
std::unique_ptr<Module> ADBM(
parseIRFile(StringRef(PreCompiledIRPath), SMD, M.getContext()));
Linker::linkModules(M, std::move(ADBM), Linker::Flags::LinkOnlyNeeded);
}
else
{
errs() <<
"Failed To Link PreCompiled AntiDebugging IR From:"
<< PreCompiledIRPath <<
"\n"
;
}
Function *ADBCallBack = M.getFunction(
"ADBCallBack"
);
if
(ADBCallBack) {
assert
(!ADBCallBack->isDeclaration() &&
"AntiDebuggingCallback is not concrete!"
);
ADBCallBack->setVisibility(GlobalValue::VisibilityTypes::HiddenVisibility);
ADBCallBack->setLinkage(GlobalValue::LinkageTypes::PrivateLinkage);
ADBCallBack->removeFnAttr(Attribute::AttrKind::NoInline);
ADBCallBack->removeFnAttr(Attribute::AttrKind::OptimizeNone);
ADBCallBack->addFnAttr(Attribute::AttrKind::AlwaysInline);
}
Function *ADBCallBack = M.getFunction(
"ADBCallBack"
);
if
(ADBCallBack) {
assert
(!ADBCallBack->isDeclaration() &&
"AntiDebuggingCallback is not concrete!"
);
ADBCallBack->setVisibility(GlobalValue::VisibilityTypes::HiddenVisibility);
ADBCallBack->setLinkage(GlobalValue::LinkageTypes::PrivateLinkage);
ADBCallBack->removeFnAttr(Attribute::AttrKind::NoInline);
ADBCallBack->removeFnAttr(Attribute::AttrKind::OptimizeNone);
ADBCallBack->addFnAttr(Attribute::AttrKind::AlwaysInline);
}
this
->initialized =
true
;
this
->triple = Triple(M.getTargetTriple());
this
->initialized =
true
;
this
->triple = Triple(M.getTargetTriple());
bool
runOnModule(Module &M) override {
if
(ProbRate > 100) {
errs() <<
"AntiDebugging application function percentage "
"-adb_prob=x must be 0 < x <= 100"
;
return
false
;
}
for
(Function &F : M) {
if
(toObfuscate(flag, &F,
"adb"
) && F.getName() !=
"ADBCallBack"
&&
F.getName() !=
"InitADB"
) {
errs() <<
"Running AntiDebugging On "
<< F.getName() <<
"\n"
;
if
(!
this
->initialized)
initialize(M);
if
(cryptoutils->get_range(100) <= ProbRate)
runOnFunction(F);
}
}
return
true
;
}
bool
runOnModule(Module &M) override {
if
(ProbRate > 100) {
errs() <<
"AntiDebugging application function percentage "
"-adb_prob=x must be 0 < x <= 100"
;
return
false
;
}
for
(Function &F : M) {
if
(toObfuscate(flag, &F,
"adb"
) && F.getName() !=
"ADBCallBack"
&&
F.getName() !=
"InitADB"
) {
errs() <<
"Running AntiDebugging On "
<< F.getName() <<
"\n"
;
if
(!
this
->initialized)
initialize(M);
if
(cryptoutils->get_range(100) <= ProbRate)
runOnFunction(F);
}
}
return
true
;
}
BasicBlock *EntryBlock = &(F.getEntryBlock());
BasicBlock *EntryBlock = &(F.getEntryBlock());
Function *ADBCallBack = F.getParent()->getFunction(
"ADBCallBack"
);
Function *ADBInit = F.getParent()->getFunction(
"InitADB"
);
Function *ADBCallBack = F.getParent()->getFunction(
"ADBCallBack"
);
Function *ADBInit = F.getParent()->getFunction(
"InitADB"
);
if
(ADBCallBack && ADBInit) {
CallInst::Create(ADBInit,
""
,
cast<Instruction>(EntryBlock->getFirstInsertionPt()));
}
else
{
errs() <<
"The ADBCallBack and ADBInit functions were not found\n"
;
if
(!F.getReturnType()
->isVoidTy())
return
false
;
if
(ADBCallBack && ADBInit) {
CallInst::Create(ADBInit,
""
,
cast<Instruction>(EntryBlock->getFirstInsertionPt()));
}
else
{
errs() <<
"The ADBCallBack and ADBInit functions were not found\n"
;
if
(!F.getReturnType()
->isVoidTy())
return
false
;
if
(triple.isOSDarwin() && triple.isAArch64()) {
errs() <<
"Injecting Inline Assembly AntiDebugging For:"
<< F.getParent()->getTargetTriple() <<
"\n"
;
std::string antidebugasm =
""
;
if
(triple.isOSDarwin() && triple.isAArch64()) {
errs() <<
"Injecting Inline Assembly AntiDebugging For:"
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!