一、泡泡聊天
1. 逆向前置
1.1 逆向目标
- 目标:账号密码登录
- 版本:v1.7.4
- 时间:2024-06-10
1.2 简要分析
- 关于泡泡聊天比较重要的就是抓包证书以及360的壳,直接抓包结果如下:

- 这是做了服务端证书的校验,所以我们需要拿到证书并且导入到charles,具体步骤如下:
- 获取 bks 或 p12证书 文件
- 获取证书相关密码
- 将证书导入到charles,可以实现抓包(bks格式需要转换p12格式)
- 用requests发送请求时,携带证书去发送请求
2. 逆向实现
2.1 获取证书&密码
- 使用frida脚本进行hook相应的证书与密码,这是上述步骤的前两步;

- 可以看到加载了不止一个证书,这是正常的,但是只有一个有密码,所以无疑就是这个了;
- 那么它的证书就在asset目录下的bks文件;那么此时我们并不知道具体是哪一个bks文件,所以在此直接保存当前获取到的流文件并且保存下来;
Java.perform(function () {
var KeyStore = Java.use("java.security.KeyStore");
var String = Java.use("java.lang.String");
KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (inputStream, v2) {
var pwd = String.$new(v2);
console.log('\n--------------------------------------------------------------')
console.log("密码:" + pwd, this.getType());
if (this.getType() === "BKS") {
var myArray = new Array(1024);
for (var i = 0; i < myArray.length; i++) {
myArray[i] = 0x0;
}
var buffer = Java.array('byte', myArray);
var file = Java.use("java.io.File").$new("/sdcard/Download/paopao-" + new Date().getTime() + ".bks");
var out = Java.use("java.io.FileOutputStream").$new(file);
var r;
while ((r = inputStream.read(buffer)) > 0) {
out.write(buffer, 0, r);
}
console.log("save success!")
out.close()
}
var res = this.load(inputStream, v2);
return res;
};
});
// frida -U -f com.paopaotalk.im -l 2.hook_save.js
- 在进行这一步之前需将app赋予可读写的权限;随后去观察是否保存成功;

- 至此证书以及密码我们获取到了;后续就需要导出证书并且导入到charles;
2.2 导出&导入证书
- 扩展:有时进行hook时会失败,提示找不到类等,是因为有壳导致,可以加上延迟;
- 此时我们需要将bks证书转换成p12的格式,才支持;使用portecle-1.11工具;








2.3 代码实现
- 具体逻辑比较简单,脱壳使用frida-dumpdex大致实现如下:
frida-dexdump -U -f 包名
# File:01-泡泡聊天登录实现.py
# Author:下雨天
# Date:2024-06-10 21:50
import hashlib
import random
from requests_pkcs12 import get, post
import json
import uuid
import urllib3
urllib3.disable_warnings()
def md5_hash(password):
md5_obj = hashlib.md5()
md5_obj.update(password.encode('utf-8'))
md5_hash = md5_obj.hexdigest()
return md5_hash
def double_md5_hash(password, openId):
# 第一次MD5加密
md5_obj = hashlib.md5()
md5_obj.update(password.encode('utf-8'))
first_md5_hash = md5_obj.hexdigest()
# 将第一次MD5加密结果与openId和"MOSGRAM"拼接起来
combined_string = first_md5_hash + openId + "MOSGRAM"
# 第二次MD5加密
md5_obj = hashlib.md5()
md5_obj.update(combined_string.encode('utf-8'))
second_md5_hash = md5_obj.hexdigest()
return second_md5_hash
def login():
password = "aa12121323"
openId = "008615222225555"
timestamp = random.randint(100, 999)
headers = {
"bundle_id": "com.paopaotalk.im",
"version": "1.7.4",
"timestamp": str(timestamp),
"sign": md5_hash(str(timestamp)),
"app_id": "qiyunxin",
"Accept-Language": "zh-CN",
"package": "com.paopaotalk.im",
"Content-Type": "application/json; charset=UTF-8",
"Host": "api.vvchat.im",
"User-Agent": "okhttp/4.8.1"
}
url = "https://api.vvchat.im/userservices/v2/user/login"
device_id = md5_hash(str(uuid.uuid4()).replace("-", ""))
data = {
"device_type": "app",
"username": openId,
"password": double_md5_hash(password, openId),
"device_id": device_id,
"device_name": "Google Pixel 2 XL",
"device_model": "Pixel 2 XL"
}
data = json.dumps(data, separators=(',', ':'))
response = post(url, headers=headers, data=data,
pkcs12_filename='Client.p12',
pkcs12_password='111111',
verify=False
)
print(response.text)
print(response)
if __name__ == '__main__':
login()
评论 (0)