本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78092365

Dalvik模式下的Android加固技术已经很成熟了,Dalvik虚拟机模式下的Android加固技术也在不断的发展和加强,鉴于Art虚拟机比Dalvik虚拟机的设计更复杂,Art虚拟机模式下兼容性更严格,一些Dalvik虚拟机模式下的Android加固技术并不能马上移植到Art模式下以及鉴于Art虚拟机模式下的设计复杂和兼容性考虑,暂时相对来说,Art模式下的Android加固并没有Dalvik虚拟机模式下的粒度细和强。

本文给出的 Art模式下基于Xposed Hook开发脱壳工具的思路和流程不是我原创,主要是源自于看雪论坛的文章《一个基于xposed和inline hook的一代壳脱壳工具》,思路和流程有原作者smartdon提供,文中提到的Art模式下的dexdump脱壳工具源码github下载地址:https://github.com/smartdone/dexdump。原作者提供的脱壳操作步骤稍微复杂了一些,在此基础上我对原作者的代码进行了修改,使脱壳更加方便,原作者的代码是Android Studio的工程,顺手将其转化为了Eclipse下的工程。作者smartdon的Art模式下脱壳思路如下图所示:

要学习Android加固的脱壳还是需要先了解一下Dalvik模式下和Art模式下Android加固的流程和思路,熟悉一下 DexClassLoader 的代码执行流程。虽然Dalvik模式下和Art模式下DexClassLoader的java层实现代码是一样的,但是从 OpenDexFileNative函数 之后Dalvik模式下和Art模式下Native层的代码实现就不一样了,后面有空花时间整理一下Android加固相关方面的知识。Art模式下基于Xposed Hook开发的脱壳工具只对整体dex加固的Android应用脱壳才有效果,对于dex文件类方法抽离这类加固处理的Android应用就显得比较苍白了。

ART模式下基于Xposed Hook开发脱壳工具的思路整理。

1. Art模式下,Inline Hook时机 的选择

Android加固的一般思路:在外壳Apk应用调用 android.app.Application类 的成员函数 attach 时,内存解密出被保护的原始dex文件使用DexClassLoader进行内存加载,Art虚拟机模式下DexClassLoader进行dex文件的加载过程中绕不开函数 const DexFile* DexFile::OpenMemory(const std::string& location, uint32_t location_checksum, MemMap* mem_map, std::string* error_msg),因此我们选择在 art::DexFile::OpenMemory函数 处进行dex文件的内存dump处理。基于Art模式下的Xposed Hook实现在外壳apk应用调用 android.app.Application类 的成员函数 attach 时,在被保护的dex文件加载之前Inline Hook OpenMemory函数,对内存解密后的原始dex文件进行拦截。

Art模式下,Xposed Hook外壳apk应用 android.app.Application类(实现的代理子类) 的成员函数 attach。

2. Art模式下,Inline Hook函数点 的选择

Art虚拟机模式下,对 art::DexFile::OpenMemory函数 进行Inline Hook操作所采用的Hook框架为作者 Ele7enxxh 编写的Android平台的Inline Hook库。作者Ele7enxxh关于该Inline Hook库的介绍和描述可以参考作者的博文《Android Arm Inline Hook》,该Inline Hook库的github下载地址为:https://github.com/ele7enxxh/Android-Inline-Hook。由于Art虚拟机模式下,dex文件的加载DexClassLoader的代码实现流程中绕不开art::DexFile::OpenMemory函数的执行,更重要的是该函数的传入参数 base表示的是dex文件所在的内存地址, size表示的是dex文件的字节长度,因此选择在art::DexFile::OpenMemory函数处进行dex文件的内存dump处理。

Android 5.0版以后ART模式下,OpenMemory函数的形式:http://androidxref.com/5.0.0_r2/xref/art/runtime/dex_file.cc#325

ART模式下基于Xposed Hook和Ele7enxxh Inline Hook开发的脱壳工具dexdump的代码详细分析。

1. 作者smartdon写了个获取当前安装应用的列表界面,用以选择需要脱壳的apk应用,然后根据选择脱壳apk应用的包名,在sdcard文件夹下生成脱壳需要的配置文件dumdex.js,dumdex.js文件中保存着需要脱壳的apk应用的包名。

选择脱壳apk应用列表界面的实现代码 MainActivity.java:

