Frida hook 加固的 Android 应用

Android 加固应用Hook方式 --- Frida:https://github.com/xiaokanghub/Android

转载:使用 frida 来 hook 加固的 Android 应用的 java 层:https://bbs.pediy.com/thread-246767.htm

使用 Frida 给 apk 脱壳并穿透加固 Hook 函数:https://xz.aliyun.com/t/7670

[推荐] 『Android安全』版2018年优秀和精华帖分类索引:https://bbs.pediy.com/thread-249602.htm

1. Frida Hook Android 加固应用 方法

1、Android 加固应用 Hook 方式 

Java.perform(function () {var application = Java.use('android.app.Application');application.attach.overload('android.content.Context').implementation = function (context) {var result = this.attach(context);var classloader = context.getClassLoader();Java.classFactory.loader = classloader;var yeyoulogin = Java.classFactory.use('com.zcm.主窗口');console.log("yeyoulogin:" + yeyoulogin);yeyoulogin.按钮_用户登录$被单击.implementation = function (arg) {console.log("retval:" + this.返回值);}}
});

列出加载的类

Java.enumerateLoadedClasses({"onMatch": function (className) { console.log(className); },"onComplete": function () { }}
)

2、Hook 动态加载类

获取构造函数的参数

Java.perform(function () {//创建一个DexClassLoader的wappervar dexclassLoader = Java.use("dalvik.system.DexClassLoader");//hook 它的构造函数$init,我们将它的四个参数打印出来看看。dexclassLoader.$init.implementation = function (dexPath, optimizedDirectory, librarySearchPath, parent) {console.log("dexPath:" + dexPath);console.log("optimizedDirectory:" + optimizedDirectory);console.log("librarySearchPath:" + librarySearchPath);console.log("parent:" + parent);//不破换它原本的逻辑,我们调用它原本的构造函数。this.$init(dexPath, optimizedDirectory, librarySearchPath, parent);}console.log("down!");
});

获取动态加载的类

