Cydia Substrate之hook native代码

内容纲要

转自:http://blog.csdn.net/qq_18870023/article/details/52247483

继上次的Cydia  Substrate  hook  java层,这里我将去hook  native层的代码,也即是C/C++代码。

我在网上找了很多资料,发现关于利用cydia hook native的文章没几篇,基本来来去去都是那几个大同小异的,都是介绍如何去hook  dvm加载dex的,估计也就是同一文章而已,只是被互相“借鉴”了。

幸好,我的英语还行,google了一把,发现有个外国小哥写得还挺好的,于是,就“借鉴”了一下~

这里,我并不是hook dvm的内容,而是自己写的一个小程序里的一个函数。

So,首先我们需要利用NDK创建一个要被hook的目标程序。

这里,需要一定的NDK开发知识,即使不懂,下面也会举个小例子简单介绍下NDK的开发,by the way,开发环境是Android Studio。

创建目标程序

1. 新建一个带MainActivity的工程,在该类内,添加加载so库的static块,添加native修饰的函数,然后在onCreate中利用Toast弹窗,调用native 函数,例子如下

[java] view plain copy

  1. public class MainActivity extends Activity {  

  2.   

  3.     @Override  

  4.     protected void onCreate(Bundle savedInstanceState) {  

  5.         super.onCreate(savedInstanceState);  

  6.         setContentView(R.layout.activity_main);  

  7.         Toast.makeText(this, hello(), Toast.LENGTH_SHORT).show();  

  8.     }  

  9.   

  10.     static{  

  11.         System.loadLibrary("hello");  

  12.     }  

  13.   

  14.     public static native String hello();  

  15.   

  16. }  

2. 在app/src/main目录下创建名为“jni”的文件夹,并新建hello.cpp文件,代码很简单, 只是调用一个createHello函数返回一个字符串“hello world”:

[cpp] view plain copy

  1. #include <jni.h>    

  2. #include <stdlib.h>    

  3.   

  4. extern "C"{  

  5.   

  6. char* createHello(){  

  7.     return "hello world";  

  8. }  

  9.   

  10. jstring Java_com_samuelzhan_hello_MainActivity_hello  

  11.         (JNIEnv *env, jobject obj, jstring str)  

  12. {  

  13.     return env->NewStringUTF(createHello());  

  14. }  

  15.   

  16. }  


注意,这里我用extern "C"将上面两个函数括起来,是因为我创建的是cpp文件,编译器用的是C++的,C++编译时,为了函数的重载而在编译后会改变函数名,会导致后面写的hook模块不能根据原来的名字找到函数的地址。下面会有详细解释,先跳过这里,继续下面的步骤。

3. 在jni文件夹下创建一个文件,命名为Android.mk,编写如下代码,与上面创建的cpp文件在同一目录下

[java] view plain copy

  1. LOCAL_PATH := $(call my-dir)  

  2. include $(CLEAR_VARS)  

  3.   

  4. LOCAL_MODULE := hello  

  5. LOCAL_SRC_FILES := hello.cpp  

  6. LOCAL_LDLIBS = -llog  

  7.   

  8. include $(BUILD_SHARED_LIBRARY)  

4. 配置三个文件:

   首先,在项目的gradle.properties文件的最下面添加一行代码

[java] view plain copy

  1. android.useDeprecatedNdk=true  

   接着,在项目的local.properties文件的最下面添加NDK路径(实际上SDK的路径已经在里面了)

[java] view plain copy

  1. ndk.dir=D\:\\android-ndk32-r10b-windows-x86\\android-ndk-r10b  

   最后,在app文件夹下的build.gradle下编辑两处地方

    一是,在defaultConfig下,添加ndk配置(moduleName就是so库的名字);  二是, 在android下,添加sourceSets(下一步ndk-build后会生成该路径),然后点击同步刷新

[java] view plain copy

  1. android {  

  2.     compileSdkVersion 23  

  3.     buildToolsVersion "23.0.3"  

  4.   

  5.     defaultConfig {  

  6.         applicationId "com.samuelzhan.hello"  

  7.         minSdkVersion 8  

  8.         targetSdkVersion 23  

  9.         versionCode 1  

  10.         versionName "1.0"  

  11.   

  12.         ndk{  

  13.             moduleName "hello"  

  14.             abiFilters "armeabi""armeabi-v7a""x86"  

  15.         }  

  16.     }  

  17.     sourceSets{  

  18.         main{  

  19.             jni.srcDirs = []  

  20.             jniLibs.srcDirs = ['src/main/libs']  

  21.         }  

  22.     }  

  23.     buildTypes {  

  24.         release {  

  25.             minifyEnabled false  

  26.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  

  27.         }  

  28.     }  

  29. }  

  30.   

  31. dependencies {  

  32.     compile fileTree(dir: 'libs', include: ['*.jar'])  

  33.     testCompile 'junit:junit:4.12'  

  34.     compile 'com.android.support:appcompat-v7:23.4.0'  

  35. }  

