一个人的闲言碎语

dr0v

blog.drov.com.cn
一个人碎碎念。
About Me
A lazy security employee.

2021年4月16日星期五

# hook原理
>在面微信的时候被开发的面试官问到了hook原理。我简单介绍了一般是替换函数地址的形式,保障先调用替换函数,再调用原始函数。
>
>面试官的理解是有两种hook方式:一种是地址替换型,一种是代码插入型。
>
>由此可知我对hook的不甚了解,所以专门找了相关资料进行学习。如文为学习总结。

## hook方式

Hook技术无论对安全软件还是恶意软件都是十分关键的一项技术,其本质就是劫持函数调用。但是由于处于Linux 用户态,每个进程都有自己独立的进程空间,所以必须先注入到所要Hook 的进程空间,修改其内存中的进程代码,替换其过程表的符号地址。

APP 劫持三步走:

1. 注入进程
	- ptrace
	- dlopen    
2. hook 目标函数
	- Java Hook
		- Static Field Hook:静态成员hook
		- Method Hook:函数hook
	- Native So Hook
		- GOT Hook:全局偏移表hook
		- SYM Hook:符号表hook
		- Inline Hook:函数内联hook
3. 执行自身代码
	- 获取敏感信息
	- 修改返回值
	- etc.

![基于 ptrace的 hook 工作流程](https://box.kancloud.cn/4a4a6f8a7696648d181d7743f8e9a5f2_646x432.png)

## xposed hook 原理分析

xposed 虽然目前已经不更新了,不过依然是 Android 平台最著名、最广泛使用的 hook 框架。

xposed hook 工作原理:

1. 获取 root 权限
2. 替换/system/bin/app_process
3. app_process在启动过程中会加载XposedBridge.jar,完成对Zygote进程及其创建的Dalvik虚拟机的劫持
4. XposedBridge.jar中会根据用户所编写的 xposed 模块,对对应 classloader 中的 method 进行替换

```
/**
  *
  * 将输入的Class中的Method方法的nativeFunc替换为xposedCallHandler 
  * 
  * @param env JniEnv
  * @param reflectedMethodIndirect 待反射的函数
  * @param declaredClassIndirect 定义的class
  * @param slot 函数偏移量
  * @param additionalInfoIndirect 添加的函数
  * 
  */
void XposedBridge_hookMethodNative(JNIEnv* env, jclass clazz, jobject reflectedMethodIndirect,
            jobject declaredClassIndirect, jint slot, jobject additionalInfoIndirect) {
    // 容错
    if (declaredClassIndirect == NULL || reflectedMethodIndirect == NULL) {
        dvmThrowIllegalArgumentException("method and declaredClass must not be null");
        return;
    }

    // 根据函数的偏移量,从classloader中找到准备替换的函数。
    ClassObject* declaredClass = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), declaredClassIndirect);
    Method* method = dvmSlotToMethod(declaredClass, slot);
    if (method == NULL) {
        dvmThrowNoSuchMethodError("Could not get internal representation for method");
        return;
    }

    if (isMethodHooked(method)) {
        // already hooked
        return;
    }

    // 保存替换前的数据信息
    XposedHookInfo* hookInfo = (XposedHookInfo*) calloc(1, sizeof(XposedHookInfo));
    memcpy(hookInfo, method, sizeof(hookInfo->originalMethodStruct));
    hookInfo->reflectedMethod = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(reflectedMethodIndirect));
    hookInfo->additionalInfo = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(additionalInfoIndirect));

    // 替换函数方法 , 让nativeFunction指向本地的hookedMethodCallback
    SET_METHOD_FLAG(method, ACC_NATIVE);
    method->nativeFunc = &hookedMethodCallback;
    method->insns = (const u2*) hookInfo;
    method->registersSize = method->insSize;
    method->outsSize = 0;

    if (PTR_gDvmJit != NULL) {
        // reset JIT cache
        char currentValue = *((char*)PTR_gDvmJit + MEMBER_OFFSET_VAR(DvmJitGlobals,codeCacheFull));
        if (currentValue == 0 || currentValue == 1) {
            MEMBER_VAL(PTR_gDvmJit, DvmJitGlobals, codeCacheFull) = true;
        } else {
            ALOGE("Unexpected current value for codeCacheFull: %d", currentValue);
        }
    }
}

```


![app_process](https://img-blog.csdn.net/20150821093617938)

## frida hook 原理
frida代码结构:
```
frida-core: Frida core library intended for static linking into bindings
frida-gum: Low-level code instrumentation library used by frida-core bindings
frida-python: Frida Python bindings
frida-node: Frida Node.js bindings
frida-qml: Frida Qml plugin
frida-swift: Frida Swift bindings
frida-tools: Frida CLI tools
capstone: instruction disammbler
```

frida的工作模式有两种:
- attach模式

    attach到已经存在的进程,核心原理是ptrace修改进程内存,如果进程处于调试状态(traceid不等于0),则attach失败
- spawn模式

    启动一个新的进程并挂起,在启动的同时注入frida代码,适用于在进程启动前的一些hook,如hook RegisterNative等,注入完成后调用resume恢复进程。

frida 的 hook 区分了 art 模式和 dalvik 模式。

### Dalvik hook 实现

frida兼容了低版本的Android, 低于Android 5.0时,采用Dalvik虚拟机,其核心实现在replaceDalvikImplementation函数中。

frida-dalvik-hook 的原理和 xposed 的 hook 原理是一样的,把 java 函数变成 native 函数,然后修改入口信息为自定义函数信息。

![dalvik 虚拟机执行 java 函数过程](https://mabin004.github.io/images/pasted-83.png)

```
//https://android.googlesource.com/platform/dalvik/+/6d874d2bda563ada1034d2b3219b35d800fc6860/vm/oo/Object.h#418
struct Method {   
    ClassObject*    clazz;   /* method所属的类 public、native等*/
    u4              accessFlags; /* 访问标记 */
    u2             methodIndex; //method索引
    //三个size为边界值,对于native函数,这3个size均等于参数列表的size
    u2              registersSize;  /* ins + locals */
    u2              outsSize;
    u2              insSize;
    const char*     name;//函数名称
    /*
     * Method prototype descriptor string (return and argument types)
     */
    DexProto        prototype;
    /* short-form method descriptor string */
    const char*     shorty;
    /*
     * The remaining items are not used for abstract or native methods.
     * (JNI is currently hijacking "insns" as a function pointer, set
     * after the first call.  For internal-native this stays null.)
     */
    /* the actual code */
    const u2*       insns;          /* instructions, in memory-mapped .dex */
    /* cached JNI argument and return-type hints */
    int             jniArgInfo;
    /*
     * Native method ptr; could be actual function or a JNI bridge.  We
     * don't currently discriminate between DalvikBridgeFunc and
     * DalvikNativeFunc; the former takes an argument superset (i.e. two
     * extra args) which will be ignored.  If necessary we can use
     * insns==NULL to detect JNI bridge vs. internal native.
     */
    DalvikBridgeFunc nativeFunc;
    /*
     * Register map data, if available.  This will point into the DEX file
     * if the data was computed during pre-verification, or into the
     * linear alloc area if not.
     */
    const RegisterMap* registerMap;

};

…
…
…

function replaceDalvikImplementation (fn) {
  if (fn === null && dalvikOriginalMethod === null) {
    return;
  }
//备份原来的method,
  if (dalvikOriginalMethod === null) {
    dalvikOriginalMethod = Memory.dup(methodId, DVM_METHOD_SIZE);
    dalvikTargetMethodId = Memory.dup(methodId, DVM_METHOD_SIZE);
  }

  if (fn !== null) {
   //自定的代码
    implementation = implement(f, fn);

    let argsSize = argTypes.reduce((acc, t) => (acc + t.size), 0);
    if (type === INSTANCE_METHOD) {
      argsSize++;
    }
    // 把method变成native函数
    /*
     * make method native (with kAccNative)
     * insSize and registersSize are set to arguments size
     */
    const accessFlags = (Memory.readU32(methodId.add(DVM_METHOD_OFFSET_ACCESS_FLAGS)) | kAccNative) >>> 0;
    const registersSize = argsSize;
    const outsSize = 0;
    const insSize = argsSize;

    Memory.writeU32(methodId.add(DVM_METHOD_OFFSET_ACCESS_FLAGS), accessFlags);
    Memory.writeU16(methodId.add(DVM_METHOD_OFFSET_REGISTERS_SIZE), registersSize);
    Memory.writeU16(methodId.add(DVM_METHOD_OFFSET_OUTS_SIZE), outsSize);
    Memory.writeU16(methodId.add(DVM_METHOD_OFFSET_INS_SIZE), insSize);
    Memory.writeU32(methodId.add(DVM_METHOD_OFFSET_JNI_ARG_INFO), computeDalvikJniArgInfo(methodId));
    //调用dvmUseJNIBridge为这个Method设置一个Bridge,本质上是修改结构体中的nativeFunc为自定义的implementation函数
    api.dvmUseJNIBridge(methodId, implementation);

    patchedMethods.add(f);
  } else {
    patchedMethods.delete(f);

    Memory.copy(methodId, dalvikOriginalMethod, DVM_METHOD_SIZE);
    implementation = null;
  }
}

```

### ART hook实现
frida的ART hook实现也是把java method转为native method, 但ART的运行机制不同于Dalvik, 其实现也较为复杂。

ART虚拟机执行 Java 方法主要有两种模式:
- quick code 模式:执行 arm 汇编指令
- Interpreter 模式:由解释器解释执行 Dalvik 字节码

所以 frida 要将 java method 转为 native method,需要将ARTMethod 结构进行如下修改:
```
patchMethod(methodId, {
  //jnicode入口entry_point_from_jni_改为自定义的代码
  'jniCode': implementation,
  //修改为access_flags_为native
  'accessFlags': (Memory.readU32(methodId.add(artMethodOffset.accessFlags)) | kAccNative | kAccFastNative) >>> 0,
  //art_quick_generic_jni_trampoline函数的地址
  'quickCode': api.artQuickGenericJniTrampoline,
  //artInterpreterToCompiledCodeBridge函数地址
  'interpreterCode': api.artInterpreterToCompiledCodeBridge
});
```


参考链接

1. [动态注入技术(hook技术)](https://www.kancloud.cn/alex_wsc/android/504478)
2. [Xposed源码剖析——概述](https://blog.csdn.net/yzzst/article/details/47659987)
3. [Xposed源码剖析——app_process作用详解](https://blog.csdn.net/yzzst/article/details/47829657)
4. [Xposed源码剖析——Xposed初始化](https://blog.csdn.net/yzzst/article/details/47834077)
5. [Xposed源码剖析——hook具体实现](https://blog.csdn.net/yzzst/article/details/47913867)
6. [Frida源码分析](https://mabin004.github.io/2018/07/31/Mac%E4%B8%8A%E7%BC%96%E8%AF%91Frida/)
Categories: , ,

0 评论:

发表评论