package com.xposedhook.dexdump;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.pm.PackageInfo;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ListView;
import android.widget.TextView; import java.util.ArrayList;
import java.util.List; import com.example.com.xposedhook.dexdump.R; public class MainActivity extends Activity { // static {
//
// // 加载动态库文件libhook.so
// System.loadLibrary("hook");
// } private List<Appinfo> appinfos;
private ListView listView;
private AppAdapter adapter;
private List<String> selected; @Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
// 设置布局文件
setContentView(R.layout.activity_main); // 调用native层实现的dump函数
// 对函数art::DexFile::OpenMemory进行Hook处理
// Dumpper.dump(); // 读取配置文件"/sdcard/dumdex.js"获取需要脱壳的apk应用的包名列表
selected = Config.getConfig(); // Apk应用信息列表
appinfos = new ArrayList<>(); // 用于显示apk应用的列表
listView = (ListView) findViewById(R.id.applist);
adapter = new AppAdapter(appinfos);
// 设置ListView控件的适配器
listView.setAdapter(adapter); // 创建线程
new Thread(){
@Override
public void run() {
super.run(); // 获取当前Android系统安装的apk应用列表
getInstallAppList();
}
}.start();
} private void getInstallAppList() { try{ // 获取当前安装应用的PackageInfo列表
List<PackageInfo> packageInfos = getPackageManager().getInstalledPackages(0);
// 遍历当前安装应用的PackageInfo列表
for(PackageInfo packageInfo : packageInfos) { Appinfo info = new Appinfo();
// 设置apk应用的名称
info.setAppName(packageInfo.applicationInfo.loadLabel(getPackageManager()).toString());
// 设置apk应用的包名
info.setAppPackage(packageInfo.packageName); // 根据当前遍历到apk应用的包名是否在配置文件中设置选中与否的现实
if(Config.contains(selected, info.getAppPackage())) { info.setChecked(true);
}else { info.setChecked(false);
}
// 添加当前遍历到apk应用的信息到apk应用的现实列表中
appinfos.addAll(info); // 更新适配器的现实
adapter.notifyDataSetChanged();
}
}catch (Exception e) {
e.printStackTrace();
}
} // ListView列表的适配器
@SuppressLint({ "ViewHolder", "InflateParams" })
class AppAdapter extends BaseAdapter{ private List<Appinfo> appinfos; public AppAdapter(List<Appinfo> appinfos){
this.appinfos = appinfos;
} @Override
public int getCount() {
return appinfos.size();
} @Override
public Object getItem(int i) {
return appinfos.get(i);
} @Override
public long getItemId(int i) {
return i;
} @Override
public View getView(int i, View view, ViewGroup viewGroup) { View v = LayoutInflater.from(MainActivity.this).inflate(R.layout.item, null);
final int posi = i;
final TextView appname = (TextView) v.findViewById(R.id.tv_appname);
appname.setText(appinfos.get(i).getAppName());
TextView appPackage = (TextView) v.findViewById(R.id.tv_apppackage);
appPackage.setText(appinfos.get(i).getAppPackage());
CheckBox checkBox = (CheckBox) v.findViewById(R.id.cb_select); if(appinfos.get(i).isChecked()) {
checkBox.setChecked(true);
}else {
checkBox.setChecked(false);
} // 监控apk应用列表的选中事件
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) { if(b) { // 添加选中的apk应用的包名到配置文件/sdcard/dumdex.js中
// 格式: ["apk包名字符串"]
Config.addOne(appinfos.get(posi).getAppPackage()); } else { // 从配置文件/sdcard/dumdex.js中删除指定包名的apk引用
Config.removeOne(appinfos.get(posi).getAppPackage());
}
}
}); return v;
}
}
}

根据用户选择的脱壳apk应用的包名,生成脱壳需要的配置文件dumdex.js文件的代码 Config.java:

package com.xposedhook.dexdump;

import android.util.Log;

