第54题 无限debugger练习/入门js
本题打开控制台就会自动无限 debugger,解决无限 debugger 的最简单方式就是使用 Firefox 121 版本以上的版本,Firefox 121 以上的版本会对代码内部的 debugger 自动过滤,只有手动打的断点才会生效。
本题是无限 debugger 练习,尝试手动解决该问题。
首先是查看断点位置及调用堆栈,可以看到,这个无限 debugger 其实就是利用了 eval 的特性不断断点:
那么我们 hook eval 函数就可以了:
let _eval = eval;
eval= function(){if (arguments[0].indexOf('debugger') != -1) {return _eval('')}return _eval.apply(this, arguments)
}
将这段代码输入控制台回车,点击执行按钮,程序就会继续执行了,但是这种方式刷新页面就失效了,可以通过油猴脚本插件实现持久化,也可以使用 v_jstools 插件自带的 hook_eval 功能或注入代码功能。
这个点过了之后,程序又会在下一处无限 debugger,可以看到,有很多个 script 标签,里面有 debugger 命令,这时可以通过替换本地内容的方式:
可以右键点击 54 文件,选择“替换内容”(override content),如果是第一次进行此项操作的话还会让你选择本地的一个文件夹,作为替换的文件夹,允许相关权限即可。然后 54 文件就会被保存到本地,并被本地的该文件替换,接下来就可以把里面的 debugger 部分删掉即可。
删掉后保存并刷新页面,可以看到此处也没有无限 debugger 了。
接着是下一处无限 debugger,查看调用堆栈可知,该处是利用了 appendChild 方法不断添加含 debugger 内容的 script 标签并移除触发的,此时需要 hook appendChild 方法。
通过控制台打印可知,n 是 document,n 的原型是 HTMLDocument ,而 HTMLDocument 经查找没有 appendChild 方法:
那么继续从原型链上查找,最终发现 appendChild 方法是 Node 节点下的:
hook 该方法可以这么写:
let _appendChild = Node.prototype.appendChild
Node.prototype.appendChild = function(){if (arguments[0].innerHTML && arguments[0].innerHTML.indexOf('debugger') != -1){arguments[0].innerHTML = ''}return _appendChild.apply(this, arguments)
}
该处无限 debugger 过掉后,代码会在如下地方再次停下:
由于是我们已经本地替换的文件内容, 直接把此部分删掉并保存即可。这样该题所有的无限 debugger 就都过掉了。
接下来就是查看接口,逆向参数了,本题需找到参数 token 的由来,全局搜索即可找到:
很简单,是对页码的 base64 编码。
第55题 结果加密跟值
此题请求没有加密,但是返回的响应是加密的,通过调用堆栈很容易找到加密位置:
查看 decode 方法:
可以看到是一个 AES 加密,根据代码推测是一个 ECB 模式的,填充方式为 Pkcs7,密钥为 aiding6666666666 的 AES 加密,可以放到标准 AES 加密里对比:
这是一个网上找的标准 AES 加密,将密码、模式和填充模式选择为对应的之后,输入框输入该题中的加密值,这里我用的第三页的加密值,点击解密:
可以看到,成功解密出了值,说明这是一个未经魔改的 AES 加密,我们本地模拟调用就可以了,对于此题,将 decode 方法复制到本地就可以直接使用。
第56题 经典入门数据加密
该题和上题一样,请求没有加密,响应结果是加密的,通过调用堆栈也很容易找到对应位置,如下:
该文件代码是经过混淆的,不方便查看,可以先解混淆再查看,可以自己通过 AST 解混淆,也可以通过一些插件解混淆,这里使用 v_jstools 插件的解混淆功能:
将文件代码复制到source框中,点击普通解混淆即可,将code框中的解混淆代码复制到本地查看:
可以看到,有一个设置 private key 的操作,ctrl 点击 PVA 查看该变量,如下:
PVA = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAy5R1R2yM5jPPvkO2F47qVqMkYj7o92DF8y1yMkCSxY1WwqG0\ndCdUZTnaoBuAz99wGt55oGLcdalV71nPUiGWs/b6GzVN5v72baz/Q2OxHtkrFKqL\nVX16LW31cW9hAntN84RCbvTeB0MNV+SHmXjIf17OQLCtDKHBZWZ5NKyqFstO+KOd\nu32d2jsw+DT5lOBzDUBk/wUw2KyFJVx7eK6sSXEyWqBk2nxMRDNYixIEN1V1EBSq\nf+OwKK5Mxi04r38+Qog8z03/t/u6CfAOWVmi+MdrD1VHXv/P7bnFlgRcLzKwK1QL\nTSLBE1PrMmNNj0oRjByhMoI9tY5X6mRBqLyDhwIDAQABAoIBAGO++RmGO6D9CNAJ\n4Bm52eKaK5UBiubOIR8NiNLLZb5qinRxg3eX35d7Wb2xzBLNwOFBWSl21trFncfY\n4qY0s+C4ZYHYQ7Om/7nsFeQAYAOj1yJYj01TXf4NTsGGF2t+W8qxZlV0H6dCOLL0\nU2YkUmRp4Le8eQVj6dyTcVaYNPxWQBnb9ZOEIEvEjeoO/DD7CCmt7LDCey9KrTQl\nAvuc2nN6uRV1Wfm0P8conKPJtVdgzMvJujNdpz+bBDqwsqgeCICjs/hSCNO81VH3\nDD7J0mG2OHqowOVqagoDHpBprHOUKxAeTs9I0KEL+hEI4zXCDL69+Xs6azuts733\nzSOmwxkCgYEA25czfPVxxcK685LhaAvwbmzWHqNp07ytRNGf+Aww6OdgWkdgPy0n\n20Gkg0HAqsxGcgZJk6cAkOy5hBLNHpHlGbeWFi+62lVNYUv3hAxumtiPyBMu7avE\nZQCTXND1H1f/2enRDJRxQsR8y/SX1ivmC5U6fx7hbpKxnXyRHnvSlk8CgYEA7VWp\nhLNkn4AEaPPW0TknwKG40At/hjecX2zWAyZVt4ydDSeKgMEOUdmvGGlSCrefAl0n\nPTfM9SdIDcO5OTa2wUayKLIsrb6TDnG6KXXN6z3HR3Q4qKJbG83eaMYDqqziPPV+\nxzRVWShI3EGwkLczASmiYy+sEAT0OkxP59xTKUkCgYBgaGjFkukJfy4fJDxsNtmv\nUX9MYkhjGrIjxbjq6UdL6dGGsVGTSxr1i0NUETkqg5bmFtaUybxY5GWqk6qUok8o\nVE7DnN73Xn4jmnun8OFagHvXxnxTApeuFGueU2tbAIKmxJ3wXPfA7Y0w6kkDUbCl\nIzZUe1VT+3mZgAgijxBsxwKBgQDNytiJ62/V6hBo3P6pPtEcdF6nb0DtpazfBaVw\n572twaywqlermzsKeCIenbx49I1ZZGLQ72C2NpCA9vTWCn5fiyiSpyScp0ImZTDS\nIIckctYoPDug5d7wdgtjeEfXp78osopyuwtCmu7Kpd8vLNt6J5raPI0K+vC22FL1\nLpOhmQKBgQCFeU448fL87N1MjMyusi8wJ5MLcn+kHbLTtpskTpfQM2p3Cnp4oL+7\nBI4AlXlKItV37rJIjZxQgLWhGoTZPplZaW4ooJCFJbazce5ua5fnsFS0oXhDN7uw\njaq+v5t8G6gFS09hEa4kz9O53t/7UGuQqh0Bxb0cJ9iNeAlhagvBDQ==\n-----END RSA PRIVATE KEY-----";
可以看到,这是一个 RSA 加解密的私钥,将密文和私钥放入标准 RSA 中解密,如下:
成功解密得到了我们所需的数据,说明这是一个标准 RSA 加密,本地模拟调用即可,python 模拟 RSA 解密的参考代码:
import rsa
import base64# 私钥
private_key_data = '''-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAy5R1R2yM5jPPvkO2F47qVqMkYj7o92DF8y1yMkCSxY1WwqG0
dCdUZTnaoBuAz99wGt55oGLcdalV71nPUiGWs/b6GzVN5v72baz/Q2OxHtkrFKqL
VX16LW31cW9hAntN84RCbvTeB0MNV+SHmXjIf17OQLCtDKHBZWZ5NKyqFstO+KOd
u32d2jsw+DT5lOBzDUBk/wUw2KyFJVx7eK6sSXEyWqBk2nxMRDNYixIEN1V1EBSq
f+OwKK5Mxi04r38+Qog8z03/t/u6CfAOWVmi+MdrD1VHXv/P7bnFlgRcLzKwK1QL
TSLBE1PrMmNNj0oRjByhMoI9tY5X6mRBqLyDhwIDAQABAoIBAGO++RmGO6D9CNAJ
4Bm52eKaK5UBiubOIR8NiNLLZb5qinRxg3eX35d7Wb2xzBLNwOFBWSl21trFncfY
4qY0s+C4ZYHYQ7Om/7nsFeQAYAOj1yJYj01TXf4NTsGGF2t+W8qxZlV0H6dCOLL0
U2YkUmRp4Le8eQVj6dyTcVaYNPxWQBnb9ZOEIEvEjeoO/DD7CCmt7LDCey9KrTQl
Avuc2nN6uRV1Wfm0P8conKPJtVdgzMvJujNdpz+bBDqwsqgeCICjs/hSCNO81VH3
DD7J0mG2OHqowOVqagoDHpBprHOUKxAeTs9I0KEL+hEI4zXCDL69+Xs6azuts733
zSOmwxkCgYEA25czfPVxxcK685LhaAvwbmzWHqNp07ytRNGf+Aww6OdgWkdgPy0n
20Gkg0HAqsxGcgZJk6cAkOy5hBLNHpHlGbeWFi+62lVNYUv3hAxumtiPyBMu7avE
ZQCTXND1H1f/2enRDJRxQsR8y/SX1ivmC5U6fx7hbpKxnXyRHnvSlk8CgYEA7VWp
hLNkn4AEaPPW0TknwKG40At/hjecX2zWAyZVt4ydDSeKgMEOUdmvGGlSCrefAl0n
PTfM9SdIDcO5OTa2wUayKLIsrb6TDnG6KXXN6z3HR3Q4qKJbG83eaMYDqqziPPV+
xzRVWShI3EGwkLczASmiYy+sEAT0OkxP59xTKUkCgYBgaGjFkukJfy4fJDxsNtmv
UX9MYkhjGrIjxbjq6UdL6dGGsVGTSxr1i0NUETkqg5bmFtaUybxY5GWqk6qUok8o
VE7DnN73Xn4jmnun8OFagHvXxnxTApeuFGueU2tbAIKmxJ3wXPfA7Y0w6kkDUbCl
IzZUe1VT+3mZgAgijxBsxwKBgQDNytiJ62/V6hBo3P6pPtEcdF6nb0DtpazfBaVw
572twaywqlermzsKeCIenbx49I1ZZGLQ72C2NpCA9vTWCn5fiyiSpyScp0ImZTDS
IIckctYoPDug5d7wdgtjeEfXp78osopyuwtCmu7Kpd8vLNt6J5raPI0K+vC22FL1
LpOhmQKBgQCFeU448fL87N1MjMyusi8wJ5MLcn+kHbLTtpskTpfQM2p3Cnp4oL+7
BI4AlXlKItV37rJIjZxQgLWhGoTZPplZaW4ooJCFJbazce5ua5fnsFS0oXhDN7uw
jaq+v5t8G6gFS09hEa4kz9O53t/7UGuQqh0Bxb0cJ9iNeAlhagvBDQ==
-----END RSA PRIVATE KEY-----'''# Base64编码的密文
base64_ciphertext = 'gYBBFZr5uSZ1KtSDNjfnwdFgJ99bZvu6fALrExo/L1ceUQiSHmVkL4HmV60vO90D80AzeoOgBVFCwH3cm+a23s45WlACTwhoAAAjZ6N6dH+pLQDOKYkIN45eZW6goCR8drDEIMLVyJL0hoTe/jB79IsAxY3xv+3cgJTg78j9liSorSXJlKqlWMwzSfdK6HRagJhJGq9QHJZJndJmvuZ6vzJf+6Nfvu7qgzdfzvHX+2u+KudrD/sTzKnEIajU6jZnROupAgb9agDYp2wANL9ORpMM9WCgzEG225XQ+cNPQJHutJoAIjBvc/WBSHs0As718OYpPQrLWaGeIVIn1sjU7w=='# 解析私钥
private_key = rsa.PrivateKey.load_pkcs1(private_key_data.encode())# 解码Base64编码的密文
ciphertext = base64.b64decode(base64_ciphertext)# 使用私钥解密密文
plaintext = rsa.decrypt(ciphertext, private_key)
print("解密后的明文:", plaintext.decode('utf-8'))# 输出:
# 解密后的明文: {"status": "1", "state": "success", "data": [{"value": "5030"}, {"value": "9161"}, {"value": "6942"}, {"value": "6932"}, {"value": "3421"}, {"value": "3035"}, {"value": "8875"}, {"value": "5787"}, {"value": "2007"}, {"value": "3938"}]}
第57题 返回数据加密第三弹
此题仍是一个返回数据加密,根据调用堆栈可以找到加密位置:
进入 I 函数查看:
可以看到,这是一个 AES 加密,不过根据调试,这不是一个标准 AES 加密,因为传入的 key 是 8 字节的,而标准 AES 加密的密钥必须是 16,24 或 32 字节的,此时只能扣代码或者补环境了。
可以注意到,这部分代码是一个 webpack 打包后的,将 script 标签内的代码复制到本地,然后修改 0x2: [function(P, n, o) { 后面的部分内容,如下:
删除 ajax 部分的代码,并在上面添加了测试输出代码,运行后结果如下:
可以看到,成功打印了对应内容, 这样就可以通过暴露全局变量的方式,将次结果保存下来,在文件首行添加如下内容:
global.result;
global.X = '__global_X';
global.l = '__global_l';
然后修改刚刚测试的地方如下:
最后在文件末尾打印:
console.log(global.result)
此时就可以在 python 中调用了:
import subprocess
import oswebpack57js = open('57webpack.js', 'r', encoding='utf-8')
jscode = webpack57js.read().replace('__global_X', 'BGsHfKJP').replace('__global_l', '7VR537hkBRLAfu0WRFny6y7U4IKEu/mSYqntvsauubCNoS4lKZ2zf8xXU5TOG1AHJfa0qaj8Ec2dPQRq3vFpgvPl9SrmAukLJcpsWBQ52WSsk3ZgqKMGLpdGrJZRKrM4Wnyb/Ub2kerUc7dDCNQpKORE/97ajxUTxA+UvlVsHUMv32DeR8PuHYspnVMF7IPpCF6vn91yjWUl9rSpqPwRzcaR1kqmkzbD8+X1KuYC6QsqjgSCWwVxCgZyCsaBG64el0asllEqszgyOr9NX7uLEdRzt0MI1CkoYdygg//70suPa+FJHQ5eMiTZT0oS92dhV0mt/UUQvSTs37RD3Vo4EA==')
with open('57_.js', 'w', encoding='utf8') as f:f.write(jscode)
result = subprocess.check_output(['node', '57_.js'])
print(result.decode())
os.remove('57_.js')
# 运行结果:
# {"status": "1", "state": "success", "data": [{"value": "3495\r"}, {"value": "7529\r"}, {"value": "8960\r"}, {"value": "238\r"}, {"value": "3033\r"}, {"value": "9569\r"}, {"value": "2520\r"}, {"value": "4727\r"}, {"value": "6179\r"}, {"value": "3153\r"}]}
第58题 勇敢牛牛不怕困难
查看请求可知,需获取 token,很容易找到 token 的来源:
可以看到,是一个 md5 加密,传入的参数是页码,经对比发现,这是一个标准 md5 加密,本地模拟即可:
import hashlibpage = 3
result = hashlib.md5(str(page).encode()).hexdigest()
print(result)
print(result[8:24])
# eccbc87e4b5ce2fe28308fd9f2a7baf3
# 4b5ce2fe28308fd9
第59题 脏数据
此题无任何加密,但是直接请求求和提交会显示错误,查看响应成功后相关处理的部分源码如下:
当 r===0x33 时,即页码为 51 页时,执行 i['data'][0x0]['value'] = '5734\x0d',也就是将 51 页的第一个值改为 5734,跳转至 51 页可以看到,第一个值确实是 5734:
但是我们请求的接口返回的第一值却是 5733:
所以总和是少了1
第60题 轻混url加密
该题是 url 加密,根据调用堆栈可以找到加密位置:
L 方法就是加密的方法,传入的参数是页码,可以看到,这是一个 AES 加密,但不是一个标准的 AES 加密,因为其密钥是 aiding88,只有 8 字节,而标准 AES 密钥需为 16,24或32字节,可以注意到这部分代码其实是在一个 script 标签中,而这个标签中的代码很明显是一个 webpack 打包,这里的处理方法类似 57 题,将 script 标签中的代码复制到本地,修改加密部分的代码如下做测试:
这里将 ajax 部分的代码删掉,添加图中打印,运行,结果如下:
将 page 改为2,再次运行测试,结果如下:
对比题目的接口,可以发现,结果不一样:
说明代码存在环境检测,这里我们可以先搜索 try 关键字,因为这里是改变程序流程的一个点,搜索后发现如下环境检测点:
将这几个 try catch 语句删掉,再次运行,结果就与题目接口中展示的一致了。然后将页码暴露全局使用即可。