简单安卓逆向学习
本文最后更新于52 天前,其中的信息可能已经过时,如有错误请发送邮件到2624241828@qq.com

面向对象编程–java

简单概念
java菜鸟教程

jadx使用

文章

快捷键功能说明
Ctrl + Shift + F全局文本搜索最常用的功能,搜索所有代码中的字符串或文本。
Ctrl + N全局类名搜索快速查找并打开特定的类。
Ctrl + F当前页面搜索在当前打开的文件中查找文本。
X查找用法 (Find Usage)选中方法/字段/类,按 X 查看哪些地方引用了它(逆向分析神器)。
D跳转到声明选中变量或方法调用,跳转到定义它的位置(等同于 Ctrl + Click)。
N重命名选中混淆的变量/方法/类(如 a, b, func),按 N 修改为可读名字。

frida

分析.so级别的hook,对指定进程的so模块进行分析,拦截和调用指定函数1.插桩技术

插桩技术是指将额外的代码注入程序中以收集运行时的信息,可分为两种:

(1)源代码插桩Source Code Instrumentation(SCI):额外代码注入到程序源代码中。

(2)二进制插桩(Binary Instrumentation):额外代码注入到二进制可执行文件中。

基本命令

用法: frida [选项] 目标  

位置参数:  
  args                  额外参数和/或目标  

选项:  
  -h, --help            显示此帮助信息并退出  
  -D ID, --device ID    连接到指定 ID 的设备  
  -U, --usb             连接到 USB 设备  
  -R, --remote          连接到远程 frida-server  
  -H HOST, --host HOST  连接到 HOST 上的远程 frida-server  
  --certificate CERTIFICATE  
                        与 HOST 使用 TLS 通信,并验证提供的 CERTIFICATE(证书)  
  --origin ORIGIN       连接到远程服务器时,设置“Origin”请求头为 ORIGIN  
  --token TOKEN         使用 TOKEN 向 HOST 进行身份验证  
  --keepalive-interval INTERVAL  
                        设置保活间隔(秒),0 表示禁用(默认为 -1,根据传输方式自动选择)  
  --p2p                 与目标建立点对点(peer-to-peer)连接  
  --stun-server ADDRESS  
                        使用 --p2p 时,设置要使用的 STUN 服务器地址 ADDRESS  
  --relay address,username,password,turn-{udp,tcp,tls}  
                        为 --p2p 添加中继(relay)服务器  
  -f TARGET, --file TARGET  
                        生成(spawn)文件 TARGET(通常是可执行文件)  
  -F, --attach-frontmost  
                        附加到最前端应用程序  
  -n NAME, --attach-name NAME  
                        附加到名称为 NAME 的进程  
  -N IDENTIFIER, --attach-identifier IDENTIFIER  
                        附加到标识符为 IDENTIFIER 的进程(如 bundle ID)  
  -p PID, --attach-pid PID  
                        附加到进程 ID 为 PID 的进程  
  -W PATTERN, --await PATTERN  
                        等待匹配 PATTERN 的进程生成  
  --stdio {inherit,pipe}  
                        生成进程时的标准输入/输出行为(默认为“inherit”继承)  
  --aux option          设置生成进程时的辅助选项(aux option),例如 “uid=(int)42”(支持的类型:string, bool, int)  
  --realm {native,emulated}  
                        要附加到的执行域(原生域 / 模拟域)  
  --runtime {qjs,v8}    要使用的脚本运行时引擎  
  --debug               启用 Node.js 兼容的脚本调试器  
  --squelch-crash       如果启用,不会将崩溃报告输出到控制台  
  -O FILE, --options-file FILE  
                        包含额外命令行选项的文本文件  
  --version             显示程序版本号并退出  
  -l SCRIPT, --load SCRIPT  
                        加载脚本文件 SCRIPT  
  -P PARAMETERS_JSON, --parameters PARAMETERS_JSON  
                        以 JSON 格式提供参数(与 Gadget 相同)  
  -C USER_CMODULE, --cmodule USER_CMODULE  
                        加载 C 模块(CModule) USER_CMODULE  
  --toolchain {any,internal,external}  
                        从源代码编译时使用的 CModule 工具链  
  -c CODESHARE_URI, --codeshare CODESHARE_URI  
                        加载代码共享 URI CODESHARE_URI  
  -e CODE, --eval CODE  执行(evaluate)代码片段 CODE  
  -q                    安静模式(无提示符),并在执行完 -l 和 -e 后退出  
  -t TIMEOUT, --timeout TIMEOUT  
                        在安静模式下,等待 TIMEOUT 秒后终止  
  --pause               在生成程序后,保持主线程暂停状态  
  -o LOGFILE, --output LOGFILE  
                        输出到日志文件 LOGFILE  
  --eternalize          在退出前持久化脚本(eternalize the script)  
  --exit-on-error       在 SCRIPT 中遇到任何异常后以代码 1 退出  
  --kill-on-exit        Frida 退出时杀死生成的程序  
  --auto-perform        自动将输入的代码包装在 `Java.perform` 中(用于 Java 交互)  
  --auto-reload         启用对提供的脚本和 C 模块的自动重载(默认开启,未来版本将变为必需)  
  --no-auto-reload      禁用对提供的脚本和 C 模块的自动重载

