dazhige 发表于 2020-3-22 17:15

记一次逆向某大学app寻找签名算法

## 起因

在测试app的过程中,经常遇到有签名校验的情况,比如修改某个数据包中的参数,会出现签名不通过:
!(https://i.loli.net/2020/03/22/dB9NH57pqJvXFsf.png)
这个时候就得去找找这个sign参数,看看能不能由我们自己生成,从而修改参数进行重放。不然没法儿继续测了。

## 寻找
### 开始
老规矩,先查壳:
!(https://i.loli.net/2020/03/22/Mxo9pGdZlwUJhkO.png)
无壳,直接上逆向工具,我这里用的是apkide:


### 入口
从哪里入手呢?这里我用夜神安装好目标app后,随便点了几下,找到一个相对比较简单的数据包:
!(https://i.loli.net/2020/03/22/lBd3CJON7EZtGWy.png)
只有一个time和sign参数。
根据url特征,在apkide中搜索GetVersionInfo,定位到相关函数:
!(https://i.loli.net/2020/03/22/opDNsjTwCgW4nHx.png)
这里我们可以看到getVersionInfoCommand()调用了Lcom/hzsun/utility/Command;->creator,然后我们去找这个creator(下面我直接反编译成java代码,方便一些):
!(https://i.loli.net/2020/03/22/eUdg5xBPHAnTKtm.png)
阅读这段代码,我们可以知道这个函数就是生成请求的参数的,paramArrayOfString1数组为参数的key,arrayOfString数组为参数的value。
这里只通过一个getTime()生成了第一个参数的value:
``` java
arrayOfString = getTime();
```
然后紧接着调用
``` java
generateReq(paramArrayOfString1, arrayOfString)
```
来得到paramArrayOfString1,也就是我们的返回值,因此我们需要的sign值应该在generateReq()中寻找:
!(https://i.loli.net/2020/03/22/pZOd5hMxkLKlrBe.png)
很明显,我们发现了这个getSignString()函数,看名字也就知道是生成sign的,继续跟进这个函数:
!(https://i.loli.net/2020/03/22/PiHMZdKcwXk5D3q.png)
到这里我们就已经找到sign的生成方式了。

## 整理
```java
creator()-->generateReq(paramArrayOfString1, paramArrayOfString2)-->getSignString(paramArrayOfString1, paramArrayOfString2)
```
一路跟进下来:
paramArrayOfString1是参数的key;paramArrayOfString2是参数的value,也就是arrayOfString;
然后我们看看具体的处理方法:
```java
String[] arrayOfString = new String;
    for (int i = 0; i < paramArrayOfString1.length; i++)
    {
      if (i == 0) {
      arrayOfString = paramArrayOfString1;
      }
      if (i > 1) {
      arrayOfString[(i - 1)] = paramArrayOfString1;
      }
    }
```
首先new一个新数组,比结果少一,为啥呢?因为要生成sign是根据其他的参数,所以少的这个一,就是sign。
然后:
```java
StringBuilder localStringBuilder = new StringBuilder();
    int j = paramArrayOfString1.length;
    for (i = 0; i < j; i++)
    {
      Object localObject = paramArrayOfString1;
      for (int k = 0; k < paramArrayOfString1.length; k++) {
      if (((String)localObject).equals(arrayOfString))
      {
          localObject = new StringBuilder();
          ((StringBuilder)localObject).append(paramArrayOfString2);
          ((StringBuilder)localObject).append("|");
          localStringBuilder.append(((StringBuilder)localObject).toString());
          break;
      }
      }
    }
```
咋一看很复杂,其实仔细看,还是很有条理的。循环自然是读取参数的key-value,然后进行拼接,每一对中间用"|"来隔开。
看这个if:
```java
if (((String)localObject).equals(arrayOfString))
```
比较的是啥呢?就是为了找出key对应的value。
循环拼接完然后再拼接一串:
```java
localStringBuilder.append("ok15we1@oid8x5afd@");
```
最后调用:
```java
Encrypt.Md5Encrypt(localStringBuilder.toString());
```
来得到sign,这个md5加密还是开发者自己写的。。。
继续跟进这个Md5Encrypt()函数:
!(https://i.loli.net/2020/03/22/PryHUJZGpnF95WE.png)
这个不用看了,为啥呢?没必要,直接扒出来用就行了,哈哈哈。

## 结论
通过这一系列的跟进、读代码、整理,我们得到了这个sign的生成算法:
读取每个参数的value值,进行拼接,用"|"进行分隔,最后加上"ok15we1@oid8x5afd@",然后用开发者自己写的md5加密算法进行加密得到sign。
举个例子:
!(https://i.loli.net/2020/03/22/h4SkYI9XDu1m8Hx.png)
这里的参数是Time=20200322133104,
拼接出来:
>20200322133104|ok15we1@oid8x5afd@

把加密算法扒出来用(直接copy的,忽略一下缩进):
```java

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Locale;

public class test {
        public static String getHexString(byte[] paramArrayOfByte)
{
    StringBuilder localStringBuilder = new StringBuilder();
    int i = paramArrayOfByte.length;
    for (int j = 0; j < i; j++)
    {
      String str = Integer.toHexString(paramArrayOfByte & 0xFF);
      if (str.length() == 1) {
      localStringBuilder.append('0');
      }
      localStringBuilder.append(str);
    }
    return localStringBuilder.toString();
}
        public static String Md5Encrypt(String paramString)
{
    try
    {
      MessageDigest localMessageDigest = MessageDigest.getInstance("MD5");
      localMessageDigest.update(paramString.getBytes());
      paramString = getHexString(localMessageDigest.digest());
      return paramString;
    }
    catch (NoSuchAlgorithmException paramString1)
    {
      paramString1.printStackTrace();
    }
    return "";
}



    public static void main(String[] args) {
                String test = "20200322133104|ok15we1@oid8x5afd@";
               
      System.out.println(Md5Encrypt(test));
    }
}
```
编译运行:
!(https://i.loli.net/2020/03/22/XDAO1HZwR7QVlJq.png)
跟上面的burp比较一下,验证了我们的结果是对的

后面我验证了一下,其实就是一般的md5 32位加密方式:
!(https://i.loli.net/2020/03/22/SpRQJF6crdUBPm5.png)
是我多虑了。。。
至于多个参数,前面有个排序:
```java
Arrays.sort(paramArrayOfString1);
```
不赘诉了。
这个搞明白了,后续改包测试也就能进行了。
app就不放了,能改签名之后漏洞太多。。。

dazhige 发表于 2020-3-22 21:06

yzlovew 发表于 2020-3-22 20:28
小白不太懂。就是找到签名有什么用处吗,是类似用户id那种吗?

这个app发送数据包的时候会带上签名来校验数据包是否被更改,我们通过找到签名的算法,来使我们可以自由更改数据包进行重放,进行漏洞挖掘等其他操作{:1_918:}

dazhige 发表于 2020-3-23 11:55

氓之嗤嗤 发表于 2020-3-22 22:08
这种情况app应该如何防御?

这个问题问得好,我个人觉得有很多角度:
第一是通过加壳、混淆等手段,让逆向过程变得复杂;
第二是保证后端尽量安全,就算攻击者能够改包重放,也找不到漏洞;
应该还有很多其他方法,我也只是略知一二,看看有没有大佬来解答下{:1_918:}

zncliving 发表于 2020-3-22 17:34

学习了很详细

大_鲸鱼 发表于 2020-3-22 18:50

收藏,谢谢啦

陈红h 发表于 2020-3-22 19:07

非常好非常好非常好非常好

陈红h 发表于 2020-3-22 19:10

非常好非常好非常好非常好

三胖胖胖 发表于 2020-3-22 19:11

感谢分享

prophet5 发表于 2020-3-22 19:16

学习了 还可以自己写sign方法

雨渊 发表于 2020-3-22 20:19

学习了学习了,原来还可以写sign

yzlovew 发表于 2020-3-22 20:28

小白不太懂。就是找到签名有什么用处吗,是类似用户id那种吗?
页: [1] 2
查看完整版本: 记一次逆向某大学app寻找签名算法