聲明
原創文章,請勿轉載!
1、本文內容僅限於安全研究,不公開具體源碼。維護網絡安全,人人有責。
2、如果想更系統地了解第三代驗證碼、更全面體會安全產品的設計,可以結合我的另幾篇文章
(1)《第三代驗證碼研究》https://www.cnblogs.com/boycelee/p/11363611.html(推薦)
(2)《頂象驗證碼破解與研究》https://www.cnblogs.com/boycelee/p/14269941.html(推薦)
(3)《極驗驗證碼破解與研究》https://www.cnblogs.com/boycelee/p/14021048.html(推薦)
(4)《極驗無感驗證破解》https://www.cnblogs.com/boycelee/p/13951819.html
(5)《同盾小程序指紋破解》https://www.cnblogs.com/boycelee/p/13899956.html
3、本文主要通過破解協議的方式繞過頂象安全驗證,思路與網上自動化的方式有很大的不同
一、個人心路歷程二、完整流程三、實例研究1、研究目標2、案例歷史研究3、官網案例分析四、請求分析1、c1請求:請求頂象服務器,獲取c參數。(1)請求參數示例 :(2)請求參數信息:(3)加密算法(4)返回參數(5)指紋本地存儲2、a請求:獲取圖片與token2(1)請求參數示例:(2)返回參數:3、v1請求:獲取token3(1)請求示例:(2)返回示例:4、check五、部分服務化源碼greenseer.js部分服務化代碼Java服務化代碼六、服務化關鍵1、圖片還原2、隨機加密算法解析(重點設計部分)(1)語法樹思路(2)舉例子七、成果八、頂象無感驗證流程(1)頂象無感驗證流程圖(2)頂象風控流程圖九、總結十、最后
一、個人心路歷程
(1)頂象的驗證碼真的非常棒。從2018年12月開始到2019年5月持續與頂象驗證碼對抗半年。
(2)js混淆與加密算法一天更換兩次、滑塊驗證碼干擾槽等應該都是在於我對抗中升級的。
(3)一年后回頭看頂象的驗證碼產品又多了語序點選、刮刮卡驗證、空間語義驗證、亂序拼圖驗證、旋轉驗證、面積驗證等驗證碼類型,在創新方面做得非常好,應該是國內數一數二的。
(4)與頂象的對抗,使我對安全產品有了更深刻、全面的認識。
(5)以下是我與頂象的對抗過程。

二、完整流程
訪問頂象官網,注冊賬號后登錄控制台,訪問“無感驗證”模塊,申請開通后系統會分配一個唯一的AppId、AppSecret。
當用戶滑動驗證碼通過后,驗證碼服務會生成一個
token
,用戶的業務請求帶上這個驗證碼token
,業務系統再調用后台 SDK 驗證token
的有效性。頂象請求鏈路如下:

三、實例研究
1、研究目標
當用戶滑動驗證碼通過后,驗證碼服務就會生成token,用戶攜帶token上傳至頂象服務進行驗證。我們的目標就是生成沒有被標記為異常的token。

2、案例歷史研究
2018年12月研究的是國航的手機注冊場景。不過由於國航該場景的驗證碼已經不再是滑塊,所以后續可能需要使用官網案例來具體分析。

3、官網案例分析

