Shutd0wn 发表于 2021-5-18 16:14

菜鸡诈尸水贴之fart适配Android9

本帖最后由 Shutd0wn 于 2021-5-18 16:11 编辑

# Android9拖壳机说明

适配了google pixel2的android 9版本,先看一下拖出来的样子




看一下修复后的结果



## 系统编译

### 下载源码

```bash
# ubuntu20.04编译android9
# 新系统下载aosp9
echo '更新系统,注意输入密码提示框'
sudo apt-get update && sudo apt-get upgrade -y
echo '安装python2'
sudo apt-get install python
echo '安装编译依赖'
sudo apt-get install bison g++-multilib git gperf libxml2-utils make zlib1g-dev:i386 zip liblz4-tool libncurses5 libssl-dev bc flex curl -y
echo '配置git'
git config -global user.email "test@test.com"
git config -global user.name "test"
echo '安装jdk8'
sudo apt-get install openjdk-8-jdk
echo '安装repo'
mkdir ~/bin
echo "PATH=~/bin:\$PATH" >> ~/.bashrc
source ~/.bashrc
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
echo '修改repo下载地址'
echo "export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo'" >> ~/.bashrc
source ~/.bashrc
echo '创建aosp文件夹,下载镜像'
mkdir ~/AND_SOURCE
cd ~/AND_SOURCE
# PQ3A.190801.002
repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest -b android-9.0.0_r46
repo sync -j4
# 强制更新
# repo sync --force-sync
```

### 下载驱动

```bash
# 下载https://developers.google.com/android/drivers驱动
# 找到与aosp版本对应的驱动版本
# 一般包含google与qcom两个文件,拷贝到aosp代码根目录下
# 解压出来sh文件后,执行解压驱动
$ ./extract-google_devices-*.sh
$ ./extract-qcom-*.sh
```

### 刷官方底包

```bash
# 版本信息
walleye-pq3a.190801.002
```

### 编译

```bash
cd ~/Downloads/AND_SOURCE
source build/envsetup.sh
lunch aosp_walleye-userdebug
export SOONG_GEN_CMAKEFILES=1
export SOONG_GEN_CMAKEFILES_DEBUG=1
make -j10
adb reboot bootloader
sleep 10
fastboot flashall
```

### `java`导入`android studio`

在源码目录下继续执行如下命令:

```bash
./development/tools/idegen/idegen.sh
```

会在根目录下生成android.iml 和 android.ipr 这两个文件,打开 android.iml 文件,搜下`excludeFolder`,在后面加入如下代码:

```bash
<excludeFolder url="file://$MODULE_DIR$/art" />
<excludeFolder url="file://$MODULE_DIR$/bionic" />
<excludeFolder url="file://$MODULE_DIR$/bootable" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/cts" />
<excludeFolder url="file://$MODULE_DIR$/dalvik" />
<excludeFolder url="file://$MODULE_DIR$/developers" />
<excludeFolder url="file://$MODULE_DIR$/development" />
<excludeFolder url="file://$MODULE_DIR$/device" />
<excludeFolder url="file://$MODULE_DIR$/docs" />
<excludeFolder url="file://$MODULE_DIR$/external" />
<excludeFolder url="file://$MODULE_DIR$/hardware" />
<excludeFolder url="file://$MODULE_DIR$/kernel" />
<excludeFolder url="file://$MODULE_DIR$/libcore" />
<excludeFolder url="file://$MODULE_DIR$/libnativehelper" />
<excludeFolder url="file://$MODULE_DIR$/out" />
<excludeFolder url="file://$MODULE_DIR$/pdk" />
<excludeFolder url="file://$MODULE_DIR$/platform_testing" />
<excludeFolder url="file://$MODULE_DIR$/prebuilts" />
<excludeFolder url="file://$MODULE_DIR$/sdk" />
<excludeFolder url="file://$MODULE_DIR$/system" />
<excludeFolder url="file://$MODULE_DIR$/test" />
<excludeFolder url="file://$MODULE_DIR$/toolchain" />
<excludeFolder url="file://$MODULE_DIR$/tools" />
<excludeFolder url="file://$MODULE_DIR$/.repo" />
```

