tiktok滑块篇之Wasm扣代码

  • 最后还剩下一个图片请求返回的内容是加密的,本章把这个图片加密解一下

  • 本篇做的是web的滑块,app跟web用的是同样的加密,用扣代码的方式把这个搞定

环境

  • 滑块版本: 3.0.22
  • 滑块地址: 点击

定位

  • 上图可以看到后端返回的内容是一个加密的值,那解密完成后会是一个字符串,然后要转成json供后面使用,肯定要调用到 JSON.parse这个函数

  • 所以这里直接Hook JSON.parse

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    const originalJsonParse = JSON.parse;  // 保存原始的 JSON.parse 函数

    // 创建一个新的 JSON.parse 函数

    JSON.parse = function (text, reviver) {

        try {

            const result = originalJsonParse(text, reviver);        // 调用原始的 JSON.parse 函数

            debugger;

            console.log("Parsed result:", result);

            return result;

        } catch (error) {

            console.error("JSON parsing error:", error);

            throw error;

        }

    };

  • 从Console中执行后,刷新下图片,重新请求

  • 刷新后,就会hook到好几个请求,很多明显不是请求相关的,一直放过就行,直到看见下面这个明显就是解密后的结果,我们可以跟一下调用栈

ts文件格式化

  • 有些朋友hook以后可能跟我的页面不一样,文件是.ts的代码都在一行 无法格式化(如下图)

  • 这里需要设置一下

  • 设置 ==> 然后找到 JavaScript source maps 去掉前面的对号 就可以了

流程分析

  • 从hook处向上点一个堆栈 看到如下位置的代码

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    case 10:

    return a = e.sent,

    e.next = 13,

    a.decryptData(atob(o.data));

    case 13:

        if (c = e.sent,

            s = {},

            e.prev = 15,

            s = JSON.parse(c.decData),

            !Qo) {

            e.next = 23;

            break

        }

        return Qo = !1,

        e.next = 21,

        _n("fetch_picture_start");

    1. c.decData 就是最后解密出来的值
    2. c = e.sent 这句话的意思是 c就是上一个 case 的返回值,所以我们要看谁跳到的 case13
    3. case10 中能看到有 e.next = 13 说明他的下一个 case 就是13 所以我们要看 case10 的返回值
    4. a.decryptData(atob(o.data)); 这是 case10 的最后一句话,我们断点打过来打一下
    5. 这里需要刷新一下图片,因为刚的断点位置已经过去了
    6. 还有我们前面的hook 也可以去掉了 JSON.parse = originalJsonParse; 执行一下 就可以

  • 这里o.data就是服务端返回的加密的内容,然后做了一个atob,通过 a.decryptData(atob(o.data)) 变成了正常的内容

  • 跟进a.decryptData看一下解密的实现

  • 这里把atob后的内容传进来,然后调用了 e 函数传进去

  • 再跟进e看一下

  • 这里是个异步直接单步跟,从e.apply进到下一个函数

  • 这里我们分析了一下其他代码没啥核心的,直接把断点下到 switch 看一下执行流程

  • 多点几次最后定位到case17 中的 d 就是我们需要的内容

  • 上面看到有一个 ccall 的调用,那加密的核心逻辑很有可能是在这里,我们跟进去看一下

  • 进入之后,有一个 a.apply 的调用,看起来比较像核心位置,点击进去

  • 调用了 Oe.h 方法,点进 Oe.h 这里就是 wasm 代码了

保存wasm文件

  • 本节我们用扣代码的方式来把这个wasm 搞定,下一节 会写 App的 wasm 纯算

  • 首先 我们先把当前打开的这个 wasm 文件保存下来

  • 新起一个项目,建一个js文件,用来扣代码,然后把上面我们存下来的wasm文件和js放到同级

还原控制流

  • 既然我们是从控制流进来的,我们先手动还原一下这个控制流看看都在干嘛

  • 这块代码我们按照 case 执行顺序 改完后就是这样

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    n = a(atob(r));

    u = t.allocate(n, t.ALLOC_NORMAL);

    s = i(4);

    l = t.ccall("cmd_clientDecrypt", "number", ["number", "number", "string", "number"], [u, n.length, null, s.offset]);

    f = t.getValue(s.offset, "i32");

    A = i(f);

    t.ccall("cmd_clientDecrypt", "number", ["number", "number", "number", "number"], [u, n.length, A.offset, s.offset]);

    f = t.getValue(s.offset, "i32");

    d = t.UTF8ArrayToString(A.data, 0, f);

    t._free(u);    

    c(s);

    c(A);

    console.log(d)

  • 最后拿到d也就能拿到解密后的结果了