操作模式

frida提供了两种操作模式来注入流程,attach附加和spawn生成

  • attach:附加到已经运行的进程frida -U -p <> -l script.js
  • spawn:启动并暂停目标进程,注入脚本后继续执行

脚本

frida脚本使用javascript api与进程交互

  1. java对象
// 关键方法  
Java.perform(() => { /* 安全执行Java操作 */ });  
Java.choose("com.example.Class", { /* 查找实例 */ });  
Java.use("com.example.Class") // 获取类引用 等同于JAVA中的Class.forName() 反射调用  
//如果是需要加载内部类的化需要使用'$'符引用例如  
java.use("com.example.Class$InnerClass")  
Java.enumerateLoadedClasses() // 枚举已加载类  

// 示例:Hook Activity.onCreate  
Java.perform(() => {  
  const Activity = Java.use("android.app.Activity");  
  Activity.onCreate.implementation = function(bundle) {  
    console.log("Activity created!");  
    this.onCreate(bundle);  
  };  
});
  1. interceptor函数拦截
// 关键方法  
Interceptor.attach(targetAddress, { /* 回调 */ });  
Interceptor.replace(targetAddress, replacementFunc);  
Interceptor.detachAll();  

// 示例:拦截 libc 的 open 函数  
const openPtr = Module.getExportByName(null, "open");  
Interceptor.attach(openPtr, {  
  onEnter: function(args) {  
    this.path = args[0].readUtf8String();  
    console.log(`Opening file: ${this.path}`);  
  },  
  onLeave: function(retval) {  
    console.log(`Returned fd: ${retval}`);  
  }  
});
  1. module模块操作
// 关键方法  
Module.load("libtarget.so"); // 动态加载  
Module.findBaseAddress("libtarget.so"); // 查找基址  
Module.enumerateImports("libtarget.so"); // 枚举导入  
Module.findExportByName("libc.so", "printf"); // 查找导出  

// 示例:扫描模块导出  
Module.enumerateExports("libart.so", {  
  onMatch: function(exp) {  
    if (exp.name.indexOf("JNI") !== -1) {  
      console.log(`Found JNI export: ${exp.name}`);  
    }  
  },  
  onComplete: function() {}  
});
  1. process进程控制
// 关键方法  
Process.id; // 当前进程ID  
Process.arch; // 架构 arm/arm64/x64  
Process.enumerateModules(); // 枚举模块  
Process.enumerateThreads(); // 枚举线程  
Process.getCurrentThreadId(); // 获取当前线程ID  

// 示例:枚举所有模块  
Process.enumerateModules().forEach(mod => {  
  console.log(`Module: ${mod.name} Base: ${mod.base}`);  
});
  1. memory内存操作
// 关键方法  
Memory.alloc(size); // 分配内存  
Memory.protect(address, size, protection); // 修改保护  
Memory.scan(address, size, pattern, callbacks); // 内存扫描  
Memory.readByteArray(address, length); // 读取字节数组  

// 示例:读取字符串  
const strPtr = ptr(0x1234);  
console.log(`String value: ${strPtr.readUtf8String()}`);  

// 示例:修改内存保护  
Memory.protect(ptr(0x4000), 4096, 'rwx');
  1. ptr指针操作
// 关键方法  
ptr("0x1234"); // 创建指针  
ptr.add(offset); // 指针加法  
ptr.sub(offset); // 指针减法  

