使用Xposed对native进行hook

这篇具有很好参考价值的文章主要介绍了使用Xposed对native进行hook。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Xposed框架可谓是“家喻户晓”的神器,它具有着frida所不具备的持久性(虽然frida也可以通过frida-gadget实现持久化,但没有Xposed使用方便)。当我们需要hook java层的代码时,Xposed使用起来得心应手,但是随着软件开发者的安全意识越来越高,放在java层的核心代码也就越来少,这就导致Xposed使用起来有点力不从心,逆向分析者也就面临着如何使用Xposed对native进行hook的问题,下面的文章就对该问题提供一个解决思路。

Dobby框架的介绍
使用Xposed注入so
结语
附录

Dobby框架的介绍

简介

Dobby是一个轻量级、多平台、多架构的inline hook框架,它使用起来轻快便捷,支持Windows/macOS/iOS/Android/Linux平台,且支持X86, X86-64, ARM, ARM64架构,因此我选择它作为inline hook的框架

环境准备

首先在Dobby的仓库中下载最新发布的版本

xposed native,逆向实战随笔,java,android,android studio,安全

xposed native,逆向实战随笔,java,android,android studio,安全

下载完成后解压,会看到里面有一个头文件和对应着四个架构的文件夹,文件夹中放着静态链接库文件,之后需要把这些文件添加到android studio的项目中

xposed native,逆向实战随笔,java,android,android studio,安全

下面使用android studio创建一个native工程,然后把需要的文件导入到工程中,我的目录结构如下(重点看红框中的,不相干的文件暂时忽略)

xposed native,逆向实战随笔,java,android,android studio,安全

然后编写CMakeLists.txt对我们导入的静态链接库做声明(只展示需要改动的部分),cmake的命令可以参看文档cmake-commands(7) — CMake 3.25.1 Documentation

xposed native,逆向实战随笔,java,android,android studio,安全

xposed native,逆向实战随笔,java,android,android studio,安全

使用方法

  1. DobbyCodePatch

    该方法的作用是修改内存中的数据,通常用来修改指令,在使用过程中要注意的是大小端的问题,在安卓平台是小端模式,所以要注意调整顺序,下面展示nop一个指令的示例 (注:getAbsoluteAddress是自定义的函数,在下面说明)

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

    该方法的作用是修改或者替换一个函数,下面给出一个替换函数的示例代码 (注: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中有符号名时hook起来会方便一些,但很多函数在IDA中都是以sub_xxx命名的,这时候怎么获取函数的地址是一个难题,但好在有人造了轮子,我们可以省点力气,我在github上找到了两个文件,分别是Utils.h和Obfuscate.h,在使用时只需要导入Utils.h,然后就可以使用getAbsoluteAddress函数获取函数的地址了,(注:文件在文末分享)

xposed native,逆向实战随笔,java,android,android studio,安全

xposed native,逆向实战随笔,java,android,android studio,安全

使用Xposed注入so

上面介绍了Dobby的基本情况,这里还需要补充一点,我们需要把hook的代码写进JNI_OnLoad中,这样当so注入的时候才能自动执行我们的hook代码,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注入到目标程序中,这里要注意的是我把native代码和Xposed代码放在一个项目中,而不是单独生成的so文件。

一般我们要注入的程序都是被加固的,因此需要先对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");
            }
        });
    }
}

在注入我们的hook so之前,也需要选择时机,如果hook的是系统的so,那么我们的hook so一定要在目标程序的so加载之前就注入进去,如果hook的是目标程序的so,那么我们的hook so的加载时机可以选择目标程序so加载完成之后的任意一个时机

在安卓8以下可以主动调用doLoad加载so,在安卓9以上可以主动调用nativeLoad加载so,下面是加载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);
    }
}

其中path是我们要加载so的路径,mclassloader是类加载器,类加载器很容易就能拿到,那么so的路径该怎么获取?

既然我的native代码写在了Xposed项目里面,那我只需要拿到Xposed模块自身的so的路径不就行了吗。在低版本的系统中,我们可以直接把so的路径写死,但在高版本的系统中是不行的,因为在路径中会有如~~cFiynmB1ZhW3l4ffMY7duw==一样的字符串,不过可以由自身进程获取。但在这之前,我们需要明白一件事情,xposed_init里面声明的类的代码是运行在目标程序里面的,可以理解为是目标程序自身运行的代码,而目标程序和我们写的Xposed模块的应用是两个进程,所以我们需要利用IPC机制来获取Xposed模块中so的路径。

安卓实现IPC的方式有很多,有Bundle、文件共享、Messenger、AIDL、ContentProvider和Socket等,我这里选择文件共享来实现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();
                        }
                    }
                }
            }
        }
    }
}

Xposed获取so路径的代码:

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

结语

