漏签真的很烦。

前言

本文仅供学习探讨之用,如果侵犯了您的权益请联系我删除。

工具

  1. Fiddler
  2. JADX
  3. IDA
  4. Frida

抓包

签到包内容

重放测试

使用Shift + R进行一个包的重放。

重放测试结果

没有问题,这其中有个DS参数看起来挺可疑的,去掉再试试。

重放测试结果

服务器直接不认了,那么可以确定这就是个时间戳签名信息。

JADX

将apk拖进jadx并等待分析完成后打开搜索窗口直接搜索DS关键字

搜索结果

然后啥也没搜到,那么换个"DS"关键字

搜索结果

看到几个可疑的目标,比如这个圈起来的类名就叫做GetDSMethodImpl,可以说非常的直接,那么跟过去看看

跟踪结果

经过分析代码后,发现DS来自与这个函数,继续跟过去看看

跟踪结果

可以看到到这里就已经进入了so层了,那么接下来进入下一层的分析。

IDA

全部的伪代码比较长,我就截取一小部分先来看看

v4 = (*a1)->GetStringUTFChars(a1, a3, 0LL); 
v64 = 0LL;
v65 = 0LL;
v66 = 0LL;
v5 = strlen(v4);                             
v6 = v5;
if ( v5 >= 0x17 )
{
    v8 = (v5 + 16) & 0xFFFFFFFFFFFFFFF0LL;
    v7 = (char *)operator new(v8);
    v65 = v6;
    v66 = v7;
    v64 = v8 | 1;
    goto LABEL_5;
}
v7 = (char *)&v64 + 1;
LOBYTE(v64) = 2 * v5;
if ( v5 )
LABEL_5:
    memcpy(v7, v4, v6);                       
v7[v6] = 0;                          
gettimeofday(&tv, 0LL);
std::to_string((std::__ndk1 *)(v63 / 1000000 + *(_QWORD *)&tv), v9);
Random::random((Random *)v58);
std::operator+<char>("salt=", &v64);
v10 = std::string::append((int)&v48, "&t=", 3u);

可以看到其中大量使用了std::string::append,那么我们来Hook这个函数看看。

需要说明一下的是米游社有Anti-Frida,所以需要反一下,可以参考一下这篇文章:反Frida检测与禁止SSLPinning的一些思路和方法

来看下Hook结果

Hook结果

可以看到这个结果跟我们抓到的包的DS参数最终效果是一致的

DS: 1663216865,2bfm47,b6069f6088a02f2ff35b1407034b520c

那么我们根据这个结果再回到IDA进行一波分析逻辑。

经过一番梳理之后得到以下流程

CMD5::CMD5((CMD5 *)&v70);                     // new MD5类

v4 = (*a1)->GetStringUTFChars(a1, a3, 0LL);   // 获取传进来的salt参数
v64 = 0LL;
v65 = 0LL;
v66 = 0LL;
v5 = strlen(v4);                              // 计算salt的长度
v6 = v5;
if ( v5 >= 0x17 )
{
    v8 = (v5 + 16) & 0xFFFFFFFFFFFFFFF0LL;
    v7 = (char *)operator new(v8);
    v65 = v6;
    v66 = v7;
    v64 = v8 | 1;
    goto LABEL_5;
}
v7 = (char *)&v64 + 1;
LOBYTE(v64) = 2 * v5;
if ( v5 )
LABEL_5:
    memcpy(v7, v4, v6);                         // copy v4(salt字符串)给v64
v7[v6] = 0;                                   // 结尾设\0
gettimeofday(&tv, 0LL);
std::to_string((std::__ndk1 *)(v63 / 1000000 + *(_QWORD *)&tv), v9);
Random::random((Random *)v58);                // 生成随机字符串
std::operator+<char>("salt=", &v64);          // 拼接salt=传进来的salt
v10 = std::string::append((int)&v48, "&t=", 3u);// 拼接&t=
v11 = *(_OWORD *)v10;
v51 = *(void **)(v10 + 16);
v50 = v11;
*(_QWORD *)(v10 + 8) = 0LL;
*(_QWORD *)(v10 + 16) = 0LL;
*(_QWORD *)v10 = 0LL;
if ( (v59 & 1) != 0 )
    v12 = v61;
else
    v12 = v60;
if ( (v59 & 1) != 0 )
    LODWORD(v13) = *(_DWORD *)&v60[7];
else
    v13 = (unsigned __int64)v59 >> 1;
v14 = std::string::append((int)&v50, v12, v13);// 拼接当前时间戳
v15 = *(_OWORD *)v14;
v53 = *(void **)(v14 + 16);
v52 = v15;
*(_QWORD *)(v14 + 8) = 0LL;
*(_QWORD *)(v14 + 16) = 0LL;
*(_QWORD *)v14 = 0LL;
v16 = std::string::append((int)&v52, "&r=", 3u);// 拼接&r=
v17 = *(_OWORD *)v16;
v71 = *(void **)(v16 + 16);
v70 = v17;
*(_QWORD *)(v16 + 8) = 0LL;
*(_QWORD *)(v16 + 16) = 0LL;
*(_QWORD *)v16 = 0LL;
if ( (v54 & 1) != 0 )
    v18 = v57;
else
    v18 = v55;
if ( (v54 & 1) != 0 )
    LODWORD(v19) = v56;
else
    v19 = (unsigned __int64)v54 >> 1;
