吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 18558|回复: 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)。
捕获.PNG
由于ele7enxxh使用的是NDK-build,为了响应时代的号召,我改写成了Cmake,但是我也会放出NDK-build的编写。新建一个CMakeLists.txt,内容如下:
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
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)中添加如下:
捕获1.PNG
其中abiFilters只能是armeabi和armeabi-v7a,这里之所以只写了armeabi-v7a是因为armeabi只有在NDK-17版本以下才支持,现在已经弃用了(为什么只能写这两项是因为这两项不少的是ARM架构,armeabi-v8基本是ARM64,这个生成的so基本全部CPU都能运行,只不过是其架构的原告会导致Hook无法成功,所以生成一个即可通用,需要注意架构)。
然后我还编写了一个Java类如下:
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
package com.yymjr.nativehook;
 
public final class nativeMethod {
    static {
        System.loadLibrary("inlineHook");
    }
 
    public static native void inlineHook();
    public static native void printfData();
}

最后我改写hooktest的代码为下:
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#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] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
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] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
9
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] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
 楼主| 一夜梦惊人 发表于 2019-5-1 20:16
4everlove 发表于 2019-5-1 20:09
看不懂看不懂尴尬

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

本版积分规则

返回列表

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

GMT+8, 2025-7-26 17:03

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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