最近两周在利用闲暇时间开发一款非常实用的App,故最近都没有时间来更新博客。现在这款App的Version 1.0已经接近尾声,第一代中构思的大部分功能都已经实现,之后不久会选择上架,写出使用说明书。这款app主要是干什么的,现在暂时保密,后续会与使用说明书一起贴出。
今天这篇文章对针对这款app内部数据的保护作一个总结。

反编译

Android程序打完包之后得到的是一个APK文件,这个文件是可以直接安装到任何Android手机上的,反编译其实也就是对这个APK文件进行反编译。

Android的反编译主要又分为两个部分,一个是对代码的反编译,一个是对资源的反编译。
具体反编译内容参考文章
Android安全攻防战,反编译与混淆技术完全解析(上)

代码混淆

混淆代码并不是让代码无法被反编译,而是将代码中的类、方法、变量等信息进行重命名,把它们改成一些毫无意义的名字。混淆代码可以在不影响程序正常运行的前提下让破解者很头疼,从而大大提升了程序的安全性。

在Android Studio当中混淆APK,借助SDK中自带的Proguard工具,只需要修改build.gradle中的一行配置即可。

1
2
3
4
5
6
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

其中minifyEnabled用于设置是否启用混淆,proguardFiles用于选定混淆配置文件。build.gradle中minifyEnabled的值,这里我们只需要把值改成true,打出来的APK包就会是混淆过的了。

注意,在release闭包内进行配置,因此只有打出正式版的APK才会进行混淆,Debug版的APK是不会混淆的。

特点:

  • 使用ProGuard混淆的应用,通过apktool还是可以看到Manifest和res资源。
  • 使用dex2jar,混淆后的源码还是可以看个大概。

参考文章
Android安全攻防战,反编译与混淆技术完全解析(下)

日志清理

在Android应用开发过程中,通过Log类输出日志是一种很重要的调试手段。

Android的log缓存区

Android提供的四个log缓存区:

  • /dev/log/main
    主应用程序log,除了下三个外,其他用户空间log将写入此节点,包括System.out.print及System.erro.print等。
  • /dev/log/events
    系统事件信息,二进制log信息将写入此节点,需要程序解析。
  • /dev/log/radio
    射频通话相关信息,tag 为”HTC_RIL” “RILJ” “RILC” “RILD” “RIL” “AT” “GSM” “STK”的log信息将写入此节点。
  • /dev/log/system
    低等级系统信息和debugging,为了防止mian缓存区溢出,而从中分离出来。

通过ADB清除Android缓存区日志:

1
2
3
adb logcat -c 清除main缓存区域日志
adb logcat -c -b events 清除系统事件信息日志
adb logcat -c -b main -b events -b radio -b system 清除所有日志

为了减少信息的泄漏,app日志需要做处理。

一般Log类的使用原则:

  • 在Debug模式下打印日志,在Release模式下不打印日志。
  • 避免滥用Log类进行输出日志。因为这样可能造成日志刷屏,淹没真正有用的日志。
  • 封装Log类,以提供同时输出日志到文件等功能。

具体总结为以下几个方法:

禁用System.out.println

Android应用中,一般通过封装过的Log类来输出日志,方便控制。而System.out.println是标准的Java输出方法,使用不当,可能造成Release模式下输出日志的结果。

禁用e.printStackTrace

禁用理由同上。
建议通过封装过的Log类来输出异常堆栈信息。

静态变量标记

Debug模式下,通过一个静态变量,控制日志的显示隐藏。
例如,使用BuildConfig.DEBUG标记:

1
2
3
4
5
6
7
private static final boolean isDebug = BuildConfig.DEBUG;

public static void i(String tag, String msg) {
if (isDebug) {
Log.i(tag, msg);
}
}

配置Proguard

Release模式下,通过Proguard配置来移除日志。使用gradle 进行打包时,添加混淆配置可以将log输出给删去。
在Proguard配置文件中,确保没有添加 –dontoptimize选项 来禁用优化。
build.gradle中修改为:

1
2
3
4
5
6
7
8
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),'proguard-rules.pro'
signingConfig signingConfigs.SginConfig
}
}

在proguard-rules.pro中添加以下代码:

1
2
3
4
5
6
7
8
9
-assumenosideeffects class android.util.Log {
public static *** println(...);
public static *** v(...);
public static *** i(...);
public static *** w(...);
public static *** d(...);
public static *** e(...);
public static *** wtf(...);
}

开启Proguard前提下,各种情况下的测试结论:

  • 日志被彻底移除,不会额外增加内存的情况:
    1
    2
    3
    4
    5
    6
    Log.i(简单字符串)
    Log.i(局部变量)
    Log.i(成员变量)
    Log.i(简单字符串+局部变量)
    Log.i(成员函数) 其中,成员函数返回值为: 简单字符串
    Log.i(成员函数) 其中,成员函数返回值为: 简单字符串+局部变量
  • 日志被移除,但是字符串拼接会存在,并占用内存的情况:
    1
    2
    Log.i(简单字符串+成员变量)
    Log.i(成员函数) 其中,成员函数返回值为: 简单字符串+成员变量
    参考文章
    Android优化系列一: 日志清理

Androrid应用打包release版时关闭log日志输出

content provider

