2

I have a MIPS .so file that I'm trying to reverse. Binary Ninja (too cheap for IDA) finds no symbols, and neither does objdump -T, instead giving "Invalid Operation":

% mips-linux-gnu-objdump -x libinet.so

libinet.so:     file format elf32-tradbigmips
libinet.so
architecture: mips:isa32r2, flags 0x00000140:
DYNAMIC, D_PAGED
start address 0x000040f0

Program Header:
0x70000000 off    0x000000f4 vaddr 0x000000f4 paddr 0x000000f4 align 2**2
         filesz 0x00000018 memsz 0x00000018 flags r--
    LOAD off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**16
         filesz 0x00013454 memsz 0x00013454 flags r-x
    LOAD off    0x00014000 vaddr 0x00024000 paddr 0x00024000 align 2**16
         filesz 0x000006c0 memsz 0x00000d6c flags rw-
 DYNAMIC off    0x0000010c vaddr 0x0000010c paddr 0x0000010c align 2**2
         filesz 0x00000108 memsz 0x00000108 flags rwx
   STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**4
         filesz 0x00000000 memsz 0x00000000 flags rw-
    NULL off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
         filesz 0x00000000 memsz 0x00000000 flags ---
private flags = 74001007: [abi=O32] [mips32r2] [mips16] [not 32bitmode] [noreorder] [PIC] [CPIC]

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
SYMBOL TABLE:
no symbols


% mips-linux-gnu-objdump -T libinet.so
mips-linux-gnu-objdump: libinet.so: Invalid operation
libinet.so:     file format elf32-tradbigmips

Anyone know why this would happen? Are there any better tools to explore the ELF metadata to see if there's something unusual about this shared object?

Edit Some more results:

% file libinet.so 
libinet.so: ELF 32-bit MSB shared object, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, corrupted section header size
% readelf -SW ./libinet.so 

There are no sections in this file.
  • look at the sections using readelf -SW <binary>. Run file against it. etc – julian Jan 15 '18 at 3:43
3

Alas, BFD-based tools like gdb or objdump can't handle ELF files without a section table. However, Linux and other OSes using ELFs do not actually require a file to have section table to be executable, only the segment table (Program headers) are enough. However, readelf does not use BFD and so can display ELF details even without section table. For example:

>readelf -SW sample.elf
There are no sections in this file.

but:

>readelf -ed sample.elf
ELF Header:
  Magic:   7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, big endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           MIPS R3000
  Version:                           0x1
  Entry point address:               0x405110
  Start of program headers:          52 (bytes into file)
  Start of section headers:          0 (bytes into file)
  Flags:                             0x50001007, noreorder, pic, cpic, o32, mips32
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         7
  Size of section headers:           0 (bytes)
  Number of section headers:         0
  Section header string table index: 0

There are no sections in this file.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00400034 0x00400034 0x000e0 0x000e0 R E 0x4
  INTERP         0x000114 0x00400114 0x00400114 0x00014 0x00014 R   0x1
      [Requesting program interpreter: /lib/ld-uClibc.so.0]
  REGINFO        0x000128 0x00400128 0x00400128 0x00018 0x00018 R   0x4
  LOAD           0x000000 0x00400000 0x00400000 0x5d86c 0x5d86c R E 0x1000
  LOAD           0x05e000 0x10000000 0x10000000 0x01f64 0x055c4 RW  0x1000
  DYNAMIC        0x000140 0x00400140 0x00400140 0x04f36 0x04f36 RWE 0x4
  GNU_EH_FRAME   0x05d850 0x0045d850 0x0045d850 0x0001c 0x0001c R   0x4

Dynamic section at offset 0x140 contains 26 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libcrypt.so.0]
 0x00000001 (NEEDED)                     Shared library: [libm.so.0]
 0x00000001 (NEEDED)                     Shared library: [libnbu.so]
 0x00000001 (NEEDED)                     Shared library: [libnbd.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so.0]
 0x0000000c (INIT)                       0x405088
 0x0000000d (FINI)                       0x4557b0
 0x00000004 (HASH)                       0x400238
 0x00000005 (STRTAB)                     0x403868
 0x00000006 (SYMTAB)                     0x401398
 0x0000000a (STRSZ)                      6158 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x70000016 (MIPS_RLD_MAP)               0x10001630
 0x00000015 (DEBUG)                      0x0
 0x00000003 (PLTGOT)                     0x10001640
 0x00000011 (REL)                        0x405078
 0x00000012 (RELSZ)                      16 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x70000001 (MIPS_RLD_VERSION)           1
 0x70000005 (MIPS_FLAGS)                 NOTPOT
 0x70000006 (MIPS_BASE_ADDRESS)          0x400000
 0x7000000a (MIPS_LOCAL_GOTNO)           13
 0x70000011 (MIPS_SYMTABNO)              589
 0x70000012 (MIPS_UNREFEXTNO)            35
 0x70000013 (MIPS_GOTSYM)                0x11
 0x00000000 (NULL)                       0x0

