吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4646|回复: 83
上一主题 下一主题
收起左侧

[Android 原创] 某工宝协议逆向-视频快进

  [复制链接]
跳转到指定楼层
楼主
3800104045 发表于 2022-11-19 23:49 回帖奖励
本帖最后由 3800104045 于 2022-11-20 12:21 编辑

某工宝协议逆向-视频快进

1. 介绍

最近一个在建筑公司上班的朋友给我说,他们现在每天都要用手机在某工宝上面看视频学习建筑知识,不能一起愉快的游戏了,让我帮忙看看能不能快进一丢丢。本着积极学习的态度,顺便看看那些并不那么出名的APP的在数据传输的时候用的协议算法,见见世面。

2.抓包

为了简便起见,这里直接使用HTTPCanary开始抓包,定位到记录视频进度的关键部分。

  1. 登录。

    登录部分比想象中简单,竟然是直接明文传输

    这就很有意思了,说明重点不在这里。

  2. 播放视频

    播放视频这里果然才是重点。目前市面上大概有以下两种记录学习进度方法:

    • 每隔固定时间上传一次记录

    • 自由上传记录,时间搭配在参数中

      不过,第一种方式,服务端可能存在主动校验,将服务器经过的时间与获得的请求经过的时间做一个比较,这种方式只能通过快速提交的方法实现快进。第二种方式,可能会存在其他与时间有关的东西做签名函数做重复校验,这种方式可以通过伪造观看位置的方法实现快进。

      根据抓包内容,可以看到,这里的记录进度的方式显示是通过将时间放在参数中记录。

      在提交的参数中,总共有以下几种:

      [
       {
           "classStuId": "6aa4b9df-fc16-4dc5-96b2-a9c7e8c68cc6", 
           "id": 1, 
           "lastPlayPotion": 0, 
           "logType": "1", 
           "memberId": "66ad20b2-f8e5-4576-a267-fdbf22189bb2", 
           "packId": "fcf228cc-1ee4-11ed-a2ee-00ff07067ec4", 
           "playEndPoint": 11, 
           "playEndTime": "2022-11-19 18:02:21", 
           "playStartPoint": 1, 
           "playStartTime": "2022-11-19 18:02:11", 
           "playType": 1, 
           "sign": "803227E1CA186200B97DDEA748033BA1", 
           "stuHourDetailId": "1ed655dc-9688-6ed7-9fca-39c8584a299d"
       }
      ]

      大胆猜测,其中的playEndPoint就是记录视频观看到那个位置的一个关键变量,因此我们只需要伪造这个变量,改成这个视频的总时间,应该就能实现快进的原理。

      其中有个关键参数sign应该就是我们这里最难的一部分了,这类跟随其余参数一起上传的sign一般都是某个散列函数,用来在服务端校验数据有没有被修改过,粗略猜测是个md5算法。

3. 反编译

为了能够确定sign是如何得到的,这里对原有app进行反编译,这里使用jadx快速看一眼,没有加壳,感谢作者。根据关键发包链接检索代码,我们这里就直接搜索sign关键字

包含了太多的sign,其实并不好找,我们可以根据发包链接videoLogSign去寻找

最后可以找到这是返回Observable的函数注解函数实现的发包,继续深入检索postCommonVideoLog

其中这一个吸引了注意,其中的List<VideoPlayLogEntity>猜测可能是日志记录的实体列表。点进去查看得到VideoPlayLogEntity,这就是我们对应的视频记录的实体类。

继续搜索VideoPlayLogEntity看看他的sign参数是何时被set进去的,真相已经很接近了。

根据反编译的代码变量名,我们基本可以确定刚才的sign肯定是跟MD5有关系,至于怎么做的散列,需要在看看源码,点进去查看源码。其代码如下:

    public static void sgin(List<VideoPlayLogEntity> list) {
        for (VideoPlayLogEntity videoPlayLogEntity : list) {
            LinkedHashMap linkedHashMap = new LinkedHashMap();
            linkedHashMap.put("playStartPoint", Integer.valueOf(videoPlayLogEntity.getPlayStartPoint()));
            linkedHashMap.put("playEndPoint", Integer.valueOf(videoPlayLogEntity.getPlayEndPoint()));
            linkedHashMap.put("playStartTime", videoPlayLogEntity.getPlayStartTime());
            linkedHashMap.put("playEndTime", videoPlayLogEntity.getPlayEndTime());
            linkedHashMap.put("playType", Integer.valueOf(videoPlayLogEntity.getPlayType()));
            String sgin = sgin(linkedHashMap, Constants.LogKey);
            Log.d("=====result:", "sgin=" + sgin);
            videoPlayLogEntity.sign = sgin;
        }
    }

在这个方法中是将VideoPlayLogEntity类中的playStartPointplayEndPointplayStartTimeplayEndTimeplayType这五个参数放入LinkedHashMap中,然后传入另外一个重载的sgin方法中。这个重载的sgin还有一个参数是Constants.LogKey,点击得到定义的salt:

  public static final String LogKey = "d^*A%8^43YsrYZ$9";