打开 Android Studio,选择 Open an existing Android Studio project,找到源码目录,点击 Android.ipr,Open,等很久,导入完毕。

### `native`导入`clion`

路径:out/development/ide/clion/建立一个`CMakeLists.txt`文件

```bash
cmake_minimum_required(VERSION 3.6)
project(AOSP-Native)
#// 添加子模块,导入了部分工程。工程很多,我是用到了再导入
add_subdirectory(frameworks/native)
add_subdirectory(art/dalvikvm/dalvikvm-arm64-android)
add_subdirectory(art/libdexfile/libdexfile-arm64-android)
add_subdirectory(art/runtime/libart-arm64-android)
add_subdirectory(bionic/libc/libc_bionic-arm64-android)
add_subdirectory(bionic/libc/libc_bionic_ndk-arm64-android)
add_subdirectory(bionic/libc/system_properties/libsystemproperties-arm64-android)
add_subdirectory(external/compiler-rt/lib/sanitizer_common/libsan-arm64-android)
add_subdirectory(frameworks/av/media/libaaudio/src/libaaudio-arm64-android)
add_subdirectory(frameworks/av/soundtrigger/libsoundtrigger-arm64-android)
add_subdirectory(frameworks/base/core/jni/libandroid_runtime-arm64-android)
add_subdirectory(frameworks/native/cmds/installd/installd-arm64-android)
add_subdirectory(frameworks/native/cmds/servicemanager/servicemanager-arm64-android)
add_subdirectory(frameworks/native/libs/binder/libbinder-arm64-android)
add_subdirectory(libcore/libjavacore-arm64-android)
add_subdirectory(libcore/libopenjdk-arm64-android)
add_subdirectory(libnativehelper/libnativehelper-arm64-android)
add_subdirectory(libnativehelper/libnativehelper_compat_libc++-arm64-android)
#add_subdirectory(kernel/msm-4.4/unifdef-x86_64-linux_glibc)
#// 内核的CMakeLists是自己写的,只导入了头文件,跳转还有问题
#add_subdirectory(kernel/msm-4.4/kernel_custom)
add_subdirectory(system/core/base/libbase-arm64-android)
add_subdirectory(system/core/init/libinit-arm64-android)
add_subdirectory(system/core/libziparchive/libziparchive-arm64-android)
add_subdirectory(system/core/liblog/liblog-arm64-android)
add_subdirectory(system/core/libcutils/libcutils-arm64-android)
add_subdirectory(system/core/libutils/libutils-arm64-android)
add_subdirectory(system/core/libprocessgroup/libprocessgroup-arm64-android)
add_subdirectory(system/core/logcat/logcatd-arm64-android)
add_subdirectory(system/core/logcat/liblogcat-arm64-android)
add_subdirectory(system/core/logd/logd-arm64-android)
add_subdirectory(system/core/logd/liblogd-arm64-android)
add_subdirectory(system/core/lmkd/liblmkd_utils-arm64-android)
add_subdirectory(system/core/lmkd/lmkd-arm64-android)
```

打开CLion,选择「New CMake Project from Sources」,指定包含 CMakeLists.txt 的目录`out/development/ide/clion`,选择「Open Existing Project」

导入之后目录结构是扁平的,需要修改工程根目录

- Tools -> CMake -> Change Project Root,指定为你的源码的路径

实际导入的过程中,`CMakeLists.txt`中可能报错,修改一下就好了。

## 修改源码编译

主要的流程如下



1. `OpenCommon`下面开始保存Dex文件与classnamelist
2. `ActivityThread`的`startOrNot`发起`fakeInvoke`的调用
3. `fakeInvoke`获取`classnamelist`,然后过滤一下不需要主动加载的类,然后加载类,获取所有的方法,开始主动调用。

其实是把`FART`的思想移植到`android 9`中,参考了`AUPK`的部分代码。具体见代码。

先dump dex文件与classname文件:`dumpDexFileAndClassNames`

