一、前言介绍
大家有没有想过,每次在网上注册账号或者登录时,那些不使用滑块验证码,也不依赖HTTP协议的验证码是如何识别我们是人而不是机器的呢?这些验证码可能只需要你简单地进行识别操作,例如输入一些文字或数字。但你可曾想过,这些看似简单的验证背后,蕴含着怎样的科技奥秘?在本文中,我们将揭开非HTTP验证码的神秘面纱,一起探索它的工作原理,了解它是如何巧妙地识别真人身份的。在这个有趣的旅程中,我们将一窥技术背后的精妙,带你进入一个充满挑战和创新的数字世界。快来和我一起探索吧!
二、网站分析
1、从某APP中抓包分析用户登录接口,发现登录接口为webview,将登录接口url使用浏览器打开分析,截图如下:
2、打开浏览器开发者工具,然后输入手机号后,点击发送验证码,请求截图如下所示:
3、浏览器Network中查看请求包,我们并没有发现http请求接口的验证码,只发现了疑似验证码的请求体和响应体,截图如下:
总结:此处我们发现response的返回内容进行了加密,加密参数为encrypt_body。我们再分析下request请求体,结果发现加密参数也是encrypt_body。这里我们初步可以确定,请求入参和response出参使用的是一套加密算法,接下来我们进入逆向分析环节吧。
三、逆向分析
1、通过Initiator进行调试分析,过滤无限debugger模式,进行断点分析截图如下所示:
2、这里我们定位到了请求体加密前参数,先暂时忽略。我们紧接着往下继续分析,截图如下:
3、此处我们查看返回参数s,可以确定是请求体加密后的参数,截图如下:
总结:虽然前面简单分析过,请求体和响应体的encrypt_body使用的是一套加密算法。那么我们先还原请求体加密,再拿请求体加密的对称加密算法进行解密,就可以判断我们前面的猜测是否正确。通过断点分析我们发现请求体加密使用了aes-ecb模式,AES是一种对称加密算法,这意味着加密和解密使用相同的密钥。接下来我们进入算法还原环节,来验证下我们的猜测吧。
四、算法还原
1、使用Python还原JS加密算法,还原后的完整加密算法如下:
# -*- coding: utf-8 -*-
# -------------------------------
# @author : TheWeiJUn
# @time : 2024.08.04 20:58:33
# -------------------------------
from Crypto.Cipher import AES
import json
import base64
from Crypto.Util.Padding import pad
def encrypt(e, n):
if e and n:
if not isinstance(e, str):
e = json.dumps(e, separators=(",", ":"))
r = 'utf-8'
key = n.encode(r) # Assuming n is a string that needs to be encoded to bytes
data = e.encode(r)
cipher = AES.new(key, AES.MODE_ECB)
padded_data = pad(data, AES.block_size) # Pad the data to be a multiple of block size
cipher_text = cipher.encrypt(padded_data) # Encrypt the padded data
s = base64.b64encode(cipher_text).decode('utf-8')
return s
if __name__ == '__main__':
# Example usage:
e = {"phone": "xxxx", "countryCode": "CN", "type": "login"}
n = "1234567890123456" # Example key (must be 16 bytes for AES-128)
result = encrypt(e, n)
print(result)
2、输出Python加密后的参数s同浏览器中生成的加密参数s进行比对,截图如下:
总结:经过对比我们发现,两个参数字符串完全一致。接下来我们使用前面分析的方法对response加密体参数encrypt_body试着进行对称解密还原。截图如下所示:
3、解密还原失败,此处我怀疑response加密参数encrypt_body应该是做了特殊处理,经过断点分析后,果真如我所言,编辑修改后的decrypt解密代码如下:
# -*- coding: utf-8 -*-
# -------------------------------
# @author : TheWeiJUn
# @time : 2024.08.04 20:58:33
# -------------------------------
from Crypto.Cipher import AES
import json
import base64
from Crypto.Util.Padding import pad, unpad
def encrypt(e, n):
if e and n:
if not isinstance(e, str):
e = json.dumps(e, separators=(",", ":"))
r = 'utf-8'
key = n.encode(r) # Assuming n is a string that needs to be encoded to bytes
data = e.encode(r)
cipher = AES.new(key, AES.MODE_ECB)
padded_data = pad(data, AES.block_size) # Pad the data to be a multiple of block size
cipher_text = cipher.encrypt(padded_data) # Encrypt the padded data
s = base64.b64encode(cipher_text).decode('utf-8')
return s
def decode_base64(t):
# Add padding to make the length a multiple of 4
t = t + '=' * (4 - len(t) % 4)
# Replace URL-safe characters with standard Base64 characters
t = t.replace('-', '+').replace('_', '/')
# Decode the Base64 string
return base64.b64decode(t)
def decrypt(e, n):
if e and n:
# Decode the Base64 encoded input
cipher_text_bytes = decode_base64(e)
# Define the encoding and decoding formats
a = 'utf-8'
key = n.encode(a) # Convert key to bytes
# Create the cipher object for decryption
cipher = AES.new(key, AES.MODE_ECB)
padded_data = cipher.decrypt(cipher_text_bytes) # Decrypt the data
# Remove padding
data = unpad(padded_data, AES.block_size)
# Parse the JSON data
return json.loads(data.decode(a))
if __name__ == '__main__':
n = "1234567890123456" # Example key (must be 16 bytes for AES-128)
e = "encrypt_body"
result = decrypt(e, n)
print(result)
4、对加密参数进行padding填充及对特殊符号进行替换处理后,即可进行对称解密操作,解码后的截图如下所示:
5、对还原后的响应体graph参数进行特殊处理后,进行base64decode,相关代码和解码后保存的字节流如下:
6、到这里基本就结束了,接下来进行验证码识别还原,这个相对来说比较简单,我们这里使用ddddocr,编辑代码后还原验证码如下所示: