Skip to content

feature: Add ipa support#204

Open
lbr77 wants to merge 1 commit into
worawit:mainfrom
lbr77:main
Open

feature: Add ipa support#204
lbr77 wants to merge 1 commit into
worawit:mainfrom
lbr77:main

Conversation

@lbr77
Copy link
Copy Markdown

@lbr77 lbr77 commented Apr 28, 2026

No description provided.

Copilot AI review requested due to automatic review settings April 28, 2026 11:46
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds experimental iOS (.ipa / app bundle) support by introducing Mach-O parsing and updating tooling/scripts to locate and analyze iOS Flutter binaries alongside existing Android ELF support.

Changes:

  • Add Mach-O parsing utilities and tests for snapshot/hash/engine-id extraction.
  • Extend blutter.py to accept .ipa inputs and extract App.framework/App + Flutter.framework/Flutter.
  • Update Frida script/template generation to support iOS module naming and non-compressed-pointer configurations.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/test_macho.py Adds unit tests for Mach-O parsing and IPA framework extraction.
macho.py New Mach-O reader for fat/thin images, snapshot note/section handling, engine id + Dart version extraction.
extract_dart_info.py Adds Mach-O path for snapshot/engine/dart version extraction; expands ELF arch handling.
blutter.py Adds .ipa extraction and broader app bundle discovery logic.
scripts/frida.template.js Introduces placeholders for module names and compressed-pointer mode; improves module discovery.
blutter/src/FridaWriter.cpp Replaces raw template copy with placeholder substitution for iOS/module + pointer mode.
blutter/src/ElfHelper.{h,cpp} Renames APIs and adds Mach-O snapshot discovery in the native mapper.
blutter/src/CodeAnalyzer_arm64.cpp Makes several compressed-pointer assumptions conditional; improves operand scaling detection.
blutter/src/Disassembler_arm64.h Adjusts register constant compilation around heap register.
blutter/src/DartFunction.h Initializes signature fields to safe defaults.
blutter/src/DartApp.cpp Updates to renamed ELF/Mach-O mapping API and adds null-function guard during finalization.
scripts/CMakeLists.txt Adjusts iOS target OS defines for Dart VM builds.
README.md Documents experimental iOS snapshot extraction and IPA/app bundle usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread blutter/src/ElfHelper.cpp
}

