Correct use of the strip command

Running 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