import org.json.JSONArray;
import org.json.JSONObject; import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List; /**
* Created by smartdone on 2017/7/2.
*/ public class Config { // sdcard的问价路径最好还是通过函数来获取
// File file=Environment.getExternalStorageDirectory();
// 直接写死有兼容性的问题
private static final String FILENAME = "/sdcard/dumdex.js"; // 将JSONArray类型的数据写入到配置文件"/sdcard/dumdex.js"中
public static void writeConfig(String s) { try { // 文件"/sdcard/dumdex.js"的文件写入流
FileOutputStream fout = new FileOutputStream(FILENAME);
// 将字符串写入到文件中
fout.write(s.getBytes("utf-8"));
// 刷新文件流
fout.flush();
fout.close(); } catch (Exception e) {
e.printStackTrace();
}
} // 添加Apk应用的包名到配置文件/sdcard/dumdex.js
public static void addOne(String name) { List<String> ori = getConfig();
if(ori == null) { JSONArray jsonArray = new JSONArray();
jsonArray.put(name);
writeConfig(jsonArray.toString()); } else { ori.add(name);
JSONArray jsonArray = new JSONArray();
for(String o : ori) {
jsonArray.put(o);
}
writeConfig(jsonArray.toString());
}
} // 从配置文件/sdcard/dumdex.js中删除指定包名的应用
public static void removeOne(String name) { List<String> ori = getConfig(); if(ori != null) { for(int i = 0; i < ori.size(); i++) { if(ori.get(i).equals(name)) { ori.remove(i);
}
} JSONArray jsonArray = new JSONArray();
for(String s : ori) { jsonArray.put(s);
} writeConfig(jsonArray.toString());
}
} // 读取配置文件"/sdcard/dumdex.js"获取需要脱壳的apk应用的包名列表
public static List<String> getConfig() { // 打开文件"/sdcard/dumdex.js"
File file = new File(FILENAME);
// 判断文件是否存在
if (file.exists()) {
try { // 构建内存缓冲区读取流
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
// 分行读取文件数据
String line = br.readLine();
// 使用读取的一行文件数据构建JSONArray对象
JSONArray jsonArray = new JSONArray(line); List<String> apps = new ArrayList<>();
// 解析JSONArray数据将需要Hook的apk应用的包名添加到列表中
for(int i = 0; i < jsonArray.length(); i++) { apps.add(jsonArray.getString(i));
} br.close();
// Log.e("DEX_DUMP", "需要hook的列表: " + line); return apps; } catch (Exception e) {
e.printStackTrace();
}
} return null;
} // 判断name是否在需要脱壳的apk应用的列表中
public static boolean contains(List<String> lists, String name) { if(lists == null) {
return false;
} for(String l : lists) { if(l.equals(name)) {
return true;
}
} return false;
} }

2. 基于Art虚拟机模式下的Xposed框架 Hook外壳Apk应用 android.app.Application类 的成员函数 attach,这里提到的Xposed Hook框架需要注意一下,不能使用支持Android 4.4.x版本之前的Xposed Hook框架(只支持Dalvik虚拟机模式,不支持Art虚拟机模式),需要使用支持Android 5.0版本以后的Xposed Hook框架(支持Art虚拟机模式),Xposed Hook框架的下载地址可以参考:http://repo.xposed.info/module/de.robv.android.xposed.installer

Art虚拟机模式下,Xposed框架 Hook外壳Apk应用 android.app.Application类 的成员函数 attach 的模块代码 com.xposedhook.dexdump.Main 编写的实现:

package com.xposedhook.dexdump;

import android.content.Context;
import android.util.Log; import java.util.List; import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage; /**
* Created by smartdone on 2017/7/1.
*/ // art模式下的Xposed Hook
public class Main implements IXposedHookLoadPackage { private static final String TAG = "DEX_DUMP"; @Override
public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { // 从配置文件"/sdcard/dumdex.js"中获取需要脱壳的apk应用列表
List<String> hooklist = Config.getConfig(); // 判断当前应用是否在需要脱壳的apk应用列表中
if(!Config.contains(hooklist, loadPackageParam.packageName))
return; XposedBridge.log("对" + loadPackageParam.packageName + "进行处理");
Log.e(TAG, "开始处理: " + loadPackageParam.packageName); try{ // 自定义加载动态库文件libhook.so
// 可以试着使用兼容性好的Android系统函数来处理路径问题
System.load("/data/data/com.xposedHook.dexdump/lib/libhook.so"); } catch (Exception e) {
Log.e(TAG, "加载动态库失败:" + e.getMessage());
}
Log.e(TAG, "加载动态库成功"); // 对Android系统类android.app.Application的attach函数进行art模式下的Hook操作
XposedHelpers.findAndHookMethod("android.app.Application",
loadPackageParam.classLoader,
"attach",
Context.class,
new XC_MethodHook() { private Context context;
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable { super.beforeHookedMethod(param); // 在类android.app.Application的attach函数调用之前进行dex文件的内存dump操作
Dumpper.dump();
} @Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
// 不处理
}
});
}
}

