Hikari源码解析的第三弹来了,预计也是最后一篇,该系列文章逐一分析了AntiClassDump、AntiDebug以及本篇AntiHook,主要侧重安全方面的保护,言归正传,该文章的源码在:https://github.com/61bcdefg/Hikari-LLVM15-Core/blob/main/AntiHooking.cpp ,开始AntiHook的分析。
该PASS主要提供了三大功能,分别是inlinehook的检测、Runtime保护以及防止借助符号重绑定攻击:
代码量相对较少,总共三百多行,所以我们直接针对具体代码来分析,梳理一下具体的实现流程。
首先看到的是ARM A64指令集的操作码签名,它们定义了特定的指令的二进制表示形式。这些签名是根据ARM架构的官方文档或说明书中关于指令编码的部分得到的,在ARM A64架构的A-profile中,每条指令都有一个特定的位模式。
这些操作码定义了指令的基本类型和行为,编译器和汇编器在生成机器码时会使用这些签名来构造正确的二进制指令。使用这些签名是为了能够识别出特定的二进制指令模式,主要用于后续进行inlinehook的检测。
配置方面还有以下这些,desc描述的很详细,不再做过多解释:
该函数的作用是在Pass初始化时设置一些必要的状态和条件。以下是详细的步骤分析:
首先,它获取当前模块的Triple
,它包含了关于目标架构的信息,例如架构类型、操作系统等。
接下来,如果PreCompiledIRPath
命令行选项没有被用户设置(默认为空字符串),那么代码会尝试构建一个默认的路径。它首先获取用户的主目录,然后在这个目录下创建一个相对路径,这个路径指向预编译的抗Hook处理IR文件。路径中包含了架构和操作系统的名称。
使用std::ifstream
检查该路径指向的文件是否存在且可读。如果文件检测成功,将会输出相关信息,并使用parseIRFile
函数将文件内容解析为Module
对象,之后使用Linker::linkModules
函数将解析出的模块与当前的模块链接。这允许将预编译的抗Hook逻辑合并入当前模块中。
接下来,检查当前模块是否支持不透明指针。LLVM的新版本中不透明指针是默认特性,这段代码用来确定当前上下文是否支持它。
检查目标架构是否是由Apple提供,并且在当前模块的上下文中是否定义了struct._objc_method
。如果这些条件满足,它会插入Objective-C相关的函数声明到模块中。这些函数包括用于获取类、注册选择器以及获取方法实现的函数。这样的函数声明对于后续检查Objective-C运行时Hook行为是必要的。
通过这个初始化函数,设置了相关的hook检查和处理机制,确保了代码可以找到正确的预编译IR文件,并将其正确地链接到当前模块。同时,对于Objective-C的运行时,通过检查模块中是否存在相关的类型定义,并在必要时插入额外的函数声明,以便在后续Pass的运行中执行这些安全检查。
runOnModule
函数整体的实现逻辑较为清晰,主要是检查和处理内联hook、防止符号重绑定(fishhook攻击手段)、以及处理Objective-C运行时hook等,此函数我们不进行逐行的分析,先梳理它的大致流程:
遍历模块M
中的所有函数(Function)对象。
对于每个函数,首先检查该函数是否应该执行"antihook"操作。判断基于编译时标志(flag)和函数属性。
如果initialized
标志为false
,调用initialize
函数以初始化Pass。
获取当前函数F
的ah_inline
属性,决定是否需要检查内联hook。如果属性未设置,使用全局的CheckInlineHook
设置。
如果当前的架构是AArch64
且CheckInlineHookTemp
为true
,则对函数F
执行HandleInlineHookAArch64
处理。
获取当前函数F
的ah_antirebind
属性,决定是否需要执行反绑定符号操作。如果属性未设置,使用全局的AntiRebindSymbol
设置。
如果AntiRebindSymbolTemp
为true
,对函数F
中的指令进行遍历。对于每个调用(Call)或调用动态分配(Invoke)指令,检查目标函数是否为外部链接声明。如果是,创建新的全局变量并替换这些指令中的调用目标。
获取当前函数F
的ah_objcruntime
属性,决定是否需要检查Objective-C运行时hook。如果属性未设置,使用全局的CheckObjectiveCRuntimeHook
设置。
如果CheckObjectiveCRuntimeHookTemp
为false
,则跳过当前函数。
对当前函数F
的使用者(User)遍历,以确定是否存在Objective-C方法结构(struct._objc_method
)。如果找到,继续分析其使用者,查找实例方法和类方法列表的全局变量。
如果找到了方法列表全局变量methodListGV
和方法结构methodStruct
,获取方法选择器(selector)名称和类名称,然后调用HandleObjcRuntimeHook
函数处理Objective-C运行时hook。
最后,函数返回true
,表示Pass成功完成了对模块的处理。
之后再提供关键代码的描述和功能:
此处针对AntiRebindSymbol
的实现我们简单分析一下,它主要是为了防止动态链接过程中的符号被重新绑定,杜绝类似fishhook等框架的应用。实现的关键步骤如下:
实际是通过在编译时创建一个指向原始函数的新全局变量来工作,由于这个变量是私有的,并且只有在编译时才被创建和初始化,运行时的攻击者将难以修改这个地址,这样就能够有效地防止通过修改符号地址来hook函数的攻击。
该函数用于在ARM64(AArch64)体系结构上检测和处理inline hook,我们分步骤进行关键代码段和对应的操作说明:
函数F
的入口基本块被存储在变量A
中,入口基本块A
在第一个不是PHI节点、调试信息或生命周期指令的指令处被分割,创建了基本块C
。基本块C
包含了原始入口基本块中的大部分指令。
基本块B
被创建作为hook检测到后将要执行的异常处理代码块。Detect
和Detect2
基本块被创建用于存放检测逻辑。
入口基本块A
的终止指令(通常是一个跳转指令)被删除,并用一个跳转到Detect
基本块的新分支指令取而代之。
使用IRBuilder
在Detect
基本块中构建检测逻辑。检查函数F
的第一个指令是否包含ARM64上特定的签名值(AARCH64_SIGNATURE_B
和AARCH64_SIGNATURE_BRK
)。如果检测到这些签名中的任何一个,跳转到异常处理基本块B
。否则,继续到Detect2
进行进一步的检测。
在Detect2
中,检查函数F
中跟在检测逻辑后的指令是否包含另一个签名值(AARCH64_SIGNATURE_BR
)。如果检测到,跳转到异常处理基本块B
。否则,跳转到原始的入口基本块后半部分C
。
在基本块B
中,调用一个异常处理的函数CreateCallbackAndJumpBack
,后续针对该函数进行分析。
该函数用于检测Objective-C方法的实现(Imp)是否被hook篡改。它通过比较运行时期待的方法实现和当前实际的方法实现来确定是否存在钩子。
参数说明:
我们针对函数执行逻辑进行代码的详细分析:
获取当前函数所在的模块,并且分割入口基本块,创建三个基本块:A,B,C。其中A是运行时钩子检测,B是处理器,C是原始后续基本块:
获取类对象和选择器:
根据是否是类方法获取相应的方法对象:
获取该方法的实现:
比较获取到的方法实现和原始方法实现是否相同(即检测是否被hook):
此函数首先通过调用Objective-C运行时函式库中的函数来获取运行时的方法实现,并且将其与当前方法实现指针进行比较。如果不一致,说明方法实现可能已经被hook,此时将控制流转移到异常处理基本块B。在基本块B中,通过调用CreateCallbackAndJumpBack
函数,执行相关的回调处理并跳转回正常执行流程的基本块C。如果实现一致,则直接跳回基本块C继续执行。
该函数主要功能是在特定情况下执行异常处理或清理操作,然后确保程序能够继续执行。它根据当前的环境和配置调用不同的处理策略,最后,无论采取了哪种处理策略,控制流都会跳转回正常执行路径的基本块C
。给到带有关键注释的CreateCallbackAndJumpBack
函数:
该Pass展现了一种精心设计的安全加固策略,能够以高度选择性和可配置的方式针对性地为代码提供保护。通过细致的架构适配和针对不同攻击手段(如hook和绑定攻击)的专门防护措施,加固了代码的抗攻击能力。此外,考虑了Objective-C运行时环境,能够识别并处理类和实例方法的挂钩问题。可配置性也极大提升了其适用性,使其能够灵活地适应多种编译场景,并且在提供强大的安全性支持的同时,还能够满足不同项目和开发者的具体需求。借助最近的相对空闲时间,针对Hikari源码分析的系列文章也暂告一段落,如有其他PASS有问题欢迎交流。
#define AARCH64_SIGNATURE_B 0b000101
#define AARCH64_SIGNATURE_BR 0b1101011000011111000000
#define AARCH64_SIGNATURE_BRK 0b11010100001
#define AARCH64_SIGNATURE_B 0b000101
#define AARCH64_SIGNATURE_BR 0b1101011000011111000000
#define AARCH64_SIGNATURE_BRK 0b11010100001
static
cl::opt<std::string>
PreCompiledIRPath(
"adhexrirpath"
,
cl::desc(
"External Path Pointing To Pre-compiled Anti "
"Hooking Handler IR"
),
cl::value_desc(
"filename"
), cl::init(
""
));
static
cl::opt<
bool
> CheckInlineHook(
"ah_inline"
, cl::init(
true
), cl::NotHidden,
cl::desc(
"Check Inline Hook for AArch64"
));
static
bool
CheckInlineHookTemp =
true
;
static
cl::opt<
bool
>
CheckObjectiveCRuntimeHook(
"ah_objcruntime"
, cl::init(
true
), cl::NotHidden,
cl::desc(
"Check Objective-C Runtime Hook"
));
static
bool
CheckObjectiveCRuntimeHookTemp =
true
;
static
cl::opt<
bool
> AntiRebindSymbol(
"ah_antirebind"
, cl::init(
false
),
cl::NotHidden,
cl::desc(
"Make fishhook unavailable"
));
static
bool
AntiRebindSymbolTemp =
false
;
static
cl::opt<std::string>
PreCompiledIRPath(
"adhexrirpath"
,
cl::desc(
"External Path Pointing To Pre-compiled Anti "
"Hooking Handler IR"
),
cl::value_desc(
"filename"
), cl::init(
""
));
static
cl::opt<
bool
> CheckInlineHook(
"ah_inline"
, cl::init(
true
), cl::NotHidden,
cl::desc(
"Check Inline Hook for AArch64"
));
static
bool
CheckInlineHookTemp =
true
;
static
cl::opt<
bool
>
CheckObjectiveCRuntimeHook(
"ah_objcruntime"
, cl::init(
true
), cl::NotHidden,
cl::desc(
"Check Objective-C Runtime Hook"
));
static
bool
CheckObjectiveCRuntimeHookTemp =
true
;
static
cl::opt<
bool
> AntiRebindSymbol(
"ah_antirebind"
, cl::init(
false
),
cl::NotHidden,
cl::desc(
"Make fishhook unavailable"
));
static
bool
AntiRebindSymbolTemp =
false
;
this
->triple = Triple(M.getTargetTriple());
this
->triple = Triple(M.getTargetTriple());
if
(PreCompiledIRPath.empty()) {
SmallString<32> Path;
if
(sys::path::home_directory(Path)) {
sys::path::append(Path,
"Hikari"
);
sys::path::append(Path,
"PrecompiledAntiHooking-"
+
Triple::getArchTypeName(triple.getArch()) +
"-"
+
Triple::getOSTypeName(triple.getOS()) +
".bc"
);
PreCompiledIRPath = Path.str();
}
}
if
(PreCompiledIRPath.empty()) {
SmallString<32> Path;
if
(sys::path::home_directory(Path)) {
sys::path::append(Path,
"Hikari"
);
sys::path::append(Path,
"PrecompiledAntiHooking-"
+
Triple::getArchTypeName(triple.getArch()) +
"-"
+
Triple::getOSTypeName(triple.getOS()) +
".bc"
);
PreCompiledIRPath = Path.str();
}
}
std::ifstream f(PreCompiledIRPath);
if
(f.good()) {
errs() <<
"Linking PreCompiled AntiHooking 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::OverrideFromSrc);
}
else
{
errs() <<
"Failed To Link PreCompiled AntiHooking IR From:"
<< PreCompiledIRPath <<
"\n"
;
}
std::ifstream f(PreCompiledIRPath);
if
(f.good()) {
errs() <<
"Linking PreCompiled AntiHooking 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::OverrideFromSrc);
}
else
{
errs() <<
"Failed To Link PreCompiled AntiHooking IR From:"
<< PreCompiledIRPath <<
"\n"
;
}
this
->opaquepointers = !M.getContext().supportsTypedPointers();
this
->opaquepointers = !M.getContext().supportsTypedPointers();
if
(triple.getVendor() == Triple::VendorType::Apple &&
StructType::getTypeByName(M.getContext(),
"struct._objc_method"
)) {
Type *Int8PtrTy = Type::getInt8PtrTy(M.getContext());
M.getOrInsertFunction(
"objc_getClass"
,
FunctionType::get(Int8PtrTy, {Int8PtrTy},
false
));
M.getOrInsertFunction(
"sel_registerName"
,
FunctionType::get(Int8PtrTy, {Int8PtrTy},
false
));
FunctionType *IMPType =
FunctionType::get(Int8PtrTy, {Int8PtrTy, Int8PtrTy},
true
);
PointerType *IMPPointerType = PointerType::getUnqual(IMPType);
M.getOrInsertFunction(
"method_getImplementation"
,
FunctionType::get(IMPPointerType,
{PointerType::getUnqual(StructType::getTypeByName(
M.getContext(),
"struct._objc_method"
))},
false
));
if
(triple.getVendor() == Triple::VendorType::Apple &&
StructType::getTypeByName(M.getContext(),
"struct._objc_method"
)) {
Type *Int8PtrTy = Type::getInt8PtrTy(M.getContext());
M.getOrInsertFunction(
"objc_getClass"
,
FunctionType::get(Int8PtrTy, {Int8PtrTy},
false
));
M.getOrInsertFunction(
"sel_registerName"
,
FunctionType::get(Int8PtrTy, {Int8PtrTy},
false
));
FunctionType *IMPType =
FunctionType::get(Int8PtrTy, {Int8PtrTy, Int8PtrTy},
true
);
PointerType *IMPPointerType = PointerType::getUnqual(IMPType);
M.getOrInsertFunction(
"method_getImplementation"
,
FunctionType::get(IMPPointerType,
{PointerType::getUnqual(StructType::getTypeByName(
M.getContext(),
"struct._objc_method"
))},
false
));
for
(Function &F : M) {
if
(toObfuscate(flag, &F,
"antihook"
)) {
errs() <<
"Running AntiHooking On "
<< F.getName() <<
"\n"
;
if
(!
this
->initialized)
initialize(M);
if
(!toObfuscateBoolOption(&F,
"ah_inline"
, &CheckInlineHookTemp))
CheckInlineHookTemp = CheckInlineHook;
if
(triple.isAArch64() && CheckInlineHookTemp) {
HandleInlineHookAArch64(&F);
}
if
(!toObfuscateBoolOption(&F,
"ah_antirebind"
, &AntiRebindSymbolTemp))
AntiRebindSymbolTemp = AntiRebindSymbol;
if
(AntiRebindSymbolTemp) {
for
(Instruction &I : instructions(F)) {
if
(isa<CallInst>(&I) || isa<InvokeInst>(&I)) {
CallSite CS(&I);
Function *Called = CS.getCalledFunction();
if
(!Called)
Called = dyn_cast<Function>(CS.getCalledValue()->stripPointerCasts());
if
(Called && Called->isDeclaration() && Called->isExternalLinkage(Called->getLinkage()) && !Called->isIntrinsic() && !Called->getName().startswith(
"clang."
)) {
GlobalVariable *GV = cast<GlobalVariable>(M.getOrInsertGlobal((
"AntiRebindSymbol_"
+ Called->getName()).str(), Called->getType()));
if
(!GV->hasInitializer()) {
GV->setConstant(
true
);
GV->setInitializer(Called);
GV->setLinkage(GlobalValue::LinkageTypes::PrivateLinkage);
}
appendToCompilerUsed(M, {GV});
Value *Load =
new
LoadInst(GV->getValueType(), GV, Called->getName(), &I);
Value *BitCasted = BitCastInst::CreateBitOrPointerCast(Load, CS.getCalledValue()->getType(),
""
, &I);
CS.setCalledFunction(BitCasted);
}
}
}
}
if
(!toObfuscateBoolOption(&F,
"ah_objcruntime"
, &CheckObjectiveCRuntimeHookTemp))
CheckObjectiveCRuntimeHookTemp = CheckObjectiveCRuntimeHook;
if
(!CheckObjectiveCRuntimeHookTemp)
continue
;
GlobalVariable *methodListGV = nullptr;
ConstantStruct *methodStruct = nullptr;
if
(methodListGV && methodStruct) {
GlobalVariable *SELNameGV = cast<GlobalVariable>(methodStruct->getOperand(0)->stripPointerCasts());
ConstantDataSequential *SELNameCDS = cast<ConstantDataSequential>(SELNameGV->getInitializer());
bool
classmethod = methodListGV->getName().startswith(
"_OBJC_$_CLASS_METHODS"
);
std::string classname = methodListGV->getName().substr(
strlen
(classmethod ?
"_OBJC_$_CLASS_METHODS_"
:
"_OBJC_$_INSTANCE_METHODS_"
)).str();
std::string selname = SELNameCDS->getAsCString().str();
HandleObjcRuntimeHook(&F, classname, selname, classmethod);
}
}
}
return
true
;
for
(Function &F : M) {
if
(toObfuscate(flag, &F,
"antihook"
)) {
errs() <<
"Running AntiHooking On "
<< F.getName() <<
"\n"
;
if
(!
this
->initialized)
initialize(M);
if
(!toObfuscateBoolOption(&F,
"ah_inline"
, &CheckInlineHookTemp))
CheckInlineHookTemp = CheckInlineHook;
if
(triple.isAArch64() && CheckInlineHookTemp) {
HandleInlineHookAArch64(&F);
}
if
(!toObfuscateBoolOption(&F,
"ah_antirebind"
, &AntiRebindSymbolTemp))
AntiRebindSymbolTemp = AntiRebindSymbol;
if
(AntiRebindSymbolTemp) {
for
(Instruction &I : instructions(F)) {
if
(isa<CallInst>(&I) || isa<InvokeInst>(&I)) {
CallSite CS(&I);
Function *Called = CS.getCalledFunction();
if
(!Called)
Called = dyn_cast<Function>(CS.getCalledValue()->stripPointerCasts());
if
(Called && Called->isDeclaration() && Called->isExternalLinkage(Called->getLinkage()) && !Called->isIntrinsic() && !Called->getName().startswith(
"clang."
)) {
GlobalVariable *GV = cast<GlobalVariable>(M.getOrInsertGlobal((
"AntiRebindSymbol_"
+ Called->getName()).str(), Called->getType()));
if
(!GV->hasInitializer()) {
GV->setConstant(
true
);
GV->setInitializer(Called);
GV->setLinkage(GlobalValue::LinkageTypes::PrivateLinkage);
}
appendToCompilerUsed(M, {GV});
Value *Load =
new
LoadInst(GV->getValueType(), GV, Called->getName(), &I);
Value *BitCasted = BitCastInst::CreateBitOrPointerCast(Load, CS.getCalledValue()->getType(),
""
, &I);
CS.setCalledFunction(BitCasted);
}
}
}
}
if
(!toObfuscateBoolOption(&F,
"ah_objcruntime"
, &CheckObjectiveCRuntimeHookTemp))
CheckObjectiveCRuntimeHookTemp = CheckObjectiveCRuntimeHook;
if
(!CheckObjectiveCRuntimeHookTemp)
continue
;
GlobalVariable *methodListGV = nullptr;
ConstantStruct *methodStruct = nullptr;
if
(methodListGV && methodStruct) {
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课