闷骚小贱男 发表于 2021-8-20 18:26

某湃新闻APP之登录验证码下发短信分析

本帖最后由 闷骚小贱男 于 2021-8-20 19:17 编辑

# 前言
分析算法,这个事情...我发现了。是真上瘾...
(PS:从某米应用商店下载了一整页的APP...)

# 工具
1. fiddler(抓网络数据包用)
2. jadx-gui(APP反编译用)
3. 易语言(复现用)
4. 算法助手(可用也可不用,xp模块,需root使用)

# 0x1 抓包
直接手机设置代{过}{滤}理,打开APP,竟然能抓到数据包。【也可以用Drony端口转发抓包、小黄鸟等】
登录下发验证码的接口为`/v3/getVerCode.msp`,codeParam值为32位长度,猜测是md5加密

还有另外一个`/v3/getVericodek.msp`的接口返回的内容为json格式
~~~
{"resultMsg":"请求成功","resultCode":"1","vericodek":"f2G8x42F57UT"}
~~~

# 0x2 分析
## jadx搜接口(不用助手)
直接搜`/v3/getVericodek.msp`

找到`cn.thepaper.paper.ui.mine.registerNew.b`类中的a函数。

## 查看调用堆栈(用助手)
直接上算法助手搜fiddler数据中`codeParam值6280CE5F15B9BCC2CD0AA9C73D0A3046`,出现一条数据记录


~~~
明文为F028407907FB062579FEE0D33D155F94C27D4A1DBB53F4DC33402F13BE590D7175FFB8EBB425208A1CB86F151398D955
~~~
但是好像并没有再助手和fiddler中搜到相关的数据,猜测明文也是经过处理的,我们看调用堆栈。
找到`cn.thepaper.paper.ui.mine.registerNew.b`类中的a函数。

~~~java
public void a(String str, String str2, String str3, String str4, String str5) {
    String str6;
    String str7;
    if (TextUtils.isEmpty(str5)) {
      str6 = str3 + "0000";
      str7 = "0000";
    } else {
      str6 = str3 + str5;
      str7 = str5;
    }
    this.f2371c.a(str, str2, str7, EncryptUtils.encryptMD5ToString(EncryptUtils.encryptAES2HexString(("verType=" + str + "mail=" + str2 + "gCode=" + str7).getBytes(), str6.getBytes())), str4).a(new c<BaseInfo>() {
    /* 此处省略N多处理代码 */
    });
}
~~~
简单看一下,根据fiddler的参数,可以得出`"verType=" + str + "mail=" + str2 + "gCode=" + str7`中

| str | str2| str7 |
| :-------------: |:-------------:| :-----:|
| a函数的参数1 | 手机号 | 变量str7|
| 6 | 18396555555| 0000|

## 查看哪里调用a函数
在a函数处右键,查找用例。有很多,但是根据上门的a函数的第一个参数str=6,可找到本次调用的位置。

双击进去查看。
~~~java
    public void t(View view) {
      if (!cn.thepaper.paper.lib.c.a.a(view)) {
            if (!PaperApp.isNetConnected()) {
                ToastUtils.showShort((int) R.string.network_fail);
                return;
            }
            cn.thepaper.paper.lib.b.a.a("294");
            String trim = this.e.getText().toString().trim();
            this.w = trim;
            if (!RegexUtils.isMobileSimple(trim)) {
                ToastUtils.showShort((int) R.string.phone_incorrect);
                return;
            }
            String str = this.v;
            if (str == null || str.length() != 12) {
                this.r.d();
                return;
            }
            this.r.a("6", this.w, this.v, "1", "");/* 此处为调用a函数 */
            this.g.requestFocus();
            m(this.g);
      }
    }
~~~
由上述f函数的代码可得出a函数所有的参数(String str, String str2, String str3, String str4, String str5)

| str | str2| str3 | str4 | str5 |
| :------------- |:-------------:| :-----:| :-----:| :-----:|
| a函数的参数1 | this.w=手机号 | this.v| 变量str4 | 变量str5 |
| 6 | 18396555555| 未知| 1| ''|

出现一个未知的this.v ,既然有调用,那就肯定有赋值,我们再右键查this.v的查找用例

果然找到一个,
~~~java
this.v = vericodek.getVericodek();

public String getVericodek() {
    return this.vericodek;
}
~~~
this.vericodek这不巧了吗这不是,这不就是`/v3/getVericodek.msp`中得到的吗?
又根据
~~~java
      if (TextUtils.isEmpty(str5)) {
            str6 = str3 + "0000";
            str7 = "0000";
      } else {
            str6 = str3 + str5;
            str7 = str5;
      }
