【验证码逆向专栏】xx邮政滑块逆向分析

2024-09-20 1 0

【验证码逆向专栏】xx邮政滑块逆向分析插图

声明

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

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

前言

粉丝答疑又双叒来了!最近仍有不少粉丝咨询逆向相关问题,K哥会尽力回复,不过毕竟精力有限,不可能说是面面俱到(忙忘了@.@)。当然,还是会尽可能多筛选些典型的案例,产出相关文章,以供大伙学习交流。本期分析的验证码,如果没有相关业务,可能很多人不会接触到,也算是较为新颖,话不多说,开始正文:

【验证码逆向专栏】xx邮政滑块逆向分析插图1

逆向目标

目标:xx 邮政滑块逆向分析

网站:aHR0cHM6Ly9wYXNzcG9ydC4xMTE4NS5jbi9jYXMvbG9naW4=

【验证码逆向专栏】xx邮政滑块逆向分析插图2

抓包分析

抓包分析,先看图片接口 gen:

【验证码逆向专栏】xx邮政滑块逆向分析插图3

返回的内容比较多,后面估计会用到,我们留意一下就行,clientUid 后文分析:

【验证码逆向专栏】xx邮政滑块逆向分析插图4

再来看验证接口,即发验证码的接口:

【验证码逆向专栏】xx邮政滑块逆向分析插图5

  • id : 图片接口返回;

  • bgImageHeight: 写死即可;

  • bgImageWidth:写死即可;

  • clientUid:同图片接口参数 clientUid;

  • deviceId:图片接口返回 deviceId;

  • enmiid:需要分析;

  • entSlidingTime:结束验证时间,模拟一下,校验不严;

  • print:需要分析;

  • sliderImageHeight:写死即可;

  • sliderImageWidth:写死即可;

  • startSlidingTime:开始验证时间,模拟一下,校验不严;

  • trackList:轨迹列表。

验证结果:

  • 失败

【验证码逆向专栏】xx邮政滑块逆向分析插图6

  • 成功

【验证码逆向专栏】xx邮政滑块逆向分析插图7

逆向分析

gen 接口

该接口有个 clientUid 参数,目测是 uuid 类型, 我们全局搜索一下,发现只在 main.js 文件中存在,进去后发现被 ob 混淆:

【验证码逆向专栏】xx邮政滑块逆向分析插图8

又是经典的 ob 混淆, 这里直接借用 v佬 的插件快速还原,工具地址如下:

Github 仓库:https://github.com/cilame/v_jstools

在线分析工具:https://astexplorer.net

还原之前还需要稍微的预处理一下,比如下面这样一堆常量的花指令,以及十六进制形式的数字:

比如下面这样一堆常量的花指令,以及十六进制形式的数字

【验证码逆向专栏】xx邮政滑块逆向分析插图9

附上简单的预处理 AST 代码:

// 字符串美化
const simplifyLiteral = {
NumericLiteral({ node }) {
if (node.extra && /^0[obx]/i.test(node.extra.raw)) {
node.extra = undefined;
}
},
StringLiteral({ node }) {
if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {
node.extra = undefined;
}
},
}

traverse(ast, simplifyLiteral);



let my_obj = {};


const merge_odj = {
VariableDeclarator(path){
let {init} = path.node;

if (!types.isObjectExpression(init)) return;

let {properties} = init;

properties.forEach(property => {
// 确保属性值是字面量
if (types.isLiteral(property.value)) {
my_obj[property.key.name] = property.value;
}
});

}
};

traverse(ast, merge_odj);


const clearObjectofValue = {
MemberExpression(path){
let {object, property} = path.node
if (!property || !types.isIdentifier(property) || !types.isIdentifier(object)) return;
let key = property.name
let value = my_obj[key]
if (!value) return;
console.log(`${path.toString()} --> ${value.value}`)
path.replaceWithSourceString(value.value)
}
}

traverse(ast, clearObjectofValue);

ast = parser.parse(generator(ast,{compact:true}).code)


remove_no_citation_obj = {
VariableDeclarator(path){
let {init,id} = path.node
if (!types.isObjectExpression(init) && !types.isIdentifier(id)) return;
let {scope} = path
let binding = scope.getBinding(id.name)
if (binding.referencePaths.length !== 0) return;
path.remove()
}
}

// 删除未被引用的对象
traverse(ast,remove_no_citation_obj)

然后再利用 v_jstools 插件中的解密高级 ob 混淆即可还原了,good:

【验证码逆向专栏】xx邮政滑块逆向分析插图10

利用浏览器的替换功能,替换 main.js 文件,注意将代码压缩替换,我们再来搜索 clientUid 看看:

【验证码逆向专栏】xx邮政滑块逆向分析插图11

返回_0x349b8e,我们再进去到_0x236e93函数中看看:

【验证码逆向专栏】xx邮政滑块逆向分析插图12