使用Xposed去hook native的原理并不难理解,无非就是使用一些native hook框架写成一个so文件,然后使用Xposed对so文件进行加载,只不过一些细节的部分有点繁琐。使用frida去hook native会简单许多,但如果是要实现持久化的话,Xposed是一个很不错的选择。

附录

Utils.h和Obfuscate.h文章来源地址https://www.toymoban.com/news/detail-784716.html

到了这里,关于使用Xposed对native进行hook的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android逆向学习(一)vscode进行android逆向修改并重新打包

    其实我不知道这个文章能不能写下去,其实我已经开了很多坑但是都没填上,现在专利也发出去了,就开始填坑了,本坑的主要内容是关于android逆向,主要的教程来源来自52破解论坛的大佬课程,但是那是windows版,我喜欢用linux,所以这是一个有比较大改动的学习教程,不过

    2024年02月10日
    浏览(47)
  • 【Java实战】微信Native扫码支付(主扫)开发详解

    最近需要对接微信的主扫支付,这里对主扫功能实现做一个简单的记录,以下代码以微信普通商户为例。 Native支付 是指商户系统生成支付二维码,用户再用微信“扫一扫”完成支付的模式。也就是 用户主动扫码 ,简称主扫。

    2024年02月06日
    浏览(32)
  • x86游戏逆向之实战游戏线程发包与普通发包的逆向

    网游找Call的过程中难免会遇到不方便通过数据来找的或者仅仅查找数据根本找不到的东西,但是网游中一般的工程肯定要发给服务器,比如你打怪,如果都是在本地处理的话就特别容易产生变态功能,而且不方便与其他玩家通信,所以找到了游戏发包的地方,再找功能就易如

    2024年02月06日
    浏览(46)
  • Android逆向学习(五)app进行动态调试

    非常抱歉鸽了那么久,前一段时间一直在忙,现在终于结束了,可以继续更新android逆向系列的,这个系列我会尽力做下去,然后如果可以的话我看看能不能开其他的系列,当然这就要看我以后忙不忙了,废话少说我们开始今天的学习,动态调试 对了,关于之前smali2java插件的

    2024年02月07日
    浏览(38)
  • 探索React Native认证实战示例项目:打造安全的移动应用体验

    项目地址:https://gitcode.com/hezhii/react-native-auth-example 在移动开发领域,React Native以其跨平台和高效性能而备受青睐。如果你正在寻找一个直观的、基于React Native的身份验证实现示例,那么这个项目—— react-native-auth-example ,将会是你的理想之选。 react-native-auth-example 是一个简单

    2024年04月27日
    浏览(42)
  • Android 逆向之脱壳实战篇

    作者:37手游安卓团队 前言 这篇文章比较干,比较偏实战,看之前建议先喝足水,慎入。 在学脱壳之前,我们先来复习一下,什么时候是加固? 加固本质上就是对 dex 文件进行加壳处理,让一些反编译工具反编译到的是 dex 壳,而不是 dex 文件本身。具体的实现方式是,将原

    2024年02月14日
    浏览(44)
  • 反汇编逆向实战——扫雷辅助制作

    刚开始是预备知识,如果熟悉的话,可以直接跳到第二部分阅读 在 Windows API 中, SetTimer 函数用于创建一个定时器,并在指定的时间间隔后触发一个定时器消息。以下是关于 SetTimer 函数的介绍: 功能:创建一个定时器,并在指定的时间间隔后触发定时器消息。 参数: hWnd:

    2024年02月08日
    浏览(42)
  • opencv实战---使用TesseractOCR进行文字识别

    什么是tesseractOCR? TesseractOCR 是一款由HP实验室开发由 Google 维护的开源 OCR(Optical Character Recognition , 光学字符识别)引擎。 简单点说,就是用来做字符识别的,可以识别超过100种语言。也可以用来训练其他的语言。 听起来不错,但识别的准确率让人恼火。于是,有人训练出

    2024年02月04日
    浏览(37)
  • 爬虫逆向实战(十三)--某课网登录

    主页地址:某课网 1、抓包 通过抓包可以发现登录接口是user/login 2、判断是否有加密参数 请求参数是否加密? 通过查看“载荷”模块可以发现有一个 password 加密参数,还有一个 browser_key 这个可以写死不需要关心 请求头是否加密? 无 响应是否加密? 无 cookie是否加密? 无

    2024年02月12日
    浏览(52)
  • WebSocket爬虫与JS逆向实战

    声明:本文章中所有内容仅供学习交流,不可用于任何商业用途和非法用途,否则后果自负,如有侵权,请联系作者立即删除!由于本人水平有限,如有理解或者描述不准确的地方,还望各位大佬指教!! 练习网站: Q3JhenkgUHJvTW9ua2V5IGh0dHBzOi8vd3d3LnBhbnpob3UuZ292LmNuL3p3Z2tfMTU4NjEve

    2024年02月07日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包