好久没写博客了,水一篇文章

受人请求帮忙提取 BOOKWALKER 里的图片。这类提供在线阅读方案的服务商为了防止用户拷贝可谓是绞尽脑汁,它们服务器返回的图片甚至是这样的:

a-004

将图片分割为一个个小块并打乱,在客户端再进行恢复并画到 canvas 上, 防止通过抓取请求这种简单方式直接获取到图片,密集+某些地方不均等的分割也让人肉还原成为几乎不可能的事情。

通过 canvas 提取

作为聪明人,我当然不会选择硬杠还原算法。我的第一反应是——从 canvas 里导出图片不就行了吗?

答案是不行,Firefox 告诉我「The operation is insecure」

…………

看着这个报错我的内心非常无语,作为 debugger 竟然连绕过安全检查的特权都没有吗?!明明右键都可以保存!

查了一下资料,貌似也没有关闭这个检查的开关,也许借助 headless browser 可以做到(后来发现确实有人用这种方案实现了),但我个人并不是很喜欢这种过重的方案,pass!

逆向还原算法

作为聪明人,一条路走不通就得换条路走。于是我打算试试从 js 中逆向出还原算法。

但是看到源代码的瞬间,我皱起了眉头

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function yIcCI(lCeYI, CCeYI) {
  var YCeYI = yIcCI;
  switch (lCeYI) {
    case 40:
      {
        var eCeYI = iLlII[$IlII("l", 377)](
          Y$lCI('"\b<]2N#\vPCCh^EV79p]3.\v,A', 361)
        )[iELCI("M", 339)]();
        this[I$lCI("*G", 203)](eClCI("G\x00\x00H#^NRVHC", 569), eCeYI),
          this[i_LlI("(=", 998)][LyYlI("9[/\r\x07=c0", 6)](
            eCeYI,
            this[E_ICI("\t:Q\x00X'T", 363)]
          );
      }
      break;
  }
}

什么鬼,这里面怎么连个正常的字符串也没有??

花了半天我才弄懂,原来这里面的字符串全部被替换成了 $IlII("l", 377) 这种形式……现在的 js 混淆真是原来越越先进了……

不过值得庆幸的是,这些字符串生成函数是全局定义的纯函数,因此只要写段代码在浏览器里 eval + replace 就能还原出字符串。 现在代码可读性一下就上来不少了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function yIcCI(lCeYI, CCeYI) {
  var YCeYI = yIcCI;
  switch (lCeYI) {
    case 40:
      {
        var eCeYI = iLlII["$"]("#settingFontFace input:checked")["val"]();
        this["b6l"]("settingFontFace", eCeYI),
          this["a6l"]["setFontFace"](eCeYI, this["directlySave"]);
      }
      break;
  }
}

然而,连字符串都整成了这个鬼样子的混淆器,在其他方面显然也不会弱。就我能看出来的,这个混淆器还做了这些事情:

  • 将部分没有副作用的语句块提取成一个函数
  • 不直接调用函数,而是塞到一个 map 里,再辅以随机的 key
  • 函数参数统统缩成两个,一个 this 一个 arguments
  • 常量展开
  • ……

又花了半天的时间,我终于……放弃了这个方法

如果有谁想顺着这条路走下去的话,这有一份我分析了一半的 viewer_image.7z, 其中第 81467 行即为核心的解密函数

Hook

聪明人总是善于发现新的路径,为什么我放弃逆向了呢,一方面是逆向确实麻烦,另一方面我想到了新的方法。

在上一步中,我试图通过看网络请求的栈回溯来分析程序流程。可惜的是,每当我点击栈回溯的时候浏览器都会卡死……(后来我才弄清楚,智障 Firefox 不会主动为我格式化源码,于是它就试图直接为我定位到一个包含上百万字符的行,然后就没有然后了)

我以为是网络请求的栈回溯生成得有问题,于是打算找 canvas 操作的栈回溯。可惜的是 Firefox 并没有提供相关断点。那就只能自己打了,于是我 hook 了 CanvasRenderingContext2D 的 drawImage 方法,打印了一些日志,然后我的浏览器又卡死了……

趁着 Firefox 还没被 OOMKiller 干掉之前,我分析了一下满屏的 drawImage 日志,我发现这是客户端在进行图片的拼接,难怪会如此频繁地调用 drawImage。 嗯?

原来是借助 canvas 进行图片拼接的?想想也是,没道理放着现成的 canvas 不用去造一个图片处理库的轮子。

于是我意识到了还有一条更简单的道路——直接记录下 drawImage 的参数,然后在本地根据这些参数进行还原。

然后就没有然后了,这个方法确实非常简单,以下就是最终的两个脚本。

  • userscript.js 用于打包下载图片及参数,在控制台执行 autoScoll() 后滚动一下等它慢慢下载完,再执行 saveAsZip() 保存下来即可
  • rerange.py 用于重新拼接图片,解压以后扔在同一目录执行