CertCTF 2025

Hade nöjet att delta i CertCTF som anordnades som en del av en kandidatuppsats inom digital forensik pĂ„ Högskolan i Halmstad i samarbete med MSB och CERT-SE. För en som inte tidigare “tĂ€vlat” i CTF:er, utan bara tuggat igenom diverse Hack The Box, TryHackMe och OverTheWire sĂ„ var CertCTF otrligt roligt och lĂ€rorikt.

Passade Ă€ven pĂ„ att dokumentera de flaggor jag lyckades samla pĂ„ mig. Nedan följer en liten braindump jag knĂ„pade ihop baserat pĂ„ mina anteckningar. Jag fick tyvĂ€rr inte ihop alla flaggor jag hade hoppats pĂ„, men jag Ă€r nöjd med att ha placerat mig pĂ„ 11:e plats av 61 deltagare. Inte illa för att vara första gĂ„ngen! 😉

NÀr CTF:en startade fick man tillgÄng till en zip-fil innehÄllandes:

  • NĂ€tverksdump ifrĂ„n angreppet
  • Windows-loggar ifrĂ„n en server som var utsatt
  • Minnesdump frĂ„n den utsatta servern

Inkluderat var Àven scenariot för CTF:en som finns att lÀsa nedan.

Intro

Gentle Dentals IT-ansvarig, Micke, har precis avslutat sin 45 minuter lÄnga kafferast dÀr han och chefen för tandvÄrdskliniken flitigt diskuterat dyra IT-sÀkerhetslösningar som chefen bedömer onödiga. NÀr Micke sÀtter sig vid skrivbordet fÄr han höra av tandlÀkaren Per att skrivaren beter sig konstigt.

NÀr Micke undersöker skrivaren fÄr han syn pÄ en mini-dator som sitter inkopplad pÄ ethernet-porten som skrivaren brukar anvÀnda. Micke fÄr panik och inser att nÄgot fruktansvÀrt kan ha hÀnt. Han ringer dig som kan det hÀr med IT-attacker och ber om hjÀlp dÄ Gentle Dental har vissa viktiga, högt uppsatta kunder.

Du fÄr ut all nÀtverkstrafik som skedde under Mickes kafferast pÄ företagets nÀtverk tillsammans med hÀndelseloggfiler och en minnesdump frÄn deras fil-server. Ditt uppdrag Àr att utreda IT-attacken som skett för att ta reda pÄ vad som har hÀnt.

Angriparens IPv4-address

Vilken IPv4-adress anvÀnde angriparen sig av initialt i attacken?

Format för svaret: 111.222.333.444

Konstaterade att det skickades otroligt mycket ARP-förfrÄgningar ifrÄn en specifik address, det ser helt klart ut som att nÄgon skannar nÀtet efter svarande hosts. Flagga 1 Flagga: 192.168.177.141

DomÀnkontrollantens IPv4-adress

Vilken IPv4-adress hade domÀnkontrollanten?

Format för svaret: 111.222.333.444

DomÀnkontrollanter pratar ofta LDAP, en sökning i nÀtverksdumpen pÄ ldap sÄ kan man konstatera att 192.168.177.129 snackar LDAP pÄ port 389 och troligtvis Àr domÀnkontrollanten

Flagga 2 Flagga: 192.168.177.129

FTP-Serverns IPv4-adress

Vilken IPv4-adress hade FTP-servern?

Format för svaret: 111.222.333.444

FTP anvÀnder oftast port 21. Söker man i nÀtverksdumpen efter trafik pÄ port 21 sÄ kan man se att angriparens server försöker prata mot flera IP adresser, men att det bara Àr 192.168.177.155 som svarar.

Flagga 3 Flagga: 192.168.177.155

Angriparens hostname

Vad var angriparens hostname?

