
SpiderDemo第六题
一:抓包分析



翻页和查看堆栈信息,发现代码强混淆,没法单步调试
先使用xhr断住

二:数组对象分析
直接查看目前存在的数据对象


从上面能看到body数据以及关键信息
1 | { "pageNumber": 1, "challengeType": "jsvmp_challenge", "authToken": "bkkW/CQe/DzxrZBA39sCWzujmUeTT4vNxQ3/Jf75al34+4rjCHHWzVIvErP1ASawrLTjO2TjogxbBiYOLI4wgwKlpQ2raAnMpQNESaEHjI/f4o77lY33+TWuH9Qjd1uMdxArEWv+t65HgP/9BNggOuV0W+m7w/WnBYtp0O9Oy00=", "timestamp": 1777988368, "fingerprint": { "userAgent": "SA6K0D7HCozqkkMhZNvWd4EkywEaFTNmHGfuqQ+KhLLxhBHEFf5q1G7CQ76QmKUEGb4mUcYTCbU3csgCZX17Efss9oI04jVFFM7naINVIsGoBbOtJ2j/RQOfY3qZFkdy30VS/p+SFyP1msMvKJUW4U/LuzYVDvBg19rd44ECzEs=", "language": "zh-CN", "screenResolution": "1920x1080", "colorDepth": "24", "timezoneOffset": -480, "cookiesEnabled": true, "plugins": "PDF Viewer, Chrome PDF Viewer, Chromium PDF Viewer, Microsoft Edge PDF Viewer, WebKit built-in PDF", "sessionStorage": true, "javaEnabled": false }} |
对比api请求的Payload
1 | •ü••••jsvmp_challenge•¬•ERY56cchTe0cL3mMXzwusXMCZPtZadL/iCGgu2MQXdedIKYXo9RiFEHNiQnC2GIuhXFcTeI14EKsPgxA6gNCVR+SX4N/WzIABScRLX6sPxhbU0LREL+/1TigeNU4kmCca6h2rDMf5ZSqMPfF4GVxiYG3RLPK0A6OcfutUsIWU98= ®åçÏ•*±•¬•DovL5XRHGnj83tLEfDROJ5FsYg4LgRGXQ1xqQj0EmM5aZ+RiVfMA05l0K46JbPm2zVhDOofT0pYD0Qfcjyu/NxN1Oq7nHVNdS0yFPqWKb3hMl1FYn3eed9n0x++VTjMKnpW5pb3UBMS6nZF6iWsGbmphKSx9b3mseZKgQnJRNk0=••zh-CN•1920x1080"•24( 0•:bPDF Viewer, Chrome PDF Viewer, Chromium PDF Viewer, Microsoft Edge PDF Viewer, WebKit built-in PDF@•H |
是不是大致一致,转换body中的513位的数组

