前言

继续搞spiderdemo第5题

网址如下

T5-数字采集加强一枪https://www.spiderdemo.cn/authentication/ob1_challenge/?challenge_type=ob1_challenge

正文

分析

打开页面,打开开发者工具,点击下一页

可以发现许多debugger

这个js的名字是ob1_challenge.js,可以把这个文件下载下来,去掉debugger

但是,先不慌

因为这个里面,有许多的混淆,比如

其实笔者尝试去解过混淆,但感觉很痛苦

那么,需要一点好玩的东西,哈哈哈哈哈哈哈

先不慌,慢慢来

确定请求

可以在ob1_challenge.js里面发现如下的东西

这个可以发现的,没有混淆

看看关键信息

const signature = O0o0O0O0();
const url = `/authentication/api/ob1_challenge/page/${page}/?challenge_type=${encodeURIComponent(type)}&signature=${encodeURIComponent(signature)}`;

结合图片,可以知道

  1. 发送的是get请求
  2. 然后O0o0O0O0这个函数,不需要传参
  3. 请求头没有加密参数
  4. params有两个,一个是challenge_type,定值ob1_challenge,另一个就是O0o0O0O0这个函数执行的结果

正常情况下,应该是要看看O0o0O0O0这个函数在干什么,笔者已经干过了,硬解混淆

但实际上没必要,不需要解混淆。

做个mitmproxy的实验

笔者把ob1_challenge.js复制到本地的index.js中,再导入index.js到html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="index.js"></script>
    <script>
        console.log(O0o0O0O0())
    </script>
</head>
</html>

运行,发现这个参数是可以打印出来的

但是,笔者点击刷新,是会被debugger

笔者想测试一下,把这个参数发送给某个端口,然后直接返回这个参数,看看可不可以

笔者首先引入jquery,额,fetch也行,这不重要。

index.html里面的代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>
<script src="index.js"></script>
<script>
    let signature= O0o0O0O0()
    let baseUrl = 'http://localhost:8082'
    $.post(baseUrl+'/a', signature, function (data) {
        alert(data);
    });
</script>
</html>

笔者发送一个post,和路由/a,

因为笔者发现空的路由会阻塞,所以随便搞了个路由。

给出mitmproxy中脚本的代码

from mitmproxy import http

def request(flow: http.HTTPFlow) -> None:
    if flow.request.method=="POST":
        flow.response = http.Response.make(
            status_code=200,
            content=flow.request.content,   # 把请求体原样返回
            headers={
                "Content-Type": flow.request.headers.get("Content-Type", "text/plain"),
                "Access-Control-Allow-Origin": "*",
                "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
                "Access-Control-Allow-Headers": "*",
            }
        )

post请求,直接原样返回。

结果如下

可以看到是拿到参数了的。

实际上就很简单了,直接发送请求就可以了

没有解混淆,不要管那些。

什么变种ob,这些那些的,笔者什么都不知道,没学过。

直接发请求

请求代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>
<script src="index.js"></script>
<script>
    let signature= O0o0O0O0()
    let baseUrl = 'http://localhost:8082'
    const url = baseUrl+`/authentication/api/ob1_challenge/page/1/?challenge_type=ob1_challenge&signature=${signature}`;
    $.get(url, function (data) {
        console.log(data);
    });
</script>
<body>

</body>
</html>

是一个get请求,添加cookie

from mitmproxy import http

def request(flow: http.HTTPFlow) -> None:
    if flow.request.method=="GET":
        flow.request.headers["Cookie"]="sessionid=ee9chqk26l9emee8runioq14st0yzc83"

mitmweb --mode reverse:https://www.spiderdemo.cn  -p 8082 -s proxy.py

结果如下

就没了,简简单单。


==============就这样,明天再说=========================================


获取100页数据

没问题,没有绕过debugger,也没有解混淆,哈哈哈哈哈哈哈哈哈。直接卡bug了

绕过debugger

首先,笔者发现一个好玩的事情,这个在pycharm里面可以调试html,

如果使用调试html

没想到,居然直接就在本地的index.js文件断下了

啊,哈哈哈哈哈,那这不是随便过了

这就不多说了

而且我在本地的index.js文件里面打断点,

调试模式,是不是也可以断下了,测试一下

笔者测试了一下

哦,想不到jetbrain系列的东西还有这个功能,笔者以前还不知道。

有点意思,发现了新大陆。

当然,比如过了debugger之后,

反而会出现报错

不是,哥们

既然如此

解混淆(一)

前面已经知道,可以在本地的js里面打断

那么,慢慢来,不慌

首先,在本地的js文件中,报错的地方打个断点

运行,结果如下

可以发现这个本质上是一个字符串String

