selenium+java破解极验滑动验证码的示例代码

2025-05-29 0 91

摘要

分析验证码素材图片混淆原理,并采用selenium模拟人拖动滑块过程,进而破解验证码。

人工验证的过程

1、打开威锋网注册页面

2、移动鼠标至小滑块,一张完整的图片会出现(如下图1)

selenium+java破解极验滑动验证码的示例代码

3、点击鼠标左键,图片中间会出现一个缺块(如下图2)

selenium+java破解极验滑动验证码的示例代码

4、移动小滑块正上方图案至缺块处

5、验证通过

selenium模拟验证的过程

  1. 加载威锋网注册页面
  2. 下载图片1和缺块图片2
  3. 根据两张图片的差异计算平移的距离x
  4. 模拟鼠标点击事件,点击小滑块向右移动x
  5. 验证通过
  6. 详细分析

1、打开chrome浏览器控制台,会发现图1所示的验证码图片并不是极验后台返回的原图。而是由多个div拼接而成(如下图3)

selenium+java破解极验滑动验证码的示例代码

通过图片显示div的style属性可知,极验后台把图片进行切割加错位处理。把素材图片切割成10 * 58大小的52张小图,再进行错位处理。在网页上显示的时候,再通过css的background-position属性对图片进行还原。以上的图1和图2都是经过了这种处理。在这种情况下,使用selenium模拟验证是需要对下载的验证码图片进行还原。如上图3的第一个div.gt_cut_fullbg_slice标签,它的大小为10px * 58px,其中style属性为background-image: url("http://static.geetest.com/pictures/gt/969ffa43c/969ffa43c.webp"); background-position: -157px -58px;会把该属性对应url的图片进行一个平移操作,以左上角为参考,向左平移157px,向上平移58px,图片超出部分不会显示。所以上图1所示图片是由26 * 2个10px * 58px大小的div组成(如下图4)。每一个小方块的大小58 * 10

selenium+java破解极验滑动验证码的示例代码

2、下载图片并还原,上一步骤分析了图片具体的混淆逻辑,具体还原图片的代码实现如下,主要逻辑是把原图裁剪为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");

//删除产生的中间图片

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();

}

还原过程需要注意的是,后台返回错位的图片是312 * 116大小的。而网页上图片div的大小是260 * 116。

3、计算平移距离,遍历图片的每一个像素点,当两张图的r、g、b之差的和大于255,说明该点的差异过大,很有可能就是需要平移到该位置的那个点,代码如下。

?

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、模拟鼠标移动事件,这一步骤是最关键的步骤,极验验证码后台正是通过移动滑块的轨迹来判断是否为机器所为。整个移动轨迹的过程越随机越好,我这里提供一种成功率较高的移动算法,代码如下。

?

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("应平移距离:" + 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 {

//设置input参数

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);

/**

* 再来一次:

* 验证失败:

*/

if(!gttype.equals("再来一次:") && !gttype.equals("验证失败:")){

thread.sleep(4000);

system.out.println(driver);

break;

}

thread.sleep(4000);

}

}

/**

* 移动

* @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("应平移距离:" + 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超时

* @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;

}

});

}

/**

* 计算需要平移的距离

* @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数组

* @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");

//删除产生的中间图片

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;

}

}

}

/**

* 图片拼接 (注意:必须两张图片长宽一致哦)

* @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

附上一张滑动效果图

selenium+java破解极验滑动验证码的示例代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持快网idc。

原文链接:https://www.jianshu.com/p/1466f1ba3275

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 selenium+java破解极验滑动验证码的示例代码 https://www.kuaiidc.com/113145.html

相关文章

发表评论
暂无评论