手游外挂基础篇之inline-hook
字数 837 2025-08-22 18:37:22

Android手游外挂开发基础:Inline Hook技术详解

1. 引言

本文档详细讲解Android平台下使用Inline Hook技术实现游戏破解的方法。Inline Hook是一种通过修改目标函数的机器码来实现函数劫持的技术,在手游安全分析和外挂开发中有着广泛应用。

2. Inline Hook基本原理

2.1 核心概念

Inline Hook主要通过以下步骤实现:

  1. 修改目标函数指令:在目标函数开始处插入跳转指令
  2. 跳转到桩函数(Stub):执行自定义的桩函数代码
  3. 执行原始指令:在桩函数中执行被覆盖的原始指令
  4. 返回原流程:跳转回目标函数的剩余部分继续执行

2.2 经典流程图

原始流程:
目标函数开始 -> 原始指令1 -> 原始指令2 -> ... -> 函数返回

Hook后流程:
目标函数开始 -> 跳转到桩函数 -> 执行自定义代码 -> 执行被覆盖的原始指令 -> 跳回原始流程 -> 原始指令3 -> ... -> 函数返回

3. 技术实现详解

3.1 核心数据结构

typedef struct tagINLINEHOOKINFO {
    void *pHookAddr;                // hook的地址
    void *pStubShellCodeAddr;       // 桩函数shellcode地址
    void (*onCallBack)(struct pt_regs *); // 回调函数
    void **ppOldFuncAddr;           // 存放原指令函数地址的指针
    BYTE szbyBackupOpcodes[OPCODEMAXLEN]; // 备份的原指令
} INLINE_HOOK_INFO;

3.2 关键功能函数

3.2.1 修改内存页属性

bool ChangePageProperty(void *pAddress, size_t size) {
    unsigned long ulPageSize = sysconf(_SC_PAGESIZE);
    unsigned long ulNewPageStartAddress = (unsigned long)(pAddress) & ~(ulPageSize - 1);
    long lPageCount = (size / ulPageSize) + 1;
    int iRet = mprotect((const void *)(ulNewPageStartAddress), 
                       lPageCount * ulPageSize, 
                       PROT_READ | PROT_WRITE | PROT_EXEC);
    return iRet != -1;
}

3.2.2 构造跳转指令

bool BuildArmJumpCode(void *pCurAddress, void *pJumpAddress) {
    // LDR PC, [PC, #-4]的机器码是0xE51FF004
    BYTE szLdrPCOpcodes[8] = {0x04, 0xF0, 0x1F, 0xE5};
    memcpy(szLdrPCOpcodes + 4, &pJumpAddress, 4);
    memcpy(pCurAddress, szLdrPCOpcodes, 8);
    cacheflush(*((uint32_t *)pCurAddress), 8, 0);
    return true;
}

3.3 Hook主流程

bool HookArm(INLINE_HOOK_INFO *pstInlineHook) {
    // 1. 初始化hook点信息
    if (!InitArmHookInfo(pstInlineHook)) return false;
    
    // 2. 构造桩函数
    if (!BuildStub(pstInlineHook)) return false;
    
    // 3. 构造原指令函数
    if (!BuildOldFunction(pstInlineHook)) return false;
    
    // 4. 覆盖原指令
    if (!RebuildHookTarget(pstInlineHook)) return false;
    
    return true;
}

3.4 桩函数实现(ARM汇编)

.global _shellcode_start_s
.global _shellcode_end_s
.global _hookstub_function_addr_s
.global _old_function_addr_s

