strip
commandRunning the strip
command on an executable is the most common program
protection method. In its default operation, the strip command removes
the symbol table and any debugging information from an executable. This
is how it is typically used. However, there is still useful information
that is not removed.
Consider the following statically linked and stripped program:
$ echo 'main() { printf("hello world\n"); }' > prog.c
$ gcc -static -o prog prog.c
$ strip prog
Examining the section header table:
$ readelf --section-headers prog
There are 18 section headers, starting at offset 0x5da1c:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .init PROGBITS 080480b4 0000b4 000025 00 AX 0 0 4
[ 2] .text PROGBITS 080480e0 0000e0 04249f 00 AX 0 0 32
[ 3] .fini PROGBITS 0808a580 042580 00001c 00 AX 0 0 4
[ 4] .rodata PROGBITS 0808a5a0 0425a0 0145ec 00 A 0 0 32
[ 5] __libc_subinit PROGBITS 0809eb8c 056b8c 000008 00 A 0 0 4
[ 6] __libc_subfreeres PROGBITS 0809eb94 056b94 000040 00 A 0 0 4
[ 7] __libc_atexit PROGBITS 0809ebd4 056bd4 000004 00 A 0 0 4
[ 8] .data PROGBITS 0809f000 057000 00112c 00 WA 0 0 32
[ 9] .eh_frame PROGBITS 080a012c 05812c 001874 00 WA 0 0 4
[10] .ctors PROGBITS 080a19a0 0599a0 000008 00 WA 0 0 4
[11] .dtors PROGBITS 080a19a8 0599a8 000008 00 WA 0 0 4
[12] .got PROGBITS 080a19b0 0599b0 000010 04 WA 0 0 4
[13] .bss NOBITS 080a19c0 0599c0 000f48 00 WA 0 0 32
[14] .comment PROGBITS 00000000 0599c0 002d00 00 0 0 1
[15] .note.ABI-tag NOTE 08048094 000094 000020 00 A 0 0 4
[16] .note NOTE 00000000 05c6c0 0012c0 00 0 0 1
[17] .shstrtab STRTAB 00000000 05d980 000099 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
There are 3 sections that are of special interest: the .comment
.note.ABI-tag
and .note
sections.
When an executable is produced from source code, there are two stages -
compilation and linking. Compiling takes a source file and produces an
object file. Linking concatenates these object files into a single
executable. The concatenation occurs by section. For example, the
.comment
section for the final executable will contain the contents of
the .comment
section of each object file that was linked into the
executable.
If we examine the contents of the .comment
section we can see the
compiler used, plus the version of the compiler. In this case
gcc-2.95.4
from the debian distribution was used. Note that the
contents of the .comment
section are not standardised, and hence the
compiler can fill it with anything it likes:
$ objdump --full-contents --section=.comment prog | head
prog: file format elf32-i386
Contents of section .comment:
0000 00474343 3a202847 4e552920 322e3935 .GCC: (GNU) 2.95
0010 2e342032 30303131 30303220 28446562 .4 20011002 (Deb
0020 69616e20 70726572 656c6561 73652900 ian prerelease).
0030 00474343 3a202847 4e552920 322e3935 .GCC: (GNU) 2.95
0040 2e342032 30303131 30303220 28446562 .4 20011002 (Deb
0050 69616e20 70726572 656c6561 73652900 ian prerelease).
Next we examine the .note section:
$ objdump --full-contents --section=.note prog | head
prog: file format elf32-i386
Contents of section .note:
0000 08000000 00000000 01000000 30312e30 ............01.0
0010 31000000 08000000 00000000 01000000 1...............
0020 30312e30 31000000 08000000 00000000 01.01...........
0030 01000000 30312e30 31000000 08000000 ....01.01.......
0040 00000000 01000000 30312e30 31000000 ........01.01...
0050 08000000 00000000 01000000 30312e30 ............01.0
The .note
section contains elements that begin with a fixed header:
/* from <elf.h> */
typedef struct
{
Elf32_Word n_namesz; /* Length of the note's name. */
Elf32_Word n_descsz; /* Length of the note's descriptor. */
Elf32_Word n_type; /* Type of the note. */
} Elf32_Nhdr;
Examining the first entry we have a version entry, the version being
"01.01"
. This is typical for code produced by gcc:
header = { n_namesz = 8; n_descsz = 0; n_type = NT_VERSION; }
n_name = "01.01";
Finally we examine the .note.ABI-tag
section:
$ objdump --full-contents --section=.note.ABI-tag prog
prog: file format elf32-i386
Contents of section .note.ABI-tag:
8048094 04000000 10000000 01000000 474e5500 ............GNU.
80480a4 00000000 02000000 02000000 00000000 ................
As per the .note
section, the entry is prefixed with a header of type
Elf32_Nhdr
. As this is an ABI-tag section, the contents of the entry
have the following form:
/* ABI information. The descriptor consists of words:
word 0: OS descriptor
word 1: major version of the ABI
word 2: minor version of the ABI
word 3: subminor version of the ABI
*/
header = { n_namesz = 4; n_descsz = 16; n_type = ELF_NOTE_ABI; }
n_name = "GNU";
n_desc = [ELF_NOTE_OS_LINUX, 2, 2, 0];
The above decoded note entry shows that the operating system is linux, and executable is for a GNU linux system with ABI 2.2.0. In plain english, this means that the executable will only run on linux kernel version 2.2.0 and later.
Summarising the contents of the .note
.comment
and .note.ABI-tag
sections, we see that the executable was compiled by gcc-2.95-4
from
the debian distribution, and requires kernel version at least 2.2.0.
This information is extremely useful for reversing the executable.
The moral of this story? Supply the proper flags to strip
to remove
these sections if you wish to make reverse engineering more difficult:
$ strip -R .comment -R .note -R .note.ABI-tag prog