这里主要是看书的笔记。从基础开始。不断记录。直到啃完这本书(android软件安全权威指南:丰生强)
常见的android文件格式(2)
AndroidManifest.xml
存放了apk的大量配置信息,包括软件名称、图标、主题、包名、组件配置。
所有配置都属于manifest标签,与程序配置相关的属于android标签。
android:allowBackup=true
允许系统在进行备份操作时,备份程序的应用数据,比如在终端执行adb backup命令。对安全敏感的情况要设置为false
android:supportsRtl=true
让apk支持rtl(right-to-left)视图。targetSdkVersion必须在17及以上。
AXML文件格式
AndroidManifest.xml在编译成apk前是明文的。编译成apk的时候,会把这个文件编译成二进制格式的文件。解压了再打开这个文件,就是乱码的。这个文件就是AXML格式。
AXML文件修改
有些加固厂商利用android系统解析AXML的漏洞,在编译APK时构造畸形的AXML。导致apktool之类的工具不能正常工作,就需要修改AXML。有些现成的工具修复这种情况。AmBinaryEditor、AndroidManifestFix
resources.arsc
包含不同语言环境中res目录下所有资源的类型、名称与id所对应的信息。和AXML差不多,这是一个ARSC文件格式
ARSC文件的修改
和AXML一样,也是有些特殊的ARSC可以正常被系统加载,又能组织apktool之类的反编译。也有一些修改是为了汉化软件。
存储apk签名有关的信息。
存放了apk的开发者证书与签名信息。通过这个文件识别开发者身份以及判断apk是否被修改。可以使用openssl来解码查看证书内容。
openssl pkcs7 -inform DER -in CERT.RSA -noout -print_certs -text
输出结果如下
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
| Certificate: Data: Version: 1 (0x0) Serial Number: 1 (0x1) Signature Algorithm: sha1WithRSAEncryption Issuer: CN=Android Debug, O=Android, C=US Validity Not Before: Aug 12 14:05:31 2020 GMT Not After : Aug 5 14:05:31 2050 GMT Subject: CN=Android Debug, O=Android, C=US Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:9d:11:0b:8d:94:5f:28:34:1f:84:9a:1b:6a:1f: 4b:1d:c6:19:59:94:6d:1d:33:b3:3d:9f:16:7d:73: ca:40:62:89:d7:39:75:15:67:c8:07:54:63:d0:42: fb:a4:c6:a2:b4:41:b5:19:6f:7c:76:eb:ec:51:3b: d1:fc:0c:f3:eb:db:4d:d8:ec:3c:6a:eb:46:3c:d2: e6:47:fe:8f:f3:8a:07:67:5c:09:9f:de:00:97:60: a7:7b:2b:f0:10:17:81:ec:e0:f7:a7:5a:55:79:89: ed:6b:ba:ea:f8:0d:d6:c2:fe:6c:fc:67:f5:36:97: 8e:24:cd:97:41:7b:df:16:0d:63:b8:d6:ab:2b:fe: 6a:1e:94:bc:41:44:f4:de:26:c3:44:d0:c2:6e:79: 37:a1:3f:a6:a1:57:42:4f:5c:3e:e3:4b:ca:0b:eb: ea:19:dd:6d:fe:c3:34:57:3d:a1:b7:38:44:5b:ca: 09:d9:da:70:3a:54:fb:4b:8d:c8:73:73:f7:38:0f: 6f:5d:5b:45:f9:0d:88:2d:2d:6e:2b:a5:c9:2a:29: 4a:8c:d0:87:74:47:4e:69:18:f8:93:0a:6d:a7:9b: 22:3d:0f:38:fb:8c:02:e9:1b:73:97:80:6d:06:31: 4f:82:75:dc:0a:76:30:aa:bb:cd:40:3d:3b:56:a6: 0d:77 Exponent: 65537 (0x10001) Signature Algorithm: sha1WithRSAEncryption 90:52:91:03:0b:de:9b:69:da:47:7a:5d:c8:3f:31:e1:3e:6d: 55:54:e6:29:25:2a:14:25:00:27:21:d1:0d:70:56:ac:3a:bf: 17:95:c9:bd:a4:83:2a:ee:5e:c9:a6:ef:4c:f0:60:35:86:b3: ee:86:9e:9a:94:7b:74:1f:99:86:65:23:a3:13:50:7f:41:ed: 53:72:7f:83:8b:6d:40:ca:54:c8:25:44:75:b3:49:c0:61:cd: 4f:11:28:dc:cb:65:76:ce:03:fa:c3:d9:1b:f6:67:af:34:9f: 25:ab:2f:94:d3:24:ae:be:50:97:76:af:d1:e5:8a:5d:25:4f: 8a:17:75:de:0f:8d:71:53:38:ed:90:e1:0d:25:20:07:6b:2d: 2d:37:18:a0:82:e1:54:71:63:7f:97:03:2a:2e:54:1e:d0:53: 55:85:83:f0:e8:14:46:9c:7f:50:6a:a8:ad:73:17:94:ac:4e: 8c:8b:56:c0:95:41:47:50:23:36:65:d1:c4:8c:53:85:8f:15: 8f:fe:94:13:b5:71:74:7e:55:30:78:20:42:50:f7:44:10:77: 87:92:2a:2a:2d:c8:f1:e8:24:1e:7f:4a:3c:55:f2:87:89:29: 98:c8:b8:79:37:41:c8:7c:35:c5:8f:1d:5c:15:e3:36:2e:83: 59:49:db:69
|
里面很多项都是用来鉴别apk是否被修改了。
签名的清单文件,是个文本文件。大致的内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Manifest-Version: 1.0 Built-By: Generated-by-ADT Created-By: Android Gradle 3.5.2
Name: AndroidManifest.xml SHA-256-Digest: tQz8omUBWakxQm32m7lgSSAFdbP68612Iq4enf/gmxw=
Name: META-INF/androidx.appcompat_appcompat.version SHA-256-Digest: n9KGQtOsoZHlx/wjg8/W+rsqrIdD8Cnau4mJrFhOMbw=
Name: META-INF/androidx.arch.core_core-runtime.version SHA-256-Digest: wo/MpTY3vIjhJK8XJd8Ty5jGne3v1i+zzb4c22t2BiQ=
Name: META-INF/androidx.asynclayoutinflater_asynclayoutinflater.version SHA-256-Digest: WYVJhIUxBN9cNT4vaBoV/HkkdC+aLkaMKa8kjc5FzgM=
|
例如我们校验一下AndroidManifest.xml的值
openssl sha256 AndroidManifest.xml
输出结果如下
1
| SHA256(AndroidManifest.xml)= b50cfca2650159a931426df69bb96049200575b3faf3ad7622ae1e9dffe09b1c
|
然后我们把这个结果base64编码一下,输出结果如下,
1
| tQz8omUBWakxQm32m7lgSSAFdbP68612Iq4enf/gmxw=
|
发现和清单里面记录的一样。如果我们修改了这个文件,和清单里面的结果就不一样了。这里保证进行apk签名验证时所有文件均未被修改
签名信息文件,也是文本文件。内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| Signature-Version: 1.0 Created-By: 1.0 (Android) SHA-256-Digest-Manifest: CsbfwGGBNUMOUJt4R7LYm+LGmb72vPx3Knonej3SUY0= X-Android-APK-Signed: 2
Name: AndroidManifest.xml SHA-256-Digest: gx84XauTSsCx8N18oEcdgiOHMpjhObi7ywdIEy3OaZU=
Name: META-INF/androidx.appcompat_appcompat.version SHA-256-Digest: ABbgKP0s08CVeuJ5ZMlIZx/AvJtb1QhNA0ffeXfCaHk=
Name: META-INF/androidx.arch.core_core-runtime.version SHA-256-Digest: PjygIQMN5T6nIKT/hi5PFaxVcEB+W20fr4f0g2n7jrg=
|
和上面的文件非常类似,但是多了两个属性
SHA-256-Digest-Manifest:这个对应的就是对MANIFEST.MF这个文件的校验。
X-Android-APK-Signed:使用签名的版本,值为2时表示使用新版APK Signature Scheme v2进行签名。android studio在新版本的sdk构建工具中增加了签名工具apksigner,同时支持旧版的v1签名和android7.0引入的v2签名。
继续看这个文件的AndroidManifest.xml的值,发现和上面清单的值不一样的。因为这里实际是对上面清单每一项的验证。
Name: AndroidManifest.xml\r\nSHA-256-Digest: tQz8omUBWakxQm32m7lgSSAFdbP68612Iq4enf/gmxw=\r\n\r\n
对这个串进行sha256计算。
831f385dab934ac0b1f0dd7ca0471d8223873298e139b8bbcb0748132dce6995
上面的值sha256的结果。然后继续进行base64编码
gx84XauTSsCx8N18oEcdgiOHMpjhObi7ywdIEy3OaZU=
现在得到的值就和文件中的一致了。
ODEX
在android5.0之前主要使用dalvik虚拟机。为了提高dex文件的执行效率,会对dex文件进行一定程度的优化,具体做法就是解析并生成一个odex,然后保存在/data/dalvik-cache目录。以后运行的时候不会读取apk中的dex。而是直接加载优化过的odex。这样节省了运行程序在优化上耗费的时间。
OAT
OAT是优化过的,用于art虚拟机执行的dex文件。类似dalvik的odex
ART虚拟机
ART使用AOT编译技术,在apk第一次安装或者系统升级,重启时,通过dex2oat将apk中的dex文件静态编译成oat文件存放到设备的/data/dalvik-cache或者/data/app/package目录。我测试安卓10是在dalvik-cache的里面。
dex2oat更像是编译器,把dalvik字节码编译成native机器码。这样就提高了程序启动的速度。art虚拟机直接执行的oat文件,而不是dex。但是aot有个确定,就是静态编译操作影响apk的安装效率。但是android7.0中增加了jit编译。提高了安装的效率。
生成OAT文件
除了安装时会自动生成OAT文件。也可以手动的生成,使用dex2oat命令。–dex-file传入dex路径,–oat-file指定oat文件路径。连接到设备,执行下面的命令。
dex2oat --dex-file=./classes.dex --oat-file=./classes.oat
生成出了oat文件。然后把oat文件pull到电脑上。
file classes.oat
看一下文件的格式
1
| classes.oat: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (GNU/Linux), dynamically linked, stripped
|
oat文件格式在设计之初就考虑到最终执行的程序是elf格式的,最终将oat文件格式直接融入android所特有的elf格式。
既然是一个ELF文件,那么可以看一下符号表。可以使用ndk中的readelf查看。我直接用ida打开查看的符号表。图如下
一个oat文件必须包含oatdata、oatexec、oatlastword三个符号。
oatdata:指向的地址是oat所在elf的.rodata段。这里存放的是oat文件头OATHeader、oat的dex文件头,OATDexFile、原始dex文件DexFile、oat的dex类OatClass等信息。
oatexec:指向的地址是oat所在elf的.text段,这里存放的是编译生成的native指令代码。
oatlastword:指向的地址是oat文件结束处在elf中的文件偏移,通过它可以知道oat文件的内容在哪里结束。
OatHeader的结构,书中记录的位置并没有找到对应的struct,但是有个类似的class
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
| class PACKED(4) OatHeader { public: static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' }; static constexpr uint8_t kOatVersion[] = { '1', '3', '8', '\0' };
static constexpr const char* kImageLocationKey = "image-location"; static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline"; static constexpr const char* kDex2OatHostKey = "dex2oat-host"; static constexpr const char* kPicKey = "pic"; static constexpr const char* kDebuggableKey = "debuggable"; static constexpr const char* kNativeDebuggableKey = "native-debuggable"; static constexpr const char* kCompilerFilter = "compiler-filter"; static constexpr const char* kClassPathKey = "classpath"; static constexpr const char* kBootClassPathKey = "bootclasspath"; static constexpr const char* kConcurrentCopying = "concurrent-copying"; static constexpr const char* kCompilationReasonKey = "compilation-reason";
static constexpr const char kTrueValue[] = "true"; static constexpr const char kFalseValue[] = "false";
static OatHeader* Create(InstructionSet instruction_set, const InstructionSetFeatures* instruction_set_features, uint32_t dex_file_count, const SafeMap<std::string, std::string>* variable_data);
bool IsValid() const; std::string GetValidationErrorMessage() const; const char* GetMagic() const; uint32_t GetChecksum() const; ... private: ... uint8_t magic_[4]; uint8_t version_[4]; uint32_t adler32_checksum_;
InstructionSet instruction_set_; uint32_t instruction_set_features_bitmap_; uint32_t dex_file_count_; uint32_t oat_dex_files_offset_; uint32_t executable_offset_; uint32_t interpreter_to_interpreter_bridge_offset_; uint32_t interpreter_to_compiled_code_bridge_offset_; uint32_t jni_dlsym_lookup_offset_; uint32_t quick_generic_jni_trampoline_offset_; uint32_t quick_imt_conflict_trampoline_offset_; uint32_t quick_resolution_trampoline_offset_; uint32_t quick_to_interpreter_bridge_offset_;
int32_t image_patch_delta_;
uint32_t image_file_location_oat_checksum_; uint32_t image_file_location_oat_data_begin_;
uint32_t key_value_store_size_; uint8_t key_value_store_[0];
DISALLOW_COPY_AND_ASSIGN(OatHeader); };
|
从代码可以直接看到,有些值是固定的。
magic:文件的标识,固定是“oat\n”。表示oat的头部
version:oat的文件格式版本
Adler32_checksum:oat的adler32校验和
insetruction_set:oat文件使用的指令集。结构如下
1 2 3 4 5 6 7 8 9 10 11
| enum class InstructionSet { kNone, kArm, kArm64, kThumb2, kX86, kX86_64, kMips, kMips64, kLast = kMips64 };
|
dex_file_count:oat文件中包含的dex文件的个数。通常普通app都是1
executable_offset:oatexec符号所在段的开始位置与oatdata符号所在段的开始位置的偏移。该值通常等于oatdata符号所在段.rodata的大小
jni_dlsym_lookup_offset:执行过程,如果类方法要调用另外一个方法是jni函数,就需要通过这个字段指向一段代码来调用
OatHeader下面是OatDexFile结构。数量和dex_file_count这个字段一致。
然后这里发现OatDexFile的结构和书中的不大一致
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
| class OatDexFile FINAL { ... private: OatDexFile(const OatFile* oat_file, const std::string& dex_file_location, const std::string& canonical_dex_file_location, uint32_t dex_file_checksum, const uint8_t* dex_file_pointer, const uint8_t* lookup_table_data, const IndexBssMapping* method_bss_mapping, const IndexBssMapping* type_bss_mapping, const IndexBssMapping* string_bss_mapping, const uint32_t* oat_class_offsets_pointer, const DexLayoutSections* dex_layout_sections);
static void AssertAotCompiler();
const OatFile* const oat_file_ = nullptr; const std::string dex_file_location_; const std::string canonical_dex_file_location_; const uint32_t dex_file_location_checksum_ = 0u; const uint8_t* const dex_file_pointer_ = nullptr; const uint8_t* const lookup_table_data_ = nullptr; const IndexBssMapping* const method_bss_mapping_ = nullptr; const IndexBssMapping* const type_bss_mapping_ = nullptr; const IndexBssMapping* const string_bss_mapping_ = nullptr; const uint32_t* const oat_class_offsets_pointer_ = 0u; mutable std::unique_ptr<TypeLookupTable> lookup_table_; const DexLayoutSections* const dex_layout_sections_ = nullptr;
friend class OatFile; friend class OatFileBase; DISALLOW_COPY_AND_ASSIGN(OatDexFile); };
|
OatDexFile数组的下面是DexFile结构体。里面存放的是一个个完整的dex。
OAT的格式图如下
OAT文件转换成dex文件
上面看到OAT文件中包含完整的dex文件。所以只要定位到OAT文件中的DexFile结构体,完整导出即可。
android系统提供了oatdump来导出所有的dex到指定目录
oatdump --export-dex-to=/data/local/tmp --oat-file=./classes.oat
命令执行完毕后得到文件classes.dex_export.dex
或者是自己写工具,来解析OAT文件格式,取出dex。也有一种简单的方式,直接搜索文件头”dex\n035”。文件头的0x20字节处保存了大小。有了偏移和大小。直接搜索即可。