发现也是这个值,直接protobuf反序列化一下
1 | import blackboxprotobuffrom loguru import logger# 前5位是空格及长度# Uint8Array的长度位513 body array = [0, 0, 0, 1, 252, 8, 3, 18, 15, 106, 115, 118, 109, 112, 95, 99, 104, 97, 108, 108, 101, 110, 103, 101, 26, 172, 1, 87, 97, 106, 75, 110, 110, 114, 79, 89, 55, 80, 113, 84, 105, 120, 101, 55, 56, 113, 118, 56, 83, 86, 82, 47, 49, 107, 47, 121, 57, 97, 67, 122, 51, 111, 108, 114, 66, 69, 81, 106, 71, 108, 67, 70, 76, 103, 55, 49, 89, 117, 80, 49, 83, 110, 114, 78, 109, 65, 104, 89, 77, 120, 102, 104, 53, 51, 82, 86, 114, 87, 83, 81, 71, 90, 57, 69, 54, 54, 88, 85, 97, 76, 104, 83, 106, 50, 65, 78, 49, 112, 122, 79, 118, 114, 115, 83, 57, 76, 47, 101, 88, 43, 48, 117, 88, 82, 89, 118, 69, 81, 114, 72, 82, 88, 73, 43, 51, 81, 77, 74, 83, 89, 110, 98, 49, 70, 77, 80, 84, 43, 104, 88, 57, 115, 65, 72, 67, 74, 55, 49, 112, 119, 106, 49, 116, 105, 98, 85, 90, 74, 97, 65, 88, 113, 105, 85, 49, 88, 103, 51, 85, 88, 75, 117, 104, 83, 89, 111, 55, 103, 61, 32, 209, 160, 232, 207, 6, 42, 177, 2, 10, 172, 1, 78, 82, 106, 83, 114, 57, 75, 122, 70, 121, 97, 73, 90, 108, 113, 52, 67, 48, 79, 50, 82, 84, 74, 98, 99, 66, 78, 115, 54, 97, 55, 73, 79, 119, 116, 120, 54, 101, 85, 72, 56, 114, 89, 80, 84, 87, 79, 114, 84, 104, 67, 97, 105, 78, 55, 81, 88, 106, 107, 119, 69, 115, 48, 85, 73, 56, 117, 82, 87, 108, 121, 112, 79, 114, 103, 116, 76, 89, 118, 112, 76, 50, 99, 99, 49, 69, 97, 71, 83, 75, 69, 101, 69, 100, 121, 65, 83, 101, 70, 72, 98, 114, 50, 118, 56, 51, 77, 81, 77, 51, 75, 112, 100, 85, 74, 109, 53, 67, 54, 68, 79, 77, 119, 100, 88, 73, 69, 56, 76, 86, 47, 106, 79, 110, 120, 83, 115, 106, 67, 120, 99, 47, 66, 122, 108, 70, 87, 78, 118, 54, 99, 101, 82, 110, 47, 79, 121, 115, 112, 111, 101, 117, 119, 65, 78, 110, 122, 78, 102, 86, 85, 61, 18, 5, 122, 104, 45, 67, 78, 26, 9, 49, 57, 50, 48, 120, 49, 48, 56, 48, 34, 2, 50, 52, 40, 32, 48, 1, 58, 98, 80, 68, 70, 32, 86, 105, 101, 119, 101, 114, 44, 32, 67, 104, 114, 111, 109, 101, 32, 80, 68, 70, 32, 86, 105, 101, 119, 101, 114, 44, 32, 67, 104, 114, 111, 109, 105, 117, 109, 32, 80, 68, 70, 32, 86, 105, 101, 119, 101, 114, 44, 32, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 69, 100, 103, 101, 32, 80, 68, 70, 32, 86, 105, 101, 119, 101, 114, 44, 32, 87, 101, 98, 75, 105, 116, 32, 98, 117, 105, 108, 116, 45, 105, 110, 32, 80, 68, 70, 64, 1, 72, 0]# 解析protobuf数据original_data, message_type = blackboxprotobuf.decode_message(bytes(array[5:]))logger.info(f'序列化结果==>>{original_data}')logger.info(f'序列化类型==>>{message_type}') |

观察序列化的结果
1 | {'1': 3,'2': b 'jsvmp_challenge','3': b 'WajKnnrOY7PqTixe78qv8SVR/1k/y9aCz3olrBEQjGlCFLg71YuP1SnrNmAhYMxfh53RVrWSQGZ9E66XUaLhSj2AN1pzOvrsS9L/eX+0uXRYvEQrHRXI+3QMJSYnb1FMPT+hX9sAHCJ71pwj1tibUZJaAXqiU1Xg3UXKuhSYo7g=','4': 1777995857,'5': {'1': b 'NRjSr9KzFyaIZlq4C0O2RTJbcBNs6a7IOwtx6eUH8rYPTWOrThCaiN7QXjkwEs0UI8uRWlypOrgtLYvpL2cc1EaGSKEeEdyASeFHbr2v83MQM3KpdUJm5C6DOMwdXIE8LV/jOnxSsjCxc/BzlFWNv6ceRn/OyspoeuwANnzNfVU=','2': b 'zh-CN','3': {'6': 3474580101544292921},'4': b '24','5': 32,'6': 1,'7': b 'PDF Viewer, Chrome PDF Viewer, Chromium PDF Viewer, Microsoft Edge PDF Viewer, WebKit built-in PDF','8': 1,'9': 0}} |
可以看到加密的只有
1 | RQ2uOoeBESxr00dfMtXLYY5JTo9xivZeqw4QntFCMm4O8aI0C8wg3H3Ireh7/v+24P+hDAO6awsxrlS5mrBVh1C0psrUrYxPuCDo4LedmvH0o2zP0gC451OKD2IuuLWJ33i7IlwCuZY21R6C47T0PzZrsy3tjGPr6KaqsTJW+ro=CajsmTe3fPFGjxG1nZLHVFpuDVJ4JhyQPXQvomCuYiPX9o0BGK90/enrp4GD8ando7seUUbk8z6lCqUEeE5rcPemL9SPZ7lVLmAnVpNrt6b4B6vHd9sJZypgGmUBBD+sPt+EgOMdprsuOIOxjqOPgZFgzm+rW68gygfHIciKzS8= |
三:加密参数分析
加密长度

长度都为172
直接AI分析一波