從請求中我們可以看出,一共是三個請求(c1、a、v1)。我們需要弄清楚這三個請求分別的含義。(從設計角度個人覺得頂象相對於極驗要簡潔一些。個人覺得頂象的設計非常棒,在做公司的安全產品時,我也會參考一些頂象的設計)
四、請求分析
1、c1請求:請求頂象服務器,獲取c參數。
目的:此處應該是計算設備指紋,通過設備指紋能夠標識唯一設備信息
(1)請求參數示例 :
1428#X8m8K4SIcMDkTOm/r2P3j5vTyaRhmW3m9aksXQ8m2r13DgOarX1TmbmTMX13/vXXC9RmXS8m2XHfDg/uV8RP/4uaMuk8/WnUNX+6Xnah2rDfmZ3YXXwR0gpt2lJu07Nozs5jXY433RaCfCO6Y8VX/6aVU2mT6cuhX14q+2v8jcQ/awvNF8X1YuuJmzf3XXbXmCvq8M8uiwXSm3vhmyyuXXOiXmo8Ya21XXGjS8ft1qT2+Al3+Ab0wXWXmyf8aCO4WrrXj6flFh9p4PnSFPokuP5vXmuFn9fM8Am+JXvlT2TiW8QL88SXjtZykz8LUtZ9kAQpkd4pn2oXmpP9upuLVr2XvVTUjyTPTXWPCy+oTofzwi2oRX41sYk1sXWARiw1w32AOrfAe1fPTXVXivd2CFHaGgNezipHxx6cciF1xORN2iUklOpQzldUBvAxwBHXSoHBAGyUXXJ99ei8lxEoj2X8sJTvz/novxbofltai6k3wmXXjtyTItyoi8QsjUMIGI0siT3XYWKG9FkI54DQdTnXm00umH0KBTIXmACOT1rFmC8XmA2rTPuVmm/Xmg1R9usmM5/6XX+vqnlSylnDXX+jGnElyi3fXXphvMw444YeNUpw0TyXuFi3HFitgAiquUM3HXtquUM3HXwt1rXSuNMs1MJfuNrniBCkEnrfYmCsYuL5Y5Cf+nCkUFCkYdCSUFrSJXonuMoXZkrpk8nf3Ue/P9F0pmjG6Q9/DgBJzVZqcg6po/NleM6PXXVE8F9oSzdt++YgMiFBYxWbOUaHPXXS+2C5jY5/ZcmEaAvR+cxxjRQ+k8CoD1xHFcxSj6ovU270Dwyik9oBPTVXYNS86LTmha3t68oQPrXs3/ZLJMPTI2QLAtomXX07drAmu4omycphXrnXYbUG9GpOSg6yts0JX2XfTXVqvE26Y/COD9OqWA5IPaQTDVxc41r6mXXn8Xo6JyIPJPyuXYQim8OYJX3tu6ci/TX3muOg/Lm8//nuY9nljN/sX3vTXX4qX1nIa//1Yc9x
(2)請求參數信息:
只展示部分數據
1"supportAddBehavior": "ab","adblock": "adb","availResolution": "ar","canvasFP": "can","cpuClass": "cc","colorDepth": "cd","cookieEnabled": "ce","canPlayType": "cpt","collectTime": "ct","doNotTrack": "dnt","deviceMemory": "dm","languages": "lugs","mimeTypes": "mts","mediaDevices": "mds","platform": "np","supportOpenDatabase": "od","devicePixelRatio": "pr","resolution": "res","plugins": "rp","supportSessionStorage": "ss","timezoneOffset": "to","touch": "ts","userAgent": "ua","webgl": "web","webgl2": "gi"
通過加密以上操作系統與瀏覽器以及網絡數據等信息生成c1請求參數param。
(3)加密算法

index.js?_t=xxxx中,進行每日更新。那么該如何確保每日都能獲取到准確的加密算法呢?通過解析原語法樹,生成我們想要的語法樹。(這個后續講)
(4)返回參數
1{"data":"f8839e00435f2e05f9ed60b3d3c5498554cb367655ec6e7318adefda150437040a74963c","msg":"lid invalid","status":-4} // 會根據指紋情況對指紋進行風險等級判斷
(5)指紋本地存儲
頂象會將指紋進行多地存儲,例如cookie、Session Storage、Local Storage等
2、a請求:獲取圖片與token2