5. NDK编译

Alt+F12进入Android Studio的终端模式,cd命令到第二步创建的jni文件夹内,输入ndk-build  APP_ABI="armeabi",回车执行。(需要将ndk路径添加到环境变量)

接着,刷新工程结构,发现会在app/src/main下多出两个文件夹libs和obj,libs里就存放着so库。

6. APK打包,安装到手机上运行。

运行结果,通过本地调用,从C代码中返回字符串“hello world”,通过Toast弹出:


目标小程序已写好,那么接下来就是hook它了,将hello world的内容改变(在C代码里)。

下载安装Cydia Substrate框架

框架下载com.saurik.substrate.apk

Cydia Substrate SDK

注意,手机需root,而且android系统在4.4以下


编写Cydia Substrate模块

这里我将编写一个模块去hook 上面小程序中的createHello函数,修改返回值。

和上面的小程序差不多,编写Substrate模块也是一个NDK开发的过程:

1. 创建一个没有Activity的空工程, 并添加Cydia Substrate SDK:

在app/src/main下创建jni文件夹,并加入三个文件(两个so库和一个头文件):



2.  在jni文件夹下新建cpp文件,名字需双后缀,一定得带 .cy 后缀,如上面的 module.cy.cpp文件:

[cpp] view plain copy

  1. #include "substrate.h"    

  2. #include <android/log.h>    

  3. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "zz", __VA_ARGS__)  

  4.   

  5. //配置,这里是hook可运行程序(即NDK小程序)的写法,下面那个就是hook dvm的写法    

  6. MSConfig(MSFilterExecutable,"/system/bin/app_process")  

  7. //MSConfig(MSFilterLibrary, "libdvm.so");    

  8.   

  9. //旧的函数地址,目的为了保留指向原来函数的入口,在新的函数执行    

  10. //完后,一般会再调用该函数,以确保程序的正常运行    

  11. char* (* hello)(void);  

  12.   

  13. //新的函数,替代hook的函数,返回修改后的值    

  14. char* newHello(void)  

  15. {  

  16.     //直接返回新的字符串  

  17.     return "fuck the world";  

  18.     //执行原函数,确保程序运行正常,但这里代码简单,可以直接返回字符串即可    

  19.     //return hello();    

  20. }  

  21.   

  22. //通过so库的绝对路径和函数名,找到其函数的映射地址    

  23. void* lookup_symbol(char* libraryname,char* symbolname)  

  24. {  

  25.     //获取so库的句柄  

  26.     void *handle = dlopen(libraryname, RTLD_GLOBAL | RTLD_NOW);  

  27.     if (handle != NULL){  

  28.         //根据so库句柄和符号名(即函数名)获取函数地址  

  29.         void * symbol = dlsym(handle, symbolname);  

  30.         if (symbol != NULL){  

  31.             return symbol;  

  32.         }else{  

  33.             LOGD("dl error: %s", dlerror());  

  34.             return NULL;  

  35.         }  

  36.     }else{  

  37.         return NULL;  

  38.     }  

  39. }  

  40.   

  41. MSInitialize  

  42. {  

  43.     //获取hook函数的地址,最好不要用下面MS提供的方法  

  44.     void * symbol = lookup_symbol("/data/data/com.samuelzhan.hello/lib/libhello.so","createHello");  

  45. //    MSImageRef  image=MSGetImageByName("/data/data/com.samuelzhan.hello/lib/libhello.so");  

  46. //    void *symbol=MSFindSymbol(image, "createHello");  

  47.   

  48.     //这里将旧函数的入口(参数一)指向hello(参数三),然后执行新函数(参数二)    

  49.     MSHookFunction(symbol, (void*)&newHello, (void**)&hello);  

  50. }    


3. 在jni文件夹下创建Android.mk文件,将substrate提供的两个so库也加进去一起编译:

[java] view plain copy

  1. LOCAL_PATH := $(call my-dir)  

  2.   

  3. include $(CLEAR_VARS)  

  4. LOCAL_MODULE:= substrate-dvm  

  5. LOCAL_SRC_FILES := libsubstrate-dvm.so  

  6. include $(PREBUILT_SHARED_LIBRARY)  

  7.   

  8. include $(CLEAR_VARS)  

  9. LOCAL_MODULE:= substrate  

  10. LOCAL_SRC_FILES := libsubstrate.so  

  11. include $(PREBUILT_SHARED_LIBRARY)  

  12.   

  13.   

  14. include $(CLEAR_VARS)  

  15. LOCAL_MODULE    := module.cy #生成的模块名  

  16. LOCAL_SRC_FILES := module.cy.cpp #源文件名  

  17. LOCAL_LDLIBS = -llog  

  18. LOCAL_LDLIBS += -L$(LOCAL_PATH) -lsubstrate-dvm -lsubstrate  

  19. include $(BUILD_SHARED_LIBRARY)  

