加固了就安全了?几个措施让你的 Android 应用更安全

Android-APK:为何你的应用老是被破解,该如何有效地做签名校验 /** 做普通的签名校验 */ private boolean doNormalSignCheck() { String trueSignMD5 = “d0add9987c7c84aeb7198c3ff26ca152”; String nowSignMD5 = “”; try { // 得到签名的MD5 PackageInfo packageInfo = getPackageManager().getPackageInfo( getPackageName(), PackageManager.GET_SIG 阅读详情

1、背景

最近新开发了一款工具类型的软件 移动工具箱,然而某天下午忽然群里来了一个不速之客说我的软件被破解了。虽然,该软件无需付费并且没有广告(很良心 😃 ),也进行了安全加固,但是还是很轻易得被别人破解了。现象是,启动页换成了别人的页面,需要用户点击页面上的按钮分享几次破解者的信息才能进入应用。并且,每次打开应用都是如此。

看到这我的心情还是非常复杂的。一开始觉得自己做个应用都赚不到钱,还要被这些人利用。本是同根生,相煎何太急呀。然而,转念一想,这里有些东西还是可以学习一下,于是我去他们的链接下载了软件,本着学习的精神进行了一波分析并制定了几个方案来加强应用安全。

2、分析

2.1 检查破解应用签名

决定要进行分析之后,我首先想到的就是去检查下应用的签名,这里使用 keytool 即可,指令如下:

keytool -printcert -jarfile 你的apk地址 

获取的结果如下,

很显然,应用的签名已经发生了变化,应用被别人二次打包了。实际上,我的应用是在 360 上面进行了软件加固(免费版),但是还是如此轻松地被别人完成了二次打包。

2.2 签名校验

如果仅仅是签名发生了变化,那么解决方式倒也简单。在应用内部增加一个签名校验就可以了。不过签名校验也有需要注意的地方。其一,签名校验可以放在 Java 层来完成,也可以放在 native 层通过 C++ 来完成。其二,在应用内部进行整个数字签名的校验还是部分校验。这是因为如果写入完整的数字签名很容易被别人发现,即便写入到 so 中,写入完整字符串比部分更容易被别人找到。

在 Java 中,你可以按照下面这种方式获取应用的数字签名。我已经把相关的方法写成了工具类,你可以在 Github 上面获取 AppUtils:

 public static String getAppSignatureMD5(final String packageName) {
        return getAppSignatureHash(packageName, "MD5");
    }

    private static String getAppSignatureHash(final String packageName, final String algorithm) {
        if (StringUtils.isSpace(packageName)) return "";
        Signature[] signature = getAppSignature(packageName);
        if (signature == null || signature.length <= 0) return "";
        return StringUtils.bytes2HexString(EncryptUtils.hashTemplate(signature[0].toByteArray(), algorithm))
                .replaceAll("(?<=[0-9A-F]{2})[0-9A-F]{2}", ":$0");
    } 

如果是使用 C++ 进行签名校验可以使用下面的方法,

jbyteArray getSignatureByteArray(JNIEnv *env, jobject context, jstring algorithm) {
    jclass context_clazz = env->GetObjectClass(context);
    // context.getPackageManager()
    jmethodID methodID_getPackageManager = env->GetMethodID(context_clazz,
            "getPackageManager", "()Landroid/content/pm/PackageManager;");
    jobject packageManager = env->CallObjectMethod(context, methodID_getPackageManager);
    jclass packageManager_clazz = env->GetObjectClass(packageManager);
    jmethodID methodID_getPackageInfo = env->GetMethodID(packageManager_clazz,
            "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    jmethodID methodID_getPackageName = env->GetMethodID(context_clazz,
            "getPackageName", "()Ljava/lang/String;");
    // context.getPackageName()
    jobject application_package_obj = env->CallObjectMethod(context, methodID_getPackageName);
    jstring application_package = static_cast<jstring>(application_package_obj);
    const char* package_name = env->GetStringUTFChars(application_package, JNI_FALSE);
    __android_log_print(ANDROID_LOG_DEBUG, "DiDiDaDa", "Package Name : %s", package_name);
    // packageManager->getPackageInfo(packageName, GET_SIGNATURES);
    jobject packageInfo = env->CallObjectMethod(packageManager, methodID_getPackageInfo, application_package_obj, /*GET_SIGNATURES*/ 64);
    jclass packageinfo_clazz = env->GetObjectClass(packageInfo);
    jfieldID fieldID_signatures = env->GetFieldID(packageinfo_clazz, "signatures", "[Landroid/content/pm/Signature;");
    jobjectArray signature_arr = (jobjectArray)env->GetObjectField(packageInfo, fieldID_signatures);
    // packageInfo.signatures[0]
    jobject signature = env->GetObjectArrayElement(signature_arr, 0);
    // signature.toByteArray()
    jclass signature_clazz = env->GetObjectClass(signature);
    jmethodID signature_toByteArray = env->GetMethodID(signature_clazz,"toByteArray", "()[B");
    jbyteArray sig_bytes = (jbyteArray) env->CallObjectMethod(signature, signature_toByteArray);
    // MessageDigest.getInstance("SHA1")
    jclass message_digest_clazz = env->FindClass("java/security/MessageDigest");
    jmethodID message_digest_getInstance = env->GetStaticMethodID(message_digest_clazz,
            "getInstance","(Ljava/lang/String;)Ljava/security/MessageDigest;");
    const char* algorithm_bytes = env->GetStringUTFChars(algorithm, JNI_FALSE);
    jstring algorithm_name = env->NewStringUTF(algorithm_bytes);
    jobject message_object = env->CallStaticObjectMethod(message_digest_clazz, message_digest_getInstance, algorithm_name);
    jthrowable exception = env->ExceptionOccurred();
    env->ExceptionClear();
    if (exception) return NULL;
    // sha1.update()
    jmethodID message_digest_update = env->GetMethodID(message_digest_clazz,"update","([B)V");
    env->CallVoidMethod(message_object, message_digest_update, sig_bytes);
    // sha1.digest()
    jmethodID digest = env->GetMethodID(message_digest_clazz, "digest", "()[B");
    jbyteArray sha1_bytes = (jbyteArray) env->CallObjectMethod(message_object, digest);
    return sha1_bytes;
} 

这里使用的是 CMake 进行编译,使用 Android Studio 进行开发。上面的逻辑很简单,就是把之前的 java 层获取签名的方法照搬到了 native 方法里。而且,借助于 Android Studio 进行开发,可以帮助我们减轻很多工作量,比如方法的映射关系就不需要你一个一个得进行对比,可以通过提示直接完成转换。当然,上面只是使用 java 的类完成了获取签名的字节数组的逻辑,进一步转换成为 hash 字符串还要进行其他操作。这里就不把代码贴出来了。

因为除了进行这些签名校验我还加了其他的安全措施,所以这部分代码不便开源。不过,我已经把一些基础功能的代码和环境配置打包成了压缩文件,你可以到我公众号里回复【安卓签名校验】获取完整的代码。

拿到了签名之后当然是进行签名校验了,基本的字符串比较即可。当然,你可以进行部分匹配,这样增加了破解的难度。

2.3 签名校验就安全了?

增加了上述的签名校验只不过是第一步,也是比较常规的安全操作。但是这样操作未必就能防止其他用户破解。进一步进行反编译,我又发现一些新的东西。破解包的目录结构挺有意思的,

相比于没有破解的包,在 lib 下面多了两个文件,这里的 libarm.so 的内容尚不清楚,这里的 libhook.apk 是一个 apk 文件,并且签名跟我的原始的包的签名一致,但是 apk 内容并不是我的完整包。所以,我猜测这里的 Apk 文件有其他用途,并且仅仅使用签名校验并不安全。因为别人可以读取你的应用的签名,然后通过某种方式进行伪造,让获取签名的方法返回的结果不是安装包真实的签名而是你的真实包的签名。这里的 apk 很有可能就是用来获取签名的。

于是,我进一步对应用进行资源反编译,这里使用 apktool 来完成,这里如下:

apktool d 破解应用.apk 

于是我发现启动应用是被篡改掉了的,这里的启动应用已经被修改为 arm.SignerPro,并且这里的启动类已经被修改为 SplashActivity:

然后,我们将 Apk 解压之后对 dex 文件进行反编译。这里使用的是 d2j-dex2jar 来完成。命令如下,

d2j-dex2jar classes.dex 

然后,我们根据反编译的结果,查看启动 Application,

这里的 Application 继承了 InvocationHandler. InvocationHandler 相比大家都不陌生,Retrofit 就是通过在 InvocationHandler 实现的代理来解析请求参数,并根据注解和入参来完成 OkHttp 请求的。我们找到该接口的实现方法 invoke(). 从上图中很明显得可以看出,我们的 getPackageInfo() 方法有很大可能被别人 hook 了。这个方法就是上面说的用来获取应用签名的方法,这里会对 getPackageInfo() 方法进行处理,并返回 signs 作为签名的获取结果而不是应用真实的签名结果。

当然,上面都是猜着,因为他们也只破解了第一个版本的包,第二个版本的包我加了新的安全措施,并且悄悄地增加了几个隐藏的页面用来在应用内获取签名和其他的信息来验证我的想法。

不管怎么说,仅仅加上签名校验很可能是不安全的!

2.4 其他安全措施

其实即便他们 hook 了获取签名的 getPackageInfo() 方法我们仍然有许多措施来应对。他们这里的 hook 有几个地方可以入手,

  1. 首先,我们并没看到 signs 的赋值操作,我猜测是通过 native 方法来完成的赋值,他们如果每次都尝试返回 signs 的话并没有屡次对 signs 进行重新赋值。而从 PackageManager 中多次读取到的 Signature 对象应该是不同的。所以,如果我们多次读取签名的时候返回的同一对象,是不是可以认为该方法被别人 hook 了呢?

  2. 另外,这里 hook 了 getPackageInfo() 方法。只是我们无法判断他们 hook 的范围,我们可以尝试自定义一个 getPackageInfo() 方法,然后自定义返回的对象类型,如果返回的不是我们指定的类型,就可以得出结论,我们的 getPackageInfo() 方法被别人 hook 了。比如,

    public class Fake {
    
        public void getPackageInfo() { }
    
        public FakePackageInfo getPackageInfo(@NonNull String packageName, int flags) throws PackageManager.NameNotFoundException {
            return new FakePackageInfo();
        }
    
        public FakePackageInfo getPackageInfo(@NonNull VersionedPackage versionedPackage, int flags) throws PackageManager.NameNotFoundException {
            return new FakePackageInfo();
        }
    
        public static class FakePackageInfo { }
    } 
    
    

    当然,如果他们只 hook 了 PackageManager 的方法,这个就不适用了。

  3. 除了签名校验,我们还可以检查其他项。比如检查 Application 是否是我们的启动 Application,可以使用如下代码获取应用的 Application 并进行比较,

    public static String getApplicationName(final String pkgName) {
        if (StringUtils.isSpace(pkgName)) return null;
        try {
            PackageManager pm = UtilsApp.getApp().getPackageManager();
            ApplicationInfo ai = pm.getApplicationInfo(pkgName, 0);
            return ai == null ? null : ai.className;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    } 
    
    
  4. 此外,我们还可以对应用的启动 Activity 进行校验。因为他们对我们的应用的启动 Activity 进行了修改,于是我们启动 Activity 进行了修改,所以这也可以作为一个应对措施:

    public static String getAppLauncher(final String pkgName) {
        if (StringUtils.isSpace(pkgName)) return null;
        try {
            PackageManager pm = UtilsApp.getApp().getPackageManager();
            PackageInfo pi = pm.getPackageInfo(pkgName, 0);
            if (pi == null) return null;
            Intent resolveIntent = new Intent(Intent.ACTION_MAIN, null);
            resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER);
            resolveIntent.setPackage(pi.packageName);
            List<ResolveInfo> resolveInfoList = pm.queryIntentActivities(resolveIntent, 0);
            ResolveInfo resolveInfo = resolveInfoList.iterator().next();
            if (resolveInfo != null) {
                return resolveInfo.activityInfo.name;
            }
            return null;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    } 
    
    
  5. 上面提到的也只是一部分方法,当然还有其他的可以操作的方法。我们也可以在对类似的破解事件进行多分析来总结,尝试从多个维度做好安全防护。

3、总结

上面是我在应对破解方面的一些简单的总结,除了进行常规的加固之外,还进行了其他的维度的校验。当然,我其实对逆向和破解的了解并不是那么深入,这里权当抛砖引玉了。

随着信息技术的快速发展和互联网的普及,IT行业 成为一个非常热门的领域,也是目前就业前景非常广阔的领域之一。

IT行业是一个非常庞大和多样化的行业,包括软件开发、网络安全、数据分析、云计算等等领域。因此,就业前景也是非常广泛和多样化的,不同的领域和职位都具有不同的就业前景和发展机会。

在软件开发领域,由于软件已经成为现代社会不可或缺的一部分,因此对软件开发人才的需求也越来越大。特别是在移动应用、大数据、人工智能等领域,软件开发人才的需求更是迅速增长。因此,软件开发人才的就业前景非常广阔,尤其是那些熟练掌握多种编程语言和技术的人才。

有幸看到一篇这样一组数据。

根据这些我不得总结,it行业确实人才紧缺,

行业发展空间大,岗位非常多

网络安全行业产业以来,随即新增加了几十个网络安全行业岗位︰网络安全专家、网络安全分析师、安全咨询师、网络安全工程师、安全架构师、安全运维工程师、渗透工程师、信息安全管理员、数据安全工程师、网络安全运营工程师、网络安全应急响应工程师、数据鉴定师、网络安全产品经理、网络安全服务工程师、网络安全培训师、网络安全审计员、威胁情报分析工程师、灾难恢复专业人员、实战攻防专业人员…

职业增值潜力大

网络安全专业具有很强的技术特性,尤其是掌握工作中的核心网络架构、安全技术,在职业发展上具有不可替代的竞争优势。

随着个人能力的不断提升,所从事工作的职业价值也会随着自身经验的丰富以及项目运作的成熟,升值空间一路看涨,这也是为什么受大家欢迎的主要原因。

从某种程度来讲,在网络安全领域,跟医生职业一样,越老越吃香,因为技术愈加成熟,自然工作会受到重视,升职加薪则是水到渠成之事。

这里我整合并且整理成了一份【282G】的网络安全/红客技术从零基础入门到进阶资料包,需要的小伙伴文末免费领取哦,无偿分享!!!

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

😝朋友们如果有需要的话,可以V扫描下方二维码联系领取~

!
在这里插入图片描述

1️⃣零基础入门

学习路线

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

image

路线对应学习视频

同时每个成长路线对应的板块都有配套的视频提供:

image-20231025112050764

2️⃣视频配套工具&国内外网安书籍、文档
工具

视频

image1

书籍

image2

资源较为敏感,未展示全面,需要的下面获取

3️⃣面试集锦

面试资料

在这里插入图片描述在这里插入图片描述

简历模板

在这里插入图片描述

在这里插入图片描述

因篇幅有限,资料较为敏感仅展示部分资料,添加上方即可获取👆

------ 🙇‍♂️ 本文转自网络,如有侵权,请联系删除 🙇‍♂️ ------

【干货分享】安卓加固原理分享 之前写过几篇,感兴趣可以看一下。最近攒了一下收集的一些资料,打算简单写一下安卓加固相关的内容。今天先简单写一篇安卓加固原理的分享。 阅读详情

相关推荐

Android应用加固原理

他是Android系统的可执行文件,包含应用程序的全部操作指令以及运行时数据由于dalvik是一种针对嵌入式设备而特殊设计的java虚拟机,所以dex文件与标准的class文件在结构设计上有着本质的区别当java程序编译成class后,还需要使用dx工具将所有的class文件整合到一个dex文件,目的是其中各个类能够共享数据,在一定程度上降低了冗余,同时也是文件结构更加经凑,实验表明,dex文件是传统jar文件大小的50%左右风险名称风险解决方案1.App防止反编译。

2501_90401822的博客 1224

【Android 安全】Android 应用 APK 加固总结 ( 加固原理 | 应用加固完整的实现方案 | 源码资源 )

一、 APK 加固原理、 1、 Android 应用反编译、 2、 ProGuard 混淆、 3、 多 dex 加载原理、 4、 代理 Application 开发、 5、Java 工具开发、 6、Application 替换、

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 1万+

Claude Skills:不是更聪明,而是更“像专家”的 Agent 养成方法

Claude Skills 不是在告诉 Agent“你能做什么”, 而是在约束它“你应该如何思考、如何判断、如何给结论”。

测试者家园 1293

Android各代加固总结

本文简单地总结了一下Android加固的背景和发展历史,也介绍了一些目前常见的脱壳工具。对于so加密的情况,目前也有许多方法应对,比如ida动态调试dump内存中的so,GG模拟器dump内存,frida dump so,unidbg等等。对于混淆的so,也有jnitrace,unidbg,还有hluwa大佬的大作obpo等工具辅助我们分析。除此以外还有还有一些优秀的脱壳工具如Fart等,我也没有再做介绍了。如有问题可关注公众号:移动安全星球。

weixin_42454310的博客 2557

Aveva Marine C# 二次开发入门001

1# 引用 C:\AVEVA\Marine\OH12.1.SP4\Aveva.ApplicationFramework.dll C:\AVEVA\Marine\OH12.1.SP4\Aveva.ApplicationFramework.Presentation.dll 2# 引用命名空间, using Aveva.ApplicationFramework.Presentation;usi...

weixin_30245867的博客 1911

解决/usr/lib64/libstdc++.so.6和/lib64/libc.so.6版本过低问题

在CentOS7.0中编译好MySQL5.7.14之后,然后在CentOS6.5中初始化MySQL,执行:/usr/local/mysql/bin/mysqld --version出现如下问题:./bin/mysqld: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by ./bin/mysqld)

poxiaonie的博客 2万+

移动安全之 APP 加固:保障移动应用安全的重要手段

无论是从防御者的角度防止 APP 被攻击,还是从攻击者的角度尝试突破加固措施,都体现了 APP 加固在移动安全领域的重要性。在 APP 与服务器之间的通信中,如果使用 https 协议,要对 SSL 证书进行校验,确保通信的安全性。对 APP 中的时间戳进行验证,确保数据的时效性和真实性。在 APP 进行一些敏感操作时,如登录、转账等,要对环境进行清场,确保没有恶意程序在后台运行,影响操作的安全性。对于 APP 中的手势密码,要将其作为一个全局的标志,在 APP 的各个相关模块中进行统一的管理和验证。

m0_57836225的博客 4990

400G光模块测试全攻略:从PCS层到CMIS合规的避坑指南

本文为网络设备测试工程师和光通信研发人员提供了一份详尽的400G光模块测试实战指南。文章深入剖析了从PCS层信号完整性、FEC容限到CMIS管理接口合规性测试的全流程,并分享了ANLT协议分析、SerDes眼图调试以及系统级流量压力测试中的核心技巧与常见避坑点,旨在帮助读者构建一套高效、精准的测试体系,确保高速光模块的可靠性与性能。

weixin_29300931的博客 210

Android应用安全加固

前言:Android开发者常常面临的一个安全问题就是防反编译破解、 防二次打包。因此,App在上线某些应用市场比如腾讯的应用宝时会要求你的应用apk进行安全加固,也就是和Windows平台exe程序的加壳一样。 一、为什么要对Android应用加固? 由于安卓APP是基于Java的,所以极容易被破解,一个不经过加固的APP犹如裸奔一样,毫无防备。之前曾有新闻报道,一些专职的APP打包黑产就是专...

何哥的博客 7294

新买的USB无线网卡插上没反应?保姆级排查指南:从设备管理器到网络列表

本文提供USB无线网卡无法识别的全流程排查手册,涵盖硬件检测、驱动管理和网络配置三大维度。重点解析设备管理器中的设备状态诊断和驱动解决方案,特别适用于Realtek芯片的无线网卡(如RTL8188GU、RTL8812BU等),帮助用户快速解决网卡插上无反应的问题。

weixin_29876033的博客 501

别再只盯着充电功率了!从PWM信号到PLC通信,一文读懂CCS2充电桩如何跟你家电动车“聊天”

本文深入解析了CCS2充电桩与电动汽车之间的12次关键通信交互,从硬件握手到PLC加密通信,再到充电参数动态协商与安全校验。通过详细的技术细节和实时交互逻辑,揭示了现代快充技术如何实现高效安全的能量传输,帮助用户理解充电过程中的核心技术要点与故障排查方法。

weixin_30527323的博客 141

Android安全开发:应用加固与反编译防护

Android安全开发中,应用加固与反编译防护是关键环节。通过代码混淆、资源加密、防篡改等措施,可以显著提升应用的安全性。本文从多个方面详细阐述了应用加固与反编译防护的原理、工具和实践案例,希望对开发者有所帮助。选择合适的加固工具:根据应用的特点和需求,选择合适的加固工具。定期更新加固策略:随着技术的发展,攻击手段也在不断更新,开发者应定期更新加固策略,以应对新的安全威胁。关注行业动态:关注Android安全领域的最新动态,了解最新的安全技术和防护措施。

2501_92487820的博客 1085

Flatten 层的深度理解与运用

Flatten 层的深度理解与运用

你要拥有一个你喜欢并且热爱的人生 7030
上一篇: 给大家推荐13个超级实用的运维工具!!!
下一篇: 学网络安全好还是学Web前端好?网络安全和Web前端哪个更吃香?_
大模型应用场景
博客等级 码龄5年 7639粉丝 1350原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值