前言
同学在群中分享某虚拟定位轻松跑步法。
作为旁观者,萌生出模拟请求进行数据伪造来完成任务(老操作了,像骨灰盒自动参加赠楼这种)
某校园跑软件
其实要求还算可以,极限情况:50天、每天1公里5分钟;就是不太好坚持
分析思路
抓包,看请求是否有签名,没有就去第4步
检测安装包是否有壳,无壳就去第4步
脱壳
分析代码
伪造请求
等待正义的审判
一、先抓包(非重点,不过多赘述)
token 理论上是用户标识
appkey 肯定大部分时间为定值
sign MD5签名
二、查壳
360加固,一般 #(cos滑稽)
三、脱壳
这里使用这个工具:
下载32位版本安装包(因为,目标APP是32位的)
单击目标应用,开始脱壳
四、分析目标“源码”位置
打开脱壳后的dex文件所在目录,
使用MT管理器 的 dex编辑器++ 打开
搜索字符串 sign
只看目标应用的包
看包名:
entities 实体包,排除
enums 枚举包,排除
network.settings 网络设置包 且类名”MyInterceptor”与拦截相关, 大概率符合条件
初步查看源码,在请求头中添加sign,肯定是这里了
接下来,使用电脑进行具体签名流程分析了
五、分析代码逻辑
分析请看注释
public final class MyInterceptor implements Interceptor {
private static final String APPKEY = "389885588s0648fa";
private static final String APPSECRET = "56E39A1658455588885690425C0FD16055A21676";
private static final Charset ChartSet_UTF8 = Charset.forName("UTF-8");
public static final Companion Companion = new Companion((DefaultConstructorMarker) null);
public MyInterceptor() {
}
static {
}
public Response intercept(Interceptor.Chain chain) {
boolean z;
String str;
Request.Builder newBuilder;
Request.Builder addHeader;
Request.Builder addHeader2;
Request.Builder newBuilder2;
Request.Builder addHeader3;
Request.Builder addHeader4;
Request.Builder addHeader5;
String str2;
String str3;
boolean z2;
String str4;
String str5;
String str6;
String str7;
Set queryParameterNames;
Interceptor.Chain chain2 = chain;
Intrinsics.checkParameterIsNotNull(chain2, "chain");
Request request = chain.request();
// 注意,TreeSet是有序集合,且默认为正序排列,即{a, b, c, d [,...]}
TreeSet treeSet = new TreeSet();
Request request2 = null;
HttpUrl url = request != null ? request.url() : null;
if (!(url == null || (queryParameterNames = url.queryParameterNames()) == null)) {
treeSet.addAll(queryParameterNames);
}
// 待签名字符串
StringBuilder sb = new StringBuilder();
Iterator it = treeSet.iterator();
// 开始迭代所有请求参数
while (true) {
z = false;
if (!it.hasNext()) {
break;
}
// str8 为参数名
String str8 = (String) it.next();
// 获取参数名对应的值
List queryParameterValues = url != null ? url.queryParameterValues(str8) : null;
if (queryParameterValues != null && (true ^ queryParameterValues.isEmpty())) {
// 参数值非空
// 取参数值列表第一个
String str9 = (String) (url != null ? url.queryParameterValues(str8) : null).get(0);
if (!TextUtils.isEmpty(str9)) {
// str8 参数名, str9 参数值
// str9非空,追加str8 str9在sb之后
sb.append(str8);
sb.append(str9);
}
}
}
// 追加APPKEY
sb.append("389885588s0648fa");
// 追加APPSECRET
sb.append("56E39A1658455588885690425C0FD16055A21676");
if (request.body() != null) {
BufferedSink buffer = new Buffer();
RequestBody body = request.body();
if (body != null) {
body.writeTo(buffer);
}
Charset charset = ChartSet_UTF8;
RequestBody body2 = request.body();
MediaType contentType = body2 != null ? body2.contentType() : null;
if (contentType != null) {
// 追加请求体
sb.append(buffer.readString(contentType.charset(charset)));
}
}
// 同sb
String sb2 = sb.toString();
Intrinsics.checkExpressionValueIsNotNull(sb2, "signStr.toString()");
CharSequence charSequence = sb2;
if (!(charSequence == null || charSequence.length() == 0)) {
// 本级语句块替换一些字符为空字符,即删除部分字符,这将导致参与MD5运算的字符串有所差异
// z2 为是否发生过替换操作
// 删除空格
if (StringsKt.contains$default(charSequence, " ", false, 2, (Object) null)) {
str3 = StringsKt.replace$default(sb2, " ", "", false, 4, (Object) null);
z2 = true;
} else {
str3 = sb2;
z2 = false;
}
// 删除~
if (StringsKt.contains$default(str3, "~", false, 2, (Object) null)) {
str4 = StringsKt.replace$default(str3, "~", "", false, 4, (Object) null);
z2 = true;
} else {
str4 = str3;
}
// 删除!
if (StringsKt.contains$default(str4, "!", false, 2, (Object) null)) {
str5 = StringsKt.replace$default(str4, "!", "", false, 4, (Object) null);
z2 = true;
} else {
str5 = str4;
}
// 删除(
if (StringsKt.contains$default(str5, "(", false, 2, (Object) null)) {
str6 = StringsKt.replace$default(str5, "(", "", false, 4, (Object) null);
z2 = true;
} else {
str6 = str5;
}
// 删除)
if (StringsKt.contains$default(str6, ")", false, 2, (Object) null)) {
str7 = StringsKt.replace$default(str6, ")", "", false, 4, (Object) null);
z2 = true;
} else {
str7 = str6;
}
// 删除'
if (StringsKt.contains$default(str7, "'", false, 2, (Object) null)) {
sb2 = StringsKt.replace$default(str7, "'", "", false, 4, (Object) null);
z = true;
} else {
z = z2;
sb2 = str7;
}
if (z) {
sb2 = URLEncoder.encode(sb2, "utf-8");
Intrinsics.checkExpressionValueIsNotNull(sb2, "URLEncoder.encode(dealStr,\"utf-8\")");
}
}
if (z) {
StringBuilder sb3 = new StringBuilder();
// 使用替换结果 sb2 计算MD5
String encodeByMD5 = MD5Digest.Companion.encodeByMD5(sb2);
if (encodeByMD5 == null) {
str2 = null;
} else if (encodeByMD5 != null) {
// 转换为大写
str2 = encodeByMD5.toUpperCase();
Intrinsics.checkExpressionValueIsNotNull(str2, "(this as java.lang.String).toUpperCase()");
} else {
throw new TypeCastException("null cannot be cast to non-null type java.lang.String");
}
sb3.append(str2);
// MD5追加编码
sb3.append("encodeutf8");
str = sb3.toString();
} else {
MD5Digest.Companion companion = MD5Digest.Companion;
// 没有发生替换,使用sb
String sb4 = sb.toString();
Intrinsics.checkExpressionValueIsNotNull(sb4, "signStr.toString()");
// 使用sb计算MD5
String encodeByMD52 = companion.encodeByMD5(sb4);
if (encodeByMD52 == null) {
str = null;
} else if (encodeByMD52 != null) {
// 转换为大写
str = encodeByMD52.toUpperCase();
Intrinsics.checkExpressionValueIsNotNull(str, "(this as java.lang.String).toUpperCase()");
} else {
throw new TypeCastException("null cannot be cast to non-null type java.lang.String");
}
}
try {
User user = (User) new PreUtil("sp_name_user").getValue("sp_user",
Reflection.getOrCreateKotlinClass(User.class), (Object) null);
if (user != null) {
OauthTokenBean oauthToken = user.getOauthToken();
String token = oauthToken != null ? oauthToken.getToken() : null;
Request request3 = chain.request();
if (!(request3 == null || (newBuilder2 = request3.newBuilder()) == null
|| (addHeader3 = newBuilder2.addHeader("token", token)) == null
|| (addHeader4 = addHeader3.addHeader("appKey", "389885588s0648fa")) == null
|| (addHeader5 = addHeader4.addHeader("sign", str)) == null) // 添加签名至头部
) {
request2 = addHeader5.build();
}
} else {
Request request4 = chain.request();
if (!(request4 == null || (newBuilder = request4.newBuilder()) == null
|| (addHeader = newBuilder.addHeader("appKey", "389885588s0648fa")) == null
|| (addHeader2 = addHeader.addHeader("sign", str)) == null)) {
request2 = addHeader2.build();
}
}
Response proceed = chain2.proceed(request2);
if (proceed == null) {
Intrinsics.throwNpe();
}
return proceed;
} catch (Exception e) {
e.printStackTrace();
Response proceed2 = chain2.proceed(request);
if (proceed2 == null) {
Intrinsics.throwNpe();
}
return proceed2;
}
}
}
六、逻辑总结(经过验证)
将请求参数正序排序
将请求参数按 参数名1参数值1参数名2参数值2.... 拼接
追加appkey, 追加appsecret
追加请求体
删除部分字符
计算MD5
评论 (0)