~~~
str5为空,所以得出:

~~~java
    str6 = str3 + "0000";str7 = "0000";
~~~


| str | str2| str3 | str4 | str5 | str6 | str7 |
| :-------------: |:-------------:| :-----:| :-----:| :-----:| :-----:| :-----:|
| a函数的参数1 | this.w=手机号 | this.v=getVericodek.msp中的vericodek| 变量str4 | 变量str5 | str6=str3+"0000" | str7="0000"|
| 6 | 18396555555| f2G8x42F57UT| 1| ''| f2G8x42F57UT0000 | 0000|

得出所有参数和变量之后,查看处理过程,现实encryptAES2HexString,然后再用encryptMD5ToString。(PS:那就是先AES加密,在用MD5加密,得出的`6280CE5F15B9BCC2CD0AA9C73D0A3046`)

## 分析
明文经过处理,得到md5值
~~~java
EncryptUtils.encryptAES2HexString(("verType=" + str + "mail=" + str2 + "gCode=" + str7).getBytes(), str6.getBytes())
~~~
也就等于
~~~java
EncryptUtils.encryptAES2HexString(("verType=" + "6" + "mail=" + "18396555555" + "gCode=" + "0000").getBytes(), str6.getBytes())
~~~
### 先看encryptAES2HexString
~~~java
public static String encryptAES2HexString(byte[] bArr, byte[] bArr2) {
    return bytes2HexString(encryptAES(bArr, bArr2));/* 看样子应该是AES加密,编码为hex而不是base64 */
}
~~~

### 再看下encryptAES
~~~
    private static final String AES_Algorithm = "AES";
    public static String AES_Transformation = "AES/ECB/PKCS5Padding";

    public static byte[] encryptAES(byte[] bArr, byte[] bArr2) {
      return desTemplate(bArr, bArr2, AES_Algorithm, AES_Transformation, true);/* AES_Algorithm= */
    }
    public static byte[] desTemplate(byte[] bArr, byte[] bArr2, String str, String str2, boolean z) {
      if (!(bArr == null || bArr.length == 0 || bArr2 == null || bArr2.length == 0)) {
            try {
                SecretKeySpec secretKeySpec = new SecretKeySpec(bArr2, str);
                Cipher instance = Cipher.getInstance(str2);
                instance.init(z ? 1 : 2, secretKeySpec, new SecureRandom());
                return instance.doFinal(bArr);
            } catch (Throwable th) {
                th.printStackTrace();
            }
      }
      return null;
    }

~~~
由desTemplate可得出AES/ECB算法参1为明文,参2为秘钥
所以用在线AES加密网站测试


加密结果为:`F028407907FB062579FEE0D33D155F94C27D4A1DBB53F4DC33402F13BE590D7175FFB8EBB425208A1CB86F151398D955`
再MD5结果为:`6280CE5F15B9BCC2CD0AA9C73D0A3046`,正好是fiddler里面的codeParam值

# 0x3 需要注意的事项
1. 秘钥的一部分this.v=f2G8x42F57UT并不是固定的,而是每一次下发验证码之前就要先获取的,
2. AES加密的结果中的字母必须为大写,小写的话直接MD5,结果会不一样哦
3. 请求的时候,记得要添加协议头哦,要不然的话会提示`没找到uuid`

# 0x4 易语言实现下发短信
因为vericodek不一样,所以易语言之后的codeParam值也和之前fiddler抓到的不一样了。





闷骚小贱男 发表于 2021-8-20 19:59

芽衣 发表于 2021-8-20 19:39
这玩得不亦乐乎
:(eew就是……总有人要让我用接口怼人
高级的玩不来,玩点简单的逆向

hswei 发表于 2021-8-20 19:31

非常感谢!

芽衣 发表于 2021-8-20 19:39

这玩得不亦乐乎{:17_1068:}

科西嘉滕 发表于 2021-8-20 19:49

感谢分享

Tamluo 发表于 2021-8-20 20:57

非常感谢!
学习了

Eilliem 发表于 2021-8-20 22:09

牛牛牛!学习啦

yoyoma211 发表于 2021-8-20 23:26

进来学习一下,感谢楼主科普

xiamen 发表于 2021-8-21 00:20

分析得好啊

Tamluo 发表于 2021-8-21 00:21

进来学习一下,感谢楼主科普
页: [1] 2 3
查看完整版本: 某湃新闻APP之登录验证码下发短信分析