The ELF Virus Writing HOWTO: sparc-debian-linux | ||
---|---|---|
Prev |
"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.
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/sparc-debian-linux/magic_elf/objdump
106a8: 9d e3 bf 98 save %sp, -104, %sp
106ac: 13 00 00 40 sethi %hi(0x10000), %o1
106b0: 92 12 60 01 or %o1, 1, %o1 ! 10001 <*ABS*+0x10001>
106b4: 90 10 20 01 mov 1, %o0
106b8: 40 00 44 a1 call 2193c <_PROCEDURE_LINKAGE_TABLE_+0x30>
106bc: 94 10 20 03 mov 3, %o2
106c0: 81 c7 e0 08 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/sparc-debian-linux/magic_elf/gdb
No symbol table is loaded. Use the "file" command.
0x106a8 <main>: save %sp, -104, %sp
0x106ac <main+4>: sethi %hi(0x10000), %o1
0x106b0 <main+8>: or %o1, 1, %o1 ! 0x10001
0x106b4 <main+12>: mov 1, %o0
0x106b8 <main+16>: call 0x2193c <write>
0x106bc <main+20>: mov 3, %o2
0x106c0 <main+24>: 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/sparc-debian-linux/magic_elf/magic_elf_static
total 496
-rwxrwxr-x 1 alba alba 11104 Aug 16 00:19 magic_elf
-rwxrwxr-x 1 alba alba 490676 Aug 16 00:19 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/sparc-debian-linux/evil_magic/static_main.gdb
No symbol table is loaded. Use the "file" command.
0x1022c <main>: save %sp, -104, %sp
0x10230 <main+4>: sethi %hi(0x10000), %o1
0x10234 <main+8>: or %o1, 1, %o1 ! 0x10001
0x10238 <main+12>: mov 1, %o0
0x1023c <main+16>: call 0x19644 <write>
0x10240 <main+20>: mov 3, %o2
0x10244 <main+24>: ret |
The function was called write before, it is called write now. Let's look what is behind the name.
Output: out/sparc-debian-linux/evil_magic/static_write.gdb
No symbol table is loaded. Use the "file" command.
0x19644 <write>: mov 4, %g1 ! 0x4
0x19648 <write+4>: ta 0x10
0x1964c <write+8>: bcc,a 0x1966c <write+40>
0x19650 <write+12>: nop
0x19654 <write+16>: save %sp, -96, %sp
0x19658 <write+20>: call 0x11508 <__errno_location>
0x1965c <write+24>: nop
0x19660 <write+28>: st %i0, [ %o0 ]
0x19664 <write+32>: ret |
There are two man pages giving some overview of system calls, intro(2) and syscalls(2). The statement mov 4,%g1 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.
Source: src/evil_magic/sparc-Linux.asm
.section .text
.globl _start
_start: mov 4, %g1 ! write
mov 1, %o0 ! fd=1
sethi %hi(0x10001), %o1
or %o1, %lo(0x10001), %o1 ! buf=0x10001
mov 3, %o2 ! count=3
ta 0x10
mov 1, %g1 ! exit
mov %g0, %o0 ! status=0
ta 0x10 |
Command: src/evil_magic/att.sh
#!/bin/sh
as -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/sparc-debian-linux/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.
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/sparc-debian-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/sparc-debian-linux/evil_magic/od
0x00010074 |
The entry point is specified as a virtual address in memory. By subtracting the base address we get the file offset:
0x10074 - 0x10000 = 0x74 = 116
Command: out/sparc-debian-linux/evil_magic/dis-att.sh
#!/bin/sh
${OBJDUMP} --start-address=0x00010074 -d \
-b elf32-sparc tmp/sparc-debian-linux/evil_magic/att \
| src/magic_elf/objdump_format.sh |
Output: out/sparc-debian-linux/evil_magic/evil_magic.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, %o1 ! 10001 <*ABS*+0x10001>
10084: 94 10 20 03 mov 3, %o2
10088: 91 d0 20 10 ta 0x10
1008c: 82 10 20 01 mov 1, %g1
10090: 90 10 00 00 mov %g0, %o0
10094: 91 d0 20 10 ta 0x10 |
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/sparc-debian-linux/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, %o1 */
0x94,0x10,0x20,0x03, /* 10084: mov 3, %o2 */
0x91,0xd0,0x20,0x10, /* 10088: ta 0x10 */
0x82,0x10,0x20,0x01, /* 1008c: mov 1, %g1 */
0x90,0x10,0x00,0x00, /* 10090: mov %g0, %o0 */
0x91,0xd0,0x20,0x10 /* 10094: ta 0x10 */
}; |
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/sparc-debian-linux/evil_magic/cc
out/sparc-debian-linux/evil_magic/evil_magic.c:2: warning: `main' is usually a function
ELF |