Eswlnk Blog Eswlnk Blog
  • 资源
    • 精彩视频
    • 破解专区
      • WHMCS
      • WordPress主题
      • WordPress插件
    • 其他分享
    • 极惠VPS
    • PDF资源
  • 关于我
    • 论文阅读
    • 关于本站
    • 通知
    • 左邻右舍
    • 玩物志趣
    • 日志
    • 专题
  • 热议话题
    • 游戏资讯
  • 红黑
    • 渗透分析
    • 攻防对抗
    • 代码发布
  • 自主研发
    • 知识库
    • 插件
      • ToolBox
      • HotSpot AI 热点创作
    • 区块
    • 快乐屋
    • 卡密
  • 乱步
    • 文章榜单
    • 热门标签
  • 问答中心反馈
  • 注册
  • 登录
首页 › 攻防对抗 › 「攻防对抗」某校园跑软件签名算法分析

「攻防对抗」某校园跑软件签名算法分析

Eswlnk的头像
Eswlnk
2022-08-11 14:00:18
「攻防对抗」某校园跑软件签名算法分析-Eswlnk Blog
智能摘要 AI
本文主要介绍了对某校园跑软件的签名算法分析过程。作者通过抓包确认请求无签名后,使用工具脱壳并分析代码,最终确定签名生成步骤。具体步骤包括:将请求参数按名称顺序拼接,追加`appkey`和`appsecret`,再追加请求体,删除特定字符(如空格、括号等),最后计算MD5并转换为大写。通过这些步骤,作者成功伪造签名以完成任务。文章详细记录了每一步的操作,并提供了具体的代码逻辑分析。

前言

同学在群中分享某虚拟定位轻松跑步法。

作为旁观者,萌生出模拟请求进行数据伪造来完成任务(老操作了,像骨灰盒自动参加赠楼这种)

某校园跑软件

其实要求还算可以,极限情况:50天、每天1公里5分钟;就是不太好坚持

分析思路

  1. 抓包,看请求是否有签名,没有就去第4步
  2. 检测安装包是否有壳,无壳就去第4步
  3. 脱壳
  4. 分析代码
  5. 伪造请求
  6. 等待正义的审判 #(受虐滑稽)

一、先抓包(非重点,不过多赘述)

  1. token 理论上是用户标识
  2. appkey 肯定大部分时间为定值
  3. sign MD5签名

二、查壳

360加固,一般 #(cos滑稽)

三、脱壳

这里使用这个工具:

CodingGay/BlackDex: BlackDex is an Android unpack(dexdump) tool, it supports Android 5.0~12 and need not rely to any environment. BlackDex can run on any Android mobile phone or emulator, you can unpack APK File in several seconds. (github.com)

下载32位版本安装包(因为,目标APP是32位的)

顺便记下包名com.tanma.unirun

单击目标应用,开始脱壳

四、分析目标“源码”位置

打开脱壳后的dex文件所在目录,

使用MT管理器 的 dex编辑器++ 打开

搜索字符串 sign

只看目标应用的包

看包名:

  1. entities 实体包,排除
  2. enums  枚举包,排除
  3. 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. 将请求参数正序排序
  2. 将请求参数按 参数名1参数值1参数名2参数值2....  拼接
  3. 追加appkey, 追加appsecret
  4. 追加请求体
  5. 删除部分字符
  6. 计算MD5

ESWINK , 版权所有丨如未注明 , 均为原创

原文标题:「攻防对抗」某校园跑软件签名算法分析

Eswink原创声明
本站默认网盘访问密码:1166
本站默认网盘访问密码:1166
声明:本站原创文章文字版权归本站所有,转载务必注明作者和出处;本站转载文章仅仅代表原作者观点,不代表本站立场,图文版权归原作者所有。如有侵权,请联系我们删除。
安卓猿之力逆向
0
0
Eswlnk的头像
Eswlnk
一个有点倒霉的研究牲站长
赞赏
关于缓冲区溢出漏洞那些事之C-gets函数
上一篇
「攻防对抗」TLS握手指纹(JA3/JA3S)检测恶意软件流量
下一篇

评论 (0)

请登录以参与评论
现在登录
    发表评论

猜你喜欢

  • 今日热点:伪Clash软件下载陷阱曝光,附防范建议
  • 「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制
  • 「攻防对抗」从上传漏洞到Getshell | 一次完整的渗透过程
  • 「日志记录」从零起步揭开路由器漏洞挖掘的面纱
  • 「攻防对抗」NSmartProxy流量特征的真实表现与应用
Eswlnk的头像

Eswlnk

一个有点倒霉的研究牲站长
1108
文章
319
评论
679
获赞

随便看看

「攻防对抗」植物大战僵尸DLL注入修改
2022-08-20 15:39:38
「视频教程」C/C++逆向汇编教程:初识汇编
2023-12-10 2:10:24
宝塔面板收集站点隐私信息
2022-05-09 1:32:58

文章目录

专题展示

WordPress53

工程实践37

热门标签

360 AI API CDN java linux Nginx PDF PHP python SEO Windows WordPress 云服务器 云服务器知识 代码 免费 安全 安卓 工具 开发日志 微信 微软 手机 插件 攻防 攻防对抗 教程 日志 渗透分析 源码 漏洞 电脑 破解 系统 编程 网站优化 网络 网络安全 脚本 苹果 谷歌 软件 运维 逆向
  • 首页
  • 知识库
  • 地图
Copyright © 2023-2025 Eswlnk Blog. Designed by XiaoWu.
本站CDN由 壹盾安全 提供高防CDN安全防护服务
蜀ICP备20002650号-10
页面生成用时 0.622 秒   |  SQL查询 31 次
本站勉强运行:
友情链接: Eswlnk Blog 网站渗透 倦意博客 特资啦!个人资源分享站 祭夜博客 iBAAO壹宝头条
  • WordPress142
  • 网络安全64
  • 漏洞52
  • 软件52
  • 安全48
现在登录
  • 资源
    • 精彩视频
    • 破解专区
      • WHMCS
      • WordPress主题
      • WordPress插件
    • 其他分享
    • 极惠VPS
    • PDF资源
  • 关于我
    • 论文阅读
    • 关于本站
    • 通知
    • 左邻右舍
    • 玩物志趣
    • 日志
    • 专题
  • 热议话题
    • 游戏资讯
  • 红黑
    • 渗透分析
    • 攻防对抗
    • 代码发布
  • 自主研发
    • 知识库
    • 插件
      • ToolBox
      • HotSpot AI 热点创作
    • 区块
    • 快乐屋
    • 卡密
  • 乱步
    • 文章榜单
    • 热门标签
  • 问答中心反馈