v20 = std::string::append((int)&v70, v18, v19);// 拼接随机字符串
v21 = *(_WORD *)(v20 + 5);
v22 = *(_DWORD *)(v20 + 1);
v23 = *(_BYTE *)v20;                          // v23获得待计算字符串所有权
v69 = *(_BYTE *)(v20 + 7);
v68 = v21;
v67 = v22;
v25 = *(_QWORD *)(v20 + 8);
v24 = *(void **)(v20 + 16);
*(_QWORD *)v20 = 0LL;
*(_QWORD *)(v20 + 8) = 0LL;
*(_QWORD *)(v20 + 16) = 0LL;

v40 = 0LL;
v41 = 0LL;
v42 = 0LL;
if ( (v23 & 1) == 0 )
{
    LOBYTE(v40) = v23;                          // v40获得待计算字符串所有权
    HIBYTE(v40) = v69;
    *(_WORD *)((char *)&v40 + 5) = v68;
    *(_DWORD *)((char *)&v40 + 1) = v67;
    v41 = v25;
    v42 = v24;
    goto LABEL_42;
}

CMD5::md5(&v70, &v40);                        // 计算v40 md5hash值
if ( (v43 & 1) != 0 )
    v33 = v45;
else
    v33 = v44;
if ( (v43 & 1) != 0 )
    LODWORD(v34) = *(_DWORD *)&v44[7];
else
    v34 = (unsigned __int64)v43 >> 1;

sub_13C40((int)&v59, ",");                    // 拼接 当前时间戳 + ","
if ( (v54 & 1) != 0 )
    v26 = v57;
else
    v26 = v55;
if ( (v54 & 1) != 0 )
    LODWORD(v27) = v56;
else
    v27 = (unsigned __int64)v54 >> 1;
v28 = std::string::append((int)&v46, v26, v27);// 拼接随机字符串
v29 = *(_OWORD *)v28;
ptr = *(void **)(v28 + 16);
v48 = v29;
*(_QWORD *)(v28 + 8) = 0LL;
*(_QWORD *)(v28 + 16) = 0LL;
*(_QWORD *)v28 = 0LL;
v30 = std::string::append((int)&v48, ",", 1u);// 拼接 ,

v35 = std::string::append((int)&v50, v33, v34);// 拼接hash值,完成整个签名过程

计算过程

可能上面看起来会有点乱,接下来总结一下对于DS的计算过程大概是这样的

input salt
input timestamp
input randomString

process params = "salt=" + salt + "&t=" + timestamp + "&r=" + randomString
output hash = md5(params)

output DS = timestamp + "," + randomString + "," + hash

代码验证

来写一份代码验证一下。

import hashlib
import time
import random

salt = 'n0KjuIrKgLHh08LWSCYP0WXlVXaYvV64'
# timestamp = str(int(time.time()))
timestamp = '1663216865'
# randomString = ''.join(random.sample('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 6))
randomString = '2bfm47'

params = 'salt={}&t={}&r={}'.format(salt, timestamp, randomString)

md5 = hashlib.md5()
md5.update(params.encode('utf-8'))
hash = md5.hexdigest()

DS = '{},{},{}'.format(timestamp, randomString, hash)

print('DS', DS)

输出

预计输出
1663216865,2bfm47,b6069f6088a02f2ff35b1407034b520c

实际输出
DS 1663216865,2bfm47,b6069f6088a02f2ff35b1407034b520c

可以看到预想与实际的结果完全一致。

实际测试

来实际发个包试试吧

测试结果

嗯,居然不认。根据结果知道我们的推理应该是不会错的,那么我们回到jadx看看是什么情况。

回到JADX

a2222这个函数查找引用后看到是有两个地方的

引用情况

根据代码分析它们所使用的salt非同一个,那么我们需要找一下salt的来源。

经过分析之后我们抠出来了这样一段代码

public class Main {
    public static final int[] iArr = {-90, 114, -70, -74, -108, 222, 60, 66, 90, 72, 84, -102, -76, 120, 84, 216, 222, -114, -68, -66, -14348907, 108, 222, 216, -68, 192, -88, -120, 150, -74, 150, -108};

    public static void main(String[] args) {
        int i;
        StringBuilder sb = new StringBuilder();
        ArrayList<Number> arrayList = new ArrayList(iArr.length);
        for (int i2 : iArr) {
            if (i2 < 0) {
                i = ((double) (-i2)) >= Math.pow(3.0d, 6.0d) ? (int) (((Math.log(-((double) i2)) / Math.log(3.0d)) - ((double) 6)) + ((double) 48)) : ~i2;
            } else {
                i = (i2 / 3) + 48;
            }
            arrayList.add(Integer.valueOf(i));
        }
        ArrayList arrayList2 = new ArrayList(arrayList.size());
        for (Number number : arrayList) {
            sb.append((char) number.intValue());
            arrayList2.add(sb);
        }
        String sb2 = sb.toString();
        System.out.println(sb2);
    }
}

执行后得到输出

YVEIkzDFNHLeKXLxzqCA9TzxCpWwbIbk

然后我们将代码中的salt替换为这个输出之后再来发个包试试

最终测试

嗯,可以看到服务器已经认可我们的包了,没问题。

TODO

  • Taskcloud版本的自动签到脚本
  • 抓米游币每日签到的接口
  • 抓米游币浏览3个帖子的接口
  • 抓米游币点赞5次的接口
  • 抓米游币分享帖子的接口

自动签到脚本慢慢再写,因为Taskcloud目前好像还有点问题,但是还没修。

米游社的其他接口可能不搞

!smart

先放脚本的仓库链接,之后如果有新脚本的话都会更新到仓库里。

脚本仓库:scripts

结语

原神真好玩,嘿嘿。

那就这样了,有缘再见~