3.在需要脱壳的apk应用进程里动态加载动态库文件/data/data/com.xposedHook.dexdump/lib/libhook.so,实现Art模式下对 const DexFile* DexFile::OpenMemory(const std::string& location, uint32_t location_checksum, MemMap* mem_map, std::string* error_msg)函数 的Inline Hook操作,在Inline Hook操作的自定义实现函数里进行dex文件的内存dump处理。使用Ele7enxxh Inline Hook框架对Art模式下的OpenMemory函数进行Hook操作的实现代码如下:

//
// Created by 袁东明 on 2017/7/1.
// extern "C" {
#include "include/inlineHook.h"
} #include "dump.h"
#include <unistd.h>
#include <android/log.h>
#include <sys/system_properties.h>
#include <stdlib.h>
#include <fcntl.h>
#include <time.h>
#include <string>
#include <dlfcn.h>
#include <dlfcn.h> #define TAG "DEX_DUMP" int isArt();
void getProcessName(int pid, char *name, int len);
void dumpFileName(char *name, int len, const char *pname, int dexlen); // 保存当前apk进程的进程名字
static char pname[256]; // 判断当前所处环境是否是Android art虚拟机模式
int isArt() { char version[10]; // 获取ro.build.version.sdk的属性值
__system_property_get("ro.build.version.sdk", version);
// 打印当前Android系统的api版本信息
__android_log_print(ANDROID_LOG_INFO, TAG, "api level %s", version); // 将api版本转换成int型版本号
int sdk = atoi(version);
// 判断api版本是否是大于21(要求Android系统的版本为 Android 5.0以上 才可以)
if (sdk >= 21) { // art虚拟机模式
return 1;
} return 0;
} // 读取/proc/self/cmdline文件的数据,获取当前apk进程的进程名字
void getProcessName(int pid, char *name, int len) { int fp = open("/proc/self/cmdline", O_RDONLY);
memset(name, 0, len);
read(fp, name, len);
close(fp);
} // 格式字符串构建dump的dex文件的路径字符串
void dumpFileName(char *name, int len, const char *pname, int dexlen) { time_t now;
struct tm *timenow;
time(&now);
// 获取当前时间(值得借鉴和学习)
timenow = localtime(&now); memset(name, 0, len);
// 格式化字符串得到当前dump的dex文件路径字符串
sprintf(name, "/data/data/%s/dump_size_%u_time_%d_%d_%d_%d_%d_%d.dex", pname, dexlen,
timenow->tm_year + 1900,
timenow->tm_mon + 1,
timenow->tm_mday,
timenow->tm_hour,
timenow->tm_min,
timenow->tm_sec);
} void writeToFile(const char *pname, u_int8_t *data, size_t length) { char dname[1024]; // pname为当前进程的名称
// 格式字符串构建dump的dex文件的路径字符串dname
dumpFileName(dname, sizeof(dname), pname, length);
__android_log_print(ANDROID_LOG_ERROR, TAG, "dump dex file name is : %s", dname); __android_log_print(ANDROID_LOG_ERROR, TAG, "start dump");
// 根据dname创建新文件用于保存内存dump的dex文件
int dex = open(dname, O_CREAT | O_WRONLY, 0644);
if (dex < 0) { __android_log_print(ANDROID_LOG_ERROR, TAG, "open or create file error");
return;
} // 将内存dex文件的数据写入到新的dname文件中
int ret = write(dex, data, length);
if (ret < 0) { __android_log_print(ANDROID_LOG_ERROR, TAG, "write file error");
} else { __android_log_print(ANDROID_LOG_ERROR, TAG, "dump dex file success `%s`", dname);
} // 关闭文件
close(dex);
} // 保存openmemory函数旧的地址
art::DexFile *(*old_openmemory)(const byte *base, size_t size, const std::string &location,
uint32_t location_checksum, art::MemMap *mem_map,
const art::OatDexFile *oat_dex_file, std::string *error_msg) = NULL; art::DexFile *new_openmemory(const byte *base, size_t size, const std::string &location,
uint32_t location_checksum, art::MemMap *mem_map,
const art::OatDexFile *oat_dex_file, std::string *error_msg) { __android_log_print(ANDROID_LOG_ERROR, TAG, "art::DexFile::OpenMemory is called"); writeToFile(pname, (uint8_t *) base, size); // 调用原art::DexFile::OpenMemory函数
return (*old_openmemory)(base, size, location, location_checksum, mem_map, oat_dex_file,
error_msg);
} void hook() { // 加载动态库文件libart.so
void *handle = dlopen("libart.so", RTLD_GLOBAL | RTLD_LAZY);
if (handle == NULL) { __android_log_print(ANDROID_LOG_ERROR, TAG, "Error: unable to find the SO : libart.so");
return;
} // 获取导出函数const DexFile* DexFile::OpenMemory(const std::string& location, uint32_t location_checksum, MemMap* mem_map, std::string* error_msg)
// 的调用地址,http://androidxref.com/5.0.0_r2/xref/art/runtime/dex_file.cc#325
// 在不同的Android版本上OpenMemory函数的名称粉碎稍有不同,需要根据实际Android系统版本进行修改
void *addr = dlsym(handle,
"_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_");
if (addr == NULL) { __android_log_print(ANDROID_LOG_ERROR, TAG,
"Error: unable to find the Symbol : _ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_");
return;
} // 使用ele7enxxh写的inline Hook框架对art模式下的art::DexFile::OpenMemory函数进行inline Hook操作
// 进行art::DexFile::OpenMemory函数inline Hook操作的Hook注册
if (registerInlineHook((uint32_t) addr, (uint32_t) new_openmemory,
(uint32_t **) &old_openmemory) != ELE7EN_OK) { __android_log_print(ANDROID_LOG_ERROR, TAG, "register inline hook failed");
return;
} // 对art模式下的art::DexFile::OpenMemory函数进行inline Hook操作
if (inlineHook((uint32_t) addr) != ELE7EN_OK) { __android_log_print(ANDROID_LOG_ERROR, TAG, "inline hook failed");
return;
} __android_log_print(ANDROID_LOG_INFO, TAG, "inline hook success");
} // java方法Dumpper.dump()的native层实现
// com.xposedhook.dexdump.Dumpper.dump
JNIEXPORT void JNICALL Java_com_xposedhook_dexdump_Dumpper_dump(JNIEnv *env, jclass clazz) { // 获取当前apk进程的进程名字
getProcessName(getpid(), pname, sizeof(pname)); // 判断当前Android虚拟机是否是art模式
if (isArt()) { // 当前Android系统运行在art模式下
// 执行inline Hook操作
hook();
}
}

