Learn Learn Android Reverse

news/2025/2/7 20:16:05/文章来源:https://www.cnblogs.com/Un1corn/p/18615567

安卓基础

NDK开发

jni调用

  1. 什么是jni?

    jni是Java Native lnterface的缩写。从java1.1开始,jni标准成为Java平台的一部分,允许Java代码和其他语言写的代码进行交互

  2. GetStringUTFChars();将java字符串转换为c字符串.java的字符串在虚拟机中,通过硬编码调用,cstring在内存中.

  3. env->functions->NewstringUTF(env, Result);将c字符串转化为java字符串返回

  4. 这写都是hook的关键位置,例如直接将返回的字符串输出

  5. jni静态注册规则,而且跟java对接的函数会有两个默认的参数

      Java_com_example_myapplication_MainActivity_stringFromJNI(JNIEnv* env, jobject /* this */)
    
  6. jobject与jclass区别:

    jobject在动态声明时使用如public native String stringFromJNI();

    jclass在静态声明时使用如public static native String stringFromJNI();

  7. extern "C" JNIEXPORT jstring JNICALL的含义

    • extern "C"表示这个函数要用C的标准实现

    • JNIEXPORT的实现

      #define JNIEXPORT  __attribute__ ((visibility ("default")))
      

      如果设置为hidden,则此函数不会出现在export table中,对外部不可见,就不能被jni调用

    • jstring是返回值类型,返回一个jstring类型

so加固

  1. SHT table规定了so文件中的一些内容从什么位置取,ida解析时与运行时调用可能不同,导致解析失败
    img
  2. 运行时dump,fix
    • 工具:SoFixer(readme中有使用方法,包含了dump与fix):https://github.com/F8LEFT/SoFixer
  3. 工具不适用的情况

    自定义Linker,不按照原先标准so文件结构来写和调用自己的so文件

NDK介绍

  1. 什么是NDK?

    一种交叉编译工具链,可以在pc端编译Android的程序,并且需要cmakelist指导进行build.
    使用:https://developer.android.com/ndk/guides?hl=zh-cn

  2. so中的输出:__android_log_print(ANDROID_LOG_DEBUG,"tag","mes");

  3. so输出函数的封装

      #include <android/log.h>#define TAG "my_tag"#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG,__VA_ARGS__)#define LOGD(...) __android_log_print(ANDROID_LOG_INFO, TAG,__VA_ARGS__)#define LOGD(...) __android_log_print(ANDROID_LOG_ERROR, TAG,__VA_ARGS__)
    

    ...对应__VA_ARGS__表示可变参数

NDK多线程

  1. 创建多线程

      int pthread_create(pthread_t* _Nonnull __pthread_ptr, pthread_attr_t const* _Nullable __attr, void* _Nonnull (* _Nonnull __start_routine)(void* _Nonnull), void* _Nullable);
    

    其中的pthread_t其实就是long,注意这个*,我们应该传入一个phread_t的地址,二四两个参数都穿nullptr就行,第三个参数传入一个函数的地址

      pthread_create(&thread_pid, nullptr, reinterpret_cast<void *(*)(void *)>(p_fun), nullptr);
    
  2. 等待进程执行结束

      pthread_join(thread_pid, nullptr);
    

JNI_OnLoad

  1. so中各种函数的执行时机

    • 顺序: init,initarray,JNI_OnLoad
    • 当so被加载(System.loadLibrary("my____");)时就会执行JNI_Onload,而不是调用so中函数时执行
    • 返回值为jint,需要返回版本号
  2. JNIEnv是一个结构体,里面有很多函数,可以实现java与c的交互,一般是so中使用java的函数.通过JavaVm *vm可以获得JNIEnv

      JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {LOGD("this is from JNI_OnLoad");JNIEnv *env = nullptr;if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {LOGD("GetEnv fail");}return JNI_VERSION_1_6;}
    

JavaVm

  1. JavaVm是一个结构体(typdef _JavaVm JavaVM),里面有很多函数
    • GetEnv()函数在主线程获得JNIEnv
    • jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)在子线程中获取JNIEnv
  2. JavaVm分为c版本(JNIInvokeInterface)和c++版本
    • c++版本其实就是封装了c语言的版本,区别是第一个参数JavaVm*被封装用this指针默认调用了
    • 由上面一点就可以知道,看到调用c++的函数名,但是进入后发现多传了一个参数(一般是第一个参数)
  3. JavaVM的获取方式
    • JNI_OnLoad的第一个参数
    • JNI_OnLoad的第一个参数
    • env->GetJavaVM
  4. JavaVm每个线程只有一个

