隨着 Android 移動安全的高速發展,不論是爲了執行效率仍是程序的安全性等,關鍵代碼下沉 native 層已成爲基本操做。
native 層的開發就是通指的 JNI/NDK 開發,經過 JNI 能夠實現 java 層和 native 層(主要是 C/C++ )的相互調用,native 層經編譯後產生 so 動態連接庫,so 文件具備可移植性廣,執行效率高,保密性強等優勢。
那麼問題來了,如何調用 so 文件顯得異常重要,固然你也能夠直接分析 so 文件的僞代碼,利用強悍的編程功底直接模擬關鍵操做,可是我想對於普通人來講頭髮仍是比較重要的。
當前調用 so 文件的主流操做應該是:
1,基於 Unicorn 的各類實現(還在學習中,暫且不表)
2,Android 服務器的搭建,在 App 內起 http 服務完成調用 so 的需求(固然前提是過了 so 的效驗等操做)
至於爲何選用 AndServer,好吧,不爲何,只是由於搜索到了它
爲何結合 Service,在學習 Android 開發的時候瞭解到了 Service 的生命週期,我的理解用 Service 去建立 Http 服務比較好。
固然也有 Application 的簡單使用,由於在正式環境中,大多數 so 文件的邏輯中都有 context 的一些包名了,簽名了的效驗等,自定義 Application 的話獲取 context 傳參就行了。
java
這是我編譯好的一個 so 文件,就是根據入參作下簡單的字符串拼接(如下是 native 層編譯前的 c 代碼)android
extern "C" JNIEXPORT jstring JNICALL Java_com_fw_myapplication_ndktest_NdkTest_stringFromUTF(JNIEnv *env, jobject instance, jstring str_) { jclass String_clazz = env->FindClass("java/lang/String"); jmethodID concat_methodID = env->GetMethodID(String_clazz, "concat", "(Ljava/lang/String;)Ljava/lang/String;"); jstring str = env->NewStringUTF(" from so --[NightTeam夜幕]"); jobject str1 = env->CallObjectMethod(str_, concat_methodID, str); const char *chars = env->GetStringUTFChars((jstring)str1, 0); return env->NewStringUTF(chars); }
這部分代碼仍是有必要貼一下的,簡單的靜態註冊使用了反射的思想,反射在逆向中相當重要
接下來是 java 代碼,定義了 native 函數編程
package com.fw.myapplication.ndktest; public class NdkTest { public static native String stringFromUTF(String str); static { System.loadLibrary("yemu"); } }
若是到這裏有點懵逼的同窗可能須要去補下 Android 開發基礎了
json
先說下個人環境,由於這個環境影響太大了
1,AndroidStudio 3.4
2,手機 Android 6 架構 armeabi-v7a
打開 AndroidStudio 新建 project
在 module 的 build 中加這麼一句,而後 sync
把編譯好的 so 文件複製到 libs 文件夾下(和剛纔的 jniLibs.srcDirs 對應)
把 so 對應的 java 代碼也 copy 過來,注意包名類名的一致性
打開 activity_main.xml 文件爲 TextView 添加 id
打開 MainActiviy.java 開始編碼
這兩行的意思就是,先從佈局中找到對應 id 的 TextView,而後爲其設置 Text(調用 native 函數的返回值)
下面測試一下我們的 so 調用狀況
能夠看到我們的 so 文件調用成功(這裏我們的 so 沒有效驗,只是測試 app 是否能夠正常調用)
AndServer 代碼編寫
AndServer 官方文檔:https://yanzhenjie.com/AndServer/
打開官方文檔,看看人家的入門介紹,新建 java 文件
如圖經典 MVC 的 C 就寫好了,定義了一個 nightteam_sign 接口,請求方式爲 get,請求參數爲 sign,調用 native 函數,而後返回 json,可是這裏我想利用 Application 獲取下 context 對象,取下包名,接下來自定義 Applictaion瀏覽器
package com.nightteam.httpso; import android.app.Application; public class MyApp extends Application { private static MyApp myApp; public static MyApp getInstance() { return myApp; } @Override public void onCreate() { super.onCreate(); myApp = this; } }
而後在 manifest 文件中指定要啓動的 Application
而後修改 MyController.java 的代碼
接下來把官方文檔-服務器的代碼 copy 下來
導入一些包,修改部分代碼以下
新版本的 AndServer.serverBuilder 已經須要傳遞 context 了,這裏把網絡地址和端口號也修改成從構造參數中獲取,到這裏 AndServer 的東西基本完了,實際上我們就搭建一個調 so 的接口,並無過多的業務邏輯,因此代碼就是使用的最簡單的
安全
我們這裏用按鈕的點擊事件啓動 Service,故在 activity_main.xml 中添加一個 button 並指定點擊事件
接下來編寫自定義 Service 代碼服務器
package com.nightteam.httpso.Service; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; import com.nightteam.httpso.ServerManager; import java.net.InetAddress; import java.net.UnknownHostException; public class MyService extends Service { private static final String TAG = "NigthTeam"; @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate: MyService"); new Thread() { @Override public void run() { super.run(); InetAddress inetAddress = null; try { inetAddress = InetAddress.getByName("0.0.0.0"); Log.d(TAG, "onCreate: " + inetAddress.getHostAddress()); ServerManager serverManager = new ServerManager(getApplicationContext(), inetAddress, 8005); serverManager.startServer(); } catch (UnknownHostException e) { e.printStackTrace(); } } }.start(); } @Override public IBinder onBind(Intent intent) { return null; } }
打上了幾個 log,在子線程中啓動 AndServer 的服務(什麼時候使用 UI 線程和子線程是 Android 基礎,這裏就不贅述了)
注意一下,這裏從 0.0.0.0 獲取 inetAddress,可不要寫錯了,localhost 和 0.0.0.0 的區別請移步搜索引擎
而後就是向 ServerManager 的構造函數傳遞 context,inetAddress,port 用來 new 對象,隨後開啓服務
最後注意檢查下 manifest 文件中 Service 的聲明
開啓 Service,並獲取本機 ip
回到咱們的 MainActivity.java 的 operate( button 的點擊事件)編寫啓動 Service 代碼網絡
public void operate(View view) { switch (view.getId()){ case R.id.id_bt_index: //啓動服務:建立-->啓動-->銷燬 //若是服務已經建立了,後續重複啓動,操做的都是同一個服務,不會再從新建立了,除非你先銷燬它 Intent it1 = new Intent(this, MyService.class); Log.d(TAG, "operate: button"); startService(it1); ((Button) view).setText("服務已開啓"); break; } }
到這裏咱們的服務基本搭建好了,可是爲了方便起見,我想把我們的本機 ip 顯示在 App 上,這樣咱們就不用去設置再查看了
我在網上找到了一個獲取 ip 地址的一個工具類,源碼以下:架構
package com.nightteam.httpso; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; import java.util.regex.Pattern; public class NetUtils { private static final Pattern IPV4_PATTERN = Pattern.compile("^(" + "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" + "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"); private static boolean isIPv4Address(String input) { return IPV4_PATTERN.matcher(input).matches(); } //獲取本機IP地址 public static InetAddress getLocalIPAddress() { Enumeration<NetworkInterface> enumeration = null; try { enumeration = NetworkInterface.getNetworkInterfaces(); } catch (SocketException e) { e.printStackTrace(); } if (enumeration != null) { while (enumeration.hasMoreElements()) { NetworkInterface nif = enumeration.nextElement(); Enumeration<InetAddress> inetAddresses = nif.getInetAddresses(); if (inetAddresses != null) while (inetAddresses.hasMoreElements()) { InetAddress inetAddress = inetAddresses.nextElement(); if (!inetAddress.isLoopbackAddress() && isIPv4Address(inetAddress.getHostAddress())) { return inetAddress; } } } } return null; } }
把工具類 copy 到咱們的 Android 項目中,繼續在 MainActivity.java 中編碼
獲取了一下本機地址和 Android SDK 版本( Android 8 以後啓動 Service 方式不同)
app
最後一步就是爲 app 申請網絡權限了
隨後鏈接咱們的手機,運行項目,測試一下,點擊開啓服務
看下 AndroidStudio 日誌
好像一切正常,在瀏覽器訪問下試試( ip 就是 App 中顯示的 ip 地址)
如圖正常訪問到了咱們想要的內容
回過頭來講下 Service,打開咱們手機的設置,找到應用程序管理-運行中的服務(手機不一樣,方式不一樣)
能夠看到咱們的程序,運行了一個服務,這個服務就是我們編碼的 MyService
接下來殺掉該 App進程,再次查看運行中的服務
我這裏在權限管理設置了自動運行,能夠保持服務的運行。(這個地方仍是根據系統有大小差別)
至此使用 App 起 http 服務調 so 就完成了
——————————————————————————————————————————
覆蓋主流平臺:抖音,快手,小紅書,TikTok,YouTube