In fact, they probably used the sstrip
software from the package ElfKicker. According to the sstrip
README
file:
sstrip is a small utility that removes the contents at the end of an
ELF file that are not part of the program's memory image.
Most ELF executables are built with both a program header table and a
section header table. However, only the former is required in order
for the OS to load, link and execute a program. sstrip attempts to
extract the ELF header, the program header table, and its contents,
leaving everything else in the bit bucket. It can only remove parts of
the file that occur at the end, after the parts to be saved. However,
this almost always includes the section header table, along with a few
other sections that are not involved in program loading and execution.
It should be noted that most programs that work with ELF files are
dependent on the section header table as an index to the file's
contents. Thus, utilities such as gdb and objdump will often have
limited functionality when working with an executable with no section
header table. Some other utilities may refuse to work with them at
all.
In fact, sstrip
remove all section information from the executable and keep the executable still usable.
But let see the different levels is strip that we can reach.
No stripping
Let consider a program (similar to the one looked at in the question) with no stripping a all.
$> objdump -tT ./crackme
./crackme: file format elf32-i386
SYMBOL TABLE:
08048134 l d .interp 00000000 .interp
08048148 l d .note.ABI-tag 00000000 .note.ABI-tag
08048168 l d .note.gnu.build-id 00000000 .note.gnu.build-id
0804818c l d .gnu.hash 00000000 .gnu.hash
080481ac l d .dynsym 00000000 .dynsym
0804822c l d .dynstr 00000000 .dynstr
...
080497dc g .bss 00000000 _end
08048390 g F .text 00000000 _start
080485f8 g O .rodata 00000004 _fp_hw
080497d8 g .bss 00000000 __bss_start
08048490 g F .text 00000000 main
00000000 w *UND* 00000000 _Jv_RegisterClasses
080497d8 g O .data 00000000 .hidden __TMC_END__
00000000 w *UND* 00000000 _ITM_registerTMCloneTable
080482f4 g F .init 00000000 _init
DYNAMIC SYMBOL TABLE:
00000000 DF *UND* 00000000 GLIBC_2.0 strcmp
00000000 DF *UND* 00000000 GLIBC_2.0 read
00000000 DF *UND* 00000000 GLIBC_2.0 printf
00000000 DF *UND* 00000000 GLIBC_2.0 system
00000000 w D *UND* 00000000 __gmon_start__
00000000 DF *UND* 00000000 GLIBC_2.0 __libc_start_main
080485fc g DO .rodata 00000004 Base _IO_stdin_used
Stripping with strip
$> strip ./crackme-striped
$> objdump -tT ./crackme-striped
./crackme-striped: file format elf32-i386
SYMBOL TABLE:
no symbols
DYNAMIC SYMBOL TABLE:
00000000 DF *UND* 00000000 GLIBC_2.0 strcmp
00000000 DF *UND* 00000000 GLIBC_2.0 read
00000000 DF *UND* 00000000 GLIBC_2.0 printf
00000000 DF *UND* 00000000 GLIBC_2.0 system
00000000 w D *UND* 00000000 __gmon_start__
00000000 DF *UND* 00000000 GLIBC_2.0 __libc_start_main
080485fc g DO .rodata 00000004 Base _IO_stdin_used
As you see, the dynamic symbols are still here when strip
is applied. The rest is just removed cleanly.
Stripping with sstrip
Finally, lets take a look at what happen when using sstrip
.
$> sstrip ./crackme-sstriped
$> objdump -tT ./crackme-sstriped
./crackme-sstriped: file format elf32-i386
objdump: ./crackme-sstriped: not a dynamic object
SYMBOL TABLE:
no symbols
DYNAMIC SYMBOL TABLE:
no symbols
As you can notice, all symbols, including dynamic symbols have been removed. In fact, all the symbols pointing towards the PLT are removed and addresses are left as static addresses. Here is an example with the _start
procedure preamble, first all the symbols:
0x8048390 <_start>: xor %ebp,%ebp
0x8048392 <_start+2>: pop %esi
0x8048393 <_start+3>: mov %esp,%ecx
0x8048395 <_start+5>: and $0xfffffff0,%esp
0x8048398 <_start+8>: push %eax
0x8048399 <_start+9>: push %esp
0x804839a <_start+10>: push %edx
0x804839b <_start+11>: push $0x80485e0
0x80483a0 <_start+16>: push $0x8048570
0x80483a5 <_start+21>: push %ecx
0x80483a6 <_start+22>: push %esi
0x80483a7 <_start+23>: push $0x8048490
0x80483ac <_start+28>: call 0x8048380 <__libc_start_main@plt>
0x80483b1 <_start+33>: hlt
And, then strip
ep:
0x8048390: xor %ebp,%ebp
0x8048392: pop %esi
0x8048393: mov %esp,%ecx
0x8048395: and $0xfffffff0,%esp
0x8048398: push %eax
0x8048399: push %esp
0x804839a: push %edx
0x804839b: push $0x80485e0
0x80483a0: push $0x8048570
0x80483a5: push %ecx
0x80483a6: push %esi
0x80483a7: push $0x8048490
0x80483ac: call 0x8048380 <__libc_start_main@plt>
0x80483b1: hlt
And, finally, the sstrip
version:
0x8048390: xor %ebp,%ebp
0x8048392: pop %esi
0x8048393: mov %esp,%ecx
0x8048395: and $0xfffffff0,%esp
0x8048398: push %eax
0x8048399: push %esp
0x804839a: push %edx
0x804839b: push $0x80485e0
0x80483a0: push $0x8048570
0x80483a5: push %ecx
0x80483a6: push %esi
0x80483a7: push $0x8048490
0x80483ac: call 0x8048380
0x80483b1: hlt
Surprisingly the executable is still functional. Let's compare what ELF headers are left after strip
and sstrip
(as suggested Igor). First, after a strip
:
$> readelf -l crackme-striped
Elf file type is EXEC (Executable file)
Entry point 0x8048390
There are 8 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 RWE 0x4
INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 RWE 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x006b4 0x006b4 RWE 0x1000
LOAD 0x0006b4 0x080496b4 0x080496b4 0x00124 0x00128 RWE 0x1000
DYNAMIC 0x0006c0 0x080496c0 0x080496c0 0x000e8 0x000e8 RWE 0x4
NOTE 0x000148 0x08048148 0x08048148 0x00044 0x00044 RWE 0x4
GNU_EH_FRAME 0x000600 0x08048600 0x08048600 0x00024 0x00024 RWE 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
And, then the version that went through with sstrip
:
$> readelf -l ./crackme-sstriped
Elf file type is EXEC (Executable file)
Entry point 0x8048390
There are 8 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 RWE 0x4
INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 RWE 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x006b4 0x006b4 RWE 0x1000
LOAD 0x0006b4 0x080496b4 0x080496b4 0x00124 0x00128 RWE 0x1000
DYNAMIC 0x0006c0 0x080496c0 0x080496c0 0x000e8 0x000e8 RWE 0x4
NOTE 0x000148 0x08048148 0x08048148 0x00044 0x00044 RWE 0x4
GNU_EH_FRAME 0x000600 0x08048600 0x08048600 0x00024 0x00024 RWE 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
As you can see, the name of the sections have also been removed (as announced in README file).
Note that, applying sstrip
on an executable that went through upx
render the final executable unusable (I tried).