Tweets by @buherablog
profile for buherator at IT Security Stack Exchange, Q&A for IT security professionals

A BitBetyár Blog

Túljártál a nagyokosok eszén? Küldd be a mutatványodat! (e-mail a buherator gmailkomra jöhet)

Full-Disclosure / Névjegy / Coming out


Promó

H.A.C.K.

Címkék

0day (110) adobe (87) adobe reader (21) anonymous (26) apple (60) az olvasó ír (49) blackhat (20) botnet (22) bug (200) buherablog (44) buhera sörözés (39) bukta (49) deface (38) dns (22) dos (29) esemény (82) facebook (26) firefox (64) flash (33) gondolat (31) google (59) google chrome (36) hacktivity (37) hírek (117) incidens (224) internet explorer (88) iphone (35) java (50) jog (22) kína (21) kriptográfia (68) kultúra (21) linux (24) malware (43) microsoft (142) móka (48) mozilla (23) office (26) oracle (40) os x (43) patch (197) php (20) politika (31) privacy (58) programozás (22) safari (34) sql injection (62) windows (85) xss (77) Címkefelhő

Licensz

Creative Commons Licenc

iCTF 2013 - Pesticides

2013.03.30. 13:36 | buherator | 5 komment

A CrySys labor idén is csapatot szervezett a 2013-as iCTF versenyre, ami a Santa Barbarai Egyetem éves Capture the Flag játéka. A kihívásból én is kivettem a részem, az alábbiakban a Pesticides pálya (vélt) megoldása következik (nem mindenre emlékszem 100%-ig az esetleges pontatlanságokért előre is elnézést).

Az idei iCTF minden feladata valamilyen kritikus információs infrastrukturát modellező szolgáltatás köré épült: a csapatoknak biztonsági problémákat kellett találniuk a szolgáltatásokban, meg kellett írniuk az ezek kihasználására alkalmas exploitot, valamint ki kellett szűrniük a beérkező hálózati forgalomból a támadásokat. A Pesticides valamiféle vegyi üzem egyik vezérlőjét szimulálta, melyen keresztül lekérdezhetők és módosíthatók egyes kemikáliák összetevői. 

Az iCTF feladatokkal kapcsolatban nem megszokott módon a szolgáltatáshoz egy README-t is mellékeltek, melyben leírták a használt protokoll működésének alapjait:

A kommunikáció első lépéseként a csatlakozó kliens egy session kulcsot (szKey) küld a szervernek, a kommunikáció további része RC4-el titkosítva zajlik, a MASTER_KEY || szKey kulccsal, ahol a || a konkatenációt, a MASTER_KEY pedig a mindkét fél számára ismert mesterkulcsot jelenti.

Ez után a kérések az alábbi formátumban küldhetők:

<PROTOCOL>\n
CODE=<COMMAND>\n
<OPT>=<VAL>\n
<OPT>=<VAL>\n
\n

egy érvényes kérés például:

MODBUS-v3\n
CODE=LIST\n
\n

A README egy hiányos példa klienst is tartalmazott, amiben egy egyszerű véletlengeneráló függvény mellett az RC4 inicializáló és titkosító eljárásait kellett implementálni, ennek lényegi része a következő:

class CClient(object):
    # ...
    def SendCommandGetAnswer(self, szCommand, dicRequest={}):
        szData = "MODBUS-v3\nCODE=%s\n" % (szCommand)
        for k in dicRequest.keys():
            szData += "%s=%s\n" % (k, dicRequest[k])
        szData += "\n"

        sock.sendall(Rc4Crypt(self._szKey, szData), False)

        # Init rc4 to receive data
        index = [0,0]
        S = range(0,256)
        Rc4InitTable(self._szKey, S)

        # Receive the data
        szData = ""
        while szData.find("\n\n") == -1:
            d = self._sock.recv(1024)
            if d == None or len(d) == 0:
                return None
            for c in d:
                szData += Rc4CryptChar(c, S, index)
                print

        return szData

A feladathoz rendelkezésre állt a a kiszolgáló obfuszkált forráskódja is, ez valahogy így nézett ki:

import re
import utils as u
import random as M1
import string as M2

from cpesticide import CPesticide as P1
from output import Output as P3
from constants import PROTOCOL as P
from password import WAW, MASTER_KEY

z = {}


