Android 逆向/反编译/Hook修改应用行为 基础实现
前言:本文通过一个简单的情景案例实现安卓逆向的基本操作
一、情景描述
本文通过一个简单的情景案例来实现安卓逆向的基本操作。在这个案例中所使用的项目程序是我自己的Demo程序,不会造成任何的财产侵害,本文仅作为日常记录及案例分享。
实现步骤大致如下:
反编译APK获取源码 → 源码分析 → 目标设备运行Frida服务(需要root权限) → 使用Python编写hook注入程序 / 编写hook脚本 → 执行程序
二、前置准备
整个流程中需要做的准备工作大致有以下几点:
1. 反编译工具
当前流行的反编译工具有许多种,例如:APKTool、JADX、JD-GUI 等等。反编译的主要目的是查看app的源码分析app的运作流程从而快速制定出所需的hook脚本,因此挑选一款适合自己的即可。不过需要注意的是:并非所有的apk包都能够反编译成功,虽然技术上大多数 APK 文件都能被反编译,但反编译的难度和完整性可能会因应用的保护措施而有所不同。具体来说,有些 APK 文件通过加固、混淆、加密等手段提高了反编译的难度,甚至可能在某些情况下完全避免了反编译的效果,由于我的案例使用的是自己的源码所以不需要考虑这一步,那就以 JADX 作为案例吧。
安装JADX
直接到github仓库下载打好的包即可,可以直接下载带UI的版本,简单易用,不过要注意系统已经有了JRE环境,如果没有的话也可以下载下方的带jre版本 https://github.com/skylot/jadx/releases
反编译APK包
直接把文件拖拽到框内即可
2. Python环境
需要用到 Python 进行脚本程序编写及运行
Python 环境安装:直接官网下载傻瓜式一键安装即可 https://www.python.org/downloads/
PyCharm 编辑器安装:同上,下载社区版(Community)就够了 https://www.jetbrains.com.cn/en-us/pycharm/download/?section=windows
3. 安装 Frida
Frida 有两部分,frida-tools以python程序为载体运行在本地并执行hook脚本,frida-server则需要在目标设备端运行并进行动态分析和hook操作
安装 frida-tools:安装frida-tools需要用到pip因此先确保python环境安装成功并设置好环境变量。在命令提示符中直接输入以下指令等待安装完成即可:pip install frida-tools
下载完成后输入指令判断是否安装成功:frida --version
如果成功返回了版本号则表示安装成功了,记住这个版本号后续还要用到
下载 frida-server:直接到github仓库下载打好的包即可 https://github.com/frida/frida/releases
注意下载的版本要frida-tools相匹配,另外还需要注意的是server的系统平台和cpu架构版本也要选对,他的命名规则是:frida-server-版本号-支持系统-CPU架构.扩展名,我需要在用到Android系统上使用因此我把对应的所有android server包都下载下来
4. 具有 root 权限的设备
由于现在的厂商真机获取 root 权限越来越困难,使用真机调试的成本较高因此选择模拟器作为android端设备载体,我在案例中使用的是夜神模拟器,直接官网安装即可 https://www.yeshen.com/ 安装完成后在设置中开启root权限即可
三、运行 hook 程序,修改应用行为
1. 启动 frida-server
启动frida-server前要先将server文件导入目标设备的 /data/local/tmp/ 目录中,执行命令修改可执行权限并运行,这里需要注意的一点是要确保目标设备的cpu架构与server文件相对应,夜神模拟器的cpu架构是x86_64因此我们选用这个即可
解压server文件
选择之前下载好的server压缩包文件并解压
导入/启动 server 文件
直接在路径栏中输入"cmd"回车进入当前命令提示符
依次执行以下指令:
输入 "adb devices" 指令查询当前已连接设备确定模拟器设备已经连接成功
输入 "adb push frida-server-16.5.7-android-x86_64 /data/local/tmp/" 指令将 frida-server 导入tmp目录,注意这里的要根据你的server文件名修改一下
等待导入完成,输入 "adb shell" 进入设备指令操作
输入 "su" 切换root权限
输入 "cd /data/local/tmp/" 进入tmp目录
输入 "ls -l" 查询当前目录下的文件信息,此时我们可以看到刚才导入进去的server文件,他的特征为rw-并没有可执行权限
输入 "chmod 777 frida-server-16.5.7-android-x86_64" 设置用户权限开启可执行
再次输入 "ls -l" 查询当前目录下的文件信息,此时我们可以看到文件特征变为rwx,可以执行
输入 "./frida-server-16.5.7-android-x86_64" 启动 frida-server,启动后光标换行没有任何提升,表示已经启动成功 server 正在运行中,注意不要关闭提示符窗口
2. 验证 frida 连接
打开 PyCharm,新建py文件,导入frida包,根据app包名启动指定进程,这一步先做连接验证,不注入hook脚本
import frida
import sysdef on_message(message, data):"""回调函数,处理 Frida 发回的消息。"""if message['type'] == 'send':print(f"[+] {message['payload']}")elif message['type'] == 'error':print(f"[!] {message['stack']}")def main():print("执行任务")try:print(f"[!] {frida.__version__}")# 连接到 Android 设备device = frida.get_usb_device(timeout=5)# 查找目标进程pid = device.spawn(["com.lxt.single_module"]) # 注意,这里要改为你需要hook的程序包名session = device.attach(pid)# 加载 Hook 脚本# script = session.create_script(HOOK_SCRIPT_1)# script.on('message', on_message) # 设置消息回调# script.load()# 恢复进程device.resume(pid)print("[*] Hook 脚本已加载,应用正在运行...")# 保持脚本运行状态sys.stdin.read()except Exception as e:print(f"[!] 错误: {e}")if __name__ == "__main__":main()
运行程序观察日志及模拟器,程序运行之后模拟器会自动调起对应的程序,如果日志中没有报错或退出则表示连接成功
3. hook 改变方法执行效果
改变一个简单的方法返回数据
假设我在 MainActivity 中有个简单的方法,该方法固定返回一个boolean类型数据"true"
fun testBool():Boolean{return true}
设置某个测试按键的点击事件为log这个方法返回的结果
viewBinding.test5.onClick {Log.e(TAG, "click result:${testBool()}")
}
点击结果
那么这时我们可以编写一个简单的hook脚本去注入修改这个方法的返回数据
创建Java.perform方法并在作用域中实现需要执行的脚本细节,Java.perform 是 Frida 提供的一个用于在 Java 虚拟机中执行代码的方法。它确保在 Java 环境完全加载并且可以安全地访问 Java 类和方法后执行给定的回调函数。所有的 Java hooking 操作都需要在这个回调内进行
Java.perform(function() {}
我们需要hook的方法是在MainActivity中,因此我们要做的第一个操作就是获取到这个对象的引用,我们可以使用Java.use()方法来获取。通过Java.use()拿到的引用你可以访问该类的方法和字段或者修改这些方法的实现,但前提是必须要知道这个需要加载的类的完整路径,这个路径我们可以通过反编译的源码中获取
var MainActivity = Java.use("com.lxt.single_module.Activity.MainActivity");
拿到 MainActivity 中 testBool 方法的引用,强制其返回一个"false"
MainActivity.testBool.implementation = function() {console.log("testBool 被 Hook,强制返回 false");// 强制返回 falsereturn false;
};
完整代码
import frida
import sysHOOK_SCRIPT_1 = """
Java.perform(function() {var MainActivity = Java.use("com.lxt.single_module.Activity.MainActivity");// Hook MainActivity 中的 testBool 方法MainActivity.testBool.implementation = function() {console.log("testBool 被 Hook,强制返回 false");// 强制返回 falsereturn false;};});
"""# Python 代码
def on_message(message, data):"""回调函数,处理 Frida 发回的消息。"""if message['type'] == 'send':print(f"[+] {message['payload']}")elif message['type'] == 'error':print(f"[!] {message['stack']}")def main():print("执行任务1")try:print(f"[!] {frida.__version__}")# 连接到 Android 设备device = frida.get_usb_device(timeout=5)# 查找目标进程pid = device.spawn(["com.lxt.single_module"])session = device.attach(pid)# 加载 Hook 脚本script = session.create_script(HOOK_SCRIPT_1)script.on('message', on_message) # 设置消息回调script.load()# 恢复进程device.resume(pid)print("[*] Hook 脚本已加载,应用正在运行...")# 保持脚本运行状态sys.stdin.read()except Exception as e:print(f"[!] 错误: {e}")if __name__ == "__main__":main()
执行程序注入hook脚本,此时再次点击测试按键就会触发hook方法
观察日志hook方法被触发,app中的testBool方法返回结果为false
改变应用行为
这里我们用一个相对较接近实际应用的情景作为案例吧
假设:我们需要在MainActivity中请求某个api再根据api的结果决定是否需要跳转到另一个指定页面
如果:我们需要绕过这个api,让程序无需根据api的请求结果而必定跳转到这个指定的页面
那么:我们可以直接hook这个请求api的方法让它必定返回一个可以让页面跳转的结果
代码情景:
点击测试按键后使用ViewModel调取请求方法,再根据请求方法返回的结果判断是否需要执行页面跳转
viewBinding.test4.onClick {lifecycleScope.launch {var login_result = viewModel.testLogin()Log.e(TAG,"request result:${login_result}")if(login_result){Log.e(TAG, "登陆成功")startActivity(Intent(this@MainActivity, SucceedActivity::class.java))}else{Log.e(TAG, "登陆失败")}}
}
正常执行结果
根据方法的执行流程去编写 hook 脚本
在这个流程中最为关键的一步就是需要根据ViewModel请求方法的结果判断是否需要进行页面跳转,那么我们只要在ViewModel对象初始化之后拿到它的引用再hook它的请求方法,让这个方法的返回结果必定为true就好了
在我的Activity框架中ViewModel是在名为"initData"的抽象方法中初始化,并且在子类实现时必须要调用super.initData()在基类中执行反射自动初始化
override fun initData() {super.initData() // 在父类初始化 viewModel// 执行其它任务rxPermissions = RxPermissions(this)check_permission()registerScreenListen()
}
由此可知:我们只需要获取到MainActivity的initData方法引用,并在执行它原本的super.initData()方法之后拿到已经初始化的ViewModel对象引用并修改请求api的方法让它必定返回一个true
拿到initData方法引用并执行它的的super方法
MainActivity.initData.implementation = function() {console.log("initData 被 Hook");// 调用原始的 initData 方法this.initData();
};
在执行完super方法后通过 Java.choose() 方法来获取已实例化的ViewModel对象,Java.choose 方法可以用于遍历已加载的类的所有实例,并执行指定的回调函数。
Java.choose("com.lxt.single_module.Activity.MainActivity", {onMatch: function(instance) {var viewModel = instance.viewModel.value;},onComplete: function() {}
});
获取到ViewModel对象之后修改 testLogin 方法为必定返回 true
if (viewModel) {console.log("成功获取到 viewModel");// 检查 viewModel 是否为 MainVM 的实例if (viewModel.$className === "com.lxt.single_module.ViewModel.MainVM") {console.log("viewModel 是 MainVM 的实例");// Hook testLogin 方法MainVM.testLogin.implementation = function() {console.log("testLogin 被 Hook,返回 true");return Java.use("java.lang.Boolean").valueOf(true);;};} else {console.log("viewModel 不是 MainVM 的实例");}
} else {console.log("viewModel 为 null");
}
完整代码
import frida
import sysHOOK_SCRIPT_1 = """
Java.perform(function() {var MainActivity = Java.use("com.lxt.single_module.Activity.MainActivity");var MainVM = Java.use("com.lxt.single_module.ViewModel.MainVM"); // 请确保这是正确的包名和类名MainActivity.initData.implementation = function() {console.log("initData 被 Hook");// 调用原始的 initData 方法this.initData();Java.choose("com.lxt.single_module.Activity.MainActivity", {onMatch: function(instance) {var viewModel = instance.viewModel.value;if (viewModel) {console.log("成功获取到 viewModel");// 检查 viewModel 是否为 MainVM 的实例if (viewModel.$className === "com.lxt.single_module.ViewModel.MainVM") {console.log("viewModel 是 MainVM 的实例");// Hook testLogin 方法MainVM.testLogin.implementation = function() {console.log("testLogin 被 Hook,返回 true");return Java.use("java.lang.Boolean").valueOf(true);;};} else {console.log("viewModel 不是 MainVM 的实例");}} else {console.log("viewModel 为 null");}},onComplete: function() {}});};// Hook MainActivity 中的 testBool 方法MainActivity.testBool.implementation = function() {console.log("testBool 被 Hook,强制返回 false");// 强制返回 falsereturn false;};});
"""# Python 代码
def on_message(message, data):"""回调函数,处理 Frida 发回的消息。"""if message['type'] == 'send':print(f"[+] {message['payload']}")elif message['type'] == 'error':print(f"[!] {message['stack']}")def main():print("执行任务")try:print(f"[!] {frida.__version__}")# 连接到 Android 设备device = frida.get_usb_device(timeout=5)# 查找目标进程pid = device.spawn(["com.lxt.single_module"])session = device.attach(pid)# 加载 Hook 脚本script = session.create_script(HOOK_SCRIPT_1)script.on('message', on_message) # 设置消息回调script.load()# 恢复进程device.resume(pid)print("[*] Hook 脚本已加载,应用正在运行...")# 保持脚本运行状态sys.stdin.read()except Exception as e:print(f"[!] 错误: {e}")if __name__ == "__main__":main()
再次运行脚本并点击测试按键
触发脚本,绕过api请求步骤执行页面跳转
相关文章:
Android 逆向/反编译/Hook修改应用行为 基础实现
前言:本文通过一个简单的情景案例实现安卓逆向的基本操作 一、情景描述 本文通过一个简单的情景案例来实现安卓逆向的基本操作。在这个案例中所使用的项目程序是我自己的Demo程序,不会造成任何的财产侵害,本文仅作为日常记录及案例分享。实…...
【前端】理解 JavaScript 对象属性访问的复杂性
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 💯前言💯理论基础:JavaScript 对象属性的访问模式1. 点符号访问(Dot Notation)2. 方括号访问(Bracket Notation)点符号…...
进入 Dystopia:第九周游戏指南
本指南将为大家详细说明在第八周的每个体验中可以获得的奖励。 在杂草丛生的反乌托邦废墟中生存,随着大自然重新开垦这片土地,文明已陷入绝望。穿越高耸入云、摇摇欲坠的摩天大楼,抵御末世社会的各种危险。适应这个文明与荒野之间的界限已经消…...
Helm安装Mysql8主从复制集群
目录 一、Helm安装 二、安装mysql 1、拉取镜像 2、修改配置文件 3、创建mysql-secret 4、安装 一、Helm安装 这里不再赘叙,具体安装请参考官网 Helm | 快速入门指南 二、安装mysql 1、拉取镜像 #添加仓库 helm repo add bitnami https://charts.bitnami.c…...
[小白系列]GPU-nvidia-smi指令
nvidia-smi(NVIDIA System Management Interface)是一种命令行实用程序,用于监控和管理NVIDIA GPU(图形处理器)的状态和性能。它提供了一种简单而强大的方式来获取有关GPU的实时信息,并且可以用于诊断、…...
Flutter 图片编辑板(一) 事件路由
一个图片编辑板,有两部分组成。编辑板和内容项。每一个内容项是被InteractiveViewer修饰的widget,具有缩放偏移的功能。 在图片编辑板上, 会有多个内容相,图片或文字(添加文字目前还没做过)。 当要编辑其中…...
Cherno C++学习笔记 P33 字符串的字面量
在这篇文章当中我们介绍一下有关于字符串更加深入的知识,也就是字符串的字面量。 首先什么是字面量?其实也很简单,就是双引号里面的那一坨,其实就是字面量,我们举一个最简单的例子: #include<iostream…...
数据结构 (36)各种排序方法的综合比较
一、常见排序方法分类 插入排序类 直接插入排序:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。希尔排序:是插入排序的一种改进版本,先将整个待排序的记录序列分割成为…...
Master EDI 项目需求分析
Master Electronics 通过其全球分销网络,支持多种采购需求,确保能够为客户提供可靠的元件供应链解决方案,同时为快速高效的与全球伙伴建立合作,Master 选择通过EDI来实现与交易伙伴间的数据传输。 EDI为交易伙伴之间建立了一个安…...
java+ssm+mysql计算机等级考试网
项目介绍: 使用javassmmysql开发的计算机等级考试信息网,系统包含前后台,包含超级管理员,系统管理员角色,功能如下: 前台:首页;考试动态;相关资源下载;考试…...
MitelMiCollab 身份绕过导致任意文件读取漏洞复现(CVE-2024-41713)
0x01 产品描述: Mitel MiCollab 是一个企业协作平台,它将各种通信工具整合到一个应用程序中,提供语音和视频通话、消息传递、状态信息、音频会议、移动支持和团队协作功能。0x02 漏洞描述: Mitel MiCollab 的 NuPoint 统一消息 (NPM) 组件中存在身份验证绕过漏洞,由于输入…...
【Python]深入Python日志管理:从logging到分布式日志追踪的完整指南
《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 日志是软件开发中的核心部分,尤其在分布式系统中,日志对于调试和问题定位至关重要。本篇文章将从Python标准库的logging模块出发,逐步探讨日志管理的最佳实践,涵盖日志配置、日志分层、日志格式化等基…...
python rstrip 的迷惑行为
在项目中,我需要把字符串末尾的一部分去掉,类似截断,我用ide的随笔提示,发现了rstrip这个方法,然后我试了下,满足我的需求,但在测试过程中,我发现了rstrip的一些行为很让我迷惑。 开…...
SPI通信协议
SPI通信协议 简介通信原理通信原理SPI数据通信的流程可以分为以下几步:通信特性设备时钟时钟速率时钟极性跟相位SPI协议层通讯流程详解优点:缺点: DS1302 时钟实验控制寄存器日历、时钟寄存器寄存器说明 DS1302 读写时序软件功能实现 简介 SP…...
vue中如何实现商品多规格添加(后台商城管理系统)
在制作商城类的后台管理系统中会遇到多规格商品的添加,需要向固定的数组内添加,通过查看数据结构正确的向数组中添加数据。 如图: 功能需求:1.每次点击添加新规格时,批量设置会多出来一行表格和一个标题输入框。 最…...
智创 AI 新视界 -- AIGC 重塑广告行业的创新力量(16 - 7)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...
Runloop
假设你的项目中有关tableView,然后还有一个定时器timer在执行,定时器代码如下: var num 0override func viewDidLoad() {super.viewDidLoad()let timer Timer(timeInterval: 1,target: self,selector: #selector(self.run),userInfo: nil,r…...
代码随想录第40天
121. 买卖股票的最佳时机 class Solution:def maxProfit(self, prices: List[int]) -> int:cost, profit float(inf), 0for price in prices:cost min(cost, price)profit max(profit, price - cost)return profit122.买卖股票的最佳时机II class Solution:def maxPr…...
element plus table组件多选获取数据id
首先给table加上 selection-change"handleSelectionChange"事件 示例 <el-table selection-change"handleSelectionChange" stripe:data"data?.slice((currentPage3 - 1) * pageSize3, currentPage3 * pageSize3)" style"width: 100%…...
自动驾驶:百年演进
亲爱的小伙伴们😘,在求知的漫漫旅途中,若你对深度学习的奥秘、JAVA 、PYTHON与SAP 的奇妙世界,亦或是读研论文的撰写攻略有所探寻🧐,那不妨给我一个小小的关注吧🥰。我会精心筹备,在…...
STM32 了解OLED
内容扩展 调试方式串口调试:通过串口调试,将调试信息发送到电脑端,电脑使用串口助手显示调试信息 显示屏调试:直接将显示屏连接到单片机,将调试信息打印到显示屏上 keil调试模式:借助Keil软件的调试模式&a…...
NanoLog起步笔记-7-log解压过程初探
nonolog起步笔记-6-log解压过程初探 再看解压过程建立调试工程修改makefile添加新的launch项 注:重新学习nanolog的README.mdPost-Execution Log Decompressor 下面我们尝试了解,解压的过程,是如何得到文件头部的meta信息的。 再看解压过程 …...
python连接redis详细步骤
1.下载并安装windows python Window 平台安装 Python: 以下为在 Window 平台上安装 Python 的简单步骤。 打开 WEB 浏览器访问 Python Releases for Windows | Python.org : 记得勾选 Add Python 3.6 to PATH。 在cmd 执行pip install redis 2.编辑python代码…...
使用redis 的stream 做消息中间件 多线程消费消息
1.redis stream 特点 1.支持消息持久化 2.消费者组模式 3.消息确认机制 4. 消息重试机制 5. 死信队列2. 消息生产者服务 2.1 如下代码Service Slf4j public class StreamMessageProducer {Autowiredprivate StringRedisTemplate redisTemplate;private static final String S…...
《操作系统 - 清华大学》6 -5:局部页面置换算法:最不常用置换算法 (LFU, Least Frequently Used)
文章目录 1. 最不常用算法的工作原理2.最不常用算法特征3. 示例 1. 最不常用算法的工作原理 最不常用算法:注意并不是表示算法本身不常用,而是采取最不常使用页面的策略,Least Frequently Used, LFU。LRU 是最久未被访问的页&…...
GWAS分析先做后学
大家好,我是邓飞。 GWAS分析是生物信息和统计学的交叉学科,上可以学习编程,下可以学习统计。对于Linux系统,R语言,作图,统计学,机器学习等方向,都是一个极好的入门项目。生物信息如…...
【threejs】创建FPS相机
原理说明 控制器是一个很麻烦的东西,需要创建更多的类来管理相机行为,并且可自定义性差,所以将部分控制器的功能绑定到相机上,可以解决这些问题,所以我以 FlyControls为例,将控制器功能绑定到相机上&#…...
路径规划之启发式算法之十一:布谷鸟搜索算法(Cuckoo Search,CS)
布谷鸟搜索算法(Cuckoo Search,CS)是一种新兴的自然启发式算法,由剑桥大学的杨新社教授和S.戴布(Xin-She Yang和Suash Deb)于2009年提出。该算法基于布谷鸟的寄生性育雏(巢寄生)行为…...
Mitel MiCollab企业协作平台存在任意文件读取漏洞(CVE-2024-41713)
免责声明: 本文旨在提供有关特定漏洞的深入信息,帮助用户充分了解潜在的安全风险。发布此信息的目的在于提升网络安全意识和推动技术进步,未经授权访问系统、网络或应用程序,可能会导致法律责任或严重后果。因此,作者不对读者基于本文内容所采取的任何行为承担责任。读者在…...
java+springboot+mysql党务(党员)管理系统
项目介绍: 使用javaspringbootmysql开发的党务管理系统,系统包含管理员、用户角色,功能如下: 管理员:用户管理;党支部管理;党员管理(入党申请、积极分子、发展对象、预备党员、正式…...
gozero项目迁移与新服务器环境配置,包含服务器安装包括go版本,Nginx,项目配置包括Mysql,redis,rabbit,域名
迁移 **GoZero** 项目到新服务器并配置相关环境涉及多个步骤。以下是一个系统化的指南,涵盖服务器环境安装、数据库和缓存配置、项目部署以及域名绑定。 ### 步骤概述 1. **服务器环境配置** - 安装 Go 语言环境 - 安装 Nginx - 安装 MySQL 和 Redis -…...
使用WebDAV来上传和下载文件
WebDAV是什么 基于Web的分布式编写和版本控制(WebDAV)是超文本传输协议(HTTP)的扩展,有利于用户间协同编辑和管理存储在万维网服务器文档。WebDAV 由互联网工程任务组的工作组在 RFC 4918 中定义。许多现代操作系统为 …...
集成运算放大电路反馈判断
集成运算放大电路 一种具有很高放大倍数的多级直接耦合放大电路,因最初用于信号运算而得名,简称集成运放或运放 模拟集成电路中的典型组件,是发展最快、品种最多、应用最广的一种 反馈 将放大电路输出信号的一部分或全部通过某种电路引回到输…...
什么是绩效文化?
绩效文化是一种组织文化,它将绩效视为核心价值观,贯穿于组织的各个层面和活动之中。 一、绩效文化的内涵 目标导向 绩效文化强调组织成员都朝着共同的目标努力。这个目标通常是明确、可衡量的,如企业的年度利润目标、市场份额增长目标等。例…...
【力扣】409.最长回文串
问题描述 思路解析 因为同时包含大小写字母,直接创建个ASCII表大小的桶来标记又因为是要回文子串,所以偶数个数的一定可以那么同时,对于出现奇数次数的,我没需要他们的次数-1,变为偶数,并且可以标记出现过…...
android studio创建虚拟机注意事项
emulator 启动模拟器的时候,可以用 AVD 界面,也可以用命令行启动,但命令行启 动的时候要注意,系统有两个 emulator.exe ,建议使用 emulator 目录下的那个!! 创建类型为google APIs的虚拟机可从…...
DP协议:缩略词
缩写代表的含义ACT分配更改触发(Allocation Change Trigger)API应用程序编程接口(Application Programming Interface)AUX辅助(Auxiliary)BER比特错误率(Bit Error Rate)bpc每色比特…...
【Rive】事件回调
1 前言 Android 中可以通过 RiveAnimationView 的 addEventListener 方法添加动画监听器,用于监听状态动画和过渡动画的开始和结束时机,实现动画开始和结束时的事件回调;也可以监听 Rive 事件触发的时机,在事件触发时响应回调。 …...
YOLOv8-ultralytics-8.2.103部分代码阅读笔记-tuner.py
tuner.py ultralytics\engine\tuner.py 目录 tuner.py 1.所需的库和模块 2.class Tuner: 1.所需的库和模块 # Ultralytics YOLO 🚀, AGPL-3.0 license# 模块提供用于对象检测、实例分割、图像分类、姿势估计和多对象跟踪的 Ultralytics YOLO 模型的超参数调整…...
【数据结构】堆的概念、结构、模拟实现以及应用
本篇我们来介绍一下数据结构中的堆。 1.堆的概念及结构 1)堆是一颗完全二叉树。 2)堆又分为大堆和小堆,大堆就是树里面任何一个父节点都大于子节点,所以根节点是最大值;小堆就是树里面任何一个父节点都小于子节点&am…...
推送(push)项目到gitlab
文章目录 1、git init1.1、在当前目录中显示隐藏文件:1.2、查看已有的远程仓库1.3、确保你的本地机器已经生成了 SSH 密钥:1.4、将生成的公钥文件(通常位于 ~/.ssh/id_rsa.pub)复制到 GitLab 的 SSH 设置中:1.5、测试 …...
springboot/ssm宠物商城网站系统Java代码web项目宠物用品购物论坛源码
springboot/ssm宠物商城网站系统Java代码web项目宠物用品购物论坛源码 基于springboot(可改ssm)htmlvue项目 开发语言:Java 框架:springboot/可改ssm vue JDK版本:JDK1.8(或11) 服务器:tomcat 数据库&…...
前端基础的讲解-JS(22)
什么是JSON? 1.json 是一种轻量级的数据交换格式 简单来说:json 就是一种在各个编程语言中流通的数据格式,负责不同编程语言中的数据传递和交互。 类似于: 国际通用语言 - 英语 中国 56 个民族不同地区的通用语言 - 普通话 …...
zerotier实现内网穿透(访问内网服务器)
moo 内网穿透工具 实用工具:zerotier 目录 内网穿透工具 Windows下zerotier安装 ubuntu系统下的zerotier安装 使用moon加速 Windows下zerotier安装 有了网络之后,会给你一个网络id,这个网络id是非常重要的,其它设备要加入…...
python语言中怎么调用不同级文件夹中数据文件
python语言中怎么调用文件夹中数据文件 python 怎么调用同一级文件夹中数据1. **读取同一级文件夹中的数据文件(如 .txt, .csv, .json 等)**示例: 2. **导入同一级文件夹中的 Python 模块**3. **使用相对路径导入模块**4. **使用 os.path 或 …...
spring事务源码解析
1 引入 在企业级应用开发中,事务管理 是确保数据一致性和完整性的重要手段。而在 Spring 框架中,事务管理提供了高度抽象和灵活的实现,开发者只需通过简单的注解或配置即可轻松实现复杂的事务逻辑。然而,Spring 事务背后的实现机…...
【每日刷题】Day165
【每日刷题】Day165 🥕个人主页:开敲🍉 🔥所属专栏:每日刷题🍍 🌼文章目录🌼 1. LCR 092. 将字符串翻转到单调递增 - 力扣(LeetCode) 2. 424. 替换后的最长…...
基于51单片机的智能公交车报站系统GPS定位语音播报智能安全检测人数统计
功能描述 1.LCD12864可显示当前年月日,星期,时间, 当前站名,经纬度,是否连接GPS,自动/手动模式, 2.自带GPS定位,可实时显示经纬度; 3.通过DS1302时钟芯片,获…...
计算机网络安全 —— 实体鉴别与生成大随机数
一、实体鉴别# 实体鉴别(经常简称为鉴别)就是一方验证另一方身份的技术。一个实体可以是人、客户/服务器进程等。这里仅讨论如何鉴别通信对端 实体的身份,即验证正在通信的对方确实是所认为的通信实体,而不是其他的假冒者。进…...
Vue3+Pinia 状态管理持久化
一、Pinia 简介 🎖️Pinia 起始于 2019 年 11 月左右的一次实验,其目的是设计一个拥有组合式 API 的 Vue 状态管理库。Vue3VitePinia 新三剑客逐渐替代了Vue2WebpackVuex 了,性能啥的各方面吊打。 1.1 什么是状态管理? …...