Xused フレームワークは、frida にはない永続性を備えた「よく知られた」アーティファクトと言えます (frida は frida-gadget を通じて永続性を実現することもできますが、Xped ほど便利ではありません)。Java 層のコードをフックする必要がある場合、Xused を使用すると便利ですが、ソフトウェア開発者がセキュリティに対する意識を高めるにつれて、Java 層に配置されるコア コードがますます少なくなっているため、Xowned は、リバース アナリストは、Xused を使用してネイティブをフックする方法という問題にも直面しています。次の記事では、この問題の解決策を提供します。
Dobby フレームワークの概要
序章
Dobby は、軽量、マルチプラットフォーム、マルチアーキテクチャのインライン フック フレームワークです。軽量で使いやすく、Windows/macOS/iOS/Android/Linux プラットフォームをサポートし、X86、X86-64、ARM、ARM64 アーキテクチャをサポートしています。インラインフック用のフレームとして選びました
環境整備
まず、Dobby のリポジトリにある最新リリース バージョンをダウンロードします。
ダウンロードが完了したら解凍すると、ヘッダファイルと4つのアーキテクチャに対応したフォルダがあり、その中にスタティックリンクライブラリファイルが入っているので、これらのファイルをAndroid Studioプロジェクトに追加する必要があります。
次に、Android Studio を使用してネイティブ プロジェクトを作成し、必要なファイルをプロジェクトにインポートします。私のディレクトリ構造は次のとおりです (赤枠内に注目し、無関係なファイルは一時的に無視します)。
次に、CMakeLists.txt を記述して、インポートした静的リンク ライブラリを宣言します (変更する必要がある部分のみを表示します)。cmake コマンドについては、ドキュメントcmake-commands(7) — CMake 3.25.1 ドキュメントを参照してください。
手順
-
ドビーコードパッチ
このメソッドの機能は、メモリ内のデータを変更することです。通常、命令を変更するために使用されます。使用中は、ビッグ エンディアンとスモール エンディアンの問題に注意する必要があります。Android プラットフォームはリトル エンディアン モードなので、以下に nop 命令の例を示します (注: getAbsoluteAddress はカスタム関数であり、後述します)。
uint8_t nop[4] = {0xD5,0x3,0x20,0x1F}; uint8_t * nop_ptr = nop; DobbyCodePatch((void*)getAbsoluteAddress("libxgVipSecurity.so", 0x20710),nop_ptr,4);
-
ドビーフック
このメソッドの機能は、関数を変更または置換することです。置換関数のサンプル コードを以下に示します (注: getAbsoluteAddress はカスタム関数であり、後述します)。
char *(*old_sub_1FCCC)(char *, char *) = nullptr; char *new_sub_1FCCC(char *a1, char *a2) { char *result = old_sub_1FCCC(a1, a2); __android_log_print(6, "guagua", "data decrypt value is %s", result); if((strstr(result,"moreOtherData") - result) < 5){ char moreOtherData[93] = {""}; strncpy(moreOtherData,result+1,92); char *data_value = (char *) malloc(0x200); sprintf(data_value, "{%s%s", moreOtherData, "\"token\":\"35151312554131451445345314\""); __android_log_print(6, "guagua", "modified data decrypt value is %s", data_value); return data_value; } return result; } // hook sub_1FCCC DobbyHook((void*)getAbsoluteAddress("libxgVipSecurity.so", 0x1FCCC), (dobby_dummy_func_t)new_sub_1FCCC, (dobby_dummy_func_t *) &old_sub_1FCCC);
カスタムユーティリティ関数
普段Dobbyを使っていると問題に遭遇するのですが、soにシンボル名があるとフックした方が便利なのですが、IDAではsub_xxx以降の関数名が多いのですが、このとき関数のアドレスをどうやって取得するかは問題ですが、幸いなことに誰かがホイールを作成してくれたので、労力を節約できます。github で Utils.h と Obfuscate.h という 2 つのファイルを見つけました。これを使用する場合は、Utils.h をインポートするだけで済みます。 getAbsoluteAddress を使用できます。関数は関数のアドレスを取得します (注: ファイルは記事の最後で共有されています)。
Xused を使用して注入します
上記は Dobby の基本的な状況を紹介しました. ここでもう 1 つの点を追加する必要があります. JNI_OnLoad にフック コードを記述する必要があります, そうすることで JNI_OnLoad が挿入されたときにフック コードが自動的に実行されるようにする必要があります. JNI_OnLoad のサンプル コードは次のとおりです:
jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
__android_log_print(6, "guagua", "插件so注入成功");
JNIEnv *env = nullptr;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) == JNI_OK) {
// nop 0x20710
uint8_t nop[4] = {0xD5,0x3,0x20,0x1F};
__android_log_print(6, "guagua", "nop 0x20710 success");
uint8_t * nop_ptr = nop;
DobbyCodePatch((void*)getAbsoluteAddress("libxgVipSecurity.so", 0x20710),nop_ptr,4);
return JNI_VERSION_1_6;
}
return 0;
}
次に行うことは、ターゲット プログラムに so を挿入することですが、ここで注意すべき点は、個別に生成された so ファイルではなく、ネイティブ コードと Xused コードを 1 つのプロジェクトに配置していることです。
通常、注入したいプログラムはすべて強化されているため、最初に classLoader を切り替える必要があります。そうしないと、アプリケーションのクラスが見つかりません。ここでは 360 度強化を例として取り上げます。選択するタイミングに注意してください。もちろん、独自のタイミング ポイントの選択を理解することもできます。
ClassLoader mclassloader = null;
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
XposedBridge.log(lpparam.packageName);
if (lpparam.packageName.equals("com.tencent.rilp")) {
XposedHelpers.findAndHookMethod("com.stub.StubApp", lpparam.classLoader, "onCreate", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
// 获取classloader
Class activitythreadclass = lpparam.classLoader.loadClass("android.app.ActivityThread");
Object activityobj = XposedHelpers.callStaticMethod(activitythreadclass, "currentActivityThread");
Object mInitialApplication = XposedHelpers.getObjectField(activityobj, "mInitialApplication");
Object mLoadedApk = XposedHelpers.getObjectField(mInitialApplication, "mLoadedApk");
mclassloader = (ClassLoader) XposedHelpers.getObjectField(mLoadedApk, "mClassLoader");
XposedBridge.log("guagua classloader change success");
}
});
}
}
フック so を注入する前に, タイミングも選択する必要があります. フックがシステムの so である場合, フック so はターゲット プログラムの so がロードされる前に注入する必要があります. フックがターゲットの so である場合フックのロードタイミングはターゲットプログラムのロード完了後の任意のタイミングで選択できるため、
Android 8 より下では、doLoad をアクティブに呼び出してロードできます。Android 9 以降では、nativeLoad をアクティブに呼び出してロードできます。以下は、ロードするためのコードです。
int version = android.os.Build.VERSION.SDK_INT;
if (!path.equals("")){
if (version >= 28) {
XposedBridge.log("guagua start inject libguagua.so");
XposedHelpers.callMethod(Runtime.getRuntime(), "nativeLoad", path, mclassloader);
} else {
XposedHelpers.callMethod(Runtime.getRuntime(), "doLoad", path, mclassloader);
}
}
このうち、pathはロードしたいパスなので、mclassloaderはクラスローダーで、クラスローダーは簡単に取得できるので、soのパスを取得するにはどうすればよいでしょうか?
ネイティブ コードは Xused プロジェクトに記述されているため、必要なのは Xowned モジュール自体の so パスを取得することだけです。低バージョンのシステムでは、so to death のパスを直接書き込むことができますが、高バージョンのシステムでは不可能です。パスには同じ文字列が存在しますが、独自のプロセスで取得できるためです~~cFiynmB1ZhW3l4ffMY7duw==
。 。しかし、その前に、xused_init で宣言されたクラスのコードはターゲット プログラム内で実行されており、ターゲット プログラム自体で実行されているコード、およびターゲット プログラムと Xused のアプリケーションとして理解できることを理解する必要があります。私たちが作成したモジュールは 2 つのプロセスであるため、IPC メカニズムを使用して Xused モジュール内のそのパスを取得する必要があります。
Android で IPC を実装するには、バンドル、ファイル共有、メッセンジャー、AIDL、ContentProvider、ソケットなど、さまざまな方法がありますが、ここではファイル共有を選択して IPC を実装します。
まず、コンポーネント クラスでアプリケーションの so パスを取得し、そのパスをファイルに保存します。ターゲット プログラムで実行されているコードは、ファイルから so のパスを読み取り、上記のメソッドを通じてそれを挿入します。 。
宣言する必要がある権限:
<!--文件读写权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
コンポーネントのクラスコード:
package com.mdcg.guaguaxposed;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.init_button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
initSoPath();
}
});
}
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE"};
private void initSoPath() {
int sdk = Build.VERSION.SDK_INT;
if (sdk <= 29){
//检查权限(NEED_PERMISSION)是否被授权 PackageManager.PERMISSION_GRANTED表示同意授权
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission
.WRITE_EXTERNAL_STORAGE)) {
Toast.makeText(this, "请开通相关权限,否则无法正常使用本应用!", Toast.LENGTH_SHORT).show();
}
//申请权限
ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
} else {
writeSdcard();
}
}
else {
if (!Environment.isExternalStorageManager()) {
Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivity(intent);
return;
} else {
writeSdcard();
}
}
}
private void writeSdcard() {
String text = "";
PackageManager pm = getPackageManager();
List<PackageInfo> pkgList = pm.getInstalledPackages(0);
if(pkgList.size() > 0) {
for (PackageInfo pi : pkgList) {
// /data/app/~~cFiynmB1ZhW3l4ffMY7duw==/com.mdcg.guaguaxposed-zyuZcPG2uq6jw8Lc7DT40A==/base.apk
if (pi.applicationInfo.publicSourceDir.indexOf("com.mdcg.guaguaxposed") != -1) {
text = pi.applicationInfo.publicSourceDir.replace("base.apk", "lib/arm64/libguagua.so");
}
}
}
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File file1=new File("/sdcard/","guaguaSoPath.txt");
if (!file1.exists()){
try {
file1.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file1);
fileOutputStream.write(text.getBytes());
Toast.makeText(this, "初始化成功!", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
}
so パスを取得するための Xpose コード:
private String getSoPath() {
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
InputStream inputStream = null;
Reader reader = null;
BufferedReader bufferedReader = null;
try {
File file=new File("/sdcard/", "guaguaSoPath.txt");
inputStream = new FileInputStream(file);
reader = new InputStreamReader(inputStream);
bufferedReader = new BufferedReader(reader);
StringBuilder result = new StringBuilder();
String temp;
while ((temp = bufferedReader.readLine()) != null) {
result.append(temp);
}
XposedBridge.log("read so path is " + result.toString());
return result.toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return "";
}
エピローグ
Xused を使用してネイティブをフックする原理は、理解するのが難しくありません。ネイティブ フック フレームワークを使用して so ファイルを作成し、Xused を使用して so ファイルをロードするだけですが、詳細は少し面倒です。frida を使用してネイティブをフックする方がはるかに簡単ですが、永続性を実現したい場合は、Xused が非常に良い選択です。