起因
在测试app的过程中,经常遇到有签名校验的情况,比如修改某个数据包中的参数,会出现签名不通过:
这个时候就得去找找这个sign参数,看看能不能由我们自己生成,从而修改参数进行重放。不然没法儿继续测了。
寻找
开始
老规矩,先查壳:
无壳,直接上逆向工具,我这里用的是apkide:
入口
从哪里入手呢?这里我用夜神安装好目标app后,随便点了几下,找到一个相对比较简单的数据包:
只有一个time和sign参数。
根据url特征,在apkide中搜索GetVersionInfo,定位到相关函数:
这里我们可以看到getVersionInfoCommand()调用了Lcom/hzsun/utility/Command;->creator,然后我们去找这个creator(下面我直接反编译成java代码,方便一些):
阅读这段代码,我们可以知道这个函数就是生成请求的参数的,paramArrayOfString1数组为参数的key,arrayOfString数组为参数的value。
这里只通过一个getTime()生成了第一个参数的value:
arrayOfString[0] = getTime();
然后紧接着调用
generateReq(paramArrayOfString1, arrayOfString)
来得到paramArrayOfString1,也就是我们的返回值,因此我们需要的sign值应该在generateReq()中寻找:
很明显,我们发现了这个getSignString()函数,看名字也就知道是生成sign的,继续跟进这个函数:
到这里我们就已经找到sign的生成方式了。
整理
creator()-->generateReq(paramArrayOfString1, paramArrayOfString2)-->getSignString(paramArrayOfString1, paramArrayOfString2)
一路跟进下来:
paramArrayOfString1是参数的key;paramArrayOfString2是参数的value,也就是arrayOfString;
然后我们看看具体的处理方法:
String[] arrayOfString = new String[paramArrayOfString1.length - 1];
for (int i = 0; i < paramArrayOfString1.length; i++)
{
if (i == 0) {
arrayOfString[i] = paramArrayOfString1[i];
}
if (i > 1) {
arrayOfString[(i - 1)] = paramArrayOfString1[i];
}
}
首先new一个新数组,比结果少一,为啥呢?因为要生成sign是根据其他的参数,所以少的这个一,就是sign。
然后:
StringBuilder localStringBuilder = new StringBuilder();
int j = paramArrayOfString1.length;
for (i = 0; i < j; i++)
{
Object localObject = paramArrayOfString1[i];
for (int k = 0; k < paramArrayOfString1.length; k++) {
if (((String)localObject).equals(arrayOfString[k]))
{
localObject = new StringBuilder();
((StringBuilder)localObject).append(paramArrayOfString2[k]);
((StringBuilder)localObject).append("|");
localStringBuilder.append(((StringBuilder)localObject).toString());
break;
}
}
}
咋一看很复杂,其实仔细看,还是很有条理的。循环自然是读取参数的key-value,然后进行拼接,每一对中间用"|"来隔开。
看这个if:
if (((String)localObject).equals(arrayOfString[k]))
比较的是啥呢?就是为了找出key对应的value。
循环拼接完然后再拼接一串:
localStringBuilder.append("ok15we1@oid8x5afd@");
最后调用:
Encrypt.Md5Encrypt(localStringBuilder.toString());
来得到sign,这个md5加密还是开发者自己写的。。。
继续跟进这个Md5Encrypt()函数:
这个不用看了,为啥呢?没必要,直接扒出来用就行了,哈哈哈。
结论
通过这一系列的跟进、读代码、整理,我们得到了这个sign的生成算法:
读取每个参数的value值,进行拼接,用"|"进行分隔,最后加上"ok15we1@oid8x5afd@",然后用开发者自己写的md5加密算法进行加密得到sign。
举个例子:
这里的参数是Time=20200322133104,
拼接出来:
20200322133104|ok15we1@oid8x5afd@
把加密算法扒出来用(直接copy的,忽略一下缩进):
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[j] & 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));
}
}
编译运行:
跟上面的burp比较一下,验证了我们的结果是对的
后面我验证了一下,其实就是一般的md5 32位加密方式:
是我多虑了。。。
至于多个参数,前面有个排序:
Arrays.sort(paramArrayOfString1);
不赘诉了。
这个搞明白了,后续改包测试也就能进行了。
app就不放了,能改签名之后漏洞太多。。。