但是,不对啊,没Data.String啊

Uncaught TypeError: Date.String is not a function

笔者在这里想了想,有问题。

为什么会是String,或者本身就是错误的代码

确实,有可能,

既然如此,不妨测试一下,

首先,一定是可以获取数据,加密一定是可以的,因为笔者已经过了。

恢复js文件本来的样子不打开开发者工具,在会报错的地方打印一下,

如果说,出现了打印语句,就说明要执行,不打印,就说明,这个代码有问题,

如果打印了,还需要判断是不是String修改如下

添加两个打印语句,结果如下

可以发现是now

有点意思,当笔者过了debugger,打印的是String,没有过打印的是now,这对吗?

笔者细细想想,就说明绕过debugger的使用,没绕对,有问题,再看看关键代码

                debugger ;const _fnStr_check = oO0oOO0O["toString"]();
                const _first50_check = _fnStr_check["substring"](0, 50);
                if (!_first50_check["includes"]("deb")) {
                    throw new Error("\u68C0\u6D4B\u5230\u53CD\u8C03\u8BD5\u4EE3\u7801\u88AB\u4FEE\u6539")
                }

首先,有debugger。

然后使用oO0oOO0O的toString方法,得到_fnStr_check

然后再执行_fnStr_check是substring方法

接着下面一个if语句,判断是否包括deb,如果不包括,就报错,

所以,很明显,笔者发现了其中的漏洞

这个代码没有说是判断是debugger,而是判断deb。

那么,笔者进行如下操作

  1. 在pycharm中,在js文件中,使用快捷键ctrl+r,把debugger替换成deb()
  2. 然后在js文件最前面写一个deb函数

如下

function deb(){
    
}

什么都不做

再获取一下数据

<script>
    let signature= O0o0O0O0()
    let baseUrl = 'http://localhost:8082'
    const url = baseUrl+`/authentication/api/ob1_challenge/page/1/?challenge_type=ob1_challenge&signature=${signature}`;
    $.get(url, function (data) {
        console.log(data);
    });
</script>

结果如下

额,又绕过去,笔者感觉出这道题的人是希望笔者解混淆

但是笔者没解就过了,哎,代码写的有bug,哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈

也又可能就是这么搞的,算了,没有标准答案

解混淆(二)

都走到这里了,笔者要把它解开

OoO0ooo0

这是一个关键的函数,代码如下

function OoO0ooo0(index) {
    deb() ;const _fnStr_check = OoO0ooo0["toString"]();
    const _first50_check = _fnStr_check["substring"](0, 50);
    if (!_first50_check["includes"]("deb")) {
        throw new Error("\u68C0\u6D4B\u5230\u53CD\u8C03\u8BD5\u4EE3\u7801\u88AB\u4FEE\u6539")
    }
    for (var OooOooOO = 3764559944 - (3764559944 - 2); OooOooOO !== 587680605 - (587680605 - 0); ) {
        deb() ;switch (OooOooOO) {
        case 3793311298 - (3793311298 - 4):
            return Oo000ooo;
            OooOooOO = 2882498261 - (2882498261 - 0);
            break;
        case 2467507125 - (2467507125 - 2):
            var o0O00Oo0 = encode_arr
              , Oo000ooo = atob(o0O00Oo0[index]);
            OooOooOO += 32641650 - (32641650 - 2);
            break
        }
    }
}

在本地js文件中打个断点,看看是怎么回事

首先encode_arr是一个数组,去掉本身吗?截取前50个

额,不重要,

后面,计算数字加减

比如2467507125 - (2467507125 - 2),就是2

这个js代码里面有非常多这样的计算

计算出来,移动就可以了

因此,代码如下

function OoO0ooo0(index) {
    return atob(encode_arr[index]);
}

O0o0O0O0

这个是最主要的函数,这个函数解混淆,额,全是数字计算,移动

慢慢来,耐心点

笔者移动之后,结果如下

突然,笔者发现了一个关键的东西

(function (myarr, num) {
        var OoOO00oo = function (nums) {
            while (--nums) {
                myarr.push(myarr.shift())
            }
        };
        OoOO00oo(++num)
    }
)(encode_arr, 16);

笔者去掉了前面1=的deb函数,这个好像在做偏移,把这个注释掉就会报错

哦哦哦哦哦,笔者明白了

搞这样,原来不是死代码。

修改一下这个偏移函数

如下

function rotateLeft(arr, num) {
    let times = num + 1;   
    while (--times) {      
        arr.push(arr.shift());
    }
}
rotateLeft(encode_arr, 16);

最终解开的代码

后续,继续美化代码

笔者是通过把一部分代码给AI和打断点两个操作

如果全部代码给AI,搞不了。


