首页
社区
课程
招聘
[原创]Android应用完整性保护总结
发表于: 2019-4-23 12:02 17569

[原创]Android应用完整性保护总结

2019-4-23 12:02
17569

相关代码:

相关代码:

FridaServer通过TCP与PC上的Frida进行通信,所以可以检测进程中是否存在 FridaServer进程

在映射的so文件中扫描Frida的库特征,例如:“gadgets”,“LIBFRIDA”等等,这 两个在Frida的所有版本中都有存在

String apkPath=context.getPackageCodePath();//获取Apk的路径
ZipFile zipFile=new ZipFile(apkPath);
ZipEntry dexEntry=ZipFile.getEntry("classes.dex");//读取zip包中的classes.dex文件
String dexCRC=dexEntry.getCrc().toString();//计算dex的CRC文件值
MessageDigest digest=MessageDigest.getInstance("MD5");
byte[] bytes=new byte[1024];
FileInputStream fileinputStream=new FileInputStream(new File(dexPath));
int byteCount;  
while(fileinputStream.read(bytes)!=-1){
 digest.update(bytes,0,byteCount);
}
BigInteger bigInteger=new BigInteger(1,digest.digest());//计算dex的哈希
String MD5=bigInteger.toString();//dex的哈希值
通过APK包的MD5摘要进行判断文件是否被修改过,这里需要计算APK的MD5值,然后将MD5上传到服务器进行判断,或者等待服务器下发原始文件的MD5值,进行比较判断。
String apkPath=context.getPackageCodePath();//获取Apk的路径
MessageDigest digest=MessageDigest.getInstance("MD5");
byte[] bytes=new byte[1024];
FileInputStream fileinputStream=new FileInputStream(new File(apkPath));//读取apk文件
int byteCount;
while(fileinputStream.read(bytes)!=-1){
    digest.update(bytes,0,byteCount);
}
BigInteger bigInteger=new BigInteger(1,digest.digest());//计算apk的哈希
String MD5=bigInteger.toString();//apk的哈希值
<1>.通过android.content.pm.PackageInfo.getPacketInfo()函数获取包信息,然后拿到签名信息
<2>.该签名信息为一个byte数组,可以转换成 X.509格式的证书信息,或者直接对该签名信息求哈希
<3>.现阶段为了增加分析的难度,通常都会将部分代码放到.so文件中。
PackageInfo  packageInfo=content.getPackageManager()
.getPackageInfo(content.getPackageName()
PackageManager.GET_SIGNATURES);//获取包信息
        Signature[] signature=packageInfo.signatures;
        Signature sign=signature[0];
MessageDigest digest=MessageDigest.getInstance("MD5");
digest.update(sign);
Context.getPackageCodePath()  用来获得当前应用程序对应的 apk 文件的路径:/data/app/包名/xxx.apk
Context.getPackageResourcePath()    获取该程序的安装包路径 : /data/app/包名/xxx.apk
packageInfo.applicationInfo.sourceDir  这里面也可以获取apk路径
android.content.pm.PackageInfo.getPacketInfo(ClassName,flags).signatures
这里需要注意 当flags为64的时候,该函数会获取签名信息
所以需要在getpackageinfo下断点,当flags为64的时候,就是获取签名信息
// 1.反射实例化PackageParser对象
Object packageParser = getPackageParser(path);
// 2.反射获取parsePackage方法
Object packageObject = getPackageInfo(path,packageParser);
// 3.调用collectCertificates方法
Method collectCertificatesMethod = packageParser.getClass().       getDeclaredMethod("collectCertificates",packageObject.getClass(),int.class);
collectCertificatesMethod.invoke(packageParser,packageObject,0);
// 4.获取mSignatures属性
Field signaturesField =             packageObject.getClass().getDeclaredField("mSignatures");
signaturesField.setAccessible(true); 
Signature[] mSignatures = (Signature[])  signaturesField.get(packageObject);
PackageManagerService.InstallPackageLI()
PackageParser.collectCertificates()
ApkSignatureVerifier.verify()
ApkSignatureSchemeV3Verifier.verify()
ApkSigningBlockUtils.findSignature()
ApkSigningBlockUtils.findApkSigningBlock()
ApkSigningBlockUtils.findApkSignatureSchemeBlock()
SignatureInfo.SignatureInfo()
ApkSignatureSchemeV3Verifier.verify()
ApkSignatureSchemeV3Verifier.verifySigner()
ApkSignatureSchemeV3Verifier.verifyAdditionalAttributes()
ApkSignatureSchemeV3Verifier.verifyProofOfRotationStruct()
ApkSignatureSchemeV3Verifier.VerifiedProofOfRotation()
de.robv.android.xposed.XposedHelpers类的
静态fieldCache字段        保存被hook的字段信息
静态methodCache字段       保存被hook的方法信息
静态constructorCache字段  保存被hook的类信息
检测内存映射列表中是否包含如下文件:
XposedBridge.so
XposedBridge.jar
handleHookMethod
invokeOriginalMethodNative
注:
1>.在dalvik.system.NativeStart.main方法后出现
  de.robv.android.xposed.XposedBridge.main的方法调用
