[TOC]

引言

本片文章主要学习Android平台的Inline-Hook来配合ptrace注入实现简单的游戏破解,了解游戏破解相关的安全技术。

概述

下面通过一张经典的inline hook流程图,做个大致介绍。

主要通过修改一条汇编指令,让指令流程跳转到我们设计好的桩函数处,执行完我们的桩函数后紧接着执行我们修改的哪条汇编指令,紧接着经过一个跳转指令返回原来的指令流程里继续程序的正常执行。

内联算法

  1. 先构造我们的桩函数,主要进行以下操作:
    • 寄存器的备份,为第三步继续执行原指令做准备
    • 跳转到用户自定义函数的指令
    • 寄存器还原操作
    • 跳转到构造好的原指令函数
  2. 构造原指令函数,这个原指令函数主要是执行将要被修改的汇编指令,并跳转到程序正常的执行流程中
  3. 指令覆盖操作。使用跳转指令覆盖原指令

代码实现

Ihook.h:头文件,声明了hook过程中用到的一些功能函数和宏定义

#include <stdio.h>
#include <Android/log.h>
#include <errno.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ptrace.h>
#include <stdbool.h>

#ifndef BYTE
#define BYTE unsigned char
#endif

#define OPCODEMAXLEN 8      //inline hook所需要的opcodes最大长度

#define LOG_TAG "GSLab"
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args);

/** shellcode里用到的参数、变量*/
extern unsigned long _shellcode_start_s;
extern unsigned long _shellcode_end_s;
extern unsigned long _hookstub_function_addr_s; //根函数地址
extern unsigned long _old_function_addr_s;  //原指令地址

//hook点信息
typedef struct tagINLINEHOOKINFO{
    void *pHookAddr;                //hook的地址
    void *pStubShellCodeAddr;            //跳过去的shellcode stub的地址
    void (*onCallBack)(struct pt_regs *);  
    //回调函数,跳转过去的函数地址
    void ** ppOldFuncAddr;             //shellcode 中存放old function的地址
    BYTE szbyBackupOpcodes[OPCODEMAXLEN];    //原来的opcodes
} INLINE_HOOK_INFO;

//更高内存页属性
bool ChangePageProperty(void *pAddress, size_t size);

//获取模块基址
extern void * GetModuleBaseAddr(pid_t pid, char* pszModuleName);

//初始化ARM指令集的hook信息结构体
bool InitArmHookInfo(INLINE_HOOK_INFO* pstInlineHook);

//构建桩函数
bool BuildStub(INLINE_HOOK_INFO* pstInlineHook);

//构建跳转代码
bool BuildArmJumpCode(void *pCurAddress , void *pJumpAddress);

//构建原指令的函数
bool BuildOldFunction(INLINE_HOOK_INFO* pstInlineHook);

//重写hook点的原指令,使其跳转到桩函数处
bool RebuildHookTarget(INLINE_HOOK_INFO* pstInlineHook);

//HOOK的总流程
extern bool HookArm(INLINE_HOOK_INFO* pstInlineHook);

Ihook.c:HookArm函数,主要的Hook模块。我们先将整个hook流程串起来,再去补全4个功能函数的代码

#include "Ihook.h"


bool HookArm(INLINE_HOOK_INFO* pstInlineHook)
{
    //hook结果
    bool bRet = false;

    while(1)
    {
        //判断是否传入Hook点信息的结构体
        if(pstInlineHook == NULL)
        {
            LOGI("pstInlineHook is null.");
            break
        }

        /* 初始化hook点的信息,如原指令地址、将要执行的用户自定义函数*/
        if(InitArmHookInfo(pstInlineHook) == false)
        {
            LOGI("Init Arm HookInfo fail.");
            break;
        }

        /* 1. 构造桩函数*/
        if(BuildStub(pstInlineHook) == false)
        {
            LOGI("BuildStub fail.");
            break;
        }

        /* 2. 构造原指令函数,执行被覆盖指令并跳转回原始指令流程*/
        if(BuildOldFunction(pstInlineHook) == false)
        {
            LOGI("BuildOldFunction fail.");
            break;
        }

        /* 3. 改写原指令为跳转指令,跳转到桩函数处*/
        if(RebuildHookTarget(pstInlineHook) == false)
        {
            LOGI("RebuildHookAddress fail.");
            break;
        }

        bRet = true;
        break;
    }

    return bRet;
}

hook.c:InitArmHookInfo函数,保存原指令的opcode

/**
 *  初始化hook点信息,保存原指令的opcode
 *
 *  @param  pstInlineHook hook点相关信息的结构体
 *  @return 初始化是否成功
 */
bool InitArmHookInfo(INLINE_HOOK_INFO* pstInlineHook)
{
    bool bRet = false;

    if(pstInlineHook == NULL)
    {
        LOGI("pstInlineHook is null");
        return bRet;
    }

    memcpy(pstInlineHook->szbyBackupOpcodes, pstInlineHook->pHookAddr, 8);
    return bRet;
}

hook.c:BuildStub函数,第一步构造桩函数,这里我们用shellcode来构造。

  • 我们申请一块内存并且将内存属性改成可执行,将shellcode拷进去,shellcode的起始地址就是桩函数的地址,将这个地址存进hook点结构体中
  • 还有将shellcode中存有原指令函数地址的变量留空,并且将该变量的内存地址存放进hook点结构体中,以便在构造原指令函数的时候,用原指令函数的地址将shellcode中的这个变量填充
  • 从hook点结构体中获取用户自定义函数的地址给shellcode中的_hookstub_function_addr_s变量赋值
/**
 *  修改页属性,改成可读可写可执行
 *  @param   pAddress   需要修改属性起始地址
 *  @param   size       需要修改页属性的长度,byte为单位
 *  @return  bool       修改是否成功
 */
bool ChangePageProperty(void *pAddress, size_t size)
{
    bool bRet = false;

    if(pAddress == NULL)
    {
        LOGI("change page property error.");
        return bRet;
    }
    //计算包含的页数、对齐起始地址
    unsigned long ulPageSize = sysconf(_SC_PAGESIZE);
    int iProtect = PROT_READ | PROT_WRITE | PROT_EXEC;
    //页对齐,把小于4096的位数(前12位)都置0,只取大于4096的位数且其值必然是4096的整数倍
    //并且这个值必然小于等于参数pAddress
    unsigned long ulNewPageStartAddress = (unsigned long)(pAddress) & ~(ulPageSize - 1);

    long lPageCount = (size / ulPageSize) + 1;
    int iRet = mprotect((const void *)(ulNewPageStartAddress), lPageCount*ulPageSize , iProtect);
    if(iRet == -1)
    {
        LOGI("mprotect error:%s", strerror(errno));
        return bRet;
    }
    return true;
}

/**
 *  1. 构造桩函数。这里的桩函数我们主要在shellcode中实现
 *      * 保存寄存器的值
 *      * 跳转到用户自定义函数callback
 *      * 寄存器还原操作
 *      * 跳转到构造好的原指令函数中
 *
 *  @param  pstInlineHook hook点相关信息的结构体
 *  @return inlinehook桩是否构造成功
 */
bool BuildStub(INLINE_HOOK_INFO* pstInlineHook)
{
点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