把wordpress变成图床/佛山市seo推广联系方式

把wordpress变成图床,佛山市seo推广联系方式,今日头条开放api接口,济南卓远网站建设公司声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 本文章未…

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!

目标

目标:Luosimao 螺丝帽人机验证逆向分析

网址:aHR0cHM6Ly9jYXB0Y2hhLmx1b3NpbWFvLmNvbS9kZW1vLw==

01

抓包分析

进入官网提供的 demo 页面,F12 开启抓包,首先加载 demo 页面,这个页面包含一个 site-key,每个网站都不一样,会在后续用到:

02

接下来是一个 captcha.js,主要用于后续的加密参数生成,乍一看以为是个 OB 混淆,其实只是更换了变量名,然后一些值是从大数组里面取的,没有 OB 混淆里的打乱数组的操作,比 OB 混淆要简单很多,后文会利用 AST 对这三个 JS 进行解混淆,后续类似的还加载了 widget.jsframe.js,也都是和加密参数的生成有关。

03

04

然后是一个 widget 的请求,该请求返回的源码里面有个 data-token,也是后续要用到的。

05

接下来是一个 request 的请求,接口返回的一些参数也是后续要用到的,同时返回的 w 值,就是要点击的文字提示信息。

06

然后是一个 frame 请求,请求带了两个加密参数,这个请求返回的源码里面包含了验证码图片信息。

07

然后就加载了验证码图片,注意这里的图片是被切割之后乱序排列了的,和极验三代的类似,所以后文我们还要对其进行顺序还原。

08

点击图像完成之后,就会发起校验请求 user_verify,校验成功的话返回的 ressuccess,相反校验不成功就是 failed

09

点击立即登录,触发最后一个 submit 请求,提交的 data 值就是上一步 user_verify 验证成功后返回的 resp 值。

10

小结一下螺丝帽就可以分为三个比较重要的步骤:request 接口请求得到要点击的内容,frame 接口请求拿到验证码图片,user_verify 接口验证点击是否正确,下文将详细分析这些步骤。

AST 解混淆

先别着急找加密逻辑,前面抓包的时候说了,一共有三个 JS 参与了加密,分别是 captcha.jswidget.jsframe.js,这三个 JS 是被混淆了的,为了后续比较好分析,我们可以先使用 babel 将其转换成 AST 语法树后,进行解混淆操作。

widget.js 为例,观察该 JS,我们可以总结出以下三个问题:

  • 开头一个大数组,如 _0x8f24,后续变量赋值操作就是从这个大数组里取值,如 _0x8f24[1]_0x8f24[2]
  • 所有的字符串都被转换成了十六进制编码的形式,不易阅读;
  • 访问对象属性是 _0x3ba3x1["Number"],而不是 _0x3ba3x1.Number,不易阅读。

所以我们只需要做三个操作:

  • 从数组取值转为直接赋值(_0x8f24[1] => "\x63\x61\x6C\x6C");
  • 十六进制编码的字符串还原("\x63\x61\x6C\x6C" => "call");
  • 对象属性还原(_0x3ba3x1["Number"] => _0x3ba3x1.Number)。

11

首先是从数组取值转为直接赋值,先将这个 JS 扔到 astexplorer.net 分别看看原始结构(如:_0x8f24[1])和替换后的结构(如:"\x63\x61\x6C\x6C"):

12

13

从上图可以看到类似 _0x8f24[1] 取值的节点类型为 MemberExpression,这个大数组没有像 OB 混淆那样做了乱序操作,可以直接取值,那么如果我们先拿到 _0x8f24 这个大数组,然后遍历 MemberExpression 节点,再将其替换成 StringLiteral 类型的节点就行了。当然遍历的时候也要有限制,必须是 path.node.object.name 的值和大数组的名称一样才能替换。然后就是我们怎么拿到 _0x8f24 这个大数组呢?这个大数组在 AST 中的位置是 program.body[0],我们可以将其转换成 JS 代码然后 eval 执行一下,把大数组加载到内存里,后续就能直接按索引取值了,当然方法不止这一种,可以按照自己的思路来实现,这一部分的 visitor 可以这么写:

const ast = parse(code);
eval(generate(ast.program.body[0]).code)const visitor = {MemberExpression(path) {if (path.node.object.name === "_0x8f24") {path.replaceWith(types.stringLiteral(eval(path.toString())));}}
}

然后就是十六进制编码的字符串还原,观察前后的 AST 语法树:

14

15