// 示例:指针运算  
const base = Module.findBaseAddress("libtarget.so");  
const targetFunc = base.add(0x1234);  
console.log(`Target function at: ${targetFunc}`);
  1. nativefunction调用原生函数
// 创建原生函数指针  
const openPtr = Module.getExportByName(null, "open");  
const open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);  

// 调用原生函数  
const path = Memory.allocUtf8String("/etc/passwd");  
const fd = open(path, 0);  
console.log(`File descriptor: ${fd}`);
  1. nativecallback创建回调函数
// 创建回调函数  
const callback = new NativeCallback(function(arg1, arg2) {  
  console.log(`Callback called with: ${arg1}, ${arg2}`);  
  return 0;  
}, 'int', ['int', 'int']);  

// 设置回调  
const setCallback = Module.getExportByName("libtarget.so", "set_callback");  
setCallback(callback);
  1. thread线程操作
// 关键方法  
Thread.backtrace(context); // 获取调用栈  
Thread.sleep(seconds); // 线程休眠  

// 示例:获取当前线程调用栈  
Interceptor.attach(targetFunc, {  
  onEnter: function(args) {  
    console.log("Backtrace:\n" +   
      Thread.backtrace(this.context).map(DebugSymbol.fromAddress).join("\n"));  
  }  
});
  1. console日志输出
console.log("Info message"); // 信息日志  
console.warn("Warning!"); // 警告日志  
console.error("Error!"); // 错误日志  
console.debug("Debug info"); // 调试日志
  1. setimmediate/settimeout 定时操作
// 立即执行  
setImmediate(() => {  
  console.log("This runs immediately after script load");  
});  

// 延迟执行  
setTimeout(() => {  
  console.log("This runs after 2 seconds");  
}, 2000);
  1. recv/send 进程通信
// 脚本中接收消息  
recv("command", (data) => {  
  console.log("Received command:", data.payload);  
  send({ result: "success" });  
});  

// Python 端发送消息  
script.post({"type": "command", "payload": "start_monitoring"})
  1. frida核心对象
Frida.heapSize; // 获取堆大小  
Frida.arch; // 当前架构  
Frida.ptrSize; // 指针大小
  1. stalker指令级跟踪
// 跟踪指令流  
Stalker.follow(threadId, {  
  events: {  
    call: true, // 跟踪调用指令  
    ret: false  // 不跟踪返回指令  
  },  
  onReceive: function(events) {  
    // 处理事件  
  }  
});  

// 停止跟踪  
Stalker.unfollow(threadId);
  1. file文件操作
// 读写文件  
const data = File.readAllBytes("/path/to/file");  
File.writeAllBytes("/path/to/output", new Uint8Array([0x01, 0x02]));

python接口

获取进程,模块,函数信息

js接口

官方详细
文章:frida入门 入门,一个大概括,喜欢
hook框架深入全面

安卓抓包

文章
遇到socket编程,无法从log日志中查看到与之通讯的socket发送和返回的数据包是什么,结合tcpdump和wireshark查看

  1. 获得androidroot权限
  2. 把tcpdump推送到android系d统
adb push /本地路径/tcpdump /data/local/tcpdump
  1. 修改tcpdump权限
adb shell chmod 6755 /data/local/tcpdump
  1. 进入root权限
adb shell
su
  1. 运行抓包
/data/local/tcpdump -p -vv -s 0 -w /sdcard/tcp_capture.pcap
  1. tcp连接操作,启动wireshark打开文件,筛选端口,三握手四挥手,查看seq,ack这些
  2. 结束抓包,导出
adb pull /sdcard/tcp_capture.pcap /本地路径

apk结构

  • assets目录:静态资源存放,如视频图片音频
  • lib目录:有主流手机用的arm64-v8a和通用的armeabi-v7a,本次题目是arm64-v8a,64位,其下.so文件时动态链接库文件
  • meta-inf目录;应用签名信息
  • res目录;resource,存放资源文件,包括图片,字符串等
  • Androidmanifest.xml文件:应用清单信息
  • classes.dex文件:java源码编译生成的java字节码文件,apk运行的主要逻辑(补充字节码文件是啥!)
  • resources.arsc文件:编译后的二进制资源文件,映射表,映射资源和ida,通过r文件中的id可以找到对应资源

快手系

