你的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://www.mzph.cn/news/821887.shtml

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

相关文章

细说postgresql之pg_rman备份恢复 —— 筑梦之路

pg_rman是一款开源的备份恢复软件&#xff0c;支持在线和基于PITR的备份恢复方式。 pg_rman类似于oracle的rman&#xff0c;可以进行全量、增量、归档日志的备份。 运行模式&#xff1a; 安装部署 Releases ossc-db/pg_rman GitHub 1、需要根据PG Server的版本&#xff0c;下…

ThreadLocal和ThreadLocalHashMap

请直接百度详细介绍 -------------------------------------------------------------------------------------------------------------------------------- 1.ThreadLocalMap是Thread类里的一个局部变量 2.ThreadLocalMap是ThreadLocal类里的一个静态内部类, 3.ThreadL…

10. Spring MVC 程序开发

本文源码位置: Spring-MVC 1. Spring MVC 概要 摘自Spring官方&#xff1a; Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, “Spring Web MVC,” comes …

Adobe AE(After Effects)2015下载地址及安装教程

Adobe After Effects是一款专业级别的视觉效果和动态图形处理软件&#xff0c;由Adobe Systems开发。它被广泛用于电影、电视节目、广告和其他多媒体项目的制作。 After Effects提供了强大的合成和特效功能&#xff0c;可以让用户创建出令人惊艳的动态图形和视觉效果。用户可以…

大创项目推荐 深度学习YOLOv5车辆颜色识别检测 - python opencv

文章目录 1 前言2 实现效果3 CNN卷积神经网络4 Yolov56 数据集处理及模型训练5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习YOLOv5车辆颜色识别检测 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0…

嵌入式学习55-ARM4(ADC和I²C)

1、什么是ADC,模拟量和数字量有什么特点&#xff1f; ADC&#xff1a; …

Ubuntu Vs code配置ROS开发环境

文章目录 1.开发环境2.集成开发环境搭建2.1 安装Ros2.2 安装 Vs code2.3 安装vs code 插件 3.Vs code 配置ROS3.1 创建ROS工作空间3.2 从文件夹启动Vs code3.3 使用Vscode 编译ROS 空间3.4 使用Vs code 创建功能包 4.编写简单Demo实例4.1编写代码4.2编译与执行 1.开发环境 系统…

【行为型模式】观察者模式

一、观察者模式概述​ 软件系统其实有点类似观察者模式&#xff0c;目的&#xff1a;一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变&#xff0c;他们之间将产生联动。 观察者模式属于对象行为型&#xff1a; 1.定义了对象之间一种一对多的依赖关系&#xff…

MyBatisPlus自定义SQL

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉🍎个人主页:Leo的博客💞当前专栏: 循序渐进学SpringBoot ✨特色专栏: MySQL学习 🥭本文内容:MyBatisPlus自定义SQL 📚个人知识库: Leo知识库,欢迎大家访问 目录 1.前言☕…

Kafka、RabbitMQ、Pulsar、RocketMQ基本原理和选型

Kafka、RabbitMQ、Pulsar、RocketMQ基本原理和选型 1. 消息队列1.1 消息队列使用场景1.2. 消息队列模式1.2.1 点对点模式&#xff0c;不可重复消费1.2.2 发布/订阅模式 2. 选型参考2.1. Kafka2.1.1 基本术语2.1.2. 系统框架2.1.3. Consumer Group2.1.4. 存储结构2.1.5. Rebalan…

Langchain入门到实战-第三弹

Langchain入门到实战 Langchain中RAG入门官网地址Langchain概述代码演示调用RAG功能更新计划 Langchain中RAG入门 Retrieval Augmented Generation 翻译成中文是“检索增强生成” 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息…

AB5 点击消除

原题链接&#xff1a;点击消除_牛客题霸_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 栈。 遍历字符串。如果栈为空或者当前字符与栈顶元素不等&#xff0c;就压栈。否则如果当前字符与堆顶元素相同&#xff0c;就出栈。 遍历完字符串…

SiLM5350系列带米勒钳位的单通道隔离驱动器 助力汽车与工业应用实现稳定与高效的解决方案

带米勒钳位的隔离驱动SiLM5350系列 单通道 30V&#xff0c;10A 带米勒钳位的隔离驱动 具有驱动电流更大、传输延时更低、抗干扰能力更强、封装体积更小等优势, 为提高电源转换效率、安全性和可靠性提供理想之选。 SiLM5350系列产品描述&#xff1a; SiLM5350系列是单通道隔离驱…

大数据平台搭建2024(三)

三&#xff1a;HBase安装 提前上传hbase安装包至虚拟机 1 上传、解压 tar -zxvf hbase-2.0.0-alpha2-bin.tar.gz -C /hadoop2 修改配置文件 在/hadoop/hbase-2.0.0-alpha2-bin/conf文件夹里 vi /hadoop/hbase-2.0.0-alpha2/conf/hbase-env.sh修改hbase-env.sh文件 export…

如何用JAVA如何实现Word、Excel、PPT在线前端预览编辑的功能?

背景 随着信息化的发展&#xff0c;在线办公也日益成为了企业办公和个人学习不可或缺的一部分&#xff0c;作为微软Office的三大组成部分&#xff1a;Word、Excel和PPT也广泛应用于各种在线办公场景&#xff0c;但是由于浏览器限制及微软Office的不开源等特性&#xff0c;导致…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之十二 简单人脸识别

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之十二 简单人脸识别 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之十二 简单人脸识别 一、简单介绍 二、简单人脸识别实现原理 三、简单人脸识别案例实现简…

数据密集型应用系统设计 PDF 电子书(Martin Kleppmann 著)

简介 《数据密集型应用系统设计》全书分为三大部分&#xff1a; 第一部分&#xff0c;主要讨论有关增强数据密集型应用系统所需的若干基本原则。首先开篇第 1 章即瞄准目标&#xff1a;可靠性、可扩展性与可维护性&#xff0c;如何认识这些问题以及如何达成目标。第 2 章我们比…

MongoDB的安装配置及使用

文章目录 前言一、MongoDB的下载、安装、配置二、检验MongoDB是否安装成功三、Navicat 操作MongoDB四、创建一个集合&#xff0c;存放三个文档总结 前言 本文内容&#xff1a; &#x1f4ab; MongoDB的下载、安装、配置 &#x1f4ab; 检验MongoDB是否安装成功 ❤️ Navicat 操…

【单例模式】饿汉式、懒汉式、静态内部类--简单例子

单例模式是⼀个单例类在任何情况下都只存在⼀个实例&#xff0c;构造⽅法必须是私有的、由⾃⼰创建⼀个静态变量存储实例&#xff0c;对外提供⼀个静态公有⽅法获取实例。 目录 一、单例模式 饿汉式 静态内部类 懒汉式 反射可以破坏单例 道高一尺魔高一丈 枚举 一、单例…

[html]一个动态js倒计时小组件

先看效果 代码 <style>.alert-sec-circle {stroke-dasharray: 735;transition: stroke-dashoffset 1s linear;} </style><div style"width: 110px; height: 110px; float: left;"><svg style"width:110px;height:110px;"><cir…