前言
前面一章讲的是模拟登录,留了一个模拟密码加密还没讲。
因为这一过程的调试探索还是蛮多内容的,我更倾向于记录自己整个探索的过程,而不是把工具拿出来讲一下用法,所以单独拿一章来讲。
调试过程
首先,既然要模拟js的加密过程,当然是要调试前端代码,从定位到起加密作用的js代码上面。
F12浏览器调试时,source目录下可以看到当前的浏览器的一些静态文件,包括页面,css,js等文件,一开始先定位到点击登录按钮之后,肯定会执行的js代码,然后在那个上面打断点。
像我要爬取的网站,会根据你所使用的浏览器使用不同的加密算法,所以要通过调试,知道它到底是怎么加密的。
打了断点之后,我成功找到了加密的代码,
可以看到起加密作用的就是ciper.Enrypt()
方法。把鼠标放在上面,点击如图所示出现的悬浮小窗里面的Enrypt(a,b)
,就跳到对应的js文件里面。
这里就有一个很神奇的东西了,跳到的js文件并不在我source目录中,而是一个VM+"一串数字"
的js中。
查询了去Stack Overflow上面查了之后,看到了如下回答
[VM] (scriptId) has no special meaning. It's a dummy name to help us to distinguish code which are not directly tied to a file name, such as code created using eval and friends.
VM是虚拟机(Virtual Machine)的缩写,后面的数字是代码的编号ID,主要是为了区别原网页的js与其他来源的js(比如eval创建的, ajax获取的等)
也就是说这个加密的js是动态加载出来的。
定位到js之后,接下去就是怎么模拟了。
ScriptEngine
加密算法,可以说是非常复杂,所以不可能用java语言去模拟。就想着能不能在jvm上运行js的文件,这么一搜,还真有。
java中的javax.script,它开始存在于JDK1.6,它可以解析通用的表达式,如三目,还可以利用js函数语法,创造一个就像java的函数一样存在于内存中随时可以被调用的函数,更可以将js中的对象直接转换成java对象。
到了java8 之后 叫做 Nashorn JavaScript 引擎。扩展了很多功能。
只不过这里面我们只使用它加载js文件,调用js中函数的功能,其他功能有兴趣的可以自己去了解一下。
先介绍下基础用法
在Java中直接调用js代码
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
/** * 直接调用js代码 */
public class ScriptEngineTest {
public static void main(String[] args) {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
try {
engine.eval("var a=3; var b=4;print (a+b);");
} catch (ScriptException e) {
e.printStackTrace();
}
}
}
Java中调用js文件中的function,传入调用参数,并获取返回值
// demo.js
function count(a, b) {
c = a * b;
return c;
}
在Java代码中读取js文件,并参数两个参数,然后回去返回值
import java.io.FileReader;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
public class ScriptEngineTest {
public static void main(String[] args) throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
String jsFileName = "demo.js";
// 读取js文件
FileReader reader = new FileReader(jsFileName);
// 执行指定脚本
engine.eval(reader);
if(engine instanceof Invocable) {
Invocable invoke = (Invocable)engine;
// 调用count方法,并传入两个参数
Double c = (Double)invoke.invokeFunction("merge", 2, 3);
System.out.println("c = " + c);
}
reader.close();
}
}
实战
了解了前面的基础知识和简单热身后,我这个问题就简单了。我浏览器上调试显示的js拷贝出来新建了一个js文件,放到resource目录下,用ScriptEngine去加载,把明文密码和公钥当做变量传进去调用js 中的function,这就好像是黑箱,我不需要知道里面的加密互相调用是怎么样。
这里有一个坑注意一下:ScriptEngine
执行js代码或者js文件的时候,被调用的代码中如果引用了浏览器中存在的对象,就会报错。比如alert
,navigator
之类的。
ReferenceError: "xxx" is not defined
原因显而易见,这个对象在你的脚本引擎中没有定义。
我在解决这个问题的时候,先是看了一下有哪些对象,这些对象具体起了什么作用。
看了之后发现,都是起着兼容性的作用,不同浏览器版本就会用不同的变量值或者说调用不同的加密方法。我就直接删去了里面兼容性判断部分的代码,给一些变量值指定了定值。最后就起到了同样的效果。