前言废话


这笔记是很早之前(2019)写好的,目前微博的有手机认证,所以不要想了😀
现在发出来分享下吧(顺带测试下Github Action)

分析阶段


这次下载了两个版本的新浪微博
1.0.0 Weico 这个是最早版本的国际版微博
3.0.0 WeiboIntl 这个是目前新版本的

首先先拿最早版本的做登录测试,发现还是可以正常的登录。

通过抓包发现登录时请求的接口是: api.weibo.cn/2/account/login

Post 请求体大概分析能得出对应参数

  • c - APP类型
  • s - 签名
  • u - 用户名
  • p - 加密后的密码

首先用了 GDA3.66 去做逆向分析,发现逆向的代码会有部分的缺失。在这耗费了太多的时间。

不过好在也发现了登录的几个关键方法。

这里对密码进行了加密,以及计算签名。 通过后续两个版本的抓包比较发现,签名是不会变化的。

首先分析下 securityPsd 的流程

  1. 将密码转为Byte[]。
  2. 解密代码中写死的加密公钥。
  3. 使用公钥加密密码。
  4. 将密码转为Base64返回。

根据上面来看,WeicoSecurityUtils 这个类很关键。

整理了一下流程:

  1. 使用 publicKeyInner 解密 publicKeyString
  2. 使用解密后的 publicKeyString 作为公钥加密传入的密码

这样密码加密后的密文就得出来了
现在就差 s 签名参数了
继续向下看发现计算 s 参数的关键方法:WeiboSecurityUtils.calculateSInJava
跟进去后发现调用的是 native 方法,搜索了下发现是调用 utility 的so库

根据传入的参数, Java的Context与 srcArray 和 Pin
看见传了context头就有点大,猜测可能会加入MAC或者手机号等做计算。因为抓包时都是同一设备做测试
只能先返回上层代码看看 srcArray 和 Pin 这两个怎么来的

查看后就发现传入的 srcArray= username+password ,并且password是原始未加密的密码。
另一个Decode查看后应该是加密后的文本,解密后就是 Logutil的那个。 这日志打点的都暴露了…..

接下来就研究下so文件里的逻辑了
加密方法存在 libutility.so 文件里,IDA中一下就能看见这个方法

进入方法后,确认入参一致

分析大致流程

流程大概是 :

  1. 将 username+password+pin 组合后,计算MD5
  2. 根据 1、5、2、10、17、9、25、27 这些位置拿出MD5 字符串对应位置的字符
  3. 返回计算好的s

代码验证


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
public class AppLoginHelper
    {
        private const string RsaPubKey =
            "***";

        private const string PublicKeyInner =
            "***";

        private const string WeicoPin =
            "***";

        private static readonly int MAX_DECRYPT_BLOCK = 128;
        private static readonly int MAX_ENCRYPT_BLOCK = 117;

        public AppLoginModel GetLoginParam(string username, string password)
        {
            var result = new AppLoginModel();

            var keyBytes = Convert.FromBase64String(RsaPubKey);
            var dd = DecryptByPublicKey(keyBytes, PublicKeyInner);
            result.p = Convert.ToBase64String(EncryptByPublicKey(Encoding.UTF8.GetBytes(password),
                Encoding.UTF8.GetString(dd)));

            // s 标签计算
            var decodeStr = Decode(WeicoPin);

            var test1 = $"{username}{password}{decodeStr}";

            var fff = MdStringOld(test1);
            var a1 = fff[1];
            var a2 = fff[5];
            var a3 = fff[2];
            var a4 = fff[10];
            var a5 = fff[17];
            var a6 = fff[9];
            var a7 = fff[25];
            var a8 = fff[27];

            result.s = $"{a1}{a2}{a3}{a4}{a5}{a6}{a7}{a8}";

            result.u = username;

            return result;
        }

        /// <summary>
        /// 微博 so库内 MD5加密
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        private static string MdStringOld(string str)
        {
            var md5 = MD5.Create();
            var s = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
            return s.Aggregate("", (current, t) => current + t.ToString("x2"));
        }

        private static string Decode(string encryptedStr)
        {
            var dd = DecryptByPublicKey(Convert.FromBase64String(encryptedStr), PublicKeyInner);
            return Encoding.UTF8.GetString(dd);
        }

        private static byte[] DecryptByPublicKey(byte[] encryptedData, string publicKey)
        {
            var keyBytes = Convert.FromBase64String(publicKey);
            var paraPub = ConvertFromPublicKey(keyBytes);
            var privateRsa = new RSACryptoServiceProvider();
            privateRsa.ImportParameters(paraPub);

            AsymmetricKeyParameter pbk = DotNetUtilities.GetRsaPublicKey(paraPub);

            var c = CipherUtilities.GetCipher("RSA/ECB/PKCS1Padding");
            c.Init(false, pbk);

            var inputLen = encryptedData.Length;
            var offSet = 0;
            var i = 0;
            var outStream = new MemoryStream();
            byte[] cache;
            while (inputLen - offSet > 0)
            {
                if (inputLen - offSet > MAX_DECRYPT_BLOCK)
                    cache = c.DoFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
                else
                    cache = c.DoFinal(encryptedData, offSet, inputLen - offSet);

                outStream.Write(cache, 0, cache.Length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }

            var decryptedData = outStream.ToArray();
            outStream.Close();
            return decryptedData;
        }

        private static byte[] EncryptByPublicKey(byte[] data, string publicKey)
        {
            var keyBytes = Convert.FromBase64String(publicKey);
            var paraPub = ConvertFromPublicKey(keyBytes);
            var privateRsa = new RSACryptoServiceProvider();
            privateRsa.ImportParameters(paraPub);

            AsymmetricKeyParameter pbk = DotNetUtilities.GetRsaPublicKey(paraPub);

            var c = CipherUtilities.GetCipher("RSA/ECB/PKCS1Padding");
            c.Init(true, pbk);

            var inputLen = data.Length;
            var offSet = 0;
            var i = 0;
            var outStream = new MemoryStream();
            byte[] cache;
            while (inputLen - offSet > 0)
            {
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK)
                    cache = c.DoFinal(data, offSet, MAX_ENCRYPT_BLOCK);
                else
                    cache = c.DoFinal(data, offSet, inputLen - offSet);

                outStream.Write(cache, 0, cache.Length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }

            var decryptedData = outStream.ToArray();
            outStream.Close();
            return decryptedData;
        }

        private static RSAParameters ConvertFromPublicKey(byte[] pubkeyBytes)
        {
            var keySize1024 = pubkeyBytes.Length == 162;
            var keySize2048 = pubkeyBytes.Length == 294;
            if (!(keySize1024 || keySize2048))
                throw new ArgumentException("pem file content is incorrect, Only support the key size is 1024 or 2048");

            var pemModulus = keySize1024 ? new byte[128] : new byte[256];
            var pemPublicExponent = new byte[3];
            Array.Copy(pubkeyBytes, keySize1024 ? 29 : 33, pemModulus, 0, keySize1024 ? 128 : 256);
            Array.Copy(pubkeyBytes, keySize1024 ? 159 : 291, pemPublicExponent, 0, 3);
            var para = new RSAParameters();
            para.Modulus = pemModulus;
            para.Exponent = pemPublicExponent;
            return para;
        }
    }

测试