2024-02-22

Parse the stripped libart.so symbols on Android 15

Author: Katana Category - Mobile - Security - Software

Since the latest Android 15 platform (Developer Preview), Google stripped all symbols from libart.so and caused millions of reinforced apps crash. Now all these apps acquice an update to adapt the latest Android 15 system. After searching for documents, we found a new way to resolve the symbols from the stripped art.

Android 15

The Android open source project (AOSP) was published a relevant component about the ELF parser, we can find it at this link: https://android.googlesource.com/platform/system/extras/+/refs/heads/main/simpleperf/read_elf.cpp

  ElfStatus ParseSymbols(const ParseSymbolCallback& callback) override {
    auto machine = GetELFHeader(elf_).e_machine;
    bool is_arm = (machine == llvm::ELF::EM_ARM || machine == llvm::ELF::EM_AARCH64);
    AddSymbolForPltSection(elf_obj_, callback);
    // Some applications deliberately ship elf files with broken section tables.
    // So check the existence of .symtab section and .dynsym section before reading symbols.
    bool has_symtab;
    bool has_dynsym;
    CheckSymbolSections(elf_obj_, &has_symtab, &has_dynsym);
    if (has_symtab && elf_obj_->symbol_begin() != elf_obj_->symbol_end()) {
      ReadSymbolTable(elf_obj_->symbol_begin(), elf_obj_->symbol_end(), callback, is_arm,
                      elf_obj_->section_end());
      return ElfStatus::NO_ERROR;
    } else if (has_dynsym && elf_obj_->dynamic_symbol_begin()->getRawDataRefImpl() !=
                                 llvm::object::DataRefImpl()) {
      ReadSymbolTable(elf_obj_->dynamic_symbol_begin(), elf_obj_->dynamic_symbol_end(), callback,
                      is_arm, elf_obj_->section_end());
    }
    std::string debugdata;
    ElfStatus result = ReadSection(".gnu_debugdata", &debugdata);
    if (result == ElfStatus::SECTION_NOT_FOUND) {
      return ElfStatus::NO_SYMBOL_TABLE;
    } else if (result == ElfStatus::NO_ERROR) {
      std::string decompressed_data;
      if (XzDecompress(debugdata, &decompressed_data)) {
        auto debugdata_elf =
            ElfFile::Open(decompressed_data.data(), decompressed_data.size(), &result);
        if (debugdata_elf) {
          return debugdata_elf->ParseSymbols(callback);
        }
      }
    }
    return result;
  }

The most important part is the debug data segment, which can help gdb to scan the symbols from a specific ELF file. We can use this command to extract the data manually:

objcopy --dump-section .gnu_debugdata=libart.dbg.xz libart.so

Now we can scan the symbols from libart again, and work above Android 15, this is a guide for extract symbols from the stripped libart library.

Android Image

Google may remove this debug data segment in the future Android release, so you must be able to ensure your project will fully compatible with the non-symbol libart runtime. This is only a temporary workaround, may lose effect in the future Android release versions.

If you still don't know how to use the debug data segment, here is an accomplished project: https://github.com/hexhacking/xDL , this project should help you out.

Though we found a temporary workaround, Google will still remove this debug data in the future. If your app is based on the ART method interpret (hook), we recommend you to transfer your project to our next generation ART hook framework, with the embedded and public CTS tests passed API, this hook framework will work on all Android devices (since Android 8), and won't affect by the stripped symbol. This framework is based on the JVMTI (ART TI) feature, and this feature won't be removed in the further Android versions.

Google always recommend developers not to use the hidden api, but millions of Android apps are still working above the hidden api ecosystem, besides, some thrid party firmwares will change the behaviour of the default implementation, using the hidden api may crash the application on such firmwares.

Author: Katana

Blog: Katana-Blog