JNIEnv

  1. 与JavaVm一样也分为c与c++版本,c++封装c,省略调用JavaEnv*
  2. JNIEnv也是一个结构体,有很多可用def(后续说)
  3. JNIEnv的获取方式
    • 函数静态/动态注册,传的第一参数
    • vm->GetEnv适用于主线程
    • globalVM->AttachCurrentThread,这个适用于子线程,并且子线程调用vm->GetEnv会报错
  4. JavaEnv每个进程就有一个

各种表的相关概念

  1. 导入表(import)导出表(export)符号表
  2. 存在import和export表的都可用frida获得地址,如果没有就需要手动计算(base_addr+off)函数地址,因为so层的hook都需要得到地址

so函数注册

  1. 静态注册
    • 命名规则:c的函数必须遵循Java_包名_类名_方法名
    • 编译时并不与函数绑定,当加载so后第一次调用函数,才会根据命名规则去找这个函数
    • 静态注册JNI函数必然在导出表中.
  2. 动态注册
    • 获得类名,注意不再用.而用/写路径

      jclass MainActivityClazz = env->FindClass("com/example/myapplication/MainActivity");
      
    • 创建对应关系(JNINativeMethod)

      typedef struct {const char* name;const char* signature;void*       fnPtr;
      } JNINativeMethod;
      

      参数为java的函数名字,签名,c函数地址

      • 签名:显示函数的参数,返回值;"(参数)返回值"
    • 注册函数,注意如果一个函数注册了多次,以最后一次为准

      env->RegisterNatives(MainActivityClazz,methods,sizeof (methods)/sizeof (JNINativeMethod));
      

      step2只是找到了函数名与函数名之间的对应关系,但是不同的类可以有同名函数,所以注册的时候需要将method对应的类也指定,第三个参数为注册的函数个数

so路径动态获取

  1. 安装好apk后,so会存在与data/app/packname-xxxxxxx,后面的是随机的.32和64的so存放路径不一样,为了更加通用,可以用代码动态获取so路径

      public String getPath(Context cxt){PackageManager pm = cxt.getPackageManager(); //创建包管理器List<PackageInfo> pkgList = pm.getInstalledPackages(0);  //将所有安装的apk转换为PackageInfo形式if (pkgList == null || pkgList.size() == 0) return null;for (PackageInfo pi : pkgList) {//遍历pkgList,找到当前的nativeLibraryDirif (pi.applicationInfo.nativeLibraryDir.startsWith("/data/app/") && pi.packageName.startsWith("com.xiaojianbang.demo")) {//Log.e("xiaojianbang", pi.applicationInfo.nativeLibraryDir);return pi.applicationInfo.nativeLibraryDir;}}return null;}
    

so相互调用(从次开始,直接copy ppt)

使用dlopen、dlsym、dlclose获取函数地址,然后调用。需要导入dlfcn.h

void *soinfo = dlopen(nativePath, RTLD_NOW);
void (*def)(char* str) = nullptr;
def = reinterpret_cast<void (*)(char *)>(dlsym(soinfo, "_Z7fromSoBPc"));
def("xiaojianbang");

通过jni创建Java对象

通过jni访问Java属性

通过jni访问Java数组

通过jni访问Java方法

通过jni访问Java父类方法

内存管理

子线程中获取Java类

init与initarray

so逆向

so逆向分析

  1. 防止重新打包

    //获得当前签名,如果与原签名不同,则被重新打包
    sha1 = getSha1(env,context);
    strcmp(sha1,app_sha1)

  2. so中用env->functions->GetMethodID调用的java函数,也会在java层被hook

  3. hook函数可以获取修改参数,获取修改返回值,替换函数

  4. frida的Java层hook和so层hook,环境配置是一样的

  5. so层hook只需要得到函数地址

    • 通过frida提供的api来得到,该函数必须有符号(存在于导入表,导出表,符号表中均可)的才可以
    • 通过计算得到地址:so基址+函数在so中的偏移[+1]

Frida枚举各种表,modules

  1. 通过枚举导入表,可以得到出现在导入表中的函数地址

      var imports = Module.enumerateImports("libxiaojianbang.so");for(var i = 0; i < imports.length; i++){if(imports[i].name == "strncat"){console.log(JSON.stringify(imports[i]));console.log(imports[i].address);break; }}
    
  2. 通过枚举导出表,可以得到出现在导出表中的函数地址

      var exports = Module.enumerateExports("libxiaojianbang.so");for(var i = 0; i < exports.length; i++){console.log(JSON.stringify(exports[i]));}
    
  3. 通过枚举符号表,可以得到出现在符号表中的函数地址

      Module.enumerateSymbols("libencryptlib.so")
    

Frida

Base

  1. 运行frida-server

      evergo:/data/local/tmp # ./frida-server
    
  2. 查看info

    frida-ps -Uai
    
    • frida-ps:这是 Frida 工具中的一个命令,用于列出目标设备(如 Android)的运行进程信息。
    • -U:表示针对通过 USB 连接的设备(包括物理设备或模拟器)列出进程。
    • -a:显示所有进程信息,而不仅仅是当前用户拥有的进程。
    • -i:详细显示每个进程的信息,包括进程 ID(PID)、进程名等。
  3. 可以用grep找到想要的程序

      frida-ps -Uai | grep '<name_of_application>'
    
  4. 得到包名后attach,之后就可以在终端输入js代码进行注入

      frida -U -f <package_name>
    
    • -f <package_name>:强制启动并附加到指定包名对应的应用程序。

Hooking a method

  1. template
    Java.perform(function() {

      var <class_reference> = Java.use("<package_name>.<class>");<class_reference>.<method_to_hook>.implementation = function(<args>) {/*OUR OWN IMPLEMENTATION OF THE METHOD*/}})
    
    • Java.perform 是 Frida 中的一个函数,用于为脚本创建一个特殊上下文,以便与 Android 应用程序中的 Java 代码进行交互。这就像打开一扇门来访问和操纵应用程序内部运行的 Java 代码。

    • var <class_reference> = Java.use("<package_name>.<class>");
      

      在这里,您声明一个变量 <class_reference> 来表示目标 Android 应用程序中的 Java 类。您可以使用 Java.use 函数指定要使用的类,该函数将类名作为参数。<package_name> 表示 Android 应用程序的包名称, 表示您要与之交互的类。

    • <class_reference>.<method_to_hook>.implementation = function(<args>) {}
      

      在所选类中,您可以使用 <class_reference>.<method_to_hook> 符号访问要挂接的方法,从而指定该方法。您可以在此处定义挂接方法被调用时要执行的逻辑。 表示传递给函数的参数。不用传参就什么都不填.

  2. 部分method是一启动程序就会运行,所有需要在启动前注入

      frida -U -f com.ad2001.frida0x1 -l .\script.js
    
    • 无参数函数hook

      Java.perform(function() {var MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");MainActivity.get_random.implementation = function() {console.log("hook!!!!")//return 5;       //修改返回值var ret_val = this.get_random();    //获得返回值console.log("The return value is " + ret_val);}
      })
      
    • 有参数hook

      处理带有参数的挂钩方法时,使用overload(arg_type)关键字指定预期的参数类型非常重要。

      Java.perform(function() {var a = Java.use("com.ad2001.frida0x1.MainActivity");
      a.check.overload('int', 'int').implementation = function(a, b) { // The function takes two arguments - check(random, input)console.log("The random number is " + a);console.log("The user input is " + b);//上面只是hook了函数,得到了他的参数,但是并没有运行原函数.this.check(a,b);
      }
      })
      
    • 调用类的静态函数

      需要在程序启动后使用,不能在启动时直接调用js文件

      Java.perform(function (){var MainActivity = Java.use("com.ad2001.frida0x2.MainActivity");MainActivity.get_flag(4919);
      })
      
    • 调用未实例非静态类中的函数

      template

      Java.perform(function() {var <class_reference> = Java.use("<package_name>.<class>");var <class_instance> = <class_reference>.$new(); // Class Object<class_instance>.<method>(); // Calling the method
      })
      

      运用

      Java.perform(function (){var check = Java.use("com.ad2001.frida0x4.Check");var cn = check.$new()var flag = cn.get_flag(1337)console.log(flag)
      })
      
    • 调用已实例非静态类中函数(先启动后,在终端输入执行)

      template

      Java.performNow(function() {
      Java.choose('<Package>.<class_Name>', {onMatch: function(instance) {// TODO},onComplete: function() {}
      });
      });
      
      • onMatch
        • 对于 Java.choose 操作期间找到的指定类的每个实例,都会执行 onMatch 回调函数。
        • 此回调函数接收当前实例作为其参数。
        • 您可以在 onMatch 回调中定义要在每个实例上执行的自定义操作。
        • function(instance) {},instance 参数表示目标类的每个匹配实例。您可以使用任何其他您想要的名称。
      • onComplete
        • 在 Java.choose 操作完成后,onComplete 回调执行操作或清理任务。此块是可选的,如果您在搜索完成后不需要执行任何特定操作,您可以选择将其留空。

      运用

      Java.performNow(function (){Java.choose('com.ad2001.frida0x5.MainActivity',{onMatch: function (instance){console.log("hook!!!");instance.flag(1337);},onComplete:function (){}})
      })
      
  3. 修改类中数据的value

    template:

      Java.perform(function (){var <class_reference> = Java.use("<package_name>.<class>");<class_reference>.<variable>.value = <value>;})
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/880203.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2016蓝桥杯省赛B组

2016蓝桥杯省赛B组 1.煤球数目#include<bits/stdc++.h>using namespace std;int dp[200];int main(){ int sum=0; for(int i=1;i<=100;i++){ dp[i]=dp[i-1]+i; sum+=dp[i]; } cout<<sum; return 0;} 思路: 每一层煤球个数 0+1-&g…

css3手册

布局浮动:做文字环绕效果 弹性盒:单行或单列布局 网格:多行多列布局 弹性盒详细文档见MDN 弹性盒小游戏生成弹性容器和弹性项目默认情况下,弹性项目沿着主轴依次排列,侧轴拉伸 更改方向 通过flex-direction可更改主轴方向主轴排列 通过justify-content属性,可以影响主轴的…

CNN

具体在第五次汇报卷积神经网络是多层感知机(MLP)的变种。卷积神经网络(Convolutional Neural Networks)是一种包含卷积计算且具有深度结构的前馈神经网络,CNN具有表征学习的能力,能够按阶层对输入数据进行平移不变分类。CNN的设计灵感来源于动物视觉系统分级处理信息的能力…

LSTM(Long Short-Term Memory)长短时记忆结构

随着RNN在长序列处理中的应用深入,发现了其难以捕捉长距离依赖的问题。LSTM是传统RNN的变体,与经典RNN相比能够有效捕捉长序列之间的语义关联,通过引入了“记忆单元”(Memory Cell)和“门控机制”来控制信息的流动,解决了标准RNN中梯度消失和梯度爆炸的问题。其核心结构可…

MDB2PPT_v2.0 mdb数据转换为PPT

前言: 本软件原名“DataToPowerPoint”,已有将近20年的历史,目的是方便将mdb数据库中的数据导出为ppt进行学习或讲座。2007-9-29曾发布于“网上读书园地”,原先为共享软件,未注册版本有功能限制,如:至多输出30条数据、不能使用高级功能。近期对软件界面的布局等作了一些…

2.7 完成剩余表

今天完成了作业中所有表的建立,还剩余历史查询功能计划明天完成所有内容,并进行测试 今天还通过视频进一步学习了javaweb的知识

RNN

一.RNN介绍在学习LSTM之前,得先学习RNN。RNN实际上就是一个带有记忆的时间序列的预测模型。RNN的基本结构包括输入层、隐藏层和输出层。在RNN中,输入序列被分成多个时间步,每一个时间步都对应于序列中的一个元素。每个时间步更新一个隐藏状态(Hidden State),该状态不仅接…

每日练习 25.2.7

Guess the K-th Zero (Hard version) 题目 这是一个交互问题。 这是问题的困难版本。与简单版不同的是,在困难版中,查询次数为 \(1 \le t \le \min(n, 10^4)\),查询总数限制为 \(6 \cdot 10^4\)。 波利卡普正在玩一个电脑游戏。在这个游戏中,一个由 \(0\) 和 \(1\) 组成的数…

树上邻域理论(树上圆理论) 小记

邻域:记 \(f(u, r)\) 表示距离 \(u\) 不超过 \(r\) 的点组成的邻域。令 \(x, y\) 为点集 \(S\) 中两个距离最远的点,设 \(u\) 为 \(x, y\) 中点(可能是一条边的中心),设 \(d\) 为 \(x, y\) 的距离,那么覆盖 \(S\) 的最小邻域为 \(f(u, \frac d2)\)。邻域 \(f(u_1, r_1)\)…

Docker搭建Jenkins并共用宿主机Docker部署服务(一)搭建Jenkins及插件配置 -转载

前言 公司项目多忙着开发,所有项目服务都是博主一个个部署的,时间久了也是心累,所以抽时间把Jenkins部署上,之后让其他开发人员自己部署(让我解脱吧!!)。 部署Jenkins容器 Docker安装就不在赘述了,可以看我之前的文章(懒了);直接开始拉取jenkins镜像。 拉取镜像 docker…

Adam优化器、其与策略梯度法结合

一.Adam优化器旨在根据历史梯度信息来调整每个参数的学习率,从而实现更高效的网络训练。Adam算法的核心思想是同时计算梯度的一阶矩(均值)和二阶矩(未中心的方差)的指数移动平均,并对它们进行偏差校正,以确保在训练初期时梯度估计不会偏向于0。Adam优化器是一种梯度下降…

ES6-3 Babel转码器

Babel是一个广泛使用的ES6转码器,可以将ES6代码转为ES5代码,从而在老版本的浏览器执行。这意味着你可以用ES6的方式编写程序,又不用担心现有的环境是否支持浏览器支持性查看:https://caniuse.com/Babel官网:https://babeljs.io/ 1、转码示例原始代码用了箭头函数,Babel将…