在这篇文章中,我们将介绍如何使用Java来逆向工程gcaptcha4.js文件,找到并破解w参数的加密算法。这个过程包括观察verify请求,定位加密位置,分析加密算法,最终还原w参数的明文。
一. 观察verify请求
首先,我们需要观察verify请求的发起者。在网络请求中,我们可以看到所有verify请求都来自于gcaptcha4.js文件。因此,这个文件成为我们分析的重点。
我们使用Java的HttpURLConnection类来捕获和分析这些请求。
java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class VerifyRequest {
public static void main(String[] args) throws Exception {
String url = "https://example.com/verify";
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("GET");
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
connection.disconnect();
System.out.println(content.toString());
}
}
通过分析响应内容,可以发现所有的verify请求都指向了gcaptcha4.js文件。
二. 定位w参数加密位置
为了找到w参数的加密位置,我们需要解析gcaptcha4.js文件。我们可以下载并格式化这个文件,然后使用正则表达式搜索关键词:w、.w、'w'或"w"。
java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FindWParameter {
public static void main(String[] args) throws IOException {
String content = new String(Files.readAllBytes(Paths.get("gcaptcha4.js")));
Pattern pattern = Pattern.compile("[\\.'\"]w[\\.'\"]");
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("Found at: " + matcher.start() + " - " + matcher.end() + ": " + matcher.group());
}
}
}
通过搜索"w",我们找到了相关代码。在第2527行,我们发现了w的值r在第2525行被定义。
三. 分析w参数加密算法
接下来,我们需要简化代码中w的定义。假设我们已经提取了相关的JavaScript代码,我们可以用Java来模拟这个过程。
java
import com.google.gson.Gson;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class WParameter {
public static String stringify(Object obj) {
Gson gson = new Gson();
return gson.toJson(obj);
}
public static String defaultEncrypt(String data, String key) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update((data + key).getBytes());
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static void main(String[] args) throws NoSuchAlgorithmException {
// 示例数据
String e = stringify(new YourClass());
String a = "your_key";
String r = defaultEncrypt(e, a);
System.out.println(r);
}
}
首先,stringify(e)相当于对e进行JSON序列化操作。然后,我们分析defaultEncrypt(stringify(e), a)的含义。
java
public class Encryption {
public static String encrypt(String e, String t) {
String n = generateGuid();
String a = new Encryptor().encrypt(n);
while (a == null || a.length() != 256) {
n = generateGuid();
a = new Encryptor().encrypt(n);
}
String o = new Encryptor().encrypt(e, n);
return arrayToHex(o) + a;
}
public static String generateGuid() {
return String.format("%04x%04x%04x%04x",
(int)(Math.random() * 0x10000),
(int)(Math.random() * 0x10000),
(int)(Math.random() * 0x10000),
(int)(Math.random() * 0x10000));
}
public static String arrayToHex(String input) {
// 实现数组到十六进制字符串的转换
// 这里只是一个简单的示例实现,实际代码可能需要处理更多细节
StringBuilder sb = new StringBuilder();
for (char c : input.toCharArray()) {
sb.append(Integer.toHexString((int) c));
}
return sb.toString();
}
public static void main(String[] args) {
// 示例数据
String e = "example_data";
String t = "example_key";
String result = encrypt(e, t);
System.out.println(result);
}
}
从这里可以确定参数t未被使用,参数e就是w的明文。接着我们分析e的组成部分:
java
import java.util.HashMap;
import java.util.Map;
public class WParameterComponents {
public static void main(String[] args) {
Map<String, Object> e = new HashMap<>();
e.put("device_id", "A8A0"); // 固定值
e.put("em", Map.of(
"cp", 0,
"ek", "11",
"nt", 0,
"ph", 0,
"sc", 0,
"si", 0,
"wd", 1
)); // 固定值
e.put("ep", "123"); // 固定值
e.put("geetest", "captcha"); // 固定值
e.put("fq6a", "1925502591"); // 固定值
e.put("lang", "zh"); // 固定值
e.put("lot_number", "7e22264d4f3e4dd8a6ffbf6e82e1122d"); // load请求返回值
e.put("passtime", 166); // 通过时间
e.put("pow_msg", "1|0|md5|2022-03-25T14:23:36.364152+08:00|24f56dc13c40dc4a02fd0318567caef5|7e22264d4f3e4dd8a6ffbf6e82e1122d||29f07cebf938aa4e"); // load请求返回加上某算法返回值
e.put("pow_sign", "2b47a3a9425dd19dd5abf902c8bb0763"); // pow_msg的md5值
e.put("setLeft", 88); // 滑动距离
e.put("track", new int[][]{{38, 18, 0}, {1, 0, 33}......}); // 轨迹
e.put("userresponse", 87.47978686742837); // 某算法返回值
System.out.println(e);
}
}
4.1 分析pow_msg和pow_sign
通过搜索pow_msg,我们找到相关代码并解析如下:
java
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
public class PowMsgAndSign {
public static String generateGuid() {
Random random = new Random();
return String.format("%04x%04x%04x%04x",
random.nextInt(0x10000),
random.nextInt(0x10000),
random.nextInt(0x10000),
random.nextInt(0x10000));
}
public static String md5(String input) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : messageDigest) {
hexString.append(String.format("%02x", b));
}
return hexString.toString();
}
public static void main(String[] args) throws NoSuchAlgorithmException {
String n = "example_n";
String a = "example_a";
String s = "example_s";
String o = "example_o";
String t = "example_t";
String e = "example_e";
String r = "example_r";
String u = String.join("|", n, a, s, o, t, e, r, "");
String p = generateGuid();
String g = u + p;
String pow_msg = u + p;
String pow_sign = md5(g);
System.out.println("pow_msg: " + pow_msg);
System.out.println("pow_sign: " + pow_sign);
}
}
4.2 分析set_left、track、passtime、userresponse
set_left:滑块移动距离的整数值
track:移动轨迹,从第二步开始,是相对上一步的相对移动距离(x, y, t)
passtime:总移动时间
userresponse:计算公式为set_left / (0.8876 * 340 / 300)
java
public class TrackAnalysis {
public static void main(String[] args) {
int set_left = 88;
int[][] track = {{38, 18, 0}, {1, 0, 33}......};
int passtime = 166;
double userresponse = set_left / (0.8876 * 340 / 300);
System.out.println("set_left: " + set_left);
System.out.println("track: " + Arrays.deepToString(track));
System.out.println("passtime: " + passtime);
System.out.println("userresponse: " + userresponse);
}
}
至此,我们已经完成了w参数的明文解析。