吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 17562|回复: 28
上一主题 下一主题
收起左侧

[Android 原创] Android逆向Hook学习——第二篇:Android inline Hook

  [复制链接]
跳转到指定楼层
楼主
一夜梦惊人 发表于 2019-5-1 19:56 回帖奖励
本帖最后由 一夜梦惊人 于 2019-5-2 22:42 编辑

一、前言
很早之前就开始准备这篇文章,但是由于种种原因一直没有写,终于乘着这个五一写下了这篇文章,为此我深感抱歉。本来呢第二篇是准备写Cydia Substrate(以下简称CS)的,但是由于CS不支持Android4.4及以上等一些因素最后我放弃了写该文章,但是呢我之前已经写过一篇CS的文章(CydiaSubtrateHook——Native注册混淆破解),我会着手准备修改的。
Android逆向Hook学习系列:
第一篇:Xposed;第二篇:Android inline Hook;第三篇:FrIDA(待添加)
二、正文
2.1 Android inline Hook准备工作
在GitHub上有许多的Android inline Hook(以下简称AIH)项目,但是选择一个好的项目将会让你事半功倍,在此我选择了ele7enxxh的Android inline Hook项目(https://github.com/ele7enxxh/Android-Inline-Hook)。当然优秀的项目还有很多,比如Rprop的And64InlineHook(https://github.com/Rprop/And64InlineHook),GToad的项目(为了适配多架构,项目过多,自行在GitHub上搜索)。请大家注意,我选择的是ARM架构的项目,如要使用,请根据自身手机架构来选择项目,Android逆向Hook学习的AIH号飞船已经启动,我是驾驶员一夜梦惊人,请大家速度上车
2.2 Android inline Hook代码编写
下载AIH项目的包,并且解压。新建一个项目,由于本人是Android studio 3.3.2,无法自动创建native project,所以我将会手动一遍。首先在项目main文件夹下新建一个jni文件夹,并且将解压后的AIH项目中的文件全部复制到该文件夹(除去example文件夹、README.md)。

由于ele7enxxh使用的是NDK-build,为了响应时代的号召,我改写成了Cmake,但是我也会放出NDK-build的编写。新建一个CMakeLists.txt,内容如下:
[Asm] 纯文本查看 复制代码
cmake_minimum_required(VERSION 3.4.1)

add_library(inlineHook SHARED
        hooktest.cpp
        inlineHook.c
        relocate.c)

target_link_libraries(
        inlineHook
        android
        log)

在build.gradle(app)中添加如下:

其中abiFilters只能是armeabi和armeabi-v7a,这里之所以只写了armeabi-v7a是因为armeabi只有在NDK-17版本以下才支持,现在已经弃用了(为什么只能写这两项是因为这两项不少的是ARM架构,armeabi-v8基本是ARM64,这个生成的so基本全部CPU都能运行,只不过是其架构的原告会导致Hook无法成功,所以生成一个即可通用,需要注意架构)。
然后我还编写了一个Java类如下:
[Java] 纯文本查看 复制代码
package com.yymjr.nativehook;

public final class nativeMethod {
    static {
        System.loadLibrary("inlineHook");
    }

    public static native void inlineHook();
    public static native void printfData();
}

最后我改写hooktest的代码为下:
[C++] 纯文本查看 复制代码
#include <stdio.h>
#include <jni.h>
#include <android/log.h>
#include "include/inlineHook.h"
#import "com_yymjr_nativehook_nativeMethod.h"

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "yymjr-AndroidInlineHook", __VA_ARGS__))

int hook(void);
const struct JNINativeInterface* NativeInterface;

jint (*old_RegisterNatives)(JNIEnv *env, jclass clazz, const JNINativeMethod* methods,jint nMethods) = NULL;
jint new_RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod* methods,jint nMethods){
    LOGI("------------------------------------------------");
    LOGI("env:%p,class:%p,methods:%p,methods_num:%d",env,clazz,methods,nMethods);
    LOGI("------------------------------------------------");
    for (int i = 0;i < nMethods;i++){
        LOGI("name:%s,sign:%s,address:%p",methods[i].name,methods[i].signature,methods[i].fnPtr);
    }
    LOGI("------------------------------------------------");
    return old_RegisterNatives(env,clazz,methods,nMethods);
}

