"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 fancy output format of The address of main was chosen for a reason. It is valid input for /bin/sh. Let's see whether In the language of mortals has main at the same offset.
Command: src/magic_elf/intel.sh
#!/bin/sh
. ${OUT}/magic_elf/addr_of_main
ndisasm -e ${ofs} -o ${main} -U ${TMP}/magic_elf/magic_elf \
| sed -e '/ret/q' |
Output: out/i386-redhat-linux/magic_elf/disasm
08048400 55 push ebp
08048401 89E5 mov ebp,esp
08048403 83EC0C sub esp,byte +0xc
08048406 6A03 push byte +0x3
08048408 6801800408 push dword 0x8048001
0804840D 6A01 push byte +0x1
0804840F E8BCFEFFFF call 0x80482d0
08048414 31C0 xor eax,eax
08048416 89EC mov esp,ebp
08048418 5D pop ebp
08048419 C3 ret |
While the simplicity of ndisasm is charming, the glitches in objdump's output require heavy machinery. Note that character ";" starts a comment on i386. On sparc a "!" is used instead.
Command: src/magic_elf/objdump.sh
#!/bin/sh
. ${OUT}/magic_elf/addr_of_main
${OBJDUMP} --start-address=${main} -d ${TMP}/magic_elf/magic_elf \
| src/magic_elf/objdump_format.sh |
Command: src/magic_elf/objdump_format.sh
#!/bin/sh
# white space is tab-stop, not just spaces
tab='[^ ]\{1,\} '
space='[[:space:]]\{1,\}'
nospace='[^[:space:]]\{1,\}'
sed -n -e 's/ * / /g' \
-e "s/${space}\([;!]\)/ \1/" \
-e "s/^\(${tab}${tab}${nospace}\)${space}/\1 /" \
-e '/^ *[[:xdigit:]]*:/,$ p' \
-e '/ret/q' \
| expand -t 12,32,40,60 |
Output: out/i386-redhat-linux/magic_elf/objdump
8048400: 55 push %ebp
8048401: 89 e5 mov %esp,%ebp
8048403: 83 ec 0c sub $0xc,%esp
8048406: 6a 03 push $0x3
8048408: 68 01 80 04 08 push $0x8048001
804840d: 6a 01 push $0x1
804840f: e8 bc fe ff ff call 80482d0 <_init+0x38>
8048414: 31 c0 xor %eax,%eax
8048416: 89 ec mov %ebp,%esp
8048418: 5d pop %ebp
8048419: c3 ret |
Both programs have main at the same file offset. Unfortunately a brief look through /bin proves this to be pure chance. And instead of a real system call for write we see a call to strange negative address (check the opcode). It resolves to a location in a shared library. But what function in what library?
Command: src/magic_elf/gdb-core.sh
#!/bin/sh
gdb ${1} -q <<EOF
set disassembly-flavor ${ASM_STYLE}
disassemble ${2}
EOF |
Command: src/magic_elf/gdb.sh
#!/bin/sh
file=${1:-${TMP}/magic_elf/magic_elf}
func=${2:-main}
src/magic_elf/gdb-core.sh ${file} ${func} \
| src/magic_elf/gdb-format.sh |
Output: out/i386-redhat-linux/magic_elf/gdb
0x8048400 <main>: push ebp
0x8048401 <main+1>: mov ebp,esp
0x8048403 <main+3>: sub esp,0xc
0x8048406 <main+6>: push 0x3
0x8048408 <main+8>: push 0x8048001
0x804840d <main+13>: push 0x1
0x804840f <main+15>: call 0x80482d0 <write>
0x8048414 <main+20>: xor eax,eax
0x8048416 <main+22>: mov esp,ebp
0x8048418 <main+24>: pop ebp
0x8048419 <main+25>: ret |
Not shown is a pathetic attempt to single-step to the actual code of write.
We can now search for a fine manual explaining how to debug shared libraries. Or just compile the bugger static.
Command: src/magic_elf/cc_static.sh
#!/bin/sh
gcc ${CFLAGS} -static ${OUT}/${arch}/magic_elf/magic_elf.c \
-o ${TMP}/magic_elf/magic_elf_static \
&& ls -l ${TMP}/magic_elf \
&& ${TMP}/magic_elf/magic_elf_static |
Output: out/i386-redhat-linux/magic_elf/magic_elf_static
total 452
-rwxrwxr-x 1 alba alba 13495 Aug 16 00:13 magic_elf
-rwxrwxr-x 1 alba alba 441430 Aug 16 00:13 magic_elf_static
ELF |
Seems we found an easy way to fill up the hard disk. Anyway, what has gdb(1) to say about it?
Output: out/i386-redhat-linux/evil_magic/static_main.gdb
0x80481e0 <main>: push ebp
0x80481e1 <main+1>: mov ebp,esp
0x80481e3 <main+3>: sub esp,0xc
0x80481e6 <main+6>: push 0x3
0x80481e8 <main+8>: push 0x8048001
0x80481ed <main+13>: push 0x1
0x80481ef <main+15>: call 0x804ccf0 <write>
0x80481f4 <main+20>: xor eax,eax
0x80481f6 <main+22>: mov esp,ebp
0x80481f8 <main+24>: pop ebp
0x80481f9 <main+25>: ret |
The function was called write before, it is called write now. Let's look what is behind the name.
Output: out/i386-redhat-linux/evil_magic/static_write.gdb
0x804ccf0 <write>: push ebx
0x804ccf1 <write+1>: mov edx,DWORD PTR [esp+16]
0x804ccf5 <write+5>: mov ecx,DWORD PTR [esp+12]
0x804ccf9 <write+9>: mov ebx,DWORD PTR [esp+8]
0x804ccfd <write+13>: mov eax,0x4
0x804cd02 <write+18>: int 0x80
0x804cd04 <write+20>: pop ebx
0x804cd05 <write+21>: cmp eax,0xfffff001
0x804cd0a <write+26>: jae 0x804d510 <__syscall_error>
0x804cd10 <write+32>: ret |
There are two man pages giving some overview of system calls, intro(2) and syscalls(2). The statement mov eax,4 corresponds to the value of __NR_write in /usr/include/asm/unistd.h.
The code generated by gcc(1) is not suitable for a virus. So here comes hand crafted code. [1]
Source: src/evil_magic/i386-Linux.asm
global _start
_start: push byte 4
pop eax ; eax = 4 = write(2)
xor ebx,ebx
inc ebx ; ebx = 1 = stdout
mov ecx,0x08048001 ; ecx = magic address
push byte 3
pop edx ; edx = 3 = three characters
int 0x80
xor eax,eax
inc eax ; eax = 1 = exit(2)
xor ebx,ebx ; ebx = 0 = return code
int 0x80 |
Command: src/evil_magic/intel.sh
#!/bin/sh
nasm -f elf -o ${TMP}/evil_magic/${ASM_STYLE}.o \
src/evil_magic/${ARCH}-${UNAME}.asm \
&& ld -o ${TMP}/evil_magic/${ASM_STYLE} ${TMP}/evil_magic/${ASM_STYLE}.o \
&& ${TMP}/evil_magic/${ASM_STYLE} |
Output: out/i386-redhat-linux/evil_magic/intel
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.
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: src/evil_magic/ofs_entry.c
#include <stddef.h>
#include <stdio.h>
#include <elf.h>
int main()
{
printf("sizeof_Elf32_Ehdr=%u\n", sizeof(Elf32_Ehdr));
printf("offset_e_entry=%u\n", offsetof(Elf32_Ehdr, e_entry));
printf("sizeof_e_entry=%u\n", sizeof(((Elf32_Ehdr*)0)->e_entry));
return 0;
} |
Output: out/i386-redhat-linux/evil_magic/ofs_entry
sizeof_Elf32_Ehdr=52
offset_e_entry=24
sizeof_e_entry=4 |
A look into /usr/include/elf.h shows that Elf32_Ehdr::e_entry is really at file offset 24.
Command: src/evil_magic/od/Linux.sh
#!/bin/sh
od -j24 -An -tx4 -N4 ${TMP}/evil_magic/${ASM_STYLE} \
| sed 's/^[[:space:]]/0x/' |
Output: out/i386-redhat-linux/evil_magic/od
0x08048080 |
The entry point is specified as a virtual address in memory. By subtracting the base address we get the file offset:
0x8048080 - 0x8048000 = 0x80 = 128
Command: out/i386-redhat-linux/evil_magic/dis-intel.sh
#!/bin/sh
ndisasm -e 128 -o 0x08048080 -U tmp/i386-redhat-linux/evil_magic/intel \
| head -12 |
Output: out/i386-redhat-linux/evil_magic/evil_magic.asm
08048080 6A04 push byte +0x4
08048082 58 pop eax
08048083 31DB xor ebx,ebx
08048085 43 inc ebx
08048086 B901800408 mov ecx,0x8048001
0804808B 6A03 push byte +0x3
0804808D 5A pop edx
0804808E CD80 int 0x80
08048090 31C0 xor eax,eax
08048092 40 inc eax
08048093 31DB xor ebx,ebx
08048095 CD80 int 0x80 |
There is still one thing left: Dressing up the hex dump as C source. We use the script from Dressing up binary code.
Output: out/i386-redhat-linux/evil_magic/evil_magic.c
const unsigned char main[]
__attribute__ (( aligned(8), section(".text") )) =
{
0x6A,0x04, /* 08048080: push byte +0x4 */
0x58, /* 08048082: pop eax */
0x31,0xDB, /* 08048083: xor ebx,ebx */
0x43, /* 08048085: inc ebx */
0xB9,0x01,0x80,0x04,0x08, /* 08048086: mov ecx,0x8048001 */
0x6A,0x03, /* 0804808B: push byte +0x3 */
0x5A, /* 0804808D: pop edx */
0xCD,0x80, /* 0804808E: int 0x80 */
0x31,0xC0, /* 08048090: xor eax,eax */
0x40, /* 08048092: inc eax */
0x31,0xDB, /* 08048093: xor ebx,ebx */
0xCD,0x80 /* 08048095: int 0x80 */
}; |
Calling the string constant main is not a mistake. Above output is a complete and valid C program.
Command: src/evil_magic/cc.sh
#!/bin/sh
gcc -Wall -O2 ${OUT}/evil_magic/evil_magic.c \
-o ${TMP}/evil_magic/cc \
&& ${TMP}/evil_magic/cc |
Output: out/i386-redhat-linux/evil_magic/cc
out/i386-redhat-linux/evil_magic/evil_magic.c:2: warning: `main' is usually a function
ELF |
[1] | Optimized for size. Twenty three is the perfect number of bytes. See http://www.goethe.de/uk/mon/archiv/gh00/e23.htm |