Use Xposed to hook native

The Xposed framework can be described as a "well-known" artifact, which has the persistence that frida does not have (although frida can also achieve persistence through frida-gadget, it is not as convenient as Xposed). When we need to hook the code of the java layer, Xposed is handy to use, but as software developers become more and more security aware, the core code placed in the java layer is getting less and less, which makes Xposed a bit powerless to use , Reverse analysts are also faced with the problem of how to use Xposed to hook native. The following article provides a solution to this problem.

The introduction of Dobby framework
uses Xposed to inject so
epilogue
appendix

Introduction to the Dobby framework

Introduction

Dobby is a lightweight, multi-platform, multi-architecture inline hook framework. It is light and convenient to use, supports Windows/macOS/iOS/Android/Linux platforms, and supports X86, X86-64, ARM, ARM64 architectures, so I Choose it as the frame for inline hook

Environmental preparation

First download the latest released version in Dobby's repository

insert image description here

insert image description here

After the download is complete, unzip it, and you will see a header file and folders corresponding to the four architectures. There are static link library files in the folder, and then you need to add these files to the android studio project.

insert image description here

Next, use android studio to create a native project, and then import the required files into the project. My directory structure is as follows (focus on the ones in the red box, and temporarily ignore irrelevant files)

insert image description here

Then write CMakeLists.txt to declare the static link library we imported (only show the parts that need to be changed). For cmake commands, please refer to the document cmake-commands(7) — CMake 3.25.1 Documentation

insert image description here

insert image description here

Instructions

  1. DobbyCodePatch

    The function of this method is to modify the data in the memory. It is usually used to modify instructions. During use, it is necessary to pay attention to the big and small endian problems. The Android platform is a little endian mode, so pay attention to adjusting the order. The following shows an example of a nop instruction (Note: getAbsoluteAddress is a custom function, described below)

    uint8_t nop[4] = {0xD5,0x3,0x20,0x1F};
    uint8_t * nop_ptr = nop;
    DobbyCodePatch((void*)getAbsoluteAddress("libxgVipSecurity.so", 0x20710),nop_ptr,4);
    
  2. DobbyHook

    The function of this method is to modify or replace a function. A sample code of a replacement function is given below (Note: getAbsoluteAddress is a custom function, explained below)

    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);
    

Custom utility functions

When I usually use Dobby, I will encounter a problem. It will be more convenient to hook when there is a symbol name in so, but many functions are named after sub_xxx in IDA. At this time, how to obtain the address of the function is a problem. , but fortunately someone made a wheel, we can save some effort, I found two files on github, namely Utils.h and Obfuscate.h, when using it, you only need to import Utils.h, and then you can use getAbsoluteAddress The function gets the address of the function, (note: the file is shared at the end of the article)

insert image description here

insert image description here

Use Xposed to inject so

The above introduces the basic situation of Dobby. Here we need to add one more point. We need to write the hook code into JNI_OnLoad, so that our hook code can be automatically executed when so is injected. The sample code of JNI_OnLoad is as follows:

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;
}

The next thing to do is to inject so into the target program. It should be noted here that I put the native code and Xposed code in one project, not a separately generated so file.

Generally, the programs we want to inject are all hardened, so we need to switch the classLoader first, otherwise the class of the application cannot be found. Here I take 360 ​​hardening as an example, pay attention to the timing I choose, of course, you can also according to your own Understand the choice of other timing points

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");
            }
        });
    }
}

Before injecting our hook so, we also need to choose the timing. If the hook is the so of the system, then our hook so must be injected before the so of the target program is loaded. If the hook is the so of the target program, then we The loading timing of the hook so can be selected at any timing after the loading of the target program so is completed

Below Android 8, you can actively call doLoad to load so, and above Android 9, you can actively call nativeLoad to load so, the following is the code for loading so

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);
    }
}

Among them, path is the path we want to load so, mclassloader is the class loader, and the class loader is easy to get, so how to get the path of so?

Since my native code is written in the Xposed project, I just need to get the so path of the Xposed module itself. In the low-version system, we can directly write the path of so to death, but it is not possible in the high-version system, because there will be the same string in the path ~~cFiynmB1ZhW3l4ffMY7duw==, but it can be obtained by its own process. But before that, we need to understand one thing, the code of the class declared in xposed_init is running in the target program, which can be understood as the code running in the target program itself, and the application of the target program and the Xposed module we wrote is Two processes, so we need to use the IPC mechanism to obtain the path of so in the Xposed module.

There are many ways for Android to implement IPC, including Bundle, file sharing, Messenger, AIDL, ContentProvider, and Socket. Here I choose file sharing to implement IPC.

First, obtain the so path of the application in the component class, and save the path in a file, and the code running in the target program is responsible for reading the path of so from the file, and injecting it through the method described above.

Permissions that need to be declared:

<!--文件读写权限-->
<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"/>

Component class code:

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();
                        }
                    }
                }
            }
        }
    }
}

Xposed code to get the so path:

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 "";
}

epilogue

The principle of using Xposed to hook native is not difficult to understand. It is nothing more than using some native hook framework to write a so file, and then use Xposed to load the so file, but some details are a bit cumbersome. It would be much simpler to use frida to hook native, but if you want to achieve persistence, Xposed is a very good choice.

appendix

Utils.h and Obfuscate.h

Guess you like

Origin blog.csdn.net/weixin_56039202/article/details/128591978