安卓逆向入门-安全基线 安卓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 > <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 <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进行组件间跳转有两种方法,一种显式调用,即通过指定组件名,用setComponent
、setClassName
、setClass
、new 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 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" ); } } } 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 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 { Class<?> activityThreadClass = Class.forName("android.app.ActivityThread" ); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread" ); Object currentActivityThread = currentActivityThreadMethod.invoke(null ); 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 )); sPackageManagerField.set(currentActivityThread, proxy); PackageManager pm = context.getPackageManager(); Field mPmField = pm.getClass().getDeclaredField("mPM" ); mPmField.setAccessible(true ); mPmField.set(pm, proxy); }catch (Exception e){ e.printStackTrace(); } } } 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); } } 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文件下,并重打包。
安全加固 ProGuard build.gradle中添加:
1 2 3 4 5 6 buildTypes{ release{ minifyEnabled true proguardFiles getDefaultProguardFile('proguard-optimize.txt' ),'proguard-rules.pro' } }
签名校验 上文说PMS返回值可能被替换,这里直接绕过系统API,直接读取data/app/包名/base.apk中签名文件,获取原始签名信息。系统PackageParser服务对APK进行解析并获取文件信息,但该系统不对外暴露,需要用反射调用:
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 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 package com.verify.signature;import android.content.Context;import android.content.DialogInterface;import android.os.Bundle;import androidx.appcompat.app.AlertDialog;import androidx.appcompat.app.AppCompatActivity;import android.util.Log;import android.view.View;import com.verify.signature.databinding.ActivityMainBinding;public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); Context context = getApplicationContext(); Log.i("Current sign ===>" , Tools.getAppSignature(context)); Log.i("Reflect sign ===>" , Tools.getReflectSignature(context)); Log.i("Native sign ===>" , Tools.getSignByJni(context)); binding.fab.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View view) { Tools.getSignatureFileCrc(context); if (!Tools.checkSignature(context)){ new AlertDialog .Builder(MainActivity.this ).setTitle("安全提醒" ) .setMessage("当前使用的客户端为非官方版本,存在极大的安全隐患。为了您的财产安全请在官方渠道下载重新安装!" ) .setPositiveButton("确定" , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialog, int which) { System.exit(0 ); } }).show(); } } }); } } package com.verify.signature;import android.content.Context;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.content.pm.Signature;import android.os.Build;import android.util.Log;import java.io.File;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.security.MessageDigest;import java.util.zip.ZipEntry;import java.util.zip.ZipFile;public class Tools { static { try { System.loadLibrary("test" ); } catch (Exception e) { e.printStackTrace(); } } public static native String getSignByJni (Context mContext) ; public static boolean checkSignature (Context context) { String sign = "8b34e97425e0e682e3a73bd55830fc28ce34a4e8" ; if (sign.equals(getReflectSignature(context))) return true ; else return false ; } public static String getSignatureFileCrc (Context context) { try { ZipFile zf; String path= context.getPackageManager().getApplicationInfo(context.getPackageName(), 0 ).publicSourceDir; zf = new ZipFile (path); ZipEntry ze = zf.getEntry("META-INF/MANIFEST.MF" ); String crcValue = String.valueOf(ze.getCrc()); Log.i("Test MANIFEST Crc ====>" ,crcValue); return crcValue; }catch (Exception e){ e.printStackTrace(); } return null ; } public static String getAppSignature (Context context) { try { return sha1(getSignature(context)); } catch (Exception e) {} return null ; } public static byte [] getSignature(Context context) { try { PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES); Signature[] signatures = packageInfo.signatures; if (signatures != null ) { Log.i("Test ===>" , signatures[0 ].toCharsString()); return signatures[0 ].toByteArray(); } } catch (Exception e) {} return null ; } public static String getReflectSignature (Context context) { try { String path= context.getPackageManager().getApplicationInfo(context.getPackageName(), 0 ).publicSourceDir; return sha1(getApkSignatureByReflect(path)); } 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 [] getApkSignatureByReflect(String apkPath) { String fullPackageParserPath = "android.content.pm.PackageParser" ; try { Class packageParserClass = Class.forName(fullPackageParserPath); Constructor pkgParserConstructor = null ; Object pkgParserIns = null ; if (Build.VERSION.SDK_INT > 20 ){ pkgParserConstructor = packageParserClass.getConstructor(); pkgParserIns = pkgParserConstructor.newInstance(); Class[] args = {File.class, Integer.TYPE};; Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage" , args); Object[] valueArgs = new Object [2 ]; valueArgs[0 ] = new File (apkPath); valueArgs[1 ] = PackageManager.GET_SIGNATURES; Object parserPackage = parsePackageMethod.invoke(pkgParserIns, valueArgs); if (Build.VERSION.SDK_INT>=28 ){ Class[] typeArgs = {parserPackage.getClass(),Boolean.TYPE}; Method collectCertificatesMethod = packageParserClass.getDeclaredMethod("collectCertificates" , typeArgs); Object[] valueArgs2 = {parserPackage, Build.VERSION.SDK_INT>28 }; collectCertificatesMethod.invoke(pkgParserIns, valueArgs2); Field mSignatures = null ; if (Build.VERSION.SDK_INT>=29 ) mSignatures = parserPackage.getClass().getDeclaredField("mSigningDetails" ); else mSignatures = parserPackage.getClass().getDeclaredField("mSignatures" ); mSignatures.setAccessible(true ); Object mSigningDetails = mSignatures.get(parserPackage); Field infoField = mSigningDetails.getClass().getDeclaredField("signatures" ); infoField.setAccessible(true ); Signature[] info = (Signature[]) infoField.get(mSigningDetails); Log.i("ReflectSignature ===>" , info[0 ].toCharsString()); return info[0 ].toByteArray(); } Class[] typeArgs = {parserPackage.getClass(),Integer.TYPE}; Method collectCertificatesMethod = packageParserClass.getDeclaredMethod("collectCertificates" , typeArgs); Object[] valueArgs2 = {parserPackage, PackageManager.GET_SIGNATURES}; collectCertificatesMethod.invoke(pkgParserIns, valueArgs2); Field packageInfoFld = parserPackage.getClass().getDeclaredField("mSignatures" ); Signature[] info = (Signature[]) packageInfoFld.get(parserPackage); Log.i("ReflectSignature ===>" , info[0 ].toCharsString()); return info[0 ].toByteArray(); } } catch (Exception e) { e.printStackTrace(); } return null ; } }
JNI层:
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 91 92 93 94 95 96 97 98 99 100 101 #include <jni.h> #include <string.h> #include <android/log.h> #include <malloc.h> #define TAG "Test" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) const char *APP_SIGNATURE = "10645EA8A12BE7A2C04B1F81DF3B4D90" ;void ByteToHexStr (const char *source, char *dest, int sourceLen) { short i; char highByte, lowByte; for (i = 0 ; i < sourceLen; i++) { highByte = source[i] >> 4 ; lowByte = source[i] & 0x0f ; highByte += 0x30 ; if (highByte > 0x39 ) dest[i * 2 ] = highByte + 0x07 ; else dest[i * 2 ] = highByte; lowByte += 0x30 ; if (lowByte > 0x39 ) dest[i * 2 + 1 ] = lowByte + 0x07 ; else dest[i * 2 + 1 ] = lowByte; } } jstring sha1 (JNIEnv *env, jbyteArray source) { jclass classMessageDigest = env->FindClass ("java/security/MessageDigest" ); jmethodID midGetInstance = env->GetStaticMethodID (classMessageDigest, "getInstance" ,"(Ljava/lang/String;)Ljava/security/MessageDigest;" ); jobject objMessageDigest = env->CallStaticObjectMethod (classMessageDigest,midGetInstance,env->NewStringUTF ("sha1" )); jmethodID midUpdate = env->GetMethodID (classMessageDigest, "update" , "([B)V" ); env->CallVoidMethod (objMessageDigest, midUpdate, source); jmethodID midDigest = env->GetMethodID (classMessageDigest, "digest" , "()[B" ); jbyteArray objArraySign = (jbyteArray) env->CallObjectMethod (objMessageDigest, midDigest); jsize intArrayLength = env->GetArrayLength (objArraySign); jbyte *byte_array_elements = env->GetByteArrayElements (objArraySign, NULL ); size_t length = (size_t ) intArrayLength * 2 + 1 ; char *char_result = (char *) malloc (length); memset (char_result, 0 , length); ByteToHexStr ((const char *) byte_array_elements, char_result, intArrayLength); *(char_result + intArrayLength * 2 ) = '\0' ; jstring stringResult = env->NewStringUTF (char_result); env->ReleaseByteArrayElements (objArraySign, byte_array_elements, JNI_ABORT); free (char_result); env->DeleteLocalRef (classMessageDigest); env->DeleteLocalRef (objMessageDigest); return stringResult; } static jobject getContext (JNIEnv *env) { jobject application = NULL ; jclass activity_thread_clz = env->FindClass ("android/app/ActivityThread" ); if (activity_thread_clz != NULL ) { jmethodID currentApplication = env->GetStaticMethodID (activity_thread_clz, "currentApplication" , "()Landroid/app/Application;" ); if (currentApplication != NULL ) application = env->CallStaticObjectMethod (activity_thread_clz, currentApplication); env->DeleteLocalRef (activity_thread_clz); } return application; } JNIEXPORT jstring JNICALL getSignByJni (JNIEnv *env, jobject thiz, jobject context) { jclass cls = env->GetObjectClass (context); jmethodID mid = env->GetMethodID (cls, "getPackageManager" ,"()Landroid/content/pm/PackageManager;" ); jobject pm = env->CallObjectMethod (context, mid); mid = env->GetMethodID (cls, "getPackageName" , "()Ljava/lang/String;" ); jstring packageName = (jstring) env->CallObjectMethod (context, mid); cls = env->GetObjectClass (pm); mid = env->GetMethodID (cls, "getPackageInfo" ,"(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;" ); jobject packageInfo = env->CallObjectMethod (pm, mid, packageName, 0x40 ); cls = env->GetObjectClass (packageInfo); jfieldID fid = env->GetFieldID (cls, "signatures" , "[Landroid/content/pm/Signature;" ); jobjectArray signatures = (jobjectArray) env->GetObjectField (packageInfo, fid); jobject signature = env->GetObjectArrayElement (signatures, 0 ); cls = env->GetObjectClass (signature); mid = env->GetMethodID (cls, "toByteArray" , "()[B" ); jbyteArray signatureByteArray = (jbyteArray) env->CallObjectMethod (signature, mid); return sha1 (env, signatureByteArray); }
SO防护 对不想对外暴露的函数进行隐藏:
1 2 3 __attribute__((visibility ("hidden" ))) int test (int a,int b) { return a+b; };
反调试 调试工具附加到目标进程时需要用ptrace
系统调用操作,以建立其与目标程序的跟踪关系。正常情况下每个进程同一时间只能被附加一次,若应用程序启动后fork一个子进程抢先一步附加到自身进程,则调试器将无法正常附加,达到调试目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dlfcn.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/syscall.h> #include <unistd.h> #include <sys/ioctl.h> #include <unistd.h> #include <sys/ptrace.h> #include <time.h> void check_debug_by_ptrace () { int pid = fork(); if (pid == 0 ) { int ppid = getppid (); if (ptrace (PTRACE_ATTACH, ppid, NULL , NULL ) == 0 ) { waitpid (ppid, NULL , 0 ); ptrace (PTRACE_CONT, NULL , NULL ); } } }
也可直接用PTRACE_TRACEME参数,在进程启动时主动请求被监控跟踪:
1 2 3 void anti_debug_by_ptrace () { ptrace (PTRACE_TRACEME,0 ,NULL ,NULL ); }
当攻击者使用调试器进行动态分析时,需要设置断点将应用挂起,此时调试器发出SIGTRAP信号。可以在应用中主动添加SIGTRAP信号监听代码:
1 2 3 4 5 6 7 8 9 void signal_handle (int sig) { exit (0 ); } void check_debug_by_signal () { long ret = (long )signal (SIGTRAP, signal_handle); raise (SIGTRAP); }
进程被附加后有些属性值被改变:
特征文件名
特征值
/proc/pid/status和/proc/pid/task/pid/status
进程status值中TracePid值非0时处于调试状态
/proc/pid/stat和/proc/pid/task/pid/stat
进程stat值中括号后跟随的第一个字母为t时处于调试状态
/proc/pid/wchan和/proc/pid/task/pid/wchan
进程wchan状态值为ptrace_stop时处于调试状态
提取特征值为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void check_debug_by_tracerpid () { char debug_file[56 ]={0 }; int state = 0 ; int pid = getpid (); sprintf (debug_file, "/proc/%d/status" , pid); FILE* fp = fopen (debug_file, "r" ); char line[1024 ]={0 }; while (fgets (line, 1024 , fp)){ if (strncmp (line, "TracerPid" , 9 ) == 0 ){ state = atoi (&line[10 ]); if (state != 0 ){ fclose (fp); exit (0 ); } break ; } memset (line, 0 , 1024 ); } fclose (fp); }
还可监测代码执行时间,可能出现单步调试现象:
1 2 3 4 5 6 7 8 9 10 void check_debug_by_time () { time_t start_time; time_t end_time; time (&start_time); time (&end_time); if (end_time - start_time > 10 ) exit (0 ); }
进行调试时需要开启并监听指定端口,若设备开启这些端口则说明在和调试器通信,处于调试状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void check_debug_by_port () { char buff[BUFF_LEN]; char line[BUFF_LEN]; const char * dir = "/proc/net/tcp" ; FILE *fp = fopen (dir, "r" ); while (fgets (buff, BUFF_LEN, fp) != NULL ) if (strstr (buff, "5D8A" ) != NULL || strstr (buff, "6B0A" ) != NULL || strstr (buff, "6B0B" ) != NULL ) { fclose (fp); exit (0 ); } fclose (fp); FILE *fd = popen ("netstat -apn" , "r" ); while (fgets (line, sizeof (line), fd) != NULL ) if (strstr (line, "23946" ) != NULL || strstr (line, "27402" ) != NULL || strstr (line, "27403" ) != NULL ){ fclose (fd); exit (0 ); } pclose (fd); }
为防Hook,可建立动态库黑名单机制,将动态库来源路径中非系统目录或其他不可信路径收集整理为黑名单。
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 #include <stdio.h> #include <stdlib.h> #include <dlfcn.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/syscall.h> #include <unistd.h> #include <sys/ioctl.h> #include <unistd.h> #include <sys/ptrace.h> void check_inject () { char pFilePath[32 ]; char pLibInfo[256 ]; char *pLibPath = NULL ; char *savePtr = NULL ; int pid = getpid (); sprintf (pFilePath, "/proc/%d/maps" , pid); FILE *fp = fopen (pFilePath, "r" ); while (fgets (pLibInfo, sizeof (pLibInfo), fp) != NULL ) { strtok_r (pLibInfo, " \t" , &savePtr); strtok_r (NULL , " \t" , &savePtr); strtok_r (NULL , " \t" , &savePtr); strtok_r (NULL , " \t" , &savePtr); strtok_r (NULL , " \t" , &savePtr); pLibPath = strtok_r (NULL , " \t" , &savePtr); if (pLibPath != NULL ) { if (check_block_list (pLibPath) == -1 ) { exit (0 ); } } memset (pLibInfo, 0 , 256 ); } fclose (fp); }
完整性校验 对应用安装包中文件计算CRC值,与原始CRC值对比,其中CRC值应保存到服务器中。
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 package com.test.integritycheck_android;import androidx.appcompat.app.AppCompatActivity;import android.content.Context;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;public class MainActivity extends AppCompatActivity { Button btn = null ; Context mContext = null ; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContext = getApplicationContext(); btn = findViewById(R.id.check); btn.setOnClickListener(new MyClick ()); } class MyClick implements View .OnClickListener{ @Override public void onClick (View view) { new Thread (new Runnable () { @Override public void run () { UtilTools.getFileCrc(mContext); UtilTools.getAllFileCrc(mContext); } }).start(); } } } package com.test.integritycheck_android;import android.content.Context;import android.util.Log;import java.util.Enumeration;import java.util.HashMap;import java.util.Map;import java.util.zip.ZipEntry;import java.util.zip.ZipFile;public class UtilTools { private static final String[] fileArray = {"META-INF/MANIFEST.MF" ,"classes.dex" ,"resources.arsc" ,"AndroidManifest.xml" }; public static Map getFileCrc (Context context) { Map result = new HashMap (); try { String path= context.getPackageManager().getApplicationInfo(context.getPackageName(), 0 ).publicSourceDir; ZipFile zf = new ZipFile (path); ZipEntry ze; for (String item:fileArray){ ze = zf.getEntry(item); if (ze != null ){ String crcValue = String.valueOf(ze.getCrc()); result.put(item, crcValue); Log.i(item+ " CRC ======>" ,crcValue); } } return result; }catch (Exception e){ e.printStackTrace(); } return null ; } public static Map getAllFileCrc (Context context) { Map result = new HashMap (); try { String path= context.getPackageManager().getApplicationInfo(context.getPackageName(), 0 ).publicSourceDir; ZipFile zf = new ZipFile (path); Enumeration<? extends ZipEntry > entries = zf.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); String crcValue = String.valueOf(entry.getCrc()); result.put(entry.getName(), crcValue); Log.i(entry.getName()+ " CRC ======>" ,crcValue); } return result; }catch (Exception e){ e.printStackTrace(); } return null ; } }