7

I want to find how can I identify calls to shared libraries in GDB only. On a stripped binary, I cannot found the dynamic symbol table:

$> objdump -tT crackme-01

crackme-01:     file format elf32-i386

objdump: crackme-01: not a dynamic object
SYMBOL TABLE:
no symbols

DYNAMIC SYMBOL TABLE:
no symbols

But still the dynamic library resolution is present, for instance before the call to strcmp:

0x08048330 in ?? ()
...
0xb7ff2420 in _dl_runtime_resolve () from /lib/ld-linux.so.2
0xb7fec020 in _dl_fixup () from /lib/ld-linux.so.2
0xb7ff6678 in __x86.get_pc_thunk.bx () from /lib/ld-linux.so.2
0xb7fec033 in _dl_fixup () from /lib/ld-linux.so.2
0xb7fe7600 in _dl_lookup_symbol_x () from /lib/ld-linux.so.2
0xb7fe6df9 in do_lookup_x () from /lib/ld-linux.so.2
0xb7fedb70 in _dl_name_match_p () from /lib/ld-linux.so.2
...
0xb7ff5d74 in strcmp () from /lib/ld-linux.so.2

My question is how the symbol table is hidden from readelf but still be used during execution ?

3 Answers 3

5

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 stripep:

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).

7
  • I don't think sstrip removes dynamic symbols.
    – Igor Skochinsky
    Commented Mar 8, 2014 at 17:48
  • Does the last one even run? Can you do readelf -l on it?
    – Igor Skochinsky
    Commented Mar 8, 2014 at 19:41
  • In fact, I never took the time to look at how it work internally. But, it certainly worth to dig it.
    – perror
    Commented Mar 8, 2014 at 19:56
  • Okay, so it does retain the DYNAMIC entry with the symbols. But it seems objdump is too dumb (ha ha) to handle this.
    – Igor Skochinsky
    Commented Mar 8, 2014 at 20:06
  • 1
    Sorry to contradict you @perror, but the dynamic symbols are not removed. The section table is removed, and objdump and readelf rely on the sections to find their way through the binary (be it through libbfd if so). But the sections are absolutely not needed and actually not used by the dynamic linker (usually something like ld.so). readelf -d would show a SYMTAB entry which points to the symbol table which can be sanely interpreted together with the DYNAMIC entries STRTAB, JMPREL and RELA.
    – Celelibi
    Commented Dec 8, 2014 at 16:17
4

So it looks like your executable does use shared objects after all. I'll use my psychic powers and hazard a guess that it's been compressed with something like UPX.

UPX takes an executable (either static or dynamic), compresses its header and segments and adds a small unpacking stub. The resulting executable looks like static to the OS.

However, when it is run, the unpacker stub unpacks the segments and the header into memory, and loads the dynamic interpreter if it was required by the original program. So at runtime, the file does use dynamic symbols (via the interpreter).

EDIT: as shown by perror, it's possible that the file is not actually packed but just had its section table stripped. While this does not affect its runnability it does break many tools, including, apparently, objdump and readelf. You may want to try our Extensive File Dumper (EFD) tool which can print the dynamic symbol table even if the section table has been stripped.

EDIT2: it seems readelf can handle such files after all. Try running readelf -D -s <file.elf>. (I first tried --dyn-syms but it did not work.)

2

The difference is as follows:

The symbol table contains symbols for functions whose code is in the binary itself.

The second snippet of code you listed above shows names of functions whose code is outside of the binary, in shared libraries.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.