JNIEXPORT void JNICALL (*old_printfData)(JNIEnv* env,jobject thiz) = NULL;
JNIEXPORT void JNICALL new_printfData(JNIEnv* env,jobject thiz)
{
    LOGI("printfInlineHook");
}

int hook(void)
{
    if (registerInlineHook((uint32_t) NativeInterface->RegisterNatives, (uint32_t) new_RegisterNatives, (uint32_t **) &old_RegisterNatives) != ELE7EN_OK) {
        return -1;
    }
    if (inlineHook((uint32_t) puts) != ELE7EN_OK) {
        return -1;
    }

    return 0;
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
    struct _JNIEnv env;
    if (vm->GetEnv( (void**)&env, JNI_VERSION_1_4) == JNI_OK) {
        NativeInterface = env.functions;
        return JNI_VERSION_1_4;
    }
}

/*
 * Class:     com_yymjr_nativehook_nativeMethod
 * Method:    inlineHook
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_yymjr_nativehook_nativeMethod_inlineHook
        (JNIEnv *, jclass)
{
    if (registerInlineHook((uint32_t) Java_com_yymjr_nativehook_nativeMethod_printfData, (uint32_t) new_printfData, (uint32_t **) &old_printfData) != ELE7EN_OK) {
        return;
    }
    if (inlineHook((uint32_t) puts) != ELE7EN_OK) {
        return;
    }
}
/*
 * Class:     com_yymjr_nativehook_nativeMethod
 * Method:    printfData
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_yymjr_nativehook_nativeMethod_printfData
        (JNIEnv *, jclass)
{
    LOGI("printfData");
}

hooktest当中实现了两个功能,一是hook了native method:printfData;二是hook了RegisterNatives方法(参考:Android中-抖音火山视频的Native注册混淆函数获取方法)。我就不阐述了,请大家自行阅读。
由于我的ARM架构手机出现了一点小问题,我也没能实时运行给大家看,大家多多体谅。如果哪位有ARM架构的手机,在运行的过程出现问题,欢迎回帖我将联系和重新编辑(只不过这么简单,我觉得是不太可能出现问题)。AIH号飞船已经正常运行,请大家慢慢体会。
最后补上NDK- build的代码:
[Asm] 纯文本查看 复制代码
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := inlineHook 
LOCAL_SRC_FILES := inlineHook.c relocate.c hooktest.cpp

LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog -lz

include $(BUILD_SHARED_LIBRARY)

2.3 Android inline Hook工作原理
AIH号飞船现在准备以光速进行逃逸,请大家以自身情况来决定是否还要继续乘坐。
我们从registerInlineHook入手。
[C] 纯文本查看 复制代码
enum ele7en_status registerInlineHook(uint32_t target_addr, uint32_t new_addr, uint32_t **proto_addr)
{
        struct inlineHookItem *item;

        // 判断地址是否在进程内
        if (!isExecutableAddr(target_addr) || !isExecutableAddr(new_addr)) {
                return ELE7EN_ERROR_NOT_EXECUTABLE;
        }

        // 判断是否存在于inlineHookItem
        item = findInlineHookItem(target_addr);
        if (item != NULL) {
                if (item->status == REGISTERED) {
                        return ELE7EN_ERROR_ALREADY_REGISTERED;
                }
                else if (item->status == HOOKED) {
                        return ELE7EN_ERROR_ALREADY_HOOKED;
                }
                else {
                        return ELE7EN_ERROR_UNKNOWN;
                }
        }
// 不存在就添加inlineHookItem
        item = addInlineHookItem();

[font=simsun][size=2][color=#808080]/*
[/color][color=#808080]* target_addr: 待Hook的目标函数地址,即为当前PC值,用于修正指令
[/color][color=#808080]* orig_instructions:存放原有指令的首地址,用于修正指令和后续对原有指令的恢复
[/color][color=#808080]* length:存放的原有指令的长度,Arm指令为8字节;Thumb指令为12字节
[/color][color=#808080]* trampoline_instructions:存放修正后指令的首地址,用于调用原函数
[/color][color=#808080]* orig_boundaries:存放原有指令的指令边界(所谓边界即为该条指令与起始地址的偏移量),用于后续线程处理中,对PC的迁移
[/color][color=#808080]* trampoline_boundaries:存放修正后指令的指令边界,用途与上相同
[/color][color=#808080]* count:处理的指令项数,用途与上相同
[/color][color=#808080]* */[/color][/size][/font]
        item->target_addr = target_addr;
        item->new_addr = new_addr;
        item->proto_addr = proto_addr;
        // TEST_BIT0是判断指令集模式
        item->length = TEST_BIT0(item->target_addr) ? 12 : 8;
        item->orig_instructions = malloc(item->length); 
        memcpy(item->orig_instructions, (void *) CLEAR_BIT0(item->target_addr), item->length);

        item->trampoline_instructions = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