exec("class v0(object):\n\tdef __init__(v6):\n\t\tv6.v1 = {}\n\t\tv6.v2 = \"\"\n\t\tv6.__v9 = \"\"\n\n\tdef A(v6, v3, szValue):\n\t\tv6.v1[v3] = szValue\n\n\tdef S(v6, v4):\n\t\tv6.v2 = v4\n\n\tdef G(v6):\n\t\treturn v6.v2\n\n\tdef R(v6):\n\t\tdef T(c, l):\n\t\t\treturn c.join([chr(x-0x10) for x in l])\n\t\tdef G(c):\n\t\t\treturn T(c, [ord('V'), ord('W')]) + ''.join(M1.choice(M2.ascii_uppercase + M2.digits + M2.ascii_lowercase) for x in range(13))\n\t\tdef Q(l):\n\t\t\treturn \"A\".join([chr(x+0x30) for x in l])\n\n\t\tv3=\"CODE=%s\\n\" % v6.v2\n\t\tfor k in sorted(v6.v1.keys()):\n\t\t\tv3 += \"%s=%s\\n\" % (k, v6.v1[k])\n\n\t\tif v6.v2 == \"OK\":\n\t\t\tif len(v6.__v9) > 0 and z.has_key(v6.__v9) == True:\n\t\t\t\tf = z[v6.__v9]\n\t\t\telse:\n\t\t\t\tf = G(\"L\")\n\t\t\tv3 += \"%s=%s\\n\" % (chr(70)+Q([28,23]), f)\n\n\t\tv3 += \"\\n\"\n\t\treturn v3\n\n\tdef D(v6, v9):\n\t\tv6.__v9 = v9\n\n\ndef handle(o):\n\tP2 = P1()\n\tI = 51\n\n\tv2 = \"\"\n\twhile v2.find(\"\\n\") == -1:\n\t\td = o.request.recv(1)\n\t\tif d == None or len(d) == 0:\n\t\t\treturn\n\t\tv2 += d\n\n\tv1 = False\n\tif len(v2) < 20:\n\t\tP3.debug(\"Client key is too short, closing connection\")\n\t\tv1 = True\n\n\telse:\n\t\tv3 = MASTER_KEY+v2[0:len(v2)-1]\n\n\tI = I<<1\n\twhile v1 == False:\n\t\tAnswer = v0()\n\n\t\tv6 = [0,0]\n\t\tv7 = range(0,256)\n\t\tu.ri(v3, v7)\n\n\t\tv4 = \"\"\n\t\twhile len(v4) < 10:\n\t\t\td = o.request.recv(len(P)+1-len(v4))\n\t\t\tif d == None or len(d) == 0:\n\t\t\t\treturn\n\t\t\tfor c in d:\n\t\t\t\tv4 += u.rec(c, v7, v6)\n\n\t\tif v4 != P+\"\\n\":\n\t\t\tAnswer.S(\"UNKNOW PROTOCOL\")\n\t\t\tv1 = True\n\t\telse:\n\t\t\tb = False\n\t\t\tv8 = \"\"\n\t\t\tv10 = \"\"\n\t\t\twhile v8.find(\"\\n\\n\") == -1:\n\t\t\t\td = o.request.recv(1024)\n\t\t\t\tif d == None or len(d) == 0:\n\t\t\t\t\treturn\n\t\t\t\tfor c in d:\n\t\t\t\t\tv8 += u.rec(c, v7, v6)\n\n\t\t\tif v8.startswith(\"CODE=CTRL\"):\n\t\t\t\tm = re.search(\"S=([^:]+):([^\\n]+):([^\\n]+)\", v8)\n\t\t\t\tif m != None:\n\t\t\t\t\tif m.group(1) != WAW:\n\t\t\t\t\t\tAnswer.S(\"INVALID PASS\")\n\t\t\t\t\telse:\n\t\t\t\t\t\tz[m.group(2)] = m.group(3)\n\t\t\t\t\t\tAnswer.S(\"OK\")\n\n\t\t\t\tm = re.search(\"G=([^\\n]+):([^\\n]+)\", v8)\n\t\t\t\tif m != None:\n\t\t\t\t\tif m.group(1) != WAW:\n\t\t\t\t\t\tAnswer.S(\"INVALID PASS\")\n\t\t\t\t\telse:\n\t\t\t\t\t\td = P2.d\n\t\t\t\t\t\td[d.keys()[0]] += 1810\n\t\t\t\t\t\tb = True\n\t\t\t\t\t\tAnswer.S(\"OK\")\n\t\t\t\t\t\tv10 = m.group(2)\n\n\t\t\tif len(Answer.G()) == 0 and b == False:\n\t\t\t\tm = re.search(\"FGID=([^\\n]+)\\n\", v8)\n\t\t\t\tif m != None:\n\t\t\t\t\tv10 = m.group(1)\n\n\t\t\t\tif o.HandleData(P2, v8, Answer) == False:\n\t\t\t\t\tAnswer.S(\"ERROR\")\n\t\t\t\telse:\n\t\t\t\t\tb = True\n\n\t\t\tif b == True:\n\t\t\t\tfor v in P2.d.values():\n\t\t\t\t\tif v > 1000:\n\t\t\t\t\t\tAnswer.D(v10)\n\n\t\tdef c(v3, v8):\n\t\t\tx = 0\n\t\t\tv1 = range(256)\n\t\t\tfor i in range(256):\n\t\t\t\tx = (x + v1[i] + ord(v3[i % len(v3)])) % 256\n\t\t\t\tv1[i], v1[x] = v1[x], v1[i]\n\t\t\tx = 0\n\t\t\ty = 0\n\t\t\tout = []\n\t\t\tfor c in v8:\n\t\t\t\tx = (x + 1) % 256\n\t\t\t\ty = (y + v1[x]) % 256\n\t\t\t\tv1[x], v1[y] = v1[y], v1[x]\n\t\t\t\tout.append(chr(ord(c) ^ v1[(v1[x] + v1[y]) % 256]))\n\t\t\treturn ''.join(out)\n\n\t\to.request.sendall(c(v3, Answer.R()))\n")