4. 配置三个文件,gradle.properties和local.properties的设置和上面编写目标程序一样,各添加一句代码即可, 至于build.gradle基本也一样,只不过moduleName改为 “module.cy”,以及加上几个库。

gradle.properties:

buid-gradle 配置

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "pro.xuji.hookdemo"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags "-frtti -fexceptions"
            }
        }
        ndk{
            moduleName "module.cy"         //生成的so名字
            ldLibs "log", "z", "m"
            abiFilters "armeabi"  //输出指定三种abi体系结构下的so库。目前可有可无。
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs =  ['src/main/jniLibs/']
        }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    provided files('lib/XposedBridgeApi-54.jar')
}

[java] view plain copy

  1. android.useDeprecatedNdk=true  

local.properties:

[java] view plain copy

  1. ndk.dir=D\:\\android-ndk32-r10b-windows-x86\\android-ndk-r10b  

build.gradle:

[java] view plain copy

  1. android {  

  2.     compileSdkVersion 23  

  3.     buildToolsVersion "23.0.3"  

  4.   

  5.     defaultConfig {  

  6.         applicationId "com.samuelzhan.cydiahookjni"  

  7.         minSdkVersion 8  

  8.         targetSdkVersion 23  

  9.         versionCode 1  

  10.         versionName "1.0"  

  11.   

  12.         ndk{  

  13.             moduleName "module.cy"  

  14.             ldLibs "substrate"  

  15.             ldLibs "substrate-dvm"  

  16.             ldLibs "log"  

  17.         }  

  18.     }  

  19.   

  20.     sourceSets{  

  21.         main{  

  22.             jni.srcDirs = []  

  23.             jniLibs.srcDirs=['src/main/libs']  

  24.         }  

  25.     }  

  26.   

  27.     buildTypes {  

  28.         release {  

  29.             minifyEnabled false  

  30.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  

  31.         }  

  32.     }  

  33. }  

  34.   

  35. dependencies {  

  36.     compile fileTree(dir: 'libs', include: ['*.jar'])  

  37.     testCompile 'junit:junit:4.12'  

  38.     compile 'com.android.support:appcompat-v7:23.4.0'  

  39. }  

5. NDK编译,和目标程序一样,cd到jni文件夹,终端里输入命令ndk-build,回车。

6. 配置AndroidManifest.xml文件,添加installLocation,hasCode, uses-permission:

[html] view plain copy

  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  

  2.     package="com.samuelzhan.cydiahookjni"  

  3.     android:installLocation="internalOnly">  

  4.   

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

  6.     <application android:hasCode="false">  

  7.   

  8.     </application>  

  9.   

  10. </manifest>  

7.打包,安装到手机,并在cydia substrate框架弹出来的消息框选择软重启。

好了,这里看看重启后,是否能成功hook到createHello这个函数,并修改其返回内容呢?


到此,已经成功hook到native代码的函数并修改。下面将针对期间extern “C”的问题进行详细说明



-----------------------------------特殊问题补充----------------------------------

在上面的Hello小程序中(目标程序),在编写cpp文件时,有一个extern "C"的代码块,这个extern "C"就是告诉C++编译器用C的规范去编译这个代码块的内容。

那么,两者编译后的函数有什么区别呢?看看下面两幅图:

当 extern "C" 的范围包括 createHello函数时,ndk-build后,利用IDA PRO打开libhello.so库:


当 extern "C" 的范围不包括 createHello函数时,ndk-build后,利用IDA PRO打开libhello.so库:


实际上,C++为了实现函数重载,编译器会在编译后修改函数的名字,在原函数名字加上前缀和后缀,而C编译器则不会。

这就容易在cydia substrate模块中查找函数地址lookup_symbol调用 void* dlsym(void* handle, const char* symbolName)造成异常情况。

比如,extern “C” 没有包括createHello这个函数,那么我在  dlsym()函数中传入的函数名“createHello”就没有太大意义了,因为C++编译器已经修改过函数名,这会抛出“symbol not defined”的错误。

当然,你可以通过IDA PRO查看到修改过的函数名,将修改过的函数名传入到 dlsym()函数也是可以找到函数地址的。


由于许多公司对APP的安全性越来越重视,因此很多公司的核心业务处理模块一般会采用NDK开发,通过jni机制调用C代码来实现模块功能。这种用C/C++开发出来的代码反编译分析的难度远远大于java开发,因为C/C++反编译过来的汇编语言可读性很差,这给反编译人员带来很大的挑战。

然而对于我这种初入逆向分析的菜鸟来说,每次看到native修饰的方法都觉得心里有道坎,尽管如此,我还是想在里面搞些文章,希望能分析出点成果~

发表回复