Android.Hook框架Cydia篇

Cydia Substrate是一個代碼修改平臺.它能夠修改任何主進程的代碼,不論是用Java仍是C/C++(native代碼)編寫的.而Xposed只支持HOOK app_process中的java函數,所以Cydia Substrate是一款強大而實用的HOOK工具.php

官網地址:http://www.cydiasubstrate.com/html

官方教程:http://www.cydiasubstrate.com/id/38be592b-bda7-4dd2-b049-cec44ef7a73bjava

SDK下載地址:http://asdk.cydiasubstrate.com/zips/cydia_substrate-r2.zipandroid

0x00Hook Java 層

以前講解過 xposed 的用法爲啥還要整這個了,下面簡單對比兩款框架.想了解以前 xposed 篇的能夠看這裏:http://drops.wooyun.org/tips/7488git

劣勢:github

  • 沒啥錯誤提醒,排錯比較麻煩.shell

  • 須要對 NDK 開發有必定了解,相對 xposed 模塊的開發學習成本高一些.小程序

  • 由於不開源網上(github)上能夠參考的模塊代碼不多.api

優點:app

  • 能夠對 native 函數進行 hook .

  • 與 xposed hook 原理不同,由於不是開源具體原理我也不清楚. 結果就是一些Anti hook 可能對 xposed 有效而對 Cydia 無效.

使用方法

1.安裝框架app:http://www.cydiasubstrate.com/download/com.saurik.substrate.apk

2.建立一個空的Android工程.因爲建立的工程將以插件的形式被加載,因此不須要activity.將SDK中的substrate-api.jar複製到project/libs文件夾中.

3.配置Manifest文件

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <meta-data android:name="com.saurik.substrate.main"
            android:value=".Main"/>
    </application>
    <uses-permission android:name="cydia.permission.SUBSTRATE"/>
</manifest>

4.建立一個類,類名爲Main.類中包含一個static方法initialize,當插件被加載的時候,該方法中的代碼就會運行,完成一些必要的初始化工做.

import com.saurik.substrate.MS;
public class Main {
    static void initialize() { 
        // ... code to run when extension is loaded
    }
}

5.hook imei example

import com.saurik.substrate.MS;
public class Main {
    static void initialize() {
        MS.hookClassLoad("android.telephony.TelephonyManager",
                new MS.ClassLoadHook() {
                    @SuppressWarnings("unchecked")
                    public void classLoaded(Class<?> arg0) {
                        Method hookimei;
                        try {
                            hookimei = arg0.getMethod("getDeviceId", null);
                        } catch (NoSuchMethodException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                            hookimei = null;
                        }
                        if (hookimei != null) {
                            final MS.MethodPointer old1 = new MS.MethodPointer();
                            MS.hookMethod(arg0, hookimei, new MS.MethodHook() {
                                @Override
                                public Object invoked(Object arg0,
                                        Object... arg1) throws Throwable {
                                    // TODO Auto-generated method stub
                                    System.out.println("hook imei----------->");
                                    String imei = (String) old1.invoke(arg0,
                                            arg1);
                                    System.out.println("imei-------->" + imei);
                                    imei = "999996015409998";
                                    return imei;
                                }
                            }, old1);
                        }
                    }
                });
    }
}

6.在 cydia app 界面中點擊 Link Substrate Files 以後重啓手機

7.使用getimei的小程序驗證imei是否被改變

public class MainActivity extends ActionBarActivity {
    private static final String tag = "MainActivity";
    TextView mText ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mText = (TextView) findViewById(R.id.text);
        TelephonyManager mtelehonyMgr = (TelephonyManager) getSystemService(this.TELEPHONY_SERVICE);
        Build bd = new Build(); 
        String imei = mtelehonyMgr.getDeviceId(); 
        String imsi = mtelehonyMgr.getSubscriberId();
        //getSimSerialNumber()   獲取 SIM 序列號  getLine1Number 獲取手機號
        String androidId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 
        String id = UUID.randomUUID().toString();
        String model = bd.MODEL;
        StringBuilder sb = new StringBuilder();
        sb.append("imei = "+ imei);
        sb.append("\nimsi = " + imsi);
        sb.append("\nandroid_id = " + androidId);
        sb.append("\nuuid = " + id);
        sb.append("\nmodel = " + model);
        if(imei!=null)
            mText.setText(sb.toString());
        else
            mText.setText("fail");
    }

8.關鍵api介紹

