Podczas turnieju pingCTF 2023 zorganizowanego przez Koło Naukowe PING z Politechniki Gdańskiej, rozwiązałem wyzwanie “Hangover” plik.

Write-up

Analizę programu rozpoczynamy od sprawdzenia sekcji .rodata, gdzie znajdujemy dwa interesujące ciągi znaków: “wrong” oraz “good”.

Dane w sekcji .rodata

Zostały one użyte w dwóch funkcjach. Plik wykonywalny nie zawiera symboli, więc nazwy funkcji trzeba nadać ręcznie(skrót klawiszowy “N”).

Funkcje "incorrect_flag" i "correct_flag"

Funkcję main rozpoczyna wywołanie funkcji signal (man 2 signal). Rejestrowana jest funkcja, która obsługuje sygnał 5 - SIGTRAP.

Zdekompilowana funkcja main

Funkcja sigtrap_handler(ponownie nadajemy funkcji nazwę), ustawia wartości pięciu zmiennych globanych.

Funkcja obsługująca sygnał SIGTRAP

Wracamy do funkcji main. Na końcu znajduje się breakpoint, czyli instrukcja używana przez debuggery do zatrzymania wykonania programu. Zastępujemy breakpointa NOPem aby Binary Ninja kontynuował analizę funkcji main.

Zastąpienie breakpointa instrukcją NOP

W poprzednio niewidocznej wcześniej części programu sprawdzany jest czas. Jeśli program byłby debugowany ustawione przez signal_handler wartości zmiennych zostały by nadpisane.

Dekompilacja dalszej części funkcji main

Funkcja sprawdzająca aktualny czas:

Funkcja sprawdzająca aktualny czas z użyciem instrukcji rdtsc

Po próbie wykrycia debuggera program rozpoczyna sprawdzanie poprawności podanej przez użytkownika flagi.

Dekompilacja zaciemnionej części funkcji main

Używając User Informed Data Flow ustawiamy wartości czterech zmiennych lokalnych (rdi_6, rdx_1, r9 i rsi_1). Wartości powinny być takie jak te co zostają ustawione po wywołaniu sigtrap_handler w odpowiedzi na wykonanie instrukcji breakpoint.

Przekazywanie dodatkowych informacji do dekompilatora

Efekt przekazania dodatkowych informacji do dekompilatora:

Zaktualizowana dekompilacja fragmentu funkcji main

W równaniach została jeszcze jedna zmienna, której wartości Binary Ninja nie zna - r14. Program kopiuje ciąg znaków “ding{ring_ding_ding_daa_baa_baaaa}” do std::string a następnie wskaźnik do skopiowanego ciągu znaków jest przypisywany do r14.

Przepływ danych w funkcji main

Ponownie używamy User Informed Data Flow, tym razem do ustawienia wartość zmiennej r14 jako wskaźnik z wartością 0x403198.

Ustawianie wartości zmiennej zdefiniowanej przez użytkownika
Adres ciągu znaków: "ding{ring_ding_ding_daa_baa_baaaa}"
Ustawianie wartości zmiennej zdefiniowanej przez użytkownika

Dzięki wykorzystaniu Binary Ninja oraz jego funkcji User Informed Data Flow, zdołaliśmy zdeobfuskować program.

Dane w sekcji .rodata

Binary Ninja Python API pozwoli nam wyświetlić flagę, wystarczy zaledwie jedna linia kodu Pythona. Używamy zmiennych current_variable i current_hlil, więc istotne jest aby przed wykonaniem kodu kliknąć na nazwę zmiennej __str_1 w linii gdzie została zdefiniowana.

Wyświetlenie flagi z pomocą Binary Ninja Python API
>>> from itertools import islice
>>> "".join(chr(current_hlil[xref.expr_id].condition.instruction_operands[1].constant) for xref in islice(current_function.get_hlil_var_refs(current_variable),1,None))
'ping{r3m3mb3r_70_h1d3_ur_d3bu993r}'

Rozwiązanie zadania zaproponowane przez jego autora

Rozwiązanie zadania zaproponowane przez jego autora