.data
_shellcode_start_s:
    push {r0, r1, r2, r3}
    mrs r0, cpsr
    str r0, [sp, #0xC]
    str r14, [sp, #8]
    add r14, sp, #0x10
    str r14, [sp, #4]
    pop {r0}
    push {r0-r12}
    mov r0, sp
    ldr r3, _hookstub_function_addr_s
    blx r3
    ldr r0, [sp, #0x3C]
    msr cpsr, r0
    ldmfd sp!, {r0-r12}
    ldr r14, [sp, #4]
    ldr sp, [r13]
    ldr pc, _old_function_addr_s

_hookstub_function_addr_s:
    .word 0xffffffff
_old_function_addr_s:
    .word 0xffffffff
_shellcode_end_s:
    .end

4. 实战案例:修改游戏计时器

4.1 目标函数分析

目标函数是一个计时器判断函数,当计数器超过300时返回胜利信息:

JNIEXPORT jstring JNICALL Java_com_example_gslab_ibored_MainActivity_UpdateResult(
    JNIEnv *pJniEnv, jclass Jclass) {
    unsigned int uiLocalVar = 1;
    uiTimeCounter += uiLocalVar;
    if (uiTimeCounter > 300) {
        return pJniEnv->NewStringUTF("Enough. You Win!");
    } else {
        return pJniEnv->NewStringUTF("Just Wait.");
    }
}

4.2 实现步骤

  1. 定位Hook点:使用IDA找到判断指令的位置
  2. 计算偏移地址:获取模块基址和Hook点偏移
  3. 实现Hook代码
void EvilHookStubFunctionForIBored(pt_regs *regs) {
    LOGI("In Evil Hook Stub.");
    regs->uregs[0] = 0x333; // 修改r0寄存器值
}

void ModifyIBored() {
    void *pModuleBaseAddr = GetModuleBaseAddr(-1, "libnative-lib.so");
    uint32_t uiHookAddr = (uint32_t)pModuleBaseAddr + 0x3349c;
    InlineHook((void *)(uiHookAddr), EvilHookStubFunctionForIBored);
}

4.3 获取模块基址

void *GetModuleBaseAddr(pid_t pid, char *pszModuleName) {
    char szMapFilePath[256] = {0};
    snprintf(szMapFilePath, sizeof(szMapFilePath), 
            pid < 0 ? "/proc/self/maps" : "/proc/%d/maps", pid);
    
    FILE *pFileMaps = fopen(szMapFilePath, "r");
    while (fgets(szFileLineBuffer, sizeof(szFileLineBuffer), pFileMaps)) {
        if (strstr(szFileLineBuffer, pszModuleName)) {
            char *pszModuleAddress = strtok(szFileLineBuffer, "-");
            return (void *)strtoul(pszModuleAddress, NULL, 16);
        }
    }
    fclose(pFileMaps);
    return 0;
}

5. 编译配置

5.1 Hook模块编译配置(Android.mk)

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CPPFLAGS += -g -O0
LOCAL_ARM_MODE := arm
LOCAL_MODULE := IHook
LOCAL_SRC_FILES := IHook.c ihookstub.s
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
include $(BUILD_STATIC_LIBRARY)

5.2 主模块编译配置

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CPPFLAGS += -g -O0
LOCAL_ARM_MODE := arm
LOCAL_MODULE := InlineHook
LOCAL_STATIC_LIBRARIES:= IHook
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../InlineHook
LOCAL_SRC_FILES := InlineHook.cpp
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
include $(BUILD_SHARED_LIBRARY)

6. 关键注意事项

  1. 指令集一致性:确保Hook代码和目标程序使用相同的指令集(ARM/Thumb)
  2. 指令覆盖长度:ARM模式下需要覆盖8字节(两条指令)
  3. 执行顺序:先构造桩函数再覆盖原指令,避免竞争条件
  4. 寄存器保存:桩函数必须妥善保存和恢复所有使用的寄存器
  5. 内存属性:修改代码段内存属性为可写可执行

7. 总结

Inline Hook技术是Android平台游戏安全分析和外挂开发的基础技术之一。通过本文介绍的方法,可以实现对目标函数的劫持和修改,达到改变游戏逻辑的目的。实际应用中还需要考虑对抗检测、稳定性优化等问题。

Android手游外挂开发基础:Inline Hook技术详解 1. 引言 本文档详细讲解Android平台下使用Inline Hook技术实现游戏破解的方法。Inline Hook是一种通过修改目标函数的机器码来实现函数劫持的技术,在手游安全分析和外挂开发中有着广泛应用。 2. Inline Hook基本原理 2.1 核心概念 Inline Hook主要通过以下步骤实现: 修改目标函数指令 :在目标函数开始处插入跳转指令 跳转到桩函数(Stub) :执行自定义的桩函数代码 执行原始指令 :在桩函数中执行被覆盖的原始指令 返回原流程 :跳转回目标函数的剩余部分继续执行 2.2 经典流程图 3. 技术实现详解 3.1 核心数据结构 3.2 关键功能函数 3.2.1 修改内存页属性 3.2.2 构造跳转指令 3.3 Hook主流程 3.4 桩函数实现(ARM汇编) 4. 实战案例:修改游戏计时器 4.1 目标函数分析 目标函数是一个计时器判断函数,当计数器超过300时返回胜利信息: 4.2 实现步骤 定位Hook点 :使用IDA找到判断指令的位置 计算偏移地址 :获取模块基址和Hook点偏移 实现Hook代码 : 4.3 获取模块基址 5. 编译配置 5.1 Hook模块编译配置(Android.mk) 5.2 主模块编译配置 6. 关键注意事项 指令集一致性 :确保Hook代码和目标程序使用相同的指令集(ARM/Thumb) 指令覆盖长度 :ARM模式下需要覆盖8字节(两条指令) 执行顺序 :先构造桩函数再覆盖原指令,避免竞争条件 寄存器保存 :桩函数必须妥善保存和恢复所有使用的寄存器 内存属性 :修改代码段内存属性为可写可执行 7. 总结 Inline Hook技术是Android平台游戏安全分析和外挂开发的基础技术之一。通过本文介绍的方法,可以实现对目标函数的劫持和修改,达到改变游戏逻辑的目的。实际应用中还需要考虑对抗检测、稳定性优化等问题。