iOS arm64e hook MGCopyAnswer got Crash or Only Partial results got hooked

直接hook MGCopyAnswer会崩溃,按照stackoverflow的9年前的帖子说法,hook MGCopyAnswer+8仅部分生效。且hook到的都是bool类型的值。

崩溃的原因就是因为指令长度过短。
libMobileGestalt.dylib`MGCopyAnswer:
-> 0x1a483b484 <+0>: mov x1, #0x0 ; =0
0x1a483b488 <+4>: b 0x1a483af00 ; ___lldb_unnamed_symbol1060
在某些比较久(9年前)版本的ios上,libMobileGestalt的MGCopyAnswer+8对应的是MGCopyAnswer调用的内部函数。这也是原来代码+8的原因。但是新版本变成了MGGetBoolAnswer函数的开头,所以只有bool类型的MG返回值被hook到了。
方法要么是把所有类型的MGGet都改了,或者hook “b 0x1a483af00 ; ___lldb_unnamed_symbol1060”内部的长函数。

其他的例如hook opendir崩溃,也是同样的原因。

但是这里直接计算解析地址hook,还是会崩溃。怎么回事呢?

通过调试排查,发现dobby在执行完hook代码逻辑返回的时候,dobby hook框架是一个blr x8,但是lldb si单步进入之后,直接崩溃,x8变成了一个很奇怪的值:例如X8  89E3262AB23B0041 | A.;.*&.. |

这个地方解析一下就发现是高位bit set成了1,所以是pac的指针。然后就出现了EXC_BAD_ACCESS。

所以现在第二次崩溃的原因是因为:PAC指针签名的原因。

系统函数进入的时候函数开头和结尾的时候多了pac指令:例如pacibsp(pac,ib 使用key b,sp指定寄存器),retab(key b验证签名)。

解决办法:我们在hook的时候,对代码签名一下即可。

pac_helper.h

#ifndef PTRAUTH_HELPERS_H
#define PTRAUTH_HELPERS_H

// Helpers for PAC archs.

// If the compiler understands __arm64e__, assume it's paired with an SDK that has
// ptrauth.h. Otherwise, it'll probably error if we try to include it so don't.
#if __arm64e__
#include <ptrauth.h>
#endif

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"

// Given a pointer to instructions, sign it so you can call it like a normal fptr.
static void *make_sym_callable(void *ptr) {
#if __arm64e__
    if (!ptr) return ptr;
    ptr = ptrauth_sign_unauthenticated(ptrauth_strip(ptr, ptrauth_key_function_pointer), ptrauth_key_function_pointer, 0);
#endif
    return ptr;
}

// Given a function pointer, strip the PAC so you can read the instructions.
static void *make_sym_readable(void *ptr) {
#if __arm64e__
    if (!ptr) return ptr;
    ptr = ptrauth_strip(ptr, ptrauth_key_function_pointer);
#endif
    return ptr;
}

#pragma clang diagnostic pop
#endif  // PTRAUTH_HELPERS_H

MyTweak.mm

#import <dlfcn.h>
#import <substrate.h>
#import <Foundation/Foundation.h>
#import <CaptainHook/CaptainHook.h>
#import "dobby.h"
#import "pac_helper.h"

#define _FUNC_ADDR_(A, O) (const void *)((long)(A) + (O))
#define ABS(x) ((x) < 0 ? -(x) : (x))

// 日志宏定义
#define DLog(fmt, ...) NSLog(@"[AA] " fmt, ##__VA_ARGS__)

// 全局变量
static void *handle = NULL;
static void *ptrMGCopyAnswer = NULL;

// 原始函数指针
static CFTypeRef (*original_MGCopyAnswer_internal)(CFStringRef key, uint32_t *status) = NULL;

// 替换函数实现
static CFTypeRef replaced_MGCopyAnswer_internal(CFStringRef key, uint32_t *status)
{
    DLog(@"MGCopyAnswer called with key: %@", key);
    
    // 在这里可以修改返回值,例如修改设备信息
    /*
    if (CFStringCompare(key, CFSTR("DeviceClass"), 0) == kCFCompareEqualTo) {
        return CFRetain(CFSTR("iPhone"));
    }
    if (CFStringCompare(key, CFSTR("ProductType"), 0) == kCFCompareEqualTo) {
        return CFRetain(CFSTR("iPhone14,2"));
    }
    if (CFStringCompare(key, CFSTR("SerialNumber"), 0) == kCFCompareEqualTo) {
        return CFRetain(CFSTR("FAKE123456"));
    }
    */
    
    // 调用原始函数
    CFTypeRef result = original_MGCopyAnswer_internal(key, status);
    
    if (result) {
        DLog(@"MGCopyAnswer result: %@", result);
    }
    
    return result;
}

// 初始化函数
static void InitializeMGCopyAnswer()
{
    // 加载 libMobileGestalt.dylib
    handle = dlopen("/usr/lib/libMobileGestalt.dylib", RTLD_NOW);
    if (!handle) {
        DLog(@"Failed to load libMobileGestalt.dylib: %s", dlerror());
        return;
    }
    
    // 获取 MGCopyAnswer 函数地址
    ptrMGCopyAnswer = dlsym(handle, "MGCopyAnswer");
    if (!ptrMGCopyAnswer) {
        DLog(@"Failed to find MGCopyAnswer: %s", dlerror());
        return;
    }
    
    DLog(@"MGCopyAnswer address: %p", ptrMGCopyAnswer);
    DLog(@"Handle base address: %p", handle);
}

// 构造函数
CHConstructor
{
    @autoreleasepool {
        DLog(@"Initializing MGCopyAnswer hook...");
        
        // 初始化
        InitializeMGCopyAnswer();
        
        if (!ptrMGCopyAnswer) {
            DLog(@"ptrMGCopyAnswer is NULL");
            return;
        }
        
        // 读取函数开头的字节码
        uint8_t *readablePtr = (uint8_t *)make_sym_readable(ptrMGCopyAnswer);
        
        // 检查是否是旧版本 (mov w1, #0; b <offset>)
        if (memcmp(readablePtr, "\x01\x00\x80\xd2\x01\x00\x00\x14", 8) == 0)
        {
            // 旧版本:直接跳转到 offset 8
            DobbyHook(make_sym_callable((void *)_FUNC_ADDR_(ptrMGCopyAnswer, 8)),
                          (void *)replaced_MGCopyAnswer_internal,
                          (void **)&original_MGCopyAnswer_internal);
            
            DLog(@"Hooked legacy MGCopyAnswer_internal");
        }
        // 检查是否是新版本 (mov w1, #0; b <offset>)
        else if (memcmp(readablePtr, "\x01\x00\x80\xd2", 4) == 0)
        {
            // 新版本:解析 B 指令获取真实函数地址
            // ARM64 B 指令格式: 0x14000000 | (offset >> 2)
            
            void *bInstPtr = (void *)((uint8_t *)ptrMGCopyAnswer + 4);
            int32_t bInst = *((int32_t *)make_sym_readable(bInstPtr));
            
            // 检查是否是 B 指令 (opcode: 000101)
            if ((bInst & 0xFC000000) != 0x14000000) {
                DLog(@"MGCopyAnswer_internal: Invalid branch instruction: 0x%x", bInst);
                return;
            }
            
            DLog(@"B instruction: 0x%x", bInst);

            // 提取 26 位偏移量并进行符号扩展
            int32_t offset = bInst & 0x3FFFFFF;
            if (offset & 0x2000000) {
                offset |= 0xFC000000;  // 符号扩展
            }
            offset <<= 2;  // 偏移量以 4 字节为单位
            
            DLog(@"Offset: 0x%x (%d bytes)", offset, offset);
            
            // 计算真实函数地址
            void *mPtrMGCopyAnswer = (void *)_FUNC_ADDR_(bInstPtr, offset);
            DLog(@"Real MGCopyAnswer_internal address: %p", mPtrMGCopyAnswer);
            DLog(@"File offset of MGCopyAnswer_internal: 0x%lx",
                        ABS((long)mPtrMGCopyAnswer - (long)handle));
            
            // Hook 真实函数
            DobbyHook(make_sym_callable(mPtrMGCopyAnswer),
                          (void *)replaced_MGCopyAnswer_internal,
                          (void **)&original_MGCopyAnswer_internal);
            
            DLog(@"Hooked modern MGCopyAnswer_internal");
        }
        else
        {
            DLog(@"Unsupported MGCopyAnswer implementation");
            DLog(@"First 8 bytes: %02x %02x %02x %02x %02x %02x %02x %02x",
                        readablePtr[0], readablePtr[1], readablePtr[2], readablePtr[3],
                        readablePtr[4], readablePtr[5], readablePtr[6], readablePtr[7]);
        }
    }
}

成功。

项目只需链接:CoreFoundation.framework, Foundation.framework, libdobby.a即可。

dobby.h libdobby.a从这里下载(github-actions released this Mar 14, 2024 latest 5dfc854):https://github.com/jmpews/Dobby/releases/download/latest/dobby-iphoneos-all.tar.gz

选取里面的universal版本即可。

<think>嗯,用户问的是在Linux ARM64架构下如何实现inline hook的技术或方法。首先,我得回忆一下inline hook的基本概念。Inline hook通常是指通过修改目标函数的机器码,插入跳转指令,从而拦截或重定向函数执行流程。这对于调试、性能分析或安全检测很有用。 接下来要考虑的是ARM64架构的特点。ARM64使用AArch64指令集,指令长度固定为4字节,所以跳转指令可能需要更多的空间。比如,ARM64的B指令只能跳转±128MB的范围,如果目标地址超出这个范围,可能需要使用LDR+BR组合或者ADR指令来构造长跳转。 然后,权限问题也很重要。修改内存中的代码需要目标内存页具有可写和可执行的权限。通常,内核模块可以通过修改页表属性来调整权限,比如使用set_memory_rw函数。但用户空间的话可能需要使用mprotect系统调用来调整权限。 还有指令修复的问题。覆盖原有指令时,如果原来的指令不是4字节对齐的,可能会导致执行错误。比如,如果原函数的前两条指令被覆盖,可能需要将这两条指令复制到跳板(trampoline)中,并在跳转回来时执行它们,保证程序正确性。 接着要考虑的是同步问题。在修改指令时,如果有其他线程正在执行该函数,可能会导致竞争条件。需要暂停所有相关线程,或者在单核环境下确保原子操作。内核中可能需要使用stop_machine机制,用户空间可能需要信号处理或其他同步方法。 此外,还需要处理缓存一致性问题。ARM架构有指令缓存和数据缓存,修改代码后需要刷新缓存,确保CPU读取到最新的指令。可以使用__builtin___clear_cache函数来刷新缓存。 然后,用户可能需要具体的步骤示例。比如,如何编写内核模块或用户空间的代码来实现hook。比如在内核模块中,找到目标函数的地址,修改内存权限,写入跳转指令,处理指令修复,刷新缓存。用户空间的话,可能需要使用mprotect和memcpy来修改函数代码。 安全性也是一个问题,inline hook可能被安全软件检测到,因此可能需要更隐蔽的方法,比如使用硬件断点或修改函数指针。不过用户可能只是需要基本的方法。 最后,生成相关问题的时候,要考虑用户可能进一步的问题,比如如何调试、如何处理不同内核版本、如何绕过保护机制等等。</think>### Linux ARM64架构下实现Inline Hook的技术实现步骤 #### 一、技术原理 Inline Hook通过修改目标函数的机器指令,插入跳转指令到自定义函数。在ARM64架构中需注意: 1. 指令长度固定为4字节,跳转指令需适配$2^{28}$地址范围限制 2. 需要处理指令缓存(ICache)与数据缓存(DCache)一致性[^1] 3. 必须确保内存页可写(通过`mprotect`或`set_memory_rw`) #### 二、具体实现步骤 1. **定位目标函数地址** - 内核空间:通过`kallsyms_lookup_name` - 用户空间:使用`dlopen/dlsym` 2. **构造跳转指令** ```c // ARM64长跳转指令模板 uint32_t hook_code[] = { 0x58000050, // LDR X16, =hook_function 0xD61F0200 // BR X16 }; ``` 3. **修改内存权限** ```c // 内核模块示例 set_memory_rw((unsigned long)target_addr, 1); ``` 4. **写入Hook指令** ```c memcpy(target_addr, hook_code, sizeof(hook_code)); ``` 5. **处理指令缓存** ```c __builtin___clear_cache((char*)target_addr, (char*)target_addr + sizeof(hook_code)); ``` 6. **跳板函数实现** ```c void trampoline(void) { // 执行被覆盖的原指令 asm volatile("nop \n nop"); // 跳转回原函数+8字节处 } ``` #### 三、关键注意事项 1. **指令对齐**:ARM64要求所有指令必须4字节对齐 2. **原子操作**:建议使用`stop_machine`防止竞争条件[^2] 3. **异常处理**:需捕获可能产生的缺页异常(用户空间) #### 四、完整代码示例(内核模块) ```c #include <linux/module.h> #include <linux/kallsyms.h> typedef void (*target_func_type)(void); static void hook_function(void) { printk(KERN_INFO "Hook triggered!\n"); ((target_func_type)(trampoline))(); } static void install_hook(void) { unsigned long target_addr = (unsigned long)kallsyms_lookup_name("target_function"); uint32_t jump_code[2] = { /* 构造的跳转指令 */ }; set_memory_rw(target_addr, 1); memcpy((void*)target_addr, jump_code, sizeof(jump_code)); __builtin___clear_cache((void*)target_addr, (void*)target_addr+8); } ``` #### 五、检测与防御 1. 完整性校验:检查关键函数首指令 2. 陷阱标志:在关键内存区域设置写保护 3. 监控系统调用:拦截`mprotect`和`ptrace`调用[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值