0


pdd(web)逆向分析

某购物网站逆向

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的函数就在下面

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.com'
}
​
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.com",
    'hostname': "www.pinduoduo.com",
    'href': "https://www.pinduoduo.com/home/girlclothes/",
    'origin': "https://www.pinduoduo.com",
    'pathname': "/home/girlclothes/"
}
​
Location = function Location() {
​
}
​
Object.setPrototypeOf(location, Location.prototype)
​
// 四、补充 navigator
navigator = {
    appCodeName: "Mozilla",
    appName: "Netscape",
    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.com/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.com");
/*
这里我们可以知道传入参数分别为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: {
                        Accept: "application/json, text/javascript",
                        "Content-Type": "application/json;charset=UTF-8"
                    },
                    timeout: 3e3,
                    baseURL: r || "https://home-api.pinduoduo.com"
                })(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 = {
    "Accept": "application/json, text/javascript",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Cache-Control": "no-cache",
    "Connection": "keep-alive",
    "Origin": "https://www.pinduoduo.com",
    "Pragma": "no-cache",
    "Referer": "https://www.pinduoduo.com/",
    "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.com/api/gindex/tf/query_tf_goods_info"
with open('anti_content.js', 'r', encoding='utf-8') as f:
    JS = f.read()
code = execjs.compile(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,完结撒花,记录下踩的坑,还得继续学习

提前祝各位新年快乐!


本文转载自: https://blog.csdn.net/2301_79445611/article/details/135979201
版权归原作者 恍惚逆向 所有, 如有侵权,请联系我们删除。

“pdd(web)逆向分析”的评论:

还没有评论