发现确实是 uuid 的构造,这边引库或者扣代码都行:

  • clientUid

var _0x5e9294;
var _0x31a6e3 = new Uint8Array(16);

function _0x2f762f() {
if (!_0x5e9294 && !(_0x5e9294 = typeof crypto != "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto) || typeof msCrypto != "undefined" && typeof msCrypto.getRandomValues == "function" && msCrypto.getRandomValues.bind(msCrypto))) {
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
}
return _0x5e9294(_0x31a6e3);
}

for (var _0x22667e = function (_0x37736f) {
return typeof _0x37736f == "string" && /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i.test(_0x37736f);
}, _0x49863c = [], _0x18d3c6 = 0; _0x18d3c6 < 256; ++_0x18d3c6) {
_0x49863c.push((_0x18d3c6 + 256).toString(16).substr(1));
}

function _0x19f649(_0x3d7e8f) {
var _0x238244 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var _0x313ed4 = (_0x49863c[_0x3d7e8f[_0x238244 + 0]] + _0x49863c[_0x3d7e8f[_0x238244 + 1]] + _0x49863c[_0x3d7e8f[_0x238244 + 2]] + _0x49863c[_0x3d7e8f[_0x238244 + 3]] + "-" + _0x49863c[_0x3d7e8f[_0x238244 + 4]] + _0x49863c[_0x3d7e8f[_0x238244 + 5]] + "-" + _0x49863c[_0x3d7e8f[_0x238244 + 6]] + _0x49863c[_0x3d7e8f[_0x238244 + 7]] + "-" + _0x49863c[_0x3d7e8f[_0x238244 + 8]] + _0x49863c[_0x3d7e8f[_0x238244 + 9]] + "-" + _0x49863c[_0x3d7e8f[_0x238244 + 10]] + _0x49863c[_0x3d7e8f[_0x238244 + 11]] + _0x49863c[_0x3d7e8f[_0x238244 + 12]] + _0x49863c[_0x3d7e8f[_0x238244 + 13]] + _0x49863c[_0x3d7e8f[_0x238244 + 14]] + _0x49863c[_0x3d7e8f[_0x238244 + 15]]).toLowerCase();
if (!_0x22667e(_0x313ed4)) {
throw TypeError("Stringified UUID is invalid");
}
return _0x313ed4;
}

function _0x236e93(_0x5bbf46, _0x3b7cda, _0x21cf16) {
var _0x574141 = (_0x5bbf46 = _0x5bbf46 || {}).random || (_0x5bbf46.rng || _0x2f762f)();
_0x574141[6] = _0x574141[6] & 15 | 64;
_0x574141[8] = _0x574141[8] & 63 | 128;
if (_0x3b7cda) {
_0x21cf16 = _0x21cf16 || 0;
for (var _0x3503fc = 0; _0x3503fc < 16; ++_0x3503fc) {
_0x3b7cda[_0x21cf16 + _0x3503fc] = _0x574141[_0x3503fc];
}
return _0x3b7cda;
}
return _0x19f649(_0x574141);
};


console.log(_0x236e93());

验证接口

重点就是 enmiid、print 参数的生成,我们通过搜索 enmiid、print 即可定位到:

【验证码逆向专栏】xx邮政滑块逆向分析插图13

_0x5e712c对象中取的值,我们可以搜索_0x5e712c的赋值操作"_0x5e712c =", 即可定位到目标位置:

【验证码逆向专栏】xx邮政滑块逆向分析插图14

可以看到目标的参数以及都生成好了,我们可以从前往后开始分析:

【验证码逆向专栏】xx邮政滑块逆向分析插图15

_0x5b4cc9就是图片返回的内容,取了 webAName、miid、rc, 然后拼接了一个 weba.js 请求这个链接,通过三次 then 得到_0x383eb4这个对象,进去到_0x383eb4对象的方法或者去看 weba.js 这个文件就可以发现又是 WebAssembly 与 JavaScript 之间进行交互,跟上篇某东 m 端的 wasm 大差不差,敢兴趣的可以去看看,我们接着向下分析,通过_0x383eb4对象下的函数对_0x2d1fb0_0x354e64进行一系列的操作最终得到了目标参数。

_0x2d1fb0就是取的 rc_0x354e64是一个对象,里面的 hitId 对应 miid、deviceId 对应图片接口返回的 deviceId_print通过_0x468795(_0x34c930)返回,_0x34c930对应 miid,然后进入到_0x468795函数:

【验证码逆向专栏】xx邮政滑块逆向分析插图16

通过 WebGL 在画布上绘制图形,并最终将绘制的内容转换为图像数据URL, 然后_0x492d6a()函数生成密文

最后再将密文与_0x5ad970+ "|" + 密文得到我们最终返回的数据,我们断点进入到_0x492d6a()函数:

【验证码逆向专栏】xx邮政滑块逆向分析插图17

可能有大佬一眼就看出来是什么加密了,也可以根据长度判断,我们也可以找一下是谁调用的,在 249 中,

我们直接找 (249) ,就在下面,加密也写出来了,我们验证一下没有问题:

【验证码逆向专栏】xx邮政滑块逆向分析插图18

【验证码逆向专栏】xx邮政滑块逆向分析插图19

最后就是_0x383eb4对象了,我们可以仿照着写,先请求 weba.js 链接,注意 weba.js 链接是动态:

"https://kks.11185.cn/static/front/" + webAName + "/bin/weba.js"

保存发现:

【验证码逆向专栏】xx邮政滑块逆向分析插图20

已经帮我们把对象给导出了,如下操作即可:

weba = require('./Weba.js');

window = global;

function ZxptRestKks(rc, _0x354e64){
var result;
var flag = false;
weba().then(function(Module) {
if (Module) {
_0x2d1fb0 = rc;   // rc
window[_0x354e64.deviceId] = function () {
return _0xa2b2bc = Module.allocateUTF8(_0x2d1fb0);
};
// 访问 PromiseResult 下的属性
_0x156e64 = Module.allocateUTF8(JSON.stringify(_0x354e64));
_0x3e92b3 = Module._rotate(_0x156e64);
_0x57276e = Module.UTF8ToString(_0x3e92b3);
result = JSON.parse(_0x57276e);
flag = true;
}
}).catch(function(error) {
// 处理 Promise 被拒绝的情况
console.error(error);
});
while (!flag) {
require('deasync').sleep(100);
}
;
return result;
};

最后就是一些细节风控的处理,该验证码对距离的识别要求较高,轨迹要求不高,会封 ip 和 ua,速度快了,也会影响成功率,在 cv2 识别后,加上延迟即可,成功率 99%。附上处理代码:

  • cv2 识别

def identify_gap(bg, tp):
"""
bg: 背景图片
tp: 缺口图片
out: 输出图片
"""
# 读取背景图片和缺口图片
bg_img = cv2.imdecode(np.frombuffer(bg, np.uint8), cv2.IMREAD_GRAYSCALE)
tp_img = cv2.imdecode(np.frombuffer(tp, np.uint8), cv2.IMREAD_GRAYSCALE)  # 缺口图片
yy = []
xx = []
for y in range(tp_img.shape[0]):
for x in range(tp_img.shape[1]):
r = tp_img[y, x]
if r < 200:
yy.append(y)
xx.append(x)
tp_img = tp_img[min(yy):max(yy), min(xx):max(xx)]
# 识别图片边缘
bg_edge = cv2.Canny(bg_img, 100, 200)
tp_edge = cv2.Canny(tp_img, 100, 200)
# 转换图片格式
bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)
# 缺口匹配
res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)  # 寻找最优匹配
# # 绘制方框
th, tw = tp_pic.shape[:2]
tl = max_loc  # 左上角点的坐标
br = (tl[0] + tw, tl[1] + th)  # 右下角点的坐标
cv2.rectangle(bg_img, tl, br, (0, 0, 255), 2)  # 绘制矩形
cv2.imwrite('distinguish.jpg', bg_img)  # 保存在本地
# 返回缺口的X坐标
print(f'value: {tl[0]}')
return max_loc[0]


