昨夜酉时,一顿困意席卷而来,简单冲洗后便沉沉睡去,深夜恍惚间风雨大作,爽风阵阵,一洗晚春高温,待至凌晨四五点,急雨退去,夜色渐明,雅雀欢喜,想来近日为推进项目,鲜有片刻缓息,此时却是难得的机会,遂有此篇,以备将来不时之需。
PDD 风控系统的关键
毫无疑问:Anti-Content
。
打开任意一个XHR
请求信息,都会有anti-content
请求参数,实践证明,每个请求只要带有合法(且匹配)的anti-content
、cookie
、user-agent
,即可通过网关。
PDD 风控系统的调试
打开开发者控制台,选择Network
面板,勾选Preserve log
和Disable cache
,随便选择一个含有Anti-Content
参数的请求,复制目标网址。
打开Source
面板,添加XHR
端点,输入目标网址:
刷新页面,触发断点:
右击左边source tree
根节点top
选择search all files
,或者直接快捷键⌥ ⌘ F
,输入目标关键字anti-content
,可以发现出现了几个目标结果:
接下来比较细节,详细步骤如下,首先随意点选一个目标,将定位到目标文件,但是是webpack
压缩后的,所以要用{}
进行格式美化(这个功能属实是反反爬的最强对手,因为任何代码在格式上无论怎么压缩基本都可以一键还原,因为压缩后的代码必须也要符合语法规范也要能够运行),格式化之后再点击刷新,会多出来一个目标选项,叫app.js.formatted
,点击展开,然后再选择其中任意一项:
接下来,可以发现,其实这个Anti-Content
是一个中间站,会多次调用,有一个声明、生成、使用的流程。理论上来说,swicth case
语句是用来描述多个同类但无联系的选项关系的,但是呢,一方面这是打包后的代码,另一方面开发者不会在某些业务逻辑上难为自己,这将导致自己的代码可维护性很差,所以我们可以直接认为,这里的逻辑就是声明、生成然后使用:
那么我们就只要知道,如何生成就可以了。在e = n.sent()
处加断点,然后取消原来的url
断点(它的使命已经完成了,我们可以更进一步了),此时右边的Breakpoints
会自动多一个断点,这是代码行级别而非文件级别的:
再次刷新,发现崩溃了,这是很正常的,由于长时间断点,导致不符合实际的请求逻辑:
我们再再次刷新即可,但是发现数据直接就加载出来了,没有停在断点处,这也是很正常的,因为这个断点是文件格式化后的断点,正常运行时是压缩的文件,它无法直接识别出行级别的断点:
所以原来的XHR
断点的使命还没有完成,我们还得把它抓回来,给勾上,重新刷新。熟悉的味道扑面而来,数据加载的小圈不断旋转,仿佛被开了无限月读。
这个时候,就要开始跳跃调试了。
我们直接按F8
即可。
好吧,又直接越过了……看样子我们之前的断点打的还是太任性了。再来,重新捕获到文件级别,搜索anti-content
,选择另一个文件,再次格式化,再次捕获到一个生成anti-content
的语句,加上断点:
然后F8
,结果又直接返回了……但别急,如果再次刷新,会发现已经直接定位在了目标位置:
此时,按ESC
打开分离式控制台,主动运行语句试试,会发现就是目标结果,而且还是同步的,这很关键,因为如果是异步的,我们就很难直接确定是不是我们的目标函数了:
接下来更细节,F11
跟进函数内部后,我们要分析一下语句,在反调试过程中,任何分支语句都是很关键的,它们对应着业务逻辑,这里的意思就是变量o
是一个二维数组,其中第一维是一个布尔整数,第二维是个字符串,如果第一维为 0,就返回第二维(目标字符串),否则就报错,报错信息为第二维(根据经验,是一个更短一些的字符串,最后的展示形式为Ajax
返回中的VerifyAuthToken
字段。这里值得小心的是o
数组直接打印时看似很短,实际是浏览器把中间段给压缩了,取了前 50、后 49 加中间的英文省略号(占一位)(输入:⌥ ;
,别问我是怎么知道的,问就是:https://sspai.com/post/45516 ,顺便夸一句:少数派这个平台的文章质量是真地高):
但如果我们再注意看,会发现,这个send
函数的外面正是定义了o
的地方,所以这个w = function(e, t) {...}
函数就是核心风控函数了,它在这个函数内部进行初始化、赋值、输出等操作,所以我们可以直接在这个函数加断点,然后o.send()
那个函的断点可以去掉了,它的使命是真正完成了。接下来,我们再次刷新,成功截在了初始化位置:
这个时候,我们需要知道这个函数,大概是什么规模,点击设置,勾选Bracket matching
和Code folding
,其中,Bracket matching
允许你在括号的左右端按⌃ M
定位到另一端,而Code folding
允许你将函数折叠,从而直接确定函数的边界。
折叠后,光标选中整个函数,发现一共有3972
个字符,在这个选中的函数里,搜索o =
赋值,有83
个 匹配,这好吗?这不好,那么对于3972
个字符的加密函数,我们又该怎么分析,以及对于接近三千行的加密文件,我们该怎么分析,且听下回分解,天大亮了,该干活了(^_^)
最后声明
- 技术是纯粹的,出于对人类向大自然求索而习得的点滴智慧的敬畏,为精进自己的技术以求向真理比别人更进一步,付出自己的时间、精力、智力、金钱等一切投入,都是值得且应当被鼓励的。
- 在此过程之中,无意中可能涉及到某些企业的商业利益,对某些企业的系统安全产生威胁,对此我们深感抱歉,同样也深怀感激,我们承诺一切技术不会用于非正当用途。
- 本文读者应同样承诺,不将本文技术用于任何非正当用途,任何通过习用本文技术,所构成的潜在的不可预估的风险,应与本文无关。