(1)請求參數示例:
1de=0&wp=1&aid=dx-1547996895410-3601284-1&jsv=1.3.11.98&c=5c3c65a6uSNGXiEhdEwxwwICxBq5Qdjdky4kxjo1&ak=5f6727ec854786a86cd4c3c171d13499&s=50&h=150&w=300&_r=0.9866721061865382
- wp:表示圖片類型。非常重要,因為通過該參數選擇圖片類型,省去了我們取轉換webp格式圖片(0表示jpg,1表示webp。目前只有chrome支持,safari不支持webp格式)。
- aid:時間戳+隨機數+1。目前猜測其目的是number once。
- jsv:表示版本號。
- c:c1請求返回參數,可以理解為第一階段token1。
- ak:appKey,頂象會為每一個接入其無感驗證碼的server提供一個appKey,以便標識他們是哪一個服務。
- s、h、w:描述圖片的長寬等信息。h與w非常重要,因為獲取的圖片信息是200*400,所以我們在計算距離時,應該按比率縮小,否則滑塊無法通過。
- _r:number once。
(2)返回參數:
1{"sid":"86ae78ed0fba04f8384f3c9376271b0d","y":30,"success":true,"p1":"/dx/ib3oV3MeuO/zib3/b5cce61f91c6447bbc2f10e7836a7827.webp","p2":"/dx/ib3oV3MeuO/zib3/e34db2ab70064b2c998d14159f0b8ff8.webp","p3":"/dx/ib3oV3MeuO/zib3/50969dc0b2f14d118fcf166dc0871021.webp","msg":null,"t":null,"result":1,"type":0,"logo":null}
- sid:token2。
- y:y坐標
- p1:不完整圖片
- p2:拼圖塊
- p3:完整圖片
注意:如果該請求錯誤,可能會返回另一種圖片驗證碼(點擊類型)
3、v1請求:獲取token3
(1)請求示例:
1y: 30 x: 33 aid: dx-1547997910506-78792589-1 sid: 75dc20f81b2bd0ff871b9f12e54ef2c8 jsv: 1.3.11.98 c: 5c448ee09pUgUAwgiaRMmhhDea79K4O1B7oQhRh1 ak: 5f6727ec854786a86cd4c3c171d13499 ac: 492#X8Xn8AQv/Y6pdvgYXXfOuMffR/W3XjVgMOYfQntkaQbRZ/smuRlFpvD6L+qHM21JPdOjXTgzLGKauxS0c29jXCAeYaTRl589z9Ug+vZ1YDXIBNwrm8X1k6vEf3rKmrXmS2WXmFPMgH3nXX8XXXXmY8XEJ37H/cWc68WnDdW+DXSjXtXZj9c6v33n6aa6W9bhYu/c8XIXui83m5U1gFUJzxqJzxoGUqNSYXpFY2Xio3O5Ja/dw/NP2X5X3oFjcWf2r9znqaJQ2LQr7homIr5X3oFPjnXMr9znqaJQ2LQr7homIr2XsBHwJhSSRygCnh3ufTc1v/8V8YZsiz/D4raoj9XLRtOYv9a53D2kZcWV8YcHUAa/mrXmS25X3oFPjvS2r9znqaJQ2LQr7homIr5XjoFPjvrMr9DIGaUiwYmYYrXslqs+7bRz2kEuonW=
y:坐標,a請求會返回,所以不需要自己計算。
x:坐標,需要計算。
aid:時間戳+隨機數+1。目前猜測其目的是number once。
jsv:表示版本號。
c:c1請求的返回值,理解為token1
ak:appKey,頂象會為每一個接入其無感驗證碼的server提供一個appKey,以便標識他們是哪一個服務。
ac:加密信息。包括時間戳、操作系統對應編碼、Referer、特殊加密算法(隨機取加密算法中一段並加密)、JSV、token(sid)、是否使用headless、瀏覽器版本、屏幕信息、服務網址、版本、檢測是否使用webdriver等方式抓取、token、鼠標軌跡等進行數據加密。
(2)返回示例:
1{"success":true,"token":"37AD877DC3953CA1116E487DFBF6DB435DFE7C4CD204B8DC861D5857BE2FDDCF4565648C95BAFD15030AA555713B5DA1FD5D57C82427F4C7D222E5990F47AB83B3F13ED08965032CFF26FA37B82012FF","msg":null,"tp":null,"sv":null,"retry":0}
返回token則表示成功。但注意此時token未必有效,如果c1請求使用appKey與v1請求使用的不同,是無法通過check校驗的。
4、check
在獲取token之后會通過check接口,進行token時效性與正確性校驗。使用方通過調用頂象的open api進行token校驗。通過則說明,驗證碼完成。
五、部分服務化源碼
greenseer.js部分服務化代碼
1function getParamUa(sid, position, referer, browserVersion) {
2var _ua = "";
3var ua = "";
4var tm = new Date().getTime();
5delete require.cache[require.resolve('../builder/dingxiang_greenseer_finish.js')];
6var encryptionFunc = require('../builder/dingxiang_greenseer_finish.js');
7var replace_encrypt = encryptionFunc.replace_encrypt;
8
9delete require.cache[require.resolve('../builder/dingxiang_version_finish.js')];
10var versionFunc = require('../builder/dingxiang_version_finish.js');
11var replace_version = versionFunc.replace_encrypt;
12let mouseMove = buildMouseMove(position);
13let mouseMoveEvent = buildMouseMoveEvent(mouseMove);
14let mouseDownEvent = buildMouseDownEvent(mouseMoveEvent);
15app = function (u, c) {
16....
17ua.replace("xxxxxxxxx", "");
18}
19process = function (t) {
20var c = [].slice.call(arguments);
21return t = c.length === 1 && (0, isArray)(t) ? t : c, t = (0, flatten)(t), (0, toStr)(t)
22}
23getTM = function () {
24var a = process((0, replace_bs8)(tm));
25app(1, (0, replace_encrypt.encryptTM)(a))
26}
27//瀏覽器版本
28getBR = function (browserVersion) {
29....
30app(2, (0, replace_encrypt.encryptBR)(a))
31}
32//屏幕信息
33getSC = function () {
34....
35app(3, (0, replace_encrypt.encryptSC)(n))
36}
37//網址
38getLO = function (referer) {
39....
40app(4, (0, replace_encrypt.encryptLO)(i))
41}
42//函數
43getCF = function () {
44let cfParam = buildCFParam();
45....
46app(5, (0, replace_encrypt.encryptCF)(s))
47}
48//是否開啟控制台
49getDI = function () {
50....
51app(6, (0, replace_encrypt.encryptDI)(a))
52}
53//檢測是否使用webdriver等方式抓取
54getEM = function () {
55....
56app(7, (0, replace_encrypt.encryptEM)(s))
57}
58//版本
59getJSV = function () {
60....
61app(8, (0, replace_encrypt.encryptJSV)(o))
62}
63//token
64getTK = function (sid) {
65....
66app(9, (0, replace_encrypt.encryptTK)(a)))
67}
68getMM = function (mouseMove) {
69var v = mouseMove.eventName
70, h = mouseMove.tm
71, d = mouseMove.x
72, p = mouseMove.y
73....
74this.app(11, (0, replace_encrypt.encryptMM)(l))
75}
76getMD = function (mouseDown) {
77var f = mouseDown.eventName
78, s = mouseDown.button
79, v = mouseDown.tm
80, h = mouseDown.x
81, d = mouseDown.y
82....
83this.app(12, (0, replace_encrypt.encryptMD)(p))
84}
85var _sa = [];
86recordSA = function (mouseMove) {
87var i = mouseMove.tm
88, u = mouseMove.x
89, c = mouseMove.y
90....
91_sa.push((0, replace_encrypt.encryptSA)(f))
92}
93sendSA = function (r) {
94this.app(17, r);
95}
96sendTemp = function (t) {
97var n = process((0, replace_bs2)(t.length), (0, replace_bss)(t));
98app(10, (0, replace_encrypt.encryptTEMP)(n))
99}
100
101var tm = getTM();
102var br = getBR(browserVersion);
103var LO = getLO(referer);
104var CF = getCF();
105var DI = getDI();
106var EM = getEM();
107var JSV = getJSV();
108var TK = getTK(sid);
109var SC = getSC();
110var MM = getMM(mouseMoveEvent[mouseMoveEvent.length - 1]);
111var MD = getMD(mouseDownEvent[0]);
112var DI = getDI();
113for (let mveIndex = mouseMoveEvent.length - 2; mveIndex >= 0; mveIndex--) {
114var MM = getMM(mouseMoveEvent[mveIndex]);
115}
116for (let mvIndex = mouseMove.length - 1; mvIndex >= 0; mvIndex--) {
117var SA1 = recordSA(mouseMove[mvIndex]);
118}
119for (let saIndex = 0; saIndex < _sa.length; saIndex++) {
120var SA2 = sendSA(_sa[saIndex]);
121}
122var temp = sendTemp(position);
123return ua;
124}
Java服務化代碼
1@Override
2public CrackResult crackDingXiang(DingXiangParam dingXiangParam, Integer captchaType, String caller) throws IOException {
3Preconditions.checkNotNull(dingXiangParam, "crackDingXiang dingXiangParam is null");
4Preconditions.checkNotNull(StringUtils.isNotBlank(caller), "caller is null");
5
6String userAgent = HeaderBuilder.buildChrome();
7String browserVersion = getBrowserVersion(userAgent);
8dingXiangParam.setUserAgent(userAgent);
9//Lid請求
10String lidResult = getDingXiangLidFunc(dingXiangParam);
11dingXiangParam = changeDingXiangParam(dingXiangParam, lidResult);
12//C請求
13String cFunctionResult = getDingxiangCFunc(dingXiangParam);
14CFunctionResponse cFunctionResponse = buildCFunctionResponse(cFunctionResult);
15AFunctionParam aFunctionParam = buildPictureRequestParam(cFunctionResponse, dingXiangParam);
16//A請求
17String aFunctionResult = getDingXiangAFunc(aFunctionParam);
18AFunctionResponse aFunctionResponse = buildAFunctionResponse(aFunctionResult);
19//分析圖片
20PictureAnalysisParam pictureAnalysisParam = buildPictureAnalysisParam(dingXiangParam, aFunctionResponse, browserVersion);
21String pictureAnalysisResult = analysePicture(pictureAnalysisParam);
22PictureAnalysisResponse pictureAnalysisResponse = buildPictureAnalysisResponse(pictureAnalysisResult, pictureAnalysisParam);
23//V請求
24VFunctionParam vFunctionParam = buildVFunctionParam(pictureAnalysisResponse, aFunctionParam, aFunctionResponse, dingXiangParam);
25List<NameValuePair> vFunctionPostParam = buildDingXiangVFuncPostParam(vFunctionParam);
26String vFunctionResult = getDingXiangVFunc(vFunctionPostParam, dingXiangParam);
27VFunctionResponse vFunctionResponse = buildDingXiangVFuncResponse(vFunctionResult);
28CrackResult crackResult = buildCrackResult(vFunctionResponse, cFunctionResponse, dingXiangParam, captchaType);
29
30return crackResult;
31}
六、服務化關鍵
1、圖片還原

