网页逆向-某音ab参数逆向

评论请求中的a_bogus参数

根据堆栈下断点,这里要多找几个地方,太靠后了的话不是需要的断点

当断在图中所示位置时,发现e的属性值里面包含了a_b参数

继续看上层函数,发现r的属性值里也有a_b参数

在这个函数开始的地方打下断点,但是运行之后发现断下来的次数太频繁了,这里使用条件断点

1
r._tnc_request_url.includes("a_bogus")

断下来之后跟到上层函数,发现此时arguments里面就携带了a_b参数

继续往上,发现是这里的p携带了参数,函数的参数e也有a_b,还得继续往上找

发现这里的e值里有a_b

同样的方法往上找,断在这里的时候this也有a_b参数

再往上找,这里的e里携带了目标参数,但是不建议在这里下断点,因为在vmp里面,下断点之后网页直接卡死了

在vmp的上面断点

可以推测m.send(p)这里的m经过后面的那段vmp的处理之后,就生成了a_b参数

通过断点的现象也可以判断,刚执行到这里的时候,m里面还没有任何值,执行完了就有了,说明关键代码就在上面看到的vmp里面,也就是xdN三个函数

先把整个js的代码给扣下来,而且这牵涉到一个问题,就是这里的X函数是在vmp里面,没法像其他的逆向一样,有一整个加密函数,然后扣代码之后直接调用封装好的加密函数就行了,通过断点也可以验证这个猜想,断在X(e, this, arguments, r)之后,不是每次断点都是执行的加密的那一步

可以看到,两次断点结果,r的值都不一样

现在要考虑的就是代码扣出来了,怎么把这个X函数给导出,就需要找加密时执行的X函数的特征,通过执行发现,当执行到加密时的X函数的时候,参数e的值是固定的,可以通过转字符串来进行判断之后导出到全局

1
2
3
4
5
6
7
8
9
10
11
12
13
function D(t, r) {
var e = z[t];
Y.has(t) && V.delete(Y.get(t));
var n = function() {
return X(e, this, arguments, r)
};
if (JSON.stringify(e) === '[[34,54,0,3,34,30,214,41,212,34,30,214,30,70,54,0,4,74,0,4,30,218,54,0,5,74,0,5,30,72,54,0,6,33,74,2,33,74,0,6,0,1,54,0,7,74,0,7,41,5,74,0,6,53,11,60,161,74,0,6,60,216,30,178,59,2,54,0,8,74,0,8,30,162,18,30,219,73,165,0,1,29,17,5,74,2,3,30,150,41,18,74,0,8,30,162,18,30,163,73,165,74,2,3,30,150,0,2,26,74,0,8,30,162,18,30,219,73,220,0,1,29,41,45,33,74,3,14,0,0,26,33,74,2,37,74,0,8,30,162,18,30,9,0,0,74,0,2,0,2,54,0,9,74,0,8,30,162,18,30,163,73,220,74,0,9,0,2,26,74,0,7,29,41,10,74,0,5,74,0,8,30,178,20,72,34,30,214,18,30,51,63,108,0,1,26,33,74,2,36,74,0,8,30,215,0,1,41,7,33,74,2,5,0,0,26,34,73,214,25,26,74,1,4,18,30,126,34,74,0,2,39,1,0,2,26,33,76],1,true,[]]') {
window.encABParam = n;
}
return Y.set(t, n),
V.set(n, [e, r]),
n
}

后面就是正常补环境,现在就是怎么去调用的问题

上层函数是m.send(p);,然后调用了n函数,m.send(p) 实际调用的是 n(p),也就是 send = n,而 n 的函数体是调用 X(e, this, arguments, r),最终调用的是 X 函数,X函数里的this就是上下文marguments就是p,所以可以通过window.aaa.call(ctx, p)或者window.aaa.apply(ctx, p)来调用,这里的ctx就是thisXMLHTTPrequest的实例)

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
let nn = new XMLHttpRequest();
nn.bdmsInvokeList = [
{
"args": [
"GET",
"/aweme/v1/web/comment/list/?device_platform=webapp&aid=6383&channel=channel_pc_web&aweme_id=7514643788959223090&cursor=10&count=10&item_type=0&insert_ids=&whale_cut_token=&cut_version=1&rcFT=&update_version_code=170400&pc_client_type=1&pc_libra_divert=Windows&support_h265=0&support_dash=0&cpu_core_num=6&version_code=170400&version_name=17.4.0&cookie_enabled=true&screen_width=1535&screen_height=711&browser_language=zh-CN&browser_platform=Win32&browser_name=Chrome&browser_version=137.0.0.0&browser_online=true&engine_name=Blink&engine_version=137.0.0.0&os_name=Windows&os_version=10&device_memory=8&platform=PC&downlink=10&effective_type=4g&round_trip_time=150&webid=7515621897335309851&uifid=e71d819f1cb72e7166823ce125547a3e5a83b631a52f7c0b3c34cd9714dd602dafc93e0a2336bc1420a7cd4f99bd725a871d41e6fabb0e6e216c95ebfe62f53e67224e860921f96e8ea7fd662468baf120f109101e7c238673ec26b8291b91c620747e7574be9201cdc4cc8d505c48b31a67372043c867e539703b05244742991f08db69f4e070fd8f8a2cb6ccc077b5b415fa2623f70fb1f038d25bc71fa034",
true
],
func:function(){}
},
{
"args": [
"Accept",
"application/json, text/plain, */*"
],
func:function(){}
},
{
"args": [
"uifid",
"e71d819f1cb72e7166823ce125547a3e5a83b631a52f7c0b3c34cd9714dd602dafc93e0a2336bc1420a7cd4f99bd725a871d41e6fabb0e6e216c95ebfe62f53e67224e860921f96e8ea7fd662468baf120f109101e7c238673ec26b8291b91c620747e7574be9201cdc4cc8d505c48b31a67372043c867e539703b05244742991f08db69f4e070fd8f8a2cb6ccc077b5b415fa2623f70fb1f038d25bc71fa034"
],
func:function(){}
}
]
const pp = [null];
let re = encABParam.apply(nn,pp);

