安卓逆向入门-安全基线

安卓OWASP10

组件安全

Android有Activity、Service、ContentProvider、Broadcast Receiver、Indent五大组件,不需要进行跨应用运行的组件需要在AndroidManifest.xml中将属性android\:exported设为false,否则该组件可被任意应用启动,被恶意调用。实在需要设为true的组件应用android\:permission指定自定义权限,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<permission
android:name="example.permission.USESERVICE"
android:protectionLevel="normal"
/>
<service
android:name="com.example.haohai.helloWorldService"
android:exported="true"
android:label="@string/app_name"
android:permission="example.permission.USESERVICE"
</service>

<!--需要调用HelloWorldActivity的需要声明权限 -->
<uses-permission
android:name="example.permission.USESERVICE"
/>

ContentProvider为外部提供统一数据存储和读取接口,赢对其设置的腹泻访问控制权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 针对APK某文件添加读取权限 -->
<provider
android:name="com.example.haohai.HelloWorldProvider"
android:authorities="com.example.haohai.HelloWorldProvider">
<path-permission
android:pathPattern="/Apk/.*"
android:readPermission="com.example.haohai.permission.READ"
android:protectionLevel="normal"
/>
</provider>

<!--设置全局刻度权限-->
<provider
android:name="com.example.haohai.HelloWorldProvider"
android:authorities="com.example.haohai.HelloWorldProvider"
android:readPermission="com.example.haohai.permission.READ">
</provider>

Intent进行组件间跳转有两种方法,一种显式调用,即通过指定组件名,用setComponentsetClassNamesetClassnew Intent(A.class,b.class)等。另一种是隐式调用,配置文件中设置Intent Filter,系统根据action、category、数据等隐式意图进行组件跳转,可能存贷判断失误而导致数据泄露。显式调用方法:

1
2
Intent intent=new Intent(HelloWolrdActivity.this,TargetActivity.class);
startActivity(intent)

数据存储安全

SharedPreference内部用键值对方式存储,XML结构存储在/data/data/packageName/shared_prefs下。MODE_PRIVAT4E表示当前文件私有化存储模式,只能被应用本身访问,写入时覆盖原文件。反之MODE_APPEND模式将追加内容,应设置为MODE_PRIVATE以防被恶意修改或泄露:

1
SharedPreferences mySharedPreferences=getSharedPreferences("HelloWorld",Activity.MODE_PRIVATE);

认证安全

WebView自带记住密码功能,密码以明文保存在/data/data/com.package.name/databases/webview.db中,Root后可读,关闭自动保存功能为:

1
myWebView.getSettings().setSavePassword(false);

数据加密

敏感信息建议用SHA-256或SHA-3加密,不建议MD5、SHA-1、PIPEMD。

授权安全

绑定设备时,客户端检测用户是否有权限访问数据或执行操作时,可能出现信息伪造、数据替换等风险。为每个用户分配一个唯一ID,可对所有访问关键数据和敏感操作进行追溯,且该唯一ID应当不可伪造。IMEI国际移动设备识别码在移动电话网络中唯一标识了每台独立的移动通信设备,但模拟器可能会模拟或伪造。为避免该风险,建议用DEVICE_ID、MAC ADDRESS、Sim Serial Number、IMEI进行组装生成哈希后使用。