============过了不知道多久=====================


最终代码如下

function get_signature() {
  /* ------------------ 配置 ------------------ */
  const requestConfig = {
    url: '/authentication/api/ob1_challenge/page',
    method: 'get',
    headers: {
      common: {
        Accept: 'application/json, text/plain, */*'
      }
    },
    params: {
      float: 'down',
      genre: '5000',
      device: 'iphone',
      type: 'one',
      brand: 'paid'
    },
    baseURL: '',
    timeout: 30000
  };

  /* ------------------ 工具函数 ------------------ */
  // 将字符串先 URI-Encode 再转成 Base64(浏览器 / Node 兼容)
  function uriToBase64(str) {
    const step1 = encodeURIComponent(str).replace(/%([0-9A-F]{2})/gi, (_, hex) =>
      String.fromCharCode(parseInt(hex, 16))
    );
    if (typeof btoa === 'function') return btoa(step1);               // 浏览器
    if (typeof Buffer === 'function') return Buffer.from(step1, 'binary').toString('base64'); // Node
    throw new Error('No Base64 encoder available');
  }

  // 简单的 XOR 加解密
  function xorCipher(text, key) {
    const base = 0;
    return Array.from(text)
      .map((ch, i) => {
        const nCode = ch.charCodeAt(0);
        const kCode = key[(i + 10) % key.length].charCodeAt(0);
        return String.fromCharCode(nCode ^ kCode);
      })
      .join('');
  }

  // 读取 Cookie(若不存在则返回当前时间戳)
  function getCookieValue(key) {
    const reg = new RegExp('(^| )' + key + '=([^;]*)(;|$)');
    const m = document.cookie.match(reg);
    return m ? decodeURIComponent(m[2]) : Date.now().toString();
  }

  /* ------------------ 主流程 ------------------ */
  // 1. 计算“syncd”偏移
  const syncdValue = -getCookieValue('syncd');
  const deltaTime = Date.now() - (syncdValue || 0) - 1661224081041;

  // 2. 拼接 params 值并排序
  const paramValues = Object.keys(requestConfig.params)
    .filter(k => k !== 'analysis')          // 跳过已存在的 analysis 字段
    .map(k => requestConfig.params[k])
    .sort()
    .join('');

  // 3. 多层追加字符串
  const delimiter = '@#';
  let payload = uriToBase64(paramValues);
  payload += delimiter + requestConfig.url.replace(requestConfig.baseURL, '');
  payload += delimiter + deltaTime;
  payload += delimiter + 3;                 // 962613973 - (962613973 - 3)

  // 4. 浏览器指纹 + 随机后缀
  let fingerprintSeed;
  try {
    // 若“document”被劫持则主动退出
    if ((Object.getOwnPropertyDescriptor(window, 'document').get + '').indexOf('native code') === -1) {
      return false;
    }
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    ctx.textBaseline = 'top';
    ctx.font = '14px Arial';
    ctx.textBaseline = 'alphabetic';
    ctx.fillStyle = '#f60';
    ctx.fillRect(125, 1, 62, 20);
    ctx.fillStyle = '#069';
    ctx.fillText('Browser fingerprint', 2, 15);
    ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
    ctx.fillText('Browser fingerprint', 4, 17);
    fingerprintSeed = 'xyz517cda96efgh' + Math.floor(Math.random() * 10);
  } catch (e) {
    fingerprintSeed = 'xyz517cda96efgh' + Math.floor(Math.random() * 9);
  }

  // 5. 再次混淆 + 时间戳
  const obfuscated = uriToBase64(xorCipher(payload, fingerprintSeed));
  const timeSuffix = new Date().getTime();
  const final = obfuscated + timeSuffix;

  // 6. 最终 Base64 一次并返回
  return btoa(final);
}

没有偏移函数

没有debugger

没有O0o0O0O0

最终解混淆之后的代码如下

最后的测试

将解混淆的函数放到a.js文件中

index.html文件的内容如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>
<script src="a.js"></script>
<script>
    let signature= get_signature()
    let baseUrl = 'http://localhost:8082'
    const url = baseUrl+`/authentication/api/ob1_challenge/page/7/?challenge_type=ob1_challenge&signature=${signature}`;
    $.get(url, function (data) {
        console.log(data);
    });
</script>
</html>

结果如下

没问题,解混淆成功。

说起来,这个东西还和cookie有关,但是笔者在本地没有cookie啊

笔者把那个cookie有关的函数的参数写死的。

没有cookie,可以使用的时间戳

也行

终于解决了

解混淆是真的繁琐。

总结

笔者发现在pyharm可以对本地html进行调试,在js文件里面打断点也会被中断,这就很好了。

解混淆是繁琐的。就这样。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