搜索__NS_sig3 字段的时候,看到的文章基本在破解快手系,去了解。
算法: 各种反签名算法快手6.8sig3算法破解
相关:__NS_sig3逆向分析(没用jadx,用的啥看不出)
nssig3算法

KWSecuritySDK

在搜索__NS_sig3字段的时候,顺便搜了下注释,看到个,升级或者接入KWSecuritySDK:3.9.1.4+版本,于是去对应搜索此保护和破解原理

保护可能性

混淆

  • 控制流平坦化(OLLVM变体,虚假控制流Bogus Control Flow,指令替换)
  • 字符串动态加密
ollvm
  • cff:最大标志,把原本的循环放入case
  • bcf:不会执行的代码块,像垃圾代码
  • sub:让汇编更复杂

动态

  • 对抗动态:针对frida,xposed检测常规端口和进程名,扫描内存特征(gadgets),检测zygisk
  • svc/syscall直接调用:内联汇编(?)直接系统调用
  • crc/checksum:自校验

环境

  • 设备id

通信协议

  • 签名参数:如sig等前面参数字段被引入随机因子或时间戳
  • 白盒加密:白盒aes(末尾堆着解析)

破解

文章相关
web滥用SSL TLS绕过
一个信息,关于cve
绕过libmsaoaidsec检测frida
frida教程加对抗思路
别人的hook脚本,包含字符串比较绕过,lua解密监控
安卓fridaAPI
对抗:

  1. frida-sever重命名为系统进程名,放在非标准目录,用非标准端口
  2. 魔改版frida:HLUDA/strongR-frida(去除了frida源码特征)
  3. hook关键函数
    从4这后面具体怎的搞得真看不懂了,复制
  4. 使用frida的stalker,svc系统调用,hook检测完整性校验函数(jni_onload(这个后面有解析)或者.init_array)

NDK