Lite klurigare men Ă€ndĂ„ ganska lĂ€tt. NĂ€r man kollar pĂ„ nĂ€tverksdumpen och filtrerar pĂ„ angriparens server sĂ„ kan man se att den gjorde den DHCP request. I DHCP requesten finns under Option 12 hostens hostname rakt upp och ned 👌

Flagga 4 Flagga: Kali

Tidszon

Vilken lokal tid (svensk tid) anlÀnde sista paketet i inspelningen av nÀtverkstrafiken? Avrunda nerÄt till hel sekund!

Format för svaret (HH:MM:SS): 01:23:45

HÀr var det bara att sÀtta wireshark till att visa lokal tid och scrolla lÀngst ned i nÀtverksdumpen.

Flagga 5 Flagga: 09:42:01

Windows Defender

Vilken MpPreference parameter Àndrade angriparen för Windows Defender Antivirus?

Format för svaret: ParameterNamnet

HĂ€r fick man ta en titt i Windows defender loggarna.

Flagga 6

Notera att registervÀrdet

HKLM\SOFTWARE\Microsoft\Windows Defender\Exclusions

Àndras pÄ nÄgot sÀtt. Detta Àr samma sak som att Àndra i

Get-MpPreference | Select-Object ExclusionPath

Flaggan blir dÀrmed: ExclusionPath

Obehörig inloggning

Angriparen lyckades logga in pÄ en av datorerna pÄ nÄgot sÀtt. Vilket Logon ID tillhör den obehöriga inloggningen?

Format för svaret: 0x123AB

Tittar man i Security loggarna efter events med Titta i Security loggarna efter angriparens lokala IP 192.168.155.141 sÄ hittar man denna lyckade inloggning, jackpot!

Flagga 7 Flagga: 0xFFCAB

Lösenord

Vilket lösenord anvÀnde angriparen för att logga in i utmaningen Obehörig inloggning

Format för svaret: Password123

Baserat pÄ loggen i Obehörig inloggning vet vi tidpunkten för inloggning samt att autentiseringen gjordes via NTLM V2. NTLM V2 Àr vida kÀnt att det Àr ett osÀkert sÄ detta borde man kunna knÀcka. Vi har Àven tillgÄng till nÀtverkstrafiken för autentiseringen.

För o knÀcka NTLMV2 behöver man veta lite saker: User, Domain, Challenge, NTLMV2Response samt HMAC-MD5.

Challengen kan vi hitta i Session Setup Responsen för NTLM autentiseringen NTLM Challenge Challenge: NTLM Server Challenge: 19c218f2f9a912c1

HMAC-MD5 och NTLMv2Response hittar vi i sjÀlva autentiseringen NTLMV2Response & HMAC-MD5 NTProofStr Àr samma som HMAC-MD5

HMAC-MD5: 7db9780888f4832bd162cf679cbffc0e

Scrollar man ner lite i samma packet sÄ hittar vi Àven User och Domain UserDomain

NTLMv2Response: 010100000000000080bf6de99591db01f7bccb719a3b1d400000000002001800470045004e0054004c004500440045004e00540041004c0001001400460054005000530045005200560049004300450004002400670065006e0074006c006500640065006e00740061006c002e006c006f00630061006c0003003a0046005400500053006500720076006900630065002e00670065006e0074006c006500640065006e00740061006c002e006c006f00630061006c00070008001829aae99591db010000000000000000

Detta kan man sedan lÀgga ihop till ett format som hashcat kan knÀcka

FTPService::gentledental.local:19c218f2f9a912c1:7db9780888f4832bd162cf679cbffc0e:010100000000000080bf6de99591db01f7bccb719a3b1d400000000002001800470045004e0054004c004500440045004e00540041004c0001001400460054005000530045005200560049004300450004002400670065006e0074006c006500640065006e00740061006c002e006c006f00630061006c0003003a0046005400500053006500720076006900630065002e00670065006e0074006c006500640065006e00740061006c002e006c006f00630061006c00070008001829aae99591db010000000000000000

