nikoxves 发表于 2015-12-17 12:01

Android Backup 文件解析

本帖最后由 nikoxves 于 2015-12-19 01:36 编辑

#0x0
今天有空做了一下RCTF的mobile题。第一题首先是android backup 的问题,这个可以手动提取backup的文件,也可以使用abe.jar提取(需要改一下文件格式),
使用前者的推荐看一下* [这篇博文](http://bobao.360.cn/learning/detail/169.html)。第二种方法则需要了解一下文件格式(其实也没多少- -)
.我去github上找了一下,backup 在 * [这里](https://github.com/android/platform_frameworks_base/blob/master/services/backup/java/com/android/server/backup/BackupManagerService.java),另外还有一个backup的app在* [这里](https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/backup/BackupManager.java)
另外我竟然发现了smali版的BMS(6666666666)。如果这里的文件不想看的话(毕竟有近1W行代码),还可以看一下abe.jar的实现代码,那里主要以解析为主,方便理解。有不对的地方请大牛多多指教

#0x1
我从github 和 abe源码对比看了下。

////////////////////////github///////////////////////////////////////
// Write the global file header.All strings are UTF-8 encoded; lines end
   // with a '\n' byte.Actual backup data begins immediately following the
   // final '\n'.
   //
   // line 1: "ANDROID BACKUP"
   // line 2: backup file format version, currently "2"
   // line 3: compressed?"0" if not compressed, "1" if compressed.
   // line 4: name of encryption algorithm
   //
   // When line 4 is not "none", then additional header data follows:
      //
      / line 5: user password salt
   // line 6: master key checksum salt
   // line 7: number of PBKDF2 rounds to use (same for user & master)
   // line 8: IV of the user key
   // line 9: master key blob
   //   IV of the master key, master key itself, master key checksum hash
   //
   // The master key checksum is the master key plus its checksum salt, run through
   // 10k rounds of PBKDF2.This is used to verify that the user has supplied the
   // correct password for decrypting the archive:the master key decrypted from
   // the archive using the user-supplied password is also run through PBKDF2 in
   // this way, and if the result does not match the checksum as stored in the
   // archive, then we know that the user-supplied password does not match the
   // archive's.
static final int BACKUP_FILE_VERSION = 3;
   static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
   static final int BACKUP_PW_FILE_VERSION = 2;
   static final String BACKUP_METADATA_FILENAME = "_meta";
   StringBuilder headerbuf = new StringBuilder(1024);
    headerbuf.append(BACKUP_FILE_HEADER_MAGIC);
    headerbuf.append(BACKUP_FILE_VERSION); // integer, no trailing \n
    headerbuf.append(mCompress ? "\n1\n" : "\n0\n");
////////////////////////////abe//////////////////////////////////
StringBuilder headerbuf = new StringBuilder(1024);

   headerbuf.append(BACKUP_FILE_HEADER_MAGIC);
   // integer, no trailing \n
   headerbuf.append(isKitKat ? BACKUP_FILE_V2 : BACKUP_FILE_V1);
   headerbuf.append(compressing ? "\n1\n" : "\n0\n");

这里一开始还是MAGIC ,然后是一个version和是否压缩的选项,version现在是3,但是代码的注释里还没有改,还可以看到version 写的是2, 另外compressed 是开启的。


try {
       // Set up the encryption stage if appropriate, and emit the correct header
       if (encrypting) {
             finalOutput = emitAesBackupHeader(headerbuf, finalOutput);
       } else {
       headerbuf.append("none\n");
      }

接下来就是判断用户是否输入了密码,有的话就加密, 没有就在header加一个"none"。这里abe对emitAesBackupHeader函数做了一些修改,感兴趣的可以去看一下,大体还是和源码一样的。然后如果有输入密码的话,会执行emitAesBackupHeader(headerbuf, finalOutput); 主要是记录一些秘钥信息和校验值
    // line 4: name of encryption algorithm
            headerbuf.append(ENCRYPTION_ALGORITHM_NAME);
            headerbuf.append('\n');
            // line 5: user password salt
            headerbuf.append(byteArrayToHex(newUserSalt));
            headerbuf.append('\n');
            // line 6: master key checksum salt
            headerbuf.append(byteArrayToHex(checksumSalt));
            headerbuf.append('\n');
            // line 7: number of PBKDF2 rounds used
            headerbuf.append(PBKDF2_HASH_ROUNDS);
            headerbuf.append('\n');

             IV = c.getIV();
            byte[] mk = masterKeySpec.getEncoded();
            byte[] checksum = makeKeyChecksum(PBKDF_CURRENT, masterKeySpec.getEncoded(),
                  checksumSalt, PBKDF2_HASH_ROUNDS);

            ByteArrayOutputStream blob = new ByteArrayOutputStream(IV.length + mk.length
                  + checksum.length + 3);
            DataOutputStream mkOut = new DataOutputStream(blob);
            mkOut.writeByte(IV.length);
            mkOut.write(IV);
            mkOut.writeByte(mk.length);
            mkOut.write(mk);
            mkOut.writeByte(checksum.length);
            mkOut.write(checksum);
            mkOut.flush();
            byte[] encryptedMk = mkC.doFinal(blob.toByteArray());
            headerbuf.append(byteArrayToHex(encryptedMk));
            headerbuf.append('\n');


分析到这一步,第一题就可以做了,只要把version和compressed的恢复一下就可以用abe提取了。 到这里整个头也分析完毕,实际就24个字节。然后abe剩下的部分也和源码不一样了,毕竟两个代码的用处都不一样。

#0x2
下面是后续分析,

    // Shared storage if requested
                if (mIncludeShared) {
                  try {
                        pkg = mPackageManager.getPackageInfo(SHARED_BACKUP_AGENT_PACKAGE, 0);
                        backupQueue.add(pkg);
                  } catch (NameNotFoundException e) {
                        Slog.e(TAG, "Unable to find shared-storage backup handler");
                  }
                }

                // Now actually run the constructed backup sequence
                int N = backupQueue.size();
                for (int i = 0; i < N; i++) {
                  pkg = backupQueue.get(i);
                  final boolean isSharedStorage =
                            pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);

                  mBackupEngine = new FullBackupEngine(out, pkg.packageName, null, mIncludeApks);
                  sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
                  mBackupEngine.backupOnePackage(pkg);

                  // after the app's agent runs to handle its private filesystem
                  // contents, back up any OBB content it has on its behalf.
                  if (mIncludeObbs) {
                        boolean obbOkay = obbConnection.backupObbs(pkg, out);
                        if (!obbOkay) {
                            throw new RuntimeException("Failure writing OBB stack for " + pkg);
                        }
                  }
                }

                // Done!
                finalizeBackup(out);

                .....

                try {
                  if (out != null) out.close();
                  mOutputFile.close();
                } catch (IOException e) {
                  /* nothing we can do about this */
                }
                synchronized (mCurrentOpLock) {
                  mCurrentOperations.clear();
                }
                synchronized (mLatch) {
                  mLatch.set(true);
                  mLatch.notifyAll();
                }
                sendEndBackup();
                obbConnection.tearDown();
                if (DEBUG) Slog.d(TAG, "Full backup pass complete.");
                mWakelock.release();

后续的操作主要是3个,把Sharedstorage.apk加入备份队列(不知道这里有没有理解错),然后就是对所选的应用进行备份,接着调用finalizeBackup(out)代表tar文件结束,最后就是一些扫尾工作。
由于右半部分比较简单不做分析。主要看循环里的代码
>首先从队列里取一个pkg,然后创建一个FullBackupEngine mBackupEngine对象.代码如下,可以看到,另外还创建了两个文件_meta,_manifest 但是这里的alsoApks 不是很清楚是做什么的,可能是因为我没有从头开始分析,导致个别参数不明确。

FullBackupEngine(OutputStream output, String packageName, FullBackupPreflight preflightHook,
                boolean alsoApks) {
            mOutput = output;
            mPreflightHook = preflightHook;
            mIncludeApks = alsoApks;
            mFilesDir = new File("/data/system");
            mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME);
            mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
      }


初始化完成后,调用sendOnBackupPackage(),参数是报名,如果是Sharedstorage.apk就用"Shared storage";
sendOnBackupPackage又调用mObserver.onBackupPackage(packageName)进行处理,这里的mObserver是一个
IFullBackupRestoreObserver对象,听名字大致可以猜测是用来监视整个备份过程的,然后
sendEndBackup()又有mObserver有关。然后在另一个目录找到相应的接口。


static final int TRANSACTION_onStartBackup = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

@Override
public void onBackupPackage(java.lang.String name) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
mRemote.transact(Stub.TRANSACTION_onBackupPackage, _data, null, android.os.IBinder.FLAG_ONEWAY);
}
finally {
_data.recycle();
}
}

