大家好,Android 应用程序加固技术分析篇二相信很多的网友都不是很明白,包括也是一样,不过没有关系,接下来就来为大家分享关于Android 应用程序加固技术分析篇二和的一些知识点,大家可以关注收藏,免得下次来找不到哦,下面我们开始吧!
Android APK加固技术研究(二)
Android APK加固技术研究(三)
为了保证Android应用程序的源代码安全,我们一般会对线上应用程序的代码进行混淆。然而,代码混淆还不够。我们还需要加强我们的应用程序,防止他人通过反编译获取我们的源代码。目前apk加固技术已经比较成熟和完善,市场上比较流行的就是“360加固”。本文对apk加固技术进行技术探索。希望读者读完后能够理解加固原理,并自行实现加固方案。
在Android apk加固技术研究(一)中,大致介绍了反编译过程以及我们能够获取源代码的原因。下面对加固的基本过程进行说明。
源码地址:https://gitee.com/openjk/apk-steady
加固流程
新建一个Android项目,在其中创建shell模块,生成加固的shell arr文件。 shell模块包含Application的子类SteadyApplication,其中包含解密dex文件的逻辑。编译shell并生成shell.aar文件并解压。将待加固的apk放入apkUnzip目录下,获取其中所有dex文件,并将apkUnzip下AndroidManifest.xml文件中application根节点下的name属性值修改为2中创建的SteadyApplication。将原apk的Application路径保存到meta-data节点,准备在SteadyApplication中解析生成。使用加密算法对上一步得到的dex文件进行加密,并删除原dex文件解压3中生成的aar文件得到里面的jar文件,然后通过dx将jar文件转换为dex文件SDK中提供的工具。将生成的dex文件放入apkUnzip文件中,并压缩apkUnzip文件夹,生成新的apk。重新签名这篇文章主要讲解上述1、2、3步骤,如何生成一个 Shell.arr(壳)文件。Shell 最终会打入到原 apk 的class.dex 中,用来解密已经加密的原 apk 中的dex和加载原来的 dex 文件
一、生成 Shell.aar(dex 解密和类加载)
1、解密加固的 dex 文件的流程
在应用程序中,您可以通过getApplicationInfo().sourceDir 获取基础APK。这个apk包含我们应用程序的所有代码。通过Application的getDir()方法,我们在应用程序的私有目录中创建一个私有文件夹SteadyDir。在2创建的目录中,我们解压bask.apk。解压后,我们得到apk的所有文件,然后过滤掉所有以dex为后缀的文件。文件。其中,classes.dex文件我们不需要,因为它已经加载到系统中,所以我们只需要处理加密的dex文件即可。将解密后的dex文件加载到程序中并运行apk真实应用程序。启动应用程序
2、如何解压 apk 文件
zip解压main 使用java中的ZipFile类。具体实现直接在代码中。代码中有注释,没有解释。
公共静态无效unZip(文件zip,文件目录){
尝试{
//清空解压文件存放目录
删除文件(目录);
ZipFile zipFile=new ZipFile(zip);
//zip 文件中的每个条目
枚举?扩展ZipEntryentries=zipFile.entries();
//遍历
while (entries.hasMoreElements()) {
ZipEntry zipEntry=items.nextElement();
//zip文件/目录名
字符串名称=zipEntry.getName();
//不再需要原来的签名文件
if (name.equals("META-INF/CERT.RSA") || name.equals("META-INF/CERT.SF") || 名称
.equals("META-INF/MANIFEST.MF")) {
继续;
}
//忽略空目录
if (!zipEntry.isDirectory()) {
文件file=new File(目录, 名称);
//创建目录
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
//写入文件
FileOutputStream fos=new FileOutputStream(文件);
输入流=zipFile.getInputStream(zipEntry);
字节[]缓冲区=新字节[2048];
int 长度;
while ((len=is.read(buffer)) !=-1) {
fos.write(缓冲区, 0, len);
}
is.close();
fos.close();
}
}
zipFile.close();
} catch (异常e) {
e.printStackTrace();
}
}
私有静态无效deleteFile(文件文件){
if (file.isDirectory()){
File[] files=file.listFiles();
对于(文件f:文件){
删除文件(f);
}
}别的{
文件.删除();
}
}
3、如何解密 dex 文件
通过第二步的解压方法,我们可以轻松将base.apk解压到私有目录。然后我们通过文件扩展名.dex过滤掉所有dex文件(不包括classes.dex),然后将每个dex读入一个字节数组,然后对字节数组进行解密。
这里的加密和解密采用的是AES方法。为了增加安全性,解密方法使用jni方法完成。解密方法如下:
jbyteArray 解密(JNIEnv *env,jbyteArray srcData) {
jstring 类型=(*env).NewStringUTF("AES");
jstring cipher_mode=(*env).NewStringUTF("AES/ECB/PKCS5Padding");
jbyteArray pwd=(*env).NewByteArray(16);
char *master_key=(char *) "huangdh"l,AMWK;";
(*env).SetByteArrayRegion(pwd,0,16,reinterpret_cast(master_key));
jclass SecretKeySpecClass=(*env).FindClass("javax/crypto/spec/SecretKeySpec");
jmethodID SecretKeySpecMethodId=(*env).GetMethodID(secretKeySpecClass,"", "([BLjava/lang/String;)V");
jobject SecretKeySpecObj=(*env).NewObject(secretKeySpecClass,secretKeySpecMethodId,pwd,type);
jclass cipherClass=(*env).FindClass("javax/crypto/Cipher");
jmethodID cipherInitMethodId=(*env).GetMethodID(cipherClass,"init", "(ILjava/security/Key;)V");
jmethodID cipherInstanceMethodId=(*env).GetStaticMethodID(cipherClass,"getInstance", "(Ljava/lang/String;)Ljavax/crypto/Cipher;");
jobject cipherObj=(*env).CallStaticObjectMethod(cipherClass,cipherInstanceMethodId,cipher_mode);
jfieldID cryptoModeFieldId=(*env).GetStaticFieldID(cipherClass,"DECRYPT_MODE", "I");
jint 模式=(*env).GetStaticIntField(cipherClass,decryptModeFieldId);
(*env).CallVoidMethod(cipherObj,cipherInitMethodId,模式,secretKeySpecObj);
jmethodID doFinalMethodId=(*env).GetMethodID(cipherClass,"doFinal", "([B)[B");
jbyteArray text=(jbyteArray)(*env).CallObjectMethod(cipherObj,doFinalMethodId,srcData);
返回文本;
}
4、加载 dex 文件
通过上面的解压解密操作,我们得到了原始的dex文件。我们将这些dex文件放入一个集合中,然后使用类加载机制来加载解密后的dex文件。类加载机制将在后续文章中进行讲解。
公共静态无效loadDex(应用程序应用程序,ListdexFiles,文件版本Dir)抛出异常{
//1.首先从ClassLoader中获取pathList变量
字段pathListField=ProxyUtils.findField(application.getClassLoader(), "pathList");
//1.1 获取DexPathList类
对象pathList=pathListField.get(application.getClassLoader());
//1.2 从DexPathList类中获取dexElements变量
字段dexElementsField=ProxyUtils.findField(pathList,"dexElements");
//1.3 获取加载的dex数组
Object[] dexElements=(Object[])dexElementsField.get(pathList);
//2.反射到初始化dexElements的方法,即获取加载dex到系统中的方法
方法makeDexElements=ProxyUtils.findMethod(pathList,"makePathElements",List.class,File.class,List.class);
//2.1 需要实例化一个集合makePathElements
ArrayListsuppressedExceptions=new ArrayList();
//2.2 反射执行makePathElements函数,将解码后的dex加载到系统中。否则会导致dex无法打开并导致崩溃。
Object[] addElements=(Object[])makeDexElements.invoke(pathList,dexFiles,versionDir,suppressedExceptions);
//3.实例化一个新数组,将当前加载的和已加载的dex合并到一个新数组中
Object[] newElements=(Object[]) Array.newInstance(dexElements.getClass().getComponentType(),dexElements.length+addElements.length);
//3.1 将系统中加载的dex放入newElements中
System.arraycopy(dexElements,0,newElements,0,dexElements.length);
//3.2 将解密后加载的dex放入新数组中
System.arraycopy(addElements,0,newElements,dexElements.length,addElements.length);
//4.将合并后的新数组重置为DexPathList的dexElements
dexElementsField.set(pathList,newElements);
}
5、加载真实的 application 类,运行 app
1. 首先从AndroidManifest.xml文件中获取原应用的类名。 (下一篇我们会讲解如何将apk原来的应用类名放在AndroidManifest.xml的meta-data标签下)
/**
* 解析项目中原有的Application名称
*/
私有无效getMateData(){
尝试{
ApplicationInfo applicationInfo=getPackageManager().getApplicationInfo(getPackageName(),
PackageManager.GET_META_DATA);//获取包信息
Bundle metaData=applicationInfo.metaData;//获取Meta-data的键值对信息
if(null !=元数据){
if(metaData.containsKey("app_name")){
app_name=metaData.getString("app_name");//获取原始包名
}
}
}catch(异常e){
e.printStackTrace();
}
}2.获取原应用的类名后,通过反射获取应用的实例。
私有无效bindRealApplication()抛出异常{
如果(isBindReal){
返回;
}
if(TextUtils.isEmpty(app_name)){
返回;
}
//1.获取attachBaseContext(context)传入的上下文ContextImpl
上下文baseContext=getBaseContext();
//2.获取真实APK应用程序的类
Class?delegateClass=Class.forName(app_name);
//反射实例化,
delegate=(Application) delegateClass.newInstance();
//获取Application的attach()方法,该方法最先被初始化
方法Attach=Application.class.getDeclaredMethod("attach",Context.class);
Attach.setAccessible(true);
//执行应用程序#attach(Context)
Attach.invoke(委托,baseContext);
//ContextImpl----mOuterContext(app)是通过Application的attachBaseContext回调参数获取的
//4.获取Context的实现类
Class?contextImplClass=Class.forName("android.app.ContextImpl");
//4.1 获取mOuterContext上下文属性
字段mOuterContextField=contextImplClass.getDeclaredField("mOuterContext");
mOuterContextField.setAccessible(true);
//4.2 将真正的应用程序交给上下文。这是按照源码执行的。 setOuterContext函数是在实例化Application后调用的,所以需要绑定Context。
//应用程序=mActivityThread.mInstrumentation.newApplication(
//cl、appClass、appContext);
//appContext.setOuterContext(app);
mOuterContextField.set(baseContext, delegate);
//ActivityThread---mAllApplications(ArrayList) ContextImpl 的mMainThread 属性
//5.获取ActivityThread变量
字段mMainThreadField=contextImplClass.getDeclaredField("mMainThread");
mMainThreadField.setAccessible(true);
//5.1 获取ActivityThread对象
对象mMainThread=mMainThreadField.get(baseContext);
//ActivityThread---mInitialApplication
//6.反射获取ActivityThread类
类?activityThreadClass=Class.forName("android.app.ActivityThread");
//6.1 获取当前加载的Application类
字段mInitialApplicationField=ActivityThreadClass.getDeclaredField("mInitialApplication");
mInitialApplicationField.setAccessible(true);
//6.2 将ActivityThread中的Applicaiton替换为真正的Application,可以用来接收相应的生命周期和一些调用等
mInitialApplicationField.set(mMainThread,委托);
//ActivityThread---mAllApplications(ArrayList) ContextImpl 的mMainThread 属性
//7.获取ActivityThread中所有Application集合对象。这是一个多进程场景。
字段mAllApplicationsField=ActivityThreadClass.getDeclaredField("mAllApplications");
mAllApplicationsField.setAccessible(true);
ArrayListmAllApplications=(ArrayList) mAllApplicationsField.get(mMainThread);
//7.1 删除ProxyApplication
mAllApplications.remove(this);
//7.2 添加真实应用
mAllApplications.add(委托);
//LoadedApk--------mApplication ContextImpl的mPackageInfo属性
//8.从ContextImpl 获取mPackageInfo 变量
字段mPackageInfoField=contextImplClass.getDeclaredField("mPackageInfo");
mPackageInfoField.setAccessible(true);
//8.1 获取LoadedApk对象
对象mPackageInfo=mPackageInfoField.get(baseContext);
//9 反射获取LoadedApk对象
//@覆盖
//公共上下文getApplicationContext() {
//返回(mPackageInfo !=null) ?
//mPackageInfo.getApplication() : mMainThread.getApplication();
//}
Class?loadedApkClass=Class.forName("android.app.LoadedApk");
字段mApplicationField=loadApkClass.getDeclaredField("mApplication");
mApplicationField.setAccessible(true);
//9.1 将LoadedApk中的Application替换为真实的Application
mApplicationField.set(mPackageInfo,委托);
//修改ApplicationInfo className LoadedApk
//10.获取LoadApk中的mApplicationInfo变量
字段mApplicationInfoField=returnedApkClass.getDeclaredField("mApplicationInfo");
mApplicationInfoField.setAccessible(true);
//10.1 根据变量反射获取ApplicationInfo对象
ApplicationInfo mApplicationInfo=(ApplicationInfo)mApplicationInfoField.get(mPackageInfo);
//10.2 为其分配我们真正的APPplication ClassName名称
mApplicationInfo.className=app_name;
//11.执行代理Application onCreate生命周期
delegate.onCreate();
//解码完成
isBindReal=true;
【Android 应用程序加固技术分析篇二】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
终于看到了加固系列的第二篇,希望能详细讲解一些常用的加固方法。
有13位网友表示赞同!
我一直很想知道APK加固技术是怎么运作的,期待学习更多新的知识。
有13位网友表示赞同!
这个话题对我很有帮助,我正在尝试保护自己的应用不被反编译。
有12位网友表示赞同!
Android开发中加固的重要性不容忽视,希望这篇文章能深入讲解一些实战经验 。
有9位网友表示赞同!
想要了解如何有效地对抗apk恶意行为, 加固技术是必不可少的。
有8位网友表示赞同!
期待作者能够分享一些最新有效的加固技术以及它们的优缺点。
有17位网友表示赞同!
学习完第一篇后对Android APK加固有了初步的了解,这篇第二篇肯定更精彩!
有14位网友表示赞同!
想了解更多关于APK反编译和防御机制的内容, 这篇文章应该是很好的参考资料。
有11位网友表示赞同!
最近在接触安全领域,希望这篇文章能给我一些新的启发。
有12位网友表示赞同!
作为一名Android开发者,了解加固技术非常必要,希望能从这篇文章中学到实战技巧。
有5位网友表示赞同!
每次看到有关Android的深入解析文章,我的热情都会被激发起来!
有18位网友表示赞同!
相信这篇文章能帮我更好地理解APK加固的过程和原理。
有12位网友表示赞同!
很期待作者能够分享一些实用的案例和工具,以便我们更好地学习和应用加固技术。
有7位网友表示赞同!
希望文章能涵盖不同类型的加固方法,并对比分析它们的优劣势。
有16位网友表示赞同!
在保护APP安全的道路上,不断学习新的加固技术非常重要。
有16位网友表示赞同!
通过阅读这篇文章,我相信我能更全面地了解APK加固的技术现状和发展趋势。
有7位网友表示赞同!
我是一个喜欢探索新鲜技术的开发者,这次一定要仔细阅读这篇文章!
有17位网友表示赞同!
感谢作者分享如此宝贵的知识,期待后续的文章继续深入探讨Android安全领域。
有7位网友表示赞同!