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

Looks plausible.

12.4. A few bytes on the stack

The new stub must fulfill a few constraints.

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.

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.

12.6. First test

12.7. Second implementation

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

12.8. Second test

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