public static Parcel obtain() {
      final Parcel[] pool = sOwnedPool;
      synchronized (pool) {
            Parcel p;
            for (int i=0; i<POOL_SIZE; i++) {
                p = pool;
                if (p != null) {
                  pool = null;
                  if (DEBUG_RECYCLE) {
                        p.mStack = new RuntimeException();
                  }
                  return p;
                }
            }
      }
      return new Parcel(0);
    }

public final boolean transact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException {
       ......
      boolean r = onTransact(code, data, reply, flags);
   ....
    }
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
.........
case TRANSACTION_onStartBackup:
{
data.enforceInterface(DESCRIPTOR);
this.onStartBackup();
return true;
}
..........

上面的代码主要用于和Binder通信,Parcel主要用于通信,不能包含复杂的数据,Binder比较复杂,这里也不过多讨论。
最后调用mBackupEngine.backupOnePackage(pkg)进行备份.

public int backupOnePackage(PackageInfo pkg) throws RemoteException {
          .......
            IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo,
                  IApplicationThread.BACKUP_MODE_FULL);
            if (agent != null) {
                ParcelFileDescriptor[] pipes = null;
                try {
                  // Call the preflight hook, if any
                  if (mPreflightHook != null) {
                        result = mPreflightHook.preflightFullBackup(pkg, agent);
                        if (MORE_DEBUG) {
                            Slog.v(TAG, "preflight returned " + result);
                        }
                  }

                  // If we're still good to go after preflighting, start moving data
                  if (result == BackupTransport.TRANSPORT_OK) {
                        pipes = ParcelFileDescriptor.createPipe();

                        ApplicationInfo app = pkg.applicationInfo;
                        final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
                        final boolean sendApk = mIncludeApks
                              && !isSharedStorage
                              && ((app.privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) == 0)
                              && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
                              (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);

                        byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(pkg.packageName,
                              UserHandle.USER_OWNER);

                        final int token = generateToken();
                        FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes,
                              token, sendApk, !isSharedStorage, widgetBlob);
                        pipes.close();   // the runner has dup'd it
                        pipes = null;
                        Thread t = new Thread(runner, "app-data-runner");
                        t.start();

                        // Now pull data from the app and stuff it into the output
                        try {
                            routeSocketDataToOutput(pipes, mOutput);
                        } catch (IOException e) {
                            Slog.i(TAG, "Caught exception reading from agent", e);
                            result = BackupTransport.AGENT_ERROR;
                        }

                        if (!waitUntilOperationComplete(token)) {
                            Slog.e(TAG, "Full backup failed on package " + pkg.packageName);
                            result = BackupTransport.AGENT_ERROR;
                        } else {
                            if (MORE_DEBUG) {
                              Slog.d(TAG, "Full package backup success: " + pkg.packageName);
                            }
                        }
                  }
                } catch (IOException e) {
                  Slog.e(TAG, "Error backing up " + pkg.packageName, e);
                  result = BackupTransport.AGENT_ERROR;
                } finally {
                  try {
                        // flush after every package
                        mOutput.flush();
                        if (pipes != null) {
                            if (pipes != null) pipes.close();
                            if (pipes != null) pipes.close();
                        }
                  } catch (IOException e) {
                        Slog.w(TAG, "Error bringing down backup stack");
                        result = BackupTransport.TRANSPORT_ERROR;
                  }
                }
            } else {
                Slog.w(TAG, "Unable to bind to full agent for " + pkg.packageName);
                result = BackupTransport.AGENT_ERROR;
            }
            tearDown(pkg);
            return result;
      }