// 修正指令
        relocateInstruction(item->target_addr, item->orig_instructions, item->length, item->trampoline_instructions, item->orig_boundaries, item->trampoline_boundaries, &item->count);

        item->status = REGISTERED;

        return ELE7EN_OK;
}

其中大部分都能看懂,最重要的就是relocateInstruction方法。
[C] 纯文本查看 复制代码
void relocateInstruction(uint32_t target_addr, void *orig_instructions, int length, void *trampoline_instructions, int *orig_boundaries, int *trampoline_boundaries, int *count)
{
        if (target_addr & 1 == 1) {
                relocateInstructionInThumb(target_addr - 1, (uint16_t *) orig_instructions, length, (uint16_t *) trampoline_instructions, orig_boundaries, trampoline_boundaries, count);
        }
        else {
                relocateInstructionInArm(target_addr, (uint32_t *) orig_instructions, length, (uint32_t *) trampoline_instructions, orig_boundaries, trampoline_boundaries, count);
        }
}

relocateInstruction方法只是做一个判断是什么指令集模式,我们就只看ARM,Thumb自行查看。
[C] 纯文本查看 复制代码
static void relocateInstructionInArm(uint32_t target_addr, uint32_t *orig_instructions, int length, uint32_t *trampoline_instructions, int *orig_boundaries, int *trampoline_boundaries, int *count)
{
        uint32_t pc;
        uint32_t lr;
        int orig_pos;
        int trampoline_pos;
        // PC为target_addr地址
        pc = target_addr + 8;
        lr = target_addr + length;

        trampoline_pos = 0;
        // 分别获取到函数地址的每条指令
        for (orig_pos = 0; orig_pos < length / sizeof(uint32_t); ++orig_pos) {
                uint32_t instruction;
                int type;

                // 指令边界
                orig_boundaries[*count] = orig_pos * sizeof(uint32_t);
                // 修正指令边界
                trampoline_boundaries[*count] = trampoline_pos * sizeof(uint32_t);
                ++(*count);

                instruction = orig_instructions[orig_pos];
                // 获取指令类型,并且根据指令类型进行修正
                type = getTypeInArm(instruction);
                if (type == BLX_ARM || type == BL_ARM || type == B_ARM || type == BX_ARM) {
                        uint32_t x;
                        int top_bit;
                        uint32_t imm32;
                        uint32_t value;


                        if (type == BLX_ARM || type == BL_ARM) {
                                trampoline_instructions[trampoline_pos++] = 0xE28FE004;        // ADD LR, PC, #4
                        }
                        trampoline_instructions[trampoline_pos++] = 0xE51FF004;          // LDR PC, [PC, #-4]
                        if (type == BLX_ARM) {
                                x = ((instruction & 0xFFFFFF) << 2) | ((instruction & 0x1000000) >> 23);
                        }
                        else if (type == BL_ARM || type == B_ARM) {
                                x = (instruction & 0xFFFFFF) << 2;
                        }
                        else {
                                x = 0;
                        }
                        
                        top_bit = x >> 25;
                        imm32 = top_bit ? (x | (0xFFFFFFFF << 26)) : x;
                        if (type == BLX_ARM) {
                                value = pc + imm32 + 1;
                        }
                        else {
                                value = pc + imm32;
                        }
                        // 实时计算跳转地址
                        trampoline_instructions[trampoline_pos++] = value;
                        
                }
                else if (type == ADD_ARM) {
                        int rd;
                        int rm;
                        int r;
                        
                        rd = (instruction & 0xF000) >> 12;
                        rm = instruction & 0xF;
                        
                        for (r = 12; ; --r) {
                                if (r != rd && r != rm) {
                                        break;
                                }
                        }

                        // PUSH {Rr},保护Rr寄存器值
                        trampoline_instructions[trampoline_pos++] = 0xE52D0004 | (r << 12);
                        // LDR Rr, [PC, #8],将PC值存入Rr寄存器中
                        trampoline_instructions[trampoline_pos++] = 0xE59F0008 | (r << 12);
                        // 变换原指令`ADR Rd, <label>`为`ADR Rd, Rr, ?`
                        trampoline_instructions[trampoline_pos++] = (instruction & 0xFFF0FFFF) | (r << 16);
                        //POP {Rr},恢复Rr寄存器值
                        trampoline_instructions[trampoline_pos++] = 0xE49D0004 | (r << 12);
                        // ADD PC, PC,跳过下一条指令
                        trampoline_instructions[trampoline_pos++] = 0xE28FF000;
                        trampoline_instructions[trampoline_pos++] = pc;
                }
                else if (type == ADR1_ARM || type == ADR2_ARM || type == LDR_ARM || type == MOV_ARM) {
                        int r;
                        uint32_t value;
                        
                        r = (instruction & 0xF000) >> 12;
                        
                        if (type == ADR1_ARM || type == ADR2_ARM || type == LDR_ARM) {
                                uint32_t imm32;
                                
                                imm32 = instruction & 0xFFF;
                                if (type == ADR1_ARM) {
                                        value = pc + imm32;
                                }
                                else if (type == ADR2_ARM) {
                                        value = pc - imm32;
                                }
                                else if (type == LDR_ARM) {
                                        int is_add;
                                        
                                        is_add = (instruction & 0x800000) >> 23;
                                        if (is_add) {
                                                value = ((uint32_t *) (pc + imm32))[0];
                                        }
                                        else {
                                                value = ((uint32_t *) (pc - imm32))[0];
                                        }
                                }
                        }
                        else {
                                value = pc;
                        }
                                
                        trampoline_instructions[trampoline_pos++] = 0xE51F0000 | (r << 12);        // LDR Rr, [PC]
                        trampoline_instructions[trampoline_pos++] = 0xE28FF000;        // ADD PC, PC
                        trampoline_instructions[trampoline_pos++] = value;
                }
                else {
                        trampoline_instructions[trampoline_pos++] = instruction;
                }
                pc += sizeof(uint32_t);
        }

        // 返回指令
        trampoline_instructions[trampoline_pos++] = 0xe51ff004;        // LDR PC, [PC, #-4]
        trampoline_instructions[trampoline_pos++] = lr;
}

