Mastering ELF File Structure: Headers, Sections, and Android Hooking
This article explains the detailed ELF file format—including ELF header, section and program header tables, various section types, and a practical Android native hook example—helping developers understand and troubleshoot native binary loading and symbol resolution.
Continuing from the previous article "ELF File Format (Part 1)", this guide dives into the detailed structure of ELF files, illustrated with diagrams.
ELF Header resides at the beginning of the file and is 52 bytes long. It contains fields such as e_ident (the magic bytes 0x7F 45 4C 46), e_type (file type, e.g., 3 for shared objects), e_machine, e_version, e_entry, e_phoff, e_shoff, and size/count fields for program and section header tables. The header essentially points to the locations of the two tables.
Section Header Table is an array of section headers, each 40 bytes, describing every section’s name, type, offset, size, etc. The sh_name field indexes into the string table (.shstrtab). Section types include STRTAB, DYNSYM, PROGBITS, among others. Tools like readelf -S list all sections; for example, libmedia.so contains 29 sections such as STRTAB, DYNSYM, and PROGBITS.
STRTAB Section stores null‑terminated strings used for section and symbol names. The first entry is empty; subsequent entries hold names like .shstrtab and .dynstr. The .dynstr section contains strings referenced by the dynamic symbol table.
Symbol Table Section (e.g., .dynsym) holds entries of 16 bytes each. Fields include st_name (index into .dynstr), st_info (type and binding), st_shndx (section index), and st_value (offset within the file). Function symbols have type STT_FUNC.
Program Header Table is similar to the section header table but describes loadable segments. Each entry points to a segment, its virtual address, file offset, and size. The LOAD segment’s VirtAddr may be non‑zero, affecting runtime mapping.
Practical Use: Native Hooking
Understanding ELF layout helps in native hooking. The typical steps are:
Use dlopen("libmedia.so") to load the shared library.
Call dlsym("function_name") to obtain the function’s address.
Replace the function entry to achieve hooking.
To locate a function manually, one can:
Read the ELF header of libmedia.so.
Find the LOAD segment in the program header table and note its smallest VirtAddr (e.g., 0x1d000).
Locate the section header table, retrieve the index of .shstrtab, and use it to resolve section names.
Identify the symbol table (.dynsym) and string table (.dynstr) sections.
Iterate over symbols, matching st_name (via .dynstr) and filtering for function symbols ( STT_FUNC).
Obtain the symbol’s st_value, adjust for VirtAddr, and compute the actual function offset (e.g., 0x64DF1).
Verification
Using objdump to disassemble libmedia.so confirms the address of the target function _ZN7android11AudioRecord4readEPvjb. Opening the binary at the calculated offset (0x64DF0) reveals the function’s machine code, confirming the hook location.
With the function address known, implementing the hook becomes straightforward.
References: Official ELF documentation and related ELF format guides.
Qizhuo Club
360 Mobile tech channel sharing practical experience and original insights from 360 Mobile Security and other teams across Android, iOS, big data, AI, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
