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.