Analizowana biblioteka realizuje 3 główne cele:
- zapobiega wielokrotnemu uruchomieniu,
- instaluje kolejne komponenty złośliwego kodu na dysku ofiary,
- zapewnia przetrwanie po restarcie poprzez technikę COM Hijacking.
Atak jako pierwszy wykrył i opisał zespół CERT-UA. Szczegóły można znaleźć tutaj: https://cert.gov.ua/article/6287250.
Celem ataku mogły być też organizacje w Polsce (taką informację podają niektóre media, ale nie jest to informacja zweryfikowana).
Nazwa: sd.dll
MD5: 744BBE8D7C3D0421FA0DEB582481F5BA
Rozmiar: 561 KB
Architektura: 64bit
Entry Point: 0x292C
Eksportowane funkcje: UIClassRegister oraz hXts
Analiza EP
Entry point prowadzi do funkcji, która z kolei wywołuje UIClassRegister(). Dla przypomnienia, nazwy funkcji w formacie FUN_<adres> są generowane automatycznie podczas dekompilacji kodu.
FUN_180001fbc(undefined8 param_1,int param_2)
{
if ((param_2 == 1) && (DAT_18008c350 == '\0')) {
UIClassRegister();
}
return 1;
}
Poza sprawdzeniem, czy biblioteka właśnie została załadowana do procesu, sprawdzana jest flaga związana z tym czy system jest już zainfekowany czy nie. Jeśli nie, wywoływana jest UIClassRegister(). Wprowadzenie tej flagi powoduje, że proces rejestracji wykona się tylko raz.
Funkcja UIClassRegister().
Zacznijmy od pierwszego fragmentu kodu rozpoczynającego się od pętli do..while:
do {
ppppCVar2 = &local_48;
if (0xf < local_30) {
ppppCVar2 = (LPCSTR ***)local_48;
}
*(byte *)((longlong)ppppCVar2 + uVar4) = (&DAT_1800105e9)[uVar4] ^ 0x43;
uVar4 = uVar4 + 1;
} while (uVar4 < 0xe);
ppppCVar2 = &local_48;
if (0xf < local_30) {
ppppCVar2 = (LPCSTR ***)local_48;
}
hMutex = CreateMutexA((LPSECURITY_ATTRIBUTES)0x0,1,(LPCSTR)ppppCVar2);
Malware dba o to, by w systemie działała tylko jedna instancja złośliwego kodu. Wykorzystuje do tego Mutex, którego nazwa jest dynamicznie odkodowywana przy użyciu operacji logicznej XOR 0x43.
0x18000202b 80 f1 43 XOR CL,0x43
Pierwszy bajt jest w tablicy pod adresem 0x1800105e9. XOR w pętli wykonywany jest 0xe czy 14 razy.
Możemy te dane odkodować za pomocą skrypty następującej komendy python. Poniżej zawartość:
DANE_BINARNE = "20 7a 70 71 25 7b 2b 24 7b 7b 27 25 71 2c 43"
wynik = "".join(chr(int(b, 16) ^ 0x43) for b in DANE_BINARNE.split())
print(f"Wynik: {wynik}")
Odkodowana wartość: c932f8hg88df2o
Następnie wywoływana jest kolejna funkcja (z adresu 0x180001d4c), która odkodowuje kolejny ciąg znaków. Jako parametr przekazywany jest adres tablicy &DAT_1800103f0. Po odkodowaniu jest to ścieżka z nazwą pliku:
%programdata%\USOPublic\Data\User\EhStoreShell.dll
Następnie za pomocą funkcji ExpandEnvironmentStringsW() (wywołane przez funkcje FUN_1800018b8) i GetFileAttributesW() sprawdzane jest czy plik istnieje.
lpFileName = FUN_1800018b8(pWVar3);
DVar1 = GetFileAttributesW(lpFileName);
Jeśli istnieje - ustawiana jest flaga na 1.
if (DVar1 != 0xffffffff) {
DAT_18008c350 = '\x01'
Jeśli nie, wywoływana jest funkcja instalująca złośliwe oprogramowanie na komputerze.if (DAT_18008c350 == '\0') {
FUN_180001918_main();
Funkcja zaczynająca się pod adresem 0x180001918 zawiera kluczową logikę dla tej biblioteki.
Po pierwsze 3 razy jest wywoływana funkcja FUN_18000159c(). Jako parametry przyjmuje 3 wartości - drugi to adres do tablicy (danych w sekcji .data) a trzeci to rozmiar. Pierwszy parametr to wynik wywołania innej funkcji, która podobnie jak opisywana wcześniej inna funkcja, dekoduje (instrukcja XOR) ścieżkę i nazwę pliku. Poniżej wywołanie tej funkcji:
pWVar1 = (LPCWSTR)FUN_180001ce0_decode_name(&DAT_180010370,local_78);
Wynik to: %programdata%\Microsoft OneDrive\setup\Cache\SplashScreen.png
Analizują zawartość funkcji FUN_18000159c() możemy zauważyć, że:
- pobierany i odkodowywany jest jakiś ciąg znaków. Dzieje się to w tej pętli powtarzanej 72 razy.
do {
ppppuVar4 = &local_68;
if (0xf < local_50) {
ppppuVar4 = (undefined8 ****)local_68;
}
*(byte *)((longlong)ppppuVar4 + uVar7) = (&DAT_180010601)[uVar7] ^ 0x43;
uVar1 = local_58;
uVar7 = uVar7 + 1;
} while (uVar7 < 0x48);
- wykonywana jest druga pętla, ale tym razem licznik to uwaga - wielkość pliku (np. 235 059). Podczas ekstrakcji plików, malware wykonuje operację XOR aż 17 milionów razy dla jednego pliku (Możliwe, że jeszcze istnieją jakieś systemy automatycznej analizy malware mające limit czasu na analizę. Może taka liczba operacji powoduje, że emulator przerwie analizę przed dotarciem do momentu zrzucenia właściwego payloadu na dysk).
Wspominałem, że 3 razy wywoływana jest funkcja “wyciągająca” pliki z analizowanej dll. Są to:
- Plik numer 1: Dane spod tablicy DAT_180051200, rozmiar 0x39633
- Plik numer 2: Dane spod tablicy DAT_18001b000, rozmiar 0x36200
- Plik numer 3: Dane spod tablicy DAT_18008a840, rozmiar 0xde4
Napisaliśmy skrypt, który robi opisane zadanie, ale bez zbędnego powielania tych samych operacji. Można go pobrać z tego miejsca https://github.com/Prevenity/malware_scripts.
Parametry jakie musimy podać: <plik źródłowy> <plik docelowy> <offset w pliku źródłowym do zakodowanego pliku docelowego> <rozmiar pliku docelowego> <offset w pliku źródłowym do zakodowanego ciągu znaków> <klucz XOR>.
Poniżej wywołanie skryptu, który na podstawie w/w informacji "wyciąga" z analizowanej DLL pierwszy plik o nazwie SplashScreen.png
python extract.py sd.dll SplashScreen.png 0x4f800 0x39633 0xfa01 0x43
Pierwszy plik jest w formacie PNG:
Jeszcze "rzut oka" na zawartość pliku. Jak się okaże w 2 części analizy, to nie jest zwykły plik graficzny.

Analizując funkcję FUN_180001918_main() można też zauważyć, że kilka razy za pomocą kopii funkcji wykonana jest ta sama operacja - związana z dekodowaniem ciągu znaków. Możliwe, że taka implementacja ma na celu utrudnienie analizy. Zazwyczaj, gdy już wiemy która funkcja odpowiada za dekodowanie, podczas analizy dynamicznej ustawiamy breakpoint na wyjściu z funkcji aby podejrzeć odkodowaną wartość. Tutaj wymaga to lokalizacji wszystkich funkcji z taką funkcjonalnością.
Spójrzmy jeszcze raz na główną funkcję FUN_180001918_main() i to co do tej pory udało nam się ustalić. Czerwona ramka - odkodowanie ścieżki i nazwy pliku, oraz odkodowanie pliku numer 1. Żółta ramka - identyczna operacja dla pliku numer 2 i zielona ramka - plik numer 3.
Za pomocą wcześniej utworzonego skryptu odczytamy też plik numer 2 i plik numer 3.
Nazwę pliku numer 2 już odczytaliśmy wcześniej (ten moment gdy malware sprawdzał czy plik istnieje już w systemie plików). Więc wiemy, że jest to EhStoreShell.dll.
Wyliczamy jedynie offset na dysku dla DAT_18001b00, który wynosi: 0x19600. Podajemy też wielkość pliku która wynosi 0x36200. Offset do ciągu odbudowującego plik i klucz XOR są oczywiście takie same.
Drugi plik to kolejna biblioteka DLL.
Odkodowana ścieżka i nazwa ostatniego pliku to: %temp%\Diagnostics\office.xml. Rozmiar 0xde4. Offset na dysku w pliku dll to: 0x88e40.
$ file office.xml
office.xml: XML 1.0 document, Unicode text, UTF-16, little-endian text, with CRLF line terminators
$ md5sum office.xml
ee0b44346db028a621d1dec99f429823 office.xml
Jego zawartość jest poniżej.
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Date>2023-01-15T21:26:01.189134</Date>
<URI>\OneDriveHealth</URI>
</RegistrationInfo>
<Triggers>
<RegistrationTrigger>
<Enabled>true</Enabled>
<Delay>PT1M</Delay>
</RegistrationTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<LogonType>InteractiveToken</LogonType>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<Enabled>true</Enabled>
<Hidden>true</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
<UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>%windir%\system32\cmd.exe</Command>
<Arguments>/c (taskkill /f /IM explorer.exe >nul 2>&1) & (start explorer >nu
l 2>&1) & (schtasks /delete /f /tn OneDriveHealth)</Arguments>
</Exec>
</Actions>
</Task>
Możemy zauważyć, że malware podszywa się pod usługę OneDriveHealth. Będzie ona uruchomiona z harmonogramu zadań systemu Windows. Zadanie, uruchomione po 1 minucie od zarejestrowania. W sekcji actions jest komenda, która “restartuje” proces explorer.exe i kasuje zadanie. Oznacza to, że jest to jednorazowa akcja.
Argumenty dla cmd.exe to: /c (taskkill /f /IM explorer.exe) & (start explorer) & (schtasks /delete /f /tn OneDriveHealth)
Spróbujmy przeanalizować dalszą część głównej funkcji FUN_180001918_main().
Zaraz po zapisaniu na dysku 3 plików, ponownie wywoływane są funkcje odkodowujące ciągi znaków.
pauVar2 = (undefined1 (*) [32])FUN_180001d4c_decode_xor(&DAT_1800103f0,local_58)
zwraca:%programdata%\USOPublic\Data\User\EhStoreShell.dll
pWVar1 = (LPCWSTR)FUN_180001c70_decode_xor(&DAT_1800104a0,local_78);
zwraca: Software\Classes\CLSID\{D9144DCD-E998-4ECA-AB6A-DCD83CCBA16D}\InProcServer32
Następnie pierwszy raz wywoływana jest funkcja FUN_1800014f8_reg(pWVar1,L"",pauVar2,2).
Ta funkcja odpowiada za utworzenie klucza w rejestrze.
Ponownie odkodowywane są kolejne ciągi znaków:
pauVar2 = (undefined1 (*) [32])FUN_180001efc_decode_xor(&DAT_180010568,local_38);
zwraca string: Apartment
pWVar3 = (LPCWSTR)FUN_180001c70_decode_xor(&DAT_1800104a0,local_58);
zwraca string: Software\Classes\CLSID\{D9144DCD-E998-4ECA-AB6A-DCD83CCBA16D}\InProcServer32
pWVar1 = (LPCWSTR)FUN_180001e90_decode_xor(&DAT_180010540,local_78);
zwraca string: ThreadingModel
Następnie wywoływana jest ponownie funkcja FUN_1800014f8_reg(pWVar3,pWVar1,pauVar2,1);.
Te nazwy oraz wpisy do rejestrów wykonane przez malware wskazują, że wykorzystana jest technika COM Hijacking. Zamiast modyfikować standardowe klucze autostartu (jak RUN), malware rejestruje złośliwą bibliotekę EhStoreShell.dll jako serwer InProc dla istniejącego w systemie obiektu CLSID.
Pełna ścieżka rejestru: HKCU\Software\Classes\CLSID\{D9144DCD-E998-4ECA-AB6A-DCD83CCBA16D}\InProcServer32
Dzięki temu, gdy aplikacja systemowa (explorer.exe) będzie chciała odwołać się do tego obiektu (Windows Storage Shell Extension), nieświadomie załaduje złośliwy kod. Aby wymusić przeładowanie, malware tworzy zadanie w harmonogramie (schtasks), które jednorazowo restartuje proces explorer.exe (ale to tym za chwile).
Następnie wywoływane są kolejne funkcje dekodujące ciągi znaków.
Przykład:
FUN_180001db8_decode_xor((byte *)L"C0 +7\"0(0m&;&cl",(longlong *)local_98);
Postać jawna tego ciągu znaków to: schtasks.exe. Inne dekodowane w podobny sposób ciągi znaków:
- %temp%\Diagnostics\office.xml
- /Create /XML
Możemy też zauważyć wkompilowane funkcje strlen czy memcpy. Są one używane do złożenia w całość ciągu który będzie użyty przez jedną z ostatnich funkcji.
Jest to funkcja FUN_180001810_add_task(lpString1), która uruchamia poniższą komendę ze złożonych wcześniej odkodowanych stringów.
"schtasks.exe /Create /tn \"OneDriveHealth\" /XML \"C:\\Users\\Jack\\AppData\\Local\\Temp\\Diagnostics\\office.xml\""
Poniżej widok z debuggera momentu wywołania CreateProcessW().
Pełna zawartość tej funkcji umieszczona jest poniżej:
FUN_180001810_add_task(LPWSTR param_1)
{
BOOL BVar1;
BOOL BVar2;
undefined4 extraout_var;
_PROCESS_INFORMATION local_98;
_STARTUPINFOW local_78;
local_78.cb = 0x68;
FUN_18000f770_memset((undefined1 (*) [32])&local_78.lpReserved,0,0x60);
local_98.dwProcessId = 0;
local_98.dwThreadId = 0;
local_98.hProcess = (HANDLE)0x0;
local_98.hThread = (HANDLE)0x0;
BVar1 = CreateProcessW((LPCWSTR)0x0,param_1,(LPSECURITY_ATTRIBUTES)0x0,(LPSECURITY_ATTRIBUTES)0x0,
0,0x8000000,(LPVOID)0x0,(LPCWSTR)0x0,&local_78,&local_98);
WaitForSingleObject(local_98.hProcess,0xffffffff);
CloseHandle(local_98.hProcess);
BVar2 = CloseHandle(local_98.hThread);
return CONCAT71((int7)(CONCAT44(extraout_var,BVar2) >> 8),BVar1 != 0);
}
Podsumowanie:
Pierwszy etap infekcji realizowany przez sd.dll to starannie wykonany dropper a wykorzystanie mechanizmu COM Hijacking pozwala na uruchomienie złośliwego kodu przy każdym starcie explorera. W kolejnym artykule zajmiemy się analizą dwóch pozostałych plików odkodowanych z tej biblioteki.
Źródła:
1. https://cert.gov.ua/article/6287250
2. https://www.zscaler.com/blogs/security-research/apt28-leverages-cve-2026-21509-operation-neusploit
3. https://github.com/Prevenity/malware_scripts


















