日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

016 Android之NDK开发

發布時間:2025/3/21 Android 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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++中的數據類型,分為兩類

  • 引用類型,在C++中以j開頭,本質都是指針
  • 基本數據類型,在C++中以j開頭,本質上就是C++中的數據類型重定義
  • 基本數據類型

    引用類型

    引用類型的繼承關系

    JNI中的描述符

    描述符分為以下幾類:類描述符 域描述符 方法描述符

    • 類描述符是類的完整名稱(包名+類名),將原來的分隔符換成斜杠。例如:
    java.lang.String--->java/lang/String
    • 數組類型的描述符為:[+類型描述符
    int[]--->[I float[]--->[F String[]--->[Ljava/lang/String;
    • 方法描述符

    將參數類型的域描述符按照聲明順序放入一對括號中后跟返回值類型的域描述符,例如:

    String test()---->()Ljava/lang/String; int f(int i,Object object)---->(ILjava/lang/Object;)I void set(byte[] bytes)---->([B)V

    JNI基本使用

    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开发的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。