同样又是分为几个要点,首先是启动一个agent,然后创建一个管道,开启一个线程转移数据,然后从管道中获取数据,最后输出到文件。
在创建runner的时候会根据是否是Sharedstorage.apk来决定写入文件。
// 这个函数还是挺难找到的
IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode) {
      IBackupAgent agent = null;
      synchronized(mAgentConnectLock) {
            mConnecting = true;
            mConnectedAgent = null;
            try {
                if (mActivityManager.bindBackupAgent(app, mode)) {
                  Log.d(TAG, "awaiting agent for " + app);

                  // success; wait for the agent to arrive
                  // only wait 10 seconds for the clear data to happen
                  long timeoutMark = System.currentTimeMillis() + TIMEOUT_INTERVAL;
                  while (mConnecting && mConnectedAgent == null
                            && (System.currentTimeMillis() < timeoutMark)) {
                        try {
                            mAgentConnectLock.wait(5000);
                        } catch (InterruptedException e) {
                            // just bail
                            return null;
                        }
                  }

                  // if we timed out with no connect, abort and move on
                  if (mConnecting == true) {
                        Log.w(TAG, "Timeout waiting for agent " + app);
                        return null;
                  }
                  agent = mConnectedAgent;
                }
            } catch (RemoteException e) {
                // can't happen
            }
      }
      return agent;
    }

