好友
阅读权限 20
听众
最后登录 1970-1-1
本帖最后由 一夜梦惊人 于 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,但是却有着其特殊之处,在我们逆向之路上是不可缺少的一部分。祝大家五一快乐,事业高升!
看到这里还不给个评分??
免费评分
查看全部评分