LoveCode 发表于 2023-11-28 22:34

绕过 AGE 动漫的 wasm 加密与解密函数

本帖最后由 LoveCode 于 2024-4-27 22:54 编辑


# 前言

此次修炼一举突破到了**练气三层初期!**终于可以和日思夜想的三阶灵兽论道了(ヾ(≧▽≦*)o)。

>   注:所谓灵兽,便是纳天地日月精华修行(搜集各种资源),初通人意(无泛滥广告),后识文墨、知礼节(可以评论区求资源),修为渐深亦可化形(有 app),倘若臻至天阶更有一丝机缘激活血脉得远古神兽的传承(唉~远古神兽自然就是倒闭的网站了,再修炼下去,就要被不知名的神秘组织从修仙界抹除)。



此次论道的网站是 `AGE 动漫:(base64 encode) aHR0cHM6Ly93d3cuYWdlZG0ub3Jn`。



## 郑重声明

因为是与 `灵兽` 论道,为了保护灵兽,此次仅仅重点记录如何**绕过最核心的 `WebAssembly` 加密与解密部分**,其它部分将省略(其它的参数利用普通方法就能解决,只要花点耐心呐)。

>   当然,如何定位到加密的位置我依然会详细讲解的。



阅读本文时,如果有以下知识将更游刃有余:

-   了解 `JavaScript` 的 `Proxy` 对象

-   了解 `JavaScript WebAssembly API`。

   

**如果大家对文中提到的一些概念都已经理解,但是文章看不懂,请一定要记住:这一定是我的问题,是我没有讲解清楚。毕竟每个人的经历、感悟不同,我在描述时难免不达意。**



---



# 芳踪难觅

先启动**浏览器无痕模式**,打开某一集动漫的播放页 `(base64 encode) aHR0cHM6Ly93d3cuYWdlZG0ub3JnL3BsYXkvMjAyMzAyMTYvMS84`,等待视频加载出来。

按照我的习惯,先利用审查元素看看视频所在的网页标签,发现视频在一个 `<iframe>` 标签中。关于此标签的作用、含义具体见文档 (https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe),简单来说是网页之中嵌套了另一个网页。

**这个最初的视频播放页链接我取名为:`anime_paly_url`,即看动漫时所在的网页,以便后文讨论。**






所以我直接将上图中的 `<iframe>` 标签的 `src` 属性值复制出来。**这里我又要取名了,将这个链接称之为 `anime_iframe_src`,即动漫播放页中的 `<iframe>` 标签的 `src` 值。**

在无痕浏览器的新标签页中打开这个 `anime_iframe_src`!同样,等待视频加载出来,优先利用审查元素查看视频所在的标签。






**这里我将上图中的动漫下载地址取名为 `anime_video_src`。**



现在需要确定上图中 `<video>` 的 `src` 属性值,也就是 `anime_video_src` 是否就存在于静态的 `HTML` 中,所以需要查看网页源代码……可是,这个网页已经被视频占据了,所以无法通过点击右键来选择 “看网页源代码” 的功能。






这里手动添加前缀:

```py
view-source:这里是 anime_iframe_src 的值
# 还记得 anime_iframe_src 这个名字吗
```



如下,可以看到网页的源代码了,并且确定视频的 `<video>` 标签、链接是动态生成的!






>   问:为什么不通过浏览器抓包界面来查看网页的源代码呢??
>
>   答:具体见后文的说明,这一步其实和后续的操作有关。



**注意!现在这种情况是:**

1.   浏览器在某个时刻得到了视频的链接,它并没有用 JS 去请求。
2.   是创建了 `<video>` 标签,然后把视频的链接写到了其 `src` 属性上!
3.   所以抓包分析、查看该视频的请求时,其 `Initiator` 是看不到结果的。



---



## 此地无银三百两

按照我的习惯,现在需要快速浏览 `HTML` 源代码来查看可疑的内容。



---