这里的关系还是比较简单的,调用mActivityManager.bindBackupAgent(app, mode)后等待Agent到来,然后赋值给agent,返回。这里同样涉及到binder,暂时不做讨论。

   public void run() {
                try {
                  FullBackupDataOutput output = new FullBackupDataOutput(mPipe);

                  if (mWriteManifest) {
                        final boolean writeWidgetData = mWidgetData != null;
                        if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
                        writeAppManifest(mPackage, mManifestFile, mSendApk, writeWidgetData);
                        FullBackup.backupToTar(mPackage.packageName, null, null,
                              mFilesDir.getAbsolutePath(),
                              mManifestFile.getAbsolutePath(),
                              output);
                        mManifestFile.delete();

                        // We only need to write a metadata file if we have widget data to stash
                        if (writeWidgetData) {
                            writeMetadata(mPackage, mMetadataFile, mWidgetData);
                            FullBackup.backupToTar(mPackage.packageName, null, null,
                                    mFilesDir.getAbsolutePath(),
                                    mMetadataFile.getAbsolutePath(),
                                    output);
                            mMetadataFile.delete();
                        }
                  }

                  if (mSendApk) {
                        writeApkToBackup(mPackage, output);
                  }

                  if (DEBUG) Slog.d(TAG, "Calling doFullBackup() on " + mPackage.packageName);
                  prepareOperationTimeout(mToken, TIMEOUT_FULL_BACKUP_INTERVAL, null);
                  mAgent.doFullBackup(mPipe, mToken, mBackupManagerBinder);
                } catch (IOException e) {
                  Slog.e(TAG, "Error running full backup for " + mPackage.packageName);
                } catch (RemoteException e) {
                  Slog.e(TAG, "Remote agent vanished during full backup of "
                            + mPackage.packageName);
                } finally {
                  try {
                        mPipe.close();
                  } catch (IOException e) {}
                }
            }

            private void writeApkToBackup(PackageInfo pkg, FullBackupDataOutput output) {
            // Forward-locked apps, system-bundled .apks, etc are filtered out before we get here
            // TODO: handle backing up split APKs
            final String appSourceDir = pkg.applicationInfo.getBaseCodePath();
            final String apkDir = new File(appSourceDir).getParent();
            FullBackup.backupToTar(pkg.packageName, FullBackup.APK_TREE_TOKEN, null,
                  apkDir, appSourceDir, output);

            // TODO: migrate this to SharedStorageBackup, since AID_SYSTEM
            // doesn't have access to external storage.

            // Save associated .obb content if it exists and we did save the apk
            // check for .obb and save those too
            final UserEnvironment userEnv = new UserEnvironment(UserHandle.USER_OWNER);
            final File obbDir = userEnv.buildExternalStorageAppObbDirs(pkg.packageName);
            if (obbDir != null) {
                if (MORE_DEBUG) Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath());
                File[] obbFiles = obbDir.listFiles();
                if (obbFiles != null) {
                  final String obbDirName = obbDir.getAbsolutePath();
                  for (File obb : obbFiles) {
                        FullBackup.backupToTar(pkg.packageName, FullBackup.OBB_TREE_TOKEN, null,
                              obbDirName, obb.getAbsolutePath(), output);
                  }
                }
            }
      }

创建线程后,普通app需要写入manifest,代码如下 ,写入到文件后,就调用FullBackup.backupToTar()将文件压缩到tar中,
下面的wigetData也差不多,就不再分析。对于mSendApk应该是和系统有关的apk,上面的注释已经很详细了。


private void writeAppManifest(PackageInfo pkg, File manifestFile,
                boolean withApk, boolean withWidgets) throws IOException {
            // Manifest format. All data are strings ending in LF:
            //   BACKUP_MANIFEST_VERSION, currently 1
            //
            // Version 1:
            //   package name
            //   package's versionCode
            //   platform versionCode
            //   getInstallerPackageName() for this package (maybe empty)
            //   boolean: "1" if archive includes .apk; any other string means not
            //   number of signatures == N
            // N*:    signature byte array in ascii format per Signature.toCharsString()
            StringBuilder builder = new StringBuilder(4096);
            StringBuilderPrinter printer = new StringBuilderPrinter(builder);

            printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
            printer.println(pkg.packageName);
            printer.println(Integer.toString(pkg.versionCode));
            printer.println(Integer.toString(Build.VERSION.SDK_INT));

            String installerName = mPackageManager.getInstallerPackageName(pkg.packageName);
            printer.println((installerName != null) ? installerName : "");

            printer.println(withApk ? "1" : "0");
            if (pkg.signatures == null) {
                printer.println("0");
            } else {
                printer.println(Integer.toString(pkg.signatures.length));
                for (Signature sig : pkg.signatures) {
                  printer.println(sig.toCharsString());
                }
            }

            FileOutputStream outstream = new FileOutputStream(manifestFile);
            outstream.write(builder.toString().getBytes());
            outstream.close();

            // We want the manifest block in the archive stream to be idempotent:
            // each time we generate a backup stream for the app, we want the manifest
            // block to be identical.The underlying tar mechanism sees it as a file,
            // though, and will propagate its mtime, causing the tar header to vary.
            // Avoid this problem by pinning the mtime to zero.
            manifestFile.setLastModified(0);
      }


上面的备份完成后就是主体的备份了,同样涉及到了Binder(可见Binder在android中的重要性),这里同样不再讨论。
等到这里结束后,管道里就是一个apk的备份数据了。


private void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out)
            throws IOException {
      FileInputStream raw = new FileInputStream(inPipe.getFileDescriptor());
      DataInputStream in = new DataInputStream(raw);

      byte[] buffer = new byte;
      int chunkTotal;
      while ((chunkTotal = in.readInt()) > 0) {
            while (chunkTotal > 0) {
                int toRead = (chunkTotal > buffer.length) ? buffer.length : chunkTotal;
                int nRead = in.read(buffer, 0, toRead);
                out.write(buffer, 0, nRead);
                chunkTotal -= nRead;
            }
      }
    }

从管道获取数据就不分析了,代码还是比较简单的。

#0xed
本来只是想分析一下backup的结构,结果不知不觉就分析了那么多的代码,收货还是挺大的。当然,灰常欢迎大大们提问。


windwing1883 发表于 2015-12-17 17:49

看着看着看困了.....论格式的重要性啊

nikoxves 发表于 2016-3-2 21:28

gaojun 发表于 2015-12-30 20:28
这是C ++吗?

代码是java的,不过深入到binder那应该是有c++的东西

半醉半醒花前坐 发表于 2015-12-17 12:32

学习一下,感谢分享

mingking777 发表于 2015-12-17 12:43

感谢分享。。。。。。。。。。。。

夜之零落兮 发表于 2015-12-17 13:33

前排膜拜~~

sarenhao 发表于 2015-12-17 17:19


学习一下,感谢分享

龙飞雪 发表于 2015-12-17 21:38

看不懂,还的学习了。

枫MapleLCG 发表于 2015-12-17 22:05

我竟然啥都看不懂。。

Sollage 发表于 2015-12-17 23:19

学习一下,感谢分享

mottoxt720 发表于 2015-12-18 00:51

学习一下,感谢分享
页: [1] 2 3 4 5
查看完整版本: Android Backup 文件解析