Writeup: FlareOn 2020: 006 - codeit

Task description

1. TLDR

codeit graph

2. Dane wejściowe

Plik z zadaniem znajduje się tutaj. Hasło: flare.

Przedmiot zadania stanowi plik codeit.exe stanowiący skompilowany skrypt. Z treści zadania wiadomo również, że flaga została ukryta jako kod QR.

3. Inspekcja pliku codeit.exe

Zweryfikowałem plik narzędziem file:

$ file codeit.exe                                                                                           
codeit.exe: PE32 executable (GUI) Intel 80386, for MS Windows, UPX compressed

Plik został spakowany popularnym narzędziem UPX.

Uruchomienie programu powodowało pojawienie się okna aplikacji:

Main window

Aplikacja pozwalała na zakodowanie podanego tekstu do kodu QR:

QR code 74wny0wl

4. Odpakowanie pliku

Korzystając z narzędzia UPX, odpakowałem plik:

$ upx -d -o codeit_unpacked.exe codeit.exe                                                                 
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX git-d7ba31+ Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
    963584 <-    481280   49.95%    win32/pe     codeit_unpacked.exe

Unpacked 1 file.

Zweryfikowałem odpakowany plik narzędziem file:

file codeit_unpacked.exe                                                                                 
codeit_unpacked.exe: PE32 executable (GUI) Intel 80386, for MS Windows

Następnie przeanalizowałem ciągi znaków występujące w pliku wykonywalnym. Wynik analizy jednoznacznie wskazał na fakt, że program został utworzony przez kompilację skryptu AutoIt:

$ strings codeit_unpacked.exe| grep -i autoit
This is a third-party compiled AutoIt script.

5. Dekompilacja

Zdekompilowałem plik codeit.exe do skryptu AutoIt wykorzystując do tego narzędzie Exe2Aut. W wyniku dekompilacji zostały odzyskane pliki:

$ file qr_encoder.dll
qr_encoder.dll: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows
file sprite.bmp
sprite.bmp: PC bitmap, Windows 3.x format, 299 x 299 x 24, image size 269102, resolution 3779 x 3779 px/m, cbSize 269156, bits offset 54

Sprite

Otworzyłem plik sprite.bmp w hexedytorze. Po 54-bajtowym nagłówku bitmapy, zidentyfikowałem powtarzające sie bajty 0xFF oraz 0xFE. Zmiana najmniej znaczącego bitu wskazywała na ukrycie informacji:

Sprite Hex

6. Deobfuskacja

Skrypt AutoIt zdeobfuskowałem wykonując napisany do tego celu skrypt. Pozostało wykonać jeszcze ręczną analizę: ponazywać funkcje i zmienne oraz zrozumieć kod. Z powodu uciążliwości tego procesu, w części 7 przedstawiłem analizę istotnej części ostatecznej wersji skryptu.

7. Analiza skryptu

Flaga została zapisana w postaci kodu QR, zaszyfrowana i zahardkodowana w kodzie funkcji areyzotafnf().

Sposób działania tej funkcji prezentuje poniższy diagram:

Decrypt function

Odczytana nazwa NetBIOS komputera była przekształcana przez funkcje mieszającą. Następnie zostawał obliczany skrót SHA-256 z wyniku tego przekształcenia. Konkatenacja stałego nagłówka i obliczonego skrótu stanowiła klucz zgodny CryptoApi, który służył do odszyfrowania kodu QR.

Zatem w celu odczytania flagi, trzeba było odgadnąć nazwę maszyny, z której wyznaczany był klucz.

Jak pozyskać oczekiwaną nazwę maszyny? Słabością całego rozwiązania okazała się funkcja MixComputerName():

Func MixComputerName(ByRef $computer_name_raw)
	Local $randomized_sprite_bmp = InstallSelectedFile(14)
	Local $hFile_sprite = CreateFileForReading($randomized_sprite_bmp)
	If $hFile_sprite <> -1 Then
		Local $hFile_sprite_size = GetFileSize($hFile_sprite)
		If $hFile_sprite_size <> -1 AND DllStructGetSize($computer_name_raw) < $hFile_sprite_size - 54 Then
			Local $in_memory_sprite_bmp = DllStructCreate("struct;byte[" & $hFile_sprite_size & "];endstruct")
			Local $is_success = ReadFile($hFile_sprite, $in_memory_sprite_bmp)
			If $is_success <> -1 Then
				Local $key = DllStructCreate("struct;byte[54];byte[" & $hFile_sprite_size - 54 & "];endstruct", DllStructGetPtr($in_memory_sprite_bmp))
				Local $sprite_offset = 1
				Local $mixed_computer_name = ""
				For $index = 1 To DllStructGetSize($computer_name_raw)
					Local $character = Number(DllStructGetData($computer_name_raw, 1, $index))
					For $shift_counter = 6 To 0 Step -1
						$character += BitShift(BitAND(Number(DllStructGetData($key, 2, $sprite_offset)), 1), -1 * $shift_counter)
						$sprite_offset += 1
					Next
					$mixed_computer_name &= Chr(BitShift($character, 1) + BitShift(BitAND($character, 1), -7))
				Next
				DllStructSetData($computer_name_raw, 1, $mixed_computer_name)
			EndIf
		EndIf
		CloseHandle($hFile_sprite)
	EndIf
	DeleteFileA($randomized_sprite_bmp)
EndFunc

Mieszanie nazwy komputera odbywało się poprzez dodawanie do każdego znaku kolejnego znaku odczytanego 7 z najmniej znaczących bitów pochodzących z pliku sprite.bmp, a następnie rotację o jeden bit w prawo.

Należało więc postawić pytanie o zawartość ciągu zbudowanego 7-bitowych znaków. Ostatni bajt 0xFE był 90 w sekwencji, należało więc odczytać ⌈90/7⌉ bajtów. W tym celu można było zmodyfikować skrypt lub napisać kawałek kodu np. w pythonie:

#! /usr/bin/python3

covert_message = [0xFF, 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, \
        0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFE, \
        0xFE, 0xFF, 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, 0xFF, 0xFE, \
        0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFE, 0xFF, \
        0xFF, 0xFE, 0xFE, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFE, 0xFE, 0xFE, \
        0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFF, \
        0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, \
        0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, \
        0xFF, 0xFE, 0xFE, 0xFF]

def main():
    message = "";
    character = 0
    for i in range(len(covert_message)):
        character |= ((covert_message[i] & 0x01) << (6 - (i % 7)))
        if i % 7 == 6:
            message += chr(character)
            character = 0
    print(message)

if __name__ == "__main__":
    main()

Uruchomienie skryptu pozwoliło na odczytanie ciągu znaków, który stanowił oczekiwaną nazwę komputera:

aut01tfan1999

8. Odczytanie flagi

Wystarczyło więc zmodyfikować skrypt tak, żeby podstawiana była zawsze oczekiwana nazwa komputera:

NetBIOS name

oraz uruchomić skrypt AutoIt:

$ "C:\Program Files (x86)\AutoIt3\AutoIt3.exe" "codeit_debug.au3"

Po kliknięciu na przycisk Can haz code? wygenerowany został kod QR:

Flag QR Code

Po skorzystaniu z aparatu telefonu lub dowolnego innego narzędzia (np. CyberChef) można było odczytać zdekodowaną flagę:

L00ks_L1k3_Y0u_D1dnt_Run_Aut0_Tim3_0n_Th1s_0ne!@flare-on.com