**在此之前我要说明一下为什么我要使用浏览器中的 "查看网页源代码" 功能,而不是在抓包工具中查看(之前的文章并没有提到这一点)。**也算是我斗法的经验。



以*西瓜视频*为例,在抓包界面查看网页代码其实不利于快速定位数据。如下图所示:







但是在 “查看网页源代码” 中我可以大概、快速定位关键数据。









如下,**像这种一大块白色字体挤在一起,中间的空白很少,并且由看不懂的数字、字母组成,那么极大概率就是关键的数据了。这就是 “此地无银三百两”,因为关键的数据经过处理之后通常都很长,需要多行显示,结果却挤在一起了。**

当鼠标滚轮快速滚动或者利用鼠标中键快速浏览器网页源代码时,下图这种一大块白色的区域非常显眼,这样就可以快速定位到可能的关键位置啦。








当然,找到可疑的位置后就可以在抓包页面查看缩进、美化之后的网页源代码来进行分析了。如果此种快速浏览的方法不行那就需要别的方法了。

好了,介绍了这个斗法经验,现在需要回到原来的事情上。



---



最终在 `anime_iframe_src` 的网页源代码中找到了可疑的信息:一个名为 `Vurl` 字符串(**请记住这个名字,后文我将会多次提及到它**)。






## 露出小尾巴:Vurl

虽然 `Vurl` 最可疑,但还需要确定是不是它。如下是我的确认方式。

*首先*,在视频正常播放的情况下,确认了 `Vurl` 的值从出现开始就没有变化,只好继续分析。

>   因为遇到过一些情况:
>
>   1.   加密的数据放在全局变量或属性 `x` 中
>   2.   网站经过一段时间的运行,`x` 的值变成了解密后的数据



*然后*,手动修改 `Vurl` 的值看看结果。






*最终*,出现了下图!到了这里我可以大胆确定:**哪怕动漫的链接不是从 `Vurl` 解密获取,那也和它脱不了干系,跟着它就能找到核心位置!**






故,根据前面的分析,一定是把 `Vurl` 的值传到了某个地方。因为其名字很具有 “个性”,所以尝试全局搜索,最终定位到了关键位置。




观察其四周的代码,有大概的过程。






## 迅猛一些??

如果一开始无法找到 `Vurl`,其实也可以尝试全局搜索 `encrypt、decrypt` 等和加密、解密相关的关键词,或者监控 `DOM` 树的变化(也就是监控 `<video>` 标签的插入)等。

更有甚者,可以根据网站自己的控制台输出快速、迅猛地定位到核心位置。






不过我会尽可能让思路连贯起来,除非走投无路才使用 `特殊关键词搜索`,我知道的一些捷径会提,但不会在文中使用,大家在实际情况时可以更加迅猛一些。



---



## 整理思路

上文定位到核心的部分了,不过为了保护灵兽,我将整个过程简化如下,并且只会着重讨论如何避免分析 `hxm_encrypt、hxm_decrypt` 两个加密、解密的函数的逻辑。



**上文中出现了很多我自定义的名字,这都是为了下面整理出思路,否则论述起来大家也摸不着头脑,不清楚我说的是哪些东西。**






---



# 键盘前论道:hxm_encrypt

如下图,在 `hxm_encrypt` 函数处下断点、不断跟进,最后来到了 wasm 代码。






并且这两个 `hxm` 函数都位于 `window` 对象中。









## 三阶灵兽的威压

**第一步:直接下载该 wasm 文件到本地,将来用 nodejs 执行它**。这对 `nodejs` 有版本要求,我用的是 `node v18.14.2`。






**第二步:找到 JS 加载 wasm 的相关代码**。

因为这个网站的 `wasm` 是利用 `go` 语言编写、生成(因为在之前的图中可以见到 `Go program` 的字符串啦),所以会用到很多外部的函数,需要导入它们。

如下在 `wasm` 文本文件的顶部就指出了需要导入哪些东西。




