你的RPCvs佬的RPC

一、课程目标

  1. 了解常见系统库的hook
  2. 了解frida_rpc

二、工具

  1. 教程Demo(更新)
  2. jadx-gui
  3. VS Code
  4. jeb
  5. IDLE

三、课程内容

1.Hook_Libart

libart.so: 在 Android 5.0(Lollipop)及更高版本中,libart.so 是 Android 运行时(ART,Android Runtime)的核心组件,它取代了之前的 Dalvik 虚拟机。可以在 libart.so 里找到 JNI 相关的实现。
PS:在高于安卓10的系统里,so的路径是/apex/com.android.runtime/lib64/libart.so,低于10的则在system/lib64/libart.so

函数名称参数描述返回值
RegisterNativesJNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods反注册类的本地方法。类将返回到链接或注册了本地方法函数前的状态。该函数不应在本地代码中使用。相反,它可以为某些程序提供一种重新加载和重新链接本地库的途径。成功时返回0;失败时返回负数
GetStringUTFCharsJNIEnv*env, jstring string, jboolean *isCopy通过JNIEnv接口指针调用,它将一个代表着Java虚拟机中的字符串jstring引用,转换成为一个UTF-8形式的C字符串-
NewStringUTFJNIEnv *env, const char *bytes以字节为单位返回字符串的 UTF-8 长度返回字符串的长度
FindClassJNIEnv *env, const char *name通过对象获取这个类。该函数比较简单,唯一注意的是对象不能为NULL,否则获取的class肯定返回也为NULL。-
GetMethodIDJNIEnv *env, jclass clazz, const char *name, const char *sig返回类或接口实例(非静态)方法的方法 ID。方法可在某个 clazz 的超类中定义,也可从 clazz 继承。GetMethodID() 可使未初始化的类初始化。方法ID,如果找不到指定的方法,则为NULL
GetStaticMethodIDJNIEnv *env, jclass clazz, const char *name, const char *sig获取类对象的静态方法ID属性ID对象。如果操作失败,则返回NULL
GetFieldIDJNIEnv *env, jclass clazz, const char *name, const char *sig回Java类(非静态)域的属性ID。该域由其名称及签名指定。访问器函数的GetField 及 SetField系列使用域 ID 检索对象域。GetFieldID() 不能用于获取数组的长度域。应使用GetArrayLength()。-
GetStaticFieldIDJNIEnv *env,jclass clazz, const char *name, const char *sig获取类的静态域ID方法-
CallMethod, CallMethodA, CallMethodVJNIEnv *env, jobject obj, jmethodID methodID, …/jvalue *args/va_list args这三个操作的方法用于从本地方法调用Java 实例方法。它们的差别仅在于向其所调用的方法传递参数时所用的机制。NativeType,具体的返回值取决于调用的类型
图片

frida_hook_libart
yang神的hook三件套
简单介绍:
hook_art.js:hook art中的jni函数并且有打印参数和返回值,使用之前记得先加上过滤的so名称,另外高版本的系统也需要在脚本68行的过滤修改成_ZN3art3JNI(最好是加载libart.so查看一下),这个脚本包含了hook_RegisterNatives.js的内容(但不太稳定,做个了解即可)
hook_RegisterNatives.js:hook打印动态注册的函数
图片

hook_artmethod.js:打印所有java函数的调用
图片

frida -U -f com.zj.wuaipojie -l hook_RegisterNatives.js --no-pause

Hook_RegisterNatives

