前言
如今的加固方案基本都有類方法抽取了,但是應該是由于公司的商業(yè)性質(zhì)問題表伦,網(wǎng)上的類方法抽取資料還是比較少的迄沫。于是我就想實現(xiàn)一下類方法抽取,實現(xiàn)之前我們需要知道如何修改程序運行時內(nèi)存指令邏輯這是類方法抽取的核心攒巍。所以本文對此進行討論嗽仪。
閱讀此文你需要掌握(我之前的文章都有提到)
- Android inline Hook
- JNI開發(fā) 之 Eclipse中靜態(tài)注冊
- ELF探究 之 ELF文件介紹(一)
- Android的加殼與脫殼 之 Android類加載器(一)(這里主要關(guān)注如何動態(tài)加載DEX)
HOOK框架我們依舊使用上一章的https://github.com/ele7enxxh/Android-Inline-Hook
開發(fā)環(huán)境
Android4.4.4
Nexus5手機(ARM)
Android Studio3.5.1
思路
整體思路就是Hook dexFindClass函數(shù)(一個方法執(zhí)行之前肯定需要解析類信息加載到內(nèi)存,而這個函數(shù)就是加載類必定運行的函數(shù))柒莉,在hook邏輯中拿到DexCode方法數(shù)據(jù)結(jié)構(gòu)體信息钦幔,最后查閱虛擬機指令集,找到加法指令碼替換原來的乘法指令碼常柄,然后覆蓋內(nèi)存中的原始指令鲤氢。
這里我們使用DexClassLoader去加載DEX文件,然后調(diào)用指定方法西潘,在項目初始化時就完成hook卷玉。所以我們需要開發(fā)一個動態(tài)加載的DEX項目
動態(tài)加載的DEX項目
這里只簡單開發(fā)了一個工具類,就是一個乘法運算喷市,到時候Hook改成加法就ok了相种。
將其編譯好后解壓出其中的dex文件,改名為CoreDex.dex并拷貝到手機的/sdcard/CoreDex.dex
項目java層代碼
創(chuàng)建一個DexUtils工具類用來動態(tài)加載調(diào)用dex里面的方法
DexUtils.java
package com.shark.androidinlinehook;
import android.content.Context;
import android.util.Log;
import java.io.File;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
public class DexUtils {
public static final String SHARK = "shark";
public static void exeCoreMethod(Context context) {
try {
//創(chuàng)建文件夾
File optfile = context.getDir("opt_dex", 0);
File libfile = context.getDir("lib_path", 0);
//得到當前Activity 的ClassLoader 以下的方法得到的都是同一個ClassLoader
ClassLoader parentClassloader = MainActivity.class.getClassLoader();
ClassLoader tmpClassLoader = context.getClassLoader();
//創(chuàng)建我們自己的DexClassLoader 指定其父節(jié)點為當前Activity 的ClassLoader
/*dexPath:目標所在的apk或者jar文件的路徑品姓,裝載器將從路徑中尋找指定的目標類寝并。
dexOutputDir:由于dex 文件在APK或者 jar文件中,所以在裝載前面前先要從里面解壓出dex文件腹备,這個路徑就是dex文件存放的路徑衬潦,
在 android系統(tǒng)中,一個應用程序?qū)粋€linux用戶id ,應用程序只對自己的數(shù)據(jù)目錄有寫的權(quán)限植酥,所以我們存放在這個路徑中镀岛。
libPath :目標類中使用的C/C++庫。
最后一個參數(shù)是該裝載器的父裝載器友驮,一般為當前執(zhí)行類的裝載器漂羊。*/
DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/CoreDex.dex",
optfile.getAbsolutePath(), libfile.getAbsolutePath(), MainActivity.class.getClassLoader());
Class<?> clazz=dexClassLoader.loadClass("com.shark.calculate.CoreUtils");
Method calculateMoney=clazz.getDeclaredMethod("calculateMoney",int.class,int.class);
Object obj=clazz.newInstance();
int result = (int)calculateMoney.invoke(obj,2,3);
Log.i(SHARK, "calculateMoney result:" + result);
} catch (Exception e) {
Log.i(SHARK, "exec exeCoreMethod err:" + Log.getStackTraceString(e));
}
}
}
上面就是簡單的加載反射調(diào)用,其中傳參為2和3卸留,其結(jié)果應該是6走越。但是我們hook后就應該是5
在MainActivity中調(diào)用
MainActivity.java
package com.shark.androidinlinehook;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("inlineHook");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//設置點擊事件 加載調(diào)用Dex中的計算方法
findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DexUtils.exeCoreMethod(getApplicationContext());
}
});
startInlineHook();
}
//開啟hook方法
public static native void startInlineHook();
}
上面代碼就是加了個點擊事件來觸發(fā)
現(xiàn)在主要來關(guān)注startInlineHook這個native方法
項目so層代碼
先將開源Hook項目拷貝到cpp中(不清楚的可以看上一章)
使用javah得到MainActivity的頭文件com_shark_androidinlinehook_MainActivity.h,拷貝到cpp中
修改CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
add_library(inlineHook SHARED
hooktest.cpp
inlineHook.c
relocate.c)
target_link_libraries(
inlineHook
android
log)
find_library(
log-lib
log )
因為我們要使用到源碼中的一些結(jié)構(gòu)和函數(shù)耻瑟,所以我們要將其中的一些文件拷貝過來
DexClass.h: /dalvik/libdex/DexClass.h
DexFile.h:/dalvik/libdex/DexFile.h
Leb128.h:/dalvik/libdex/Leb128.h
Common.h:/dalvik/vm/Common.h
去除一些不用的頭文件
Common.h
//去除
#include "cutils/log.h"
DexFile.h
//去除
#include "libdex/SysUtil.h"
準備工作都做完了現(xiàn)在就來看看hooktest.cpp中的代碼吧
先介紹一下需要用到的兩個函數(shù)
//打印指令函數(shù)
void printMethodInsns(const DexFile *pFile, DexMethod *pDexMethod) {
const DexCode *dexCode = dexGetCode(pFile, pDexMethod);
LOGI("method insns size:%d", dexCode->insnsSize);
const u2 *insns = dexCode->insns;
for (int i = 0; i < dexCode->insnsSize; i++) {
LOGI("insns:%d", (*(insns++)));
}
}
//修改內(nèi)存讀寫屬性
int changeMemWrite(int start_add) {
/* 獲取操作系統(tǒng)一個頁的大小, 一般是 4KB == 4096 */
long page_size = sysconf(_SC_PAGESIZE);
//必須是整頁修改旨指,不然無效
start_add = start_add - (start_add % page_size);
LOGI("code add:0x%x, pagesize:%d", start_add, (int)page_size);
int result = mprotect((void *) start_add, page_size, PROT_READ | PROT_WRITE);
return result;
}
printMethodInsns用來打印指令
changeMemWrite函數(shù)是用來修改內(nèi)存讀寫屬性
修改的起始地址一定是系統(tǒng)內(nèi)存頁的整數(shù)倍赏酥,所以需要做一次轉(zhuǎn)化。
現(xiàn)在來看看HOOK的代碼
#include <stdio.h>
#include <jni.h>
#include <string.h>
#include <android/log.h>
#include <sys/mman.h>
#include <bits/sysconf.h>
#include "vm/Common.h"
#include <dlfcn.h>
#include "inlineHook.h"
#include "DexFile.h"
#include "DexClass.h"
#import "com_shark_androidinlinehook_MainActivity.h"
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "SharkChilli", __VA_ARGS__))
//定義DexReadAndVerifyClassData方法類型
typedef DexClassData *(*DexReadAndVerifyClassData)(const u1 **, const u1 *);
//定義DexClassDef方法類型
typedef const DexClassDef *(*OldDexFindClass)(const DexFile *pFile, const char *descriptor);
//定義OldDexFindClass用來保存被hook的方法
OldDexFindClass oldDexFindClass;
//打開的so文件
void *dvmLib;
const DexClassDef *newDexFindClass(const DexFile *pFile, const char *descriptor) {
...
}
void *getDexFindClass(void *funclib) {
//得到方法的內(nèi)存地址
void *func = dlsym(funclib, "_Z12dexFindClassPK7DexFilePKc");
LOGI("func:%p", func);
if (registerInlineHook((uint32_t) func,
(uint32_t) newDexFindClass, (uint32_t **) &oldDexFindClass) !=
ELE7EN_OK) {
LOGI("registerInlineHook ERROR");
return NULL;
}
return func;
}
void hookDvm() {
//打開so文件
dvmLib = dlopen("/system/lib/libdvm.so", RTLD_LAZY);
LOGI("dvmLib:%p", dvmLib);
void *func = getDexFindClass(dvmLib);
if (inlineHook((uint32_t) func) != ELE7EN_OK) {
LOGI("inlineHook ERROR");
return;
}
}
/*
* Class: com_shark_androidinlinehook_MainActivity
* Method: startInlineHook
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_shark_androidinlinehook_MainActivity_startInlineHook
(JNIEnv *, jclass) {
LOGI("hook start");
hookDvm();
LOGI("hook end");
}
如何Hook在上一章已經(jīng)說明淤毛。這里的問題是hook哪個函數(shù)呢?
dexFindClass函數(shù)(一個方法執(zhí)行之前肯定需要解析類信息加載到內(nèi)存今缚,而這個函數(shù)就是加載類必定運行的函數(shù))
- 第一個參數(shù):DexFile結(jié)構(gòu)體指針
- 第二個參數(shù):是加載類的名稱
我們需要利用系統(tǒng)函數(shù)dlopen和dlsym來獲取指定函數(shù)的地址
要得到dexFindClass函數(shù)的地址,我們需要知道這個函數(shù)的導出名稱低淡。這個函數(shù)在設備的/system/lib/libdvm.so下
為了獲取正確的函數(shù)名稱姓言,我們需要導出設備中的libdvm.so文件,拷貝出來蔗蹋,然后用IDA打開libdvm.so文件:
查詢DexFindClass,找到其導出名稱
所以
void *func = dlsym(funclib, "_Z12dexFindClassPK7DexFilePKc");
這樣就得到了DexFindClass在內(nèi)存中的地址了
這里我省略了newDexFindClass的代碼何荚,這里就是我們hook成功后,怎么去修改內(nèi)存中的指令邏輯了猪杭。
const DexClassDef *newDexFindClass(const DexFile *pFile, const char *descriptor) {
//只關(guān)注需要修改的類Lcom/shark/calculate/CoreUtils;
int cmp = strcmp("Lcom/shark/calculate/CoreUtils;", descriptor);
if (cmp == 0) {
//執(zhí)行原來的邏輯得到類結(jié)構(gòu)信息
const DexClassDef *pClassDef = oldDexFindClass(pFile, descriptor);
if (pClassDef == NULL) {
return pClassDef;
}
//打印信息
LOGI("class def:%d", (int)pClassDef);
LOGI("class dex find class name:%s", descriptor);
//我們需要調(diào)用DexReadAndVerifyClassData得到DexClassData代碼結(jié)構(gòu)餐塘,所以需要得到其地址
//依然需要用IDA打開libdvm.so文件查看DexReadAndVerifyClassData函數(shù)的導出名稱:
DexReadAndVerifyClassData getClassData = (DexReadAndVerifyClassData) dlsym(
dvmLib, "_Z25dexReadAndVerifyClassDataPPKhS0_");
const u1 *pEncodedData = dexGetClassData(pFile, pClassDef);
DexClassData *pClassData = getClassData(&pEncodedData, NULL);
DexClassDataHeader header = pClassData->header;
//打印對象方法數(shù)量
LOGI("method size:%d", header.virtualMethodsSize);
//得到首個對象方法的指針
DexMethod *pDexVirtualMethod = pClassData->virtualMethods;
u1 *ptr = (u1 *) pDexVirtualMethod;
//循環(huán)遍歷每個方法
for (int i = 0; i < header.virtualMethodsSize; i++) {
//這里每個方法都是相鄰的,每個大小都是DexMethod結(jié)構(gòu)體的大小
pDexVirtualMethod = (DexMethod *) (ptr + sizeof(DexMethod) * i);
//得到方法名稱
const DexMethodId *methodId = dexGetMethodId(pFile, pDexVirtualMethod->methodIdx);
const char *methodName = dexStringById(pFile, methodId->nameIdx);
//如果是calculateMoney方法就進行替換邏輯
if (strcmp("calculateMoney", methodName) == 0) {
LOGI("pDexVirtualMethod methodName:%s", methodName);
//打印指令
printMethodInsns(pFile, pDexVirtualMethod);
//修改內(nèi)存頁屬性
int start_add = (int) (pFile->baseAddr + pDexVirtualMethod->codeOff);
int result = changeMemWrite(start_add);
LOGI("mp result:%d", result);
//獲取方法對應DexCode結(jié)構(gòu)
DexCode *dexCode = (DexCode *) dexGetCode(pFile, pDexVirtualMethod);
//下面就是覆蓋指令了
u2 new_ins[3] = {144, 770, 15};
memcpy(dexCode->insns, &new_ins, 3 * sizeof(u2));
printMethodInsns(pFile,pDexVirtualMethod);
}
}
return pClassDef;
} else{
//執(zhí)行原來的邏輯
return oldDexFindClass(pFile,descriptor);
}
}
DexReadAndVerifyClassData函數(shù)依然需要用IDA打開libdvm.so文件查看DexReadAndVerifyClassData函數(shù)的導出名稱:
因為我們知道那個calculateMoney方法是對象方法,所以這里直接獲取對象方法結(jié)構(gòu)體信息,然后依次遍歷獲取每個方法姐呐,通過系統(tǒng)函數(shù)dexGetMethodId獲取DexMethodIds結(jié)構(gòu)體信息,
然后在利用系統(tǒng)函數(shù)dexStringById獲取方法名稱竞阐,
有了方法名就需要進行過濾了齐饮,只處理我們的那個calculateMoney方法,然后在獲取方法對應的數(shù)據(jù)結(jié)構(gòu)信息DexCode了,
然后我們因為需要修改內(nèi)存指令,所以還需要把內(nèi)存修改為可讀屬性
然后利用系統(tǒng)函數(shù)dexGetCode通過DexMethod結(jié)構(gòu)體獲取DexCode結(jié)構(gòu)體信息
接下來就可以構(gòu)造指令不翩,然后替換內(nèi)存指令即可。那么如何獲取原始指令麻裳,怎么把乘法改成加法呢口蝠?這里就需要利用010Editor軟件了,直接查看這個方法的指令數(shù)據(jù):
這里看到津坑,這個方法有三條指令妙蔗,但是一條指令是兩個字節(jié),所以一共是6個字節(jié)国瓮,這里看到的是十進制的數(shù)據(jù)了灭必,我們可以把這三個十進制數(shù)據(jù)轉(zhuǎn)化成6個十六進制數(shù)據(jù):
然后我們現(xiàn)在只需要把乘法指令碼改成加法指令碼即可,這個需要參考Bytecode of Dalvik了:
所以覆蓋指令代碼如下:
運行
點擊Test按鈕后
引用
姜維大佬寫的真的牛啊(可惜沒有源碼)
Android免Root權(quán)限通過Hook系統(tǒng)函數(shù)修改程序運行時內(nèi)存指令邏輯