某购物网站逆向
url:'aHR0cHM6Ly93d3cucGluZHVvZHVvLmNvbS9ob21lL2JveXNoaXJ0Lw=='
目标:抓取到商品数据,难点:补环境+export时间戳
声明:本文只作学习研究,禁止用于非法用途,否则后果自负,如有侵权,请告知删除,谢谢!(手动狗头)
一、抓包分析
很明显,咱要的东西就是在这,然后要逆向的参数就是anti_content
二、寻找anti_content
直接搜索
发现有两个地方,很开心,分别打上断点,然后刷新页面
发现断到这里了,然后发现此时还没有找到anti_content
然后走t.next = 10,最后return Object(l.a)();
控制台打印一下Object(l.a)();
发现是个Promise对象(pending状态),那么我们就打印看看它的返回值
发现就在这里!进去l.a看看
走y这个函数,此时this指向什么?就是new y,也就是y的实例,然后怎么办呢?找y函数呗
仔细一瞧,发现y的函数就在下面
javascript">function y() {
return (y = u(i.a.mark(function e() {
return i.a.wrap(function(e) {
for (; ; )
//前面都是乱七八糟的,不用看
switch (e.prev = e.next) { //一开始就是0=0
case 0:
if (r) {
e.next = 3;
break
}
return e.next = 3, //这里敲个断点
new Promise(function(e) {//promise对象
c.push(e),
f()
}
);
case 3:
return e.next = 5,
r.messagePackSync();//这里也敲一个断点
case 5:
return e.abrupt("return", e.sent);
case 6:
case "end":
return e.stop()//结束值
}
}, e)
}))).apply(this, arguments)
}
仔细分析发现,先来到return e.next = 3 ,先看看这个promise的结果
很明显,不是这里,接着来到r.messagePackSync();//这里也敲一个断点
歪日,发现结果就在这,进去干
wc,混淆,不要怕,这是好消息至少位置对了,不然为啥要混淆,此地无银三百两((//̀Д/́/)),先断到promise对象这里
发现值已经生成了,很明显最后的n[r("0x15c", "S]Zj")](t, nr("0x1bb", "A3e0"))有很大问题
发现咱要的结果出来了,嘿嘿,简化一下看看,就是dt()
跳过来终于发现咱要的就是dt()函数,提前预定window.dog = dt()
三、扣代码
将所有代码扣下来拉到Notepad++里面,折叠所有层次
发现是webpack,简单~首要任务就是找调度器
先把dt()所在的层次扣下来
发现就是fbeZ这一个层次,直接弄过去,然后顺带在dt函数执行完了之后加个window.dog = dt()
那么我们之后就是要执行下fbeZ这个函数,让得到的anti_content值赋值给window.dog变量
那么想要执行webpack,第一步找调度器
3.1 寻找调度器
找到最开始的fbeZ,打印输出n函数,然后跳转到调度器
发现就在这里,那咱甭跟它客气,直接全扣,
然后再增加这两个地方用来输出r ,以及将调度器赋值给全局变量
3.2定义个函数输出anti_content
找到调用fbeZ所需要的三个参数
function get_data(){
//获得三个参数
var e = {"i":"fbeZ","l":false,"exports":{}},
t = {},
n = window.loaders //就是n函数
fbeZ(e,t,n)//执行fbeZ
console.log(window.dog())
}
执行结果:
又到了愉快的补环境时间~
四、补环境(一)
4.1 8oxB+ YuTi webpack
直接搜索8oxB
发现在这个文件,直接扣下来
同理可得YuTi
找到这个地方 ("0x3f", "LZ%H")in re[P];
注意:搜索的时候必须要取消空格,不然根本搜不到
4.2 补充基本环境
很明显,看到document就知道精彩的东西来了,开始一些基本的补环境,那就是windows、location、document、history、navigator
location
navigator
screen
在控制台分别打印输出
// 一、补充window环境
window = {}
window = global;
//二、补充 document
document = {
referrer: 'https://www.pinduoduo.***'
}
Document = function Document() {
}
Object.setPrototypeOf(document, Document.prototype)
Document.prototype.addEventListener = function () {
}
Document.prototype.cookie = '_nano_fp=Xpmon5EoXpCqX0doXT_ubdWsBT4vZ2vjB~PCm7Pn; api_uid=rBUUO2W4rXuDCDYK0P5+Ag=='
//三、补充 location
location = {
'hash': "",
'host': "www.pinduoduo.***",
'hostname': "www.pinduoduo.***",
'href': "https://www.pinduoduo.***/home/girlclothes/",
'origin': "https://www.pinduoduo.***",
'pathname': "/home/girlclothes/"
}
Location = function Location() {
}
Object.setPrototypeOf(location, Location.prototype)
// 四、补充 navigator
navigator = {
appCodeName: "Mozilla",
appName: "***scape",
appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0",
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0'
}
Navigator = function Navigator() {
}
Object.setPrototypeOf(navigator, Navigator.prototype)
Navigator.prototype.hasOwnProperty = function () {
return true
}
// 五、补充 screen
screen = {
// width: 1728,
}
Screen = function Screen() {
}
Object.setPrototypeOf(screen, Screen.prototype)
// 六、补充 history对象
history = {}
History = function History() {
}
Object.setPrototypeOf(history, History.prototype)
History.prototype.back = function () {
}
发现此时已经出了值,但是真的那么简单吗?
那我们拿到值去试一试
很明显,没那么简单
五、补环境(二)
那么我们到底是哪里的问题呢?
第一反应是补环境没补全,可以用node-inspect 打印输出看看,到底哪些值没有弄。先配置好proxy代理(说白了就是hook)
5.1 配置代理
// proxy代理声明
function proxy_watch(obj) {
return new Proxy(obj, {
get(target, p, receiver) {
debugger;
let val = Reflect.get(...arguments);
console.log("get", target, "=获取属性>", p, "=值>", val);
return val;
},
set(target, p, value, receiver) {
debugger;
console.log("set", target, "=设置属性>", p, "=值>", value);
return Reflect.set(...arguments);
}
});
};
window = proxy_watch(window)
document = proxy_watch(document)
location = proxy_watch(location)
navigator = proxy_watch(navigator)
5.2 删除node里的环境
随便补齐一点点,发现还是不能通过,后来我意识到这样补下去没有尽头,于是我就思考,有些浏览器不能执行,而node里可以执行,也就是检测到node
www.zhihu.***/question/584082357
//在node中删去以下的东西
delete global;
delete Buffer;
发现还是没有获得值
六、查找其它问题
让我们先思考下,整个调用流程是什么,首先毫无疑问就是先用调度器(自执行函数)这里毫无疑问,所有的都要执行
然后将调度器赋值给全局变量window.loaders,作为参数n传递给fbeZ,问题就在于fbeZ执行过程中,绝对有哪一部分没有执行到。
先看看dt有没有执行到
执行了并赋值了,说明上面的代码都没问题,继续往下拉代码看看
分别在每一段可疑的地方插上的console输出,看看到底哪里没有执行
那么我们可以直接在网站上找到t[_("0x1d0", "&CF7")]
发现是exports,已经是说浏览器上加载了这个exports,而本地环境中没有去加载
var lt = new ut;
t[_("0x1d0", "&CF7")] = function() {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}
, t = _;
return e[j] && re && lt[t("0x1f", "@0Zy")](e[j]), //发现此时执行的lt(ut对象的实例)
lt
}
/*
t("0x1f", "@0Zy") == "updateServerTime"
e[j] == 1706758046 时间戳
很明显,咱的对象就是围绕时间,没有经过exports,我们用的都是错误的时间,导致得到的结果就是错误的,那么如何才能保持时间正确呢?
两种方法:
一、找到ut函数,去寻找跟时间有关的东西,然后把它变成动态的
二、
*/
6.1 查找ut函数
然后往下查找
发现该地方出现了时间戳,很可疑,打上断点然后刷新页面,此时 e[j] = undefined,t("0x135", "O3]W") = “updateServerTime”
很明显就是将一个固定的数值当作时间戳赋值过去,那么我们要做的正是替换 改成如下代码,获得动态的时间戳
this[t("0x135", "O3]W")](new Date().getTime());
此时结果出来了,看着像是我们瞎猫碰到死耗子,看着挺简单的,但实际上这东西需要思考几小时才能发现,狗
6.2执行export
return e[j] && re && lt[t("0x1f", "@0Zy")](e[j]), //发现此时执行lt,lt[t("0x1f", "@0Zy")]作为函数
lt["updataServerTime"](e[j])
//e[j]此时为固定值,所以需要持续更新的时间戳
6.3 总结pdd恶意时间戳
到此为止我们已经把恶意的时间戳给补齐,也算是个大坑,平常谁会想到这个时间竟然是个检测点,太nb了。
现在我们就去找找updataServerTime到底是怎么更新时间戳的,我们直接全局搜索servertime
分别到这两个地方打上断点,刷新页面寻找时间是如何过来的
发现此时断点断在了这里
e是从t.sent传过来的,而t.sent作为上一步的返回值,需要找的就是W(),进入到l后发现又是个控制流
从上到下执行时,很明显case2后面都是一些判断逻辑,而t.server_time是已经拿到时间戳了,真相就在我打断点的那一行,也就是o.a
Object(o.a)("/api/server/_stm", "get", {}, "https://apiv2.pinduoduo.***");
/*
这里我们可以知道传入参数分别为url,请求方式是get请求
然后我们就要找o.a函数
*/
function i(e, t, n, r) {
//t == get, e、r都是一段url
"" === t && (t = "post");
var i = {
url: e,
method: t,
data: n
};
//返回的结果是一个Promise对象
return new Promise(function(e, t) {
Date.now();//猜的没错是个时间戳
o.a.create({
headers: {
A***ept: "application/json, text/javascript",
"Content-Type": "application/json;charset=UTF-8"
},
timeout: 3e3,
baseURL: r || "https://home-api.pinduoduo.***"
})(i).then(function(t) {
e(t)
}).catch(function(e) {
t(e)
})
}
)
}
//到这里我有点好奇baseurl + url(e+r) 发送请求后的返回值是啥
发现就是请求该接口返回到server_time的值,而promise对象就是调用接口
此时e已经是时间戳了,作为一个对象{ serverTime: e };
传递给i函数,new i是个实例,我们可以跳到i函数看看
发现这不就是export
这个t[G]就是时间戳,X = 879609302220 也是最初的值,要来到这里重新赋值
这就是恶意时间戳,我只能说一句NB
七、代码整合
歪日,干了那么久终于要结束了,都不知道以后瑞数、Akamai、航司、227该如何下手,诶,走一步看一步吧
js代码就不弄上来了,2w多行自己去扣吧,现在就扣下python代码,然后给你们看看执行结果吧
import requests
import subprocess
from functools import partial
subprocess.Popen = partial(subprocess.Popen, encoding='utf-8')
import execjs
headers = {
"A***ept": "application/json, text/javascript",
"A***ept-Language": "zh-***,zh;q=0.9",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Origin": "https://www.pinduoduo.***",
"Pragma": "no-cache",
"Referer": "https://www.pinduoduo.***/",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0",
"sec-ch-ua": "^\\^Not",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "^\\^Windows^^"
}
url = "https://apiv2.pinduoduo.***/api/gindex/tf/query_tf_goods_info"
with open('anti_content.js', 'r', encoding='utf-8') as f:
JS = f.read()
code = execjs.***pile(JS)
anti_content = code.call('get_data')
print(anti_content)
params = {
"tf_id": "TFRQ0v00000Y_13397",
"page": "1",
"size": "100",
"anti_content": anti_content
}
response = requests.get(url, headers=headers, params=params)
print(response.text)
OK,完结撒花,记录下踩的坑,还得继续学习
提前祝各位新年快乐!