function find_RegisterNatives(params) {// 在 libart.so 库中枚举所有符号(函数、变量等)let symbols = Module.enumerateSymbolsSync("libart.so");  let addrRegisterNatives = null; // 用于存储 RegisterNatives 方法的地址// 遍历所有符号来查找 RegisterNatives 方法for (let i = 0; i < symbols.length; i++) {let symbol = symbols[i]; // 当前遍历到的符号// 检查符号名称是否符合 RegisterNatives 方法的特征if (symbol.name.indexOf("art") >= 0 && //RegisterNatives 是 ART(Android Runtime)环境的一部分symbol.name.indexOf("JNI") >= 0 &&  //RegisterNatives 是 JNI(Java Native Interface)的一部分symbol.name.indexOf("RegisterNatives") >= 0 && //检查符号名称中是否包含 "RegisterNatives" 字样。symbol.name.indexOf("CheckJNI") < 0) { //CheckJNI 是用于调试和验证 JNI 调用的工具,如果不过滤,会有两个RegisterNatives,而带有CheckJNI的系统一般是关闭的,所有要过滤掉addrRegisterNatives = symbol.address; // 保存方法地址console.log("RegisterNatives is at ", symbol.address, symbol.name); // 输出地址和名称hook_RegisterNatives(addrRegisterNatives); // 调用hook函数}}
}function hook_RegisterNatives(addrRegisterNatives) {// 确保提供的地址不为空if (addrRegisterNatives != null) {// 使用 Frida 的 Interceptor hook指定地址的函数Interceptor.attach(addrRegisterNatives, {// 当函数被调用时执行的代码onEnter: function (args) {// 打印调用方法的数量console.log("[RegisterNatives] method_count:", args[3]);// 获取 Java 类并打印类名let java_class = args[1];let class_name = Java.vm.tryGetEnv().getClassName(java_class);let methods_ptr = ptr(args[2]); // 获取方法数组的指针let method_count = parseInt(args[3]); // 获取方法数量// 遍历所有方法//jni方法里包含三个部分:方法名指针、方法签名指针和方法函数指针。每个指针在内存中占用 Process.pointerSize 的空间(这是因为在 32 位系统中指针大小是 4 字节,在 64 位系统中是 8 字节)。为了提高兼容性,统一用Process.pointerSize,系统会自动根据架构来适配for (let i = 0; i < method_count; i++) {// 读取方法的名称、签名和函数指针let name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));//读取方法名的指针。这是每个方法结构体的第一部分,所以直接从起始地址读取。let sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));//读取方法签名的指针。这是结构体的第二部分,所以在起始地址的基础上增加了一个指针的大小let fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));//读取方法函数的指针。这是结构体的第三部分,所以在起始地址的基础上增加了两个指针的大小(Process.pointerSize * 2)。// 将指针内容转换为字符串let name = Memory.readCString(name_ptr);let sig = Memory.readCString(sig_ptr);// 获取方法的调试符号let symbol = DebugSymbol.fromAddress(fnPtr_ptr);// 打印每个注册的方法的相关信息console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr,  " fnOffset:", symbol, " callee:", DebugSymbol.fromAddress(this.returnAddress));}}});}
}setImmediate(find_RegisterNatives); // 立即执行 find_RegisterNatives 函数

hook_GetStringUTFChars
在这里插入图片描述

function hook_GetStringUTFChars() {var GetStringUTFChars_addr = null;// jni 系统函数都在 libart.so 中var module_libart = Process.findModuleByName("libart.so");var symbols = module_libart.enumerateSymbols();for (var i = 0; i < symbols.length; i++) {var name = symbols[i].name;if ((name.indexOf("JNI") >= 0) && (name.indexOf("CheckJNI") == -1) && (name.indexOf("art") >= 0)) {if (name.indexOf("GetStringUTFChars") >= 0) {// 获取到指定 jni 方法地址GetStringUTFChars_addr = symbols[i].address;}}}Java.perform(function(){Interceptor.attach(GetStringUTFChars_addr, {onEnter: function(args){}, onLeave: function(retval){// retval const char*console.log("GetStringUTFChars onLeave : ", ptr(retval).readCString());if(ptr(retval).readCString().indexOf("普通") >=0){console.log("GetStringUTFChars onLeave : ", ptr(retval).readCString());console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');}}})})
}
function main(){Java.perform(function(){hook_GetStringUTFChars();});
} 
setImmediate(main);

2.Hook_Libc

libc.so: 这是一个标准的 C 语言库,用于提供基本的系统调用和功能,如文件操作、字符串处理、内存分配等。在Android系统中,libc 是最基础的库之一。

类别函数名称参数描述
字符串类操作strcpychar *dest, const char *src将字符串 src 复制到 dest
strcatchar *dest, const char *src将字符串 src 连接到 dest 的末尾
strlenconst char *str返回 str 的长度
strcmpconst char *str1, const char *str2比较两个字符串
文件类操作fopenconst char *filename, const char *mode打开文件
freadvoid *ptr, size_t size, size_t count, FILE *stream从文件读取数据
fwriteconst void *ptr, size_t size, size_t count, FILE *stream写入数据到文件
fcloseFILE *stream关闭文件
网络IO类操作socketint domain, int type, int protocol创建网络套接字
connectint sockfd, const struct sockaddr *addr, socklen_t addrlen连接套接字
recvint sockfd, void *buf, size_t len, int flags从套接字接收数据
sendint sockfd, const void *buf, size_t len, int flags通过套接字发送数据
线程类操作pthread_createpthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg创建线程
进程控制操作killpid_t pid, int sig向指定进程发送信号
系统属性查询操作__system_property_getconst char *name, char *value从Android系统属性服务中读取指定属性的值
unamestruct utsname *buf获取当前系统的名称、版本和其他相关信息
sysconfint name获取运行时系统的配置信息,如CPU数量、页大小