这些导入函数就在下图中,因为内容很多,此处就不展示全部代码了,**我将这个函数称之为 `go_js_wasm_exec`,请一定要记住这个名字,后面将多次提及到它**。






**第三步:找到实际加载 wasm 的 JS 代码**。

上面的 `go_js_wasm_exec` 函数只是*准备好了东西,还没有实际加载、运行 `wasm`*,就像是创建了类但没有实例化对象一样。

如下,在获取 `wasm` 文件的请求中找到了调用者。




此处 `loadWasm` 的分析将省略,后文将展示其相关的代码。



**第四步:在 NodeJS 中执行该 wasm 文件**。

请记住这里的 `const go = new Go();` 以及 `go.importObject`,这在后文非常重要,有个印象也好。

```js
const fs = require('fs');

// 这个就是第二步中提到的 go_js_wasm_exec 啦
require('./go_js_wasm_exec')

// 涉及到 JS WebAssembly API
async function readAndInstantiate(file, importObject) {
    // 读取 Wasm 二进制文件
    const buffer = fs.readFileSync(file);
    // 创建 WebAssembly 模块
    const wasmModule = new WebAssembly.Module(buffer);
    // 创建 WebAssembly 实例
    return WebAssembly.instantiate(wasmModule, importObject);
}

// 这里就是第三步中 loadWasm 的逻辑
const go = new Go();
go["importObject"]["env"]["syscall/js.finalizeRef"] = () => { }

// main.wasm 是第一步中下载到本地的 wasm 文件
readAndInstantiate("main.wasm", go.importObject)
    .then(instance => {
      go.run(instance); // 这里开始运行 wasm
   
            // 这个 hxm_encrypt 函数是在 wasm 内部复制给全局对象的,所以可以访问
      console.log(hxm_encrypt("123456"));
    })
```



不出意外地,它报错了。






## 练气三层的实力

上述在本地运行 wasm 为什么会报错呢??

目前(至少必应搜索到的资料)`wasm` 还无法和浏览器(DON、DOM 等浏览器 API)直接交互,需要经过 `js` 导入相关内容到 `wasm` 中才行。

>   简单而言,比如 js 可以直接通过 `window.location.href` 访问当前标签页的链接。
>
>   但是 wasm 中并没有相关的指令可以做到这一点,需要用 js 将它导入到 wasm 中,这样 wasm 才能访问。

从宏观上来看,如下图






所以就目前而言 `wasm` 是可以在本地用 `nodejs` 运行的,**在本地运行会报错是因为缺少了导入的东西(通常都是 `window` 对象的一些属性等)**,这个导入的入口处在 `JS API 的 WebAssembly.instantiate()` 方法的第二个参数。

所以这里的关键其实在于 `go_js_wasm_exec` 函数,因为是它起到了连接 `wasm` 和浏览器的作用。

如下可以确认是**将 window 对象导入到了 wasm 中,所以在 wasm 中才能将两个 `hxm` 函数添加到 `window` 对象上。**









那么在 `wasm` 中到底访问了 `window` 的哪些属性、方法呢??**只要知道 wasm 中和浏览器做了怎样的交互,那么在 nodejs 中就可以还原出来**。这里需要用到 `Proxy` 对象。












现在我们知道 wasm 中会访问 `global.window`,也就是会访问全局对象 `window`,那么继续利用 `Proxy` 对象来看一看究竟在 wasm 中访问了 `window` 的哪些东西!

>   注意!此时不能使用 `global.window = xx` 的形式来修改 `global.window` 属性。
>
>   因为 `global` 自身就是 `window`,哪有自己修改自己的??就像 `window.window = 1` 是没有效果的。