MS.hookClassLoad:該方法實如今指定的類被加載的時候發出通知(改變其實現方式?).由於一個類能夠在任什麼時候候被加載,因此Substrate提供了一個方法用來檢測用戶感興趣的類什麼時候被加載.

這個api須要實現一個簡單的接口MS.ClassLoadHook,該接口只有一個方法classLoaded,當類被加載的時候該方法會被執行.加載的類以參數形式傳入此方法.

void hookClassLoad(String name, MS.ClassLoadHook hook);


參數          描述

name 包名+類名,使用java的.符號(被hook的完整類名)

hook MS.ClassLoadHook的一個實例,當這個類被加載的時候,它的classLoaded方法會被執行.

MS.hookClassLoad("java.net.HttpURLConnection",
    new MS.ClassLoadHook() {
        public void classLoaded(Class<?> _class) {
            /* do something with _class argument */
        }
    }
);

MS.hookMethod:該API容許開發者提供一個回調函數替換原來的方法,這個回調函數是一個實現了MS.MethodHook接口的對象,是一個典型的匿名內部類.它包含一個invoked函數.

void hookMethod(Class _class, Member member, MS.MethodHook hook, MS.MethodPointer old);

參數 描述

_class 加載的目標類,爲classLoaded傳下來的類參數

member 經過反射獲得的須要hook的方法(或構造函數). 注意:不能HOOK字段 (在編譯的時候會進行檢測).

hook MS.MethodHook的一個實例,其包含的invoked方法會被調用,用以代替member中的代碼

0x01Hook Native 層

這塊的功能 xposed 就不能實現啦.

整個流程大體以下:

  • 建立工程,添加 NDK 支持

  • 將 cydia 的庫和頭文件加入工程

  • 修改 AndroidManifest配置文件

  • 修改Android.md

  • 開發模塊

    • MSGetImageByName or dlopen

    • MSFindSymbol or dlsym or nlist 指定方法,獲得開始地址

    • MSHookFunction 替換函數

    • 指定要hook 的 lib 庫

    • 保留原來的地址

    • 替換的函數

    • Substrate entry point

使用方法

**第零步:添加 ndk 支持,將 cydia 的庫和頭文件加入工程

有關 ndk 開發的基礎能夠參考此文: NDK入門篇

注意要是 xxx.cy.cpp,不要忘記.cy

其實應該是動態連接庫名稱中的 cy 必須有,全部在 Android.md 中module 處的 .cy 必須帶上咯

LOCAL_MODULE    := DumpDex2.cy

第一步:修改配置文件

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:installLocation="internalOnly"
>
    <application android:hasCode="false">
    </application>

    <uses-permission android:name="cydia.permission.SUBSTRATE"/>
</manifest>

設置 android:hasCode 屬性 false,設置android:installLocation屬性internalOnly"

第二步:指定要 hook 的 lib 庫

#include <substrate.h>

MSConfig(MSFilterExecutable, "/system/bin/app_process")  //MSConfig(MSFilterLibrary, "liblog.so")

// this is a macro that uses __attribute__((__constructor__))
MSInitialize {
    // ... code to run when extension is loaded
}

設置要 hook 的可執行文件或者動態庫

第三步: 等待 class

static void OnResources(JNIEnv *jni, jclass resources, void *data) {
    // ... code to modify the class when loaded
}

MSInitialize {
    MSJavaHookClassLoad(NULL, "android/content/res/Resources", &OnResources);
}

第四步:修改實現

static jint (*_Resources$getColor)(JNIEnv *jni, jobject _this, ...);

static jint $Resources$getColor(JNIEnv *jni, jobject _this, jint rid) {
    jint color = _Resources$getColor(jni, _this, rid);
    return color & ~0x0000ff00 | 0x00ff0000;
}

static void OnResources(JNIEnv *jni, jclass resources, void *data) {
    jmethodID method = jni->GetMethodID(resources, "getColor", "(I)I");
    if (method != NULL)
        MSJavaHookMethod(jni, resources, method,
            &$Resources$getColor, &_Resources$getColor);
}

下面是步驟是在官網教程基礎上對小白同窗的一些補充吧.

» file libprocess.so                                                                  
libprocess.so: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), not stripped

第五步

複製libsubstrate-dvm.so(注意 arm 和 x86平臺的選擇)和substrate.h到 jni 目錄下.建立SuperMathHook.cy.cpp文件

第六步