hook_kill
在这里插入图片描述

function replaceKILL() {// 查找libc.so库中kill函数的地址var kill_addr = Module.findExportByName("libc.so", "kill");// 使用Interceptor.replace来替换kill函数Interceptor.replace(kill_addr, new NativeCallback(function (arg0, arg1) {// 当kill函数被调用时,打印第一个参数(通常是进程ID)console.log("arg0=> ", arg0);// 打印第二个参数(通常是发送的信号)console.log("arg1=> ", arg1);// 打印调用kill函数的堆栈跟踪信息console.log('libc.so!kill called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');}, "int", ["int", "int"]))
}

hook_pthread_create6

function hook_pthread_create(){//hook反调试var pthread_create_addr = Module.findExportByName("libc.so", "pthread_create");console.log("pthread_create_addr: ", pthread_create_addr);Interceptor.attach(pthread_create_addr,{onEnter:function(args){console.log(args[0], args[1], args[2], args[4]);},onLeave:function(retval){console.log("retval is =>",retval)}})
}

hook_str_cmp
在这里插入图片描述

function hook_strcmp() {var pt_strcmp = Module.findExportByName("libc.so", 'strcmp');Interceptor.attach(pt_strcmp, {onEnter: function (args) {var str1 = args[0].readCString();var str2 = args[1].readCString();if (str2.indexOf("hh") !== -1) {console.log("strcmp-->", str1, str2);this.printStack = true;}}, onLeave: function (retval) {if (this.printStack) { var stack = Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n");console.log("Stack trace:\n" + stack);}}})
}

3.Hook_Libdl

libdl.so是一个处理动态链接和加载的标准库,它提供了dlopen、dlclose、dlsym等函数,用于在运行时动态地加载和使用共享库

类别函数名称参数描述
动态链接库操作dlopenconst char *filename, int flag打开动态链接库文件
dlsymvoid *handle, const char *symbol从动态链接库中获取符号地址Hook_dlsym获取jni静态注册方法地址

Hook_dlsym获取jni静态注册方法地址

在这里插入图片描述

function hook_dlsym() {var dlsymAddr = Module.findExportByName("libdl.so", "dlsym");Interceptor.attach(dlsymAddr, {onEnter: function(args) {this.args1 = args[1];},onLeave: function(retval) {var module = Process.findModuleByAddress(retval);if (module === null) return; console.log(this.args1.readCString(), module.name, retval, retval.sub(module.base));}});
}

4.Hook_Linker

Linker是Android系统动态库so的加载器/链接器,通过android源码分析 init 和 init_array 是在 callConstructor 中被调用的
在这里插入图片描述

hookInit和hookInitArray
frida hook init_array自吐新解
经过安卓源码比对,从Android 8 ~ 14,结构体中init_array的位置都很稳定,提取部分头文件信息在CModule中定义一个soinfo结构体,接着定义一个接受一个soinfo指针参数和一个callback函数的函数,输出init_array信息

function hook_call_constructors() {// 初始化变量let get_soname = null;let call_constructors_addr = null;let hook_call_constructors_addr = true;// 根据进程的指针大小找到对应的linker模块let linker = null;if (Process.pointerSize == 4) {linker = Process.findModuleByName("linker");} else {linker = Process.findModuleByName("linker64");}// 枚举linker模块中的所有符号let symbols = linker.enumerateSymbols();for (let index = 0; index < symbols.length; index++) {let symbol = symbols[index];// 查找名为"__dl__ZN6soinfo17call_constructorsEv"的符号地址if (symbol.name == "__dl__ZN6soinfo17call_constructorsEv") {call_constructors_addr = symbol.address;// 查找名为"__dl__ZNK6soinfo10get_sonameEv"的符号地址,获取soname} else if (symbol.name == "__dl__ZNK6soinfo10get_sonameEv") {get_soname = new NativeFunction(symbol.address, "pointer", ["pointer"]);}}// 如果找到了所有需要的地址和函数if (hook_call_constructors_addr && call_constructors_addr && get_soname) {// 挂钩call_constructors函数Interceptor.attach(call_constructors_addr,{onEnter: function(args){// 从参数获取soinfo对象let soinfo = args[0];// 使用get_soname函数获取模块名称let soname = get_soname(soinfo).readCString();// 调用tell_init_info函数并传递一个回调,用于记录构造函数的调用信息tell_init_info(soinfo, new NativeCallback((count, init_array_ptr, init_func) => {console.log(`[call_constructors] ${soname} count:${count}`);console.log(`[call_constructors] init_array_ptr:${init_array_ptr}`);console.log(`[call_constructors] init_func:${init_func} -> ${get_addr_info(init_func)}`);// 遍历所有初始化函数,并打印它们的信息for (let index = 0; index < count; index++) {let init_array_func = init_array_ptr.add(Process.pointerSize * index).readPointer();let func_info = get_addr_info(init_array_func);console.log(`[call_constructors] init_array:${index} ${init_array_func} -> ${func_info}`);}}, "void", ["int", "pointer", "pointer"]));}});}
}

5.frida_rpc

frida 提供了一种跨平台的 rpc(就是Remote Procedure Call 远程过程调用) 机制,通过 frida rpc 可以在主机和目标设备之间进行通信,并在目标设备上执行代码,简单理解就是可以不需要分析某些复杂加密,通过传入参数获取返回值,进而来实现python或易语言来调用的一系列操作,多用于爬虫。

包名附加进程

import frida, sys
jsCode = """ ...... """
script.exports.rpcfunc()
process = frida.get_usb_device().attach('包名') # 获取USB设备并附加到应用
script = process.create_script(jsCode) # 创建并加载脚本
script.load()# 执行脚本
sys.stdin.read()# 保持脚本运行状态,防止它执行完毕后立即退出

spawn方式启动

import frida, sys
jsCode = """ ...... """
script.exports.rpcfunc()
device = frida.get_usb_device()
pid = device.spawn(["包名"])    #以挂起方式创建进程
process = device.attach(pid)
script = process.create_script(jsCode)
script.load()
device.resume(pid)  #加载完脚本, 恢复进程运行
sys.stdin.read()

连接非标准端口

import frida, sys
jsCode = """ ...... """
script.exports.rpcfunc()
process = frida.get_device_manager().add_remote_device('192.168.1.22:6666').attach('包名')
script = process.create_script(jsCode)
script.load()
sys.stdin.read()复制代码 隐藏代码
function get_url() {let ChallengeNinth = Java.use("com.zj.wuaipojie.ui.ChallengeNinth");ChallengeNinth["updateUI"].implementation = function (list) {let ret = this.updateUI(list);// 获取List的大小var size = list.size();// 遍历并打印List中的每个ImageEntity对象for (var i = 0; i < size; i++) {var imageEntity = Java.cast(list.get(i), Java.use('com.zj.wuaipojie.entity.ImageEntity'));console.log(imageEntity.name.value + imageEntity.cover.value);}return ret;};
}

需要提前pip安装好的几个库

frida-tools==9.2.4,uvicorn,fastapi,requests
#导入需要的库
from fastapi import FastAPI
from fastapi.responses import JSONResponse
import frida, sys
import uvicorn
#创建FastAPI应用实例
app = FastAPI()# 定义一个GET请求的路由'/download-images/'
@app.get("/download-images/")
def download_images():# 定义处理frida消息的回调函数def on_message(message, data):message_type = message['type']if message_type == 'send':print('[* message]', message['payload'])elif message_type == 'error':stack = message['stack']print('[* error]', stack)else:print(message)# Frida脚本代码,用于在目标应用内部执行jsCode = """function getinfo(){var result = [];Java.perform(function(){Java.choose("com.zj.wuaipojie.ui.ChallengeNinth",{onMatch:function(instance){instance.setupScrollListener(); // 调用目标方法},onComplete:function(){}});Java.choose("com.zj.wuaipojie.entity.ImageEntity",{onMatch:function(instance){var name = instance.getName();var cover = instance.getCover();result.push({name: name, cover: cover}); // 收集数据},onComplete:function(){}});});return result; // 返回收集的结果}rpc.exports = {getinfo: getinfo // 导出函数供外部调用};"""# 使用frida连接到设备并附加到指定进程process = frida.get_usb_device().attach("com.zj.wuaipojie")# 创建并加载Frida脚本script = process.create_script(jsCode)script.on("message", on_message)  # 设置消息处理回调script.load()  # 加载脚本getcovers = script.exports.getinfo()  # 调用脚本中的函数获取信息print(getcovers)# 返回获取的信息作为JSON响应return JSONResponse(content=getcovers)# 主入口,运行FastAPI应用
if __name__ == "__main__":uvicorn.run(app, host="127.0.0.1", port=8000)  # 使用uvicorn作为ASGI服务器启动应用

提示词

写一段python的requests代码,访问http://127.0.0.1:8000/download-images/端口,会获得如下的json数据,按照名字把图片爬取到同目录的pic文件夹里,并写好注释
[{
"name": "霸王别姬",
"cover": "https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c"
},{
"name": "这个杀手不太冷",
"cover": "https://p1.meituan.net/movie/6bea9af4524dfbd0b668eaa7e187c3df767253.jpg@464w_644h_1e_1c"
},{
"name": "肖申克的救赎",
"cover": "https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@464w_644h_1e_1c"
}]

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode_链表的回文结构

✨✨所属专栏&#xff1a;LeetCode刷题专栏✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ 题目描述&#xff1a; 对于一个链表&#xff0c;请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法&#xff0c;判断其是否为回文结构。给定一个链表的头指针A&#xff0c;请返回一个bo…

谷歌、Meta、OpenAI 联同其他行业巨头共同打击 AI 产生的儿童性虐待图像|TodayAI

谷歌、Meta、OpenAI 等全球科技巨头已联合行动&#xff0c;与其他行业领导者共同宣布&#xff0c;将加强措施&#xff0c;防止人工智能技术被用来制造或传播儿童性虐待图像。 为打击儿童性虐待材料&#xff08;CSAM&#xff1a;child sexual abuse material&#xff09;的传播…

wfs 文件存储系统 v1.0.5

前言&#xff1a;wfs 是高性能海量小文件存储系统 &#xff0c;支持Linux&#xff0c;Windows&#xff0c;Macos&#xff0c;FreeBSD等系统&#xff0c; 可以高效地进行文件存储和读取。wfs 支持文件压缩归档&#xff0c;并提供简洁的数据读取方式和文件后台管理和 以及归档文件…

《设计模式之美》第三章 总结

《设计模式之美》总结 第三章 设计原则 3.1 单一职责原则&#xff1a;如何判定某个类的职责是否单一 3.1.1 单一职责原则的定义和解读 定义&#xff1a;一个类或模块只负责完成一个职责&#xff08;功能&#xff09; 含义&#xff1a;不要设计功能大而全的类或模块&#xff…

汽车纵染压制专用液压机比例阀放大器

汽车纵染压制专用液压机比例阀放大器是一种专门用于汽车纵梁拉伸工艺的设备&#xff0c;它也可以用于其他金属薄板的压制成型及校正工艺。该类型的液压机通常具备独立的动力机构和电气系统&#xff0c;采用PLC技术进行控制&#xff0c;以确保操作的准确性和稳定性。除了纵梁拉伸…

【随想录】Day31—第八章 贪心算法 part01

目录 题目1: 455. 分发饼干1- 思路2- 题解⭐分发饼干 ——题解思路 题目2: 摆动序列1- 思路2- 题解⭐摆动序列 ——题解思路 题目3: 最大子数组和1- 思路2- 题解⭐ 最大子数组和 ——题解思路 题目1: 455. 分发饼干 题目链接&#xff1a;455. 分发饼干 1- 思路 贪心的思路&am…

Linux多进程(二)进程通信方式二 消息队列

消息队列是在两个进程之间传递二进制块数据的一种简单有效的方式。每个数据块都有一个特定的类型&#xff0c;接收方可以根据类型来有选择地接收数据&#xff0c;而不一定像管道和命名管道那样必须以先进先出的方式接收数据。 一、创建消息队列 创建一个消息队列或者获取一个…

Linux多进程(二)进程通信方式一 管道

管道的是进程间通信&#xff08;IPC - InterProcess Communication&#xff09;的一种方式&#xff0c;管道的本质其实就是内核中的一块内存(或者叫内核缓冲区)&#xff0c;这块缓冲区中的数据存储在一个环形队列中&#xff0c;因为管道在内核里边&#xff0c;因此我们不能直接…

Go语言并发赋值的安全性

struct并发赋值 type Test struct {X intY int }func main() {var g Testfor i : 0; i < 1000000; i {var wg sync.WaitGroup// 协程 1wg.Add(1)go func() {defer wg.Done()g Test{1, 2}}()// 协程 2wg.Add(1)go func() {defer wg.Done()g Test{3, 4}}()wg.Wait()// 赋值…

全氟己酮灭火绳的用法早知道:灭火绳多少钱一米?

全氟己酮灭火装置作为一种高效、安全、环保的灭火技术&#xff0c;已经成为了备受青睐的新型灭火选择之一。伴随着市场需求不断增长&#xff0c;在全氟己酮厂家的努力下&#xff0c;各式各样的全氟己酮自动灭火装置不断涌现&#xff0c;包括自动灭火贴、灭火片、灭火毯、灭火绳…

直播报名 | 科技出海新势力,遥感+AI助力一带一路

遥感技术的出海之路顺畅吗&#xff1f; 国内外遥感应用的关注点相同吗&#xff1f; 目前珈和主要辐射哪些海外国家&#xff1f; … 上周数据赋农季第三期《科技出海&#xff0c;遥感AI赋能“一带一路”提升种植园规模效益》直播预告一出&#xff0c;小伙伴们纷纷来咨询珈和的海…

《S32G3系列芯片——Boot详解》持续更新中...

总目录&#xff1a;《S32G3系列芯片——Boot详解》持续更新中... ... 一、前言二、启动时序概述&#xff08;Boot Sequence&#xff09;三、启动特性&#xff08;Boot Features&#xff09;四、启动模式&#xff08;Boot Mode&#xff09;五、《S32G3系列芯片——Boot详解》系列…

Centos之yum安装好玩的命令

1.会动的小火车 我在root下使用的 yum install sl.x86_64sl2.figlet yum install figlet.x86_64figlet 55553.cowsay会说话 yum install cowsay

力扣数据库题库学习(4.23日)

610. 判断三角形 问题链接 解题思路 题目要求&#xff1a;对每三个线段报告它们是否可以形成一个三角形。以 任意顺序 返回结果表。 对于三个线段能否组成三角形的判定&#xff1a;任意两边之和大于第三边&#xff0c;对于这个表内的记录&#xff0c;要求就是&#xff08;x…

一看就会,uni-app打包运行成微信小程序,部署

前言 这篇文章主要针对刚开始接触混合开发的小伙伴&#xff0c;全文使用uniapp框架&#xff0c;使用HBuilderX结合微信小程序开发工具作为开发环境。搭建一个简单的入手项目&#xff0c;主要是对搭建项目的流程做一个简单介绍 提示&#xff1a;以下是本篇文章正文内容&#xff…

【GEE】优雅地实现年度、季度、月度甚至旬度影像合成(附完整代码)

以下文章来源于GEE学习室 &#xff0c;作者GEEStudyRoom 光学影像由于受到天气因素&#xff08;云、雨和雾等&#xff09;影响&#xff0c;导致单张影像数据存在大量坏死像元。此处&#xff0c;坏死像元指由于受到云遮挡等导致下垫面地物覆盖不能准确被卫星信息捕捉从而不能正…

【Linux系统编程】第八弹---权限管理操作(中)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、修改文件权限的做法(二) 2、文件类型 3、可执行权限 4、创建文件/目录的默认权限 4.1、权限掩码 总结 前面一弹我们学…

git 重命名文件,提交后,此文件的提交记录丢失

零、问题现象&#xff1a; 文件重命名后&#xff0c;提交到 git 仓库&#xff0c;发现重命名操作 变成 删除旧文件&#xff0c;新增一个新文件&#xff0c;原来文件的提交记录丢失&#xff0c;看不到了。 一、正确的重命名提交方法 1.1、 先执行add命令来将修改内容后的文件…

校园论坛圈子,校园跑腿小程序,2024 一款功能强大校园综合服务小程序开源源码

目的 本课题主要目标是设计并能够实现一个基于微信校园跑腿小程序系统&#xff0c;前台用户使用小程序发布跑腿任何和接跑腿任务&#xff0c;系统基于TP6uni-app框架开发;客户移动端采用uni-app开发&#xff0c;管理后台TH6开发&#xff1b;通过后台管理跑腿的用户、查看跑腿信…

2024年最佳软件测试工具40强清单

什么是测试工具 软件测试工具是指那些支持从计划、需求收集、构建创建、测试执行、缺陷记录到测试分析等各种测试活动的产品。这些工具主要用于检测软件的稳定性、彻底性以及其他性能参数。 市场上有大量的软件测试工具&#xff0c;众多选择使得难以确定最适合你项目的测试工具…