课程培训
扫码咨询


专注于SRC漏洞挖掘、红蓝对抗、渗透测试、代码审计JS逆向,CNVD和EDUSRC漏洞挖掘,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。加内部圈子,文末有彩蛋(课程培训限时优惠)。
文章作者:988hvjnkj
文章来源:https://www.freebuf.com/articles/mobile/453346.html
01
一、原始抓包
1.1抓包


1.2 抓包结果分析
•
核心发现:请求参数仅存在1个——参数d,且整个数据包已被加密,无法直接读取原始业务数据(如账号、密码),需通过逆向分析还原加密流程。
二、逆向分析:定位核心代码与参数
2.1 登录接口定位
通过搜索登录相关关键词(如“login/phone”),定位到APP源码中登录功能的核心常量与请求发送方法:
--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
// 登录接口URL常量
public static final String f6071I4 = "login/phone";
// 登录请求发送方法:str=账号,str2=原始密码,httpCallBack=回调对象
public static void m10245e(String str, String str2, HttpCallBack httpCallBack) {
HashMap<String, String> hashMap = C2165k.getHashMap();
hashMap.put("account", str); // 存入账号参数
hashMap.put("pwd", C2428c.m6288d(str2)); // 存入经加密处理的密码
// 发送POST请求:传入接口URL、参数集合、回调函数
BaseRequest.post(C2165k.getHostUrlV2(InterfaceC2418c.f6071I4), hashMap, httpCallBack.mCallback);
}
2.2 逆向核心目标
需解决3个关键问题,逐步还原加密流程:
1、C2165k.getHashMap():获取的通用参数集合包含哪些内容?
2、C2428c.m6288d(str2):该方法对原始密码(str2)进行了何种加密处理?
3、BaseRequest.post():如何利用传入的hashMap构建最终加密参数d?
三、关键参数解析:通用hashMap集合
3.1 C2165k.getHashMap()方法源码
该方法用于封装APP所有请求的通用参数,源码如下:
--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
public static HashMap<String, String> getHashMap() {
HashMap<String, String> hashMap = new HashMap<>();
// 未登录状态下,uid和token为空(登录后会添加)
if (C5772a.m10514g() != null && !TextUtils.isEmpty(C5772a.m10509b()) && !TextUtils.isEmpty(C5772a.m10513f())) {
hashMap.put(InterfaceC2417b.f5966a, C5772a.m10509b()); // 添加uid(未登录时无值)
hashMap.put("token", C5772a.m10513f()); // 添加用户凭证token(未登录时无值)
}
// 添加极光推送ID(jPushId),推测用于推送服务(如广告、通知)
String registrationID = JPushInterface.getRegistrationID(App.getContext());
if (!TextUtils.isEmpty(registrationID)) {
hashMap.put("jPushId", registrationID);
InterfaceC2423h.f6552e.mo6554c("jpush_registrationID = " + registrationID, new Object[0]);
}
// 添加APP版本号
hashMap.put("version", C2108a.f5088e);
return hashMap;
}
3.2 未登录状态下的hashMap值获取(Hook脚本)
因未登录时uid和token为空,仅需获取jPushId和version,可通过Hook脚本直接捕获方法返回值:
--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
setTimeout(function() {
Java.perform(function() {
console.log("开始Hook C2165k.getHashMap()");
const KClass = Java.use("你的类名"); // 需替换为实际C2165k类名
KClass.getHashMap.implementation = function() {
const app = this.getHashMap(); // 执行原方法并获取返回值
console.log("原始通用hashmap:" + app);
return app;
};
});
}, 8000); // 延迟8秒执行,避免APP未加载完成

