一
实现原理
该PASS主要提供了三大功能,分别是inlinehook的检测、Runtime保护以及防止借助符号重绑定攻击:
1.检测和中断潜在的内联hook:通过分析目标程序中的指令序列,该Pass可以识别可能的hook代码,即那些可能被攻击者植入用以控制程序行为的代码。一旦检测到这样的指令模式,Pass会插入额外的检查,如果检测到非法操作,就会转到错误处理流程,从而保护程序不被进一步篡改。
2.保护Objective-C运行时环境:在iOS和macOS等使用Objective-C的环境中,方法实现可能会在运行时被替换或hook,这是常见的动态行为修改手段。AntiHook
Pass通过向代码中插入特定的检查逻辑,确保Objective-C方法在执行时没有被非法替换或修改。
3.反制动态库的重绑定攻击:动态库中符号的重绑定(例如通过fishhook)是攻击者常用的手段来劫持程序中的正常调用。该Pass通过分析外部函数调用,并修改这些调用以指向安全的存根或替代实现,从而阻止这种类型的攻击。
二
代码分析
代码量相对较少,总共三百多行,所以我们直接针对具体代码来分析,梳理一下具体的实现流程。
首先看到的是ARM A64指令集的操作码签名,它们定义了特定的指令的二进制表示形式。这些签名是根据ARM架构的官方文档或说明书中关于指令编码的部分得到的,在ARM A64架构的A-profile中,每条指令都有一个特定的位模式。
// Arm A64 Instruction Set for A-profile architecture 2022-12, Page 56
#define AARCH64_SIGNATURE_B 0b000101
// Arm A64 Instruction Set for A-profile architecture 2022-12, Page 75
#define AARCH64_SIGNATURE_BR 0b1101011000011111000000
// Arm A64 Instruction Set for A-profile architecture 2022-12, Page 79
#define AARCH64_SIGNATURE_BRK 0b11010100001
1.AARCH64_SIGNATURE_B
是B指令(分支指令)的操作码签名,B指令用于无条件分支(跳转)。它在Arm A64指令集的相关文档中被指定为0b000101
,在二进制中,前缀0b
指明接下来的数字是二进制形式。
2.AARCH64_SIGNATURE_BR
是BR指令(分支到寄存器指令)的操作码,BR指令用于跳转到在某个寄存器中给出的地址。操作码0b1101011000011111000000
是这个指令的二进制模式。
3.AARCH64_SIGNATURE_BRK
是BRK指令(中断指令)的操作码,BRK指令用于生成一个同步中断,通常用于调试。操作码0b11010100001
是BRK指令的唯一标识。
这些操作码定义了指令的基本类型和行为,编译器和汇编器在生成机器码时会使用这些签名来构造正确的二进制指令。使用这些签名是为了能够识别出特定的二进制指令模式,主要用于后续进行inlinehook的检测。
配置方面还有以下这些,desc描述的很详细,不再做过多解释:
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;
该函数的作用是在Pass初始化时设置一些必要的状态和条件。以下是详细的步骤分析:
首先,它获取当前模块的Triple
,它包含了关于目标架构的信息,例如架构类型、操作系统等。
this->triple = Triple(M.getTargetTriple());
接下来,如果PreCompiledIRPath
命令行选项没有被用户设置(默认为空字符串),那么代码会尝试构建一个默认的路径。它首先获取用户的主目录,然后在这个目录下创建一个相对路径,这个路径指向预编译的抗Hook处理IR文件。路径中包含了架构和操作系统的名称。
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
检查该路径指向的文件是否存在且可读。如果文件检测成功,将会输出相关信息,并使用parseIRFile
函数将文件内容解析为Module
对象,之后使用Linker::linkModules
函数将解析出的模块与当前的模块链接。这允许将预编译的抗Hook逻辑合并入当前模块中。
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";
}
接下来,检查当前模块是否支持不透明指针。LLVM的新版本中不透明指针是默认特性,这段代码用来确定当前上下文是否支持它。
this->opaquepointers = !M.getContext().supportsTypedPointers();
检查目标架构是否是由Apple提供,并且在当前模块的上下文中是否定义了struct._objc_method
。如果这些条件满足,它会插入Objective-C相关的函数声明到模块中。这些函数包括用于获取类、注册选择器以及获取方法实现的函数。这样的函数声明对于后续检查Objective-C运行时Hook行为是必要的。
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));// ...
通过这个初始化函数,设置了相关的hook检查和处理机制,确保了代码可以找到正确的预编译IR文件,并将其正确地链接到当前模块。同时,对于Objective-C的运行时,通过检查模块中是否存在相关的类型定义,并在必要时插入额外的函数声明,以便在后续Pass的运行中执行这些安全检查。
runOnModule
函数整体的实现逻辑较为清晰,主要是检查和处理内联hook、防止符号重绑定(fishhook攻击手段)、以及处理Objective-C运行时hook等,此函数我们不进行逐行的分析,先梳理它的大致流程:
1.遍历模块M
中的所有函数(Function)对象。
2.对于每个函数,首先检查该函数是否应该执行"antihook"操作。判断基于编译时标志(flag)和函数属性。
3.如果initialized
标志为false
,调用initialize
函数以初始化Pass。
4.获取当前函数F
的ah_inline
属性,决定是否需要检查内联hook。如果属性未设置,使用全局的CheckInlineHook
设置。
5.如果当前的架构是AArch64
且CheckInlineHookTemp
为true
,则对函数F
执行HandleInlineHookAArch64
处理。
6.获取当前函数F
的ah_antirebind
属性,决定是否需要执行反绑定符号操作。如果属性未设置,使用全局的AntiRebindSymbol
设置。
7.如果AntiRebindSymbolTemp
为true
,对函数F
中的指令进行遍历。对于每个调用(Call)或调用动态分配(Invoke)指令,检查目标函数是否为外部链接声明。如果是,创建新的全局变量并替换这些指令中的调用目标。
8.获取当前函数F
的ah_objcruntime
属性,决定是否需要检查Objective-C运行时hook。如果属性未设置,使用全局的CheckObjectiveCRuntimeHook
设置。
9.如果CheckObjectiveCRuntimeHookTemp
为false
,则跳过当前函数。
10.对当前函数F
的使用者(User)遍历,以确定是否存在Objective-C方法结构(struct._objc_method
)。如果找到,继续分析其使用者,查找实例方法和类方法列表的全局变量。
11.如果找到了方法列表全局变量methodListGV
和方法结构methodStruct
,获取方法选择器(selector)名称和类名称,然后调用HandleObjcRuntimeHook
函数处理Objective-C运行时hook。
12.最后,函数返回true
,表示Pass成功完成了对模块的处理。
之后再提供关键代码的描述和功能:
// 遍历模块中的所有函数
for (Function &F : M) {
// 检查是否需要对函数应用antihook
if (toObfuscate(flag, &F, "antihook")) {
errs() << "Running AntiHooking On " << F.getName() << "\n";// 如果Pass还未初始化,则进行初始化
if (!this->initialized)
initialize(M);// 根据函数属性确定是否检查内联hook
if (!toObfuscateBoolOption(&F, "ah_inline", &CheckInlineHookTemp))
CheckInlineHookTemp = CheckInlineHook;// 如果是AArch64架构并且需要检查内联hook,则进行处理
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);
}
}
}
}// 确定是否检查Objective-C运行时hook
if (!toObfuscateBoolOption(&F, "ah_objcruntime", &CheckObjectiveCRuntimeHookTemp))
CheckObjectiveCRuntimeHookTemp = CheckObjectiveCRuntimeHook;// 如果不需要检查Objective-C运行时hook,则继续下一次循环
if (!CheckObjectiveCRuntimeHookTemp)
continue;// 查找Objective-C方法结构及其关联的类方法或实例方法
GlobalVariable *methodListGV = nullptr;
ConstantStruct *methodStruct = nullptr;
// ... 代码省略:遍历函数的使用者和它们的使用者 ...// 如果找到了Objective-C方法列表和方法结构
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();// 处理Objective-C运行时hook
HandleObjcRuntimeHook(&F, classname, selname, classmethod);
}
}
}
return true;
此处针对AntiRebindSymbol
的实现我们简单分析一下,它主要是为了防止动态链接过程中的符号被重新绑定,杜绝类似fishhook等框架的应用。实现的关键步骤如下:
① 寻找外部链接的函数调用:遍历函数中的所有指令,寻找调用指令(CallInst
)或动态调用指令(InvokeInst
)。
② 检查是否为外部声明:对于每个调用指令,检查它是否是对一个外部函数的声明调用。外部函数意味着它在本模块中没有定义,是在运行时通过动态链接库来解析的。
③ 创建和初始化全局变量:对于每个这样的外部函数,创建一个新的全局变量。这个变量被初始化为指向该外部函数,且将其链接性设置为私有(PrivateLinkage
),使它在模块外部不可见。
④ 替换原有的函数调用:使用一个新的加载指令(LoadInst
)来加载前面创建的全局变量中的函数地址,然后用BitCastInst
来保证类型正确。最后,用这个加载的地址替换原来的函数调用地址。
实际是通过在编译时创建一个指向原始函数的新全局变量来工作,由于这个变量是私有的,并且只有在编译时才被创建和初始化,运行时的攻击者将难以修改这个地址,这样就能够有效地防止通过修改符号地址来hook函数的攻击。
该函数用于在ARM64(AArch64)体系结构上检测和处理inline hook,我们分步骤进行关键代码段和对应的操作说明:
函数F
的入口基本块被存储在变量A
中,入口基本块A
在第一个不是PHI节点、调试信息或生命周期指令的指令处被分割,创建了基本块C
。基本块C
包含了原始入口基本块中的大部分指令。
BasicBlock *A = &(F->getEntryBlock()); // 获取函数的入口基本块A
BasicBlock *C = A->splitBasicBlock(A->getFirstNonPHIOrDbgOrLifetime()); // 在第一个非 PHI/Dbg/Lifetime 指令处分割基本块A,创建基本块C
基本块B
被创建作为hook检测到后将要执行的异常处理代码块。Detect
和Detect2
基本块被创建用于存放检测逻辑。
BasicBlock *B = BasicBlock::Create(F->getContext(), "HookDetectedHandler", F); // 创建异常处理基本块B
BasicBlock *Detect = BasicBlock::Create(F->getContext(), "", F); // 创建第一个检测基本块Detect
BasicBlock *Detect2 = BasicBlock::Create(F->getContext(), "", F); // 创建第二个检测基本块Detect2
入口基本块A
的终止指令(通常是一个跳转指令)被删除,并用一个跳转到Detect
基本块的新分支指令取而代之。
A->getTerminator()->eraseFromParent(); // 删除A的原终止指令
BranchInst::Create(Detect, A); // 在基本块A末尾添加一条跳转到Detect的分支指令
使用IRBuilder
在Detect
基本块中构建检测逻辑。检查函数F
的第一个指令是否包含ARM64上特定的签名值(AARCH64_SIGNATURE_B
和AARCH64_SIGNATURE_BRK
)。如果检测到这些签名中的任何一个,跳转到异常处理基本块B
。否则,继续到Detect2
进行进一步的检测。
IRBuilder<> IRBDetect(Detect); // 创建IRBuilder用于构建Detect基本块的IR
// ... 创建和配置检测逻辑的指令 ...
Value *Load = IRBDetect.CreateLoad(Int32Ty, IRBDetect.CreateBitCast(F, Int32PtrTy));
Value *LS2 = IRBDetect.CreateLShr(Load, ConstantInt::get(Int32Ty, 26));
Value *ICmpEQ2 = IRBDetect.CreateICmpEQ(
LS2, ConstantInt::get(Int32Ty, AARCH64_SIGNATURE_B));
Value *LS3 = IRBDetect.CreateLShr(Load, ConstantInt::get(Int32Ty, 21));
Value *ICmpEQ3 = IRBDetect.CreateICmpEQ(
LS3, ConstantInt::get(Int32Ty, AARCH64_SIGNATURE_BRK));
Value *Or = IRBDetect.CreateOr(ICmpEQ2, ICmpEQ3);
// 根据检测结果跳转到基本块B(处理hook)或Detect2(进一步检测)
IRBDetect.CreateCondBr(Or, B, Detect2);
在Detect2
中,检查函数F
中跟在检测逻辑后的指令是否包含另一个签名值(AARCH64_SIGNATURE_BR
)。如果检测到,跳转到异常处理基本块B
。否则,跳转到原始的入口基本块后半部分C
。
IRBuilder<> IRBDetect2(Detect2); // 创建IRBuilder用于构建Detect2基本块的IR
// ... 创建和配置进一步的检测逻辑的指令 ...
Value *PTI = IRBDetect2.CreatePtrToInt(F, Int64Ty);
Value *AddFour = IRBDetect2.CreateAdd(PTI, ConstantInt::get(Int64Ty, 4));
Value *ITP = IRBDetect2.CreateIntToPtr(AddFour, Int32PtrTy);
Value *Load2 = IRBDetect2.CreateLoad(Int32Ty, ITP);
Value *LS4 = IRBDetect2.CreateLShr(Load2, ConstantInt::get(Int32Ty, 10));
Value *ICmpEQ4 = IRBDetect2.CreateICmpEQ(
LS4, ConstantInt::get(Int32Ty, AARCH64_SIGNATURE_BR));
Value *AddEight = IRBDetect2.CreateAdd(PTI, ConstantInt::get(Int64Ty, 8));
Value *ITP2 = IRBDetect2.CreateIntToPtr(AddEight, Int32PtrTy);
Value *Load3 = IRBDetect2.CreateLoad(Int32Ty, ITP2);
Value *LS5 = IRBDetect2.CreateLShr(Load3, ConstantInt::get(Int32Ty, 10));
Value *ICmpEQ5 = IRBDetect2.CreateICmpEQ(
LS5, ConstantInt::get(Int32Ty, AARCH64_SIGNATURE_BR));
Value *Or2 = IRBDetect2.CreateOr(ICmpEQ4, ICmpEQ5);
// 根据进一步检测的结果跳转到基本块B(处理hook)或C(继续正常执行)
IRBDetect2.CreateCondBr(Or2, B, C);
在基本块B
中,调用一个异常处理的函数CreateCallbackAndJumpBack
,后续针对该函数进行分析。
IRBuilder<> IRBB(B); // 创建IRBuilder用于构建基本块B的IR
// ... 添加异常处理的回调函数和返回跳转 ...
CreateCallbackAndJumpBack(&IRBB, C); // 创建在检测到hook时调用的回调,并最终跳回基本块C
该函数用于检测Objective-C方法的实现(Imp)是否被hook篡改。它通过比较运行时期待的方法实现和当前实际的方法实现来确定是否存在钩子。
void HandleObjcRuntimeHook(Function *ObjcMethodImp, std::string classname,
std::string selname, bool classmethod)
参数说明:
◆ObjcMethodImp
:指向Objective-C方法实现的函数指针;
◆classname
:类名的字符串表示;
◆selname
:选择器名的字符串表示;
◆classmethod
:一个布尔值,指示方法是否是类方法。
我们针对函数执行逻辑进行代码的详细分析:
获取当前函数所在的模块,并且分割入口基本块,创建三个基本块:A,B,C。其中A是运行时钩子检测,B是处理器,C是原始后续基本块:
Module *M = ObjcMethodImp->getParent();
BasicBlock *A = &(ObjcMethodImp->getEntryBlock());
BasicBlock *C = A->splitBasicBlock(A->getFirstNonPHIOrDbgOrLifetime());
BasicBlock *B = BasicBlock::Create(A->getContext(), "HookDetectedHandler", ObjcMethodImp, C);
A->getTerminator()->eraseFromParent(); // 删除A的终止指令
IRBuilder
在基本块A和B中构造指令:IRBuilder<> IRBA(A);
IRBuilder<> IRBB(B);
◆获取类对象和选择器:
Value *GetClass = IRBA.CreateCall(M->getFunction("objc_getClass"), {IRBA.CreateGlobalStringPtr(classname)});
Value *GetSelector = IRBA.CreateCall(M->getFunction("sel_registerName"), {IRBA.CreateGlobalStringPtr(selname)});
◆根据是否是类方法获取相应的方法对象:
Value *GetMethod = IRBA.CreateCall(M->getFunction(classmethod ? "class_getClassMethod" : "class_getInstanceMethod"), {GetClass, GetSelector});
◆获取该方法的实现:
Value *GetMethodImp = IRBA.CreateCall(M->getFunction("method_getImplementation"), {GetMethod});
◆比较获取到的方法实现和原始方法实现是否相同(即检测是否被hook):
Value *IcmpEq = IRBA.CreateICmpEQ(IRBA.CreateBitCast(GetMethodImp, Int8PtrTy), ConstantExpr::getBitCast(ObjcMethodImp, Int8PtrTy));
IRBA.CreateCondBr(IcmpEq, C, B); // 如果相等,跳转到C,否则跳转到B
CreateCallbackAndJumpBack(&IRBB, C);
此函数首先通过调用Objective-C运行时函式库中的函数来获取运行时的方法实现,并且将其与当前方法实现指针进行比较。如果不一致,说明方法实现可能已经被hook,此时将控制流转移到异常处理基本块B。在基本块B中,通过调用CreateCallbackAndJumpBack
函数,执行相关的回调处理并跳转回正常执行流程的基本块C。如果实现一致,则直接跳回基本块C继续执行。
该函数主要功能是在特定情况下执行异常处理或清理操作,然后确保程序能够继续执行。它根据当前的环境和配置调用不同的处理策略,最后,无论采取了哪种处理策略,控制流都会跳转回正常执行路径的基本块C
。给到带有关键注释的CreateCallbackAndJumpBack
函数:
// CreateCallbackAndJumpBack函数的目的是在检测到非法操作(如内联hook)后,调用一个回调函数,并且将控制流跳转回到正常的代码执行路径。
void CreateCallbackAndJumpBack(IRBuilder<> *IRBB, BasicBlock *C) {
// 首先获取当前基本块所在的模块
Module *M = C->getModule();// 尝试从模块中获取名为"AHCallBack"的函数,这个函数可能是一个预先定义的安全检查回调
Function *AHCallBack = M->getFunction("AHCallBack");// 如果找到了AHCallBack函数,使用IRBuilder创建一个调用该函数的指令
if (AHCallBack) {
IRBB->CreateCall(AHCallBack);
} else {
// 如果没有找到AHCallBack函数,检查是否在Darwin操作系统上的AArch64架构,这通常指的是iOS或macOS
if (triple.isOSDarwin() && triple.isAArch64()) {
// 如果是,构造一个退出系统调用的内联汇编代码
std::string exitsvcasm = "mov w16, #1\n"; // 设置系统调用号为1,通常是退出系统调用
exitsvcasm +=
"svc #" + std::to_string(cryptoutils->get_range(65536)) + "\n"; // 产生一个随机系统调用中断// 获取内联汇编类型并创建内联汇编指令
InlineAsm *IA =
InlineAsm::get(FunctionType::get(IRBB->getVoidTy(), false),
exitsvcasm, "", true, false);
IRBB->CreateCall(IA); // 使用IRBuilder创建调用内联汇编的指令
} else {
// 如果不是Darwin操作系统或不是AArch64架构,那么创建一个调用"abort"函数的指令
// 首先获取或插入一个abort函数的声明
FunctionType *ABFT =
FunctionType::get(Type::getVoidTy(M->getContext()), false);
Function *abort_declare =
cast<Function>(M->getOrInsertFunction("abort", ABFT).getCallee());
abort_declare->addFnAttr(Attribute::AttrKind::NoReturn); // 标记abort函数是不返回的IRBB->CreateCall(abort_declare); // 使用IRBuilder创建调用abort函数的指令
}
}// 最后,不管是否调用了回调函数,都使用IRBuilder创建一条跳转回基本块C的指令
IRBB->CreateBr(C);
}
三三
总结
该Pass展现了一种精心设计的安全加固策略,能够以高度选择性和可配置的方式针对性地为代码提供保护。通过细致的架构适配和针对不同攻击手段(如hook和绑定攻击)的专门防护措施,加固了代码的抗攻击能力。此外,考虑了Objective-C运行时环境,能够识别并处理类和实例方法的挂钩问题。可配置性也极大提升了其适用性,使其能够灵活地适应多种编译场景,并且在提供强大的安全性支持的同时,还能够满足不同项目和开发者的具体需求。借助最近的相对空闲时间,针对Hikari源码分析的系列文章也暂告一段落,如有其他PASS有问题欢迎交流。
看雪ID:ElainaDaemon
https://bbs.kanxue.com/user-home-945395.htm
# 往期推荐
2、在Windows平台使用VS2022的MSVC编译LLVM16
3、神挡杀神——揭开世界第一手游保护nProtect的神秘面纱
球分享
球点赞
球在看