如果不打算向其他应用授予访问APP的 ContentProvider的权限,在应用清单中将其标记为 [android:exported=false];要允许其他应用访问APP的数据,将 [android:exported]属性设置为 “true”。

android:exported

android:exported 是Android中的四大组件 Activity,Service,Provider,Receiver 四大组件中都会有的一个属性。

总体来说它的主要作用是:是否支持其它应用调用当前组件。
默认值:如果包含有intent-filter 默认值为true; 没有intent-filter默认值为false。
在Activity中,该属性用来表示:当前Activity是否可以被另一个Application的组件启动:
true允许被启动;false不允许被启动。

1
2
3
<activity
android:exported=["true" | "false"]
/>

如果被设置为了false,那么这个Activity将只会被当前Application或者拥有同样user ID的Application的组件调用。
exported 的默认值根据Activity中是否有intent filter 来定。
没有任何的filter意味着这个Activity只有在详细的描述了他的class name后才能被唤醒。这意味着这个Activity只能在应用内部使用,因为其它application并不知道这个class的存在。所以在这种情况下,它的默认值是false。从另一方面讲,如果Activity里面至少有一个filter的话,意味着这个Activity可以被其它应用从外部唤起,这个时候它的默认值是true。

android:protectionLevel

如果要在其他自己的应用上访问该APP,那么首先将 [android:exported]属性设置为 “true”,然后最好设置一下[android:protectionLevel]属性。

Android protectionLevel分4个级别:

  • normal
    低风险权限,只要申请了就可以使用(在AndroidManifest.xml中添加<uses-permission>标签),安装时不需要用户确认。
  • dangerous
    高风险权限,安装时需要用户的确认才可使用。
  • signature
    只有当申请权限的应用程序的数字签名与声明此权限的应用程序的数字签名相同时(如果是申请系统权限,则需要与系统签名相同),才能将权限授给它。
  • signatureOrSystem
    签名相同,或者申请权限的应用为系统应用(在system image中)。

如果开发者需要对自己的应用程序(或部分应用)进行访问控制,则可以通过在AndroidManifest.xml中添加<permission>标签,最好将其属性中的protectionLevel设置为signature 保护级别。

访问内容提供程序时,请使用参数化的查询方法(例如 query()、update() 和 delete()),以免产生来源不受信任的 SQL 注入风险。请注意,如果以组合用户数据的方式构建 selection 参数,然后再将其提交至参数化方法,则使用参数化方法可能不够安全。

参考文章
android:exported 属性详解

Android 权限的一些细节

Activity组件安全

使用activity的风险和选择取决于需求对activity的定义。这里我们基于activity的使用方式将其分为4中类型:

  • 私有activity:只能有APP内部使用的activity,最安全的activity。
  • 公共activity:任意APP都能启动此类activity。
  • 伙伴activity:合作伙伴APP才能启动的activity,授权。
  • 内部activity:只有内部APP才能启动,全家桶。

私有activity

私有activity不能由其他应用程序启动,因此它是最安全的。当一个activity只在本APP内部使用时,只要将activity声明为显式intent调用方式,那么就不需要担心其他应用程序调用开启。

然而,有一个风险是第三方APP可以读取一个开启activity的intent。因此,为了避免第三方应用程序读取Intent来复制intent,我们可以在intent中放置一些extra做判断,避免第三方应用程序调用。

一个私有activity必须做到的关键几点如下:

  • 不声明taskAffinity
  • 不声明launchMode
  • 设置exported属性为false
  • 保证intent发送时的安全性,确定intent是来自本应用程序(签名验证和包名验证)
  • 启动activity的时候不设置FLAG_ACTIVITY_NEW_TASK
  • 使用显示的intent和指定的类的方式来调用一个activity
  • 敏感信息放在extra中发送
  • 在onActivityResult的时候需要对返回的data小心处理

组件传输数据验证

对组件之间,特别是跨应用的组件之间的数据传入和返回做数据验证,防止恶意数据传入,更要防止敏感数据的返回。
对于导出的组件,最好做intent的校验:
数据格式或数据结构校验;
异常处理:防止crash攻击。

隐式intent没有明确指明哪些接收方有权限接收,恶意程序指定action标识后,可以获取intent内容,导致数据泄漏、intent劫持、仿冒、钓鱼应用等风险使用。

建议:

  • 显示intent调用,直接写类名
  • 使用Intent.setPackage、Intent.setComponent、Intent.setClassName、Intent.setClass四种方法中任一种明确指定目标组件名称。

传输端:

1
2
3
4
5
6
7
Intent intent;
intent = new Intent(MainActivity.this,SecondActivity.class);
intent.setPackage(getPackageName());
bundle = new Bundle();
bundle.put...;
intent.putExtra("...",bundle);
startActivity(intent);

接收端:

1
2
3
4
5
6
7
8
9
10
Intent intent = getIntent();
String packagename = intent.getPackage().toString();
if(packagename.equals(getPackageName())) {
Bundle bundle = intent.getBundleExtra("...");
if(bundle != null) {
...
}
}else{
finish();
}

AndroidManifest.xml

需要特别注意的点:

  • 权限检查:
    a) 最小化系统权限
    b) 自定义权限进行级别保护设置
  • 调试标记检查:debuggable=”false”
  • 程序数据任意备份检查:allowBackup=”false”

参考文章
Android App 安全策略