AnvÀnd valfri wordlist, t.ex rockyou

Kör igÄng katten med

hashcat.exe -a0 -m5600 hashcatcrack.txt rockyou.txt
  • a0 specificerar straight mode, dvs applicera en wordlist.

  • m5600 specificerar hash typen, i detta fall NetNTLMv2 hashes.

  • hascatcrack.txt Ă€r strĂ€ngen vi satte ihop tidigare.

  • rockyou.txt Ă€r wordlisten vi anvĂ€nder.

Sverige, vi har ett resultat!

Session..........: hashcat
Status...........: Cracked

FTPSERVICE::gentledental.local:19c218f2f9a912c1:7db9780888f4832bd162cf679cbffc0e:010100000000000080bf6de99591db01f7bccb719a3b1d400000000002001800470045004e0054004c004500440045004e00540041004c0001001400460054005000530045005200560049004300450004002400670065006e0074006c006500640065006e00740061006c002e006c006f00630061006c0003003a0046005400500053006500720076006900630065002e00670065006e0074006c006500640065006e00740061006c002e006c006f00630061006c00070008001829aae99591db010000000000000000:DentalSurgery528

Lösenordet finns i slutet av strÀngen.

Flagga: DentalSurgery528

Angriparens server

Vilken IPv4-adress hade angriparens egna server?

Format för svaret: 111.222.333.444

I Powershell Operational loggarna kan man se att powershellscriptet som körs av angriparen b.la modiferar DNS Doh

Kort utklipp ur loggen:

$script:ClassName = 'ROOT/StandardCimv2/MSFT_DNSClientDohServerAddress'

Detta i kombination med den trafik som kan observeras gÄ ut ifrÄn FTP servern till en okÀnd IP, men som klassificeras som DNS trafik, fick mig att tÀnka att data exfiltreras via DNS. DNS-anropen ser Àven ytterst misstÀnkta ut, varför skulle nÄgot prata med Facebook domÀner helt random?

Flagga 9

Drog en kvalificerad gissning att 10.245.122.37 Àr angriparens server och det visade sig stÀmma.

Flagga: 10.245.122.37

Ransomware

Utöver den “vanliga” utmaningen fanns det Ă€ven med en delutmaning gĂ€llande ransomware. Scenariot kan man lĂ€sa nedan.

Din kollega som har varit ansvarig för diskforensiken hittade en exekverbar fil och tre andra textfiler. En fil med kÀnslig data verkar nu innehÄlla nÄgon form av krypterad data. Kollegan Àr rÀtt sÀker pÄ att den kÀnsliga filen gÄr att dekryptera och kommer till dig och ber om hjÀlp dÄ du Àr expert inom omrÄden sÄsom reverse engineering och kryptering.

Vad Àr nyckeln för att dekryptera filen?

Tillsammans med introt fick man Àven tillgÄng till filerna:

  • ransom_note.txt
  • pubkey.txt
  • sensative_data.encrypted
  • enc.exe

enc.exe anvÀndes för att kryptera filen.

pubkey.txt innehöll en publik nyckel: 506395958

sensative_data.encrypted innehöll krypterad data i formatet

(540488803, 187826906) (512275051, 36345508)...

Om man öppnar upp enc.exe i Ghidra och pillar runt lite sÄ kan man hitta funktionen nedan som gissningsvis anvÀnds för att generera den public keyn.

gen_pub_k

Efter mycket googlande, och lite AI-chattande, fick jag följande svar som kÀndes rimligt:

void gen_pub_k(uint param_1)
{
  mod_exp(2, param_1, 0x40000017);
  return;
}

mod_exp(base, exponent, modulus): This function is likely performing modular exponentiation:

result = (2^{\text{param_1}}) \mod 0x40000017 The value 0x40000017 in decimal is 1073741847, which is close to 2^30 , possibly chosen for cryptographic reasons.

param_1 is the exponent (probably a secret key or part of the key derivation process).