2>如果Xposed hook了调用栈里的一个方法,
  还会有de.robv.android.xposed.XposedBridge.handleHookedMethod
  和de.robv.android.xposed.XposedBridge.invokeOriginalMethodNative调用
de.robv.android.xposed.XposedBridge类
静态disableHooks   成员变量 
这个字段表示是否对当前应用进行hook操作

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-6-17 11:43 被陌殇编辑 ,原因:
收藏
免费 4
支持
分享
最新回复 (15)
雪    币: 324
活跃值: (419)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
2
2019-4-23 12:03
0
雪    币: 1867
活跃值: (4088)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
3
DexFile dexFile = Class.getDex(); 这个接口获取的dex数据,是原始dex的数据么?还是被优化过之后的,能否用作完整性校验?
2019-4-23 13:24
0
雪    币: 15028
活跃值: (6233)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
java层都被hook了,怎么检测也白搭。
看利用so库检测是不是好些?
2019-4-23 15:43
0
雪    币: 1867
活跃值: (4088)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
5
在来两个
多开检测:

package com.tencent.StubShell;

public class CheckVirtual {
    private static final java.lang.String TAG = "CheckVirtual";

    private static java.lang.String exec(java.lang.String str) {
        java.io.BufferedOutputStream bufferedOutputStream;
        java.lang.Object obj;
        java.lang.Object obj2;
        java.io.BufferedInputStream obj3;
        java.lang.Throwable th;
        java.lang.Throwable th2;
        java.lang.String str2 = null;
        java.lang.Process exec;
        try {
            exec = java.lang.Runtime.getRuntime().exec("sh");
            try {
                bufferedOutputStream = new java.io.BufferedOutputStream(exec.getOutputStream());
            } catch (java.lang.Exception e) {
                obj3 = str2;
                obj2 = str2;
                if (bufferedOutputStream != null) {
                }
                if (obj3 != null) {
                }
                if (exec != null) {
                }
                return str2;
            } catch (java.lang.Throwable th3) {
                obj2 = str2;
                java.lang.String str3 = str2;
                th = th3;
                obj3 = str3;
                if (bufferedOutputStream != null) {
                }
                if (obj3 != null) {
                }
                if (exec != null) {
                }
                throw th;
            }
            try {
                obj3 = new java.io.BufferedInputStream(exec.getInputStream());
                try {
                    bufferedOutputStream.write(str.getBytes());
                    bufferedOutputStream.write(10);
                    bufferedOutputStream.flush();
                    bufferedOutputStream.close();
                    exec.waitFor();
                    str2 = getStrFromBufferInputSteam(obj3);
                    if (bufferedOutputStream != null) {
                        try {
                            bufferedOutputStream.close();
                        } catch (java.io.IOException e2) {
                            e2.printStackTrace();
                        }
                    }
                    if (obj3 != null) {
                        try {
                            obj3.close();
                        } catch (java.io.IOException e3) {
                            e3.printStackTrace();
                        }
                    }
                    if (exec != null) {
                        exec.destroy();
                    }
                } catch (java.lang.Exception e4) {
                } catch (Throwable th4) {
                    th = th4;
                    if (bufferedOutputStream != null) {
                    }
                    if (obj3 != null) {
                    }
                    if (exec != null) {
                    }
                    throw th;
                }
            } catch (java.lang.Exception e5) {
                obj3 = str2;
                if (bufferedOutputStream != null) {
                    try {
                        bufferedOutputStream.close();
                    } catch (java.io.IOException e22) {
                        e22.printStackTrace();
                    }
                }
                if (obj3 != null) {
                    try {
                        obj3.close();
                    } catch (java.io.IOException e32) {
                        e32.printStackTrace();
                    }
                }
                if (exec != null) {
                    exec.destroy();
                }
                return str2;
            } catch (java.lang.Throwable th32) {
                th2 = th32;
                obj3 = str2;
                th = th2;
                if (bufferedOutputStream != null) {
                    try {
                        bufferedOutputStream.close();
                    } catch (java.io.IOException e222) {
                        e222.printStackTrace();
                    }
                }
                if (obj3 != null) {
                    try {
                        obj3.close();
                    } catch (java.io.IOException e322) {
                        e322.printStackTrace();
                    }
                }
                if (exec != null) {
                    exec.destroy();
                }
                throw th;
            }
        } catch (java.lang.Exception e6) {
            exec = str2;
            obj3 = str2;
            bufferedOutputStream = str2;
        } catch (java.lang.Throwable th5) {
            obj3 = str2;
            bufferedOutputStream = str2;
            th2 = th5;
            exec = str2;
            th = th2;
            if (bufferedOutputStream != null) {
            }
            if (obj3 != null) {
            }
            if (exec != null) {
            }
            throw th;
        }
        return str2;
    }

