Backdoor i parę “ciekawostek” w serii routerów D-Link DWR – Reverse Engineering tylnej furtki

Z racji tego, że nie wszyscy mogą być zainteresowani procesem analizy tylnej furki, postanowiłem wydzielić tą część i przedstawić w osobnym poście. Opis reszty podatności routerów z serii DWR znajdziesz tutaj.

Dla formalności jeszcze raz:

Podatne urządzenia wraz z wersjami oprogramowania

Warto dodać, że urządzenia DWR-113, DWR-712, DWR-755, DWR-927 nie były dostępne w oficjalnej sprzedaży w Polsce.

Co piszczy w httpd?

Po rozpakowaniu firmware binwalkiem (polecam bardzo!) i załadowaniu binarki serwera hostującego panel administacyjny w IDA (ścieżka: /usr/sbin/httpd) zostaniemy uraczeni takim spisem ciągów tekstowych w pliku:
dwr_backdoor_1

Pierwsze dwa tropy! Interesująca funkcja do_login() oraz dodatkowy string “guest” o niezidentyfikowanym przeznaczeniu.

Funkcja do_login() wywoływana jest jednokrotnie w kodzie httpd, z funkcji o początku w adresie 0x40B630 (wywołanie następuje w 0x40B914).

dwr_backdoor_2

do_login() przyjmuje jeden parametr, który jest jednocześnie parametrem funkcji jej wywołującej, w rejestrze $a0. Procedura po zakończeniu swojego działania zwraca liczbę całkowitą, która określa uprawnienia użytkownika (o tym później). Jeżeli jest ona równa 0, uwierzytelnianie zostaje zakończone niepowodzeniem.

dwr_backdoor_3

Na początku swojego działania do_login() blokuje dla siebie mutex’a, następnie zmiennej, która jest flagą uprawnień usera (zwaną przeze mnie user_login_id) przypisywane jest 0.

Funkcje open_csman(), read_csman(), write_csman() są składowymi biblioteki CSMan, która odpowiada za realizację RPC za pomocą socketów.

Na potrzeby posta, wystarczy wiedzieć, że open_csman() otwiera kanał komunikacji z procesem określanym w źródłach jako CSMan Server. Funkcja read_csman() wczytuje dane bufora w pamięci do wskazanego bufora w argumencie, a write_csman(), co oczywiste, umieszcza dane w buforze.

read_csman() oraz write_csman() przyjmują następujące argumenty (w kolejności wywoływania):

  • fd – deskryptor socketa
  • csid – adres (offset) bufora źródłowego (read_csman) lub docelowego (write_csman) w pamięci – przedstawione CSIDy w tym poście znajdują się w pliku /include/csid/csid_local.h
  • buf – wskaźnik bufora docelowego (read_csman) lub bufora źródłowego (write_csman)
  • count – rozmiar bufora
  • flags – flagi

Głodnych wiedzy zachęcam do przejrzenia funkcji bibliotecznych w paczce z komponentami na licencji GPL. Nie próbujcie Google’ować – Google nie rozumie problemu 😉

Zwracaną przez open_csman() wartością, w przypadku powodzenia, jest indeks socketu w tablicy socketów.

Kolejnym interesującym krokiem jest sposób walidacji poświadczeń. Na początku zerowany jest bufor pomocniczy na nazwę użytkownika (helper_username_buffer) i wczytywany jest do niego string za pomocą read_csman().

dwr_backdoor_9

Jeżeli operacja się powiedzie – pierwszy bajt bufora jest różny od zera, następuje porównanie zmiennej username z helper_username_buffer a potem walidacja haseł za pomocą funkcji strcmp().

dwr_backdoor_4

Kiedy hasło podane przez usera i zapisane w pamięci urządzenia jest takie samo, funkcja zwraca zero. Wówczas następuje przypisanie do flagi uprawnień user_login_id wartości 7, co powoduje przypisanie najwyższego poziomu uprawnień (deklaracja w pliku wsif.h, widoczne w komentarzach na screenie).

dwr_backdoor_5

Następnie sprawdzana jest flaga ustawiona przed chwilą. W przypadku wartości różnej od 7 następuje kolejne sprawdzenie nazwy użytkownika, tym razem dla konta “guest”.

dwr_backdoor_6

Uwierzytelnienie rozpoczyna się od weryfikacji zgodności nazwy użytkownika, następnie za pomocą wywołania funkcji read_csman() bufor, zwany przeze mnie, empty_password_buffer (32 znaki hasła + znak końca ciągu) jest “wypełniany pustką” spod offsetu 0x37F. Na końcu ciągi są porównywane za pomocą funkcji strcmp().

Tak jak poprzednio, w przypadku zwrócenia przez funkcję wartości zero (zgodności stringów), fladze uprawnień user_login_id przypisywana jest wartość określająca najwyższe uprawnienia czyli 7.

dwr_backdoor_7Teraz mamy już z górki 😉 Ostatnim sprawdzeniem jakie na nas czeka, jest porównanie wartości user_login_id z zerem. Każda wartość flagi różna od zera spowoduje zalogowanie do webowego panelu administratora.

dwr_backdoor_8Końcówka funkcji do_login() to, jak wspominałem opisując jej wywołanie, zwrócenie flagi uprawnień user_login_id. Następnie “sprzątanie” czyli zwolnienie mutex’a, zamykanie kanału komunikacji z procesem CSMan Server i przywrócenie stanu rejestrów z przed wywołania funkcji.