配置Android.mk文件

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE:= substrate-dvm
LOCAL_SRC_FILES := libsubstrate-dvm.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE    := SuperMathHook.cy
LOCAL_SRC_FILES := SuperMathHook.cy.cpp
LOCAL_LDLIBS := -llog
LOCAL_LDLIBS += -L$(LOCAL_PATH) -lsubstrate-dvm //-L指定庫文件的目錄,-l指定庫文件名,-I指定頭文件的目錄.
include $(BUILD_SHARED_LIBRARY)

加入 c 的 lib

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE:= substrate-dvm
LOCAL_SRC_FILES := libsubstrate-dvm.so
include $(PREBUILT_SHARED_LIBRARY)


include $(CLEAR_VARS)
LOCAL_MODULE:= substrate
LOCAL_SRC_FILES := libsubstrate.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE    := CydiaN.cy
LOCAL_SRC_FILES := CydiaN.cy.cpp
LOCAL_LDLIBS := -llog
LOCAL_LDLIBS += -L$(LOCAL_PATH) -lsubstrate-dvm -lsubstrate

include $(BUILD_SHARED_LIBRARY)

strings 查看下里面的函數.

/data/data/com.jerome.jni/lib # strings libprocess.so                                                               <
/system/bin/linker
__cxa_finalize
__cxa_atexit
Jstring2CStr
malloc
memcpy
__aeabi_unwind_cpp_pr0
Java_com_jerome_jni_JNIProcess_getInfoMD5
....

脫殼機模塊發開

網上流傳的 IDA dump 脫殼流程大體以下:

  • 對/system/lib/libdvm.so 方法JNI_OnLoad/dvmLoadNativeCode/dvmDexFileOpenPartial下斷點分析

  • IDA 附加 app (IDA6.5以及以後版本)

  • Ctrl+s 查看基地址+偏移

  • IDA 分析尋找 dump 點

  • F8/F9執行到dex徹底被解密到內存中時候進行 dump

如今目標就是經過 Cydia 的模塊來自動化完成這個功能.這裏咱選擇對dvmDexFileOpenPartial函數進行 hook.至於爲何要選擇這裏了?這就須要分析下 android dex優化過程

Android會對每個安裝的應用的dex文件進行優化,生成一個odex文件.相比於dex文件,odex文件多了一個optheader,依賴庫信息(dex文件所須要的本地函數庫)和輔助信息(類索引信息等).

dex的優化過程是一個獨立的功能模塊來實現的,位於http://androidxref.com/4.4.3_r1.1/xref/dalvik/dexopt/OptMain.cpp#57 其中extractAndProcessZip()函數完成優化操做.

http://androidxref.com/4.1.1/xref/dalvik/dexopt/OptMain.cpp

OptMain中的main函數就是加載dex的最原始入口

int main(int argc, char* const argv[])
{
    set_process_name("dexopt");
 
    setvbuf(stdout, NULL, _IONBF, 0);
 
    if (argc > 1) {
        if (strcmp(argv[1], "--zip") == 0)
            return fromZip(argc, argv);
        else if (strcmp(argv[1], "--dex") == 0)
            return fromDex(argc, argv);
        else if (strcmp(argv[1], "--preopt") == 0)
            return preopt(argc, argv);
    }
    ...
    return 1;
}

能夠看到,這裏會分別對3中類型的文件作不一樣處理,咱們關心的是dex文件,因此接下來看看fromDex函數:

static int fromDex(int argc, char* const argv[])
{
...
if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode, flags) != 0) {
    ALOGE("VM init failed");
    goto bail;
}
 
vmStarted = true;
 
/* do the optimization */
if (!dvmContinueOptimization(fd, offset, length, debugFileName,
        modWhen, crc, (flags & DEXOPT_IS_BOOTSTRAP) != 0))
{
    ALOGE("Optimization failed");
    goto bail;
}
...
}

這個函數先初始化了一個虛擬機,而後調用dvmContinueOptimization函數/dalvik/vm/analysis/DexPrepare.cpp,進入這個函數:

bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,
    const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
{
    ...
    /*
         * Rewrite the file.  Byte reordering, structure realigning,
         * class verification, and bytecode optimization are all performed
         * here.
         *
         * In theory the file could change size and bits could shift around.
         * In practice this would be annoying to deal with, so the file
         * layout is designed so that it can always be rewritten in place.
         *
         * This creates the class lookup table as part of doing the processing.
         */
        success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,
                    doVerify, doOpt, &pClassLookup, NULL);
 
        if (success) {
            DvmDex* pDvmDex = NULL;
            u1* dexAddr = ((u1*) mapAddr) + dexOffset;
 
            if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) {
                ALOGE("Unable to create DexFile");
                success = false;
            } else {
    ...
}

