pdd(web)逆向分析

恍惚逆向 2024-08-01 11:33:01 阅读 97

某购物网站逆向

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

<code>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所需要的三个参数

<code>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

 

在控制台分别打印输出

<code>// 一、补充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 配置代理

<code>// 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

<code>//在node中删去以下的东西

delete global;

delete Buffer;

发现还是没有获得值

六、查找其它问题

让我们先思考下,整个调用流程是什么,首先毫无疑问就是先用调度器(自执行函数)这里毫无疑问,所有的都要执行

然后将调度器赋值给全局变量window.loaders,作为参数n传递给fbeZ,问题就在于fbeZ执行过程中,绝对有哪一部分没有执行到。

先看看dt有没有执行到

执行了并赋值了,说明上面的代码都没问题,继续往下拉代码看看

分别在每一段可疑的地方插上的console输出,看看到底哪里没有执行

那么我们可以直接在网站上找到t[_("0x1d0", "&CF7")]

发现是exports,已经是说浏览器上加载了这个exports,而本地环境中没有去加载

<code>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”

很明显就是将一个固定的数值当作时间戳赋值过去,那么我们要做的正是替换 改成如下代码,获得动态的时间戳

<code>this[t("0x135", "O3]W")](new Date().getTime());

此时结果出来了,看着像是我们瞎猫碰到死耗子,看着挺简单的,但实际上这东西需要思考几小时才能发现,狗

6.2执行export

<code> 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

<code>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代码,然后给你们看看执行结果吧

<code>import requests

import subprocess

from functools import partial

subprocess.Popen = partial(subprocess.Popen, encoding='utf-8')code>

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:code>

   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,完结撒花,记录下踩的坑,还得继续学习

提前祝各位新年快乐!



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。