Android免Root 修改程序運行時內(nèi)存指令邏輯(Hook系統(tǒng)函數(shù))

前言

如今的加固方案基本都有類方法抽取了,但是應該是由于公司的商業(yè)性質(zhì)問題表伦,網(wǎng)上的類方法抽取資料還是比較少的迄沫。于是我就想實現(xiàn)一下類方法抽取,實現(xiàn)之前我們需要知道如何修改程序運行時內(nèi)存指令邏輯這是類方法抽取的核心攒巍。所以本文對此進行討論嗽仪。

閱讀此文你需要掌握(我之前的文章都有提到)

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項目

image.png

這里只簡單開發(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中(不清楚的可以看上一章)


image.png

使用javah得到MainActivity的頭文件com_shark_androidinlinehook_MainActivity.h,拷貝到cpp中


image.png

修改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ù)耻瑟,所以我們要將其中的一些文件拷貝過來


image.png

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文件:


image.png

查詢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ù)的導出名稱:


image.png

因為我們知道那個calculateMoney方法是對象方法,所以這里直接獲取對象方法結(jié)構(gòu)體信息,然后依次遍歷獲取每個方法姐呐,通過系統(tǒng)函數(shù)dexGetMethodId獲取DexMethodIds結(jié)構(gòu)體信息,


image.png

然后在利用系統(tǒng)函數(shù)dexStringById獲取方法名稱竞阐,


image.png

有了方法名就需要進行過濾了齐饮,只處理我們的那個calculateMoney方法,然后在獲取方法對應的數(shù)據(jù)結(jié)構(gòu)信息DexCode了,


image.png

然后我們因為需要修改內(nèi)存指令,所以還需要把內(nèi)存修改為可讀屬性


image.png

然后利用系統(tǒng)函數(shù)dexGetCode通過DexMethod結(jié)構(gòu)體獲取DexCode結(jié)構(gòu)體信息


image.png

接下來就可以構(gòu)造指令不翩,然后替換內(nèi)存指令即可。那么如何獲取原始指令麻裳,怎么把乘法改成加法呢口蝠?這里就需要利用010Editor軟件了,直接查看這個方法的指令數(shù)據(jù):


image.png

這里看到津坑,這個方法有三條指令妙蔗,但是一條指令是兩個字節(jié),所以一共是6個字節(jié)国瓮,這里看到的是十進制的數(shù)據(jù)了灭必,我們可以把這三個十進制數(shù)據(jù)轉(zhuǎn)化成6個十六進制數(shù)據(jù):

image.png

然后我們現(xiàn)在只需要把乘法指令碼改成加法指令碼即可,這個需要參考Bytecode of Dalvik了:
image.png

所以覆蓋指令代碼如下:
image.png

運行

點擊Test按鈕后


image.png

引用

姜維大佬寫的真的牛啊(可惜沒有源碼)
Android免Root權(quán)限通過Hook系統(tǒng)函數(shù)修改程序運行時內(nèi)存指令邏輯

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乃摹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子跟衅,更是在濱河造成了極大的恐慌孵睬,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伶跷,死亡現(xiàn)場離奇詭異掰读,居然都是意外死亡秘狞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門蹈集,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烁试,“玉大人,你說我怎么就攤上這事拢肆〖跸欤” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵郭怪,是天一觀的道長支示。 經(jīng)常有香客問我,道長鄙才,這世上最難降的妖魔是什么颂鸿? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮攒庵,結(jié)果婚禮上嘴纺,老公的妹妹穿的比我還像新娘。我一直安慰自己浓冒,他們只是感情好栽渴,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著裆蒸,像睡著了一般熔萧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上僚祷,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天佛致,我揣著相機與錄音,去河邊找鬼辙谜。 笑死俺榆,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的装哆。 我是一名探鬼主播罐脊,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蜕琴!你這毒婦竟也來了萍桌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤凌简,失蹤者是張志新(化名)和其女友劉穎上炎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雏搂,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡藕施,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年寇损,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裳食。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡矛市,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诲祸,到底是詐尸還是另有隱情浊吏,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布烦绳,位于F島的核電站卿捎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏径密。R本人自食惡果不足惜午阵,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望享扔。 院中可真熱鬧底桂,春花似錦、人聲如沸惧眠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽氛魁。三九已至暮顺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秀存,已是汗流浹背捶码。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留或链,地道東北人惫恼。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像澳盐,于是被迫代替她去往敵國和親祈纯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354