面向对象编程–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与进程交互
- 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);
};
});
- 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}`);
}
});
- 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() {}
});
- 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}`);
});
- 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');
- 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}`);
- 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}`);
- 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);
- 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"));
}
});
- console日志输出
console.log("Info message"); // 信息日志
console.warn("Warning!"); // 警告日志
console.error("Error!"); // 错误日志
console.debug("Debug info"); // 调试日志
- setimmediate/settimeout 定时操作
// 立即执行
setImmediate(() => {
console.log("This runs immediately after script load");
});
// 延迟执行
setTimeout(() => {
console.log("This runs after 2 seconds");
}, 2000);
- recv/send 进程通信
// 脚本中接收消息
recv("command", (data) => {
console.log("Received command:", data.payload);
send({ result: "success" });
});
// Python 端发送消息
script.post({"type": "command", "payload": "start_monitoring"})
- frida核心对象
Frida.heapSize; // 获取堆大小
Frida.arch; // 当前架构
Frida.ptrSize; // 指针大小
- stalker指令级跟踪
// 跟踪指令流
Stalker.follow(threadId, {
events: {
call: true, // 跟踪调用指令
ret: false // 不跟踪返回指令
},
onReceive: function(events) {
// 处理事件
}
});
// 停止跟踪
Stalker.unfollow(threadId);
- 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查看
- 获得androidroot权限
- 把tcpdump推送到android系d统
adb push /本地路径/tcpdump /data/local/tcpdump
- 修改tcpdump权限
adb shell chmod 6755 /data/local/tcpdump
- 进入root权限
adb shell
su
- 运行抓包
/data/local/tcpdump -p -vv -s 0 -w /sdcard/tcp_capture.pcap
- tcp连接操作,启动wireshark打开文件,筛选端口,三握手四挥手,查看seq,ack这些
- 结束抓包,导出
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
对抗:
- frida-sever重命名为系统进程名,放在非标准目录,用非标准端口
- 魔改版frida:HLUDA/strongR-frida(去除了frida源码特征)
- hook关键函数
从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()的作用主要有几点:
- 告诉JVM,这个库需要要求使用的JNI版本是什么
- 执行初始化操作
- 将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) |
|---|---|---|
| boolean | jboolean | 8, unsigned |
| byte | jbyte | 8 |
| char | jchar | 16, unsigned |
| short | jshort | 16 |
| int | jint | 32 |
| long | jlong | 64 |
| float | jfloat | 32 |
| double | jdouble | 64 |
| void | void | n/a |
| Java类型 | C/C++类型 |
|---|---|
| byte | jbyte(等价于char) |
| short | jshort(等价于short) |
| int | jint(等价于int) |
| long | jlong(等价于long long) |
| float | jfloat(等价于float) |
| double | jdouble(等价于double) |
| boolean | jboolean(等价于unsigned char,0表示false,非0表示true) |
| Object | jobject(指向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函数和类引用
追踪安卓方法调用
smali
dalvik的寄存器语言(art相当于它的升级版),smali代码由dex翻译而来
关键字
| 名称 | 注释 |
|---|---|
| .class | 类名 |
| .super | 父类名,继承的上级类名名称 |
| .source | 源名 |
| .field | 变量 |
| .method | 方法名 |
| .register | 寄存器 |
| .end method | 方法名的结束 |
| public | 公有 |
| protected | 半公开,只有同一家人才能用 |
| private | 私有,只能自己使用 |
| .parameter | 方法参数 |
| .prologue | 方法开始 |
| .line xxx | 位于第xxx行 |
| 数据类型对应 |
| Smali类型 | Java类型 | 注释 |
|---|---|---|
| V | void | 无返回值 |
| Z | boolean | 布尔值类型,返回0或1 |
| B | byte | 字节类型,返回字节 |
| S | short | 短整数类型,返回数字 |
| C | char | 字符类型,返回字符 |
| I | int | 整数类型,返回数字 |
| J | long (64位 需要2个寄存器存储) | 长整数类型,返回数字 |
| F | float | 单浮点类型,返回数字 |
| D | double (64位 需要2个寄存器存储) | 双浮点类型,返回数字 |
| string | String | 文本类型,返回字符串 |
| Lxxx/xxx/xxx | object | 对象类型,返回对象 |
| 常用指令 |
| 关键字 | 注释 |
|---|---|
| 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:推送数据
连接管理(生命周期)
三次握手
- 客户机进入SYN_SENT状态,syn标志位被设置为1,带上client分配好的sn序列号(时间产生随机值,通常情况下每间隔4ms会加1)
- Server端收到SYN帧,进入SYN_RCVD状态,同时返回SYN+ACK帧给Client,通知,ack标志位设置为1,an为client的sn+1,syn+ack的syn标志位为1
- 收到第二次握手确认帧后,将状态SYN_SENT变成ESTABLISHED,客户机开始发数据,然后发ack帧给server,ack标志位为1,确认序号为server端sn+1。
- server收到ack帧后,从SYN_RCVD状态会进入ESTABLISHED状态。tcp全双工连接建立完成
四次挥手
- Client发送FIN帧,FIN标志位设置为1,带上当前的序列号sn,进入 FIN_WAIT_1 状态
- Server收到FIN帧,回复ACK帧,ACK标志位设置为1,确认序号an为Client的sn+1。Server进入 CLOSE_WAIT 状态,客户机收到ack后,进入FIN_WAIT_2 状态
- Server等待数据发送完毕后,发送FIN帧,FIN标志位设置为1,带上Server当前的序列号sn,进入 LAST_ACK 状态
- Client收到FIN帧,回复ACK帧,ACK标志位设置为1,确认序号an为Server的sn+1。Client进入 TIME_WAIT 状态,收到ack帧后关闭。










吓哭了(ฅ´ω`ฅ)
被玉米国王吓哭了