顶象滑块逆向分析——背景图还原分析
本章节内容,是对顶象滑块背景图的还原算法分析。
官网demo地址
https://www.dingxiang-inc.com/business/captcha
你可以用官网提供的demo去分析,也可以直接注册一个顶象账号,然后放到本地调用调试。
我是自己注册了一个账号,调用官方SDK做的测试,具体细节可自行查看文档相关SDK,分析代码是通用的,好处是可以防止一些其他东西的干扰,方便调试分析
环境准备
在本地起一个服务,成功之后就是这个样子
开始分析
可以看到,背景图是做了乱序处理的
通过请求堆栈,这里是要去查看第二张请求完的背景图图片的请求堆栈,不要去看第一张的,直接定位到最后一个调用堆栈
格式化之后,在此处下断点,可以看到,这是一个赋值操作,断点上面有几个需要注意的可疑点,这里提一嘴,像这种图片还原操作,目前正向开发99%绝壁是会用到canvas去操作。
开启Fiddler文件替换调试
替换之后可能会出现跨域问题,这里的建议是自己制作一个跨域浏览器,简单方便,无视跨域问题,一劳永逸,这种问题就不要花太久时间折腾。
在basic-Captcha-js.js文件中:
在p.toDataURL()这行代码,这是在获取canvas的base64的内容,我们输出看一下
再将base64内容显示一下,可以看到,这是他还原之后的内容,那继续往回跟堆栈
我们先看一下他的上下文代码,这一块全是逗号表达式调用,而且关键字很多,这里可以手动还原一下简化之后的代码,这样看他的逻辑就清晰很多了
n.exports = function (n, e, r, b, x, m, A) { var j = "ous", _ = "8"; return new l(function (l, m) { var C = "}"; var y = "l"; var w = new Image(); var S = d("_r_") + Math.floor(1e10 * Math.random()); (window[S] = w), w.setAttribute("crossOrigin", "Anonymous"), g("begin to load img"), g(b), (w.onload = function () { var d = w.width; var u = w.height; try { if (A) (n.innerHTML = ""), n.appendChild(w), w.setAttribute("name", "piece-complete"); else { n.innerHTML = (function (n, e) { return ( '<canvas width="' + n + '" height="' + e + '"></canvas>' ); })(d, u); var p = n.getElementsByTagName("canvas")[0]; !(function (n, e, r, i, o) { var a = n.getContext("2d"); a.drawImage(e, 0, 0, r, i); var c = Math["floor"](r / o.length); h(o, function (n, r) { for (var t = [2, 0, 1], o = 0; ; ) { switch (t[o++]) { case 0: var s = c; continue; case 1: a.drawImage(e, d, 0, s, i, r * c, 0, s, i); continue; case 2: var d = n * c; continue; } break; } }); })(p, w, d, u, x), (p.style.width = e + "px"), (p.style.height = r + "px"), g("canvas element"), g(p), g("canvas data"), _dx.inSDK && g(p.toDataURL()); console.log(p.toDataURL()) (window[S] = null); delete window[S]; } l({ w: d, h: u }); } catch (v) {} }), (w.onerror = function (n) { m("img_load_error"); }), p(b) || (b = b + "&_r=" + Math.random()), (w.src = b), A && ((w.style.width = e + v(["70,7", _].join(""))), (w.style.height = r + v("70,78"))); }); };
这有一个地方需要说明一下,官网demo和通过SDK调用的代码,会有一些区别,在上面代码中
!(function (n, e, r, i, o) 这个匿名函数,在官网demo中会是一个 h 函数,或者其他名字,他的内容是下面这样的
- var w = new Image(); // 实例化一个图片对象;
- w.onload,图片加载完毕之后,创建canvas标签;
- 然后执行一个匿名函数!(function (n, e, r, i, o);
- 在匿名函数里面又有画图方法a.drawImage,对canvas进行画图;
- 最后执行其他操作,然后结束
了解了这些之后,我们看一下匿名函数里面的参数,输出一下这几个参数,可以看到,这个参数o是一个数组,可以基本判定他就是图片还原的路径列表
我们知道了这个,还要找出一个东西,路径是怎么生成的??
到此,我们基本上把背景还原算法需要的东西都整理出来了。
背景还原路径算法
下面是图片URL还原路径的算法:
function En (n, e, r) { if (!r) return [] var t var i var o = { o: undefined } return o.o ? _n(o.o) : _n( e ? ((t = 'len'), (i = (i = r.split('/'))[i.length - 1]).split('.')[0]) : Cn(r) )}function r (n, e) { if (n.includes) return n.includes(e) for (var r = 0, t = n.length; r < t; r++) if (n[r] === e) return !0 return !1}function _n (n) { for (var e, t = [], i = 0; i < n.length; i++) { var o = n.charCodeAt(i) if (32 === i) break for (; r(t, o % 32); ) o++ t[ ((e = 'hsup'), e .split('') .reverse() .join('')) ](o % 32) } return t}function Cn (n) { var e if (!n) return '' e = n.split('?')[1][['spl', 'it'].join('')]('&') var r = [0, 0] return ( t(e, function (n) { var e = n.split('=') n && 'c' === e[0] && e[1] && 'null' !== e[1] ? (r = [e[1]]) : ('aid' === e[0] && (r[1] = e[1]), 'sid' === e[0] && (r[0] = e[1])) }), r.join('') )}// 这里的图片链接会失效,请拿最新的链接去测试let urlPATH = 'http://static.dingxiang-inc.com/picture/dx/79bBXvCGvU/zib3/4ac41ecfd57b44c1b62fa07c9c843ed4.webp'let pathList = En({}, true, urlPATH)console.log(pathList)
验证一下,路径列表一致
背景图片还原
拿到路径列表算法之后,我们再去还原图片,部分算法如下,在上面n.exports = function (n, e, r, b, x, m, A) 的基础上做了一些删改,将不需要的代码简化了,另外做了函数封装处理,完整部分在这个链接
https://gitcode.net/weixin_45307278/dxslider_test.git
function getImageBase64 (urlPATH) { function En (n, e, r) { if (!r) return [] var t var i var o = { o: undefined } return o.o ? _n(o.o) : _n( e ? ((t = 'len'), (i = (i = r.split('/'))[i.length - 1]).split('.')[0]) : Cn(r) ) } function r (n, e) { if (n.includes) return n.includes(e) for (var r = 0, t = n.length; r < t; r++) if (n[r] === e) return !0 return !1 } function _n (n) { for (var e, t = [], i = 0; i < n.length; i++) { var o = n.charCodeAt(i) if (32 === i) break for (; r(t, o % 32); ) o++ t[ ((e = 'hsup'), e .split('') .reverse() .join('')) ](o % 32) } return t } function Cn (n) { var e if (!n) return '' e = n.split('?')[1][['spl', 'it'].join('')]('&') var r = [0, 0] return ( t(e, function (n) { var e = n.split('=') n && 'c' === e[0] && e[1] && 'null' !== e[1] ? (r = [e[1]]) : ('aid' === e[0] && (r[1] = e[1]), 'sid' === e[0] && (r[0] = e[1])) }), r.join('') ) } var pathList = En({}, true, urlPATH) console.log(pathList) let w = new Image() w.setAttribute('crossOrigin', 'Anonymous') w.src = urlPATH w.onload = function () { var d = w.width var u = w.height let el = document.getElementsByTagName('body')[0] el.innerHTML = '<canvas id="canvas"></canvas>' let p = document.getElementById('canvas') !(function (n, e, r, i, o) { var a = n.getContext('2d') a.drawImage(e, 0, 0, r, i) var c = Math.floor(r / o.length) win_h(o, function (n, r) { for (var t = [2, 0, 1], o = 0; ; ) { switch (t[o++]) { case 0: var s = c continue case 1: a.drawImage(e, d, 0, s, i, r * c, 0, s, i) continue case 2: var d = n * c continue } break } }) })(p, w, d, u, pathList) console.log(p.toDataURL()) }}// 这里的图片链接会失效,请拿最新的链接去测试var urlPATH = 'http://static.dingxiang-inc.com/picture/dx/79bBXvCGvU/zib3/4a64f9302585450da279c527c161a35b.webp'getImageBase64(urlPATH)