4. 使用当前脱壳工具进行Art虚拟机模式下的Android加固脱壳需要注意的地方:

A. 移动设备的Android系统必须是 Api 21以上即Android 5.0以上版本的Android系统并且要运行在Art虚拟机模式下,不能运行在Dalvik虚拟机模式下。

B. 移动设备上安装的Xposed Hook框架必须是支持Android 5.0以上版本的ART虚拟机模式下的Xposed Hook框架。

C. 第1次使用该脱壳工具com.xposedhook.dexdump.apk时,先使用apk应用列表界面选择需要脱壳的apk应用,生成后面脱壳需要的配置文件"/sdcard/dumdex.js"。

D. 重启移动设备使Xposed框架的Hook模块com.xposedhook.dexdump.Main生效,运行需要脱壳的apk应用等待脱壳完成,脱壳后的dex文件在 /data/data/脱壳apk应用的包名/ 文件夹下。

注释版完整的代码下载地址:http://download.csdn.net/download/qq1084283172/9996172

ART模式下基于Xposed Hook开发脱壳工具的更多相关文章

  1. 基于dalvik模式下的Xposed Hook开发的某加固脱壳工具

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/77966109 这段时间好好的学习了一下Android加固相关的知识和流程也大致把A ...

  2. ART模式下基于dex2oat脱壳的原理分析

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78513483 一般情况下,Android Dex文件在加载到内存之前需要先对dex ...

  3. 基于Frida框架打造Art模式下的脱壳工具(OpenMemory)的原理分析

    本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80956614 作者dstmath在看雪论坛公布一个Android的art模式下基 ...

  4. Dalvik模式下基于Android运行时类加载的函数dexFindClass脱壳

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78003184 前段时间在看雪论坛发现了<发现一个安卓万能脱壳方法>这篇 ...

  5. Android逆向进阶—— 脱壳的奥义(基ART模式下的dump)

    本文作者:i春秋作家HAI_ZHU 0×00 前言 市面上的资料大多都是基于Dalvik模式的dump,所以这此准备搞一个ART模式下的dump.HAI_的使用手册(各种好东西) Dalvik模式是A ...

  6. 查找和定位Android应用的按钮点击事件的代码位置基于Xposed Hook实现

    本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80956455 在进行Android程序的逆向分析的时候,经常需要通过Androi ...

  7. 基于xposed Hook框架实现个人免签支付方案

    我的个人网站如何实现支付功能? 想必很多程序员都有过想开发一个自己的网站来获得一些额外的收入,但做这件事会遇到支付这个问题.目前个人网站是无法实现支付功能的. 今天我就给大家分享一下我的实现方案:&l ...

  8. 基于Xposed Hook实现的Android App的协议算法分析小工具-CryptoFucker

    本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80962121 在进行Android应用的网络协议分析的时候,不可避免涉及到网络传 ...

  9. 基于Xposed hook 实时监测微信消息

    本文以微信版本6.7.3为例进行分析有hook, 大部分做微信机器人的话,首先要实时抓取微信的消息,在这里展示三种方式对微信的消息进行hook: 1.基于UI层拉取加载进行监听 2.基于微信dao层调 ...