```c
std::unique_ptr<DexFile> ArtDexFileLoader::OpenCommon(const uint8_t* base,
                                                      size_t size,
                                                      const uint8_t* data_base,
                                                      size_t data_size,
                                                      const std::string& location,
                                                      uint32_t location_checksum,
                                                      const OatDexFile* oat_dex_file,
                                                      bool verify,
                                                      bool verify_checksum,
                                                      std::string* error_msg,
                                                      std::unique_ptr<DexFileContainer> container,
                                                      VerifyResult* verify_result) {
std::unique_ptr<DexFile> dex_file = DexFileLoader::OpenCommon(base,
                                                                size,
                                                                data_base,
                                                                data_size,
                                                                location,
                                                                location_checksum,
                                                                oat_dex_file,
                                                                verify,
                                                                verify_checksum,
                                                                error_msg,
                                                                std::move(container),
                                                                verify_result);

// Check if this dex file is located in the framework directory.
// If it is, set a flag on the dex file. This is used by hidden API
// policy decision logic.
// Location can contain multidex suffix, so fetch its canonical version. Note
// that this will call `realpath`.
Laster::dumpDexFileAndClassNames(dex_file);
std::string path = DexFileLoader::GetDexCanonicalLocation(location.c_str());
if (dex_file != nullptr && LocationIsOnSystemFramework(path.c_str())) {
    dex_file->SetIsPlatformDexFile();
}

return dex_file;
}

    void Laster::dumpDexFileAndClassNames(std::unique_ptr<DexFile> &dex_file) {
      std::string configPkgName = Laster::readConfigFile();
      std::string procName = Laster::getProcessName();
      if (!configPkgName.empty()) {
            LOG(WARNING) << "Laster native configPkgName size: " << configPkgName.size() << ", configPkgName: "
                         << configPkgName << ", procName size: " << procName.size() << ", procName: " << procName;
            size_t compareRet = strncmp(procName.c_str(), configPkgName.c_str(), configPkgName.size());
            if (compareRet == 0) {
                Laster::dumpDexFile(dex_file, configPkgName);
                Laster::dumpClassName(dex_file, configPkgName);
            }
      }
    }
```

再启动加载类遍历方法调用:`startOrNot`

```java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
      ......
      
      String packageName = r.packageInfo.getPackageName();
      String activityName = r.activityInfo.name;
      Log.i("Laster", "ActivityThread:performLaunchActivity, packageName: " + packageName + ", activityName: " + activityName);
      Laster.startOrNot(packageName, r.activity.getClassLoader());
      return activity;
    }
   
public static synchronized void startOrNot(String packageName, final ClassLoader classLoader) {
      String readFileDirStr = "/data/data/" + packageName + "/dexfile/";
      Log.d("Laster", "startOrNot read file dir: " + readFileDirStr);
      if (isDirExist(readFileDirStr)) {
            cleanDirFiles(readFileDirStr);
            new Thread(new Runnable() {
                @Override
                public void run() {
                  try {
                        Log.d("Laster", "startOrNotsleep 5s");
                        Thread.sleep(5 * 1000);
                        Log.d("Laster", "startOrNottry to start...");
                        initFilterList();

                        File[] classNameFileList = getClassnameFileList(readFileDirStr);

                        for (File file : classNameFileList) {
                            Set classnameSet = readFileList(file.getAbsolutePath());
                            classnameSet = filterClassNameList(classnameSet);
                            Log.d("Laster", "startOrNottry to get classnameSet");
                            if (classnameSet != null) {
                              Log.d("Laster", "startOrNottry to get start to dump method insn");
                              startToDumpMethodInsn(classnameSet, classLoader, packageName);
                            }
                            Thread.sleep(1500);
                        }


                  } catch (Exception e) {
                        Log.d("Laster", "startOrNot: error, " + e.getMessage());
                  }

                }
            }).start();
      }
    }

......
      private static synchronized void startToDumpMethodInsn(Set<String> classnameSet, ClassLoader loader, String packageName) {
      try {
            for (String classname : classnameSet) {
                Class target = null;
                try {
                  saveClassnameList(classname, packageName);
                  target = loader.loadClass(classname);
                } catch (Exception e) {
                }
                if (target != null) {
                  List<Object> methodList = getAllClassMethods(target);
                  for (Object method : methodList) {
                        try {
                            if (method instanceof Method) {
                              Log.d("Laster", "Start to dump dex file class method name: " + ((Method) method).getName());
                            } else {
                              Log.d("Laster", "Start to dump dex file class cons name: " + ((Constructor) method).getName());
                            }
                            # 这里调用native层的方法开始模拟调用
                            fakeInvoke(method);
                        } catch (Exception e) {
                            Log.d("Laster", "Start to dump dex file class method error!");
                        }
                  }
                }
            }
      } catch (Exception e) {
            Log.d("Laster", "startToDumpMethodInst: error, " + e.getMessage());
      }

    }
```

