Android技术防范与揭秘第九章总结
防止利用系统组件的漏洞
- 最小化组件暴露:在调用的组件添加,android:exported=“false”属性,说明他是私有的,只有同一个应用程序的组件或者带有相同用户的ID的应用程序才能启动或者绑定该服务。如果 只是内部调用,还可以给组件加入签名权限以防止不同签名的应用恶意调用
- 设置组件的访问权限:对于跨应用间调用的组件或者是公开的Brocast Receiver Service设置权限,只有该权限才能调用该组件
- 组件传输数据验证:对组件之间,特别是跨应用的组件之间的数据传入和返回做验证,防止恶意调用数据传入,更防止敏感数据的返回。
- 暴露组件的代码检查:组件代码检查,即在运行时防止自身的权限被恶意篡改所带来的防范缺失。我们能够使用这些API在应用运行时检查,执行,授予和撤销权限
Activity编码安全
Activity A-Activity B 通过intent来启动如果intent被拦截,或者被伪造,就有可能将Activity中重要的数据泄露
Activity的分类
- 私有的Activity:不能由另一个应用程序启动的Activity,是最安全的Activity
- 公共的Activity:所使用的Activity是最常见的Activity,没有确定性的启动Activity,任意应用程序都能启动此类Activity
- 伙伴Activity:此类Activity只能由特定的应用程序使用,即合作伙伴的应用才能启动的Activity
内部的Activity:此类Activity只能由内部其他应用程序使用
这里说的公共和私有并不是指Class的Public 和private。这里指的是使用权限
私有Activity
只要将Activity定义为显示的Intent的调用,那么不需要担心其他应用使用Activity。 由一个风险,第三方应用可以读取一个开启的Activity的Intent,为了避免第三方读取Intent 复制Intent,我们可以在Intent中放置一些extra来判断是否是第三方调用
私有的Activity所需要做到的关键几点
- 不声明taskaffinity
- 不声明lauchMode
- 设置exported属性为false
- 保证Intent发送时的安全性,确定Intent是来自本应用程序
- 在确保是本应用程序发送Intent的时候,可以防止一些敏感信息
- 启动Activity的时候不设置FLAG_ACTIVIT_NEW_TASK
- 使用显示Intent与指定的类的方式来调用一个Activity
- 敏感的信息放置在extra中发送
- 在onActivityResult 的时候需要返回的data小心处理
//私有的
<activity
android:name=".PrivateActivity"
android:exported="false" />
公共的Activity
公共的Activity是公开的Activity,一般在多个地方都会启动且数量不定的Activity上。一旦Activity声明为public Activity ,那么任何应用程序都可以发送一个Intent 来启动它
一个共有的Activity要做到几点
- AndroidManifast.xml 中的exproted 属性设置为true
- 接受到intent 的时候小心处理
- Finish的时候别再Intent中放置一些敏感信息
//xml中设置
android:exported="true"
//判断param来源的正确性,发送之前,可以适当做一些加密处理
String param = getIntent().getStringExtra("PARAM");
//finish
Intent intent=new Intent();
intent.putExtra("RESULT","Not Sensitivie Info");
setResult(200,intent);
伙伴Activity
给特定的应用程序使用的Activity,这样的Activity会有一个风险,第三方应用可以读取启动这个Activity的Intent信息,因此不要在intent放置敏感信息
注意点
- 不声明taskaffinity
- 不声明lauchMode
- 不添加Intent-fillter设置exproted属性为true
- 使用白名单机制验证应用签名
- 处理PartnerActivity来的Intent的时候小心注意
//伙伴应用的签名
private static final String PARTNER_SINGATURE="xxxx";
//伙伴应用的包名
private static final String PARTENR_PKG_NAME="xxxx";
private boolean check(Intent intent){
String aPackage = intent.getPackage();
if (!aPackage.equals(PARTENR_PKG_NAME)){
return false;
}
if (!getSingature(aPackage).equal(PARTNER_SINGATURE)){
return false;
}
return true;
}
对于PartnerActivity的使用必须是双方的,另一方传递数据
注意点
- 不使用FLAG_ACTIVITY_NEW_TASk
- extra中放置一些公开与不敏感的信息
- 使用显示Intent,具体到包名,类名
- 使用startActivityForRequest(),返回可以做校验
private static final int REQUEST_CODE = 1;
//目标Activity的包名
private static final String TAGGET_PACKAGE = "xxx";
//目标Activity的类名
private static final String TAGGET_ACTIVITY = "xxx";
void click() {
Intent intent = new Intent();
intent.putExtra("PARAM", "非敏感数据,公开");
intent.setClassName(TAGGET_PACKAGE, TAGGET_ACTIVITY);
startActivityForResult(intent,REQUEST_CODE);
}
内部的Activity
禁止自身应用程序外的应用程序使用。这类的Activity是内部使用的,可以在应用程序内部安全地共享信息。一般设置一个signatrue权限,如系统内部的应用程序,都具有相同的签名,风险,第三方应用可以读取启动这个Activity的Intent信息
注意点
- 定义Activity的签名权限为signature
- 不声明taskaffinity
- 不声明lauchMode
- 不添加Intent-fillter设置exproted属性为true
- 验证签名
- 通过Intent传输的数据需要小心
//xml中
<permission
android:name="com.example.xxx.MY_PERSSION"
android:protectionLevel="signature" />
<activity
android:name=".InhouseActivity"
android:exported="true"
android:permission="com.example.xxx.MY_PERSSION" />
private static final String MY_PERMISSION="com.example.xxx.MY_PERSSION";
if (!MY_PERMISSION.equals(getPermission(this))){
finish();
}
Brocast Receiver编码安全
- 私有的Brocast Receiver:此类的Brocast指能接受应用内部所接受,所以是最安全的
- 公共的Brocast Receiver:没有具体制定的接受着的Brocast,能被所以应用接受
- 内部的Brocast Receiver:只能被特定的应用所接受
静态广播和动态广播
4. 静态广播:在xml中配置,能接受一些有限制性的系统发送的广播如ACTION_BATTERY_CHANGED,广播在系统这个存货期间都能收到,应用退出也可以
5. 动态广播:在代码中注册和取消的 ,可能会忽略掉一些广播接受不到,此类广播接受者在一段时间内接受,也可以在接受后断开,
6. 私有的Brocast不能被创建
在v4包下提供了一个类为 ,主要负责程序内部广播的注册和发送 使用LocalBrocastManager的好处
- 发送的广播只会在自己的App内传播,不会泄露到其他App,确保隐私数据不会泄露
- 其他App也无法向自己的App发送广播
- 比系统全局广播更加高效
LocalBroadcastManager.getInstance(this).sendBrocast();//发送异步广播
LocalBroadcastManager.getInstance(this).sendBrocastSync();//发送同步广播
LocalBroadcastManager.getInstance(this).registerReceiver();//注册广播
LocalBroadcastManager.getInstance(this).unregisterReceiver();//注销广播
//只使用代码注册发送广播,对于AndroidManifest注册的广播接受则不适合
私有的广播
私有的广播是最安全的广播,因为发出的广播,只有应用程序内部收到。动态广播无法注册为私有,所以私有广播只存在于静态广播中
<receiver
android:name=".PrivateBrocast"
android:exported="true" />
public void onReceive(Context context, Intent intent) {
setResultCode(200);
//处理敏感数据
String param = intent.getStringExtra("param");
setResultData(param);
//终止广播不需要继续发送广播
abortBroadcast();
}
公共广播
注意点
- 设置exported为true
- 获取intent小心
- return result 不放置敏感信息
- 优先考虑动态广播
内部广播
- 定义一个内部的Singature Permession来发送,接受广播
- 设置expotred属性为true
- 静态,动态广播,注册的是也需要声明Singature Permession
Service编码安全
Service 和Activity的编码安全相似,同意Service的攻击方式和Activity攻击方式相似,也分为私有,公共,合作,伙伴等权限级别,注意点也相似。
Provider编码安全
因为ContenProvider 提供数据的接口,所以在对外公开的时候未处理不当,或者对查询的uri path没有正确的判断处理,则可能造成数据泄露
分类
- 私有的ContentProvider:不能被其他的应用程序所使用,所以是最安全的ContenProvider
- 公有的ContentProvider:没有指定特定接受者的ContentProvider,大多数应用能访问
- 合作的ContentProvider:可以被授权的合作应用所使用
- 内部的ContentProvider:只能被内部的应用所使用
- 部分的ContentProvider:基本上是私有的ContentProvider,单运行特定的应用使用特定的URI来访问
私有的ContentProvider
在单一应用中,是使用最安全的,在创建私有的ContentProvider注意
- Android2.2之前的版本别使用
- 设置exported属性为false
- 只能在同一个应用程序里面进行敏感信息发送和接受
- 在处理uri中,使用正则对Uri进行过滤判断,防止特殊的Uri
公共的ContentProvider
此类的ContentProvider没有置自定方,所以容易被恶意软件所攻击,容易被调用其update,insert,delete,select方法,在各个方法中做好请求数据的恶意检测。
合作的ContentProvider
不具备相同签名的不同应用,希望互相通过使用ContentProvider
注意点
- 设置exported属性为true
- 在代码中对使用方的包名和签名做检查操作
- 接受和处理敏感信息小心
- 确认可以公开给合作伙伴的信息才返回
内部的ContentProvider
- 只允许相同签名的应用程序使用,一般是一个系统中或者一个公司不同产品线的子项目共享的Provider
注意点 - 定义一个signature的permission
- 设置该ContentProvider需要次permission
- 设置exported为true
- 在代码内部检查传入应用的签名与包名的合法性
- 传入的参数信息与返回信息的敏感信息注意
部分的ContentProvider
只是部分开发的ContentProvider,运行临时访问ContentProvider的一部分内容。ContentProvider基本上是私有的,但运行使用特殊的URI来访问,一般通过特殊的标示来区别
注意点
- Android2.2之前的系统被使用
- 设置系统的exported为false
- 指定运行访问的URI权限授予临时路径
- 处理接受到的请求数据和安全,即使已经获得应用程序的临时授权
- 返回数据:注意只返回部分开发的内容
- 给予指定的URI的Intent临时访问权限
防止逆向
五种防御方式
- 代码混淆保护
- 针对不同逆向工具的保护技术如(IDA,JD-GUI)
- 增加逆向难度(java代码native化)
- 动态加载技术
- 代码验证技术
代码混淆
Android默认使用ProGuard工具进行混淆,好处在于
- 删除不可见字符,注释等无用代码,创建紧凑的代码文档,为了更快的网络传输,快速装载和更小的内存占用
- 重命名,是得创建的程序和程序库很难进行反向工程
- 能删除来自源文件中的没有调用的代码。
- 存放利用java6的快速加载的优点来提前检测和返回java6中存在的类文件
混淆的缺点
- 混淆错误,使用第三方jar的时候,必须告诉ProGuard不要检查,负责ProGuard会报错
- 运行错误,当code不能混淆的时候,需要正确配置,负责程序会运行出错,这种情况最多
- 调试定位较为痛苦,打印的错误信息中错误堆中是混淆后的代码,不容易定位,需要自己存一份map文件,来记录对应的混淆映射关系
DEX保护
DEX文件是Android的逻辑所在处,是非常重要的文件,虽然使用了ProGuard进行代码混淆,但是不能阻止逆向的问题
工具DEX Guard(混淆收费工具)除了提供混淆还提供,dex动态分割,class加密,字符串加密,API加密等
加壳
原理十分详细 http://blog.csdn.net/jiangwei0910410003/article/details/48415225
查看文章
防止jd-GUI查看代码
针对jd-GUI的一个bug来是其运行时候产生崩溃
switch (0) {
case 1001:
JSONObject jsonObject = null;
String date = null;
boolean isClose = false;
try {
jsonObject = new JSONObject("");
date = jsonObject.getString("date");
isClose = jsonObject.getBoolean("isClose");
} catch (Exception e) {
e.printStackTrace();
}
TT.set(null, "", date);
break;
}
static class TT {
public static void set(Context context, String key, String value) {
}
}
//在代码中植入这段无意义的代码,switch中的代码永远不会执行到,jd-gui在解析的时候回出现bug,
防止二次打包
二次打包一般是通过逆向工具APKTool工具逆向修改后。进行APK文件重新生成。
常用的验证策略里
在java层签名策略
获取签名和验证信息卸载Android的java代码
public class MyApplication extends Application {
private static final String SIGATRUE_MD5 = "xxx";
@Override
public void onCreate() {
super.onCreate();
String signatureMD5 = getSignatureMD5(this);
if (!signatureMD5.equals(SIGATRUE_MD5)) {
System.exit(0);
}
}
private String getSignatureMD5(Context context) {
String backString = "";
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
byte[] bytes = packageInfo.signatures[0].toByteArray();
backString = DisgUtils.md5(bytes);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return backString;
}
}
虽然保护可行,但是对于打包党保护意义并不大 smail中代码 if语句为
....
if -nez v0,##签名的判断位置
...
//只要将这个if-nez改为if-eqz 就变成
if(signatureMD5.equals(SIGATRUE_MD5)){
System.exit(0);
}
//在这里进行逻辑翻转
服务器签名验证
服务器根据签名与包名的正确性与否,给予设备是否返回正确的数据或进行强制退出操作,缺点
对于目前Android应用,多数是没有联网操作,一旦没有网络验证保护就无效了,用Android方法获取的签名信息用Java方法也可以获取,验证存放在服务器上也是为了保护正确的签名信息,但是保护意义没有任何作用,同样通过全局搜索关键字,伪造一个正确的签名信息也可以完美破解
NDK技术底层获取签名和验证
在java层做验证容易被逆向者修改,那么我们将逻辑代码植入到C/C++代码去实现,通过把Context ,Activity,PackageManager,PackageInfo 四个对象中的一个对象传递去底层,在底层获取签名信息进行验证。因为获取验证都封闭在更安全的so文件下,所以能在一定意义上其保护作用
防止动态分析
检查运行环境,只要不在我们制定的平台或者指定的环境中我们就认为在做调试工具
检查是否运行在IDA Pro中,在使用IDA,gdb等工具,动态调试Android应用程序,都会在Android设备上启动一些本地进程,如常见的android_server,gdbserver等,只要判断此类进程是否存在就可以认为当前处于调试阶段,终止该应用程序
检查是否是模拟器
根据设备的imei与Build.MODE来判断是否是模拟器
private boolean isEmualotr(Context context) {
try {
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String deviceId = telephonyManager.getDeviceId();
if (deviceId != null && deviceId.equals("000000000000000")) {
return true;
}
return Build.MODEL.equals("sdk") || Build.MODEL.equals("google_sdk");
} catch (Exception ioe) {
}
return false;
}
防止动态注入
在Android平台上调用ptrace函数才能进行so注入,我们需要防止对方使用ptrace函数,或者保证自己的平台进行注入检查
我们对当前的so进行排查,存在非本包名下的so文件均认为被注入了,如果是合作伙伴的so,可以加入白名单机制
public boolean isCheck(int pid, String pkg) {
File file = new File("/proc/" + pid + "/maps");
if (!file.exists()) {
return false;
}
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
String lineString = null;
while ((lineString = bufferedReader.readLine()) != null) {
String trim = lineString.trim();
if (trim.contains("/data/data") && !trim.contains("/data/data/" + pkg)) {
return false;
}
}
bufferedReader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
渗透测试
在发布前,对系统的任何弱点,技术缺陷或漏洞的主动分析,渗透分析是从一个攻击者可能存在的位置进行。
渗透步骤
被动阶段,测试人员试图去理解被测应用的逻辑,使用工具去收集信息,如HTTP代理工具去查看所有的请求与响应,使用ADB工具查看Android应用程序释放的本地储存文件,特别是私有文件
主动阶段,收集相关的应用程序逻辑以及暴露的地方,通常都是基于特殊环境下,如拥有Root手机,不同版本的Android系统。
渗透测试工具
- 安全评估框架drozer,是针对Android系统的安全审计与攻击的框架
- BusyBox,BusyBox是标准的linux工具,
- 集成化工具,dSploit,zANTI等
参考文件
http://blog.csdn.net/javy_codercoder/article/details/51692334
http://blog.csdn.net/jiangwei0910410003/article/details/48415225
Android安全技术揭秘与防范