```js
// 这是一个对 window 进行代{过}{滤}理的 Proxy
let _window = new Proxy(window, {
    get(target, property) {
      console.log(`window Getting ${property}: ${target}`);
      return target;
    },
    set(target, property, value) {
      console.log(`window Setting ${property}=${value}`);
      return target = value;
    },
    apply: (target, thisArg, args) => {
      console.log(`window call ${target}(${args})`);
      return target.apply(thisArg, args);
    }
});


let _global = global;
// 创建 global 的 Proxy 对象,看看访问了哪些属性、方法
global = new Proxy(_global, {
    get(target, property) {
      // 当访问 global.window 时,返回一个对 window 的 Proxy 对象!!!
      if (property === 'window') {
            return _window;
      }
      console.log(`global Getting ${property}: ${target}`);
      return target;
    }
});
```



最终确定了关键的东西 `window.Domain`!






**至此!可以在 `nodejs` 中模拟并调用 `hxm_encrypt、hxm_decrypt` 这两个函数了,只需要在 `go_js_wasm_exec` 文件中添加如下代码!也就是补全 wasm 的运行环境。**






---



# 尾声

虽然网站使用了 `wasm` 对数据进行加密与解密,但是该 wasm 运行的期间与浏览器、JS 的交互太浅(在本文中仅仅只是访问了 `window.Domain` 这一个属性),从而可以比较简单地在本地补全 wasm 的运行环境,姑且算是 “绕过了它的加密与解密部分”,我也不清楚 wasm 文件中到底做了什么。

不过该网站还有一个的陷阱!如果还采取 `缺什么补什么` 的思想,则可能陷入僵局。






另外,此次发文之后又会修炼一段时间……根骨不行,只能多修炼了。





---



# 番外篇:来自灵兽的彩蛋


















**事实上,我确实无法从 wasm 中找出 aes 加密、解密的 `key`,多出来的时间多看会动漫吧**。


---


# 补充
网站有多个播放源,本文分析的是 `VIP 西瓜` 的播放源。其它的播放源似乎可以直接拿到 `m3u8` 链接……………

我是不会改名的 发表于 2023-11-28 23:28

你不都找到key了吗
btoa(String.fromCharCode.apply(null, new Uint8Array(memories.$memory.buffer,80381,16)))

翼国游者 发表于 2023-11-29 16:03

花了约44分钟,从头到尾硬着头皮一字不差看完了,虽然我没有学习任何js、逆向、编译等等一切相关知识,但是为了表示尊重,还是说一句,特么的休仙真难!

linix 发表于 2023-12-13 16:02

大佬写的很精彩,但是人家不是给你地址了么?

然后2个m3u8就可以获取到下载的ts。难道因为你写的文章网站改了?

LoveCode 发表于 2023-11-29 10:41

我是不会改名的 发表于 2023-11-28 23:28
你不都找到key了吗
btoa(String.fromCharCode.apply(null, new Uint8Array(memories.$memory.buffer,80381 ...

原来是这样!我没发现还需要进行一次 btoa(),试了几次就放弃了。谢谢你!

100斤d胖子 发表于 2023-11-29 10:11

我是不会改名的 发表于 2023-11-28 23:28
你不都找到key了吗
btoa(String.fromCharCode.apply(null, new Uint8Array(memories.$memory.buffer,80381 ...

你好,这工具是什么呀?{:1_909:}

OVVO 发表于 2023-11-28 23:16

厉害啊!大佬

cv14 发表于 2023-11-28 23:17

超详细与耐心的技术交流 看好你哦大佬

zswpug 发表于 2023-11-28 23:21

厉害,很详细,谢谢分享

漁滒 发表于 2023-11-28 23:28

硬看汇编是很难的,好像go编译的wasm,业务函数都是动态绑定的,可以尝试转成.o文件用ida分析

bolic 发表于 2023-11-28 23:40

大佬NB,多谢分享{:1_893:}

xixicoco 发表于 2023-11-29 00:47

秒加精华啊,牛逼

atest 发表于 2023-11-29 01:14

学习一下前端的工具方法

chuan9 发表于 2023-11-29 07:11

感谢分享,太好了
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 绕过 AGE 动漫的 wasm 加密与解密函数