AI分析为RSA加密,那么直接全局搜索RSA的关键字encrypt和setPublicKey



断住后重新请求

可以看到已经断住,继续走

看到断到加密位置了,字符加密有点像base64

可以看到实际为base64加密的拼接字符,然后Rsa加密获得结果,直接交给AI帮我们生成python代码

1 | import rsaimport base64# 你的公钥 PEM 字符串PUBLIC_KEY_PEM = """-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCHshK1NixnImx08tq3V6VjvfwkARCOJbnPMZr4Ogh16ORkC45CFUB+2gu+IN2vjH3oJaY2e/6y0shD2fURMB3urxKE3gewRZMm5/T7noAXs4DJTOxJGwAJFHFfd9Zp73vXEcac4DKrvItOkjLx9P5JkA3947brgsMObYGdG3HOdwIDAQAB-----END PUBLIC KEY-----"""def rsa_encrypt(plaintext: bytes) -> str: # 从 PEM 格式加载公钥 pub_key = rsa.PublicKey.load_pkcs1_openssl_pem(PUBLIC_KEY_PEM.encode()) # 加密(rsa 库自动使用 PKCS#1 v1.5 填充) ciphertext = rsa.encrypt(plaintext, pub_key) # 返回 Base64 编码的密文 return base64.b64encode(ciphertext).decode('utf-8')# 示例if __name__ == "__main__": message = b'MTc3Nzk5MzY5N19qc3ZtcF9zZWNyZXRfa2V5XzIwMjQ=' encrypted = rsa_encrypt(message) print(encrypted) |

对比输出长度,也是172

后续执行能看到另一个值是user-agent加密实现


继续执行后可以看到加密值实际的位置

四:函数调用
经过上面的分析,目前的主要就是拼接出protobuf序列化的结果,在反序列化拼接出请求中的protobuf类型。核心代码如下
1 | timestamp = int(time.time())auth_token = rsa_encrypt(base64.b64encode(f'{timestamp}_jsvmp_secret_key_2024'.encode('utf-8')).decode('utf-8'))ua = rsa_encrypt("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)")original_data = { '1': i, '2': b'jsvmp_challenge', '3': auth_token.encode(), '4': timestamp, '5': { '1': ua.encode(), '2': b'zh-CN', '3': { '6': 3474580101544292921 }, '4': b'24', '5': 32, '6': 1, '7': b"PDF Viewer, Chrome PDF Viewer, Chromium PDF Viewer, Microsoft Edge PDF Viewer, WebKit built-in PDF", '8': 1, '9': 0 }}form_data = bytes(blackboxprotobuf.encode_message(original_data, message_type))response = requests.post(url, headers=headers, data=bytes([0, 0, 0, 1, 252]) + form_data) |
执行结果如下所示,获得的是protobuf数据

解密,直接通过binascii.hexlify转换成16进制,然后剥离 gRPC 帧头。也就是从第10为开始取
1 | def hexlify(response): """ 直接通过binascii.hexlify转换成16进制,然后从第10位开始取,因为前面10位都是空的,是剥离 gRPC 帧头 @param response: @return: """ response = binascii.hexlify(response.content).decode('utf-8') byte_data = bytes.fromhex(response[10:]) data = blackboxprotobuf.protobuf_to_json(byte_data) return data |


替换掉所有的<span class=\'context_kw{i}\'></span>将它作为一个空字符
能得到 _673、1_17、_054、_686、6_67、51_6、617_、4_24、8_74、9_96

第二页的数字为:8673、1517、1054、6686、6967、5156、6178、4424、8674、9596
结合转换的base64值58,57,53,56,61,57,58,54,56,57能得出
1 | 8 ---> 585 ---> 571 ---> 536 ---> 569 ---> 618 ---> 584 ---> 54 |
得出的结论是,奇数减52,偶数减50得到缺值
1 | def get_data(response_data): """ 获取替换后的实际值 @param response_data: @return: """ response = json.loads(response_data[0]) html = response['6'] for i in range(10): html = html.replace(f"<span class='context_kw{i}'></span>", '_') html_list = html.split(' ') jiem = base64.b64decode((response['7']).encode('utf-8')).decode('utf-8') jiem = jiem.split(',') numbers = [int(html_list[i].replace('_', f'{int(jiem[i]) - 50}')) if int(jiem[i]) % 2 == 0 else int( html_list[i].replace('_', f'{int(jiem[i]) - 52}')) for i in range(len(jiem))] return numbers |

更多内容也在公众号更新:码字的秃猴

本文是原创文章,采用CC BY-NC-SA 4.0协议,完整转载请注明来自码字的秃猴





