I have been working on Android framework for nearly three years . Now the company asked me to do some research on Android APK security, so I recently found a lot of information on the Internet to learn. Now I will summarize the results of my recent study. I will make a series of these results and share them with you so that we can make progress together. This article mainly talks about APK 's shelling technology. Without further ado, I will get straight to the point. Packing Technology Principle The so-called apk shelling technology is the same as the PC exe shelling principle, which is to wrap another piece of code outside the program to protect the code inside from being illegally modified or decompiled, and to take control of the program first when the program is running to do some work we want to do. (Haha, it's similar to the principle of viruses) The principle of PC exe 's packing is as follows: Android apk packing implementation The technical points that need to be solved to achieve shelling are as follows: ( 1 ) How to execute our shell program immediately ? First of all, according to the above principle, if we want to get the control of the program first in apk , as android apk developers, we all know that Application will be called by the system first and our program will be executed here. ( 2 ) How to merge our shell program with the original Android apk file ? We know that android apk will eventually be packaged to generate a dex file. After our program generates a dex file, we can merge the apk we want to pack and our dex file into one file, and then modify the checksum , signature and file_size information in the dex file header , and attach the length information of the packed apk to the dex file so that we can unpack and ensure the normal operation of the original apk . The structure of the entire file after packing is as follows: ( 3 ) How to make the original apk run normally ? According to the merging method in ( 2 ), when our program is first running, the dex file is read in reverse to obtain the original apk file and dynamically loaded through DexClassLoader . The specific implementation is as follows: ( 1 ) Modify the AndroidMainfest.xml file of the original apk . If the content of the AndroidMainfest.xml file of the original apk is as follows: 1. <application 2. android:icon = "@drawable/ic_launcher" 3. android:label = "@string/app_name" 4. android:theme = "@style/AppTheme" android:name ="com.android.MyApplication" > 5. </application> The modified content is as follows: 1. <application 2. android:icon = "@drawable/ic_launcher" 3. android:label = "@string/app_name" 4. android:theme = "@style/AppTheme" android:name ="com.android.shellApplication" > 5. <meta-data android:name = "APPLICATION_CLASS_NAME" android:value = " com.android.MyApplication " /> 6. </application> com.android.shellApplication is the name of our application , and 7. <meta-data android:name = "APPLICATION_CLASS_NAME" android:value = " com.android.MyApplication " /> Is the application name of the original apk . ( 2 ) The code for merging files is as follows: - public class ShellTool {
-
-
-
- public static void main(String[] args) {
-
- try {
- File payloadSrcFile = new File( "payload.apk" );
- File unShellDexFile = new File( "classes.dex" );
- byte [] payloadArray = encrpt(readFileBytes(payloadSrcFile));
- byte [] unShellDexArray = readFileBytes(unShellDexFile);
- int payloadLen = payloadArray.length;
- int unShellDexLen = unShellDexArray.length;
- int totalLen = payloadLen + unShellDexLen + 4 ;
- byte [] newdex = new byte [totalLen];
-
- System.arraycopy(unShellDexArray, 0 , newdex, 0 , unShellDexLen);
-
- System.arraycopy(payloadArray, 0 , newdex, unShellDexLen,
- payloadLen);
-
- System.arraycopy(intToByte(payloadLen), 0 , newdex, totalLen- 4 , 4 );
-
- fixFileSizeHeader(newdex);
-
- fixSHA1Header(newdex);
-
- fixCheckSumHeader(newdex);
-
-
- String str = "outdir/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){
- return srcdata;
- }
-
-
- private static void fixCheckSumHeader( byte [] dexBytes) {
- Adler32 adler = new Adler32();
- adler.update(dexBytes, 12 , dexBytes.length - 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 );
- System.out.println(Long.toHexString(value));
- System.out.println();
- }
-
-
- 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;
- }
-
-
- private static void fixSHA1Header( byte [] dexBytes)
- throws NoSuchAlgorithmException {
- MessageDigest md = MessageDigest.getInstance( "SHA-1" );
- md.update(dexBytes, 32 , dexBytes.length - 32 );
- byte [] newdt = md.digest();
- System.arraycopy(newdt, 0 , dexBytes, 12 , 20 );
- String hexstr = "" ;
- for ( int i = 0 ; i < newdt.length; i++) {
- hexstr += Integer.toString((newdt[i] & 0xff ) + 0x100 , 16 )
- .substring( 1 );
- }
- System.out.println(hexstr);
- }
-
-
- 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 );
- }
-
-
- 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();
- }
- }
- }
-
-
- }
? ( 3 ) Load and run the original apk file in our program . The code is as follows: - public class shellApplication extends Application {
-
-
- private static final String appkey = "APPLICATION_CLASS_NAME" ;
- private String apkFileName;
- private String odexPath;
- private String libPath;
-
-
- protected void attachBaseContext(Context base) {
- super .attachBaseContext(base);
- try {
- File odex = this .getDir( "payload_odex" , MODE_PRIVATE);
- File libs = this .getDir( "payload_lib" , MODE_PRIVATE);
- odexPath = odex.getAbsolutePath();
- libPath = libs.getAbsolutePath();
- apkFileName = odex.getAbsolutePath() + "/payload.apk" ;
- File dexFile = new File(apkFileName);
- if (!dexFile.exists())
- dexFile.createNewFile();
-
- byte [] dexdata = this .readDexFileFromApk();
-
- this .splitPayLoadFromDex(dexdata);
-
- Object currentActivityThread = RefInvoke.invokeStaticMethod(
- "android.app.ActivityThread" , "currentActivityThread" ,
- new Class[] {}, new Object[] {});
- String packageName = this .getPackageName();
- HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect(
- "android.app.ActivityThread" , currentActivityThread,
- "mPackages" );
- WeakReference wr = (WeakReference) mPackages.get(packageName);
- DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
- libPath, (ClassLoader) RefInvoke.getFieldOjbect(
- "android.app.LoadedApk" , wr.get(), "mClassLoader" ));
- RefInvoke.setFieldOjbect( "android.app.LoadedApk" , "mClassLoader" ,
- wr.get(), dLoader);
-
-
- } catch (Exception e) {
-
- e.printStackTrace();
- }
- }
-
-
- public void onCreate() {
- {
-
-
-
- String appClassName = null ;
- try {
- ApplicationInfo ai = this .getPackageManager()
- .getApplicationInfo( this .getPackageName(),
- PackageManager.GET_META_DATA);
- Bundle bundle = ai.metaData;
- if (bundle != null
- && bundle.containsKey( "APPLICATION_CLASS_NAME" )) {
- appClassName = bundle.getString( "APPLICATION_CLASS_NAME" );
- } else {
- return ;
- }
- } catch (NameNotFoundException e) {
-
- e.printStackTrace();
- }
-
-
- Object currentActivityThread = RefInvoke.invokeStaticMethod(
- "android.app.ActivityThread" , "currentActivityThread" ,
- new Class[] {}, new Object[] {});
- Object mBoundApplication = RefInvoke.getFieldOjbect(
- "android.app.ActivityThread" , currentActivityThread,
- "mBoundApplication" );
- Object loadedApkInfo = RefInvoke.getFieldOjbect(
- "android.app.ActivityThread$AppBindData" ,
- mBoundApplication, "info" );
- RefInvoke.setFieldOjbect( "android.app.LoadedApk" , "mApplication" ,
- loadedApkInfo, null );
- Object oldApplication = RefInvoke.getFieldOjbect(
- "android.app.ActivityThread" , currentActivityThread,
- "mInitialApplication" );
- ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke
- .getFieldOjbect( "android.app.ActivityThread" ,
- currentActivityThread, "mAllApplications" );
- mAllApplications.remove(oldApplication);
- ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
- .getFieldOjbect( "android.app.LoadedApk" , loadedApkInfo,
- "mApplicationInfo" );
- ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
- .getFieldOjbect( "android.app.ActivityThread$AppBindData" ,
- mBoundApplication, "appInfo" );
- appinfo_In_LoadedApk.className = appClassName;
- appinfo_In_AppBindData.className = appClassName;
- Application app = (Application) RefInvoke.invokeMethod(
- "android.app.LoadedApk" , "makeApplication" , loadedApkInfo,
- new Class[] { boolean . class , Instrumentation. class },
- new Object[] { false , null });
- RefInvoke.setFieldOjbect( "android.app.ActivityThread" ,
- "mInitialApplication" , currentActivityThread, app);
-
-
- HashMap mProviderMap = (HashMap) RefInvoke.getFieldOjbect(
- "android.app.ActivityThread" , currentActivityThread,
- "mProviderMap" );
- Iterator it = mProviderMap.values().iterator();
- while (it.hasNext()) {
- Object providerClientRecord = it.next();
- Object localProvider = RefInvoke.getFieldOjbect(
- "android.app.ActivityThread$ProviderClientRecord" ,
- providerClientRecord, "mLocalProvider" );
- RefInvoke.setFieldOjbect( "android.content.ContentProvider" ,
- "mContext" , localProvider, app);
- }
- app.onCreate();
- }
- }
-
-
- private void splitPayLoadFromDex( byte [] data) throws IOException {
- byte [] apkdata = decrypt(data);
- int ablen = apkdata.length;
- byte [] dexlen = new byte [ 4 ];
- System.arraycopy(apkdata, ablen - 4 , dexlen, 0 , 4 );
- ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
- DataInputStream in = new DataInputStream(bais);
- int readInt = in.readInt();
- System.out.println(Integer.toHexString(readInt));
- byte [] newdex = new byte [readInt];
- System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0 , readInt);
- File file = new File(apkFileName);
- try {
- FileOutputStream localFileOutputStream = new FileOutputStream(file);
- localFileOutputStream.write(newdex);
- localFileOutputStream.close();
-
-
- } catch (IOException localIOException) {
- throw new RuntimeException(localIOException);
- }
-
-
- ZipInputStream localZipInputStream = new ZipInputStream(
- new BufferedInputStream( new FileInputStream(file)));
- while ( true ) {
- ZipEntry localZipEntry = localZipInputStream.getNextEntry();
- if (localZipEntry == null ) {
- localZipInputStream.close();
- break ;
- }
- String name = localZipEntry.getName();
- if (name.startsWith( "lib/" ) && name.endsWith( ".so" )) {
- File storeFile = new File(libPath + "/"
- + name.substring(name.lastIndexOf( '/' )));
- storeFile.createNewFile();
- FileOutputStream fos = new FileOutputStream(storeFile);
- byte [] arrayOfByte = new byte [ 1024 ];
- while ( true ) {
- int i = localZipInputStream.read(arrayOfByte);
- if (i == - 1 )
- break ;
- fos.write(arrayOfByte, 0 , i);
- }
- fos.flush();
- fos.close();
- }
- localZipInputStream.closeEntry();
- }
- localZipInputStream.close();
-
-
- }
-
-
- private byte [] readDexFileFromApk() throws IOException {
- ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
- ZipInputStream localZipInputStream = new ZipInputStream(
- new BufferedInputStream( new FileInputStream(
- this .getApplicationInfo().sourceDir)));
- while ( true ) {
- ZipEntry localZipEntry = localZipInputStream.getNextEntry();
- if (localZipEntry == null ) {
- localZipInputStream.close();
- break ;
- }
- if (localZipEntry.getName().equals( "classes.dex" )) {
- byte [] arrayOfByte = new byte [ 1024 ];
- while ( true ) {
- int i = localZipInputStream.read(arrayOfByte);
- if (i == - 1 )
- break ;
- dexByteArrayOutputStream.write(arrayOfByte, 0 , i);
- }
- }
- localZipInputStream.closeEntry();
- }
- localZipInputStream.close();
- return dexByteArrayOutputStream.toByteArray();
- }
-
-
-
- private byte [] decrypt( byte [] data) {
- return data;
- }
? Based on the above description, I believe everyone has a certain understanding of apk shelling technology. In the next article, we will explain another android apk anti-decompilation technology - modifying dalvik instructions at runtime (http://my.oschina.net/u/2323218/blog/396203). |