找加载位置

  • wasm如果没有基础的同学,去看星球里这篇文章,讲的很清晰

  • wasm 有同步和异步两种加载方式

    • 同步:

      1

      2

      const module = new WebAssembly.Module(wasmBuffer);

      const instance = new WebAssembly.Instance(module, importObject);

    • 异步:

      1

      2

      WebAssembly.instantiateStreaming    (需要比较新浏览器)

      WebAssembly.instantiate    (兼容性更好)

  • 所以,同步加载有一种方式,异步加载有两种方式,那我们直接搜索一下,把有加载的位置都下上断点,看看走了那一个

    • 异步加载一

    • 同步加载一

  • 搜索完发现就这两处,直接都下上断点,重新运行

  • 停在了这里 我们看一下 e 是一个和很长的数组,其实这里向上跟两个堆栈就能看到,这个e就是wasm的代码,也就是我们上面从网页上保存的那个.wasm的文件

  • 所以这里就是我们要找的加载位置

扣代码

  • 这里加载都是在一起的,我们可以直接把这一块代码都扣出来
  1. 首先把全部的代码复制到一个可以折叠的编辑器里,我这里是 notepad++

  2. 折叠完后,直接搜索 WebAssembly.instantiate(e, t) 这一块加载代码

  3. 搜到之后 我们直接向上找他属于那一个代码块,把内容拿下来,不用函数包裹了

    • 开始:

    • 结束:

  4. 直接复制到我们的js文件

    • 复制过来之后,586行会报个错,因为我们直接粘过来,他不是一个函数了,不需要返回,所以删掉这两行就可以了
  5. 处理一下,代码里的加载方式,让他读我们本地的文件,并且把调用方式用改成同步的,同步处理起来会简单些

    • 这块代码我们直接改为同步加载

    1

    2

    3

    4

    5

    6

    function V(e, t, r) {

        const fs = require("fs");

        n = new WebAssembly.Module(fs.readFileSync("./00021e0a.wasm"));  // 传入前面保存的wasm文件路径

        b = new WebAssembly.Instance(n,t);    // 第一个参数是Model  第二个参数是 导入的对象

        return r(b);

    }

  6. 然后加载代码搞好之后,我们把前面控制流的代码封成一个函数,放在底部

  7. 我们运行一下z1函数,然后会出现个报错

    • 原因是因为Oe是一个空对象,那我们看一下Oe的生成

    • 就是这个t函数 我们看一下t的返回值

    • 问题就出在这,我们修改一下 让他返回t的执行结果

      1

      2

      3

      X(0, z, e, (function (e) {

      return t(e)

      }

    • 改成上面的就可以了

  8. 报错r没有定义,这里r 就是服务端返回的加密的内容,我们去复制一个

  9. 直接去调用一下

    • 复制结果,放到js里就可以 let r = 复制的结果
  10. 报错 a 没有定义

    • 这里直接去把a扣一下

    • 这里看到下一行有个i 函数,我们后面也会用到就一起拿下来
    • 还有一个 c 函数,也没有,我们直接点进去一起扣下来了

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    a = function (e) {

        var t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 0,

        r = 0;

        if (0 == (r = t <= 0 ? e.length : t))

            return null;

        for (var n = new Array(r), o = 0; o < r; o++)

            if (o > e.length)

                n[o] = 0;

            else {

                var i = e.charCodeAt(o);

                n[o] = i

            }

        return n

    },

    i = function (e) {

        if (e <= 0)

            return null;

        var r = t._malloc(e);

        return null == r ? null : (t.HEAPU8.set(new Uint8Array(e), r), {

            data: t.HEAPU8.subarray(r, r + e),

            offset: r

        })

    }

    c = function(e) {

        e && "number" == typeof e.offset ? t._free(e.offset) : console.log("free error ", e)

    }

  11. 报错 t 未定义

    • 我们在网站上看一下 t 是什么

    • t 有这么多的方法,我们随便搜一个

    • 看到一段熟悉的代码,前面我们处理return错误的时候就是这个函数,所以这里的c就是我们用到的

  12. 再次运行,就成功解密了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值