這個函數中對Dex文件作了一些優化(如字節重排序,結構對齊等),而後從新寫入Dex文件.若是優化成功的話接下來調用dvmDexFileOpenPartial,而這個函數中調用了真正的Dex文件.在具體看看這個函數/dalvik/vm/DvmDex.cpp

/*
 * Create a DexFile structure for a "partial" DEX.  This is one that is in
 * the process of being optimized.  The optimization header isn't finished
 * and we won't have any of the auxillary data tables, so we have to do
 * the initialization slightly differently.
 *
 * Returns nonzero on error.
 */
int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
{
    DvmDex* pDvmDex;
    DexFile* pDexFile;
    int parseFlags = kDexParseDefault;
    int result = -1;
 
    /* -- file is incomplete, new checksum has not yet been calculated
    if (gDvm.verifyDexChecksum)
        parseFlags |= kDexParseVerifyChecksum;
    */
 
    pDexFile = dexFileParse((u1*)addr, len, parseFlags);
    if (pDexFile == NULL) {
        ALOGE("DEX parse failed");
        goto bail;
    }
    pDvmDex = allocateAuxStructures(pDexFile);
    if (pDvmDex == NULL) {
        dexFileFree(pDexFile);
        goto bail;
    }
 
    pDvmDex->isMappedReadOnly = false;
    *ppDvmDex = pDvmDex;
    result = 0;
 
bail:
    return result;
}

這個函數的前兩個參數很是關鍵,第一個參數是dex文件的起始地址,第二個參數是dex文件的長度,有了這兩個參數,就能夠從內存中將這個dex文件dump下來了,這也是在此函數下斷點的緣由.該函數會調用dexFileParse()對dex文件進行解析

因此在dexFileParse函數處來進行 dump 也是可行的.可是由於這個函數的原型是

DexFile* dexFileParse(const u1* data, size_t length, int flags)

其返回值爲一個結構體指針struct DexFile { ... },要 hook 這個函數得把結構體從 android 源碼中扣出來或者直接改鏡像.

找到dvmDexFileOpenPartial函數在 libdvm.so 對應的名稱

» strings libdvm_arm.so|grep dvmDexFileOpenPartial
_Z21dvmDexFileOpenPartialPKviPP6DvmDex
 
» strings libdvm_arm.so|grep dexFileParse
_Z12dexFileParsePKhji

有了上述理論基礎,如今能夠正式開發模塊了.大體流程以下

  • 指定要hook 的 lib 庫

  • Original method template 原函數模板

  • Modified method 替換的函數

  • Substrate entry point

    • MSGetImageByName or dlopen 載入lib獲得 image

    • MSFindSymbol or dlsym or nlist 指定方法,獲得開始地址

    • MSHookFunction 替換函數

完整代碼

#include "substrate.h"
#include <android/log.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
 
#define BUFLEN 1024
#define TAG "DEXDUMP"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
 
//get packagename from pid
int getProcessName(char * buffer){
    char path_t[256]={0};
    pid_t pid=getpid();
    char str[15];
    sprintf(str, "%d", pid);
    memset(path_t, 0 , sizeof(path_t));
    strcat(path_t, "/proc/");
    strcat(path_t, str);
    strcat(path_t, "/cmdline");
    //LOG_ERROR("zhw", "path:%s", path_t);
    int fd_t = open(path_t, O_RDONLY);
    if(fd_t>0){
        int read_count = read(fd_t, buffer, BUFLEN);
 
        if(read_count>0){
              int  processIndex=0;
              for(processIndex=0;processIndex<strlen(buffer);processIndex++){
                  if(buffer[processIndex]==':'){
                      buffer[processIndex]='_';
                  }
 
              }
            return 1;
        }
    }
    return 0;
}
 
//指定要hook 的 lib 庫
MSConfig(MSFilterLibrary,"/system/lib/libdvm.so")
 
//保留原來的地址  DexFile* dexFileParse(const u1* data, size_t length, int flags)
int (* oldDexFileParse)(const void * addr,int len,int flags);
 
