本帖最后由 wxyzyou 于 2020-3-22 22:24 编辑
分享一个APP post参数验签校验案例
仅分享逆向分析过程,关键代码都有,还有截图,就不发APP了
APP中有个实名认证功能,需要上传身份证正反和身份证信息
提交POST时,有个keystr参数,是用来校验其它参数信息的,分析目标就是它了
Fiddler抓到的包文,keystr参数看起来像是一个MD5
用的ApkToolAid,对APP进行查壳,反编译
查到结果,未加壳,反编译正常,但是分析代码和文件,发现它是 ReactNative+NodeJS 开发的APP
此种类型的APP大都是把整个项目编译成一个 index.android.bundle 文件,放置于安卓目录结构的 assets路径下
把index.android.bundle文件,后缀名改为js, index.android.js 用相关JS编辑工具打开该文件,进行分析(我用的code)
用Code格式化JS代码,方便查看,直接在JS文件中搜索关键字,POST参数里的几个参数名,定位到相关函数 uploadPersonalInfo (有几个类似的搜索结果,经过对比参数,确定函数位置)
从代码上看keystr是通过参数 t 传递进来的,搜索 uploadPersonalInfo函数名,找到调用位置,发现参数时来自全局对象的 c.NativeModules.HeHe.getKey
再次在JS文件中搜索getKey函数,未找到,那就只有1个可能,该函数在android代码里
我们先确认好,getKey传递的几个参数内容
参数内容,pid, fPhone手机号, IDCard身份证号, Name姓名
getKey(this.props.member.pId, this.props.member.fPhone, this.props.IDCard, this.props.Name)
用ApkToolAid再次生成app的jar包,方便我们使用 jd-gui 工具,查看JAVA明文代码 (反编译的文件是dex类型,需要再次转为jar查看)
在jar包里搜索全局对象里的关键字 HeHe或getKey 定位到目标函数,对比参数数量和类型,确认无误
跟进c.a(paramInteger, paramString1, paramString2, paramString3) 函数体内
可以看到加密方式就在眼前了.(图片和代码在下面)
对代码进行分析时卡在函数内的第一行 new a().a(paramString3),
我们知道参数 paramString3是姓名,这 new a().a 对姓名做了什么处理,这里卡了我比较久
因为反编译的代码并非完全正确,对逆向分析和代码测试都带来了不少困扰
经过代码修复和测试,最后在百度搜索,修复代码生成的 private int[] c = new int[27] 数组内的内容时,无意中发现别人的JAVA生成姓名的首字母代码案例,内容比较一致,就大胆认为,它就是获取姓名首字母
(后经算法生成结果对比,确认了确实是姓名首字母)
此图就是JAVA获取姓名首字母的算法用到的数组
通过对加密函数的分析,发现大量的字符串拼接和MD5调用.
详细分析结果,已经注释到了代码中,转至下方代码查看
易语言加密算法函数
(如易语言引起不适,多多包涵)
下图为加密函数
关键代码,加入了分析结果和注释
[Java] 纯文本查看 复制代码 package com.GeneApp.invokenative;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class c
{
private static final String[] a = { "A", "c", "9", "G", "H", "5", "P", "v", "7", "m", "d" };//密码本a
private static final String[] b = { "l", "I", "1", "O", "o", "w", "u", "#", "n", "q", "E" };//密码本b
private static final String c = "pjjsdsb";
public static String a(Integer paramInteger, String paramString1, String paramString2, String paramString3)
{
//获取姓名的首字母
paramString3 = new a().a(paramString3);
//获取首字母的第一位,姓的字母
String str = paramString3.substring(0, 1);
//获取手机号的第3 6 8位的内容转为整数型,从上方的 b 数组中获取对应 3 6 8的成员,拼接为字符串,赋值给 localObject1
Object localObject1 = new StringBuilder();
((StringBuilder)localObject1).append(b[Integer.parseInt(paramString1.substring(2, 3))]);
((StringBuilder)localObject1).append(b[Integer.parseInt(paramString1.substring(5, 6))]);
((StringBuilder)localObject1).append(b[Integer.parseInt(paramString1.substring(7, 8))]);
localObject1 = ((StringBuilder)localObject1).toString();
//拼接字符串 _和参数pid+6的结果 赋值给 localObject2
//如果pid为 384187 拼接结果为 _384193
Object localObject2 = new StringBuilder();
((StringBuilder)localObject2).append("_");
((StringBuilder)localObject2).append(paramInteger.intValue() + 6);
paramInteger = "";
((StringBuilder)localObject2).append("");
localObject2 = ((StringBuilder)localObject2).toString();
//获取手机号中的第4 5 7的内容转为整数型,从上方的 a 数组中获取对应 4 5 7的成员,拼接为字符串,赋值给 paramString1
Object localObject3 = new StringBuilder();
((StringBuilder)localObject3).append(a[Integer.parseInt(paramString1.substring(3, 4))]);
((StringBuilder)localObject3).append(a[Integer.parseInt(paramString1.substring(4, 5))]);
((StringBuilder)localObject3).append(a[Integer.parseInt(paramString1.substring(6, 7))]);
paramString1 = ((StringBuilder)localObject3).toString();
//身份证的拼接
localObject3 = new StringBuilder();
//获取身份证号的,第4位开始的3个字符的内容 拼接
((StringBuilder)localObject3).append(paramString2.substring(3, 6));
//获取身份证号的,第13位开始的4个字符的内容 拼接
((StringBuilder)localObject3).append(paramString2.substring(12, 16));
//拼接一个#
((StringBuilder)localObject3).append("#");
//获取身份证号的,第10位开始的5个字符的内容 拼接
((StringBuilder)localObject3).append(paramString2.substring(9, 14));
//拼接结果案例 7841174#01011
//拼接结果,赋值给 localObject3
localObject3 = ((StringBuilder)localObject3).toString();
/
/把以上所有拼接结果再次拼接
//拼接结果案例 Z#IO_3841937cH7841174#01011ZBJ
paramString2 = new StringBuilder();
//拼接姓首字母
paramString2.append(str);
//拼接第1个结果
paramString2.append((String)localObject1);
//拼接第2个结果
paramString2.append((String)localObject2);
//拼接第3个结果
paramString2.append(paramString1);
//拼接第4个结果
paramString2.append((String)localObject3);
//拼接姓名首字母
paramString2.append(paramString3);
//拼接结果,赋值给 paramString1
paramString1 = paramString2.toString();
try
{
//最终拼接
paramString2 = new java/lang/StringBuilder;
paramString2.<init>();
//把上面的拼接结果,生成MD5 拼接
paramString2.append(a(paramString1));
//拼接 pjjsdsb
paramString2.append("pjjsdsb");
//拼接案例 ff69dd766a0be2705c5dff6ddf71d96fpjjsdsb
//把最终拼接的结果,生成MD5返回
paramString1 = a(paramString2.toString());
paramInteger = paramString1;
}
catch (Exception paramString1)
{
for (;;) {}
}
return paramInteger;
}
//生成MD5函数
public static String a(String paramString)
throws Exception
{
try
{
paramString = MessageDigest.getInstance("MD5").digest(paramString.getBytes("UTF-8"));
StringBuilder localStringBuilder = new StringBuilder(paramString.length * 2);
int i = paramString.length;
for (int j = 0; j < i; j++)
{
int k = paramString[j] & 0xFF;
if (k < 16) {
localStringBuilder.append("0");
}
localStringBuilder.append(Integer.toHexString(k));
}
return localStringBuilder.toString();
}
catch (UnsupportedEncodingException paramString)
{
throw new RuntimeException("Huh, UTF-8 should be supported?", paramString);
}
catch (NoSuchAlgorithmException paramString)
{
paramString = new RuntimeException("Huh, MD5 should be supported?", paramString);
}
for (;;)
{
throw paramString;
}
}
}
获取姓名首字母的类
[Java] 纯文本查看 复制代码 package com.GeneApp.invokenative;
import java.io.PrintStream;
public class a
{
private char[] a = { 21834, -32083, 25830, 25645, -30978, 21457, 22134, 21704, 21704, 20987, 21888, 22403, 22920, 25343, 21734, 21866, 26399, 28982, 25746, 22604, 22604, 22604, 25366, 26132, 21387, 21277, 24231 };
private char[] b = { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 };
private int[] c = new int[27];
public a()
{
for (int i = 0; i < 27; i++) {
this.c[i] = b(this.a[i]);
}
}
public static void a(String[] paramArrayOfString)
{
paramArrayOfString = new a();
System.out.println(paramArrayOfString.a("是"));
}
private boolean a(int paramInt1, int paramInt2)
{
if (paramInt2 < this.c[paramInt1]) {
return false;
}
for (int i = paramInt1 + 1; i < 26; i++)
{
int[] arrayOfInt = this.c;
if (arrayOfInt[i] != arrayOfInt[paramInt1]) {
break;
}
}
boolean bool1 = true;
boolean bool2 = true;
if (i == 26)
{
if (paramInt2 > this.c[i]) {
bool2 = false;
}
return bool2;
}
if (paramInt2 < this.c[i]) {
bool2 = bool1;
} else {
bool2 = false;
}
return bool2;
}
private int b(char paramChar)
{
Object localObject = new String();
StringBuilder localStringBuilder = new StringBuilder();
localStringBuilder.append((String)localObject);
localStringBuilder.append(paramChar);
localObject = localStringBuilder.toString();
try
{
localObject = ((String)localObject).getBytes("GB2312");
if (localObject.length < 2) {
return 0;
}
int i = localObject[0];
int j = localObject[1];
return (i << 8 & 0xFF00) + (j & 0xFF);
}
catch (Exception localException) {}
return 0;
}
public char a(char paramChar)
{
if ((paramChar >= 'a') && (paramChar <= 'z')) {
return (char)(paramChar - 'a' + 65);
}
if ((paramChar >= 'A') && (paramChar <= 'Z')) {
return paramChar;
}
int i = b(paramChar);
int[] arrayOfInt = this.c;
int j = 0;
if (i < arrayOfInt[0]) {
return '0';
}
while ((j < 26) && (!a(j, i))) {
j++;
}
if (j >= 26) {
return '0';
}
return this.b[j];
}
public String a(String paramString)
{
int i = paramString.length();
int j = 0;
str1 = "";
str2 = "";
for (;;)
{
if (j < i) {}
try
{
StringBuilder localStringBuilder = new java/lang/StringBuilder;
localStringBuilder.<init>();
localStringBuilder.append(str2);
localStringBuilder.append(a(paramString.charAt(j)));
str2 = localStringBuilder.toString();
j++;
}
catch (Exception paramString)
{
for (;;)
{
str2 = str1;
}
}
}
return str2;
}
}
|