最近在研究安卓安全问题,在网上找了很多资料,杂乱,也不知道是不是我能力问题,反正没有找到类似一个稍微来说全一点,而且又可以直接拿来参考的资料(个人比较懒,喜欢直接拿来用,见谅)。以下是我单方面的见解,如有不正确的地方,希望大家多多指教,相互交流,相互学习,共同进步
正传:安卓安全问题,总体分为三点:代码安全,资源安全,代码加固。
一,首先说下代码安全,主要是java代码安全,现在主要用到的技术为代码混淆(据说没什么卵用),本着有总比没有好的原则,还是起到一定作用的,起码扰乱了对方的思维,消耗对方的时间去理解代码,后面还会讲到加壳技术(不能正确的反编译出java代码)。案例如下:
-optimizationpasses 5 #指定代码的压缩级别 0 - 7
-dontusemixedcaseclassnames #是否使用大小写混合
-dontskipnonpubliclibraryclasses #如果应用程序引入的有jar包,并且想混淆jar包里面的class
-dontpreverify #混淆时是否做预校验(可去掉加快混淆速度)
-ignorewarnings #这1句是屏蔽警告,脚本中把这行注释去掉
-verbose #混淆时是否记录日志(混淆后生产映射文件 map 类名 -> 转化后类名的映射
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* #淆采用的算法
#如果加入jar包,必须在此写一个-libraryjars libs/XX.jar
-libraryjars libs/BaiduLBS_Android.jar
-libraryjars libs/fastjson-1.1.37.jar
-dontwarn #dontwarn去掉警告
-dontskipnonpubliclibraryclassmembers
-keep public class * extends Android.app.Activity #所有activity的子类不要去混淆
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService #指定具体类不要去混淆
-keep class com.shixu.faceapp.model.** { *; }
-keepclasseswithmembernames class * {
native <methods>; #保持 native 的方法不去混淆
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet); #保持自定义控件类不被混淆,指定格式的构造方法不去混淆
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View); #保持指定规则的方法不被混淆(Android layout 布局文件中为控件配置的onClick方法不能混淆)
}
-keep public class * extends android.view.View { #保持自定义控件指定规则的方法不被混淆
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
}
-keepclassmembers enum * { #保持枚举 enum 不被混淆
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable { #保持 Parcelable 不被混淆(aidl文件不能去混淆)
public static final android.os.Parcelable$Creator *;
}
-keepnames class * implements java.io.Serializable #需要序列化和反序列化的类不能被混淆(注:Java反射用到的类也不能被混淆)
-keepclassmembers class * implements java.io.Serializable { #保护实现接口Serializable的类中,指定规则的类成员不被混淆
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
-keepattributes Signature #过滤泛型(不写可能会出现类型转换错误,一般情况把这个加上就是了)
-keepattributes *Annotation* #假如项目中有用到注解,应加入这行配置
-keep class **.R$* { *; } #保持R文件不被混淆,否则,你的反射是获取不到资源id的
-keep class **.Webview2JsInterface { *; } #保护WebView对HTML页面的API不被混淆
-keepclassmembers class * extends android.webkit.WebViewClient { #如果你的项目中用到了webview的复杂操作 ,最好加入
public void *(android.webkit.WebView,java.lang.String,android.graphics.Bitmap);
public boolean *(android.webkit.WebView,java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebChromeClient { #如果你的项目中用到了webview的复杂操作 ,最好加入
public void *(android.webkit.WebView,java.lang.String);
}
#对WebView的简单说明下:经过实战检验,做腾讯QQ登录,如果引用他们提供的jar,若不加防止WebChromeClient混淆的代码,oauth认证无法回调,反编译基代码后可看到他们有用到WebChromeClient,加入此代码即可。
-keepclassmembernames class com.cgv.cn.movie.common.bean.** { *; } #转换JSON的JavaBean,类成员名称保护,使其不被混淆
注:
1.可将以上代码拷贝到项目的proguard-project.txt文件中,并且在project.properties将proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt前面的#去掉即可使用。
2.如果用到fastjson或者Gjson,代码混淆会出现找不到对象的问题,在这里需要不混淆model实体类处理,如以上代码:-keep class com.shixu.faceapp.model.** { *; }
3.以上代码不一定满足所有人的需求,网上很多相关方面的代码,可以自行拷贝
4.如果代码不正确签名打包会报错,这里不要急,查看报错原因,慢慢调试,如果实在不行,换代码再试
。
二,java代码加壳技术
加壳技术原理如图所示:
图片也是我从网上找的,原理不多说,看图,如果想详细了解原理(我具体也不是很懂,再次不自我补课了,后期会深入研究学习),可以去百度一下,网上一大坨,我只说我在这里怎么用。案例如下:
/*
* 文 件 名: AdnroidShell.java
* 版 权: Anhui Shixu Intelligent Technology CO.,LTD. Copyright YYYY-YYYY, All rights reserved
* 描 述: <描述>
* 修 改 人: gaoqiang
* 修改时间: 2016-9-14
* 跟踪单号: <跟踪单号>
* 修改单号: <修改单号>
* 修改内容: <修改内容>
*/
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.Adler32;
/**
*
* apk加壳技术运用
*
* @author gaoqiang
* @version [版本号, 2016-9-14]
* @see [相关类/方法]
* @since [产品/模块版本]
*/
public class AdnroidShell
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO Auto-generated method stub
try
{
File payloadSrcFile = new File("force/**.apk"); // 需要加壳的程序
System.out.println("apk size:" + payloadSrcFile.length());
File unShellDexFile = new File("force/**.dex"); // 解客dex
byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));// 以二进制形式读出apk,并进行加密处理//对源Apk进行加密操作
byte[] unShellDexArray = readFileBytes(unShellDexFile);// 以二进制形式读出dex
int payloadLen = payloadArray.length;
int unShellDexLen = unShellDexArray.length;
int totalLen = payloadLen + unShellDexLen + 4;// 多出4字节是存放长度的。
byte[] newdex = new byte[totalLen]; // 申请了新的长度
// 添加解壳代码
System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);// 先拷贝dex内容
// 添加加密后的解壳数据
System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);// 再在dex内容后面拷贝apk的内容
// 添加解壳数据长度
System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen - 4, 4);// 最后4为长度
// 修改DEX file size文件头
fixFileSizeHeader(newdex);
// 修改DEX SHA1 文件头
fixSHA1Header(newdex);
// 修改DEX CheckSum文件头
fixCheckSumHeader(newdex);
String str = "force/classes.dex";
File file = new File(str);
if (!file.exists())
{
file.createNewFile();
}
FileOutputStream localFileOutputStream = new FileOutputStream(str);
localFileOutputStream.write(newdex);
localFileOutputStream.flush();
localFileOutputStream.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
// 直接返回数据,读者可以添加自己加密方法
private static byte[] encrpt(byte[] srcdata)
{
for (int i = 0; i < srcdata.length; i++)
{
srcdata[i] = (byte)(0xFF ^ srcdata[i]);
}
return srcdata;
}
/**
* 修改dex头,CheckSum 校验码
*
* @param dexBytes
*/
private static void fixCheckSumHeader(byte[] dexBytes)
{
Adler32 adler = new Adler32();
adler.update(dexBytes, 12, dexBytes.length - 12);// 从12到文件末尾计算校验码
long value = adler.getValue();
int va = (int)value;
byte[] newcs = intToByte(va);
// 高位在前,低位在前掉个个
byte[] recs = new byte[4];
for (int i = 0; i < 4; i++)
{
recs[i] = newcs[newcs.length - 1 - i];
System.out.println(Integer.toHexString(newcs[i]));
}
System.arraycopy(recs, 0, dexBytes, 8, 4);// 效验码赋值(8-11)
System.out.println(Long.toHexString(value));
System.out.println();
}
/**
* int 转byte[]
*
* @param number
* @return
*/
public static byte[] intToByte(int number)
{
byte[] b = new byte[4];
for (int i = 3; i >= 0; i--)
{
b[i] = (byte)(number % 256);
number >>= 8;
}
return b;
}
/**
* 修改dex头 sha1值
*
* @param dexBytes
* @throws NoSuchAlgorithmException
*/
private static void fixSHA1Header(byte[] dexBytes)
throws NoSuchAlgorithmException
{
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(dexBytes, 32, dexBytes.length - 32);// 从32为到结束计算sha--1
byte[] newdt = md.digest();
System.arraycopy(newdt, 0, dexBytes, 12, 20);// 修改sha-1值(12-31)
// 输出sha-1值,可有可无
String hexstr = "";
for (int i = 0; i < newdt.length; i++)
{
hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16).substring(1);
}
System.out.println(hexstr);
}
/**
* 修改dex头 file_size值
*
* @param dexBytes
*/
private static void fixFileSizeHeader(byte[] dexBytes)
{
// 新文件长度
byte[] newfs = intToByte(dexBytes.length);
System.out.println(Integer.toHexString(dexBytes.length));
byte[] refs = new byte[4];
// 高位在前,低位在前掉个个
for (int i = 0; i < 4; i++)
{
refs[i] = newfs[newfs.length - 1 - i];
System.out.println(Integer.toHexString(newfs[i]));
}
System.arraycopy(refs, 0, dexBytes, 32, 4);// 修改(32-35)
}
/**
* 以二进制读出文件内容
*
* @param file
* @return
* @throws IOException
*/
private static byte[] readFileBytes(File file)
throws IOException
{
byte[] arrayOfByte = new byte[1024];
ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
FileInputStream fis = new FileInputStream(file);
while (true)
{
int i = fis.read(arrayOfByte);
if (i != -1)
{
localByteArrayOutputStream.write(arrayOfByte, 0, i);
}
else
{
return localByteArrayOutputStream.toByteArray();
}
}
}
}
操作步骤:
1.将以上代码拷贝到java项目中,并且新建force文件夹(和src文件夹同级别);
2.运行你自己的app项目,将bin里面的apk和classes.dex(必须改名例如和apk名称相同)拷贝到java项目中的force文件夹中,并且把java项目中的force/**.apk和force/**.dex名称换成你自己的名称并且运行java代码,F5刷新下foce文件夹会得到新的classes.dex;
3.将classes.dex文件拷贝回app项目中的bin文件下,然后打包签名即可。
备注:
1.加壳技术我也是在网上找的,我比较笨,他说的我没怎么看懂(),我用自己的方法同样可以实现app加壳;
2.网上搜了说加壳技术可以用一些比较好的算法,这样安全系数更高,但是毕竟才看了那么小几天,所以没时间去具体看,后期我也会深入研究的,如果有好的意见和建议,非常欢迎大家给我指正,指导。
三,app资源保护
这个问题困扰了我好久,我搞了好几天思路倒是出来好几个,但是网上搜了一大片,反正具体的做法目前还没有推敲出来(后期同样自己会深入研究)。
大体思路如下:
1.资源以二进制流形式保存,并且加密;
2.使用AAPT技术,同样是二进制流形式操作。
困惑:最大的困惑是我在网上下载了QQ,微信,美团等等Apk,但是我使用apktool反编译,只能编译出smali文件(网上搜了一大圈,没找到具体思路),这是为什么,天啦,为什么????谁能告诉我
但是我也有解决办法
那就是用爱加密,百度一下爱加密,注意啦,又不要钱的哈,不要钱不用白不用,不要那么傻。能走捷径就走,心里非常不爽,想把这个技术研究透搞(想我这样的),但是这样节约时间,回头有时间在去研究是一样地,说不定还有意外收获。说说大体做法吧:
1.爱加密注册,登录......省略一万字,自己去想;
2.用户个人中心有个加密记录,将自己打包签名的apk(注意啦,以上代码混淆和加壳技术都完成啦),提交加密
输入相关信息,加密应该不会超过半小时,反正我的10分钟内就加密成功。然后再列表中下载加密后的apk
2.爱加密后的apk不能直接用,需要用爱加密自己的签名工具再次签名打包(自己在里面下载apk签名工具)
3.下载签名工具之后打包下apk就OK啦,然后测试下,测试没问题就可以引用啦~