Bara ett par klick bort i Ghidra sÄ hittar man mod_exp funktionen.

mod_exp

Exakt hur denna fungerar Ă€r för mig en gĂ„ta, man skulle kanske lĂ€st lite krypteringskurser pĂ„ högskolan… Men ChatGPT hade ett svar!

ChatGPT

Med det sagt hjÀlpte den ocksÄ till att generera ett Python-script för att rÀkna ut den privata nyckeln, om nÄgot fick man modifiera det lite sÄ att det skulle fungera..

import math

def brute_force_discrete_log(base, modulus, public_key, max_attempts=2**24):
    """Brute-force method to find x in base^x ≡ public_key (mod modulus)"""
    value = 1  # Start with base^0 = 1
    for x in range(max_attempts):
        if value == public_key:
            return x  # Found private key!
        value = (value * base) % modulus  # Compute next power
    return None  # Not found within the attempt limit

def baby_step_giant_step(base, modulus, public_key):
    """Baby-step Giant-step algorithm for discrete logarithm"""
    m = math.isqrt(modulus) + 1  # Step size
    table = {}

    # Baby-step: Compute base^j % modulus for j in [0, m]
    value = 1
    for j in range(m):
        table[value] = j
        value = (value * base) % modulus

    # Compute base^(-m) mod modulus (modular inverse of base^m)
    base_inv_m = mod_inverse(pow(base, m, modulus), modulus)

    # Giant-step: Compute public_key * base^(-i*m) % modulus
    value = public_key
    for i in range(m):
        if value in table:
            return i * m + table[value]  # Solution found
        value = (value * base_inv_m) % modulus

    return None  # No solution found

# Given values
base = 2
modulus = 1073741847  # 0x40000017
public_key = 506395958  # Example public key (replace with actual value)

# Try brute-force first (for small keys)
private_key = brute_force_discrete_log(base, modulus, public_key)
if private_key is None:
    print("Brute-force failed, trying Baby-Step Giant-Step...")
    private_key = baby_step_giant_step(base, modulus, public_key)

if private_key is not None:
    print(f"Recovered private key: {private_key}")
else:
    print("Failed to recover the private key.")

Kör man detta sÄ resulterar det i att man fÄr ut den privata nycklen som anvÀndes vid krypteringen, 177370085.

I Ghidra kan man kolla vidare och hitta följande funktion, som anvÀnds för att kryptera datan.

void eg_enc(int param_1,uint param_2,uint *param_3,uint *param_4)

{
  int iVar1;
  uint uVar2;
  uint uVar3;
  
  iVar1 = rand();
  uVar3 = iVar1 % 0x40000016 + 1;
  uVar2 = mod_exp(2,uVar3,0x40000017);
  *param_3 = uVar2;
  uVar3 = mod_exp(param_2,uVar3,0x40000017);
  *param_4 = (uVar3 * param_1) % 0x40000017;
  return;
}

Men Ànnu lite AI hjÀlp kan man fÄ ut följande lösning:

EncryptionSolution

Även hĂ€r fick man lite hjĂ€lp med att generera ett script som avkrypterar filerna med hjĂ€lp av private keyn.

from sympy import mod_inverse

def elgamal_decrypt(private_key, p, c1, c2):
    """Decrypts a single ElGamal encrypted pair (c1, c2)."""
    s = pow(c1, private_key, p)  # Compute shared secret: s = c1^private_key mod p
    s_inv = mod_inverse(s, p)  # Compute modular inverse of s
    M = (c2 * s_inv) % p  # Recover plaintext
    return M

# Given values
p = 1073741847  # Prime modulus
private_key = 177370085  # Recovered private key