1
2
3
4
5
6
7
8
9
10
11
TelephonyManager tm=(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String DEVICE_ID=tm.getDevice();

WifiManager wifi=(WifiManager)getSystemService(Context.WIFI_SERVICE);
WifiInfo info=wifi.getConnectionInfo();
String macAddress=info.getMacAddress();

TelephonyManager tm=(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String SimSerialNumber=tm.getSimSerialNumber();

String IMEI=((TelephonyManager)getSystemService(TELEPHONY_SERVICE)).getDeviceId();

其他

SSL通信服务/客户端检测信任任意整数:开发者重写了checkServerTrusted但没做任何校验,使得可用中间人攻击获取加密内容。

WebView忽略SSL证书错误:WebView调用onReceivedSslError时直接执行handler.proceed()来忽略证书错误,可引起中间人攻击。

HTTPS关闭主机名验证:构造HttpClient时设置HostnameVerifier使用ALLOW_ALL_HOSTNAME_VERIFIER或空,可导致中间人攻击。修复应用STRICT_HOSTNAME_VERIFIER并自定义实现。

Intent Scheme URL攻击:AndroidManifest.xml设置Scheme协议后可用浏览器打开对应Activity,攻击者可构造Intent唤起应用相应组件,引起拒绝服务或提权漏洞。修复应配置category filter以添加android.intent.category.BROWSABLE。

AES弱加密:使用AES/ECB/NoPadding或AES/ECB/PKCS5padding模式。修复应指定CBC或CFB模式,填充PKCS5padding填充,密钥至少128位,推荐256位。

Provider文件目录便利:Provider被导出且复写openFile时没有对Content Query Uri进行判断或过滤,导致文件遍历。

WebView启用访问文件数据:WebView使用默认的setAllowFileAccess(true)时,则在File域下可执行任意JS代码,绕过同源策略对私有文件目录访问,造成隐私泄露。

Unzip解压缩:getName获取文件名后未对名称进行校验,被解压文件可进行目录跳转而被解压到其他目录,覆盖相应文件造成任意代码执行。

未使用编译器堆栈保护技术:Stack Canaries、PIE,修复在Android.mk中添加:

1
2
LOCAL_CFLAGS:=-Wall-O2-U_FORTIFY_SOURCE-fstack-protector-all
LOCAL_CFLAGS:=-fpie -pie

随机数不安全使用:用SecureRandom类setSeed时生成随机数有确定性,应用/dev/urandom或/dev/random来初始化伪随机数生成器

AES/DES硬编码密钥。

权限安全

PackageManagerService中有ApplicationInfo.FLAG_SYSTEM标记,或位于特定目录如/vendor/overlay、/system/framework、/system/priv-app、/system/app、/vendor/app、/oem/app等,为系统应用。

PackageManagerService中有ApplicationInfo.PRIVATE_FLAG_PRIVILEGED标记的系统应用为特权应用,可使用protectionLevel为signatureOrSystem或signature|privileged权限。

权限分为正常权限、危险权限、signature、signatureOrSystem。危险权限如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
CALENDAR:
READ_CALENDAR
WRITE_CALENDAR

CAMERA:
CAMERA

CONTACTS:
READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS

LOCATION:
ACCESS_FINE_LOCATION
ACCESE_COARSE_LOCATION

MICROPHONE:
RECORD_AUDIO

PHONE:
READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS

SENSORS:
BODY_SENSORS

SMS:
SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS

STORAGE:
READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_SOTRAGE

signature权限只有当请求该权限应用具有与声明权限的应用相同签名时,不通知用户并授予权限。signatureOrSystem权限时请求和声明权限的应用签名相同或请求权限的为系统应用。

MobSF

安装:

1
2
docker pull opensecurity/mobile-security-framework-mobsf:latest
docker run -it --rm -p 8000:8000 opensecurity/mobile-security-framework-mobsf:latest

默认登陆账密mobsf。

IoT入门

IoT安卓应用常见高风险权限:

1
2
3
4
5
6
7
8
9
10
11
ACCESS_COARSE_LOCATION CellID或WiFi 接收到基站服务信号便可获得位置信息
ACCESS_FINE_LOCATION 精良位置GPS
CAMERA
GET_TASKS
MOUNT_UNMOUNT_FILESYSTEMS SD卡内创建删除文件
READ_EXTERNAL_STORAGE
READ_PHONE_STATE 获取手机号码、IMEI、IMSI等
REQUEST_INSTALL_PACKAGES 安装未知来源应用
SYSTEM_ALERT_WINDOW 全局弹出系统弹出框
WRITE_EXTERNAL_STORAGE 向SD卡写入
WRITE_SETTINGS 修改系统设置数据

Android数组存储位置有:

1
2
3
4
5
6
/data/data/包名/
/data/data/包名/databases
/data/data/包名/shared_prefs
/data/data/包名/files/*.realm
/data/data/包名/app_webview
/sdcard/Android/data/包名

常见攻击方法

重签名绕过

例如签名校验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//MainActivity
package com.demo.repackage;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import com.demo.tools.ToolUtils;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ToolUtils.getSignature2(getApplicationContext());
if (ToolUtils.checkSignature(getApplicationContext())){
Log.i("checkSignature", "True");
}else {
Log.i("checkSignature", "False");
}
}
}

//ToolUtils
package com.demo.tools;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.util.Log;
import java.security.MessageDigest;
public class ToolUtils {
static String sign = "8b34e97425e0e682e3a73bd55830fc28ce34a4e8";
public static boolean checkSignature(Context context){
if (sign.equals(getAppSignature(context))){
return true;
}else {
return false;
}
}
public static String getAppSignature(Context context) {
try {
return sha1(getSignature(context));
} catch (Exception e) {}
return null;
}
public static String sha1(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(bytes);
byte[] b = md.digest();
int i;
StringBuilder sb = new StringBuilder();
for (byte value : b) {
i = value;
if (i < 0)
i += 256;
if (i < 16)
sb.append("0");
sb.append(Integer.toHexString(i));
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public static byte[] getSignature(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
Signature[] signatures = packageInfo.signatures;
if (signatures != null){
return signatures[0].toByteArray();
}
} catch (Exception e) {}
return null;
}
}

这里通过PacakgeManagerService(PMS)服务获取应用签名信息,通过反射结合动态代理方式控制PMS返回值,达到替换签名的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//HookServiceWraper 替换PMS
package com.verify.signature;
import android.content.Context;
import android.content.pm.PackageManager;
import com.verify.signature.ProxyHookPMS;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class HookServiceWraper {
public static void startHookPMS(Context context, String signData, String packageName){
try{
// 获取全局ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 获取原始sPackageManager
Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
sPackageManagerField.setAccessible(true);
Object sPackageManager = sPackageManagerField.get(currentActivityThread);
// 准备代理对象
Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(),new Class<?>[] { iPackageManagerInterface },new ProxyHookPMS(sPackageManager, signData, packageName, 0));
// 替换掉sPackageManager
sPackageManagerField.set(currentActivityThread, proxy);
// 替换 ApplicationPackageManager中 mPM对象
PackageManager pm = context.getPackageManager();
Field mPmField = pm.getClass().getDeclaredField("mPM");
mPmField.setAccessible(true);
mPmField.set(pm, proxy);
}catch (Exception e){
e.printStackTrace();
}
}
}

//ProxyHookPMS 用官方签名信息替换伪造的签名信息
package com.verify.signature;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.util.Log;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyHookPMS implements InvocationHandler{
private Object base;
private String SIGN;
private String appPkgName = "";
public ProxyHookPMS(Object base, String sign, String appPkgName, int hashCode) {
try {
this.base = base;
this.SIGN = sign;
this.appPkgName = appPkgName;
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("getPackageInfo".equals(method.getName())){
String pkgName = (String)args[0];
Integer flag = (Integer)args[1];
if(flag == PackageManager.GET_SIGNATURES && appPkgName.equals(pkgName)){
Signature sign = new Signature(SIGN);
PackageInfo info = (PackageInfo) method.invoke(base, args);
info.signatures[0] = sign;
return info;
}
}
return method.invoke(base, args);
}
}

//MyApplication
package com.verify.signature;
import android.app.Application;
import android.content.Context;
import com.verify.signature.HookServiceWraper;
public class MyApplication extends Application{
@Override
protected void attachBaseContext(Context baseContext) {
String sign = "3082038f30820277a00302010202045ad85776300d06092a864886f70d01010b05003078311230100603550406130952657061636b616765311230100603550408130952657061636b616765311230100603550407130952657061636b61676531123010060355040a130952657061636b61676531123010060355040b130952657061636b616765311230100603550403130952657061636b616765301e170d3232303431323031353032315a170d3437303430363031353032315a3078311230100603550406130952657061636b616765311230100603550408130952657061636b616765311230100603550407130952657061636b61676531123010060355040a130952657061636b61676531123010060355040b130952657061636b616765311230100603550403130952657061636b61676530820122300d06092a864886f70d01010105000382010f003082010a0282010100a207868100beed73e1f04f36435af22054e4ef1eed443c87e73844ece9c1269f40d1ec652528f7c0375cb81ad2edc9acc57136ac2e43252b158d5e8a1118d2d0772aa3b9f0ef6f34906e185eedee4e5dacc45c78b57e1d5b42a031261e3f05cce8055033cfdf4ace0ecdb2ff4c704b0c7d05ef45590040d3921393e15d38b782d1349156155a69461745b83325fed5c4dc8bb2f6c0a74969c1f35e19a5f7ba568e17245360de5c26718454e9a165b857fa236840635b9048bcccf620ea5083cec6b875ee6a541db17d3a8da6a328b237dba73f2d66191593fe76aa9a133cd5dcdafa33d697052b2599c7440689b74434702b6df163152631628985c7eb7120fb0203010001a321301f301d0603551d0e0416041407eaf98b249478511f997b2bb8eea40c236000c4300d06092a864886f70d01010b050003820101004708ad73716e350908dd2bf4fde35667885aa8c0bd3ac5805d9fcee40e1fd70d2cb8db75cb80d9db2bb0e9876f5e7c4bf057816d26592e2608f24ff3b5d9fbe8080ff6f8bfa7ae5bfb48d9a497474005ef51fc41af1d805e9f2bf33786c42b95c488db6c69a201bee659023509ea6182a435330e94f0c1c5ac51a37d3b4785189b66354e9403d6bd72af99b0dd30ae66bfc3a9fc07e8a3c84121d3f6d2dc3941b710b84f46df1604a13c42598dfdc1a6da6fde94a47abf16e042657fd361e8e5cce789b6078b604b0df20abe0d11734fa671fe41eb5c563e27e6568de367fc0c786c90f298cb1783a6881891b04005b0c84079103283b404cdfe7c91d319bf20";
String packageNeme = "com.demo.repackage";
HookServiceWraper.startHookPMS(baseContext, sign, packageNeme);
super.attachBaseContext(baseContext);
}
@Override
public void onCreate() {
super.onCreate();
}
}

使用方法是创建一个和目标应用包名相同的新应用,将上述Hook代码编译为APK再解包,获取Smali代码文件。将目标应用也解包,将Hook用的Smali塞到目标应用的Smali文件下,并重打包。