一. 观察verify请求
首先,我们需要观察verify请求的Initiator,可以看到所有请求都来自于gcaptcha4.js这个文件,所以该文件是我们此次的目标所在。
打开gcaptcha4.js文件,格式化后,我们在13339行发现,代码经过混淆处理,这时候我们有两种选择:硬刚或者使用AST还原。我选择了AST还原JS文件,然后使用Chrome上的reres插件将混淆的JS替换掉,再一步步调试。AST还原代码已放在GitHub上,reres插件的安装和使用请自行搜索。
替换成功后,我们得到以下结果:
二. 定位w参数加密位置
搜索w参数,我们可以通过以下关键词定位:
w
.w
'w'
"w"
搜索"w"后,我们找到了目标,在2527行打上断点,可以看到w的值r在2525行定义,我们也在此打上断点,刷新页面,成功断上。至此,我们已完成w参数的加密位置定位。
三. 分析w参数加密算法
简化w的值r的定义:
javascript
var r = (0, d["default"])(l["default"]["stringify"](e), a)
等价于:
javascript
var r = d.default(l.default.stringify(e), a)
分析l.default.stringify(e)的含义:
可以看出此处是对e进行JSON序列化操作。
分析d.default(l.default.stringify(e), a)的含义:
跟进d函数,在几个条件判断处打上断点,调试后得出结论:
javascript
function a(e, t) {
var n = c["guid"](),
a = new _["default"]()["encrypt"](n);
o = i["default"]["encrypt"](e, n);
return c["arrayToHex"](o) + a;
}
我们确定参数t并未使用,参数e即w的明文。继续分析JSON序列化前e的组成:
elixir
e = %{
'device_id' => 'A8A0', # 固定值
'em' => %{
'cp' => 0,
'ek' => '11',
'nt' => 0,
'ph' => 0,
'sc' => 0,
'si' => 0,
'wd' => 1
}, # 固定值
'ep' => '123', # 固定值
'geetest' => 'captcha', # 固定值
'fq6a' => '1925502591', # 固定值
'lang' => 'zh', # 固定值
'lot_number' => '7e22264d4f3e4dd8a6ffbf6e82e1122d', # load请求返回值
'passtime' => 166, # 通过时间
'pow_msg' => '1|0|md5|2022-03-25T14:23:36.364152+08:00|24f56dc13c40dc4a02fd0318567caef5|7e22264d4f3e4dd8a6ffbf6e82e1122d||29f07cebf938aa4e', # load请求返回加上某算法返回值
'pow_sign' => '2b47a3a9425dd19dd5abf902c8bb0763', # pow_msg的md5值
'setLeft' => 88, # 滑动距离
'track' => [[38, 18, 0], [1, 0, 33]......], # 轨迹
'userresponse' => 87.47978686742837 # 某算法返回值
}
四. 分析pow_msg和pow_sign
搜索pow_msg,打上断点,刷新,保留成功断上的断点:
elixir
u = n <> "|" <> a <> "|" <> s <> "|" <> o <> "|" <> t <> "|" <> e <> "|" <> r <> "|"
p = guid()
g = u <> p
pow_msg = u <> p
pow_sign = :crypto.hash(:md5, g) |> Base.encode16(case: :lower)
我们需要分析guid算法:
elixir
defmodule Guid do
def generate do
Enum.join(for _ <- 1..4, do: :rand.uniform(65536) |> Integer.to_string(16))
end
end
五. 分析set_left、track、passtime、userresponse
set_left为滑块移动距离取整。
track为移动轨迹,从第二步开始,是上一步的相对移动距离(x, y, t)。
passtime为总移动时间。
userresponse为set_left / (0.8876 * 340 / 300)。
至此,我们已将w的明文分析完毕。以下是Elixir实现代码:
六. 代码实现
elixir
defmodule GeetestCracker do
def generate_w_params do
device_id = "A8A0"
em = %{
cp: 0,
ek: "11",
nt: 0,
ph: 0,
sc: 0,
si: 0,
wd: 1
}
ep = "123"
geetest = "captcha"
fq6a = "1925502591"
lang = "zh"
lot_number = "7e22264d4f3e4dd8a6ffbf6e82e1122d"
passtime = 166
set_left = 88
track = [[38, 18, 0], [1, 0, 33]...] # 完整轨迹数据
userresponse = set_left / (0.8876 * 340 / 300)
pow_msg = generate_pow_msg(lot_number)
pow_sign = :crypto.hash(:md5, pow_msg) |> Base.encode16(case: :lower)
%{
device_id: device_id,
em: em,
ep: ep,
geetest: geetest,
fq6a: fq6a,
lang: lang,
lot_number: lot_number,
passtime: passtime,
pow_msg: pow_msg,
pow_sign: pow_sign,
setLeft: set_left,
track: track,
userresponse: userresponse
}
end
defp generate_pow_msg(lot_number) do
# 生成pow_msg的逻辑
guid = Guid.generate()
"#{lot_number}|#{guid}"
end
end
defmodule Guid do
def generate do
Enum.join(for _ <- 1..4, do: :rand.uniform(65536) |> Integer.to_string(16))
end
end