摘要
分析驗(yàn)證碼素材圖片混淆原理,并采用selenium模擬人拖動(dòng)滑塊過程,進(jìn)而破解驗(yàn)證碼。
人工驗(yàn)證的過程
1、打開威鋒網(wǎng)注冊(cè)頁(yè)面
2、移動(dòng)鼠標(biāo)至小滑塊,一張完整的圖片會(huì)出現(xiàn)(如下圖1)
3、點(diǎn)擊鼠標(biāo)左鍵,圖片中間會(huì)出現(xiàn)一個(gè)缺塊(如下圖2)
4、移動(dòng)小滑塊正上方圖案至缺塊處
5、驗(yàn)證通過
selenium模擬驗(yàn)證的過程
- 加載威鋒網(wǎng)注冊(cè)頁(yè)面
- 下載圖片1和缺塊圖片2
- 根據(jù)兩張圖片的差異計(jì)算平移的距離x
- 模擬鼠標(biāo)點(diǎn)擊事件,點(diǎn)擊小滑塊向右移動(dòng)x
- 驗(yàn)證通過
- 詳細(xì)分析
1、打開chrome瀏覽器控制臺(tái),會(huì)發(fā)現(xiàn)圖1所示的驗(yàn)證碼圖片并不是極驗(yàn)后臺(tái)返回的原圖。而是由多個(gè)div拼接而成(如下圖3)
通過圖片顯示div的style屬性可知,極驗(yàn)后臺(tái)把圖片進(jìn)行切割加錯(cuò)位處理。把素材圖片切割成10 * 58大小的52張小圖,再進(jìn)行錯(cuò)位處理。在網(wǎng)頁(yè)上顯示的時(shí)候,再通過css的background-position屬性對(duì)圖片進(jìn)行還原。以上的圖1和圖2都是經(jīng)過了這種處理。在這種情況下,使用selenium模擬驗(yàn)證是需要對(duì)下載的驗(yàn)證碼圖片進(jìn)行還原。如上圖3的第一個(gè)div.gt_cut_fullbg_slice標(biāo)簽,它的大小為10px * 58px,其中style屬性為background-image: url("http://static.geetest.com/pictures/gt/969ffa43c/969ffa43c.webp"); background-position: -157px -58px;會(huì)把該屬性對(duì)應(yīng)url的圖片進(jìn)行一個(gè)平移操作,以左上角為參考,向左平移157px,向上平移58px,圖片超出部分不會(huì)顯示。所以上圖1所示圖片是由26 * 2個(gè)10px * 58px大小的div組成(如下圖4)。每一個(gè)小方塊的大小58 * 10
2、下載圖片并還原,上一步驟分析了圖片具體的混淆邏輯,具體還原圖片的代碼實(shí)現(xiàn)如下,主要邏輯是把原圖裁剪為52張小圖,然后拼接成一張完整的圖。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
/** *還原圖片 * @param type */ private static void restoreimage(string type) throws ioexception { //把圖片裁剪為2 * 26份 for ( int i = 0 ; i < 52 ; i++){ cutpic(basepath + type + ".jpg" ,basepath + "result/" + type + i + ".jpg" , -movearray[i][ 0 ], -movearray[i][ 1 ], 10 , 58 ); } //拼接圖片 string[] b = new string[ 26 ]; for ( int i = 0 ; i < 26 ; i++){ b[i] = string.format(basepath + "result/" + type + "%d.jpg" , i); } mergeimage(b, 1 , basepath + "result/" + type + "result1.jpg" ); //拼接圖片 string[] c = new string[ 26 ]; for ( int i = 0 ; i < 26 ; i++){ c[i] = string.format(basepath + "result/" + type + "%d.jpg" , i + 26 ); } mergeimage(c, 1 , basepath + "result/" + type + "result2.jpg" ); mergeimage( new string[]{basepath + "result/" + type + "result1.jpg" , basepath + "result/" + type + "result2.jpg" }, 2 , basepath + "result/" + type + "result3.jpg" ); //刪除產(chǎn)生的中間圖片 for ( int i = 0 ; i < 52 ; i++){ new file(basepath + "result/" + type + i + ".jpg" ).deleteonexit(); } new file(basepath + "result/" + type + "result1.jpg" ).deleteonexit(); new file(basepath + "result/" + type + "result2.jpg" ).deleteonexit(); } |
還原過程需要注意的是,后臺(tái)返回錯(cuò)位的圖片是312 * 116大小的。而網(wǎng)頁(yè)上圖片div的大小是260 * 116。
3、計(jì)算平移距離,遍歷圖片的每一個(gè)像素點(diǎn),當(dāng)兩張圖的r、g、b之差的和大于255,說(shuō)明該點(diǎn)的差異過大,很有可能就是需要平移到該位置的那個(gè)點(diǎn),代碼如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
bufferedimage fullbi = imageio.read( new file(basepath + "result/" + full_image_name + "result3.jpg" )); bufferedimage bgbi = imageio.read( new file(basepath + "result/" + bg_image_name + "result3.jpg" )); for ( int i = 0 ; i < bgbi.getwidth(); i++){ for ( int j = 0 ; j < bgbi.getheight(); j++) { int [] fullrgb = new int [ 3 ]; fullrgb[ 0 ] = (fullbi.getrgb(i, j) & 0xff0000 ) >> 16 ; fullrgb[ 1 ] = (fullbi.getrgb(i, j) & 0xff00 ) >> 8 ; fullrgb[ 2 ] = (fullbi.getrgb(i, j) & 0xff ); int [] bgrgb = new int [ 3 ]; bgrgb[ 0 ] = (bgbi.getrgb(i, j) & 0xff0000 ) >> 16 ; bgrgb[ 1 ] = (bgbi.getrgb(i, j) & 0xff00 ) >> 8 ; bgrgb[ 2 ] = (bgbi.getrgb(i, j) & 0xff ); if (difference(fullrgb, bgrgb) > 255 ){ return i; } } } |
4、模擬鼠標(biāo)移動(dòng)事件,這一步驟是最關(guān)鍵的步驟,極驗(yàn)驗(yàn)證碼后臺(tái)正是通過移動(dòng)滑塊的軌跡來(lái)判斷是否為機(jī)器所為。整個(gè)移動(dòng)軌跡的過程越隨機(jī)越好,我這里提供一種成功率較高的移動(dòng)算法,代碼如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public static void move(webdriver driver, webelement element, int distance) throws interruptedexception { int xdis = distance + 11 ; system.out.println( "應(yīng)平移距離:" + xdis); int movex = new random().nextint( 8 ) - 5 ; int movey = 1 ; actions actions = new actions(driver); new actions(driver).clickandhold(element).perform(); thread.sleep( 200 ); printlocation(element); actions.movetoelement(element, movex, movey).perform(); system.out.println(movex + "--" + movey); printlocation(element); for ( int i = 0 ; i < 22 ; i++){ int s = 10 ; if (i % 2 == 0 ){ s = - 10 ; } actions.movetoelement(element, s, 1 ).perform(); printlocation(element); thread.sleep( new random().nextint( 100 ) + 150 ); } system.out.println(xdis + "--" + 1 ); actions.movebyoffset(xdis, 1 ).perform(); printlocation(element); thread.sleep( 200 ); actions.release(element).perform(); } |
完整代碼如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
|
package com.github.wycm; import org.apache.commons.io.fileutils; import org.jsoup.jsoup; import org.jsoup.nodes.document; import org.jsoup.nodes.element; import org.jsoup.select.elements; import org.openqa.selenium.by; import org.openqa.selenium.point; import org.openqa.selenium.webdriver; import org.openqa.selenium.webelement; import org.openqa.selenium.chrome.chromedriver; import org.openqa.selenium.interactions.actions; import org.openqa.selenium.support.ui.expectedcondition; import org.openqa.selenium.support.ui.webdriverwait; import javax.imageio.imageio; import javax.imageio.imagereadparam; import javax.imageio.imagereader; import javax.imageio.stream.imageinputstream; import java.awt.*; import java.awt.image.bufferedimage; import java.io.file; import java.io.fileinputstream; import java.io.ioexception; import java.net.url; import java.util.iterator; import java.util.random; import java.util.regex.matcher; import java.util.regex.pattern; public class geettestcrawler { private static string basepath = "src/main/resources/" ; private static string full_image_name = "full-image" ; private static string bg_image_name = "bg-image" ; private static int [][] movearray = new int [ 52 ][ 2 ]; private static boolean movearrayinit = false ; private static string index_url = "https://passport.feng.com/?r=user/register" ; private static webdriver driver; static { system.setproperty( "webdriver.chrome.driver" , "d:/dev/selenium/chromedriver_v2.30/chromedriver_win32/chromedriver.exe" ); if (!system.getproperty( "os.name" ).tolowercase().contains( "windows" )){ system.setproperty( "webdriver.chrome.driver" , "/users/wangyang/workspace/selenium/chromedriver_v2.30/chromedriver" ); } driver = new chromedriver(); } public static void main(string[] args) throws interruptedexception { for ( int i = 0 ; i < 10 ; i++){ try { invoke(); } catch (ioexception e) { e.printstacktrace(); } catch (interruptedexception e) { e.printstacktrace(); } } driver.quit(); } private static void invoke() throws ioexception, interruptedexception { //設(shè)置input參數(shù) driver.get(index_url); //通過[class=gt_slider_knob gt_show] by movebtn = by.cssselector( ".gt_slider_knob.gt_show" ); waitforload(driver, movebtn); webelement moveelemet = driver.findelement(movebtn); int i = 0 ; while (i++ < 15 ){ int distance = getmovedistance(driver); move(driver, moveelemet, distance - 6 ); by gttypeby = by.cssselector( ".gt_info_type" ); by gtinfoby = by.cssselector( ".gt_info_content" ); waitforload(driver, gttypeby); waitforload(driver, gtinfoby); string gttype = driver.findelement(gttypeby).gettext(); string gtinfo = driver.findelement(gtinfoby).gettext(); system.out.println(gttype + "---" + gtinfo); /** * 再來(lái)一次: * 驗(yàn)證失?。?/code> */ if (!gttype.equals( "再來(lái)一次:" ) && !gttype.equals( "驗(yàn)證失敗:" )){ thread.sleep( 4000 ); system.out.println(driver); break ; } thread.sleep( 4000 ); } } /** * 移動(dòng) * @param driver * @param element * @param distance * @throws interruptedexception */ public static void move(webdriver driver, webelement element, int distance) throws interruptedexception { int xdis = distance + 11 ; system.out.println( "應(yīng)平移距離:" + xdis); int movex = new random().nextint( 8 ) - 5 ; int movey = 1 ; actions actions = new actions(driver); new actions(driver).clickandhold(element).perform(); thread.sleep( 200 ); printlocation(element); actions.movetoelement(element, movex, movey).perform(); system.out.println(movex + "--" + movey); printlocation(element); for ( int i = 0 ; i < 22 ; i++){ int s = 10 ; if (i % 2 == 0 ){ s = - 10 ; } actions.movetoelement(element, s, 1 ).perform(); // printlocation(element); thread.sleep( new random().nextint( 100 ) + 150 ); } system.out.println(xdis + "--" + 1 ); actions.movebyoffset(xdis, 1 ).perform(); printlocation(element); thread.sleep( 200 ); actions.release(element).perform(); } private static void printlocation(webelement element){ point point = element.getlocation(); system.out.println(point.tostring()); } /** * 等待元素加載,10s超時(shí) * @param driver * @param by */ public static void waitforload( final webdriver driver, final by by){ new webdriverwait(driver, 10 ).until( new expectedcondition< boolean >() { public boolean apply(webdriver d) { webelement element = driver.findelement(by); if (element != null ){ return true ; } return false ; } }); } /** * 計(jì)算需要平移的距離 * @param driver * @return * @throws ioexception */ public static int getmovedistance(webdriver driver) throws ioexception { string pagesource = driver.getpagesource(); string fullimageurl = getfullimageurl(pagesource); fileutils.copyurltofile( new url(fullimageurl), new file(basepath + full_image_name + ".jpg" )); string getbgimageurl = getbgimageurl(pagesource); fileutils.copyurltofile( new url(getbgimageurl), new file(basepath + bg_image_name + ".jpg" )); initmovearray(driver); restoreimage(full_image_name); restoreimage(bg_image_name); bufferedimage fullbi = imageio.read( new file(basepath + "result/" + full_image_name + "result3.jpg" )); bufferedimage bgbi = imageio.read( new file(basepath + "result/" + bg_image_name + "result3.jpg" )); for ( int i = 0 ; i < bgbi.getwidth(); i++){ for ( int j = 0 ; j < bgbi.getheight(); j++) { int [] fullrgb = new int [ 3 ]; fullrgb[ 0 ] = (fullbi.getrgb(i, j) & 0xff0000 ) >> 16 ; fullrgb[ 1 ] = (fullbi.getrgb(i, j) & 0xff00 ) >> 8 ; fullrgb[ 2 ] = (fullbi.getrgb(i, j) & 0xff ); int [] bgrgb = new int [ 3 ]; bgrgb[ 0 ] = (bgbi.getrgb(i, j) & 0xff0000 ) >> 16 ; bgrgb[ 1 ] = (bgbi.getrgb(i, j) & 0xff00 ) >> 8 ; bgrgb[ 2 ] = (bgbi.getrgb(i, j) & 0xff ); if (difference(fullrgb, bgrgb) > 255 ){ return i; } } } throw new runtimeexception( "未找到需要平移的位置" ); } private static int difference( int [] a, int [] b){ return math.abs(a[ 0 ] - b[ 0 ]) + math.abs(a[ 1 ] - b[ 1 ]) + math.abs(a[ 2 ] - b[ 2 ]); } /** * 獲取move數(shù)組 * @param driver */ private static void initmovearray(webdriver driver){ if (movearrayinit){ return ; } document document = jsoup.parse(driver.getpagesource()); elements elements = document.select( "[class=gt_cut_bg gt_show]" ).first().children(); int i = 0 ; for (element element : elements){ pattern pattern = pattern.compile( ".*background-position: (.*?)px (.*?)px.*" ); matcher matcher = pattern.matcher(element.tostring()); if (matcher.find()){ string width = matcher.group( 1 ); string height = matcher.group( 2 ); movearray[i][ 0 ] = integer.parseint(width); movearray[i++][ 1 ] = integer.parseint(height); } else { throw new runtimeexception( "解析異常" ); } } movearrayinit = true ; } /** *還原圖片 * @param type */ private static void restoreimage(string type) throws ioexception { //把圖片裁剪為2 * 26份 for ( int i = 0 ; i < 52 ; i++){ cutpic(basepath + type + ".jpg" ,basepath + "result/" + type + i + ".jpg" , -movearray[i][ 0 ], -movearray[i][ 1 ], 10 , 58 ); } //拼接圖片 string[] b = new string[ 26 ]; for ( int i = 0 ; i < 26 ; i++){ b[i] = string.format(basepath + "result/" + type + "%d.jpg" , i); } mergeimage(b, 1 , basepath + "result/" + type + "result1.jpg" ); //拼接圖片 string[] c = new string[ 26 ]; for ( int i = 0 ; i < 26 ; i++){ c[i] = string.format(basepath + "result/" + type + "%d.jpg" , i + 26 ); } mergeimage(c, 1 , basepath + "result/" + type + "result2.jpg" ); mergeimage( new string[]{basepath + "result/" + type + "result1.jpg" , basepath + "result/" + type + "result2.jpg" }, 2 , basepath + "result/" + type + "result3.jpg" ); //刪除產(chǎn)生的中間圖片 for ( int i = 0 ; i < 52 ; i++){ new file(basepath + "result/" + type + i + ".jpg" ).deleteonexit(); } new file(basepath + "result/" + type + "result1.jpg" ).deleteonexit(); new file(basepath + "result/" + type + "result2.jpg" ).deleteonexit(); } /** * 獲取原始圖url * @param pagesource * @return */ private static string getfullimageurl(string pagesource){ string url = null ; document document = jsoup.parse(pagesource); string style = document.select( "[class=gt_cut_fullbg_slice]" ).first().attr( "style" ); pattern pattern = pattern.compile( "url\\(\"(.*)\"\\)" ); matcher matcher = pattern.matcher(style); if (matcher.find()){ url = matcher.group( 1 ); } url = url.replace( ".webp" , ".jpg" ); system.out.println(url); return url; } /** * 獲取帶背景的url * @param pagesource * @return */ private static string getbgimageurl(string pagesource){ string url = null ; document document = jsoup.parse(pagesource); string style = document.select( ".gt_cut_bg_slice" ).first().attr( "style" ); pattern pattern = pattern.compile( "url\\(\"(.*)\"\\)" ); matcher matcher = pattern.matcher(style); if (matcher.find()){ url = matcher.group( 1 ); } url = url.replace( ".webp" , ".jpg" ); system.out.println(url); return url; } public static boolean cutpic(string srcfile, string outfile, int x, int y, int width, int height) { fileinputstream is = null ; imageinputstream iis = null ; try { if (! new file(srcfile).exists()) { return false ; } is = new fileinputstream(srcfile); string ext = srcfile.substring(srcfile.lastindexof( "." ) + 1 ); iterator<imagereader> it = imageio.getimagereadersbyformatname(ext); imagereader reader = it.next(); iis = imageio.createimageinputstream(is); reader.setinput(iis, true ); imagereadparam param = reader.getdefaultreadparam(); rectangle rect = new rectangle(x, y, width, height); param.setsourceregion(rect); bufferedimage bi = reader.read( 0 , param); file tempoutfile = new file(outfile); if (!tempoutfile.exists()) { tempoutfile.mkdirs(); } imageio.write(bi, ext, new file(outfile)); return true ; } catch (exception e) { e.printstacktrace(); return false ; } finally { try { if (is != null ) { is.close(); } if (iis != null ) { iis.close(); } } catch (ioexception e) { e.printstacktrace(); return false ; } } } /** * 圖片拼接 (注意:必須兩張圖片長(zhǎng)寬一致哦) * @param files 要拼接的文件列表 * @param type 1橫向拼接,2 縱向拼接 * @param targetfile 輸出文件 */ private static void mergeimage(string[] files, int type, string targetfile) { int length = files.length; file[] src = new file[length]; bufferedimage[] images = new bufferedimage[length]; int [][] imagearrays = new int [length][]; for ( int i = 0 ; i < length; i++) { try { src[i] = new file(files[i]); images[i] = imageio.read(src[i]); } catch (exception e) { throw new runtimeexception(e); } int width = images[i].getwidth(); int height = images[i].getheight(); imagearrays[i] = new int [width * height]; imagearrays[i] = images[i].getrgb( 0 , 0 , width, height, imagearrays[i], 0 , width); } int newheight = 0 ; int newwidth = 0 ; for ( int i = 0 ; i < images.length; i++) { // 橫向 if (type == 1 ) { newheight = newheight > images[i].getheight() ? newheight : images[i].getheight(); newwidth += images[i].getwidth(); } else if (type == 2 ) { // 縱向 newwidth = newwidth > images[i].getwidth() ? newwidth : images[i].getwidth(); newheight += images[i].getheight(); } } if (type == 1 && newwidth < 1 ) { return ; } if (type == 2 && newheight < 1 ) { return ; } // 生成新圖片 try { bufferedimage imagenew = new bufferedimage(newwidth, newheight, bufferedimage.type_int_rgb); int height_i = 0 ; int width_i = 0 ; for ( int i = 0 ; i < images.length; i++) { if (type == 1 ) { imagenew.setrgb(width_i, 0 , images[i].getwidth(), newheight, imagearrays[i], 0 , images[i].getwidth()); width_i += images[i].getwidth(); } else if (type == 2 ) { imagenew.setrgb( 0 , height_i, newwidth, images[i].getheight(), imagearrays[i], 0 , newwidth); height_i += images[i].getheight(); } } //輸出想要的圖片 imageio.write(imagenew, targetfile.split( "\\." )[ 1 ], new file(targetfile)); } catch (exception e) { throw new runtimeexception(e); } } } |
pom文件依賴如下
1
2
3
4
5
6
7
8
9
10
11
|
<dependency> <groupid>org.seleniumhq.selenium</groupid> <artifactid>selenium-server</artifactid> <version> 3.0 . 1 </version> </dependency> <!-- https: //mvnrepository.com/artifact/org.jsoup/jsoup --> <dependency> <groupid>org.jsoup</groupid> <artifactid>jsoup</artifactid> <version> 1.7 . 2 </version> </dependency> |
最后
完整代碼已上傳至github,地址:https://github.com/wycm/selenium-geetest-crack
附上一張滑動(dòng)效果圖
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:https://www.jianshu.com/p/1466f1ba3275