原始通用hashmap:{jPushId=100d8559086045b508a, version=4.8.3}
四、密码加密逻辑:C2428c.m6288d()方法解析
4.1 方法源码与核心问题
该方法用于处理原始密码,源码中存在关键错误,需结合逻辑分析修正:
--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
import java.security.Key;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class C2428c {
private static final String f6621a = "514345744E41596C4E41496C"; // 24字节3DES密钥(示例)
private static final String f6622b = "DESede/ECB/PKCS7Padding"; // 3DES加密配置(算法+模式+填充)
private static C2428c f6623c;
// 密码加密方法:调用m6299e()实现加密
public static String m6288d(String str) {
try {
return m6291i().m6299e(str, f6621a);
} catch (Exception e2) {
e2.printStackTrace();
return str; // 异常时返回原始密码(安全性缺陷)
}
}
// 单例模式获取C2428c实例,添加BouncyCastle加密提供者
public static C2428c m6291i() {
if (f6623c == null) {
synchronized (C2428c.class) {
if (f6623c == null) {
Security.addProvider(new BouncyCastleProvider());
f6623c = new C2428c();
}
}
}
return f6623c;
}
// 密钥创建方法(核心错误点)
private Key m6292j(String str) {
// 错误:SecretKeySpec需传入纯算法名("DESede"),而非含模式/填充的完整字符串
return new SecretKeySpec(str.getBytes(), f6622b);
}
private Key m6293k(byte[] bArr) {
// 同上述错误:算法参数传入错误
return new SecretKeySpec(bArr, f6622b);
}
// 加密核心逻辑:3DES加密+十六进制转换
public String m6299e(String str, String str2) throws Exception {
Cipher.getInstance(f6622b).init(1, m6292j(str2));
return m6296n(m6300f(str.getBytes(), str2.getBytes()));
}
// 字节数组转十六进制字符串
private String m6296n(byte[] bArr) {
return new String(m6295m(bArr));
}
private byte[] m6295m(byte[] bArr) {
byte[] bArr2 = new byte[bArr.length * 2];
for (int i2 = 0; i2 < bArr.length; i2++) {
byte b = (byte) ((bArr[i2] >> 4) & 15);
byte b2 = (byte) (bArr[i2] & 15);
int i3 = i2 * 2;
bArr2[i3] = (byte) (b < 10 ? b + 48 : b + 55);
bArr2[i3 + 1] = (byte) (b2 < 10 ? b2 + 48 : b2 + 55);
}
return bArr2;
}
// 执行3DES加密
private byte[] m6300f(byte[] bArr, byte[] bArr2) throws Exception {
Cipher cipher = Cipher.getInstance(f6622b);
cipher.init(1, m6293k(bArr2));
return cipher.doFinal(bArr);
}
}
4.2 代码错误分析(结合AI解读)
需从“原本意图→具体错误→后果→实际加密方式”四维度拆解:
1. 原本意图
实现标准3DES加密(Triple DES),配置如下:
•
算法:DESede(3DES)
•
模式:ECB(电子密码本模式)
•
填充:PKCS7Padding
•
密钥:24字节字符串(符合3DES密钥长度要求)
2. 具体错误
SecretKeySpec算法参数传入错误: 创建密钥时,需传入纯算法名("DESede"),但代码中传入了含模式/填充的完整字符串("DESede/ECB/PKCS7Padding")。SecretKeySpec仅负责定义密钥对应的算法,模式和填充是Cipher(加密器)的配置项,与密钥无关。
3. 错误后果
•
密钥初始化异常:虽Java加密框架可能容错(忽略模式/填充,提取"DESede"),但不符合规范,可能导致加密强度退化(如等效单DES)。
•
加密结果异常:标准3DES密文长度应为8字节整数倍(对应十六进制16字符整数倍),但代码加密"111111"后得到15字符结果(如
C3EAB353AA425226),长度异常。
4. 实际加密方式
仍为错误实现的3DES加密:核心算法基于3DES,但因密钥参数错误,流程不标准,安全性降低。
4.3 Python还原错误3DES加密逻辑
通过Python模拟代码中的错误加密流程,还原密码加密结果:
--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
import pyDes
import binascii
def java_like_hex_encode(byte_array):
"""模拟Java中m6295m()的字节数组转十六进制逻辑"""
hex_chars = []
for b in byte_array:
# 模拟Java有符号byte(pyDes返回无符号字节)
b_signed = b if b < 128 else b - 256
high = (b_signed >> 4) & 0x0F
low = b_signed & 0x0F
# 转十六进制字符(0-9→48-57,A-F→65-70)
high_char = chr(high + 48) if high < 10 else chr(high + 55)
low_char = chr(low + 48) if low < 10 else chr(low + 55)
hex_chars.append(high_char + low_char)
return''.join(hex_chars)
def encrypt_111111():
# 1. 明文:原始密码"111111"(UTF-8编码)
plaintext = "111111".encode('utf-8')
# 2. 密钥:对应Java中的f6621a(24字节)
key_str = "你的密钥"
key = key_str.encode('utf-8')
# 3. 初始化3DES加密器(ECB模式+PKCS7填充)
cipher = pyDes.triple_des(key, mode=pyDes.ECB, pad=None, padmode=2)
# 4. 执行加密(自动处理PKCS7填充)
ciphertext = cipher.encrypt(plaintext)
# 5. 模拟Java十六进制转换
encrypted_hex = java_like_hex_encode(ciphertext)
return encrypted_hex
# 测试:加密"111111"
result = encrypt_111111()
print("加密结果:", result) # 输出示例:C3EAB353AA425226(因错误实现,长度可能异常)
五、最终请求参数d构建:BaseRequest.post()方法解析
该方法是加密流程核心,负责将通用参数、密码、时间戳、签名整合为加密参数d,分两阶段解析:
5.1 阶段一:添加timestamp与signToken
1. 源码关键逻辑
--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
public static InterfaceC9173e post(String str, Map<String, String> map, InterfaceC9175f interfaceC9175f) {
// 日志:打印请求URL与原始参数
InterfaceC2423h.f6548a.mo6554c("Request " + str + "\nParameters " + map, new Object[0]);
// 1. 添加时间戳(timestamp):毫秒级时间戳,防重复请求
map.put(InterfaceC2421f.f6488f0, String.valueOf(System.currentTimeMillis()));
// 2. 添加签名(signToken):防数据篡改,通过C2434i.m6346d()生成
map.put(InterfaceC2421f.f6486e0, C2434i.m6346d(map));
// 3. 加密生成最终参数d:调用C2430e.m6301a()
Map<String, String> m6301a = C2430e.m6301a(map);
// 后续为HTTP请求头构建、请求发送逻辑(略)
return mo28839a;
}
2. signToken生成逻辑(C2434i.m6346d())
signToken通过SHA-512哈希+多步字符串处理生成,确保参数未被篡改,源码与步骤如下:
--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
public static String m6346d(Map<String, String> map) {
// 步骤1:将无序HashMap转为有序TreeMap(按Key自然排序,避免顺序影响哈希结果)
TreeMap treeMap = new TreeMap();
for (Map.Entry<String, String> entry : map.entrySet()) {
treeMap.put(entry.getKey(), entry.getValue() == null ? "" : entry.getValue());
}
// 步骤2:序列化TreeMap为字节数组
byte[] bytes = C2451f.m6453f(treeMap).getBytes();
try {
// 步骤3:计算SHA-512哈希值
MessageDigest messageDigest = MessageDigest.getInstance(MessageDigestAlgorithms.SHA_512);
messageDigest.update(bytes);
byte[] digestBytes = messageDigest.digest();
// 步骤4:多步处理哈希结果(m6343a→m6344b→m6345c→m6696r)
return C2480d0.m6696r(m6345c(m6344b(m6343a(digestBytes))));
} catch (Exception unused) {
return null;
}
}
3. 哈希结果处理四步曲
5.2 阶段二:加密生成参数d(C2430e.m6301a())
该方法是参数d的核心加密逻辑,需拆分为四步还原:
1. 源码核心片段
--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
public static Map<String, String> m6301a(Map<String, String> map) {
// 步骤1:TreeMap排序(同signToken生成,确保顺序一致)
TreeMap treeMap = new TreeMap();
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (true) {
String str2 = "";
if (!it.hasNext()) break;
Map.Entry<String, String> next = it.next();
String key = next.getKey();
if (next.getValue() != null) str2 = next.getValue();
treeMap.put(key, str2);
}
HashMap hashMap = new HashMap();
String m6281f = AbstractC2426a.m6281f(); // 步骤2:生成16位随机串(AES密钥)
try {
// 步骤3:RSA加密随机串(用Native层获取的RSA公钥)
String str = C2433h.m6337f(C2433h.m6340i(C2450e.m6420m().f6667k, C2450e.m6420m().f6668l), m6281f);
} catch (Exception e2) {
e2.printStackTrace();
str = "";
}
try {
// 步骤4:AES加密业务数据(用随机串做密钥,IV从Native层获取)
String m6277b2 = AbstractC2426a.m6277b(C2451f.m6453f(treeMap), m6281f, C2450e.m6420m().f6669m);
// 拼接:AES加密结果 + 空字符串 + RSA加密结果
StringBuilder sb2 = new StringBuilder();
sb2.append(m6277b2).append("").append(str);
// 编码处理:调用m6286d()(Base64),存入参数d
hashMap.put(App.get().getResources().getString(C2404f.p.request_parment), AbstractC2427b.m6286d(sb2.toString().getBytes()));
} catch (Exception e3) {
e3.printStackTrace();
}
return hashMap;
}
2. 四步还原参数d加密逻辑
步骤1:生成16位随机串(AbstractC2426a.m6281f())
用于作为AES加密的密钥(符合AES-128对16字节密钥的要求),源码如下:
--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
public static String m6281f() {
Random random = new Random();
StringBuffer stringBuffer = new StringBuffer();
// 字符池:大小写字母+数字(共62个字符)
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for (int i2 = 0; i2 < 16; i2++) {
// 随机选择字符,拼接为16位串
stringBuffer.append(chars.charAt(random.nextInt(62)));
}
return stringBuffer.toString();
}
步骤2:RSA公钥加密随机串
•
RSA参数来源:
f6667k(模,Module)和f6668l(公钥指数,Exponent)从Native层(C/C++)获取,通过NativeUtil.getNetSignModuleKey()和NativeUtil.getNetSignExponent()调用,提升反编译难度。•
公钥构建:通过
C2433h.m6340i()将模和指数转为RSAPublicKey:--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
public static RSAPublicKey m6340i(String str, String str2) {
try {
return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(new BigInteger(str), new BigInteger(str2)));
} catch (Exception e2) {
e2.printStackTrace();
return null;
}
}•
分段加密:因RSA加密长度限制,对16位随机串分段加密(每段≤117字节),结果转Base64:
--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
public static final String f6626a = "RSA/NONE/PKCS1Padding";
public static final String f6627b = "BC";
public static String m6337f(PublicKey publicKey, String str) throws Exception {
Cipher cipher = Cipher.getInstance(f6626a, f6627b);
cipher.init(1, publicKey);
byte[] bytes = str.getBytes("UTF-8");
int length = bytes.length;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int i2 = 0;
while (true) {
int i4 = length - i2;
if (i4 <= 0) {
byte[] byteArray = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
return new String(Base64.encode(byteArray, 2));
}
// 分段加密:超过117字节则截取,否则直接加密
byte[] doFinal = i4 > 117 ? cipher.doFinal(bytes, i2, 117) : cipher.doFinal(bytes, i2, i4);
byteArrayOutputStream.write(doFinal, 0, doFinal.length);
i2 += 117;
}
}
步骤3:Hook获取RSA参数(模+指数)
因RSA参数存于Native层,需通过Hook脚本在APP运行时捕获:
--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
Java.perform(function() {
const EncryptClass = Java.use("你的类名"); // 替换为实际加密类名
EncryptClass.a.implementation = function(map) {
console.log("======================================");
console.log(" 触发请求,获取RSA关键参数:");
// 1. 获取Native层的RSA模、指数、IV
const EClass = Java.use("你的类名"); // 替换为C2450e相关类名
const eInstance = EClass.m();
const eCls = EClass.class;
// 工具函数:获取类字段值
const getFieldValue = (fieldName) => {
try {
const field = eCls.getDeclaredField(fieldName);
field.setAccessible(true); // 突破访问权限
return field.get(eInstance);
} catch (e) {
return"获取失败:" + e.message;
}
};
// 获取RSA模(j)、指数(k)、AES IV(l/m)
const rsaModule = getFieldValue("j"); // RSA模数
const rsaExponent = getFieldValue("k"); // RSA指数
const aesIV1 = getFieldValue("l");
const aesIV2 = getFieldValue("m");
console.log(` RSA模数(j): ${rsaModule}`);
console.log(` RSA指数(k): ${rsaExponent}`);
console.log(` AES IV值(l): ${aesIV1}`);
console.log(` AES IV值(m): ${aesIV2}`);
// 2. 执行原方法,返回结果
const result = this.a(map);
console.log("======================================\n");
return result;
};
console.log(" RSA参数Hook脚本就绪!发起请求即可捕获");
});