As you can see, it does have a proper dynamic table with DT_SYMTAB entry necessary to resolve symbols. However, readelf lacks a disassembler so we will have to use objdump after all, just without relying on the section table. First, let's extract the first LOAD segment (it has "(R)ead (E)execute" flags so likely to contain code) into a separate file:

dd if=sample.elf bs=1 skip=0 count=383084 of=text.bin

(unfortunately dd only accepts decimal values).

P.S. In fact, since the segment starts at 0, you could have skipped this step and disassembled the input file directly. However, this is not always the case. so may be useful for other files.

Now we can disassemble it with objdump as raw binary:

mips-linux-gnu-objdump -b binary -D -m mips:isa32r2 --adjust-vma=0x00400000 --start-address=0x405110  -EB text.bin 

Breakdown of the options:

  • b binary: treat input as raw binary
  • -D: disassemble everything as code (necessary for raw binaries since they don't have sections or other metadata)
  • -m mips:isa32r2 : treat code as MIPS32r2 instructions
  • --adjust-vma=0x00400000 : assume that binary is loaded at 0x00400000 (VirtAddr column from the readelf program headers dump).
  • --start-address=0x405110: start disassembly at 0x405110 ("Entry point address" from the header dump)
  • -EB: instructions are big-endian (as hinted by readelf).

The result looks like:

Disassembly of section .data:

00405110 <.data+0x5110>:
  405110:       04100001        bltzal  zero,0x405118
  405114:       00000000        nop
  405118:       3c1c0fc0        lui     gp,0xfc0
  40511c:       279c4518        addiu   gp,gp,17688
  405120:       039fe021        addu    gp,gp,ra
  405124:       0000f821        move    ra,zero
  405128:       8fa40000        lw      a0,0(sp)
  40512c:       27a50004        addiu   a1,sp,4
  405130:       24860001        addiu   a2,a0,1
  405134:       00063080        sll     a2,a2,0x2
  405138:       00c53020        add     a2,a2,a1
  40513c:       8f87861c        lw      a3,-31204(gp)
  405140:       27bdffe8        addiu   sp,sp,-24
  405144:       8f82829c        lw      v0,-32100(gp)
  405148:       00000000        nop
  40514c:       afa20010        sw      v0,16(sp)
  405150:       8f998114        lw      t9,-32492(gp)
  405154:       00000000        nop
  405158:       0320f809        jalr    t9
  40515c:       00000000        nop
  405160:       27bd0018        addiu   sp,sp,24
  405164:       1000ffff        b       0x405164
  405168:       00000000        nop

Don't pay attention to .data, that's just the default name when objdump has no other information.

Making sense of the disassembly is left as an exercise to the reader :)


EDIT

I forgot readelf options. In fact, the manpage mentions:


-D
--use-dynamic

When displaying symbols, this option makes readelf use the symbol table in the file's dynamic section, rather than the one in the symbols section.


And indeed, readelf -Ds sample.elf shows nice output:

Symbol table for image:
  Num Buc:    Value  Size   Type   Bind Vis      Ndx Name
   58   0: 00455690     0 FUNC    GLOBAL DEFAULT UND execvp
  384   1: 00454c90     0 FUNC    GLOBAL DEFAULT UND getpgrp
   68   3: 00455640     0 FUNC    GLOBAL DEFAULT UND open
  123   3: 004554a0     0 FUNC    GLOBAL DEFAULT UND socketpair
  513   3: 0044a298     0 FUNC    GLOBAL DEFAULT bad xdr_dirpath
  120   4: 004554b0     0 FUNC    GLOBAL DEFAULT UND strftime
  373   4: 00454ce0     0 FUNC    GLOBAL DEFAULT UND strrchr
  572   4: 00454730     0 FUNC    GLOBAL DEFAULT UND waitpid
  • What kind of compilation options/steps lead to an ELF file lacking a section table? Is there any way to dump the contents of DT_SYMTAB to read the mappings from symbol -> function? – David Jan 15 '18 at 19:26
  • I think some toolchains default to no section table to save space, but I don’t know the details. In theory readelf has all the information needed to dump the symtab, I’m not sure why it’s not doing it (probably nobody got around implementing it). it has the same format as the standard elf .symtab, so you just need to map the virtual address back to the file offset using the program headers. – Igor Skochinsky Jan 15 '18 at 19:48
  • @David see edit. – Igor Skochinsky Jan 15 '18 at 19:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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