|
ELF provides two parallel views of a file's contents. The linking view is defined by the section header table, an array of Elf32_Shdr. The execution view is defined by the program header table, an array of Elf32_Phdr.
Theoretically the ELF specification is quite liberal. Position, contents and order of sections and segments are not restricted. But in real life an operating system is to used to just one program loader, one linker and few compilers. This makes the work of virus writers easier. We can reverse engineer the de-facto standard, a tiny subset of what the ELF standard allows. On a typical system only a minority of programs violates this subset, so ignoring them does not lower chances of survival.
All headers necessary to execute a program are stuffed into the first page (0x1000 bytes on sparc), probably to simplify the design of an OS' executable format detector. Dynamically linked executables require a few program headers more than static executables, but that's about all the variation there is. In any case the program header of the code segments is followed by the program header of the data segment. Static executables have the code segment at index 0, dynamically linked executables at index 2.
Let's get a bit more serious and examine the assembly program from The language of evil. A standalone executable built from assembler source is probably the most trivial example we can find.
Command: pre/sparc-debian2.2-linux/segments/objdump.sh
#!/bin/bash
cd tmp/sparc-debian2.2-linux/evil_magic
/bin/ls -Ll att
/usr/bin/objdump -fp att |
Output: out/sparc-debian2.2-linux/segments/objdump
-rwxr-xr-x 1 alba alba 444 Feb 15 23:55 att
att: file format elf32-sparc
architecture: sparc, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x0000000000010074
Program Header:
LOAD off 0x0000000000000000 vaddr 0x0000000000010000 paddr 0x0000000000010000 align 2**16
filesz 0x0000000000000098 memsz 0x0000000000000098 flags r-x
LOAD off 0x0000000000000098 vaddr 0x0000000000020098 paddr 0x0000000000020098 align 2**16
filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
|
objdump's output is butt-ugly. On to readelf. The line starting with "There are 2 program headers" shows the value of e_phnum and e_phoff.
Command: pre/sparc-debian2.2-linux/segments/readelf.sh
#!/bin/bash
cd tmp/sparc-debian2.2-linux/evil_magic
/bin/ls -Ll att
/usr/bin/readelf -l att |
Output: out/sparc-debian2.2-linux/segments/readelf
-rwxr-xr-x 1 alba alba 444 Feb 15 23:55 att
Elf file type is EXEC (Executable file)
Entry point 0x10074
There are 2 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x00010000 0x00010000 0x00098 0x00098 R E 0x10000
LOAD 0x000098 0x00020098 0x00020098 0x00000 0x00000 RW 0x10000
Section to Segment mapping:
Segment Sections...
00 .text
01 |
Nice to see the entry point (0x10074) again. Program layout is a simplified variation of Sort of an answer. The value of FileSiz includes ELF header and program header.
Overhead:
overhead = Entry point - VirtAddr =
0x10074 - 0x10000 = 0x74 = 116 bytes
Effective code size:
code size = FileSiz - overhead =
0x98 - 0x74 = 0x24 = 36 bytes
This matches with the disassembly listing. However, the ratio of file size to effective code deserves the title "Bloat", with capital B. Only 8 percent of the file actually do something useful!
Bloat factor:
code size / file size = 36 / 444 = 0.081
Anyway, we see that even for trivial examples the code is surrounded by lots of other stuff. Let's zoom in on our target. [1].
Command: pre/sparc-debian2.2-linux/segments/sh/readelf.sh
#!/bin/bash
shell=$( /bin/sed 1q \
out/sparc-debian2.2-linux/scanner/segment_padding/infect )
[ -x "${shell}" ] || exit 1
/bin/ls -Ll ${shell}
/usr/bin/readelf -l ${shell} |
Output: out/sparc-debian2.2-linux/segments/sh/readelf
-rwxr-xr-x 1 root root 512932 Jul 17 2002 /bin/bash
Elf file type is EXEC (Executable file)
Entry point 0x1f598
There are 6 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x00010034 0x00010034 0x000c0 0x000c0 R E 0x4
INTERP 0x0000f4 0x000100f4 0x000100f4 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x00010000 0x00010000 0x75763 0x75763 R E 0x10000
LOAD 0x075768 0x00095768 0x00095768 0x057cc 0x09070 RWE 0x10000
DYNAMIC 0x07ae84 0x0009ae84 0x0009ae84 0x000b0 0x000b0 RW 0x4
NOTE 0x000108 0x00010108 0x00010108 0x00020 0x00020 R 0x4
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.bss .rela.plt .init .text .fini .rodata
03 .data .eh_frame .ctors .dtors .plt .got .dynamic .bss
04 .dynamic
05 .note.ABI-tag |
Looks intimidating. But then the ELF specification says that only segments of type "LOAD" are considered for execution. Since the flags of the first one include "execute" but not "write" it must be the code segment. The other one has the "write" flags set, so it must be the data segment. There is one possible deviation: On sparc-sunos most executables built by Sun feature a data segment with "execute" flag.
MemSiz (0x9070) is larger than FileSiz (0x57cc) in the data segment. Just like with mmap(2) excessive bytes are defined to be initialized with 0. The linker takes advantages of that by grouping all variables that should be initialized to zero at the end. Note that the last section of segment 3 (counting starts with 0) is called .bss, the traditional name for this kind of area.
The mapping for segment 2 looks even more complex. But I would guess that .rodata means "read-only data" and .text contains productive code, as opposed to the administrative stuff in the other sections.
Some executables of Red Hat 8.0 have an additional program header of type GNU_EH_FRAME.
Previous examples in The language of evil used an __attribute__ clause to put the code into section .text. Without that it would end up in section .rodata. Both are members of the code segment which is executable in it its entireness; in this regard that would make no difference. But what about putting the code a write enabled data segment? These settings can probably be changed by mprotect(2). [1] But what are the default settings?
Our minimal example is position independent and can be moved around freely. We need a slight modification to be able to call it like a function, though.
Output = Source: out/sparc-debian2.2-linux/evil_magic/func.inc
const unsigned char in_code[]
__attribute__ (( aligned(8), section(".text") )) =
{
0x82,0x10,0x20,0x04, /* 0: mov 4, %g1 */
0x90,0x10,0x20,0x01, /* 4: mov 1, %o0 */
0x13,0x00,0x00,0x40, /* 8: sethi %hi(0x10000), %o1 */
0x92,0x12,0x60,0x01, /* c: or %o1, 1, %o159 */
0x94,0x10,0x20,0x03, /* 10: mov 3, %o2 */
0x91,0xd0,0x20,0x10, /* 14: ta 0x10 */
0x81,0xc3,0xe0,0x08, /* 18: retl */
0x01,0x00,0x00,0x00 /* 1c: nop */
}; /* 32 bytes (0x20) */ |
The program using this piece is platform independent. You will find it at Self modifying code (i). Below is the output. The
Output: out/sparc-debian2.2-linux/evil_magic/self_modify
0x10818 is code ... ELF sigill=0
0x21d60 is data ... ELF sigill=0
0x21d90 is heap ... ELF sigill=0
0xefffeb68 is stack ... ELF sigill=0 |
[1] | The matter is actually quite complex. Theo de Raadt himself describes the problems they had with making OpenBSD more secure at http://marc.theaimsgroup.com/?l=openbsd-tech&m=104391783312978&w=2 |