继续进入到重载的sgin方法中,代码如下:

    public static String sgin(Map<String, Object> map, String str) {
        Set<String> keySet = map.keySet();
        String[] strArr = (String[]) keySet.toArray(new String[keySet.size()]);
        StringBuilder sb = new StringBuilder();
        for (String str2 : strArr) {
            if (String.valueOf(map.get(str2)).trim().length() > 0) {
                sb.append(str2);
                sb.append("=");
                sb.append(String.valueOf(map.get(str2)).trim());
                sb.append("&");
            }
        }
        sb.append("key=" + str);
        System.out.print("=====sgin:" + sb.toString());
        return md5(str, sb.toString()).toUpperCase();
    }

这部分代码就是将接收到的Map中各个参数取出来并拼接成如下所示字符串:

playStartPoint=3&playEndPoint=2297&playStartTime=2022-11-19 17:48:00&playEndTime=2022-11-19 17:48:33&playType=1&key=d^*A%8^43YsrYZ$9

随后将这部分字符串放入定义的md5函数

    public static String md5(String str, String str2) {
        try {
            MessageDigest instance = MessageDigest.getInstance("MD5");
            instance.update(str.getBytes());
            instance.update(str2.getBytes(InternalZipConstants.CHARSET_UTF8));
            byte[] digest = instance.digest();
            String str3 = "";
            for (int i = 0; i < digest.length; i++) {
                str3 = str3 + Integer.toHexString((digest[i] & 255) | InputDeviceCompat.SOURCE_ANY).substring(6);
            }
            return str3.toUpperCase();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

很明显,这是对经典md5算法的一种魔改,将经典md5算法得到的结果与255做与操作然后与InputDeviceCompat.SOURCE_ANY(-256)做或操作,最后将得到的整数转成十六进制,取十六进制最后两位。

4. 协议复现

有了上述原理就很好复现出快进协议了,整理python的代码如下:

def get_sign(sp, ep, st='2022-11-19 17:48:00', et='2022-11-19 17:48:33'):
    salt = 'd^*A%8^43YsrYZ$9'
    sign_input = f'playStartPoint={sp}&playEndPoint={ep}&playStartTime={st}&playEndTime={et}&playType=1&key=d^*A%8^43YsrYZ$9'
    m = hashlib.md5()
    m.update(salt.encode())
    m.update(bytes(sign_input, 'UTF-8'))

    digest = m.digest()
    result = ''
    for b in digest:
        h = hex(((b & 255) | -256) & 0xffffffff)
        result += h[-2:]
    return result.upper()

这里有个坑,python的hex()函数转十六进制是不包括负号的,也就是不会把二进制第一个1看成是负号,而java的Integer.toHexString()这个函数会将二进制第一个1看作是负号。因此,python需要在与上0xffffffff才能得到与java一致的结果。

最终复现结果得到sign为:

5. 总结

  1. 现有许多app基本混淆都不加,很容易被反汇编获取源代码,甚至对服务器发起攻击。
  2. 就算不加混淆,也应该将加密函数放在so层,增加逆向难度。

免费评分

参与人数 26威望 +2 吾爱币 +125 热心值 +25 收起 理由
wkend + 1 学习了
emptynullnill + 1 + 1 谢谢@Thanks!
hao827754 + 1 + 1 我很赞同!
hncsxpyj + 1 + 1 谢谢@Thanks!
longling + 1 + 1 用心讨论,共获提升!
s757129 + 1 + 1 ggnb!
dincia + 1 谢谢@Thanks!
allspark + 1 + 1 用心讨论,共获提升!
skiss + 1 + 1 用心讨论,共获提升!
笙若 + 1 + 1 谢谢@Thanks!
gaosld + 1 + 1 谢谢@Thanks!
guo15049434245 + 1 + 1 我很赞同!
pwp + 3 + 1 热心回复!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
SPXJ1234 + 1 + 1 我很赞同!
belikovlin + 1 + 1 用心讨论,共获提升!
junjia215 + 1 + 1 用心讨论,共获提升!
王破解 + 1 热心回复!
badboybyd + 1 + 1 我很赞同!
tomhex + 1 + 1 谢谢@Thanks!
cowboy206 + 1 + 1 我很赞同!
ankh04 + 1 + 1 我很赞同!
wmsj666 + 1 谢谢@Thanks!
db0805 + 1 + 1 用心讨论,共获提升!
Arcticlyc + 1 + 1 我很赞同!
正己 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
tomhex 发表于 2022-11-21 18:53
谢谢大神分享,学习了
推荐
latanghu 发表于 2022-12-6 20:59
这个应该是典工宝,楼主这个分析给平时做APP的朋友提了醒,还是要注意该加密的地方要加密,要有安全意识!
沙发
xzhtx 发表于 2022-11-21 08:28
3#
孤寡 发表于 2022-11-21 09:11
路过 学习一下啊
4#
lvlxl1 发表于 2022-11-21 09:41
大佬牛逼
5#
wmsj666 发表于 2022-11-21 11:09
感谢分享
6#
Ironhatking 发表于 2022-11-21 11:49
牛牛牛牛
7#
ankh04 发表于 2022-11-21 12:21
赞!思路清晰,自己写app的时候也应该多加小心了!
8#
Alan1020 发表于 2022-11-21 13:51
牛逼啊大佬
9#
dahuoyzs 发表于 2022-11-21 17:30
牛牛牛牛
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2022-12-9 21:06

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表