Post otwierający serie postów traktujących o sposobie szyfrowania i deszyfrowania czy to:
– plików konfiguracyjnych
– skradzionych danych
– ciągów znaków reprezentujących np. nazwę winapi
wykorzystywanym przez twórców trojanów bankowych.
Przedstawiając kolejne rodziny trojanów postaram się je ułożyć w kolejności zaczynając od tych stosujących najmniej skomplikowany sposób szyfrowania do tych prezentujących coraz to bardziej złożony rodzaj algorytmów ( o ile ,którykolwiek z nich można tak określić :P).
Pierwszy twór, któremu się przyjrzymy na VirusTotal przedstawia się w sposób następujący: 7050369f20acdb4587872ff1eccf43f3cccddf5dc03c7cd6a936a383a6863aac
[=]Dane[=]
Antivirus Version Last Update Result AhnLab-V3 5.0.0.2 2009.04.26 Dropper/Agent.61440.AF AntiVir 7.9.0.156 2009.04.25 TR/BHO.gcw Authentium 5.1.2.4 2009.04.25 W32/Dropper.AHBB Avast 4.8.1335.0 2009.04.25 Win32:Trojan-gen {Other} AVG 8.5.0.287 2009.04.26 Dropper.Agent.LCJ BitDefender 7.2 2009.04.26 Trojan.Generic.1252968 CAT-QuickHeal 10.00 2009.04.25 TrojanDropper.Agent.abjf Comodo 1130 2009.04.25 TrojWare.Win32.Trojan.Agent.Gen DrWeb 4.44.0.09170 2009.04.26 Trojan.Fakealert.3764 F-Prot 4.4.4.56 2009.04.25 W32/Dropper.AHBB F-Secure 8.0.14470.0 2009.04.25 Trojan-Dropper.Win32.Agent.abjf Fortinet 3.117.0.0 2009.04.26 PossibleThreat GData 19 2009.04.26 Trojan.Generic.1252968 Ikarus T3.1.1.49.0 2009.04.26 Trojan-Dropper.Agent K7AntiVirus 7.10.716 2009.04.25 Trojan-Dropper.Win32.Agent.abjn Kaspersky 7.0.0.125 2009.04.26 Trojan-Dropper.Win32.Agent.abjf McAfee 5596 2009.04.25 Generic.dx McAfee+Artemis 5596 2009.04.25 Generic.dx McAfee-GW-Edition 6.7.6 2009.04.26 Trojan.BHO.gcw Microsoft 1.4602 2009.04.26 Trojan:Win32/Relbma.A NOD32 4035 2009.04.25 Win32/BHO.NDB Norman 2009.04.24 W32/Agent.KPCN nProtect 2009.1.8.0 2009.04.26 Trojan-Dropper/W32.Agent.61440.AW Panda 10.0.0.14 2009.04.26 Generic Trojan Prevx1 3.0 2009.04.26 High Risk Worm Sophos 4.41.0 2009.04.26 Mal/Generic-A Sunbelt 3.2.1858.2 2009.04.24 Trojan-Downloader.Win32.FraudLoad.vdxo Symantec 1.4.4.12 2009.04.26 Trojan Horse TheHacker 6.3.4.1.314 2009.04.26 Trojan/Dropper.Agent.abjf TrendMicro 8.700.0.1004 2009.04.25 TROJ_AGENT.PRPR VBA32 3.12.10.3 2009.04.25 Trojan-Dropper.Win32.Agent.abjf VirusBuster 4.6.5.0 2009.04.25 Trojan.DR.Agent.IMHD Additional information File size: 61440 bytes MD5 : fa1faad9a5db4f33173ee0b439e410f6 SHA1 : 5aea25ef14f52930ebc3520a1bf43b1b74573899 SHA256: 7050369f20acdb4587872ff1eccf43f3cccddf5dc03c7cd6a936a383a6863aac PEiD : Armadillo v1.71
Jak widać nazwy sygnatur, są bardzo ogólne co może wskazywać jedynie na wieeeeele mutacji tej rodziny pojawiających się w sieci.
[=]Prolog[=]
Tak jak wspominałem wcześniej będziemy się przyglądać trojanom stosującym coraz to bardziej wyrafinowane metody szyfrowania, więc zgodnie z nasza konwencją niniejszy
sampel, a dokładnie metody stosowane przez niego nie powinny szokować( no chyba, że prostotą :D).
Jakie elementy będą „wymagały” tu deszyfrowania:
– plik konfiguracyjny
– dll’ka, która jest instalowana w systemie jako BHO
– adres url wskazujący na drop host
Napisałem „wymagały” ,ponieważ tak na dobrą sprawę akurat w tym konkretnym samplu np.autor postanowił ułatwić badaczowi sprawę i plik konfiguracyjny jest dropowany do filesystem’u już po deszyfrowaniu :D. Inna kwestią jest to, że zamiast deszyfrować konkretne elementy, można zrobić dump’a całego procesu i np. w przypadku kiedy config jest deszyfrowany przez trojana tylko i wyłącznie na czas użycia, liczyć na to, że w dump’e znajdziemy go już w plain-text’e. Podejść do sprawy jest wiele, ale w naszych badaniach zależeć nam będzie na tym, żeby poznać algorytm szyfrowania i w miarę możliwości go odwrócić. Oczywiście zdarzają się przypadki gdzie:
– nie zależy nam na poznawaniu algorytmu używanego przez trojana, bo jest to sampel należący do rodziny, która widzimy pierwszy raz i nie koniecznie będzie dla nas użyteczny z różnych względów. Wtedy oczywiście idziemy po najmniejsze lini oporu.
– procedura deszyfrująca jest na tyle duża, a my na tyle leniwi ;], lub mało zainteresowani jej odwracaniem nie podejmujemy się pisania dekryptora od zera, ale np. wykorzystujemy prockę używaną przez trojana w charakterze shellcode’u (o tym więcej w dalszych postach).
[=] Analiza [=]
Ok., pora na analizę naszego celu. Jako, że zależy nam tylko na procedurach (de)szyfrującyh to bezpośrednio będę wskazywał elementy oraz ich lokalizacje, które będą wymagały naszej interwencji. Ewentualne pokaże sposoby na znalezienie interesujących nas części kodu procedur.
Tak na dobrą sprawę wszystkich istotne elementy, które postaramy się deszyfrować znajdują się w zasobach(‘Resource Directory’) pliku wykonywalnego. Może podejrzeć zasoby używając do tego celu np.
CFF Explorer’a:
Jak widać na powyższym screen’e dane w takiej postaci niczego nam nie mówią, ale w miarę wykonywanej przez nas deszyfracji wszystko stanie się bardziej klarowne. Deszyfrować zwartość zasobów zaczniemy od końca czyli od „WFP”.Zanim zaczniemy, wykonajmy dump zasobu do plik np.
C:config i sprawdźmy jego entropię:
Już spieszę z wyjaśnieniami. Skala, którą widać po prawej reprezentuje poziom entropii w zakresie od 0-8. Tak jak obiecywałem algorytm(y) szyfrowania użyty w tym trojanie będzie prosty o czym już teraz możemy się przekonać analizując wykres. Wartości funkcji, są dość nie regularne i rozciągają się w zakresie od 4,2 do 5,4. Od razu wspomnę, że ciekawszych procek szyfrujących można się spodziewać w przypadku entropii na poziomie 7-8.
Dla porównania wykres entropii dump’a traffic’u zawierającego dll’ki, ściągane przez Mebroot’a:
[=]Analiza procedur szyfrujących[=]
Jak już wiemy interesujące nas bloki danych znajdują się w resource directory. Teraz rodzi się pytanie w jaki sposób odnaleźć funkcje (de)szyfrującą/e? Każdy kto pisał aplikacje z użyciem winapi, korzystającą z katalogu zasobów skojarzył od razu funkcje „FindResourceA/W”, która zwraca uchwyt do zasobu, którego nazwę podamy jako jeden z parametrów tego api.
Więc nasuwają się tutaj dwa podejścia, a tak naprawdę jedno chyba najbardziej logiczne ;].
– wyszukać wszystkie referencje do wywołań api FindResourceA/W
albo odnaleźć wszystkie referencje do stringu „WFP” .
Oczywiście posłużymy się tutaj podejściem drugim które najprawdopodobniej od razu wskaże nam okolice kodu zawierającego procedurę deszyfrującą.
Ohoo…wygląda na strzał w dziesiątkę, co prawda na razie nie widać jakichkolwiek manipulacji na uzyskanym pointerze do zasobu, zerknijmy jednak do procedury :
CALL zly_troj.0040155F
mając na uwadze, że argumentami tej procki są:
arg1 : pointer do zasobu „WFP”
arg2 : rozmiar zasobu „WFP”
Ahhh udało nam się zlokalizować prockę deszyfrującą!(zielona ramka).
Jak widać okazała się nią prosta operacja xorująca każdy bajt z kluczem o wartości:
0x13.
[=]Deszyfrowanie[=]
Oczywiście po tym co właśnie zobaczyliśmy stworzenie własnego deszyfratora, który „sksoruje”, każdy bajt dumpu zasobu, który wcześniej wykonaliśmy z kluczem 0x13.
Świetnie się do tego nada JEDNOLINIJKOWY skrypt w python’e:
"".join(map(lambda x: chr(ord(x)^0x13),file(r’C:config’,’rb’).read()))
, a oto rezultat:
widać teraz, że rzeczywiście jest to plik konfiguracyjny zapisany w xml’u.
Przejdźmy do następnego zasobu.
„REM”
Rzućmy okiem na entropię:
Mhmm…tutaj już entropia wygląda ciekawiej, więc z pewnością możemy się spodziewać czegoś więcej niż tylko prostego xor’owania.
Oczywiście, miejsce z istotną dla nas procką znajdujemy analogicznie do poprzedniego przypadku, a naszym oczom ukazuje się taki o to kod:
Jak widzimy, w tym przypadku klucz xor’ujący nie jest stały. Jego wartość będzie zmieniała się cyklicznie po każdej iteracji, stąd też widoczne na wykresie entropii zmiany w kierunku wyższego zbioru wartości.
Warto zwrócić uwagę na dwie istotne kwestie, które są wykonywane przed wejściem do pętli:
ESI – pointer na zaalokowaną pamięć, która będzie zawierać deszyfrowane dane. 004013CD |. C606 4D MOV BYTE PTR DS:[ESI],4D 004013D3 |. C646 01 5A MOV BYTE PTR DS:[ESI+1],5A
4D5A ,czy coś wam to mówi ?:D. Oczywiście jest to magic number, który zawsze powinien znaleźć się na początku pliku PE.
Jak widać autor trojana dodaje magic number dopiero w momencie deszyfrowania. Takie podejście może świadczyć jedynie o tym iż autor chciał utrudnić wykrycie potencjalnie niebezpiecznego zasobu antywirusą czy innym skanerą. Drugą kwestią jest wyliczenie stałej która będzie wykorzystywana do indexowania zaszyfrowanych danych. Jej wyliczenie wygląda następująco:
004013D9 |. 895D FC MOV [LOCAL.1],EBX ; EBX = encrypted_data 004013DC |. 2975 FC SUB [LOCAL.1],ESI ; ESI = decrypted_data
następnie w pętli mamy: (stale wartości odpowiednie do pierwszej iteracji)
EDI = 2 ; 004013E5 |. 8D0C37 |LEA ECX,DWORD PTR DS:[EDI+ESI] … 004013EA |. 8B45 FC |MOV EAX,[LOCAL.1] … 004013EF |. 321408 |XOR DL,BYTE PTR DS:[EAX+ECX]
widzimy, że w momencie xor’owania :
DL – cyklicznie zmieniający się klucz
a na co wskazuje BYTE PTR DS:[EAX+ECX]?
Prosty zapis matematyczny wszystko wyjaśnia:
LOCAL.1 = encrypted_data - decrypted_data ECX = decrypted_data + 2 EAX = LOCAL.1 I w momencie xor’owania: XOR DL,BYTE PTR DS:[LOCAL.1+decrypted_data+2] podstawiajac za LOCAL.1 XOR DL,BYTE PTR DS:[ encrypted_data - decrypted_data +decrypted_data+2] ,więc w ostateczności mamy XOR DL,BYTE PTR DS:[ encrypted_data + 2]
ot taki troszeczkę nie intuicyjny sposób indexowania ;).
Przykładowy skrypt deszyfrujący:
def decrypt(encrypted): decrypted = [] edi = 2 decrypted.append('M')# 0x4d decrypted.append('Z')# 0x5a while edi != len(encrypted): edx = edi % 0x78 edx = (edx << 1) & 0xFF decrypted.append(chr( (ord(encrypted[edi])^edx)&0xFF )) edi = edi + 1 return "".join(decrypted) if __name__ == "__main__": f = file(r"c:evil_dll",'rb') fw = file(r"c:decrypted_evil_dll",'wb') fw.write(decrypt(f.read())) f.close() fw.close()
i rezultat deszyfrowania:
Jak widać na screen’e w katalogu zasobów „REM” ukrywała się dll’ka.
Ostatni już katalog zasobów „CAS”. Jako, że danych w każdej pozycji katalogu CAS jest niewiele, trudno tutaj mówić/prezentować wykres rozkładu entropii. Przejdziemy więc od razu do sposobu ich deszyfrowania. Co prawda dane z tego zasobu, są ekstraktowane do systemu , (a dokładnie do rejestru )lecz w formie nie zmienionej. Dopiero dll’ka , która wcześniej mieliśmy przyjemność deszyfrować(a która jest dropowana do C:WINDOWSsystem32sxmg4.dll i rejestrowana jako BHO)
będzie pobierała te dane i deszyfrowała tylko w momencie zaistniałem potrzeby.
Zaszyfrowane dane w rejestrze prezentują się w następujący sposób(interesują nas tylko wartości Ad i Ad2):
HKEY_LOCAL_MACHINESOFTWARETSoft
Ok, weźmy pod lupę więc dll’ke: Stosując identyczne podejście jak w dwóch poprzednich przypadkach znajdujemy następujący kod:
Kolejna różna od pozostałych procedura bazująca wciąż na xor’owaniu, lecz tym razem użyto stałego klucza o długości 3 bytów. Warto wspomnieć jakie konsekwencje niesie za sobą takie podejście oraz jak to rozwiązano w niniejszej implementacji:
ECX = ilość odczytanych(zaszyfrowanych) bajtów z wartości rejestru 100053B0 |. 6A 02 PUSH 2 100053B2 |. 33C0 XOR EAX,EAX 100053B4 |. 5A POP EDX 100053B5 |. 889C0D F0FDFF>MOV BYTE PTR SS:[EBP+ECX-210],BL 100053BC |. 3BCA CMP ECX,EDX 100053BE |. 76 31 JBE SHORT sxmg4.100053F1
Jak widać sprawdzane jest tutaj czy ilość bajtów które mają być odszyfrowane przekracza 2, ponieważ przy mniejszej ilości, w pętli już po pierwszej iteracji dostalibyśmy błędne dane (akurat przy tej implementacji nie wystąpi ,ani BO ani Off-by-One). To jedna kwestia, kolejną jest to, że długość danych do rozszyfrowania może nie być wielokrotnością 3’ki.
Więc należy tutaj zadbać o sprawdzanie czy w kolejnym kroku deszyfrowania mamy przynajmniej 3 bajt do przetworzenia. Jeśli nie, pętla powinna się zakończyć a pozostałe bajty( max 2) należy deszyfrować indywidualnie.
Sprawdźmy jak to zostało tu zaimplementowane:
Przed wejściem do pętli mamy :
EDX = 2 100053C0 |. 8DB5 F1FDFFFF LEA ESI,DWORD PTR SS:[EBP-20F] 100053C6 |. 2BD6 SUB EDX,ESI EBP – 210h = encrypted_data ,więc EBP – 20Fh == encrypted_data +1
w pętli:
… 100053E3 |. 8D3402 |LEA ESI,DWORD PTR DS:[EDX+EAX] 100053E6 |. 8DB435 F1FDFF>|LEA ESI,DWORD PTR SS:[EBP+ESI-20F] 100053ED |. 3BF1 |CMP ESI,ECX 100053EF |.^ 72 D7 JB SHORT sxmg4.100053C8
kiedy przyjrzymy się temu kawałkowi kodu mamy:
ECX = encrypted_data_length EAX = index EDX = 2 – (encrypted_data+1) ESI = EDX + EAX ESI <- EBP + ESI – 20F == EBP -20F + ESI == (encrypted_data+1) + 2 –(encrypted_data+1)+ index czyli ostatecznie ESI = 2+index
,więc co iteracje w pętli( a jest to pętla do while) sprawdzane jest czy
index+2 < encrypted_data_length, jeżeli tak to pętla jest kontynuowana.
Po zakończeniu pętli możemy zauważyć to o czym wspominałem, a mianowicie indywidualne xor’owanie bajtów, które pozostały lub w przypadku kiedy długość zaszyfrowanych danych < 3:
100053F1 |> 3BC1 CMP EAX,ECX 100053F3 |. 73 08 JNB SHORT sxmg4.100053FD 100053F5 |. 80B405 F0FDFF>XOR BYTE PTR SS:[EBP+EAX-210],0F 100053FD |> 8D50 01 LEA EDX,DWORD PTR DS:[EAX+1] 10005400 |. 3BD1 CMP EDX,ECX 10005402 |. 73 0F JNB SHORT sxmg4.10005413 10005404 |. 80B405 F1FDFF>XOR BYTE PTR SS:[EBP+EAX-20F],10
Prosty kod który powinien działać 😀 przy dowolnej długości klucza:
def decrypt(buffer,key): decrypted = [] notAligned = len(buffer) % len(key) secureLen = len(buffer) - notAligned for i in range(0,secureLen,len(key)): for j in range(0,len(key)): decrypted.append( chr( ord(buffer[i+j])^key[j] ) ) #dekrypcja pozostalych NIE odszyfrowanych bajtow o ile takie istnieja if notAligned: for i in range(secureLen,len(buffer)): decrypted.append( chr( ord(buffer[i])^ key[-secureLen+i]) ) return "".join(decrypted) if __name__ == "__main__": key = [0xf,0x10,0x11] print decrypt(file("c:\evil_host1").read(),key) print decrypt(file("c:\evil_host2").read(),key)
oto rezultat działania kodu:
(evil_host1 i evil_host2 to oczywiście wartości z rejestru Ad1 i Ad2)
Jak widać zaszyfrowane dane przedstawiają najprawdopodobniej adresy drop hostów, albo hostów z których mają być pobierane dodatkowe moduły, nowe plik konfiguracyjne ,etc.
[=]Epilog[=]
To już wszystko odnośnie tego trojana, jak mieliśmy okazje zobaczyć jego autor „nie ograniczał” swojej fantazji jeżeli chodzi o rożnego typu algorytmy szyfrowania choć bazowały one na prostym xor’owaniu to jednak zobaczyliśmy parę mutacji;]. Wspomnę jeszcze, że trojan ten zawiera jeszcze parę zaszyfrowanych bloków danych jednak ze względu na identyczne metody dekodowania ( zmienia się np. klucz xor’ujacy z 0x13 na 0xc) postanowiłem je pominąć. W następnych postach (o ile czas w najbliższym okresie wakacyjnym pozwoli :D) zajmiemy się trochę bardziej złożonymi algorytmami, sposobami ustalania formatu pliku konfiguracyjnego ,etc.
Postarales sie 🙂 Tak na przyszlosc sugerowalbym wklejania wiecej debugow i sposobow myslenia poprzez debug.
GW once again nigggggggggggga 🙂