`native`层抄了`FART`与`AUPK`的部分代码,只是对Android 9做了适配。



## 刷机测试

安装目标应用,

在`/data/local/tmp/`下配置两个文件

```bash
# 配置过滤class,有些class不需要加载
filter.config
# 配置需要拖壳的包名
laster.config
```





点击app,启动,等待拖壳完成,看日志如果方法加载完成。就算结束了。

复制出dex文件

```bash
adb shell
su
cd /data/data/packagename/
cp -r dexfile/ ~/sdcard/
exit
exit
adb pull /sdcard/dexfile ./
```

拷贝dex文件与method文件到到一个文件夹,使用python脚本修复:



```bash
python3 fart_fix_dex_00.py -d dex_*.dex -b dex_*_method.txt
```



### 编译Release版本

`Android`标准签名`key`文件位于源码`/build/target/product/security`目录下,四组默认签名供`Android.mk`在编译`APK`使用。主要有4个key:

- `testkey`:普通签名`APK`,默认情况下使用。
- `platform`:该`APK`完成一些系统的核心功能。经过对系统中存在的文件夹的访问测试,这种方式编译出来的`APK`所在进程的`UID`为`system`。
- `shared`:该`APK`需要和`home/contacts`进程共享数据。
- `media`:该`APK`是`media/download`系统中的一环。

应用程序的`Android.mk`中有一个`LOCAL_CERTIFICATE`字段,由它指定哪个`key`签名,未指定的默认用`testkey`.
`build/target/product/security`目录下查看:



`*.pk8`代表私钥,`*.x509.pem`公钥,它们都是成对出现;
` testkey`是作为`android`编译的时候默认的签名`key`,如果系统中的`apk`的`Android.mk`中没有设置`LOCAL_CERTIFICATE`的值,就默认使用`testkey`。



在aosp代码根目录下创建脚本,生成key

```bash
# create_cert.sh
subject='/C=CN/ST=Shanghai/L=Shanghai/O=ingeek/OU=mo/CN=www.ingeek.com/emailAddress=iot-ingeek@ingeek.com'
for x in releasekey platform shared media;
do
./development/tools/make_key ./build/target/product/security/$x "$subject";
done
```

另存`/build/target/product/security`下所有证书信息,保存原始key

```bash
$ cd ./build/target/product/security/
$ mkdir bak
$ mv *.pk8 bak/
$ mv *.pem bak/
$ cd bak/
$ cp verity ../
```

运行脚本创建key,提示输入密码时,直接回车,使用默认密码。

修改默认签名key

1. 修改Android配置`./build/core/config.mk`中定义变量:

   ```bash
   # The default key if not set as LOCAL_CERTIFICATE
   ifdef PRODUCT_DEFAULT_DEV_CERTIFICATE
   DEFAULT_SYSTEM_DEV_CERTIFICATE := $(PRODUCT_DEFAULT_DEV_CERTIFICATE)
   else
   + DEFAULT_SYSTEM_DEV_CERTIFICATE := build/target/product/security/releasekey
   - DEFAULT_SYSTEM_DEV_CERTIFICATE := build/target/product/security/testkey
   endif
   ```

2. 修改`./build/core/Makefile`中定义变量:

   ```bash
   ifeq ($(DEFAULT_SYSTEM_DEV_CERTIFICATE),build/target/product/security/releasekey)
   BUILD_KEYS := release-keys
   ```

3. 修改`./system/sepolicy/private/keys.conf`和`./system/sepolicy/prebuilts/api/{apilevel}/private/keys.conf`

   ```bash
   # Example of ALL TARGET_BUILD_VARIANTS
   [@RELEASE]
   ENG       : $DEFAULT_SYSTEM_DEV_CERTIFICATE/releasekey.x509.pem
   USER      : $DEFAULT_SYSTEM_DEV_CERTIFICATE/releasekey.x509.pem
   USERDEBUG : $DEFAULT_SYSTEM_DEV_CERTIFICATE/releasekey.x509.pem
   ```

4. 添加user版本编译选项(如果`lunch`中没有)

   ```bash
   cd ./device
   grep -ril 'aosp_walleye-userdebug' # 按照你的手机来
   ./google/muskie/vendorsetup.sh
   gedit ./google/muskie/vendorsetup.sh
   # 添加一行
   # add_lunch_combo aosp_walleye-user
   ```

   

编译系统

```bash
cd ~/Downloads/AND_SOURCE
source build/envsetup.sh
lunch aosp_walleye-user
make clean
export SOONG_GEN_CMAKEFILES=1
export SOONG_GEN_CMAKEFILES_DEBUG=1
make -j10
adb reboot bootloader
sleep 10
fastboot flashall
# 如果开不了机,进入recovery双清一下
```

### 使用

1. root你的手机,安装应用

2. 配置`packagename`到`/data/local/tmp/laster.config`

   ```bash
   echo 'packagename' > /data/local/tmp/laster.config
   ```

3. 点击apk启动,等一会,如果你只是想dump出dex文件,那么现在你就可以去

   ```bash
   /data/data/packagename/dexfile/
   ```

   下面拿到dex文件

4. 如果你还想尝试修复抽取的代码,那么你现在可以先删掉

   ```bash
   /data/local/tmp/laster.config
   ```

   这个文件,然后不停的点开你的apk,然后通过日志,或者

   ```bash
   /data/data/packagename/dexfile/loaded.txt
   ```

   查看主动调用到哪个class失败了,然后在

   ```bash
   /data/local/tmp/filter.config
   ```

   中配置过滤来尝试尽可能的多加载类,如果一切顺利apk没有闪退会在

   ```bash
   /data/data/packagename/dexfile/
   ```

   产生对应的`method.txt`文件,拖出来尝试使用python脚本修复。

### windows下python脚本修复

#### 安装`kaitaistruct`

```bash
pip install --upgrade git+https://github.com/kaitai-io/kaitai_struct_python_runtime.git
```

#### 使用

```bash
python3 fart_fix_dex_00.py -d dexfile -b binfile
```
测试镜像
```bash
链接:https://pan.baidu.com/s/1sMLDq6Mr5K8GmLe8IunNVQ
提取码:PV74
```

guangzisam 发表于 2021-5-18 16:51

用FART脱壳。没官网地址,没说明官方适配Android版本

bant 发表于 2021-5-18 17:37

贾东平 发表于 2021-5-18 20:05

这个真不错啊。

茶茶大人L 发表于 2021-5-18 21:26

不明觉厉,前排{:1_893:}

xixicoco 发表于 2021-5-18 23:54

牛逼的,支持你

fzj 发表于 2021-5-19 05:30

楼主厉害,感谢分享

紫色叶景 发表于 2021-5-19 06:33

感谢楼主分享 这个思路还是很有参考性的 学习一下

legs 发表于 2021-5-19 09:27

感谢楼主分享,想法很好,学习一下

shiqiangge 发表于 2021-5-19 09:47

感谢分享心得,学习了{:1_893:}
页: [1] 2 3
查看完整版本: 菜鸡诈尸水贴之fart适配Android9