微博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 的流程
- 将密码转为Byte[]。
- 解密代码中写死的加密公钥。
- 使用公钥加密密码。
- 将密码转为Base64返回。
根据上面来看,WeicoSecurityUtils 这个类很关键。
整理了一下流程:
- 使用 publicKeyInner 解密 publicKeyString
- 使用解密后的 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中一下就能看见这个方法
进入方法后,确认入参一致
分析大致流程
流程大概是 :
- 将 username+password+pin 组合后,计算MD5
- 根据 1、5、2、10、17、9、25、27 这些位置拿出MD5 字符串对应位置的字符
- 返回计算好的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;
}
}