The ECFS file format is both simple and complicated! The ELF file format is complex in general, and ECFS inherits those complexities from a structural point of view. On the other side of the token, ECFS helps make navigating a process image quite easy if you know what specific features it has and what to look for.
In previous sections, we gave some real-life examples of utilizing ECFS that demonstrated many of its primary features. However, it is also important to have a simple and direct reference to what those characteristics are, such as which custom sections exist and what exactly they mean. In this section, we will provide a reference for the ECFS snapshot files.
The ECFS handler uses advanced understanding of the ELF binary format and even the dwarf debugging format—specifically with the dynamic segment and the GNU_EH_FRAME segment—to fully reconstruct the symbol tables of the program. Even if the original binary has been stripped and has no section headers, the ECFS handler is intelligent enough to rebuild the symbol tables.
I have personally never encountered a situation where symbol table reconstruction failed completely. It usually reconstructs all or most symbol table entries. The symbol tables can be accessed using a utility such as readelf or readecfs. The libecfs API also has several functions:
int get_dynamic_symbols(ecfs_elf_t *desc, ecfs_sym_t **syms) int get_local_symbols(ecfs_elf_t *desc, ecfs_sym_t **syms)
One function gets the dynamic symbol table and the other gets the local symbol table—.dynsym and .symtab, respectively.
The following is the reading symbol table with readelf:
$ readelf -s host.6758
Symbol table '.dynsym' contains 8 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00007f3dfd48b000 0 NOTYPE LOCAL DEFAULT UND
1: 00007f3dfd4f9730 0 FUNC GLOBAL DEFAULT UND fputs
2: 00007f3dfd4acdd0 0 FUNC GLOBAL DEFAULT UND __libc_start_main
3: 00007f3dfd4f9220 0 FUNC GLOBAL DEFAULT UND fgets
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 00007f3dfd4f94e0 0 FUNC GLOBAL DEFAULT UND fopen
6: 00007f3dfd54bd00 0 FUNC GLOBAL DEFAULT UND sleep
7: 00007f3dfd84a870 8 OBJECT GLOBAL DEFAULT 25 stdout
Symbol table '.symtab' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000004004f0 112 FUNC GLOBAL DEFAULT 10 sub_4004f0
1: 0000000000400560 42 FUNC GLOBAL DEFAULT 10 sub_400560
2: 000000000040064d 138 FUNC GLOBAL DEFAULT 10 sub_40064d
3: 00000000004006e0 101 FUNC GLOBAL DEFAULT 10 sub_4006e0
4: 0000000000400750 2 FUNC GLOBAL DEFAULT 10 sub_400750The ECFS handler reconstructs most of the original section headers that a program may have had. It also adds quite a few new sections and section types that can be very useful for forensic analysis. Section headers are identified by both name and type and contain data or code.
Parsing section headers is very easy, and therefore they are very useful for creating a map of the process memory image. Navigating the entire process layout through section headers is a lot easier than having only program headers (such as with regular core files), which don't even have string names. The program headers are what describe the segments of memory, and the section headers are what give context to each part of a given segment. Section headers help give a much higher resolution to the reverse engineer.
The ECFS core file format is essentially backward compatible with regular Linux core files, and can therefore be used as core files for debugging with GDB in the traditional way.
The ELF file header for ECFS files has its e_type (ELF type) set to ET_NONE instead of ET_CORE, however. This is because core files are not expected to have section headers but ECFS files do have section headers, and to make sure that they are acknowledged by certain utilities such as objdump, objcopy, and so on, we have to mark them as files other than CORE files. The quickest way to toggle the ELF type in an ECFS file is with the et_flip utility that comes with the ECFS software suite.
Here's an example of using GDB with an ECFS core file:
$ gdb -q /usr/sbin/sshd sshd.1195 Reading symbols from /usr/sbin/sshd...(no debugging symbols found)...done. "/opt/ecfs/cores/sshd.1195" is not a core dump: File format not recognized (gdb) quit
Then, the following is an example of changing the ELF file type to ET_CORE and trying again:
$ et_flip sshd.1195 $ gdb -q /usr/sbin/sshd sshd.1195 Reading symbols from /usr/sbin/sshd...(no debugging symbols found)...done. [New LWP 1195] [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Core was generated by `/usr/sbin/sshd -D'. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x00007ff4066b8d83 in __select_nocancel () at ../sysdeps/unix/syscall-template.S:81 81 ../sysdeps/unix/syscall-template.S: No such file or directory. (gdb)
The libecfs API is the key component for integrating ECFS support into your malware analysis and reverse engineering tools for Linux. There is too much to document on this library to put into a single chapter of this book. I recommend that you use the manual that is still growing right alongside the project itself:
https://github.com/elfmaster/ecfs/blob/master/Documentation/libecfs_manual.txt