const uint32_t beMagic = read_be32(file);
if (beMagic == macho::FAT_MAGIC || beMagic == macho::FAT_MAGIC_64) {
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

select_macho_image reads nfat from file + 4 without first ensuring fileSize >= sizeof(fat_header) (8 bytes). A truncated file that starts with a FAT magic will cause an out-of-bounds read and may crash. Add an explicit fileSize check (or use checked_ptr<fat_header> and validate) before accessing bytes past the first 4.

Suggested change
if (beMagic == macho::FAT_MAGIC || beMagic == macho::FAT_MAGIC_64) {
if (beMagic == macho::FAT_MAGIC || beMagic == macho::FAT_MAGIC_64) {
if (fileSize < sizeof(macho::fat_header)) {
throw std::invalid_argument("Mach-O: truncated fat header");
}

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +17
std::ifstream templateFile(FRIDA_TEMPLATE_DIR "/frida.template.js");
std::stringstream templateBuffer;
templateBuffer << templateFile.rdbuf();
auto code = templateBuffer.str();
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FridaWriter::Create does not verify that the Frida template file opened successfully before reading it. If the file is missing/unreadable, code will be empty and the later placeholder replacement will throw a misleading "Missing ... placeholder" error. Check templateFile.is_open() (and optionally templateOut open state) and surface an explicit file I/O error.

Copilot uses AI. Check for mistakes.
Comment thread macho.py
Comment on lines +96 to +110
magic = _u32be(data, 0)
if magic not in (FAT_MAGIC, FAT_MAGIC_64):
return []

nfat_arch = _u32be(data, 4)
arches = []
offset = 8
for _ in range(nfat_arch):
if magic == FAT_MAGIC:
cputype, _, arch_offset, arch_size, _ = struct.unpack_from(">IIIII", data, offset)
offset += 20
else:
cputype, _, arch_offset, arch_size, _, _ = struct.unpack_from(">IIQQII", data, offset)
offset += 32
arches.append(FatArch(cputype, arch_offset, arch_size))
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_parse_fat_arches assumes the file is long enough for the fat header and each arch entry; on truncated/invalid inputs struct.unpack_from will raise a raw struct.error. Add explicit length/bounds checks (including verifying offset + entry_size <= len(data)) and fail with a clear assertion/exception message.

Suggested change
magic = _u32be(data, 0)
if magic not in (FAT_MAGIC, FAT_MAGIC_64):
return []
nfat_arch = _u32be(data, 4)
arches = []
offset = 8
for _ in range(nfat_arch):
if magic == FAT_MAGIC:
cputype, _, arch_offset, arch_size, _ = struct.unpack_from(">IIIII", data, offset)
offset += 20
else:
cputype, _, arch_offset, arch_size, _, _ = struct.unpack_from(">IIQQII", data, offset)
offset += 32
arches.append(FatArch(cputype, arch_offset, arch_size))
if len(data) < 4:
raise ValueError("Mach-O data too short to read fat magic")
magic = _u32be(data, 0)
if magic not in (FAT_MAGIC, FAT_MAGIC_64):
return []
if len(data) < 8:
raise ValueError("Mach-O fat header too short")
nfat_arch = _u32be(data, 4)
arches = []
offset = 8
entry_size = 20 if magic == FAT_MAGIC else 32
for i in range(nfat_arch):
if offset + entry_size > len(data):
raise ValueError(
f"Mach-O fat arch table truncated: entry {i} at offset {offset} "
f"requires {entry_size} bytes, file has {len(data)} bytes"
)
if magic == FAT_MAGIC:
cputype, _, arch_offset, arch_size, _ = struct.unpack_from(">IIIII", data, offset)
else:
cputype, _, arch_offset, arch_size, _, _ = struct.unpack_from(">IIQQII", data, offset)
if arch_offset + arch_size > len(data):
raise ValueError(
f"Mach-O fat arch slice out of bounds: entry {i} has offset {arch_offset} "
f"and size {arch_size}, file has {len(data)} bytes"
)
arches.append(FatArch(cputype, arch_offset, arch_size))
offset += entry_size

Copilot uses AI. Check for mistakes.
Comment thread extract_dart_info.py
if elf.header.e_machine == 'EM_AARCH64': # 183
arch = 'arm64'
elif elf.header.e_machine == 'EM_IA_64': # 50
elif elf.header.e_machine in ('EM_X86_64', 'EM_IA_64'): # 62, 50
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EM_IA_64 represents the Itanium architecture, not x86_64. Treating it as 'x64' can mislabel binaries and lead to incorrect downstream assumptions. Consider handling only EM_X86_64 as 'x64' (and error out or add a separate label for EM_IA_64).

Suggested change
elif elf.header.e_machine in ('EM_X86_64', 'EM_IA_64'): # 62, 50
elif elf.header.e_machine == 'EM_X86_64': # 62

Copilot uses AI. Check for mistakes.
Comment thread blutter/src/ElfHelper.cpp
Comment on lines 188 to 195
HANDLE hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hMapFile == INVALID_HANDLE_VALUE)
return NULL;
if (hMapFile == INVALID_HANDLE_VALUE) {
CloseHandle(hFile);
throw std::invalid_argument(std::format("Cannot map input file: {}", path));
}

// need RW because dart initialization need writing data in BSS
void* mem = MapViewOfFile(hMapFile, FILE_MAP_COPY, 0, 0, 0);
CloseHandle(hMapFile);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Windows, CreateFileMapping returns NULL on failure (not INVALID_HANDLE_VALUE), so the current check won't catch mapping creation errors at the right point. Also, MapViewOfFile(..., FILE_MAP_COPY, ...) typically requires creating the mapping with a compatible protection such as PAGE_WRITECOPY/PAGE_EXECUTE_WRITECOPY; using PAGE_READONLY may cause the view mapping to fail. Update the failure check and ensure the protection/access flags are consistent.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants