W dniach od 18 do 22 czerwca 2018 odbyły się eliminacje do European Cyber Security Challenge. Konkurs został podzielony na 2 grupy wiekowe : Juniorzy (14-20 lat) oraz Seniorzy (21 - 25 lat). Ze względu na mój wiek rozwiązywałem zadania z pierwszej kategorii wiekowej. Zadanie które opiszę w tym poście to Cobra Ransomware za 300 pkt (RE) - link.
Do dyspozycji otrzymujemy zaszyfrowany plik oraz binarkę ransomware.
Zwróćmy uwagę na ikonę nawet przed uruchomieniem pliku. Od razu skojarzyło mi się to z jakimś bundlerem skryptów Pythona. Celem bundlerów jest utworzenie samowystarczalnego pliku wykonywalnego który zawiera nasz skrypt oraz całe środowisko uruchomieniowe. Kluczowe z pythonowych bundlerów to : Py2exe, cx_freeze oraz PyInstaller. Ikona naszego pliku wskazuje jakby był to plik spakowany przez ten ostatni.
Czas uruchomić naszą próbkę ransomware. Pamiętajmy aby prawdziwy malware testować tylko w izolowanym środowisku np. poprzez maszynę wirtualną.
Program podaje zawsze ten sam UserID oraz oczekuje na prawidłowy klucz którzy rzekomo mielibyśmy wpisać w celu odzyskania naszych plików. Wnioskując że mamy do czynienia z PyInstallerem który pakuje całość UPX’em a sam proces wykonywania kodu Pythona na poziomie kodu maszynowego może być zbyt skomplikowany więc odpuszczamy z korzystania z debuggera (straciłem na to 7 dni :) ). Hook’ując funkcje systemowe programem API Monitor trafimy na ścieżkę zawierającą pliki zapewniające środowisko uruchomieniowe (wypakowane z samego pliku cobra.exe).
Zawartość wypakowanego pliku:
Pliki exe tworzone przez PyInstaller mają specyficzną strukturę którą możemy poznać poprzez czytanie kodu źródłowego pyinstaller. Skrótowe przedstawienie tej struktury przedstawia obrazek:
Wszystkie pliki oraz obiekty code (wg. implementacji CPython jest to wykonywalny kod bajtowy pythona) są niejako doklejone do pliku .exe. W źródłach PyInstallera cała ta struktura jest nazwana CArchive.
Analizę tego archiwum podobnie jak plików .zip zaczynamy od końca pliku, gdzie znajduje się CArchive Cookie zawierająca m.in. spis zawartości archiwum. Jego strukturę możemy poznać czytając /bootloader/src/pyi_archive.h.
Każdy wpis TOC możemy traktować jako nagłówek pliku który mamy „wyeksportować” z pliku. Należy dodać że każde pole jest kodowane w big endian. Dla przykładu objaśnienie nagłówka dla pliku python27.dll:
pole typcd nagłówka TOC może przyjmować następujące wartości :
“m” - moduł języka Python (plik .pyc)
“M” - pakiet języka Python
”s” - skrypt w postaci obiektu code zserializowany (marshal)
“b” - dane binarne (np. .dll .pyd)
“z” - ZlibArchive
“Z” - archiwum Zip
“d” - ścieżka do archiwum zawierające potrzebne zależności (dependencies)
“a” - archiwum CArchive
“x” - dane
“o” - opcje konfiguracyjne
Naturalnym planem działania w celu rozwiązania tego zadania jest rozpakowanie CArchive zawartego w cobra.exe. Plan działania jest prosty :
Zparsowanie CArchiveCookie (znajduje się ono na samym końcu pliku)
Parsowanie każdego TOC dopóki nie przeczytamy TOClen bajtów
Eksportowanie każdego pliku, jeżeli w nagłówku flaga cflag jest ustawiono musimy użyć zlib’a aby wypakować dane.
W archiwum zlib out00-PYZ.pyz zawarte są obiekty code modułów pythona. Aby umożliwić ich późniejszą dekompilacje dostępnymi programami doklejamy do każdego obiektu code nagłówek pliku .pyc (4 bajty wartość magiczna zależna od wersji języka python + 4 bajty timestamp) oraz zapisujemy wszystkie takie pliki .pyc na dysk, w celu dalszej dekompilacji i analizy.
Napisany przeze mnie skrypt pythona lepiej pomoże nam zrozumieć jak ugryźć całą tą strukturę plików. Skrypt pisany typowo pod ctf 😂
Wypakowywując wszystkie pliki wraz z out00-PYZ.pyz otrzymujemy:
Interesujący jest dla nas plik entry, który jest głównym obiektem code naszego programu.
Ja z początku otworzyłem go hexedytorem oraz zobaczyłem „import cobra”, więc zajrzałem do wyeksportowanego ZlibArchive w celu zobaczenia czy takowy istnieje. Gdy go znalazłem skorzystałem z narzędzia uncompyle6 aby przetworzyć ten plik na zwykły kod pythona. W tym momencie naszym oczom ukazuje się zobfuskowany kod pythona (prawdopodobnie przez pyobfuscate)
Który przekonwertowałem do nieco bardziej czytelnej postaci
Nazwana przeze mnie funkcja „crypt” zwraca zawsze taki sam klucz, który następnie jest porównywany z tym co wpisaliśmy. Zwyczajnie wypisujemy klucz poprzez linijkę „print ‚’.join(arr)”.
Uruchamiamy cobra.exe, wpisujemy nasz nowo poznany klucz oraz otrzymujemy odszyfrowany plik .pdf z flagą.
No i super ! Rozwiązaliśmy zadanie za 300 pkt ! Przechytrzyliśmy ransomware :)