应用安全 --- frida脚本 之 dump 自动化动脱 so
为什么我要动态在内存中查找so并下载修复一个so,因为这个so文件被安全软件进行了加固处理使得代码大面积加密,用ida打开后会发现代码是红色的报错
用到的脱壳so的工具:
https://github.com/lasting-yang/frida_dump/tree/master/android
原版不好用,存在的问题有不支持远程连接,不支持延迟加载so导致有些so文件无法获取。我进行了脚本优化,支持多个adb设备指定。
前提是已经有一个root手机,安装了magisk,并安装好了frida,并且app没有检测root和frida。
整体功能:这是一个用于从Android应用中dump(提取)SO库文件的工具
主要流程:
设备连接:自动检测并选择连接的Android设备
进程附加:使用Frida连接到目标应用(得物App)
模块查找:在应用的内存中查找指定的SO库
内存dump:将SO库从内存中提取出来
文件修复:使用SoFixer工具修复dump出来的文件,让它能正常使用
清理工作:删除临时文件
关键技术点:
使用Frida进行动态分析和内存操作
通过ADB与Android设备通信
处理ARM32和ARM64两种架构
包含重试机制确保模块加载完成
1. 动态内存分析原理
Frida Hook技术: Frida是一个动态插桩框架,可以在运行时注入JavaScript代码到目标进程
通过Hook系统调用和内存操作,获取进程的内存布局信息
不需要root权限就能访问应用的内存空间
内存映射获取: # 通过Frida脚本获取所有已加载的模块信息
allmodule = script.exports_sync.allmodule()
# 每个模块包含:名称、基址、大小、路径等信息
2. SO文件在内存中的存储原理
ELF文件加载过程: Android系统加载SO文件时,会将其映射到进程的虚拟内存空间
系统会记录每个SO文件的基址(base address)和大小
文件在内存中可能被分散存储,但逻辑上是连续的
内存布局: 进程内存空间:
[0x7000000000] - libc.so
[0x7001000000] - libssl.so
[0x7002000000] - 目标SO文件 ← 我们要提取的
[0x7003000000] - 其他库文件
3. 内存Dump的核心机制
直接内存读取: // 在dump_so.js中(Frida脚本)
function dumpmodule(module_name) {
var module = Process.findModuleByName(module_name);
if (module) {
// 直接从内存基址读取指定大小的数据
var buffer = Memory.readByteArray(module.base, module.size);
return buffer;
}
}
关键步骤: 通过Process.findModuleByName()定位模块
获取模块的基址和大小信息
使用Memory.readByteArray()直接读取内存数据
将二进制数据传回Python端保存为文件
4. 文件修复的必要性
为什么需要修复: 从内存dump出来的SO文件可能缺少某些段信息
内存中的地址是虚拟地址,需要转换为文件偏移
某些重定位信息可能已被修改
SoFixer工具原理: # SoFixer的作用
/data/local/tmp/SoFixer -m 基址 -s 源文件 -o 输出文件
-m:指定原始内存基址,用于地址重定位
重建ELF文件头和段表
修复符号表和重定位表
确保文件可以被IDA、Ghidra等工具正确解析
脚本如下:
我希望用aya工具箱进行远程连接手机,手机打开远程调试功能,输入ip和端口就可以远程adb了。
注意要将adb命令配置为环境变量,这样这个脚本就可以进行使用adb进行远程拉取脱壳后的so了。我们会发现目录下多了一个修复好的so文件。
注意过掉frida检测。我这个app过检测的方法是在/data/app的/lib下删除libmasaosec.so这个验证文件,如果不删除在执行脚本时会发现应用闪退。
我执行的命令如下
# 指定设备
python dump_so.py -d 192.168.1.164:41309 -p com.shizhuanxxxxx.duapp -s libGameVMP.so
Multiple devices found:
1. 192.168.1.164:41309
2. adb-KCAIKN05L048ZAF-ovuptT._adb-tls-connect._tcp
Select device (1-2): 1
Started and attached to process: com.shizhuanxxxx.duapp (PID: 5913)
Frida script loaded
Looking for module: libGameVMP.so
Module not found, retry 1/10...
Found module info:
{'name': 'libGameVMP.so', 'version': None, 'base': '0x725d4cf000', 'size': 454656, 'path': '/data/app/~~HP4rmsQIdDjYodK-wFzpgg==/com.shizhuanxxxxx.duapp-53DwSEmI6IWzTVKI_jTzYg==/lib/arm64/libGameVMP.so'}
Starting dump of module: base=0x725d4cf000, size=454656
Saved dump file: libGameVMP.so.dump.so
Device architecture: arm64
android/SoFixer64: 1 file pushed, 0 skipped. 4.8 MB/s (2672240 bytes in 0.536s)
libGameVMP.so.dump.so: 1 file pushed, 0 skipped. 23.0 MB/s (454656 bytes in 0.019s)
adb -s 192.168.1.164:41309 shell /data/local/tmp/SoFixer -m 0x725d4cf000 -s /data/local/tmp/libGameVMP.so.dump.so -o /data/local/tmp/libGameVMP.so.dump.so.fix.so
[main_loop:87]start to rebuild elf file
[Load:69]dynamic segment have been found in loadable segment, argument baseso will be ignored.
[RebuildPhdr:25]=============LoadDynamicSectionFromBaseSource==========RebuildPhdr=========================
[RebuildPhdr:37]=====================RebuildPhdr End======================
[ReadSoInfo:552]=======================ReadSoInfo=========================
[ReadSoInfo:699]soname
[ReadSoInfo:624] constructors (DT_INIT) found at 20230
[ReadSoInfo:632] constructors (DT_INIT_ARRAY) found at 6b8e0
[ReadSoInfo:636] constructors (DT_INIT_ARRAYSZ) 35
[ReadSoInfo:640] destructors (DT_FINI_ARRAY) found at 6b9f8
[ReadSoInfo:644] destructors (DT_FINI_ARRAYSZ) 2
[ReadSoInfo:583]string table found at 10f0
[ReadSoInfo:587]symbol table found at 568
[ReadSoInfo:598] plt_rel_count (DT_PLTRELSZ) 123
[ReadSoInfo:594] plt_rel (DT_JMPREL) found at 2110
[ReadSoInfo:702]Unused DT entry: type 0x00000009 arg 0x00000018
[ReadSoInfo:702]Unused DT entry: type 0x00000018 arg 0x00000000
[ReadSoInfo:702]Unused DT entry: type 0x6ffffffb arg 0x00000001
[ReadSoInfo:702]Unused DT entry: type 0x6ffffffe arg 0x000015e8
[ReadSoInfo:702]Unused DT entry: type 0x6fffffff arg 0x00000003
[ReadSoInfo:702]Unused DT entry: type 0x6ffffff0 arg 0x000014ee
[ReadSoInfo:702]Unused DT entry: type 0x6ffffff9 arg 0x00000059
[ReadSoInfo:706]=======================ReadSoInfo End=========================
[RebuildShdr:42]=======================RebuildShdr=========================
[RebuildShdr:539]=====================RebuildShdr End======================
[RebuildRelocs:786]=======================RebuildRelocs=========================
[RebuildRelocs:812]=======================RebuildRelocs End=======================
[RebuildFin:712]=======================try to finish file rebuild =========================
[RebuildFin:736]=======================End=========================
[main:123]Done!!!
/data/local/tmp/libGameVMP.so.dump.so.fix....skipped. 4.3 MB/s (455601 bytes in 0.100s)
Fixed SO file: libGameVMP.so_0x725d4cf000_454656_fix.so
Cleaned up temporary files
PS C:\Users\21558\Downloads\frida_dump-master>
最后我们分析一下js关键代码
dump_so.js // 定义RPC导出对象,这些函数可以被Python端调用
rpc.exports = {
// 查找指定名称的模块函数
findmodule: function (so_name) {
// 使用Process.findModuleByName()在当前进程中查找指定名称的模块
// so_name: 要查找的SO文件名,如"libnative.so"
var libso = Process.findModuleByName(so_name);
// 返回模块对象,包含name、base、size、path等信息
// 如果找不到模块则返回null
return libso;
}, // dump指定模块的内存数据函数
dumpmodule: function (so_name) {
// 首先查找指定名称的模块
var libso = Process.findModuleByName(so_name);
// 如果模块不存在,返回-1表示失败
if (libso == null) {
return -1;
} // 修改内存保护属性为可读写执行(rwx)
// 这是为了确保我们能够读取模块的所有内存区域
// ptr(libso.base): 将基址转换为指针对象
// libso.size: 模块的大小
// 'rwx': 读(r)写(w)执行(x)权限
Memory.protect(ptr(libso.base), libso.size, 'rwx'); // 从模块基址开始读取整个模块的字节数据
// ptr(libso.base): 模块在内存中的起始地址
// readByteArray(libso.size): 读取指定大小的字节数组
var libso_buffer = ptr(libso.base).readByteArray(libso.size); // 将读取的缓冲区数据附加到模块对象上(可选,用于调试)
libso.buffer = libso_buffer; // 返回读取到的字节数组,这就是SO文件的完整内存映像
return libso_buffer;
}, // 获取所有已加载模块的函数
allmodule: function () {
// Process.enumerateModules()返回当前进程中所有已加载模块的数组
// 每个模块对象包含:name(名称)、base(基址)、size(大小)、path(路径)
return Process.enumerateModules()
}, // 获取当前设备架构的函数
arch: function () {
// Process.arch返回当前进程的CPU架构
// 可能的值:'arm', 'arm64', 'ia32', 'x64'
// 这个信息用于选择正确的SoFixer工具版本
return Process.arch;
}
}
dump_dex.js
// 获取当前进程名称的函数
function get_self_process_name() {
// 找到open导出函数
var openPtr = Module.getExportByName('libc.so', 'open');
// NativeFunction是c和js函数的桥梁。创建open函数的NativeFunction包装,参数:返回类型int,参数类型[pointer, int]。将一个已知地址的原生 C 函数 open,包装成一个可以被 JavaScript 直接、安全调用的 JavaScript 函数 open。它定义了如何转换参数和返回值,使得两个不同语言的世界能够无缝通信。
var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']); // 找到read导出函数
var readPtr = Module.getExportByName("libc.so", "read");
// 创建read函数的NativeFunction包装,参数:返回类型int,参数类型[int, pointer, int]
var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]); // 获取libc.so中close函数的地址
var closePtr = Module.getExportByName('libc.so', 'close');
// 创建close函数的NativeFunction包装,参数:返回类型int,参数类型[int]
var close = new NativeFunction(closePtr, 'int', ['int']); // Memory.allocUtf8String() 的作用就是充当 js和 C串指针之间的桥梁。
// 这个文件包含当前进程的命令行参数,第一个参数就是进程名
var path = Memory.allocUtf8String("/proc/self/cmdline");
// 打开文件,参数0表示只读模式
var fd = open(path, 0);
// 如果文件打开成功(文件描述符不等于-1)
if (fd != -1) {
// 分配4KB内存用于读取文件内容
var buffer = Memory.alloc(0x1000); // 从文件中读取数据到缓冲区
var result = read(fd, buffer, 0x1000);
// 关闭文件
close(fd);
// 将缓冲区内容转换为C字符串并返回
result = ptr(buffer).readCString();
return result;
} // 如果获取失败,返回"-1"
return "-1";
} // 创建目录
function mkdir(path) {
// 获取libc.so中mkdir函数的地址
var mkdirPtr = Module.getExportByName('libc.so', 'mkdir');
// 创建mkdir函数的NativeFunction包装
var mkdir = new NativeFunction(mkdirPtr, 'int', ['pointer', 'int']); // 获取libc.so中opendir函数的地址,用于检查目录是否存在
var opendirPtr = Module.getExportByName('libc.so', 'opendir');
// 创建opendir函数的NativeFunction包装
var opendir = new NativeFunction(opendirPtr, 'pointer', ['pointer']); // 获取libc.so中closedir函数的地址
var closedirPtr = Module.getExportByName('libc.so', 'closedir');
// 创建closedir函数的NativeFunction包装
var closedir = new NativeFunction(closedirPtr, 'int', ['pointer']); // 将js路径字符串转换为C字符串
var cPath = Memory.allocUtf8String(path);
// 尝试打开目录,检查是否存在
var dir = opendir(cPath);
// 如果目录存在(opendir返回非0值)
if (dir != 0) {
// 关闭目录句柄
closedir(dir);
// 目录已存在,直接返回
return 0;
}
// 目录不存在,创建目录,权限设置为755(rwxr-xr-x)
mkdir(cPath, 755);
// 设置目录权限
chmod(path);
} // 修改文件/目录权限的函数
function chmod(path) {
// 获取libc.so中chmod函数的地址
var chmodPtr = Module.getExportByName('libc.so', 'chmod');
// 创建chmod函数的NativeFunction包装
var chmod = new NativeFunction(chmodPtr, 'int', ['pointer', 'int']);
// 将路径字符串转换为C字符串
var cPath = Memory.allocUtf8String(path);
// 设置权限为755(rwxr-xr-x)
chmod(cPath, 755);
} // DEX文件dump的核心函数
function dump_dex() {
// 查找libart.so模块,这是Android Runtime的核心库
var libart = Process.findModuleByName("libart.so");
// 初始化DefineClass函数地址为null
var addr_DefineClass = null;
// 枚举libart.so中的所有符号
var symbols = libart.enumerateSymbols();
// 遍历所有符号,查找DefineClass函数
for (var index = 0; index < symbols.length; index++) {
var symbol = symbols[index];
var symbol_name = symbol.name;
// 这个DefineClass的函数签名是Android9的
// _ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE
// 通过符号名称特征匹配DefineClass函数 // _ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE // 拆解后就是: // 符号部分 含义(“地址”组成部分) 通俗解释
// _ZN ... E 开始和结束标记 这是一个“修饰名”的包裹。
// 3art 命名空间 (Namespace) art。Android Runtime,就是安卓的系统核心。
// 11ClassLinker 类名 (Class) ClassLinker 类。这是ART里一个负责加载和链接类的“管理员”。
// 11DefineClass 函数名 (Function) DefineClass 方法。这是这个“管理员”的核心工作:定义一个类。
// EPNS_6ThreadE 参数1 (Parameter 1) art::Thread*。需要一个线程指针。就像办事要指明是哪个“工作人员”在处理。
// PKc 参数2 (Parameter 2) const char*。一个字符串,通常是类的描述符(如 "java/lang/String")。
// m 参数3 (Parameter 3) size_t。一个数字,表示上面字符串的长度。
// PNS_6HandleINS_6mirror11ClassLoaderEEE 参数4 (Parameter 4) art::Handle<art::mirror::ClassLoader>。一个类加载器对象的句柄。告诉系统用哪个“工具箱”(比如App自己的还是系统的)来加载这个类。
// RKNS_7DexFileE 参数5 (Parameter 5) const art::DexFile&。一个Dex文件的常量引用。这是最重要的参数!它告诉管理员:“请从这个DEX文件包裹里”取出类来。
// RKNS9_8ClassDefE 参数6 (Parameter 6) const art::DexFile::ClassDef&。一个类定义的常量引用。它进一步指明:“就取这个包裹里特定的那一份文件(类定义)”。
// 所以,这个函数到底是干嘛的?
// 它的核心工作就一件事: // 当一个Android App运行时,系统需要把DEX文件(打包好的Java代码)里的类加载到内存中才能执行。 // 这个 DefineClass 函数就是ART虚拟机里负责这项工作的“首席加载官”。 // 调用它的过程,就像是下指令:
// “喂!ART系统的ClassLinker管理员!(3art11ClassLinker)
// 现在请你 (11DefineClass):
// 在当前这个线程 (EPNS_6ThreadE) 上,
// 根据这个名字叫"com/example/MyClass" (PKc) 长度是XX (m) 的类,
// 使用App提供的这个类加载器 (PNS_6HandleINS_6mirror11ClassLoaderEEE),
// 从这个DEX文件里 (RKNS_7DexFileE),
// 找到这个类的具体定义数据 (RKNS9_8ClassDefE),
// 然后把它在内存里创建出来!” // 为什么Dump Dex的脚本要Hook它?
// 这正是脚本聪明的地方! // 时机完美:这个函数被调用时,意味着系统正在主动地读取并加载一个DEX文件中的类。此时,整个DEX文件肯定已经完整地映射到内存中了。 // 信息齐全:这个函数的参数就像一个“情报包”,直接包含了两个关键情报: // RKNS_7DexFileE: DexFile对象的内存地址。通过这个对象,脚本就能顺藤摸瓜找到DEX文件在内存中的起始地址 (begin_) 和大小 (size_)。 // 这样,脚本就不需要漫无目的地搜索内存,而是在这个函数被调用时,直接“领取”了DEX文件的地址和大小,然后把它 dump 到磁盘上。 // 总结一下:这个奇怪的字符串就是ART虚拟机里“加载类”这个核心功能员的完整身份证。Hook它,就能在最合适的时机、用最直接的方式,拿到我们想要dump的DEX文件的内存地址。 if (symbol_name.indexOf("ClassLinker") >= 0 &&
symbol_name.indexOf("DefineClass") >= 0 &&
symbol_name.indexOf("Thread") >= 0 &&
symbol_name.indexOf("DexFile") >= 0) {
console.log(symbol_name, symbol.address);
// 保存找到的DefineClass函数地址
addr_DefineClass = symbol.address;
}
}
// 用于存储已发现的DEX文件映射(基址->大小)
var dex_maps = {};
// DEX文件计数器,用于生成文件名
var dex_count = 1; console.log("[DefineClass:]", addr_DefineClass);
// 如果找到了DefineClass函数
if (addr_DefineClass) {
// hook DefineClass函数
Interceptor.attach(addr_DefineClass, {
// 函数调用前的回调
onEnter: function (args) { // _ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE //真实的参数列表是这样的: // 序号 参数 对应 args[]
// 0 this (指向 ClassLinker 对象的指针) args[0]
// 1 art::Thread* thread args[1]
// 2 const char* descriptor args[2]
// 3 size_t hash args[3]
// 4 art::Handle... class_loader args[4]
// 5 const art::DexFile& dex_file <-- 目标 args[5]
// 6 const art::DexFile::ClassDef& class_def args[6]
// 结论:
// dex_file 参数在函数的正式参数列表中排在第5位(从0开始数),是因为它前面还有一个看不见的“第0号”参数——this 指针。 // 所以,args[5] 取到的就是传递给 DefineClass 函数的 dex_file 参数。 var dex_file = args[5]; // 这是DEX文件在内存中的起始地址
var base = ptr(dex_file).add(Process.pointerSize).readPointer();
// 这是DEX文件的大小
var size = ptr(dex_file).add(Process.pointerSize + Process.pointerSize).readUInt(); // 如果这个DEX文件还没有被记录过
if (dex_maps[base] == undefined) {
// 记录DEX文件的基址和大小
dex_maps[base] = size;
// 读取DEX文件的魔数(前几个字节)
var magic = ptr(base).readCString();
// 检查是否是有效的DEX文件(以"dex"开头)
if (magic.indexOf("dex") == 0) {
// 获取当前进程名
var process_name = get_self_process_name();
if (process_name != "-1") {
// 构建dump目录路径
var dex_dir_path = "/data/data/" + process_name + "/files/dump_dex_" + process_name;
// 创建dump目录
mkdir(dex_dir_path);
// 构建DEX文件路径,第一个文件名为class.dex,后续为class2.dex, class3.dex...
var dex_path = dex_dir_path + "/class" + (dex_count == 1 ? "" : dex_count) + ".dex";
console.log("[find dex]:", dex_path);
// 创建文件用于写入
var fd = new File(dex_path, "wb");
if (fd && fd != null) {
// 增加DEX文件计数
dex_count++;
// 从内存中读取完整的DEX文件数据
var dex_buffer = ptr(base).readByteArray(size);
// 写入文件
fd.write(dex_buffer);
// 刷新缓冲区
fd.flush();
// 关闭文件
fd.close();
console.log("[dump dex]:", dex_path);
}
}
}
}
},
// 函数调用后的回调(这里为空)
onLeave: function (retval) { }
});
}
} // 标记是否已经hook了libart.so
var is_hook_libart = false; // hook动态库加载函数的函数
function hook_dlopen() {
// hook标准的dlopen函数 在较老版本的Android,或者一些非常规的、直接调用标准C库的场景中使用。
Interceptor.attach(Module.findExportByName(null, "dlopen"), {
// dlopen调用前的回调
onEnter: function (args) {
// args[0]是库文件路径参数
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
// 读取库文件路径字符串
var path = ptr(pathptr).readCString();
//console.log("dlopen:", path);
// 如果正在加载libart.so
if (path.indexOf("libart.so") >= 0) {
// 标记可以hook libart
this.can_hook_libart = true;
console.log("[dlopen:]", path);
}
}
},
// dlopen调用后的回调
onLeave: function (retval) {
// 如果可以hook libart且还没有hook过
if (this.can_hook_libart && !is_hook_libart) {
// 开始dump DEX文件
dump_dex();
// 标记已经hook过了
is_hook_libart = true;
}
}
}) // hook Android特有的android_dlopen_ext函数
//例如 Java 代码中加载原生库
// static {
// System.loadLibrary("my-native-lib"); // 这会触发 dlopen 或 android_dlopen_ext
// } 在现代Android版本中,系统内部加载核心库(如 libart.so)时,更倾向于使用这个功能更强的函数。
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
// 调用前的回调
onEnter: function (args) {
// args[0]是库文件路径参数
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
// 读取库文件路径字符串
var path = ptr(pathptr).readCString();
//console.log("android_dlopen_ext:", path);
// 如果正在加载libart.so
if (path.indexOf("libart.so") >= 0) {
// 标记可以hook libart
this.can_hook_libart = true;
console.log("[android_dlopen_ext:]", path);
}
}
},
// android_dlopen_ext调用后的回调
onLeave: function (retval) {
// 如果可以hook libart且还没有hook过
if (this.can_hook_libart && !is_hook_libart) {
// 开始dump DEX文件
dump_dex();
// 标记已经hook过了
is_hook_libart = true;
}
}
});
} // 立即执行dump_dex函数(如果libart.so已经加载)
setImmediate(dump_dex);
脱出所有dex和so的测试脚本
https://gitee.com/null_465_7266/dump-all-so/tree/master
使用方法
PS C:\Users\21558\Documents\dumpallso\dump-all-so> python dump_so.py -p com.shizhuang.duapp --app-path "/data/app/~~YxjKiMfU5GhbqDTDhPhhpw==/com.shizhuang.duapp-f6V4lziH2H4ySLWwb1S4_A==/lib/arm64
应用安全 --- frida脚本 之 dump 自动化动脱 so的更多相关文章
- 通过shell脚本实现代码自动化部署
通过shell脚本实现代码自动化部署 一.传统部署方式及优缺点 1.传统部署方式 (1)纯手工scp (2)纯手工登录git pull.svn update (3)纯手工xftp往上拉 (4)开发给打 ...
- 基于脚本的modelsim自动化仿真笔记
这里记录一下基于脚本的modelsim自动化仿真的一些知识和模板,以后忘记了可以到这里查找.转载请标明出处:http://www.cnblogs.com/IClearner/ . 一.基本介绍 这里介 ...
- 持续集成之⑤:jenkins结合脚本实现代码自动化部署及一键回滚至上一版本
持续集成之⑤:jenkins结合脚本实现代码自动化部署及一键回滚至上一版本 一:本文通过jenkins调用shell脚本的的方式完成从Git服务器获取代码.打包.部署到web服务器.将web服务器从负 ...
- jenkins结合脚本实现代码自动化部署及一键回滚至上一版本
持续集成之⑤:jenkins结合脚本实现代码自动化部署及一键回滚至上一版本 一:本文通过jenkins调用shell脚本的的方式完成从Git服务器获取代码.打包.部署到web服务器.将web服务器从负 ...
- awr脚本使用dump导出导入
实际工作中,存在这么一种场景.客户现场分析问题,无法立即得出结论,且无法远程服务器,因此对于服务器中的awr信息,如何提取是一个问题,oracle有脚本可以对服务器中以db为单位导出awr基表的dum ...
- svn备份与还原_脚本_(dump命令)
今天备份svn, 能保证好用就行先, 回头再研究 buerguo.bat @echo off :: 关闭回显 :: 说明:如有命令不明白,请使用帮助命令:命令/? .如:for/? :: 设置标题 t ...
- AutoIt脚本在做自动化操作的时候,如何进行错误捕获?
我的自动化脚本在运行的时候,会生成一个界面,点击该页面上的按钮能够进行自动化操作. 经常遇到的一个问题是: 脚本运行一半,GUI程序出现了异常情况,这个时候,再次点击生成的界面上的按钮,不会有任何反应 ...
- Python脚本之——API自动化框架总结
学完了Python脚本接口自动化之后,一直没有对该框架做总结,今天终于试着来做一份总结了. 框架结构如下图: 来说一下每个目录的作用: Configs:该目录下存放的是.conf,.ini文件格式的配 ...
- 你在和脚本谈恋爱(自动化在IM聊天中的应用)
谢谢打开这篇文章的每个你 测开之分层自动化(Python)招生简章 Python自动化测试报告美化 在python中进行数据驱动测试 太嚣张了!他竟用Python绕过了“验证码” 在网络世界里你不知道 ...
- python脚本实现接口自动化轻松搞定上千条接口用例
接口自动化目前是测试圈主流的一个话题,我也在网上搜索了很多关于自动化的关键词,大多数博主分享的python做接口自动化都是以开源的框架,比如:pytest.unittest+ddt(数据驱动) 最常见 ...
随机推荐
- win11 24h2系统更新后右键没反应的问题
有雨林木风官网的用户,已经在电脑上更新到最新版win11 24h2系统了,但是更新后,却出现使用鼠标右键没有反应的问题,该如何解决呢?本文中,雨林木风小编将于大家分享详细的处理方法,有需要的朋友可以一 ...
- 如何在 Git 中控制某些文件不被提交?
回答重点 在 Git 中控制某些文件不被提交的主要方法是使用 .gitignore 文件.通过在 .gitignore 文件中列出你不希望被提交的文件或文件夹路径,Git 就会自动忽略这些文件,不会将 ...
- Golang 文本模板,你指定没用过
最近在倒腾"AI大模型基础设施", 宏观目标是做一个基于云原生的AI算力平台,目前因公司隐私暂不能公开宏观背景和技术方案, 姑且记录实践中遇到的一些技能点. Arena是阿里云开源 ...
- 虚拟机-Linux开发板交叉编译问题记录
遇到一堆很久之前见过的问题,重新解决一次. 1.虚拟机没法上网 发现虚拟机浏览器上不了网,运行ifconfig查看,发现要么没有IP地址,要么只有IPv6的地址.最后发现是昨天VMware卡死了,启动 ...
- mdadm 和 LVM 存储管理工具区别
mdadm 和 LVM 是 Linux 系统中两种不同的存储管理工具,核心目标和技术原理存在本质差异.虽然都涉及多块硬盘的管理,但解决的问题和应用场景截然不同.以下是详细对比及实际场景示例: ...
- 前端知识之CSS(3)-盒子模型、浮动布局、溢出属性、定位、脱离文档流、z-index之模态框
目录 盒子模型 浮动布局(float) 1.什么是浮动 2.浮动的作用 3.浮动有俩个特点 4.浮动(float)格式 5.浮动会造成父标签塌陷 这是一个不好的现象 因为会引起歧义 6.解决父标签塌陷 ...
- [题解]P3200 [HNOI2009] 有趣的数列
P3200 [HNOI2009] 有趣的数列 给出另一种转化思路,模拟赛的时候想到的. 将我们构造的序列看作 \(n\) 个点 \((a_1,a_2),(a_3,a_4),\dots,(a_{2n-1 ...
- [笔记]树形dp - 1/4(节点选择类)
树形dp,是一种建立在树形结构上的dp,因此dfs一般是实现它的通用手段. 是一种很美的动态规划呢. P1352 没有上司的舞会 P1352 没有上司的舞会. 在一棵树中,找到若干个互相独立(即互相没 ...
- 修改linux ll 命令的日期显示格式
0.ls -lh ( ll -h )查看文件的详细信息,显示的日期格式是英文,不直观. 1.vi ~/.bash_profile,打开配置文件并增加环境变量.文件末尾添加这行 export TIME ...
- 洛谷 P2971 [USACO10HOL] Cow Politics G 题解
怎么没有树上启发式合并的题解呢?我来发一篇吧! 简化题意 给定一棵 \(n\) 个点的树,每个点属于 \(k\) 种颜色之一(每种颜色至少有 2 个点).求每种颜色中,任意两点间的最大距离. 核心思想 ...