2、隨機加密算法解析(重點設計部分)
加密算法會隨着js混淆的改變而改變,如何服務化?

(1)語法樹思路

(2)舉例子
(1)未處理的index.js。

(2)創建模板並生成其語法樹。

(3)將加密算法轉化成語法樹-> 與模板語法樹結合 -> 生成新的語法樹 -> 生成新的js文件

(4)啟用定時任務,每天固定時間去生成新的加密算法js文件。
七、成果

八、頂象無感驗證流程
(1)頂象無感驗證流程圖

(2)頂象風控流程圖

九、總結
(1)頂象相對於極驗產品設計更加清晰。步驟非常明確 "設備數據->行為數據->驗證";
(2)通過每日更新混淆js文件、加密算法提升破解服務化難度;
(3)通過驗證碼下發策略,進行驗證碼種類變換,提升驗證碼識別難度;
(4)驗證碼產品創新能力強,例如:空間語義驗證、亂序拼圖驗證、面積驗證、旋轉驗證等;
(5)整體而言,頂象對於驗證碼的理解應該是業界領先的。
十、最后
1、目前國內在安全產品方面的文章較少,我們很難全面了解與學習安全產品,希望我的文章能夠幫助到更多人。
2、如果想系統地了解第三代驗證碼,可以結合我的另幾篇篇文章
(1)《第三代驗證碼研究》https://www.cnblogs.com/boycelee/p/11363611.html(推薦)
(2)《頂象驗證碼破解與研究》https://www.cnblogs.com/boycelee/p/14269941.html(推薦)
(3)《極驗驗證碼破解與研究》https://www.cnblogs.com/boycelee/p/14021048.html(推薦)
(4)《極驗無感驗證破解》https://www.cnblogs.com/boycelee/p/13951819.html
(5)《同盾小程序指紋破解》https://www.cnblogs.com/boycelee/category/1819211.html
3、本文不提供完整解決方案和完整數據,僅用於理論研究,維護網絡安全,人人有責。