//替換的函數
int myDexFileParse(const void * addr,int len,void ** dvmdex)
{
    LOGD("call my dvm dex!!:%d",getpid());
 
    {
        //write to file
        //char buf[200];
        // 導出dex文件
        char dexbuffer[64]={0};
        char dexbufferNamed[128]={0};
        char * bufferProcess=(char*)calloc(256,sizeof(char));
        int  processStatus= getProcessName(bufferProcess);
        sprintf(dexbuffer, "_dump_%d", len);
        strcat(dexbufferNamed,"/sdcard/");
        if (processStatus==1) {
          strcat(dexbufferNamed,bufferProcess);
            strcat(dexbufferNamed,dexbuffer);
 
        }else{
            LOGD("FAULT pid not  found\n");
        }
 
        if(bufferProcess!=NULL)
        {
 
          free(bufferProcess);
        }
 
        strcat(dexbufferNamed,".dex");
 
        //sprintf(buf,"/sdcard/dex.%d",len);
        FILE * f=fopen(dexbufferNamed,"wb");
        if(!f)
        {
            LOGD(dexbuffer + " : error open sdcard file to write");
        }
        else{
            fwrite(addr,1,len,f);
            fclose(f);
        }
 
 
 
    }
    //進行原來的調用,不影響程序運行
    return oldDexFileParse(addr,len,dvmdex);
}
 
//Substrate entry point
MSInitialize
{
    LOGD("Substrate initialized.");
    MSImageRef image;
    //載入lib
    image = MSGetImageByName("/system/lib/libdvm.so");
    if (image != NULL)
    {
 
        void * dexload=MSFindSymbol(image,"_Z21dvmDexFileOpenPartialPKviPP6DvmDex");
        if(dexload==NULL)
        {
            LOGD("error find _Z21dvmDexFileOpenPartialPKviPP6DvmDex ");
 
        }
        else{
            //替換函數
            //3.MSHookFunction
            MSHookFunction(dexload,(void*)&myDexFileParse,(void **)&oldDexFileParse);
        }
    }
    else{
        LOGD("ERROR FIND LIBDVM");
    }
}

效果以下:

shell@hammerhead:/sdcard $ l |grep dex
app_process_classes_3220.dex
com.ali.tg.testapp_classes_606716.dex
com.chaozh.iReaderFree_classes_4673256.dex
com.secken.app_xg_service_v2_classes_6327832.dex

脫殼機模塊改進一

更改 hook 點爲 dexFileParse,上文已經講解了爲啥也能夠選擇這裏.也分析了 dex 優化的過程,這裏在分析下 dex 加載的過程.

DexClassLoader普遍被開發者用於插件的動態加載.而PathClassLoader幾乎沒怎麼見過.

由於PathClassLoader 沒有提供優化 dex 的目錄而是固定將 odex 存放到 /data/dalvik-cache 中 ,故它只能加載已經安裝到 Android 系統中的 apk 文件,也就是 /data/app 目錄下的 apk 文件.

PathClassLoader 和 DexClassLoader 父類爲 BaseDexClassLoader

http://androidxref.com/4.4.2_r1/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
       String libraryPath, ClassLoader parent) {

http://androidxref.com/4.4.2_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

DexPathList(this, dexPath, libraryPath, optimizedDirectory);
private static DexFile loadDexFile(File file, File optimizedDirectory)

http://androidxref.com/4.4.2_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java

static public DexFile loadDex(String sourcePathName, String outputPathName, int flags)

調用 native 函數 native private static int openDexFileNative(String sourceName, String outputName, int flags)

294    private static int openDexFile(String sourceName, String outputName,
295        int flags) throws IOException {
296        return openDexFileNative( new File(sourceName).getCanonicalPath(),
297                                 (outputName == null) ? null : new File(outputName).getCanonicalPath(),
298                                 flags);
299    }

脫殼機模塊改進二

  • 加入encode

  • 優化輸出

  • ...

github 地址以下,裏面已經有一個編譯好可是沒有簽名的 apk 了...

https://github.com/WooyunDota/DumpDex

若是提取的是 encode 版的,須要 decode 一下:

base64 -D -i com.ali.tg.testapp_606716.dex.encode.dex -o my.dex

0x03參考


http://www.cnblogs.com/goodhacker/p/4014617.html

http://www.cnblogs.com/goodhacker/p/4014617.html

http://www.cnblogs.com/baizx/p/4254359.html

http://www.gitzx.com/android-cydiasubstrate/

從源碼中跟蹤Dex的加載流程

https://github.com/bunnyblue/DexExtractor

Android逆向之動態調試總結

dex文件的優化解析及裝載

Android系統ODEX文件格式解析

DexClassLoader4.4.2動態加載分析(磁盤加載分析)

Android4.0內存Dex數據動態加載技術

相關文章
相關標籤/搜索