然后运行之后调试进入return X(e, this, arguments, r)这里,现在的问题是怎么拿到返回值,通过调试可以发现,当return X(e, this, arguments, r)被调用之后,this(也就是thisXMLHTTPRequest的实例)的secureOpenArgs的值中就会携带了ab参数

可以看到刚进入X函数的时候,此时secureOpenArgs的值中还没有ab参数

可以通过hook来找secureOpenArgs什么时候被赋值的,以下代码仅作用于步入X函数之后r对象还没有secureOpenArgs属性的时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Object.defineProperty(r, 'secureOpenArgs', {
configurable: true,
enumerable: true,
set(value) {
debugger; // 在这里断下来
console.log('secureOpenArgs 被赋值为:', value);
console.trace(); // 打印调用栈(可选)
Object.defineProperty(r, 'secureOpenArgs', {
value,
writable: true,
configurable: true,
enumerable: true,
});
},
get() {
return undefined; // 初始还没赋值,返回 undefined
}
});

然后就蒙蔽了,此时调试发现r刚进函数的时候已经有secureOpenArgs字段了,因为代理的关系只能重新调试,这个时候是在未登录状态下进行调试的,未登录状态的加密函数和基本逻辑没有变,只是X的上层函数变了,可以直接在X那里打条件断点

1
2
3
4
5
typeof this === 'object' &&
this !== null &&
'bdmsInvokeList' in this &&
'secureOpenArgs' in this &&
this.secureOpenArgs[1].includes("comment")

然后就发现未登录状态下,ab参数的生成之后的值不是放在secureOpenArgs里面了,而是执行之后丢到this_url属性里面去了

而且调试发现刚进X函数的时候,r对象还没有_url属性,所以可以hook下来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Object.defineProperty(r, '_url', {
configurable: true,
enumerable: true,
set(value) {
debugger; // 在这里断下来
console.log('_url 被赋值为:', value);
console.trace(); // 打印调用栈(可选)
Object.defineProperty(r, '_url', {
value,
writable: true,
configurable: true,
enumerable: true,
});
},
get() {
return undefined; // 初始还没赋值,返回 undefined
}
});

hook之后发现是在var m = n.apply(d, e);这里进行赋值的,当然是在hook之后的函数的上面好几层函数

然后通过打印de,定位到e的生成,打印参数就行了

1
2
3
4
5
6
7
8
var m = n.apply(d, e);
if (e[1] != undefined && Array.isArray(e[1]) && (typeof e[1][1] === 'string' || Array.isArray(e[1][1])) && e[1][1].includes("a_bogus")){
console.log((e[1][1]));
}
// if (e[1][1].includes("a_bogus")){
// console.log(e[1]);
// }
v[++p] = m

几个注意的点,一是传参的时候,p[null]而不是null,二是msToken的值要放到localStoragexmst属性里(hook的时候会打印)或者直接丢参数里面

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
let msToken = 'xxxxxxx';
let nn = new XMLHttpRequest();
nn.bdmsInvokeList = [
{
"args": [
"GET",
"https://www.douyin.com/aweme/v1/web/comment/list/?device_platform=webapp&aid=6383&channel=channel_pc_web&aweme_id=7515806861174557987&cursor=0&count=10&item_type=0&insert_ids=&whale_cut_token=&cut_version=1&rcFT=&update_version_code=170400&pc_client_type=1&pc_libra_divert=Windows&support_h265=0&support_dash=0&cpu_core_num=6&version_code=170400&version_name=17.4.0&cookie_enabled=true&screen_width=1535&screen_height=711&browser_language=zh-CN&browser_platform=Win32&browser_name=Chrome&browser_version=137.0.0.0&browser_online=true&engine_name=Blink&engine_version=137.0.0.0&os_name=Windows&os_version=10&device_memory=8&platform=PC&downlink=1.45&effective_type=3g&round_trip_time=450&webid=7515621897335309851&uifid=e71d819f1cb72e7166823ce125547a3e5a83b631a52f7c0b3c34cd9714dd602dafc93e0a2336bc1420a7cd4f99bd725a871d41e6fabb0e6e216c95ebfe62f53e67224e860921f96e8ea7fd662468baf120f109101e7c238673ec26b8291b91c620747e7574be9201cdc4cc8d505c48b31a67372043c867e539703b05244742991f08db69f4e070fd8f8a2cb6ccc077b5b415fa2623f70fb1f038d25bc71fa034&mstoken={msToken}",
true
],func:function(){}
},
{
"args": [
"Accept",
"application/json, text/plain, */*"
],func:function(){}
},
{
"args": [
"uifid",
"e71d819f1cb72e7166823ce125547a3e5a83b631a52f7c0b3c34cd9714dd602dafc93e0a2336bc1420a7cd4f99bd725a871d41e6fabb0e6e216c95ebfe62f53e67224e860921f96e8ea7fd662468baf120f109101e7c238673ec26b8291b91c620747e7574be9201cdc4cc8d505c48b31a67372043c867e539703b05244742991f08db69f4e070fd8f8a2cb6ccc077b5b415fa2623f70fb1f038d25bc71fa034"
],func:function(){}
}
]
const pp = [null];
encABParam.apply(nn,pp);