步骤4:AES加密业务数据与Base64编码
•
AES加密:用步骤1生成的16位随机串做密钥,
f6669m(从Native层获取)做IV(在步骤三中的HOOK脚本里有获取IV的操作),加密序列化后的业务数据(TreeMap):--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
public static String m6277b(String str, String str2, String str3) throws Exception {
// 校验密钥:非空且长度为16字节(AES-128)
if (str2 == null) {
System.out.print("Key为空null");
return null;
}
if (str2.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
// 初始化AES密钥与加密器(CBC模式+PKCS5Padding)
SecretKeySpec secretKeySpec = new SecretKeySpec(str2.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(1, secretKeySpec, new IvParameterSpec(str3.getBytes()));
// 加密+Base64编码(调用m6286d())
return AbstractC2427b.m6286d(cipher.doFinal(str.getBytes()));
}•
Base64编码(AbstractC2427b.m6286d()):自定义Base64编码实现,将AES加密结果与RSA加密结果的拼接串转为最终参数d的值,核心是通过字符池(
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/)完成字节与字符串的映射。
六、整体加密流程总结
APP登录请求的完整加密链路如下:
1、参数准备:通过C2165k.getHashMap()获取通用参数(jPushId、version),添加账号(account)与3DES加密后的密码(pwd)。
2、添加防篡改与防重放参数:
加入timestamp(毫秒级时间戳),防重复请求。
生成signToken(SHA-512→多步字符串处理→MD5),防数据篡改。
3、生成参数d:
生成16位随机串(AES密钥)。
RSA加密随机串(用Native层公钥)。
AES加密业务参数(用随机串+Native层IV)。
拼接AES结果与RSA结果,Base64编码后作为参数d。
4、发送请求:
以参数d为核心,发起POST请求,服务器解密验证后响应。
02
26
SRC漏洞挖掘培训课程

1.课程价格目前是500(后面也会随着人数越多,涨价)🌟师傅们还可以上车补票,冲冲冲!
2.报名成功送知识星球一个,拉内部小圈子交流群+SRC直播通知群!✨
3.一周2节课程,直播+录播形式,课程内容大家可以看课表,目前是第一期,一次报名永久无限听课!❤️
4.目前是第一期课程,后面比如说开了二、三期,都是不用在花钱的!
5.上课结束后,会把视频录播+课件笔记一起打包发直播群!
6.哔哩哔哩SRC课程公开课,链接🔗直达:
https://space.bilibili.com/642258933
SRC课程详情🔎:学了一堆理论,还是挖不到漏洞?你缺的是实战!




最后也是希望大家都可以赚钱,找到好工作🎉



上课结束后,会把视频录播+课件笔记一起打包发直播群
「神农安全」知识星球目前已经累计2200+网络安全爱好者的加入!
后面也是小圈子做大起来了,师傅们也都喜欢看我文章,想着给大家教下src漏洞挖掘思路,所以自己花了很长时间做了✨课件和课表,都是纯自己手搓的,大家也可以看下课表的内容。


03
课程主打真实,一线SRC漏洞挖掘师傅是如何学习和挖掘SRC漏洞的,让你真正了解SRC漏洞挖掘,助力在岗人员和大学生的能力提升,掌握新的技能树,为下一次跳槽涨薪做好准备。本课程内容覆盖企业SRC、众测项目挖掘、护网HVV红蓝攻防技巧、CVE、CNVD、EDUSRC等平台通杀案例技巧挖掘方法。
本课程适合人群(光看不挖啥也不会)
1、想从0转行入行的大学生或自学者2、想从CTF比赛/Web或SRC进阶到项目实战的选手3、想参与项目/找工作/提高收入的转型者
1、课程价格真心实惠,绝不割韭菜2、两三百的课程价格让你体会大几千的培训课程内容3、带着大家从0到1,本人上课坚持手搓课件(实战案例+知识体系)4、拒绝使用PPT演讲模式(无实操,很枯燥)

讲师介绍
id:一个想当文人的黑客



04


内部圈子介绍(报课赠送)

圈子专注于更新src/红蓝攻防相关:
1、维护更新src专项漏洞知识库,包含原理、挖掘技巧、实战案例2、知识星球专属微信“小圈子交流群”3、微信小群一起挖洞4、内部团队专属EDUSRC证书站漏洞报告5、分享src优质视频课程(企业src/EDUSRC/红蓝队攻防)6、分享src挖掘技巧tips7、不定期有众测、渗透测试项目(一起挣钱)8、不定期有工作招聘内推(工作/护网内推)9、送全国职业技能大赛环境+WP解析(比赛拿奖)10、十个专栏会持续更新~提前续费有优惠,好用不贵很实惠11、每日内部资料分享,内部圈子资料1000+12、联系圈主获取:内部漏洞知识库+圈子使用手册+内部圈子交流群13、VX:routing_love,技术交流+疑问解决
内部圈子专栏介绍
知识星球内部共享资料截屏详情如下
(只要没有特殊情况,每天都保持更新)


05


















有需要的师傅们直接扫描文章二维码加入,然后要是后面群聊二维码扫描加入不了的师傅们,直接扫描文章开头的二维码加我(备注加群)


申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,
所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.


往期回顾













夜雨聆风