AIH的指令修正我们已经看完,接下来回过头来看inlineHook方法。
[C] 纯文本查看 复制代码
enum ele7en_status inlineHook(uint32_t target_addr)
{
        int i;
        struct inlineHookItem *item;
        
        item = NULL;
        // 获取target_addr的inlineHookItem
        for (i = 0; i < info.size; ++i) {
                if (info.item[i].target_addr == target_addr) {
                        item = &info.item[i];
                        break;
                }
        }

        if (item == NULL) {
                return ELE7EN_ERROR_NOT_REGISTERED;
        }
        
        // 判断inlineHook状态
        if (item->status == REGISTERED) {
                pid_t pid;

                pid = freeze(item, ACTION_ENABLE);

                doInlineHook(item);

                unFreeze(pid);

                return ELE7EN_OK;
        }
        else if (item->status == HOOKED) {
                return ELE7EN_ERROR_ALREADY_HOOKED;
        }
        else {
                return ELE7EN_ERROR_UNKNOWN;
        }
}

其中最为重要是doInlineHook方法。
[Asm] 纯文本查看 复制代码
static void doInlineHook(struct inlineHookItem *item)
{
        mprotect((void *) PAGE_START(CLEAR_BIT0(item->target_addr)), PAGE_SIZE * 2, PROT_READ | PROT_WRITE | PROT_EXEC);

        if (item->proto_addr != NULL) {
                *(item->proto_addr) = TEST_BIT0(item->target_addr) ? (uint32_t *) SET_BIT0((uint32_t) item->trampoline_instructions) : item->trampoline_instructions;
        }
        
        if (TEST_BIT0(item->target_addr)) {
                int i;

                i = 0;
// 判断指令集模式
                if (CLEAR_BIT0(item->target_addr) % 4 != 0) {
                        ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xBF00;  // NOP
                }
                ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xF8DF;
                ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xF000;        // LDR.W PC, [PC]
                ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = item->new_addr & 0xFFFF;
                ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = item->new_addr >> 16;
        }
        else {
                ((uint32_t *) (item->target_addr))[0] = 0xe51ff004;        // LDR PC, [PC, #-4]
                ((uint32_t *) (item->target_addr))[1] = item->new_addr;
        }

        mprotect((void *) PAGE_START(CLEAR_BIT0(item->target_addr)), PAGE_SIZE * 2, PROT_READ | PROT_EXEC);

        item->status = HOOKED;
        
        cacheflush(CLEAR_BIT0(item->target_addr), CLEAR_BIT0(item->target_addr) + item->length, 0);
}

