Skip to content

微博APP模拟登录

前言废话


这笔记是很早之前(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

代码验证


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;
        }
    }

测试