可以发现只要将 path.node.extra.raw 的值换为 path.node.extra.rawValue 或者 path.node.value即可,当然因为 NumericLiteralStringLiteral 类型的extra 节点并非必需,这样在将其删除时,也不会影响原节点,所以还可以直接 delete path.node.extra 或者 delete path.node.extra.raw 来还原字符串,这一部分的 visitor 可以这么写:

const visitor2 = {StringLiteral(path) {if (path.node.extra) {// 以下方法均可// path.node.extra.raw = '"' + path.node.extra.rawValue + '"'// path.node.extra.raw = '"' + path.node.value + '"'// delete path.node.extradelete path.node.extra.raw}}
}

最后就是对象属性还原,同样的先观察前后的 AST 语法树:

16

17

可以看到 _0x3ba3x1["Number"] => _0x3ba3x1.Number,是 MemberExpression 下的 property 节点由 StringLiteral 类型的变成了 Identifier 类型的,computed 值由 true 变成了 false,这一部分的 visitor 可以这么写:

const visitor = {MemberExpression(path){if (path.node.property.type === "StringLiteral" && path.node.property.value !== "") {path.node.computed = falsepath.node.property = types.identifier(path.node.property.value)}}
}

前面抓包的时候也说了,一共有三个 JS 参与了加密,分别是 captcha.jswidget.jsframe.js,他们的混淆都是一样的,所以综上所述我们的 AST 解混淆代码完整版可以是这样的:

const fs = require('fs');
const types = require("@babel/types");
const parse = require("@babel/parser").parse;
const traverse = require("@babel/traverse").default;
const generate = require("@babel/generator").default;function deconfusion(code, arrName) {const ast = parse(code);eval(generate(ast.program.body[0]).code)const visitor1 = {MemberExpression(path) {if (path.node.object.name === arrName) {path.replaceWith(types.stringLiteral(eval(path.toString())));}}}const visitor2 = {StringLiteral(path) {if (path.node.extra) {// 以下方法均可// path.node.extra.raw = '"' + path.node.extra.rawValue + '"'// path.node.extra.raw = '"' + path.node.value + '"'// delete path.node.extradelete path.node.extra.raw}},MemberExpression(path){if (path.node.property.type === "StringLiteral" && path.node.property.value !== "") {path.node.computed = falsepath.node.property = types.identifier(path.node.property.value)}}}traverse(ast, visitor1);traverse(ast, visitor2);delete ast.program.body[0]return generate(ast, {jsescOption: {"minimal": true}}).code
}const widget = fs.readFileSync('widget.js', 'utf-8');
const newWidget = deconfusion(widget, "_0x8f24")
fs.writeFileSync('newWidget.js', newWidget, 'utf-8');const captcha = fs.readFileSync('captcha.js', 'utf-8');
const newCaptcha = deconfusion(captcha, "_0x2d28")
fs.writeFileSync('newCaptcha.js', newCaptcha, 'utf-8');const  frame = fs.readFileSync('frame.js', 'utf-8');
const newFrame = deconfusion(frame, "_0x3f7b")
fs.writeFileSync('newFrame.js', newFrame, 'utf-8');

解混淆之后,将代码替换掉原始代码,然后就可以愉快的进行分析了。

获取验证码信息

首先来看 request 接口,POST 请求,params 有 k 和 l 两个参数,data 有 bg 和 b 两个加密参数,如下图所示:

18

k 参数通过直接搜索可以发现就存在于页面的 html 里,如下图所示的 data-site-key 就是 k 的值,从这个名字也可以看出应该是每个网站分配的一个 key。

19

bg 和 b 参数搜索不到,且每次都是变化的,通过观察可知这是一个 XHR 请求,那么就可以通过 XHR 断点,或者直接跟栈的方式来找加密入口,好在栈也不多,直接跟进去下断点,在 ajax send 方法这里,就可以看到 bg 和 b 已经生成。

20

21

继续往上跟栈,就很容易发现 bg 和 b 的生成位置,如下图所示:

22

"bg=" + _0x3ba3xc.encryption(_0x3ba3x1) + "&b=" + _0x3ba3xc.encryption(_0x3ba3x3),先来看 _0x3ba3x1_0x3ba3x3 是怎么生成的:

23

var _0x3ba3x1 = _0x3ba3xc.env.us + "||" + _0x3ba3xc.getToken() + "||" + _0x3ba3xc.env.sc.w + ":" + _0x3ba3xc.env.sc.h + "||" + _0x3ba3xc.env.pf.toLowerCase() + "||" + _0x3ba3xc.prefix.toLowerCase(),
_0x3ba3x3 = _0x3ba3xc.path[0] + ":" + _0x3ba3xc.timePoint[0] + "||" + _0x3ba3xc.path[1] + ":" + _0x3ba3xc.timePoint[1];
  • _0x3ba3xc.env.us:User-Agent;
  • _0x3ba3xc.env.sc.w:屏幕宽度;
  • _0x3ba3xc.env.sc.h:屏幕高度;
  • _0x3ba3xc.env.pf.toLowerCase():platform(如 win32) 小写;
  • _0x3ba3xc.prefix.toLowerCase():浏览器引擎(如 webkit)小写。

_0x3ba3xc.getToken() 是一个函数,跟进去可以看到是取 widget 请求返回的 html 里面的 data-token 值,如下图所示:

24

25

widget 请求还有个 i 参数,也是加密生成的,直接全局搜索 i:,可以发现在 captcha.js_0x7125x5.id 就是 i 的值,如下图所示:

26

跟进去,generateID() 方法 return "_" + Math.random().toString(36).substr(2, 9); 就可以生成这个值了。

27

然后是 _0x3ba3x3,主要由 path 和 timePoint 组成,反复对比你会发现,path = [鼠标第一次进入点击区域的坐标,鼠标点击时的坐标]timePoint = [页面加载完毕的时间,开始点击的时间],如下图所示,可以在左上角和右下角都点一下看看这个点击的区域坐标范围是啥,然后随机构建一下就行了。

28

总结下来,_0x3ba3x1_0x3ba3x3 就可以通过以下代码实现:

function randomNum(min, max) {return Math.floor(Math.random() * (max - min + 1) + min);
}const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
const screen = {width: 1920, height: 1080};
const platform = "Win32";
const prefix = "Webkit";
//[鼠标第一次进入点击区域的坐标,鼠标点击时的坐标]
const path = [`${randomNum(60, 200)},${randomNum(0, 3)}`,`${randomNum(60, 200)},${randomNum(10, 20)}`
];
// [页面加载完毕的时间,开始点击的时间]
const time = +new Date();
const timePoint = [time, time + randomNum(1000, 6000)];const _0x3ba3x1 = ua + "||" + token + "||" + screen.width + ":" + screen.height + "||" + platform.toLowerCase() + "||" + prefix.toLowerCase();
const _0x3ba3x3 = path[0] + ":" + timePoint[0] + "||" + path[1] + ":" + timePoint[1];

最后一步加密 "bg=" + _0x3ba3xc.encryption(_0x3ba3x1) + "&b=" + _0x3ba3xc.encryption(_0x3ba3x3);,跟进 encryption 方法熟悉的 iv、mode、padding,但他这里写的却是 SHA3,很明显是骗人的,对比测试一下加密结果,发现是 AES 加密,直接引库就完事儿了。

29

至此 request 接口就分析完毕了。

获取验证码图片

然后是获取验证码图片,直接搜索图片的名称,可以发现是在 frame 请求返回的 html 源码里面,如下图所示:

30

这个 captchaImage 对象包含两个值,p 是验证码乱序的图片,有三个图片,这个应该是防止宕机,有多个节点,实际三张图都是一样的内容,而 l 则是用来还原乱序图片的。

var captchaImage = {p:['https://i5-captcha.luosimao.com/22/aa27352e782eb74ccccef04eb91bc23c.png','https://i2-captcha.luosimao.com/22/aa27352e782eb74ccccef04eb91bc23c.png','https://i1-captcha.luosimao.com/22/aa27352e782eb74ccccef04eb91bc23c.png'],l: [["40","80"],["220","0"],["280","0"],["200","80"],["100","0"],["40","0"],["0","80"],["180","0"],["20","0"],["120","80"],["220","80"],["240","0"],["180","80"],["0","0"],["280","80"],["140","80"],["140","0"],["200","0"],["160","0"],["260","0"],["20","80"],["240","80"],["100","80"],["60","80"],["120","0"],["260","80"],["160","80"],["80","0"],["80","80"],["60","0"]]
};

我们查看图片的源码,可以发现这个 l 的坐标就是 css background-position 属性的值,如下图所示:

31

逻辑也很简单,图片尺寸 300x160 px,切割的乱序图片,分为上下两部分,每一部分又被分为 15 个小片段,那么上半部分从左至右,每一片段的左上角坐标为:[0, 0][20, 0][40, 0] …,以此类推,下半部分则是 [0, 80][20, 80][40, 80] …,以此类推,而前面的 l 的值,就表示原始图片第 N 个位置,对应乱序图片的某个片段的左上角的坐标,例如 l 的第一个值为 ["40","80"],则表示原始图片第一个位置是乱序图中坐标为 [40, 80] 的片段,换句话说,也就是原始图片第一个位置,应该是乱序图中下半部分从左至右的第三个片段。图片的还原在 Python 中可以用以下代码实现:

from PIL import Imagesection = [["40","80"],["220","0"],["280","0"],["200","80"], ......]
image = Image.open("乱序图片.png")
canvas = Image.new("RGBA", (300, 160))for index in range(len(section)):x = int(section[index][0])y = int(section[index][1])slice_ = image.crop(box=(x, y, x + 20, y + 80))canvas.paste(slice_, box=(index % 15 * 20, 80 if index > 14 else 0))canvas.save("正确图片.png")

然后就是这个 frame 请求,包含了一个 s 参数,这个是前面 request 请求返回的,如下图所示:

32

33

发送验证

然后就是点击发送验证请求了,user_verify 包含三个参数 h、v 和 s,h 是前面 request 接口返回的,v 和 s 是需要我们逆向的,如下图所示:

34

同样也直接跟栈,如下图所示 _0xaaefx15.toString() 就是最终的 s 值,而 s 是最终的 v 值:

35

先来看 s,s = _0xaaefx11.toString();,而 _0xaaefx11 和前面一样也是 AES 加密,其中 key 是前面 request 接口返回的 i 的值,待加密的值是 _0xaaefx5,而 _0xaaefx5 = _0xaaefx3.dots.join("#")_0xaaefx3.dots 就是点击的坐标,不过这个坐标要注意,他的 x 和 y 坐标是反着排列的,整个数组也是倒序的,直观点儿来讲就是 _0xaaefx3.dots = ["第三次点击的 y,第三次点击的 x", "第二次点击的 y,第二次点击的 x", "第一次点击的 y,第一次点击的 x"],如下图所示:

36

然后就是 _0xaaefx15,经过 MD5 加密得到最终的值,如下图所示:

37

注意事项

请求会校验 header 的 Host 字段,frame 接口和其他接口的 Host 是不一样的,注意观察替换,Host 不正确会导致请求失败。

至此所有流程就都分析完毕了。

结果验证

38

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/84868.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

工作应当有挑战

有挑战 才能有所成长 正所谓人到山前必有路 是挑战 一般就会有未知 未知往往伴随着困难 有困难 并不可怕,也不必自我抱怨,自我抱怨只会陷入无尽的精神内耗 我们只要做好自己 困难就会迎刃而解 如果自己的获得 没有达到自己的期望 其实那也不必气馁 再…

【ICCV 2023】FocalFormer3D : Focusing on Hard Instance for 3D Object Detection

原文链接:https://arxiv.org/abs/2308.04556 1. 引言 目前的3D目标检测方法没有显式地去考虑漏检问题。   本文提出了困难实例探测(HIP)。受目标检测的级联解码头启发,HIP逐步探测误检样本,极大提高召回率。在每个阶…

UDP 的报文结构

1.UDP特点2.UDP协议报文结构 1.UDP特点 UDP特点分为: 无连接:知道对端的IP和端口号就可以进行传输,即通信时不需要创建连接(发送数据结束时也没有连接可以释放)所以减小了开销和发送数据前的时延;比如&am…

nginx部署多个项目

前言 实现在一台服务器上使用nginx部署多个项目的方法 查看并修改nginx安装的默认配置文件 在 Linux 操作系统中,Nginx 在编译安装时默认的配置文件路径是 /usr/local/nginx/conf/nginx.conf。 如果是通过发行版的包管理器安装,则默认的配置文件路径可能…

基于数据驱动的成本洞察,趣丸科技的FinOps进阶之路~

今年以来,我们注意到越来越多的单位开始积极实践FinOps,而随着FinOps的发展,大家对于其落地过程的关注也更加具体和深入,涉及了账单波动、FinOps的边际效应、成本模型、依赖工具等多个关键问题。 本月「UGeek大咖说」线上直播活动…

【c语言】贪吃蛇

当我们不想学习新知识的时候,并且特别无聊,就会突然先看看别人怎么写游戏的,今天给大家分享的是贪吃蛇,所需要的知识有结构体,枚举,以及easy-x图形库的一些基本函数就完全够用了,本来我想插入游…

创建一个简单的外卖订餐系统

在今天的快节奏生活中,外卖订餐系统已经成为了人们日常生活中不可或缺的一部分。这些系统通过在线点餐和配送服务,为用户提供了便捷的用餐体验。在本文中,我们将创建一个简单的外卖订餐系统,使用Python和Flask框架构建后端&#x…

Mac电脑系统怎么样才能干干净净地卸载应用程序?

Mac系统怎么样才能干干净净地卸载应用程序,不留下隐私数据和用户信息呢?如果有方法的话,那么该方法对于Mac电脑小白是否友好呢? CleanMyMac就是一款用于清理Mac系统下应用程序的一款清理工具,其内置了应用程序的安全卸…

轻松搞定Spring集成缓存,让你的应用程序飞起来!

Spring集成缓存 缓存接口开启注解缓存注解使用CacheableCachePutCacheEvictCachingCacheConfig 缓存存储使用 ConcurrentHashMap 作为缓存使用 Ehcache 作为缓存使用 Caffeine 作为缓存 主页传送门:📀 传送 Spring 提供了对缓存的支持,允许你…

Spring MVC常见面试题

Spring MVC简介 Spring MVC框架是以请求为驱动,围绕Servlet设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。简单来说,Spring MVC整合了前端请求的处理及响应。 Servlet 是运行在 Web 服务器或应用…

内存管理之虚拟内存

本篇遵循内存管理->地址空间->虚拟内存的顺序描述了内存管理、地址空间与虚拟内存见的递进关系,较为详细的介绍了作为在校大学生对于虚拟内存的理解。 内存管理 引入 RAM(内存)是计算机中非常重要的资源,由于造价的昂贵&…

Linux 用户和用户组

Linux中关于权限的管控级别有2个级别,分别是: 针对用户的权限控制 针对用户组的权限控制 比如,针对某文件,可以控制用户的权限,也可以控制用户组的权限。 1、用户组管理 1.1、创建用户组 groupadd 用户组名 1.2、删除用户组 groupdel 用户…

社交媒体商业禁令冲击:TikTok如何应对印尼政策变化?

随着社交媒体的普及和发展,各国政府开始越来越关注这一领域的监管和控制。印尼政府最近的决定,禁止在社交媒体上进行商品交易,引起了广泛的关注。 这一政策变化对TikTok等社交媒体平台带来了巨大的挑战,要求它们重新审视商业模式…

CFCA证书 申请 流程(一)

跳过科普,可直接进入申请👉https://blog.csdn.net/Ximerr/article/details/133169391 CFCA证书 CFCA证书是指由中国金融认证中心颁发的证书,包括普通数字证书、服务器数字证书和预植证书等,目前,各大银行和金融机构都…

网站有反爬机制就爬不了数据?那是你不会【反】反爬

目录 前言 一、什么是代理IP 二、使用代理IP反反爬 1.获取代理IP 2.设置代理IP 3.验证代理IP 4.设置代理池 5.定时更新代理IP 三、反反爬案例 1.分析目标网站 2.爬取目标网站 四、总结 前言 爬虫技术的不断发展,使得许多网站都采取了反爬机制&#xff…

【深度学习】实验12 使用PyTorch训练模型

文章目录 使用PyTorch训练模型1. 线性回归类2. 创建数据集3. 训练模型4. 测试模型 附:系列文章 使用PyTorch训练模型 PyTorch是一个基于Python的科学计算库,它是一个开源的机器学习框架,由Facebook公司于2016年开源。它提供了构建动态计算图…

Linux 线程(thread)

进程线程区别 创建线程 #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); -功能&#xff1a;创建一个子线程&#xff0c;一般情况下main函数所在的线程称为主线程&#xff0c;…

21天学会C++:Day14----模板

CSDN的uu们&#xff0c;大家好。这里是C入门的第十四讲。 座右铭&#xff1a;前路坎坷&#xff0c;披荆斩棘&#xff0c;扶摇直上。 博客主页&#xff1a; 姬如祎 收录专栏&#xff1a;C专题 目录 1. 知识引入 2. 模板的使用 2.1 函数模板 2.2 类模板 3. 模板声明和定义…

kubernetes(k8s)PVC

概念 PVC 的全称是&#xff1a;PersistentVolumeClaim&#xff08;持久化卷声明&#xff09;&#xff0c;PVC 是用户存储的一种声明&#xff0c;PVC 和 Pod 比较类似&#xff0c;Pod 消耗的是节点&#xff0c;PVC 消耗的是 PV 资源&#xff0c;Pod 可以请求 CPU 和内存&#x…

基于Kubernetes的Serverless PaaS稳定性建设万字总结

作者&#xff1a;许成铭&#xff08;竞霄&#xff09; 数字经济的今天&#xff0c;云计算俨然已经作为基础设施融入到人们的日常生活中&#xff0c;稳定性作为云产品的基本要求&#xff0c;研发人员的技术底线&#xff0c;其不仅仅是文档里承诺的几个九的 SLA 数字&#xff0c…