如果想要形象一点的话请看GToad的讲解,有图而每条指令的作用都会讲解,可以查看(指令修复:https://gtoad.github.io/2018/07/13/Android-Inline-Hook-Fix/)。因为本人缘故,也就只能如此了。最后AIH号飞船已经成功到底目的地,请各位乘客下船。
三、后语
AIH虽然只能inline Hook,但是却有着其特殊之处,在我们逆向之路上是不可缺少的一部分。祝大家五一快乐,事业高升!


看到这里还不给个评分??

免费评分

参与人数 9威望 +1 吾爱币 +16 热心值 +9 收起 理由
hzzheyang + 1 + 1 谢谢@Thanks!
qtfreet00 + 1 + 9 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
耳食之辈 + 1 谢谢@Thanks!
zycode + 1 + 1 谢谢@Thanks!
Cailgun + 1 + 1 热心回复!
winwei + 1 + 1 用心讨论,共获提升!
独行风云 + 1 + 1 谢谢@Thanks!
夜步城 + 1 + 1 感觉很高深啊,学习一下
笙若 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
 楼主| 一夜梦惊人 发表于 2019-5-4 21:05 |楼主
心由你变 发表于 2019-5-4 19:32
能否给看看,微群管理助手,这个软件挺好,我用MT管理器破解内购支付,支付成功了,实际没到账,可能是服 ...

嗯,我是没有时间来做这个破解,但是我可以给你说说。你的内购破解了意思差不多就是本地显示购买到VIP这类,但是没到账那么就是会有三种情况。一是功能的VIP验证和你破解的这些不同,导致的就是破解的不完整;二是这个功能需要服务器的配合,那么服务器在使用的时候验证了VIP,第三种就是MT管理器那样了。
推荐
iGreenMind 发表于 2019-5-2 21:59
感谢楼主的知识分享。给楼主提个建议哈,既然这是第一篇,那能不能在这个第二篇的前面,或者稍后的某个地方,将你第一篇的学习的链接地址贴出来?
我是从首页看到的你这个链接,所以就有心想去看看第一篇。
如果你能想到这个贴心的功能,对于自己的帖子的热度、还有用户的方便,都是挺好的建议。
望采纳。

不过采不采纳也都是个人喜好,感谢分享
2019年5月2日21:59:52
沙发
4everlove 发表于 2019-5-1 20:09
3#
 楼主| 一夜梦惊人 发表于 2019-5-1 20:16 |楼主
4everlove 发表于 2019-5-1 20:09
看不懂看不懂尴尬

不涉及原理那部分野看不懂吗?
4#
多幸运遇见baby 发表于 2019-5-1 21:40
鼓励转贴优秀软件安全工具和文档!
5#
xinggewang 发表于 2019-5-1 23:53
虽然看不懂,但是我支持有文化的人
6#
风雨辰 发表于 2019-5-2 07:58
谢谢楼主的教程
7#
lsrh2000 发表于 2019-5-2 20:04
感谢分享,好好学习天天向上
8#
Boy77wapj 发表于 2019-5-2 20:29
能不能把魔力小V 最新版给pj了啊 谢谢啊 大神 还有这个软件https://www.kzwr.com/kzwrfs?fid=64f475bc8adch7zfms.zip
9#
smith168668 发表于 2019-5-2 21:25
努力学习
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 15:29

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表