The two parts
The runtime is an ordinary Linux executable. When the AppImage is started, the runtime mounts
the image that follows it and runs the file AppRun in the image’s root. The image
is a compressed, read-only file system that contains the application, one desktop file and its
icons.
The AppImage specification requires the bytes 0x41 0x49 0x02 (“AI” and the type
number 2) at offset 8, inside the ELF identification at the start of the file. That marker is
what tells a type 2 AppImage apart from any other Linux program without running it.
| Offset and length | Part | Contents |
|---|---|---|
| 0 0x0000 64 bytes | ELF runtime | ELF header: 7F "ELF", 64-bit; "AI" 0x02 at offset 8; section header table at 1,600, 5 entries |
| 64 0x0040 640 bytes | ELF runtime | Program headers, code and data of the runtime |
| 704 0x02C0 4 bytes | ELF runtime | The bytes "hsqs" inside the runtime’s data: not a file system |
| 708 0x02C4 892 bytes | ELF runtime | More code and data |
| 1,600 0x0640 320 bytes | ELF runtime | Section header table: 5 entries of 64 bytes |
| 1,920 0x0780 1,024 bytes | ELF runtime | Data of the last section, after the table |
| 2,944 0x0B80 96 bytes | SquashFS image | Superblock: "hsqs", version 4.0, compression ID 6 (zstd), block size 131,072 |
| 3,040 0x0BE0 160 bytes | SquashFS image | Tables and compressed file data (the AppDir) |
How the offset is found
The runtime computes the size of its own ELF file and treats everything after it as the file
system. In type2-runtime this takes two numbers from the ELF headers: the end of
the section header table, and the end of the data of the last section. The larger one is the
offset.
-
Section header table:
e_shoff + e_shentsize × e_shnum= 1,600 + 64 × 5 = 1,920 -
Last section:
sh_offset + sh_size= 1,920 + 1,024 = 2,944 -
Offset: the larger value, 2,944. The superblock there starts
with
hsqs.
In this example the second number decides: the table ends at 1,920, but a section's data follows it, so the image starts at 2,944. Reading only the end of the table would miss it.
The same number is what --appimage-offset prints, and the AppImage documentation
uses it to mount the image with mount -o offset=. That option is handled by the
runtime, so getting the number this way runs the AppImage’s runtime.
Why searching for “hsqs” is only a heuristic
A common shortcut is to search the file for hsqs, the SquashFS signature, and use
the first match as the offset. It can work, but the four bytes are not reserved: nothing keeps
them out of the runtime’s code or data, and a search does not know where the ELF file ends.
In the example above, a search stops at byte 704, inside the runtime. The ELF headers point to byte 2,944, where the image really begins. The runtime itself never searches.
What the image contains
The image is an AppDir: AppRun as the entry point, one desktop file in the root
(the AppDir specification allows no more than one), the application’s icon, and .DirIcon, the icon of the AppImage itself. Where each of them lives and how tools
find the icon is explained in How AppImage icons are stored and found.
The SquashFS superblock
The image begins with a 96-byte superblock. It holds the signature hsqs, the
format version (4.0), the block size, the positions of the inode, directory and
fragment tables, and one compression ID that applies to the whole image. A reader needs a
decompressor for exactly that method, and not every AppImage reader has the same ones: AppImage compression: gzip, zstd and xz lists which reads what.
Type 1 and type 2
Type 1 AppImages, the older format, are ISO 9660 images that are also ELF executables, marked
with 0x41 0x49 0x01 at offset 8. Type 2 replaced the ISO image with a file system
appended to the ELF runtime. The specification only asks for “a filesystem that the ELF part
can mount”; type2-runtime mounts SquashFS. Some other runtimes also support
DwarFS.
Looking inside without running it
The documented ways to look inside a type 2 AppImage go through its runtime: --appimage-extract unpacks the image into squashfs-root, --appimage-mount mounts it, and --appimage-offset prints the offset
for mount. The AppImage documentation notes that there is currently no way to
extract without calling the AppImage, which is not always appropriate for a file you do not
trust.
Because the offset follows from the ELF headers, the same work can be done on the file as data: read the ELF header and the last section header, open the superblock at the computed offset, and read tables and files from there. That is how UNQIRO reads an AppImage, in your browser, in small pieces, without executing anything.
What UNQIRO reads, and what it does not
- Type 2 AppImages with a SquashFS 4.0 image compressed with gzip or zstd. It reads the ELF header, the last section header, the superblock, the tables it needs, the desktop file and the icon files, not the whole file.
- Not read: images compressed with lzma, lzo, xz or lz4, which are named in the message; AppImages that store their files in DwarFS instead of SquashFS; type 1 AppImages.
-
Linux programs without the
AI 0x02marker are not AppImages. They usually keep their desktop icons outside the executable.
Check your own file
The AppImage tool reads your file in the browser as data. It shows the architecture, the file system and its compression, and lists the icons it finds. Nothing is uploaded, and nothing in the file is run.
Inspect an AppImage without running it (AppImage Icon Extractor)