[简略理解参考文章](https://www.geeksforgeeks.org/android/what-is-ndk-in-android/ https://developer.android.google.cn/ndk/guides?hl=en)
让你可以用c语言代码在安卓使用
map结构,ndk生成std::map
map学习

java调用native方法流程

JNI_OnLoad()

JNI_OnLoad()的作用主要有几点:

  1. 告诉JVM,这个库需要要求使用的JNI版本是什么
  2. 执行初始化操作
  3. 将JavaVM参数保存为全局对象,方便以后在任何地方获取JNIEnv对象
    clazz:指定的类,即 native 方法所属的类
    methods:方法数组,这里需要了解一下 JNINativeMethod 结构体
    nMethods:方法数组的长度
    native方法调用连接java层和本地c/c++关键,Android Runtime(ART)作为Android系统的核心运行环境,其Native方法调用链路涉及Java虚拟机、JNI(Java Native Interface)、本地方法注册、栈帧管理等多个复杂环节。(参考文章
    在java中,native方法通过native关键字进行声明
public class NativeExample {
    // 声明一个Native方法,该方法无参数且返回int类型
    public native int nativeAdd(int a, int b); 
    static {
        // 加载包含Native方法实现的动态库
        System.loadLibrary("native-lib"); 
    }
}

此处仅声明,具体逻辑通过System.loadLibrary加载动态库(那个lib),动态库包含对应native方法实现,System.loadLibrary会调用art动态库,art通过dlopen系统调用(linux)下面打开指定动态库,映射到进程的地址空间中,会进行,符号解析(查找native的实现函数,如动态库依赖其他库的函数,art会递归–当ida一样的,也是递归流程,但ida补充了一个线性,这个之后查详细的–加载并解析),初始化钩子函数(执行动态库中的初始化函数,如JNI_OnLoad,可用于注册native方法,设置JNI版本等操作)如果动态库中定义了JNI_OnLoad函数,ART会在库加载完成后自动调用它的操作,本地注册。
注册:将指定native和so对应映射,分为静态注册和动态注册两种,默认静态

  • 静态注册:通过JNIEXPORT和JNICALL两个宏定义声明方法,命名规则为java+包名+类名+方法名
// Java native method
public native String stringFromJNI();
// JNI method 
JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_MainActivity_stringFromJNI( JNIEnv *env, jobject instance);
  • 动态注册:通过RegisterNatives完成native方法和so的绑定
    Java调用System.loadLibrary()加载一个库的时候,会首先在库中搜索JNI_OnLoad()函数,如果该函数存在,则执行它

javaVM与JNIEnv在调用链路

javaVM

java虚拟机全局入口,当本地线程需要调用java方法或者执行native方法时,首先要通过javaVM的AttackCurrentThread函数将线程附加到java虚拟机,获取与之关联的jnienv,也负责Java虚拟机的生命周期(如启动、关闭)、类加载、内存分配等核心功能,确保Native方法调用过程中Java运行环境的稳定。

JNIEnv

提供接口
结构体,线程相关的本地接口环境,提供函数指针,用于本地代码中操作java对象,调用方法。复制粘贴别人的例子!例如,通过NewObject函数创建Java对象,CallObjectMethod调用Java实例方法,GetFieldID获取Java对象字段ID等。

两者的协同工作机制

JavaVM与JNIEnv在Native方法调用链路中紧密协作。JavaVM负责线程与虚拟机的连接管理,而JNIEnv则专注于线程级别的Java对象操作与方法调用。当本地线程调用Native方法时,JavaVM先通过AttachCurrentThread为其分配JNIEnv实例,随后本地代码利用该实例调用JNI接口完成与Java层的交互。调用结束后,若不再需要JNIEnv实例,可通过JavaVM的DetachCurrentThread函数将线程从虚拟机分离,释放相关资源。这种分工模式确保了多线程环境下Native方法调用的安全性与高效性。

java栈帧和本地栈帧区别

在方法调用过程中,java栈帧由局部变量表,操作数栈,动态连接信息,方法返回地址等部分组成,存储方法调用过程中的临时数据和控制信息,当java方法调用native方法时,需要进行栈帧的切换与数据传递,art会适配java栈帧和本地栈帧(保存java栈帧状态,创建本地栈帧,跳转至本地函数,恢复java栈帧),还有栈溢出处理,限制栈大小,检查边界(栈指针是否越界),处理异常(art抛出stackoverflowerror,java通过try-catch来捕获)

参数传递和数据类型转换

java与jni的基本类型映射

Java类型本地类型字节(bit)
booleanjboolean8, unsigned
bytejbyte8
charjchar16, unsigned
shortjshort16
intjint32
longjlong64
floatjfloat32
doublejdouble64
voidvoidn/a
Java类型C/C++类型
bytejbyte(等价于char
shortjshort(等价于short
intjint(等价于int
longjlong(等价于long long
floatjfloat(等价于float
doublejdouble(等价于double
booleanjboolean(等价于unsigned char,0表示false,非0表示true
Objectjobject(指向Java对象的指针)
文章参考:[java与jni的基本类型映射](“https://blog.csdn.net/weixin_39997400/article/details/111559142?ops_request_misc=%257B%2522request%255Fid%2522%253A%25224481e6103410a895dcf2f547f4dbb183%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=4481e6103410a895dcf2f547f4dbb183&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-4-111559142-null-null.142^v102^pc_search_result_base4&utm_term=Java%E4%B8%8E%E6%9C%AC%E5%9C%B0C%2FC%2B%2B%E7%9A%84%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E9%9C%80%E8%A6%81%E8%BF%9B%E8%A1%8C%E6%98%A0%E5%B0%84%E8%BD%AC%E6%8D%A2%E3%80%81&spm=1018.2226.3001.4187 “)

java到native调用转换原理

在Android runtime(art)中,java到native的调用转换实现了java与c的代码交互,包含以下步骤文章

方法查找和解析

方法查找流程

java调用native方法时,要在运行中查找对应的本地方法。在art/runtime/jni_env.cc中,CallVoidMethod等JNI调用接口会触发方法查找

本地方法解析

解析具体实现,了解核心逻辑
尝试静态注册动态注册查找

jni环境准备

jnienv结构初始化

jnienv时一个指向jni函数表的指针,为本地代码访问java虚拟机提供接口

局部引用表管理

管理本地代码中创建的java对象引用,确保java对象被正常调用

参数传递与转换

基本数据类型传递(int,float等)

直接进行值传递

对象引用传递

通过jni接口操作引用

//处理对象类型参数传递
void SetupObjectArg(uint8_t* args, jobject obj) {
    // 将Java对象引用转换为JNI引用
    if (obj == nullptr) {
        *reinterpret_cast<jobject*>(args) = nullptr;
    } else {
        // 创建局部引用
        Thread* self = Thread::Current();
        JNIEnvExt* env = self->GetJniEnv();
        jobject local_ref = env->NewLocalRef(obj);
        *reinterpret_cast<jobject*>(args) = local_ref;
    }
}

字符串与数组处理

需要专门转换逻辑,在art/runtime/jni_string.cc和art/runtime/jni_array.cc中,定义了字符串和数组的处理逻辑

// 将Java字符串转换为C字符串
const char* JNIEnvExt::GetStringUTFChars(jstring string, jboolean* isCopy) {
    if (string == nullptr) {
        return nullptr;
    }

    // 获取Java字符串对象
    mirror::String* str = Decode<mirror::String*>(string);

    // 转换为UTF-8编码
    std::string utf8_str = str->ToModifiedUtf8();

    // 分配内存并复制字符串
    char* result = static_cast<char*>(malloc(utf8_str.length() + 1));
    if (result == nullptr) {
        return nullptr;
    }

    memcpy(result, utf8_str.c_str(), utf8_str.length() + 1);

    // 记录分配的内存,以便后续释放
    AddPendingLocalFree(result);

    if (isCopy != nullptr) {
        *isCopy = JNI_TRUE;  // 总是返回副本
    }

    return result;
}

// 释放C字符串
void JNIEnvExt::ReleaseStringUTFChars(jstring string, const char* utf) {
    // 查找并释放之前分配的内存
    RemovePendingLocalFree(const_cast<char*>(utf));
    free(const_cast<char*>(utf));
}

栈帧转换与调用

栈帧结构

在art/runtime/stack_frame.h,定义了栈帧的基本结构

class StackFrame {
 public:
    // 栈帧类型
    enum class Type {
        kFrameInterpreter,    // 解释器栈帧
        kFrameQuick,          // 快速编译栈帧
        kFrameJni,            // JNI栈帧
        kFrameJniTransition,  // JNI转换栈帧
        //...其他类型
    };

    // 获取栈帧类型
    Type GetType() const;

    // 获取栈帧大小
    size_t GetSize() const;

    // 获取调用者栈帧
    StackFrame* GetCaller() const;

    //...其他方法
};

栈帧转换过程

java到native调用需要进行栈帧转换,art/runtime/jni_bridge.cc

// 执行JNI调用
void JniBridge(mirror::ArtMethod* method, uint32_t* args, JValue* result) {
    Thread* self = Thread::Current();
    JNIEnvExt* env = self->GetJniEnv();

    // 保存当前Java栈状态
    StackHandleScope<1> hs(self);
    Handle<mirror::Object> receiver(hs.NewHandle(
        reinterpret_cast<mirror::Object*>(args[0])));

    // 创建JNI调用栈帧
    JniTransition jni_transition(self);

    // 获取本地方法指针
    void* native_method = method->GetEntryPointFromQuickCompiledCode();

    // 准备调用参数
    uint8_t* native_args = jni_transition.GetNativeArgumentBuffer();
    SetupArgs(method, native_args, args);

    // 调用本地方法
    typedef void (*NativeMethod)(JNIEnv*, jobject, ...);
    NativeMethod native_fn = reinterpret_cast<NativeMethod>(native_method);

    // 根据返回类型调用不同的函数
    switch (method->GetReturnType()) {
        case Primitive::kPrimVoid:
            native_fn(env, receiver.Get(), native_args);
            result->SetVoid();
            break;
        case Primitive::kPrimBoolean:
            result->SetZ(native_fn(env, receiver.Get(), native_args));
            break;
        case Primitive::kPrimByte:
            result->SetB(native_fn(env, receiver.Get(), native_args));
            break;
        //...其他返回类型处理
    }

    // 恢复Java栈状态
    jni_transition.Finish();
}

调用约定适配

不同架构有不同调用约定,art需要适配,在art/runtime/arch目录下

// ARM架构的JNI调用桥接函数
void art_quick_generic_jni_trampoline(uint32_t* args, JValue* result, size_t args_size) {
    // 获取方法和JNI环境
    mirror::ArtMethod* method = reinterpret_cast<mirror::ArtMethod*>(args[0]);
    Thread* self = Thread::Current();
    JNIEnvExt* env = self->GetJniEnv();

    // 提取参数
    uint8_t* native_args = &args[1];

    // 获取本地方法指针
    void* native_method = method->GetEntryPointFromQuickCompiledCode();

    // 调用本地方法
    typedef void (*NativeMethod)(JNIEnv*, jobject, ...);
    NativeMethod native_fn = reinterpret_cast<NativeMethod>(native_method);

    // 根据返回类型调用不同的函数
    //...
}

本地方法执行

本地方法入口

本地方法通过JNI函数表提供的接口访问Java虚拟机功能。在art/runtime/jni_env_ext.cc中

// 初始化JNI函数表
void JNIEnvExt::InitFunctions() {
    functions_ = &gNativeInterface;

    // 设置JNI函数表的各个函数指针
    functions_->GetVersion = &JNIEnvExt::GetVersion;
    functions_->DefineClass = &JNIEnvExt::DefineClass;
    functions_->FindClass = &JNIEnvExt::FindClass;
    //...其他函数
}

jni接口实现

art/runtime/jni目录下

本地方法调用java代码

在art/runtime/jni_env_ext.cc中,定义

返回值处理

基础数据类型

直接本地返回转成java类型

对象引用返回

本地引用转换为java对象引用

缓存和预取

art预存了常用jni函数和类引用

追踪安卓方法调用

https://www.youncyb.cn/?p=1214

smali

dalvik的寄存器语言(art相当于它的升级版),smali代码由dex翻译而来
关键字

名称注释
.class类名
.super父类名,继承的上级类名名称
.source源名
.field变量
.method方法名
.register寄存器
.end method方法名的结束
public公有
protected半公开,只有同一家人才能用
private私有,只能自己使用
.parameter方法参数
.prologue方法开始
.line xxx位于第xxx行
数据类型对应
Smali类型Java类型注释
Vvoid无返回值
Zboolean布尔值类型,返回0或1
Bbyte字节类型,返回字节
Sshort短整数类型,返回数字
Cchar字符类型,返回字符
Iint整数类型,返回数字
Jlong (64位 需要2个寄存器存储)长整数类型,返回数字
Ffloat单浮点类型,返回数字
Ddouble (64位 需要2个寄存器存储)双浮点类型,返回数字
stringString文本类型,返回字符串
Lxxx/xxx/xxxobject对象类型,返回对象
常用指令
关键字注释
const重写整数属性,真假属性内容,只能是数字类型
const-string重写字符串内容
const-wide重写长整数类型,多用于修改到期时间。
return返回指令
if-eq全称equal(a=b),比较寄存器ab内容,相同则跳
if-ne全称not equal(a!=b),ab内容不相同则跳
if-eqz全称equal zero(a=0),z即是0的标记,a等于0则跳
if-nez全称not equal zero(a!=0),a不等于0则跳
if-ge全称greater equal(a>=b),a大于或等于则跳
if-le全称little equal(a<=b),a小于或等于则跳
goto强制跳到指定位置
switch分支跳转,一般会有多个分支线,并根据指令跳转到适当位置
iget获取寄存器数据

零碎知识

crc

循环冗余校验(CRC)是用于检测数据损坏的错误检测码。发送数据时,会根据数据内容生成简短的校验和,并将其与数据一起发送。接收数据时,将再次生成校验和并将其与发送的校验和进行比较。如果两者相等,则没有数据损坏。所述CRC-32算法本身转换可变长度字符串转换成8个字符的字符串。

app的盐值

通常在密码哈希里面使用,添加盐值,是每个密码又度的的哈希值,可以防止彩虹表攻击和哈希碰撞,
创建和应用

  #创建
  import os
  salt = os.urandom(16)
  #应用
  import hashlib
  hashed_password=hashlib.sha256(salt+password.encode()).hexdigest()

md5算法

MD5加密算法,其全称是Message-Digest Algorithm 5,通常被称为信息摘要算法,所谓的信息摘要就是把明文内容按一定规则生成一段哈希(hash)值,即得到这段明文内容的信息摘要。利用MD5可以基于任意长度的明文字符串生成128位的哈希值,结果唯一且不可逆,因此MD5经常被用于防止信息被篡改、数字签名、以及对明文进行加密等场景。 KwaiSign sig3 = getSig3(value, strEncodedPath);

白盒aes

合并key和sbox,让密钥成为结构一部分,加入随机双射变换
解决:DCA,BGE,unicorn模拟

序列化和反序列化

Java序列化:就是指把Java对象转换为字节序列的过程
Java反序列化:就是指把字节序列恢复为Java对象的过程。
序列化:在传递和保存对象时.保证对象的完整性和可传递性。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。
反序列化:根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。

json/xml的数据传递

在数据传输(也可称为网络传输)前,先通过序列化工具类将Java对象序列化为json/xml文件。
在数据传输(也可称为网络传输)后,再将json/xml文件反序列化为对应语言的对象

实现序列化和反序列化的三种实现

Student类实现了Serializable接口

ObjectOutputStream采用默认的序列化方式,对Student对象的非transient的实例变量进行序列化。
ObjcetInputStream采用默认的反序列化方式,对Student对象的非transient的实例变量进行反序列化。

Student类实现了Serializable接口,并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out)。

ObjectOutputStream调用Student对象的writeObject(ObjectOutputStream out)的方法进行序列化。
ObjectInputStream会调用Student对象的readObject(ObjectInputStream in)的方法进行反序列化。

Student类实现了Externalnalizable接口,且Student类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法

ObjectOutputStream调用Student对象的writeExternal(ObjectOutput out))的方法进行序列化。
ObjectInputStream会调用Student对象的readExternal(ObjectInput in)的方法进行反序列化。

Java中的序列化与反序列化实现

Java 提供了内置的序列化工具,使对象能方便地转换为字节流。主要工具包括ObjectOutputStream和ObjectInputStream,以及Serializable和Externalizable接口。

1. Serializable接口

标识对象可序列化
要实现序列化的对象类需要实现Serializable接口。标记接口。

2. Externalizable接口

自定义序列化逻辑

  • writeExternal(ObjectOutput out):定义如何序列化对象。
  • readExternal(ObjectInput in):定义如何反序列化对象。

3.ObjectOutputStream类和ObjectInputStream类

读写字节流的对象

  • ObjectOutputStream:执行序列化。
  • ObjectInputStream:反序列化。

4. transient关键字

保护敏感数据
在某些场景中,可能不希望某些字段被序列化,例如密码、敏感信息等。可以用transient关键字声明这些字段,以忽略序列化。

RPC远程过程调用协议

文章
允许程序调用另一个机器或者同一机器的另一个进程的过程协议

tcp协议

文章(分层模型,osi也讲了)
面向连接的,基于字节流的传输层通信协议
头部通常20字节,包含端口,seq(序列号),ack(确认号),标志位(flags)
flags

  • syn:发起连接
  • ack:确认收到
  • rst:重置连接
  • fin:结束连接
  • psh:推送数据

连接管理(生命周期)

三次握手

  1. 客户机进入SYN_SENT状态,syn标志位被设置为1,带上client分配好的sn序列号(时间产生随机值,通常情况下每间隔4ms会加1)
  2. Server端收到SYN帧,进入SYN_RCVD状态,同时返回SYN+ACK帧给Client,通知,ack标志位设置为1,an为client的sn+1,syn+ack的syn标志位为1
  3. 收到第二次握手确认帧后,将状态SYN_SENT变成ESTABLISHED,客户机开始发数据,然后发ack帧给server,ack标志位为1,确认序号为server端sn+1。
  4. server收到ack帧后,从SYN_RCVD状态会进入ESTABLISHED状态。tcp全双工连接建立完成

四次挥手

  1. Client发送FIN帧,FIN标志位设置为1,带上当前的序列号sn,进入 FIN_WAIT_1 状态
  2. Server收到FIN帧,回复ACK帧,ACK标志位设置为1,确认序号an为Client的sn+1。Server进入 CLOSE_WAIT 状态,客户机收到ack后,进入FIN_WAIT_2 状态
  3. Server等待数据发送完毕后,发送FIN帧,FIN标志位设置为1,带上Server当前的序列号sn,进入 LAST_ACK 状态
  4. Client收到FIN帧,回复ACK帧,ACK标志位设置为1,确认序号an为Server的sn+1。Client进入 TIME_WAIT 状态,收到ack帧后关闭。
文末附加内容

评论

  1. p0pc003n
    Windows Edge
    6 月前
    2025-12-17 13:58:40

    吓哭了(ฅ´ω`ฅ)

    • 博主
      p0pc003n
      Windows Chrome
      6 月前
      2025-12-22 16:07:37

      被玉米国王吓哭了

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