# Encrypted data from file
encrypted_pairs = [
    (540488803, 187826906), (512275051, 37345508), (231599851, 823437630),
    (631399654, 628252041), (1035050165, 1006835836), (896417195, 206453254),
    (14141576, 263001145), (204987116, 186642158), (606374554, 598224082),
    (547306682, 128791588), (587262377, 531917810), (982294489, 538345953),
    (471862643, 967908048), (634400714, 763656344), (375171454, 544183238),
    (1055094817, 793021731), (39117044, 951544349), (361114958, 4360479),
    (327139844, 501552997), (467622466, 316485080), (36903752, 556784675),
    (44658571, 501318067), (443001524, 1031119903), (555206443, 620324572),
    (499990172, 366058919)
]

# Decrypt all pairs
decrypted_messages = [elgamal_decrypt(private_key, p, c1, c2) for c1, c2 in encrypted_pairs]

print("Decrypted messages:", decrypted_messages)

Kör man detta resluterar det i gÀng med ASCII vÀrden:

Decrypted messages: [mpz(80), mpz(101), mpz(108), mpz(108), mpz(101), mpz(95), mpz(83), mpz(118), mpz(97), mpz(110), mpz(115), mpz(108), mpz(111), mpz(115), mpz(95), mpz(72), mpz(97), mpz(114), mpz(95), mpz(83), mpz(118), mpz(97), mpz(110), mpz(115), mpz(10)]

Som i sin tur kan avkodas till text:

decrypted_ascii = [80, 101, 108, 108, 101, 95, 83, 118, 97, 110, 115, 108, 111, 115, 95, 72, 97, 114, 95, 83, 118, 97, 110, 115, 10]
decrypted_text = "".join(chr(c) for c in decrypted_ascii)
print(decrypted_text)

Resultat?

Flagga 1: 177370085

Flagga 2: Pelle_Svanslos_Har_Svans

Slut pÄ det roliga

Kan absolut inte sÀga att jag förstÄr allting som görs, speciellt inte den bakomliggaande mattematiken, men nÄgonting nytt har man ÀndÄ lÀrt sig. Att decompila binÀrfiler och hitta funktioner i den var ÀndÄ riktigt kul! Utredningsflaggorna kÀnde jag mig hemma pÄ, medan ransomware var betydligt klurigare.

Man kan inte slÀnga under stolen att AI Àr ett anvÀndbart verktyg, speciellt nÀr man sjÀlv har ett litet hum om vad det Àr man vill fÄ ut. Utan ChatGPT och Deepseeks utvecklande resonemang hade jag lÀtt fastnat pÄ vissa bitar. Handlar helt enkelt om att anvÀnda dem pÄ rÀtt sÀtt, man fÄr se dem som en förlÀngning av ens egna förmÄga.

Hade jag haft lite mer tid under helgen Àr jag sÀker pÄ att hade kunnat ta nÄgra av de andra flaggorna, speciellt de som hade med autentiseringen att göra.

En full writeup av CTF:ens alla flaggor finns pÄ organisatörernas github: https://github.com/heltseriost/CertCTF2025-Writeup

LÀste igenom samtliga och blir lite frustrerad nÀr man inser att man var sÄ nÀra att lösa en flagga, men inte riktigt nÄdde hela vÀgen. En riktig miss jag gjorde var att inte titta pÄ minnesdumpen, fastnade totalt i event loggar och decompiling. Men det Àr en del av att fortsÀtta lÀra sig.

Till nÀsta CTF, som inte Àr allt för lÄngt borta, tar jag med mig detta. Att inte fastna allt för djupt i detaljerna, likasÄ försöka bilda mig en bild av hela hÀndelseförloppet och inte bara varje individuell flagga.

Allt som allt en rolig CTF, speciellt för en nybörjare. Ransomware delen var svÄr, men ÀndÄ görbar med hjÀlp av google och lite AI. Helt klart nöjd med min 11:e plats.

Scoreboard

Avslutningsvis…

https://www.youtube.com/watch?v=bLlj_GeKniA

Ses pĂ„ lan, eller kanske pĂ„ UNDUTMANINGEN 2025 đŸ€