A whitespace-ek megfelelő átalakítása, illetve a program indítószkriptjének tanulmányozása után kiderült, hogy a fent definiált osztályban lényegében a SocketServer.BaseRequestHandler handle() metódusa került megvalósításra, ez az osztály felelős tehát lényegében minden alkalmazáslogikáért, minden más csak körítés. Az RC4-et azért szeretjük, mert alapesetben néhány sorban implementálható, gyakorlott szemmel pedig az ember könnyen felismeri az algoritmust az importált utils csomag ri(), rec() és rea() metódusaiban. Ezeket felhasználva a README-ben található példakód könnyen kiegészíthető volt; a password.py-ban található MASTER_KEY felhasználásával sikeresen meg lehetett szólítani a tesztkiszolgálónkat.

Kisebb meglepetést okozott, hogy egy egyszerű LIST kód elküldése után a vegyianyag szintek mellett rögtön megkaptunk egy pontot érő flaget is. Mint kiderült, a kiszolgáló minden érvényes (megfejthető) kérés mellé csomagolt egy ilyen ajándékot - szépséghiba, hogy a többi csapat mesterkulcsát nem ismertük...

Itt volt az ideje tehát egy kis kriptós gondolkodásnak! Bár az RC4-el kapcsolatban az utóbbi időben nem sok jót hallani, Leventéék felvilágosítottak, hogy a kulcsfolyam megfelelő mértékű megjóslására még a kulcs részleges ismerete esetén sincs reális esély, érdemes viszont végiggondolni a folyamtitkosítók általános konstrukcióját:
 a kriptoszöveg a nyíltszöveg és valamilyen, valódi véletlengenerátort közelíteni próbáló kulcsfolyam-generátor bitenkénti XOR összegével áll elő.

Az a példa kliens kódjából egyértelműen látszik, hogy a válaszok megfejtéséhez a kliens mindig újrainicializálja az RC4 kulcsfolyam generátorát, feltételezhető tehát, hogy szerver is ugyanígy tesz, csak a helyes mesterkulcs felhasználásával. Így minden üzenet azonos kucsfolyammal lesz össze XOR-olva, a nyílt szöveg ismeretében pedig a kulcsfolyam megszerezhető és újrahasznosítható. Ennek a ténynek a kihasználásához az alábbi kódrészletre kell felfigyelnünk a handle() metódusban (Néhány változót átneveztem):

        while len(v4) < 10:
            d = o.request.recv(len(P)+1-len(v4))
            if d == None or len(d) == 0:
                return
            for c in d:
                v4 += u.rec(c, range256, v6)

        if v4 != P+"\n":
            Answer.S("UNKNOW PROTOCOL")
            problem = True

