你的RPCvs佬的RPC

news2024/11/29 4:50:39

一、课程目标

  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://www.coloradmin.cn/o/1603296.html

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

相关文章

STM32 HAL库之使用8080时序在LCD上实现任意位置画点和读点以及字符显示

必要知识 本实验用的是240*320屏幕 LCD的驱动原理&#xff1a; LCD屏&#xff08;MCU接口&#xff09;驱动的核心是&#xff1a;驱动LCD驱动芯片 LCD驱动基本知识: 1&#xff0c;8080时序&#xff0c;LCD驱动芯片一般使用8080时序控制&#xff0c;实现数据写入/读取 2&…

Photoshop 2024 (ps) v25.6中文 强大的图像处理软件 mac/win

Photoshop 2024 for Mac是一款强大的图像处理软件&#xff0c;专为Mac用户设计。它继承了Adobe Photoshop一贯的优秀功能&#xff0c;并进一步提升了性能和稳定性。 Mac版Photoshop 2024 (ps)v25.6中文激活版下载 win版Photoshop 2024 (ps)v25.6直装版下载 无论是专业的设计师还…

Maven多模块管理

Maven多模块管理 在了解怎么进行Maven多模块管理之前&#xff0c;先聊聊为什么要进行Maven多模块管理 为什么要Maven多模块管理&#xff1f; 在传统的单体架构开发下&#xff0c;一个项目中的依赖只需要使用一个pom.xml文件管理即可。但是随着微服务的流行&#xff0c;将原有…

【每日刷题】Day17

【每日刷题】Day17 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 19. 删除链表的倒数第 N 个结点 - 力扣&#xff08;LeetCode&#xff09; 2. 162. 寻找峰值 - 力扣…

详解运算符重载,赋值运算符重载,++运算符重载

目录 前言 运算符重载 概念 目的 写法 调用 注意事项 详解注意事项 运算符重载成全局性的弊端 类中隐含的this指针 赋值运算符重载 赋值运算符重载格式 注意点 明晰赋值运算符重载函数的调用 连续赋值 传引用与传值返回 默认赋值运算符重载 前置和后置重载 前…

使用 Flask-WTF 防止跨站请求攻击(CSRF):一份全面指南

在构建 Web 应用时&#xff0c;防止跨站请求攻击&#xff08;CSRF&#xff09;是一项至关重要的安全措施。CSRF 攻击允许恶意网站执行未经授权的操作&#xff0c;如用户身份验证或数据篡改。幸运的是&#xff0c;Flask-WTF 库为我们提供了强大的 CSRF 保护功能。在本篇博客中&a…

【模拟】Leetcode 数青蛙