value = identify_gap(requests.get(backgroundImage).content, requests.get(sliderImage).content)
value = int(value * 0.55)
print(value)
time.sleep(1)
  • 轨迹构造

def get_sub_trajectory(trajectories, value):
# 检查value是否在轨迹的x值中
for trajectory in trajectories:
if trajectory['x'] == value:
# 如果找到,截取从轨迹开始到该点的子数组
return [t for t in trajectories if t['x'] <= value]

# 如果value不在x值中,找到最接近value的x值
closest_x = None
min_diff = float('inf')
for trajectory in trajectories:
if abs(trajectory['x'] - value) < min_diff:
min_diff = abs(trajectory['x'] - value)
closest_x = trajectory['x']

# 截取从轨迹开始到最接近的x值的子数组
return [t for t in trajectories if t['x'] <= closest_x]


# 调用函数
# trackList : 浏览器生成的最大距离轨迹
value = 128  # 假设我们要找的value是 128
sub_trajectory = get_sub_trajectory(trackList, value)
sub_trajectory[-1]['type'] = 'up'
print(sub_trajectory)

结果验证

【验证码逆向专栏】xx邮政滑块逆向分析插图21


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

Rust 的风,终究还是吹到了前端
内存马第三弹——Controller内存马
Fastjson反序列化漏洞原理分析
codeql自动化漏洞检测入门
艾体宝干货丨OIDA之二:掌握数据包分析-学会识别
Patchwork更新武器库,首次利用Brute Ratel C4和PGoshell增强版发起攻击

发布评论