随机推荐

  1. PAT-1066(Root of AVL Tree)Java语言实现

    Root of AVL Tree PAT-1066 这是关于AVL即二叉平衡查找树的基本操作,包括旋转和插入 这里的数据结构主要在原来的基础上加上节点的高度信息. import java.util.* ...

  2. POJ-2195(最小费用最大流+MCMF算法)

    Going Home POJ-2195 这题使用的是最小费用流的模板. 建模的时候我的方法出现错误,导致出现WA,根据网上的建图方法没错. 这里的建图方法是每次到相邻点的最大容量为INF,而花费为1, ...

  3. div中如何让文本元素、img元素水平居中且垂直居中

    一.文本元素在div中的水平居中且垂直居中方法 html代码 <div id="box"> <p>文本元素</p> </div> c ...

  4. CVE-2019-10758-Mongo-express-远程代码执行

    漏洞分析 https://xz.aliyun.com/t/7056 漏洞简介 mongo-express是一款mongodb的第三方Web界面,使用node和express开发. 如果攻击者可以成功登 ...

  5. springmvc 最权威的知识点

    1.什么是Spring MVC ?简单介绍下你对springMVC的理解? Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,C ...

  6. Day2:Windows常用快捷键与基本的Dos命令

    Windows常用快捷键 必须掌握: Ctrl+C:复制 Ctrl+V:粘贴 Ctrl+Z:撤销 Ctrl+S:保存 Win键+R:运行(run) alt+F4:关闭窗口/页面 Ctrl+A:全选 C ...

  7. python2文件开头两行

    #!/usr/bin/python  或者  #!/usr/bin/env python 告诉操作系统python位置 # -*- coding:utf-8 -*- 设置文件编码为utf-8  (默认 ...

  8. Airtest简单上手讲解

    Airtest是网易开发的手机UI界面自动化测试工具,它原本的目的是通过所见即所得,截图点击等等功能,简化手机App图形界面测试代码编写工作. 安装和使用 由于本文的目的是介绍如何使用Airtest来 ...

  9. 在ASP.NET Core中用HttpClient(五)——通过CancellationToken取消HTTP请求

    ​用户向服务器发送HTTP请求应用程序页面是一种非常可能的情况.当我们的应用程序处理请求时,用户可以从该页面离开.在这种情况下,我们希望取消HTTP请求,因为响应对该用户不再重要.当然,这只是实际应用 ...

  10. DAOS 分布式异步对象存储|故障模型

    DAOS 依靠大规模分布式单端口存储.因此,每个 Target 实际上都是一个单独的失败点. DAOS 通过在不同的容错域中提供 Target 间的冗余来实现数据和元数据的可用性和持久性.DAOS 内 ...