016 Android之NDK开发
文章目錄
- NDK入門指南
- 下載NDK和工具
- NDK工程說明
- JNI數據類型
- JNI中的描述符
- JNI基本使用
- JAVA代碼調用C++代碼
- C++代碼調用JAVA代碼
- C++代碼修改JAVA字段
- 動態注冊
NDK入門指南
原生開發套件(NDK)是一套工具,能夠在Android應用中使用C和C++代碼,并提供眾多平臺庫,可以使用這些平臺庫管理原生Activity和訪問物理設備組件。
與NDK密切相關的另一個詞匯則是JNI,它是NDK開發中的樞紐,Java與底層交互大多數通過它來完成。
JNI: Java Native Interface 也就是java本地接口 ,它是一個協議,這個協議用來溝通Java代碼和C++代碼。通過這個協議 ,Java類的某些方法可以用原生實現,同時可以讓他們像普通的Java方法一樣被調用和使用。
也就是說使用JNI這種協議可以實現Java代碼和C++代碼的相互調用
那為什么要使用NDK開發呢?
- Java是半解釋型語言,容易被反匯編成源碼,在開發一些重要協議時,為了安全起見,使用C語言來編寫這些重要的部分,來增大系統的安全性
- 在一些復雜性的計算中,要求高性能的場景中,C++更有效率,代碼也便于復用
下載NDK和工具
在Android Studio中,點開Config
找到SKD Manager
勾選LLIB NDK和CMake
NDK工程說明
新建一個項目,選擇Native C++
語言選擇JAVA,后續所有操作默認即可
在默認生成的工程中有一個Native方法stringFromJNI
該方法的實現在native-lib.cpp里
Java_com_example_ndkdemo_MainActivity_stringFromJNI當前這個函數的名稱由以下幾部分組成
Java_[包名]_[類名]_[函數名]這個函數默認有兩個參數,如果java中有參數,就繼續在兩個參數后面加。
其中參數一JNIEnv* env,是JNI的環境指針,我們用到的jni函數,都在這個指針中;參數二jobject是java對象的this
JNI數據類型
java在C++中的數據類型,分為兩類
基本數據類型
引用類型
引用類型的繼承關系
JNI中的描述符
描述符分為以下幾類:類描述符 域描述符 方法描述符
- 類描述符是類的完整名稱(包名+類名),將原來的分隔符換成斜杠。例如:
- 數組類型的描述符為:[+類型描述符
- 方法描述符
將參數類型的域描述符按照聲明順序放入一對括號中后跟返回值類型的域描述符,例如:
String test()---->()Ljava/lang/String; int f(int i,Object object)---->(ILjava/lang/Object;)I void set(byte[] bytes)---->([B)VJNI基本使用
JAVA代碼調用C++代碼
先來寫一個最簡單的Crackme,體驗NDK編程的完整流程
界面代碼如下
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><EditTextandroid:id="@+id/edt_user"android:hint="請在此輸入用戶名"android:layout_width="match_parent"android:layout_height="wrap_content"></EditText><EditTextandroid:id="@+id/edt_pass"android:hint="請在此輸入密碼"android:layout_width="match_parent"android:layout_height="wrap_content"></EditText><Buttonandroid:id="@+id/btn1"android:text="注冊"android:onClick="onClick"android:layout_width="match_parent"android:layout_height="wrap_content"></Button></LinearLayout>接著新增一個Native函數
public native boolean stringFromJNI2(String user,String pass);然后在C++中編寫實現代碼
extern "C" JNIEXPORT jboolean JNICALL Java_com_example_ndkdemo_MainActivity_stringFromJNI2(JNIEnv *env, jobject thiz, jstring user,jstring pass) {bool bRet=false;const char* pUser=env->GetStringUTFChars(user,JNI_FALSE);const char* pPass=env->GetStringUTFChars(pass,JNI_FALSE);if (strcmp(pUser,pPass)==0){bRet= true;}//釋放字符串env->ReleaseStringUTFChars(user,pUser);env->ReleaseStringUTFChars(pass,pPass);return bRet;}然后在按鈕點擊事件中調用C接口
public void onClick(View view) {EditText editText=findViewById(R.id.edt_user);EditText editText1=findViewById(R.id.edt_pass);String user=editText.getText().toString();String pass=editText1.getText().toString();boolean bRet=stringFromJNI2(user,pass);if (bRet) {Toast.makeText(this,"恭喜 注冊成功",Toast.LENGTH_LONG).show();}else {Toast.makeText(this,"注冊失敗",Toast.LENGTH_LONG).show();}}這樣就完成了一個完整的JAVA代碼調用C代碼的過程
C++代碼調用JAVA代碼
上面的Demo顯然安全性不夠,因為只要反編譯JAVA代碼直接修改返回值就能直接破解,不需要分析C++代碼,所以再進一步進行修改。
public native void stringFromJNI2(String user,String pass);首先修改方法原型,讓函數返回空
public void onClick(View view) {EditText editText=findViewById(R.id.edt_user);EditText editText1=findViewById(R.id.edt_pass);String user=editText.getText().toString();String pass=editText1.getText().toString();stringFromJNI2(user,pass);}在onClick方法內直接調用stringFromJNI2函數
public void ShowText(){Toast.makeText(this,"恭喜 注冊成功",Toast.LENGTH_LONG).show();}然后封裝一個ShowText方法
extern "C" JNIEXPORT void JNICALL Java_com_example_ndkdemo_MainActivity_stringFromJNI2(JNIEnv *env, jobject thiz, jstring user,jstring pass) {const char* pUser=env->GetStringUTFChars(user,JNI_FALSE);const char* pPass=env->GetStringUTFChars(pass,JNI_FALSE);if (strcmp(pUser,pPass)==0){//獲取類類型jclass jclass1=env->GetObjectClass(thiz);//獲取方法IDjmethodID jmethodId=env->GetMethodID(jclass1,"ShowText","()V");//調用方法env->CallVoidMethod(thiz,jmethodId);}//釋放字符串env->ReleaseStringUTFChars(user,pUser);env->ReleaseStringUTFChars(pass,pPass); }接著在C++代碼中調用JAVA成員函數
C++代碼修改JAVA字段
接著再對上面的代碼進行修改
public String mString="不好意思 出錯了";public void ShowError(){Toast.makeText(this,mString,Toast.LENGTH_LONG).show();}首先封裝ShowError方法,彈出錯誤提示。接著修改C++的Native方法,如果輸入錯誤則彈出錯誤提示,如果正確則將提示字符串修改為注冊成功。
extern "C" JNIEXPORT void JNICALL Java_com_example_ndkdemo_MainActivity_stringFromJNI3(JNIEnv *env, jobject thiz, jstring user,jstring pass) {const char* pUser=env->GetStringUTFChars(user,JNI_FALSE);const char* pPass=env->GetStringUTFChars(pass,JNI_FALSE);if (strcmp(pUser,pPass)==0){//修改JAVA中的字段//獲取類類型jclass jclass1=env->GetObjectClass(thiz);//獲取字段IDjfieldID jfieldId=env->GetFieldID(jclass1,"mString","Ljava/lang/String;");//修改字段env->SetObjectField(thiz,jfieldId,env->NewStringUTF("恭喜 注冊成功"));}//獲取類類型jclass jclass1=env->GetObjectClass(thiz);//獲取方法IDjmethodID jmethodId=env->GetMethodID(jclass1,"ShowError","()V");//調用方法env->CallVoidMethod(thiz,jmethodId);//釋放字符串env->ReleaseStringUTFChars(user,pUser);env->ReleaseStringUTFChars(pass,pPass);}這樣就完成了用C++代碼修改JAVA字段。
但是這樣就引出了一個問題
我們只要直接用IDA加載so文件,在導出表中搜索類名,就能很容易找到編寫的Native方法
然后直接通過F5查看C++源碼。這里我們想讓分析人員不那么容易找到對應的Native方法,這樣就引入了動態注冊的概念
動態注冊
public native void Check(String user,String pass);新增一個Native方法,命名為Check
extern "C" JNIEXPORT void JNICALL AAA(JNIEnv *env, jobject thiz, jstring user, jstring pass) {// TODO: implement Check() }然后在生成的C++代碼中,將名字修改為AAA。接下來我們利用動態注冊的方式將Check方法和AAA進行綁定
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)在C++代碼中實現一個onload方法,在so文件加載時,在JNI_OnLoad方法中進行注冊,將Check方法和AAA進行綁定,實現代碼如下:
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {JNIEnv* env;//獲取JNI環境指針jint jret = vm->GetEnv((void **)&env,JNI_VERSION_1_6);if (jret!=JNI_OK){return JNI_ERR;}//獲取類類型 package com.example.ndkdemo;jclass jclass1=env->FindClass("com/example/ndkdemo/MainActivity");//準備結構體數組const JNINativeMethod method={"Check","(Ljava/lang/String;Ljava/lang/String;)V",(void *)AAA};//注冊env->RegisterNatives(jclass1,&method,1);return JNI_VERSION_1_6; }注冊完整之后就可以在JAVA代碼中直接調用Check方法
這種方法的好處在于直接搜索函數名稱是無法搜索到的
通過這種方法動態注冊的函數只能通過JNI_Onload找到實現代碼
總結
以上是生活随笔為你收集整理的016 Android之NDK开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 015 Android之可执行文件dex
- 下一篇: 017 Android加固之APK混淆和