    private static java.lang.String getStrFromBufferInputSteam(java.io.BufferedInputStream bufferedInputStream) {
        if (bufferedInputStream == null) {
            return "";
        }
        byte[] bArr = new byte[512];
        java.lang.StringBuilder stringBuilder = new java.lang.StringBuilder();
        int read;
        do {
            try {
                read = bufferedInputStream.read(bArr);
                if (read > 0) {
                    stringBuilder.append(new java.lang.String(bArr, 0, read));
                    continue;
                }
            } catch (java.lang.Exception e) {
                e.printStackTrace();
            }
        } while (read >= 512);
        return stringBuilder.toString();
    }

    public static java.lang.String getUidStrFormat() {
        java.lang.String exec = exec("cat /proc/self/cgroup");
        if (exec == null || exec.length() == 0) {
            return null;
        }
        int lastIndexOf = exec.lastIndexOf("uid");
        int lastIndexOf2 = exec.lastIndexOf("/pid");
        if (lastIndexOf < 0) {
            return null;
        }
        if (lastIndexOf2 <= 0) {
            lastIndexOf2 = exec.length();
        }
        try {
            if (!isNumber(exec.substring(lastIndexOf + 4, lastIndexOf2).replaceAll("\n", ""))) {
                return null;
            }
            return java.lang.String.format("u0_a%d", new java.lang.Object[]{java.lang.Integer.valueOf(java.lang.Integer.valueOf(exec.substring(lastIndexOf + 4, lastIndexOf2).replaceAll("\n", "")).intValue() - 10000)});
        } catch (java.lang.Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static boolean isNumber(java.lang.String str) {
        if (str == null || str.length() == 0) {
            return false;
        }
        for (int i = 0; i < str.length(); i++) {
            if (!java.lang.Character.isDigit(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    public static boolean isRunInVirtual() {
        java.lang.CharSequence uidStrFormat = getUidStrFormat();
        java.lang.String exec = exec("ps");
        if (exec == null || exec == "") {
            return false;
        }
        java.lang.String[] psLine = exec.split("\n");
        if (psLine == null || psLine.length <= 0) {
            return false;
        }
        int i = 0;
        for (int i2 = 0; i2 < psLine.length; i2++) {
            if (psLine[i2].contains(uidStrFormat)) {
                int lastIndexOf = psLine[i2].lastIndexOf(" ");
                java.lang.String substring = psLine[i2].substring(lastIndexOf <= 0 ? 0 : lastIndexOf + 1, psLine[i2].length());
                if (new java.io.File(java.lang.String.format("/data/data/%s", new java.lang.Object[]{substring, java.util.Locale.CHINA})).exists()) {
                    i++;
                }
            }
        }
        return i > 1;
    }
}

xposed检测
package com.tencent.StubShell;

public class XposedCheck {
    private static final java.lang.String XPOSED_BRIDGE = "de.robv.android.xposed.XposedBridge";
    private static final java.lang.String XPOSED_HELPERS = "de.robv.android.xposed.XposedHelpers";

    private static boolean isXposedExistByThrow() {
        try {
            throw new java.lang.Exception("exe xp");
        } catch (java.lang.Exception e) {
            for (java.lang.StackTraceElement className : e.getStackTrace()) {
                if (className.getClassName().contains(XPOSED_BRIDGE)) {
                    return true;
                }
            }
            return false;
        }
    }

    private static boolean isXposedExists() {
        try {
            java.lang.ClassLoader.getSystemClassLoader().loadClass(XPOSED_HELPERS).newInstance();
            try {
                java.lang.ClassLoader.getSystemClassLoader().loadClass(XPOSED_BRIDGE).newInstance();
                return true;
            } catch (java.lang.InstantiationException e) {
                e.printStackTrace();
                return true;
            } catch (java.lang.IllegalAccessException e2) {
                e2.printStackTrace();
                return true;
            } catch (java.lang.ClassNotFoundException e3) {
                e3.printStackTrace();
                return false;
            }
        } catch (java.lang.InstantiationException e4) {
            e4.printStackTrace();
            return true;
        } catch (java.lang.IllegalAccessException e22) {
            e22.printStackTrace();
            return true;
        } catch (java.lang.ClassNotFoundException e32) {
            e32.printStackTrace();
            return false;
        }
    }

    public static boolean tryShutdownXposed() {
        if (!isXposedExistByThrow()) {
            return true;
        }
        try {
            java.lang.reflect.Field declaredField = java.lang.ClassLoader.getSystemClassLoader().loadClass(XPOSED_BRIDGE).getDeclaredField("disableHooks");
            declaredField.setAccessible(true);
            declaredField.set(null, java.lang.Boolean.valueOf(true));
            return true;
        } catch (java.lang.NoSuchFieldException e) {
            e.printStackTrace();
            return false;
        } catch (java.lang.ClassNotFoundException e2) {
            e2.printStackTrace();
            return false;
        } catch (java.lang.IllegalAccessException e3) {
            e3.printStackTrace();
            return false;
        }
    }
}



2019-4-23 17:03
1
雪    币: 1110
活跃值: (3374)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
6
方向是对的,但方法还需要考量一下,至少不要在 JVM 中操作,涉及到的 I/O 操作也最好从系统调用走。
2019-4-24 17:49
1
雪    币: 73
活跃值: (923)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
hook来hook去也没啥意思
2019-4-25 02:25
0
雪    币: 355
活跃值: (15)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
8
辛苦楼主,赞一个!
2019-4-26 14:34
0
雪    币: 38
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
辛苦楼主,总结的很好
2019-6-14 00:50
0
雪    币: 574
活跃值: (405)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
赞一个,长知识了
2019-6-14 14:51
0
雪    币: 102
活跃值: (2165)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
11
mark
2019-6-14 18:05
0
雪    币: 109
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
需要加固可以私信哟,专业的虚机技术,基于LLVM编译器进行改造的IR指令。
2019-6-19 15:32
0
雪    币: 9686
活跃值: (1675)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
13
写得好,收藏一下好好学习
2019-6-25 11:33
0
雪    币: 15
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
make下,好好学习
2019-12-10 18:59
0
雪    币: 1892
活跃值: (1618)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
15
Amun 方向是对的,但方法还需要考量一下,至少不要在 JVM 中操作,涉及到的 I/O 操作也最好从系统调用走。
自己写.S吗?有没有什么具体思路可以分享一下的?
2020-2-7 09:34
0
雪    币: 376
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16

由于Android 稍高版本在安装apk的过程中会把classes.dex 抠出来转化为odex或者vdex等其他格式的优化文件,原apk中无classes.dex,

...,故无法校验classes.dex,也无法校验整个APK文件哈希值


楼主,你说原apk中无classes.dex是什么意思啊?

为什么你第3点又说对dex文件做MD5校验呢?

Android从什么版本开始这样做的?


---------------------------

找到一个资料,主要是和ART 的编译选项有关

LOCAL_DEX_PREOPT 支持分别使用值“true”和“false”来启用和停用预先优化功能。此外,如果不想在预先优化过程中将 classes.dex 文件从 APK 或 JAR 文件中剥离出来,则可以指定“nostripping”。通常情况下,此文件会被剥离出来,因为在进行预先优化之后将不再需要该文件;但若要使第三方 APK 签名仍保持有效,则必须使用最后这个选项。



最后于 2020-7-17 18:58 被peparam编辑 ,原因:
2020-7-16 17:21
0
游客
登录 | 注册 方可回帖
返回
//