该篇文章是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)
随机选择一种内联汇编代码注入:
- 生成的内联汇编代码可能使用系统调用尝试触发反调试行为,例如,通过
ptrace
调用来检测是否被调试。
- 使用
InlineAsm::get
创建内联汇编对象,然后将其插入到函数的最后一条指令之前,通常是函数的返回指令之前。
我们先对配置进行简单的解读,代码开始位置定义了两个静态全局命令行选项:
-
PreCompiledIRPath
命令行选项:
1
2
3
4
|
static cl::opt PreCompiledIRPath(
"adbextirpath" ,
cl::desc( "External Path Pointing To Pre-compiled AntiDebugging IR" ),
cl::value_desc( "filename" ), cl::init( "" ));
|
-
cl::opt
定义了一个类型为 std::string
的命令行选项。
-
"adbextirpath"
是命令行选项的名称,在命令行中指定该选项时使用的标志。
-
cl::desc
提供了该选项的描述,告诉用户这个选项是用来指定预编译反调试IR文件的外部路径。
-
cl::value_desc
是命令行参数的描述,告诉用户这个参数应该是一个文件名。
-
cl::init("")
初始化了这个选项的默认值,这里是一个空字符串,表示默认不指定任何路径。
-
ProbRate
命令行选项:
1
2
3
4
5
|
static cl::opt 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);
|
-
cl::opt
定义了一个类型为 uint32_t
(无符号32位整数)的命令行选项。
-
"adb_prob"
是该命令行选项的名称。
-
cl::desc
这个参数设定了一个百分比,用于决定每个函数被反调试混淆的概率。
-
cl::value_desc
用于描述该命令行选项所期望的值类型,在这个例子中,用户应该提供一个“概率率”。
-
cl::init(40)
表示这个选项的默认值是40,即如果用户没有在命令行中指定该选项,它的值将自动设为40%。
-
cl::Optional
表示这个命令行选项是可选的,用户可以选择是否提供这个选项。
总的来说,允许用户在命令行中通过-adbextirpath
选项指定预编译反调试IR文件的路径,以及用-adb_prob
选项指定每个函数被混淆的概率。
接下来我们详细地解析initialize
函数的代码,并梳理它的整体逻辑。
1. 检查预编译IR路径:
首先判断了PreCompiledIRPath
是否为空。如果是,就尝试构建一个默认的路径。它假定有一个名为"Hikari"的文件夹在用户的home_directory
目录下,然后根据当前模块的目标架构和操作系统类型来构建文件名称。
1
2
3
4
5
6
7
8
9
10
11
12
|
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();
}
}
|
2. 链接预编译的IR:
在这个部分,首先使用一个ifstream
对象f
来检查文件是否存在。如果存在,就尝试链接预编译的IR文件。如果文件不存在或不可读,就输出一条错误信息。
1
2
3
4
5
6
7
8
9
10
11
|
std::ifstream f(PreCompiledIRPath);
if (f.good()) {
errs() << "Linking PreCompiled AntiDebugging IR From:" << PreCompiledIRPath << "\n" ;
SMDiagnostic SMD;
std::unique_ptr 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" ;
}
|
3. 修改ADBCallBack
和InitADB
函数的属性:
如果找到了ADBCallBack
函数,就断言它不是一个声明(即它已经被定义了),然后改变它的可见性、链接属性以及函数属性,保证它在优化和链接期间的行为。
1
2
3
4
5
6
7
8
9
10
11
|
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);
}
|
4. 设置初始化标志和目标三元组信息:
在成功链接预编译IR之后,设置了initialized
标志为true
,并且将模块的triple
信息存储起来。
1
2
|
this ->initialized = true ;
this ->triple = Triple(M.getTargetTriple());
|
最终,initialize
方法在完成它的任务后返回true
。通过这种方式,如果在程序编译时包含了这个LLVM Pass,它会为每个模块提供一个初始化和链接预编译IR的过程,从而植入防调试代码。如果初始化失败,它将输出错误,并且可能停止Pass的进一步执行。
2.1 runOnModule
runOnModule
函数比较简单,整体逻辑也很清晰,通过使用一个设定的概率值来决定是否对模块中的各个函数应用反调试混淆。它首先确保用户输入的概率值在合理范围内(0到100),然后遍历模块的所有函数,并通过toObfuscate
函数和概率判断来决定是否对非特定函数(即非ADBCallBack
和InitADB
)应用混淆。如果是,则进行相应的混淆处理,并在处理过程中初始化必要的数据结构。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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 ;
}
|
2.2 runOnFunction
该函数为整个PASS的核心函数,我们也将针对该函数进行详细解析。
1. 获取函数F
的入口基本块EntryBlock
获取了函数的第一个基本块,通常用于插入初始化代码或其他前置逻辑。
1 |
BasicBlock *EntryBlock = &(F.getEntryBlock());
|
2. 尝试获取ADBCallBack
和InitADB
函数的引用
尝试从当前函数所在的模块(F.getParent()
)中获取名为ADBCallBack
和InitADB
的函数。
1
2
|
Function *ADBCallBack = F.getParent()->getFunction( "ADBCallBack" );
Function *ADBInit = F.getParent()->getFunction( "InitADB" );
|
3. ADBCallBack
和InitADB
函数的处理
如果找到这两个函数的处理,则在入口基本块中创建对InitADB
的调用。
如果ADBCallBack
或InitADB
没有找到,则输出错误消息,并且如果函数F
的返回类型不是void
,则返回false
。
1
2
3
4
5
6
7
8
9
10
|
if (ADBCallBack && ADBInit) {
CallInst::Create(ADBInit, "" ,
cast(EntryBlock->getFirstInsertionPt()));
} else {
errs() << "The ADBCallBack and ADBInit functions were not found\n" ;
if (!F.getReturnType()
->isVoidTy())
return false ;
|
4. 检查目标操作系统和架构并构建内联汇编代码字符串
如果目标系统是 Darwin(例如 macOS 或 iOS)且架构是 AArch64(ARM64),则继续执行,初始化一个空的字符串,用于后续构建内联汇编代码。
1
2
3
4
|
if (triple.isOSDarwin() && triple.isAArch64()) {
errs() << "Injecting Inline Assembly AntiDebugging For:"
<< F.getParent()->getTargetTriple() << "\n" ;
std::string antidebugasm = "" ;
|
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课