Java.perform(function () {var dexclassLoader = Java.use("dalvik.system.DexClassLoader");var hookClass = undefined;var ClassUse = Java.use("java.lang.Class");dexclassLoader.loadClass.overload('java.lang.String').implementation = function (name) {//定义一个String变量,指定我们需要的类var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";//直接调用第二个重载方法,跟原本的逻辑相同。var result = this.loadClass(name, false);//如果loadClass的name参数和我们想要hook的类名相同if (name === hookname) {//则拿到它的值hookClass = result;//打印hookClass变量的值console.log(hookClass);send(hookClass);return result;}return result;}
});

通过 Java.cast 处理泛型方法(JAVA中Class<?>表示泛型),再调用动态加载方法

Java.perform(function(){var hookClass = undefined;var ClassUse = Java.use("java.lang.Class");var dexclassLoader = Java.use("dalvik.system.DexClassLoader");var constructorclass = Java.use("java.lang.reflect.Constructor");var objectclass= Java.use("java.lang.Object");dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";var result = this.loadClass(name,false);if(name == hookname){var hookClass = result;console.log("------------------------------CAST--------------------------------")//类型转换var hookClassCast = Java.cast(hookClass,ClassUse);//调用getMethods()获取类下的所有方法var methods = hookClassCast.getMethods();console.log(methods);console.log("-----------------------------NOT CAST-----------------------------")//未进行类型转换,看看能否调用getMethods()方法var methodtest = hookClass.getMethods();console.log(methodtest);console.log("---------------------OVER------------------------")return result;}return result;}});

利用 getDeclaredConstructor() 获取具有指定参数列表构造函数的Constructor 并实例化

Java.perform(function () {var hookClass = undefined;var ClassUse = Java.use("java.lang.Class");var objectclass = Java.use("java.lang.Object");var dexclassLoader = Java.use("dalvik.system.DexClassLoader");var orininclass = Java.use("cn.chaitin.geektan.crackme.MainActivity");var Integerclass = Java.use("java.lang.Integer");//实例化MainActivity对象var mainAc = orininclass.$new();dexclassLoader.loadClass.overload('java.lang.String').implementation = function (name) {var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";var result = this.loadClass(name, false);if (name == hookname) {var hookClass = result;var hookClassCast = Java.cast(hookClass, ClassUse);console.log("-----------------------------BEGIN-------------------------------------");//获取构造器var ConstructorParam = Java.array('Ljava.lang.Object;', [objectclass.class]);var Constructor = hookClassCast.getDeclaredConstructor(ConstructorParam);console.log("Constructor:" + Constructor);console.log("orinin:" + mainAc);//实例化,newInstance的参数也是Ljava.lang.Object;var instance = Constructor.newInstance([mainAc]);console.log("patchAc:" + instance);send(instance);console.log("--------------------------------------------------------------------");return result;}return result;}
});

利用 getDeclaredMethods(),获取本类中的所有方法

Java.perform(function(){var hookClass = undefined;var ClassUse = Java.use("java.lang.Class");var objectclass= Java.use("java.lang.Object");var dexclassLoader = Java.use("dalvik.system.DexClassLoader");var orininclass = Java.use("cn.chaitin.geektan.crackme.MainActivity");var Integerclass = Java.use("java.lang.Integer");//实例化MainActivity对象var mainAc = orininclass.$new();dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";var result = this.loadClass(name,false);if(name == hookname){var hookClass = result;var hookClassCast = Java.cast(hookClass,ClassUse);console.log("-----------------------------BEGIN-------------------------------------");//获取构造器var ConstructorParam =Java.array('Ljava.lang.Object;',[objectclass.class]);var Constructor = hookClassCast.getDeclaredConstructor(ConstructorParam);console.log("Constructor:"+Constructor);console.log("orinin:"+mainAc);//实例化,newInstance的参数也是Ljava.lang.Object;var instance = Constructor.newInstance([mainAc]);console.log("MainActivityPatchInstance:"+instance);send(instance);console.log("----------------------------Methods---------------------------------");var func = hookClassCast.getDeclaredMethods();console.log(func);console.log("--------------------------Need Method---------------------------------");console.log(func[0]);var f = func[0];console.log("---------------------------- OVER---------------------------------");return result;}return result;}
});

调用 Method.invoke() 去执行方法

invoke 方法的参数

  • 第一个参数:是执行这个方法的对象实例,
  • 第二个参数:是带入的实际值数组,
  • 返回值:是 Object,也既是该方法执行后的返回值
f.invoke(instance,Array);

read-std-string

/** Note: Only compatible with libc++, though libstdc++'s std::string is a lot simpler.*/function readStdString(str) {const isTiny = (str.readU8() & 1) === 0;if (isTiny) {return str.add(1).readUtf8String();}return str.add(2 * Process.pointerSize).readPointer().readUtf8String();
}

where_is_native

Java.perform(function () {var SystemDef = Java.use('java.lang.System');var RuntimeDef = Java.use('java.lang.Runtime');var exceptionClass = Java.use('java.lang.Exception');var SystemLoad_1 = SystemDef.load.overload('java.lang.String');var SystemLoad_2 = SystemDef.loadLibrary.overload('java.lang.String');var RuntimeLoad_1 = RuntimeDef.load.overload('java.lang.String');var RuntimeLoad_2 = RuntimeDef.loadLibrary.overload('java.lang.String');var ThreadDef = Java.use('java.lang.Thread');var ThreadObj = ThreadDef.$new();SystemLoad_1.implementation = function (library) {send("Loading dynamic library => " + library);stackTrace();return SystemLoad_1.call(this, library);}SystemLoad_2.implementation = function (library) {send("Loading dynamic library => " + library);stackTrace();SystemLoad_2.call(this, library);return;}RuntimeLoad_1.implementation = function (library) {send("Loading dynamic library => " + library);stackTrace();RuntimeLoad_1.call(this, library);return;}RuntimeLoad_2.implementation = function (library) {send("Loading dynamic library => " + library);stackTrace();RuntimeLoad_2.call(this, library);return;}function stackTrace() {var stack = ThreadObj.currentThread().getStackTrace();for (var i = 0; i < stack.length; i++) {send(i + " => " + stack[i].toString());}send("--------------------------------------------------------------------------");}
});

Non --- ASCII ( 不可见字符 )

如果代码进行了混淆,一些函数、方法会变成 非ASCII、甚至有一些不可见的字符,所以可以先编码打印出来,再用编码后的字符串去 hook

 int ֏(int x) {return x + 100;}

JavaScript 代码:

Java.perform(function x() {var targetClass = "com.example.hooktest.MainActivity";var hookCls = Java.use(targetClass);var methods = hookCls.class.getDeclaredMethods();for (var i in methods) {console.log(methods[i].toString());console.log(encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1")));}hookCls[decodeURIComponent("%D6%8F")].implementation = function (x) {console.log("original call: fun(" + x + ")");var result = this[decodeURIComponent("%D6%8F")](900);return result;}}
)

使用 objection 打印混淆的方法名

使用 objection 打印混淆的方法名,然后再 hook 打印的方法名即可 hook 对应的函数

objection 是基于 frida 的命令行 hook 工具,可以让你不写代码, 敲几句命令就可以对 java 函数的高颗粒度 hook, 还支持 RPC 调用

objection 目前只支持 Java层的 hook,但是 objection 有提供插件接口,可以自己写 frida 脚本去定义接口,

比如葫芦娃大佬的脱壳插件,实名推荐: https://github.com/hluwa/FRIDA-DEXDump

官方仓库: https://github.com/sensepost/objection

这里以 腾讯新闻.apk 为例:

通过 抓包分析可知,腾讯新闻 app 有三个参数需要破解:qn-rid、qn-sig、qn-newsig,通过 jadx-gui 分析源码可知,

  • qn-rid :是一个 uuid。
  • qn-sig :通过 qn-rid 加上一些别的参数,然后 md5 得到。
  • qn-newsig :通过请求中的一些参数组合,然后  sha-256 得到

这里分析  qn-newsig 参数:

双击,定位到 qn-newsig

找到真正的 加密方法

使用 objection 注入 com.tencent.news,命令:objection -g com.tencent.news explore

列出 类 中的所有方法:

命令:android hooking list class_methods com.tencent.news.utils.n.b

在结合反编译后的源码,根据 函数返回值反编译后的注释函数参数的类型和个数 找出 真正要hook的函数

public static java.lang.String com.tencent.news.utils.n.b.ʼ(byte[])
public static java.lang.String com.tencent.news.utils.n.b.ʼ(java.lang.String)
public static java.lang.String com.tencent.news.utils.n.b.ʼ(java.lang.String,int)
public static java.lang.String com.tencent.news.utils.n.b.ʼ(java.lang.String,java.lang.String)
public static java.lang.String com.tencent.news.utils.n.b.ʼ(long)
public static java.lang.String com.tencent.news.utils.n.b.ʼ(long,int)

public static java.lang.String com.tencent.news.utils.n.b.ʼ(java.lang.String) 这个函数就是真正要 hook 的函数,直接 hook,打印参数、返回值。即可

Hook 数据库

var SQLiteDatabase = Java.use('com.tencent.wcdb.database.SQLiteDatabase');
var Set = Java.use("java.util.Set");
var ContentValues = Java.use("android.content.ContentValues");
SQLiteDatabase.insert.implementation = function (arg1, arg2, arg3) {this.insert.call(this, arg1, arg2, arg3);console.log("[insert] -> arg1:" + arg1 + "\t arg2:" + arg2);var values = Java.cast(arg3, ContentValues);var sets = Java.cast(values.keySet(), Set);var arr = sets.toArray().toString().split(",");for (var i = 0; i < arr.length; i++) {console.log("[insert] -> key:" + arr[i] + "\t value:" + values.get(arr[i]));}
};

2. 使用 Frida - DEXDump 进行 apk 脱壳

From:https://mp.weixin.qq.com/s/x8_aa762wpsvA4nhSLoppQ

github 地址:https://github.com/hluwa/FRIDA-DEXDump

示例:使用 Frida 给 apk 脱壳并穿透加固 Hook 函数:https://xz.aliyun.com/t/7670

脱壳的需求

APP 加固发展到现在已经好几代了,从整体加固代码抽取虚拟机保护,加固和脱壳的方案也逐渐趋于稳定。随着保护越来越强,脱壳机们也变得越来越费劲,繁琐。

不过对于我来说,很多时候其实并不需要那些被强保护起来的代码,我只是想单纯的看看这个地方的业务逻辑。所以,我追求的是更加快速、简便的脱壳方法。

实现

得益于FRIDA, 在 PC 上面进行内存搜索、转储都变得十分方便,再也不需要考虑什么Xposed、什么Android开发、什么代码注入,只需要关注如何去搜索想要的东西,于是依赖一个几十行代码的小脚本,就可以将大部分内存中的 dex 脱下来。在过去的一年,我几乎所有脱壳的工作都是由此脚本来完成,目前已经随手开源:https://github.com/hluwa/FRIDA-DEXDump

对于完整的 dex,采用暴力搜索 dex035 即可找到。而对于抹头的 dex,通过匹配一些特征来找到。

使用

不会安装使用 FRIDA 的,请先自行百度学会。。。然后,默念一声 "我想脱个壳"。

  • 启动 APP。
  • 启动 frida-server。
  • python main.py。默数三秒,脱好了。

或者可以将脚本封装成命令:

3. 示 例

获取完整的demo

完整的代码已经上传github,https://github.com/smartdone/Frida-Scripts/tree/master/shell

需求

在对一些加固的Android应用做测试的时候,脱壳二次打包是一个相当相当复杂的工作。所以一般是脱壳分析代码,然后用hook的方式来动态劫持代码。使用xposed来hook加固的应用大家可能已经很熟悉了,但是使用frida大概没有多少人尝试,今天就给大家分享下如何使用xposed来hook加固之后的Android应用。

基本原理

要hook加固的应用分为三步,第一步是拿到加载应用本事dex的classloader;第二步是通过这个classloader去找到被加固的类;第三步是通过这个类去hook需要hook的方法

得到第一步的classloader之后的hook操作和hook未加固的应用基本类似。

如何获取classloader

我们看Android的android.app.Application的源码http://androidxref.com/7.1.2_r36/xref/frameworks/base/core/java/android/app/Application.java#188可以发现,自己定义的ApplicationattachBaseContext方法是在Applicationattach方法里面被调用的。而基本上所有的壳都是在attachBaseContext里面完成的代码解密并且内存加载dex,在attachBaseContext执行完之后就可以去拿classloader,此时的classloader就已经是加载过加固dex的classloader了。

开始 hook 加固应用

以 "i春秋 app ( https://www.wandoujia.com/apps/7456953 )" 为例,此应用使用的360加固,我们的目标是hook他的flytv.run.monitor.fragment.user.AyWelcome 的 onCreate 方法,然后弹出一个 Toast

直接使用 Java.use

我们直接使用Java.use来获取这个Activity,代码如下:

if(Java.available) {Java.perform(function(){var AyWelcome = Java.use("flytv.run.monitor.fragment.user.AyWelcome");if(AyWelcome != undefined) {console.log("AyWelcome: " + AyWelcome.toString());} else {console.log("AyWelcome: undefined");}});
}

使用如下命令来注入这个js:

frida -R -f com.ni.ichunqiu -l hook_java.js

运行之后会报如下的错误:

也就是找不到这个类,也就是我们现在这个默认的 classloader 找不到flytv.run.monitor.fragment.user.AyWelcome这个类。

获取 classloader

代码如下:

if(Java.available) {Java.perform(function(){var application = Java.use("android.app.Application");application.attach.overload('android.content.Context').implementation = function(context) {var result = this.attach(context); // 先执行原来的attach方法var classloader = context.getClassLoader();return result;}});
}

现在我们在attach方法执行之后拿到了Context,并且通过context获取了classloader,我们看现在的classloader是否加载了被加固的dex。我们使用classloader的loadClass方法去加载flytv.run.monitor.fragment.user.AyWelcome这个类,看是否成功:

if(Java.available) {Java.perform(function(){var application = Java.use("android.app.Application");var reflectClass = Java.use("java.lang.Class");application.attach.overload('android.content.Context').implementation = function(context) {var result = this.attach(context); // 先执行原来的attach方法var classloader = context.getClassLoader(); // 获取classloadervar AyWelcome = classloader.loadClass("flytv.run.monitor.fragment.user.AyWelcome"); // 使用classloader加载类AyWelcome = Java.cast(AyWelcome, reflectClass); // 因为loadClass得到的是一个Object对象,我们需要把它强制转换成Classconsole.log("AyWelcome class name: " + AyWelcome.getName());return result;}});
}

注入这个 js,可以正确的打印出flytv.run.monitor.fragment.user.AyWelcome类名,说明我们拿到这个这个classloader是加载了加固过的dex的。

转换成Java.use获取到的js对象

在上一步我们虽然可以通过frida来获取到加固之后的class,但是你如果直接使用这个{class}.{fuction}依然会失败,因为class没有这个成员变量,所以我们需要来实现获取到与Java.use一样的js对象,那么如何解决呢?当然是read the fuking source code

我们看frida-java的use方法的实现,代码在frida-java-bridge/class-factory.js at 9becc27091576fc198dc2a719c0fedb30a270b28 · frida/frida-java-bridge · GitHub

代码如下:

this.use = function (className) {let C = classes[className];if (!C) {const env = vm.getEnv();if (loader !== null) {const usedLoader = loader;if (cachedLoaderMethod === null) {cachedLoaderInvoke = env.vaMethod('pointer', ['pointer']);cachedLoaderMethod = loader.loadClass.overload('java.lang.String').handle;}const getClassHandle = function (env) {const classNameValue = env.newStringUtf(className);const tid = Process.getCurrentThreadId();ignore(tid);try {return cachedLoaderInvoke(env.handle, usedLoader.$handle, cachedLoaderMethod, classNameValue);} finally {unignore(tid);env.deleteLocalRef(classNameValue);}};C = ensureClass(getClassHandle, className);} else {const canonicalClassName = className.replace(/\./g, '/');const getClassHandle = function (env) {const tid = Process.getCurrentThreadId();ignore(tid);try {return env.findClass(canonicalClassName);} finally {unignore(tid);}};C = ensureClass(getClassHandle, className);}}return new C(null);};

从代码中我们可以看出来,他会先到他存class的一个列表里面去找,如果找不到,就会判断loader是不是null,loader不为null,就会使用loader加载class,loader为null就会使用JNIEnv的findClass方法去找类,也就是使用默认的classloader。所以现在目标明确了,我们只需要让这个loader是我们从Applicaiton的attach方法获取到的classloader即可,那么怎么替换呢?

很显然直接Java.loader会说undefined,我们看最终导出的是index.js这个脚本frida-java-bridge/index.js at 022bc7d95c00d627091d4edc0ff87b67de5a9739 · frida/frida-java-bridge · GitHub,有下面几个成员变量:

let initialized = false;
let api = null;
let apiError = null;
let vm = null;
let classFactory = null;
let pending = [];
let threadsInPerform = 0;
let cachedIsAppProcess = null;

我们看到了,这个classFactory不就是我们刚刚上面看到的那个loader所在的地方吗,那么要引用这个loader就很简单了,直接Java.classFactory.loader就可以引用了,你可以使用console.log("classloader: " + Java.classFactory.loader);来获取这个loader的值,后面我们直接将这个值替换为我们获取的classloader就行了,代码如下:

if(Java.available) {Java.perform(function(){var application = Java.use("android.app.Application");var reflectClass = Java.use("java.lang.Class");console.log("application: " + application);application.attach.overload('android.content.Context').implementation = function(context) {var result = this.attach(context); // 先执行原来的attach方法var classloader = context.getClassLoader(); // 获取classloaderJava.classFactory.loader = classloader;var AyWelcome = Java.classFactory.use("flytv.run.monitor.fragment.user.AyWelcome"); //这里能直接使用Java.use,因为java.use会检查在不在perform里面,不在就会失败console.log("AyWelcome: " + AyWelcome);return result;}});
}

写 hook 加固的类的代码,弹出 toast

if(Java.available) {Java.perform(function(){var application = Java.use("android.app.Application");var Toast = Java.use('android.widget.Toast');application.attach.overload('android.content.Context').implementation = function(context) {var result = this.attach(context); // 先执行原来的attach方法var classloader = context.getClassLoader(); // 获取classloaderJava.classFactory.loader = classloader;var AyWelcome = Java.classFactory.use("flytv.run.monitor.fragment.user.AyWelcome"); //这里不能直接使用Java.use,因为java.use会检查在不在perform里面,不在就会失败console.log("AyWelcome: " + AyWelcome);// 然后下面的代码就和写正常的hook一样啦AyWelcome.onCreate.overload('android.os.Bundle').implementation = function(bundle) {var ret = this.onCreate(bundle);Toast.makeText(context, "onCreate called", 1).show(); //弹出Toastreturn ret;}return result;}});
}

最后效果如下:

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

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

相关文章

176页报告辟谣自动化时代的就业危机(附下载)

来源&#xff1a;智东西摘要&#xff1a;世行从新兴技术的社会影响出发&#xff0c;盘点劳动力市场的再培训、再就业需求&#xff0c;以及资本、政府的社会职责。近十年&#xff0c;以人工智能为代表的技术爆炸正在重塑新一轮社会经济格局。这些创新极大地改变了就业市场形势&a…

CSS3技巧 —— 渐变

CSS渐变在Webkit率先得到实现&#xff0c;现在Firefox 3.6也支持了&#xff0c;来看下各个浏览器如何实现CSS渐变效果。 Webkit 下面这行代码可用于Chrome, Safari等&#xff0c;它能实现线性渐变&#xff0c;从top(#ccc)渐变到bottom(#000)。 background: -webkit-gradient(li…

Frida hook 插件化 apk ( classloader )

From&#xff1a;使用 frida hook 插件化 apk&#xff1a;https://bbs.pediy.com/thread-258772.htm 最近拿到一个XX视频apk样本&#xff0c;里面有视频、直播和小说&#xff0c;没有VIP只能试看30秒&#xff0c;刚好最近学习frida&#xff0c;用来练习下&#xff0c;分析过程中…

MIT开发出新界面系统 操作员可用思维控制机器人

来源&#xff1a;VentureBeat、网易科技摘要&#xff1a;麻省理工学院(MIT)下属计算机科学与人工智能实验室(CSAIL)的研究人员开发了一种新界面&#xff0c;它可以读取人类操作人员的脑电波&#xff0c;让他们通过思维命令机器执行任务。据VentureBeat报道&#xff0c;用思维控…

windows 远程执行 cmd 命令的 9 种方法

一、远程执行命令方式及对应端口:  IPC$AT 445  PSEXEC 445  WMI 135  Winrm 5985(HTTP)&5986(HTTPS) 二、9种远程执行cmd命令的方法&#xff1a; 1.WMI执行命令方式,无回显&#xff1a; wmic /node:192.168.1.158 /user:pt007 /password:admin123 process call …

不要指望未来科学的发展会改变元素周期表的形式

来源&#xff1a;陈敏伯科学网博客摘要&#xff1a;对于自然界的许多规律&#xff0c;哪怕我们对其物理具体内容还不知道、实验证据还不足&#xff0c;但是可以单凭问题中明显可见的对称性质&#xff0c;就可以从理论上演绎出一些重要结论。很长时间以来&#xff0c;化学界关于…

Appium 简明教程

转载&#xff1a;Appium 简明教程&#xff1a;http://www.testclass.net/appium https://www.cnblogs.com/fnng/p/4540731.html Appium 官网&#xff1a;http://appium.io/ Github 地址&#xff1a;https://github.com/appium/appium 主要包括以下几部分&#xff1a; appium新…

陈俊龙:从深度强化学习到宽度强化学习—结构,算法,机遇及挑战

来源&#xff1a;AI科技评论摘要&#xff1a;如何赋予机器自主学习的能力&#xff0c;一直是人工智能领域的研究热点。强化学习与宽度学习如何赋予机器自主学习的能力&#xff0c;一直是人工智能领域的研究热点。在越来越多的复杂现实场景任务中&#xff0c;需要利用深度学习、…

Web.Config文件配置之数据库连接配置

Web.Config文件以XML形式存在于ASP.NET应用程序中&#xff0c;是ASP.NET应用程序的配置文件&#xff0c;包含程序调试、会话和全球化设置等配置信息&#xff0c;可以直接用记事本打开进行编辑。下面通过实例说明如何配置Web.Config文件。 一、配置Access数据库连接 Access数据库…

pyspider 安装 和 快速开始

From&#xff1a;官方文档 --- 快速开始&#xff1a;http://docs.pyspider.org/en/latest/Quickstart/ pyspider github 地址&#xff1a;https://github.com/binux/pyspider pyspider 官方文档&#xff1a;http://docs.pyspider.org/en/latest/ 爬虫框架 pyspider个人总结&…

【干货】强化学习介绍

作者 | Thomas Simonini编译 | 专知整理 | Yongxi摘要&#xff1a;由于Alpha Go的成功&#xff0c;强化学习始终是人们谈论的焦点。现在Thomas Simonini在国外blog网站上发布了系列强化学习教程&#xff0c;以下是本系列的第一篇&#xff0c;简单介绍了强化学习的基本概念。An …

爬虫教程( 2 ) --- 爬虫框架 Scrapy、Scrapy 实战

From&#xff1a;https://piaosanlang.gitbooks.io/spiders/content/ scrapy-cookbook &#xff1a;https://scrapy-cookbook.readthedocs.io/zh_CN/latest/index.html 1. 爬虫框架 Scrapy 爬虫框架中比较好用的是 Scrapy 和 PySpider。 PySpider 优点&#xff1a;分布式框架&a…

传粉飞行器是拯救传粉昆虫危机的利器还是毁灭者

来源&#xff1a;陈华燕的科学网博客摘要&#xff1a;近年来科学家陆续发现&#xff0c;传粉昆虫正在面临着重重危机&#xff0c;至少在欧洲和北美已发现传粉昆虫的数量正在逐渐下降。近年来科学家陆续发现&#xff0c;传粉昆虫正在面临着重重危机&#xff0c;至少在欧洲和北美…

字符串比较函数实现,超简单的面试题,回过头来发现原来我的c多么的薄弱

今天某个公司面试&#xff0c;尽管报了个测试类的&#xff0c;但是面试依旧不给力&#xff0c;先是写个字符串比较函数&#xff0c;只判断相等和不相等的情况&#xff0c;当时大概这么写的&#xff1a; #include<stdio.h>bool strCompare(char *str1,char *str2){char *s…

思略特报告解读:智能制造企业如何实现数字化?

来源&#xff1a;亿欧智库摘要&#xff1a;全球制造业已经将数字化运营或者工业4.0提上日程&#xff0c;基于此&#xff0c;思略特调研了1100多为企业高管&#xff0c;了解他们对数字化的看法。根据调研&#xff0c;总结了四大业务生态体系&#xff1a;客户解决方案体系、运营体…

爬虫教程( 4 ) --- 分布式爬虫 scrapy-redis、集群

1、分布式爬虫 scrapy - redis scrapy 分布式爬虫 文档&#xff1a;http://doc.scrapy.org/en/master/topics/practices.html#distributed-crawls Scrapy 并没有提供内置的机制支持分布式(多服务器)爬取。不过还是有办法进行分布式爬取&#xff0c; 取决于您要怎么分布了。 …

爬虫教程( 5 ) --- Selenium 与 PhantomJS

1. Selenium 中式读法&#xff1a;【 瑟林捏幕 】 Selenium&#xff08; selenium 中文网&#xff1a;http://www.selenium.org.cn/ &#xff09;是一个强大的网络数据采集工具&#xff0c;最初是为了网站自动化测试而开发的&#xff0c;被用来测试 Web 应用程序在不同的浏览器…

详解深度学习的可解释性研究(上篇)

作者 | 王小贱来源 | BIGSCity知乎专栏摘要&#xff1a;《深度学习的可解释性研究》系列文章希望能用尽可能浅显的语言带领大家了解可解释性的概念与方法&#xff0c;以及关于深度学习可解释性工作的研究成果。本文是该系列的第一部分。01深度学习的可解释性研究&#xff08;一…

Office 安装

Office Tool Plus 官网&#xff1a;https://otp.landian.vip/zh-cn/ &#xff1a;https://zhuanlan.zhihu.com/p/486882686 Office Tool Plus 基于 Office 部署工具 (ODT) 打造&#xff0c;可以很轻松地部署 Office。无论你是个体还是团队&#xff0c;Office Tool Plus 都是您…

前瞻性总结:全球未来十年不可不知的10大趋势

来源&#xff1a;混沌大学摘要&#xff1a;无论你身在何处&#xff0c;真正的大趋势必将把地球上每一个人深卷其中&#xff0c;并重构大至国家、城市&#xff0c;小至企业、消费者之间一系列错综复杂的关系。不管你是处于创业模式&#xff0c;还是在大企业里面工作&#xff0c;…