题目讲解 1419. 数青蛙 算法讲解 class Solution { public:int minNumberOfFrogs(string croakOfFrogs) {string target "croak";int n target.size();//保存target每个字符的位置indexunordered_map<char, int>index;for(int i 0; i < n; i)index[tar…

Day08React——第八天

useEffect 概念&#xff1a;useEffect 是一个 React Hook 函数&#xff0c;用于在React组件中创建不是由事件引起而是由渲染本身引起的操作&#xff0c;比如发送AJAx请求&#xff0c;更改daom等等 需求&#xff1a;在组件渲染完毕后&#xff0c;立刻从服务器获取频道列表数据…

什么是Rust语言?探索安全系统编程的未来

&#x1f680; 什么是Rust语言&#xff1f;探索安全系统编程的未来 文章目录 &#x1f680; 什么是Rust语言&#xff1f;探索安全系统编程的未来摘要引言正文&#x1f4d8; Rust语言简介&#x1f31f; 发展历程&#x1f3af; Rust的技术意义和优势&#x1f4e6; Rust解决的问题…

HarmonyOS开发实例:【分布式新闻客户端】

介绍 本篇Codelab基于栅格布局、设备管理和多端协同&#xff0c;实现一次开发&#xff0c;多端部署的分布式新闻客户端页面。主要包含以下功能&#xff1a; 展示新闻列表以及左右滑动切换新闻Tab。点击新闻展示新闻详情页。点击新闻详情页底部的分享按钮&#xff0c;发现周边…

Go 之 sync.Mutex 加锁失效现象

我先声明一下&#xff0c;并不是真的加锁失效&#xff0c;而是我之前的理解有误&#xff0c;导致看起来像是加锁失效一样。于是乎记录一下&#xff0c;加深一下印象。 我之前有个理解误区&#xff08;不知道大家有没有&#xff0c;有的话赶紧纠正一下——其实也是因为我这块的…

项目7-音乐播放器5+注册账号

1.前端代码 MAPPER Insert("insert into user(username,password) values (#{username},#{password}) ")Integer insertUserInfo(String username,String password); Service public Result insertUserInfo(String username, String oldpassword,String newpasswo…

算法学习——LeetCode力扣补充篇11(64. 最小路径和、48. 旋转图像 、169. 多数元素、394. 字符串解码、240. 搜索二维矩阵 II )

算法学习——LeetCode力扣补充篇11 64. 最小路径和 64. 最小路径和 - 力扣&#xff08;LeetCode&#xff09; 描述 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只…

测绘管理与法律法规 | 测绘资质管理办法 | 学习笔记

目录 一、测绘资质概述 二、测绘资质分类与等级 三、审批与管理 四、申请条件 五、审批程序 六、测绘资质证书 七、监督管理 八、违规处理 九、特殊规定 十、审批受理时间要点补充 1. 审批机关决定是否受理的时间 2. 审批机关作出批准与否的决定时间 3. 颁发测绘资…

在报表控件 FastReport .NET 中使用 PageCreate 事件

FastReport Business Graphics .NET&#xff0c;是一款基于fastreport报表开发控件的商业图形库&#xff0c;借助 FastReport 商业图形库&#xff0c;您可以可视化不同的分层数据&#xff0c;构建业务图表以进行进一步分析和决策。利用数据呈现领域专家针对 .NET 7、.NET Core、…

论文阅读-Federated-Unlearning-With-Momentum-Degradation

论文阅读-Federated Unlearning With Momentum Degradation 联邦忘却与动量退化 Yian Zhao IEEE Internet of Things Journal 2023 年 10 月 2 日 CCF-C momentum degradation-MoDe 动量退化 memory guidance-记忆引导 knowledge erasure-知识擦除 Deep-learning neural n…

【记录】Python|Selenium 下载 PDF 不预览不弹窗(2024年)

版本&#xff1a; Chrome 124Python 12Selenium 4.19.0 版本与我有差异不要紧&#xff0c;只要别差异太大比如 Chrome 用 57 之前的版本了&#xff0c;就可以看本文。 如果你从前完全没使用过、没安装过Selenium&#xff0c;可以参考这篇博客《【记录】Python3&#xff5c;Sele…

搭建Zookeeper完全分布式集群(CentOS 9 )

ZooKeeper是一个开源的分布式协调服务&#xff0c;它为分布式应用提供了高效且可靠的分布式协调服务&#xff0c;并且是分布式应用保证数据一致性的解决方案。该项目由雅虎公司创建&#xff0c;是Google Chubby的开源实现。 分布式应用可以基于ZooKeeper实现诸如数据发布/订阅…

UE5 C++ 射线检测

一.声明四个变量 FVector StartLocation;FVector ForwardVector;FVector EndLocation;FHitResult HitResult;二.起点从摄像机&#xff0c;重点为摄像机前9999m。射线检测 使用LineTraceSingleByChannel 射线直线通道检测&#xff0c;所以 void AMyCharacter::Tick(float Delt…

c++ qt6.5 打包sqlite组件无法使用,尽然 也需要dll支持!这和开发php 有什么区别!

运行 程序会默认使用当前所在文件夹中的 dll 文件&#xff0c;若文件不存在&#xff0c;会使用系统环境变量路径中的文件&#xff1b;又或者是需要在程序源代码中明确指定使用的 dll 的路径。由于我安装 Qt 时将相关 dll 文件路径都添加到了系统环境变量中&#xff0c;所以即使…