Amint az a hibaüzenetből is látszik, P az elvárt protokollazonosító ("MODBUS-v3") - ha a fogadott üzenet nem ezzel a sorral kezdődik, a kiszolgáló visszautasítja a lekérdezést és nem ad flaget sem. Ezzel egyrészt ellenőrizhető, hogy a kliens helyes mesterkulcsot használ-e, másrészt viszont utat enged a kulcsfolyam megismeréséhez: az Answer.S() a session+mesterkulcs felhasználásával titkosítva küldi vissza a paraméter titkosított változatát a kliensnek, ha nem tudja értelmezni az üzenetünket. A kulcsfolyam ezek után például a következő módon kapható meg:

orig="CODE=UNKNOW PROTOCOL\n\n"
sock.send(binascii.unhexlify("00...000a")+("\x31"*21+"\x00")) # session kulcs+szemét
oracle=sock.recv(1024)
keystream=xor(orig,oracle)

A keystream-et az után tetszőleges üzenettel XOR-olva a kiszolgáló számára értelmes üzenetet állíthatunk elő:

lst=bytearray("MODBUS-v3\nCODE=LIST\n\n")
sock.send(binascii.unhexlify("00...000a")+xor(lst,keystream)) # fontos, hogy a session kulcs azonos legyen!
ans=sock.recv(1024)
print xor(ans,keystream)

Ezzel azonban csak a küszöbön tettük be a lábunkat: a kulcsfolyam ismert része ugyanis túl rövid ahoz, hogy a válaszban a Flag értékét is meg tudjuk fejteni (vagy - beleélve magunkat a játékba - komplexebb parancsok kiadásával befolyásoljuk a keverési folyamatot).

A megoldáshoz több lépésben kell megismételnünk a támadást, egyre hosszabb és hosszabb kexstream-ekre szert téve. A végső megoldás valahogy így épül fel:

  1. Valamilyen szemetet elküldve titkosított UNKNOW PROTOCOL üzenetet kapunk, melyből visszaállítjuk a keystream első 17 byte-ját.
  2. A 17 byte-os keystream-et felhasználva INFO üzenetet küldünk, melyre a kiszolgáló verzióinformációval válaszol. Mivel a válasz formátuma, illetve a lehetséges verzióértékek ismertek, visszaállíthatjuk a kulcsfolyam további szakaszát.
  3. A hosszabb kulcsfolyam alkalmas egy INC üzenet kiadására, mellyel növelhetjük az egyik vegyianyag koncentrációját, a válaszban pedig csak a Flag-et kapjuk vissza olyan pozícióban, ameddig a kulcsfolyamunk még elér.

A probléma kihasználásához kulcsfontosságú, hogy a támadó mindig azonos session kulcsot használjon, az adatfolyam további része viszont titkosított, a támadás detektálásához tehát az ismételt session kulcsokat (a szolgáltatáshoz érkező üzenetek első 20 byte-ját) érdemes figyelni. Legalábbis az én elgondolásom szerint - a rendszer erre a megoldásra illetve az exploitra sem adott végül pontot, ami fájdalom, de a megoldás szerintem jól demonstrálja a folyamtitkosítók helytelen használatából eredő kockázatokat.

Az iCTF-en végül a 23. helyen végeztünk nagyjából 100 csapatból, ami csak amiatt kellemetlen, mert a verseny utolsó 3 órájában a pontjaink javát jelentő netflow-ból (amiben a támadásokat kellett detektálni) egyszerűen nem osztott nekünk a rendszer, így kb. 10 helyet csúsztunk vissza :P

Ezzel együtt én nagyon élveztem a játékra szánt órákat, a feladatok sokkal jobban kidolgozottak voltak, mint előző évben, és úgy érzem, hogy a csapat is remekül dolgozott - köszi srácok!

Az iCTF pályákat a nálam lévő teszt/exploit kódokkal mindjárt feltöltöm Gitoriousra (akinél van még kód, az küldjön merge requestet!) - lehet gyakorolni, szinte biztos, hogy a Pesticides-ben is van még hiba! A Defcon CTF-en találkozunk!

Címkék: kriptográfia ctf ictf pesticides

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

domi007 2013.03.30. 22:38:52

Jó öreg WEP-fragmentation attack, egyik személyes kedvencem :)

Joe80 2013.04.29. 10:49:15

Ez ugyanaz mint a CTF mitől lett iCTF?

Joe80 2013.04.29. 10:50:38

Na jó az lejött hogy nem ugyanaz mert nem defcon-os kár h megtévesztő a név.