Java爬虫(四)-- java 调用js函数 模拟页面js密码加密(附几个知识点)

Richard_Yi 2018年03月04日 115次浏览

前言

前面一章讲的是模拟登录,留了一个模拟密码加密还没讲。

因为这一过程的调试探索还是蛮多内容的,我更倾向于记录自己整个探索的过程,而不是把工具拿出来讲一下用法,所以单独拿一章来讲。

调试过程

首先,既然要模拟js的加密过程,当然是要调试前端代码,从定位到起加密作用的js代码上面。

F12浏览器调试时,source目录下可以看到当前的浏览器的一些静态文件,包括页面,css,js等文件,一开始先定位到点击登录按钮之后,肯定会执行的js代码,然后在那个上面打断点。

像我要爬取的网站,会根据你所使用的浏览器使用不同的加密算法,所以要通过调试,知道它到底是怎么加密的。

打了断点之后,我成功找到了加密的代码,

undefined

可以看到起加密作用的就是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文件的时候,被调用的代码中如果引用了浏览器中存在的对象,就会报错。比如alertnavigator之类的。

ReferenceError: "xxx" is not defined

原因显而易见,这个对象在你的脚本引擎中没有定义。

我在解决这个问题的时候,先是看了一下有哪些对象,这些对象具体起了什么作用。

看了之后发现,都是起着兼容性的作用,不同浏览器版本就会用不同的变量值或者说调用不同的加密方法。我就直接删去了里面兼容性判断部分的代码,给一些变量值指定了定值。最后就起到了同样的效果。