Geoff Chappell - Software Analyst
In allegations by someone presenting himself as Phil Bucking, a buffer overflow bug is said to exist in the AIM client software and a particular data packet is reproduced which is said to trigger that bug. Although no other clue is given to the bug’s location, it may at least be taken as granted that Phil Bucking has given the packet in its entirety, showing all the bytes. In an attempt to locate the bug, assuming it does exist, the obvious question must be: how would the AIM client software handle the Phil Bucking packet?
Note that you do not need to trace such handling, e.g., in a debugger. It is enough to disassemble the software, producing a text file of assembly mnemonics, the same ones that you would see when debugging, and then to follow the execution as an exercise in logic, as if doing the debugging hypothetically. Still, the AIM client software must, in practice, be installed at least enough to see its executable files.
A glance at the directory in which the AIM client software is installed will show many files. The client is run by executing the AIM.EXE program. However, this little AIM.EXE program is just a stub to start the main component of the AIM client (much as Microsoft’s IEXPLORE.EXE program is just a stub to start the Internet Explorer package that is actually implemented mostly as SHDOCVW.DLL). This main AIM component is named OSCORE.DLL. It exports numerous functions for the shared use of the many smaller modules that handle the various features of Instant Messaging.
Among these smaller modules, the one of particular interest here is PROTO.OCM since it has the job of sending and receiving data packets in the language, or protocol, that is understood by the AIM server. Within PROTO.OCM is a routine that receives data packets and parses them according to the AIM protocol.
Inspection of this routine, hereafter called the read routine, produces the highest level of syntax in the AIM protocol. All data packets that conform to the protocol begin with a 6-byte packet header, to be followed immediately by packet data:
Offset | Size | Description |
---|---|---|
00h | byte | necessarily 2Ah |
01h | byte | channel number |
02h | word | serial number of packet |
04h | word | size, in bytes, of packet data that follows |
06h | packet data (size given at offset 04h) |
The ordering of bytes within words is the opposite of the usual ordering on Intel machines. Specifically, the first of the two bytes is the more significant.
PROTO reads the packet 0200h bytes at a time (using the documented WinSock function named recv) to a 0200h-byte buffer on the stack. There is defensiveness, most notably against the possibility that a packet will be received with fewer bytes than required for a valid packet header. Also, packets must be received in sequence, as indicated by the serial number at offset 02h.
If a non-zero size is given for the packet data that is to follow the packet header, PROTO obtains a memory block of that size and builds a copy of the packet data there for further analysis. As suggested above, this may be done in parts, reading from WinSock to the temporary location on the stack, 0200h bytes at a time. Note that the given size is believed without question. Everything that follows the packet header is interpreted as packet data, belonging to this one packet, until the given number of bytes have been received.
When this read routine has a complete packet, it calls a subroutine that will distribute the handling of that packet data according to (among other things) the channel number given in the packet header. This subroutine, hereafter called the dispatch routine, recognises valid channel numbers 01h to 04h inclusive.
The Phil Bucking packet has channel number 02h, and attention is henceforth restricted to this case. For this channel number, PROTO simply assumes that there are at least two words of packet data. These are interpreted as a group number and a function number respectively. Again, the ordering of bytes within words is the opposite of the usual ordering on Intel machines.
By group, it is meant here to draw a correspondence with the on_group, on_message and on_type keywords, as recognised in the AIM client’s configuration file (the text file specified through the /c switch on the AIM command line, defaulting to AIM.ODL). An AIM message is a communication between modules of the AIM client package. AIM messages are distinguished by type and group numbers. AIM modules register handlers for messages of a particular type, a particular group or a particular combination. In general, analysis of packet data is passed by PROTO to other AIM modules as an AIM message of type 0001h, with the group number as given in the packet data. However, group 0001h is internal to PROTO.OCM.
The Phil Bucking packet has group number 0001h, and attention is henceforth restricted to this case. For this group number, PROTO assumes that the packet data begins not just with two words but with what may as well be named a SNAC Header (because interpretation is performed by calling a function that is imported from OSCORE.DLL under the name SNACGetHeader).
Two forms of SNAC header are supported by OSCORE. The one of concern here is the simpler: a ten-byte structure consisting of three words and a dword:
Offset | Size | Description |
---|---|---|
00h | word | group number |
02h | word | function number |
04h | word | short parameter |
06h | dword | long parameter |
The ordering of bytes within words and dwords is the opposite of the usual ordering on Intel machines.
For group 0001h, the function numbers that are recognised one way or another are 0001h, 0003h, 0005h, 0007h, 000Ah, 000Bh, 000Dh, 000Fh, 0010h, 0012h, 0013h and 0018h. For each, PROTO passes further analysis to yet another subroutine.
The Phil Bucking packet has function number 0013h, and attention is henceforth restricted to this case. For this function number, the SNAC header is assumed to be followed immediately by a word that serves as a subfunction number and then by an array of variably-sized data elements, which continue to the end of the packet.
Offset | Size | Description |
---|---|---|
00h | word | subfunction number |
02h | array of variably-sized data elements |
Each of these variably-sized data elements is given as a type, length and value:
Offset | Size | Description |
---|---|---|
00h | word | element type |
02h | word | size, in bytes, of element data that follows |
04h | number of bytes given at offset 02h | element data |
Again, the ordering of bytes within words is the opposite of the usual ordering on Intel machines.
Formal recognition is given one way or another to subfunction numbers 0001h to 0004h inclusive, but not until all the remaining packet data has been scanned as a series of data elements. The only elements of which PROTO takes lasting notice are those whose element type is 000Bh. For each such element, the element data that follows is copied to the one 0100h-byte buffer on the stack and a null byte is then appended, as if to treat the copied data as a string. The copy is just assumed to fit, with room left for one more byte. Also, there is just the one buffer, as if to assume either that there is just the one element of type 000Bh or that only the last such element matters.
That the copy is just assumed to fit is a buffer overflow bug. This subroutine that handles channel 02h, group 0001h, function 0013h is hereafter called the buggy routine.
The Phil Bucking packet has subfunction 00FFh. There is then one data element, with type 000Bh and size 0118h bytes. Before discovering that subfunction 00FFh is unsupported, the buggy routine would copy the 0118h bytes of element data to the 0100h-byte buffer.
This buffer overflow is therefore the one whose existence is alleged in the Phil Bucking correspondence. PROTO simply has no defence against receiving a packet with channel 02h, group 0001h, function 0013h and a data element of type 000Bh that gives its size as 0100h bytes or more. Receipt of such a packet will corrupt memory on the stack beyond the end of the buffer.
To assist reviewers who do not easily read binary code as presented in terms of 80x86 assembly language mnemonics by a debugger or file dumping utility (such as Microsoft’s own DUMPBIN), there follows a C-language representation of the PROTO.OCM routine that has the buffer overflow bug. Lest the acknowledgement not be clear from the context of these several pages, the following text is a representation in the C programming language of presumably original work in the intellectual property of America Online. This representation is published here as an aid to critical review of that work.
#include <windows.h> /* Some declarations for SNAC access through OSCORE */ typedef PVOID SNACHANDLE; BOOL SNACGetWord (SNACHANDLE, WORD *); WORD SNACBytesRemaining (SNACHANDLE); VOID SNACSkipBytes (SNACHANDLE, WORD); BOOL SNACGetBlock (SNACHANDLE, WORD, CHAR *); /* Representation of buggy routine */ VOID __cdecl ProtoFunc0013h (PVOID Irrelevant, SNACHANDLE hSnac, DWORD Dword) { CHAR buf [0x0100]; WORD subfunc; WORD type; WORD size; /* Read the subfunction number. Simply assume it is there (i.e., do not check SNACGetWord for its indication of success or failure). */ SNACGetWord (hSnac, &subfunc); /* For as long as there is more packet data, process it. */ while (SNACBytesRemaining (hSnac) > 0) { /* Whenever there is more packet data, interpret it as another data element. Read the element type and the size of the element data. Simply assume that they are there (i.e., do not check SNACGetWord for its indication of success or failure). */ SNACGetWord (hSnac, &type); SNACGetWord (hSnac, &size); /* If the element type is anything other than 000Bh, the data that follows is of no interest: just skip it. */ if (type != 0x000B) { SNACSkipBytes (hSnac, size); } else { /* For element type 000Bh, copy the element data to a buffer on the stack. Simply assume that the data is there (i.e., do not check SNACGetBlock for its indication of success or failure). Simply assume that the data, plus a terminating null byte, will fit in the buffer. (This is the buffer overflow bug.) */ SNACGetBlock (hSnac, size, buf); buf [size] = '\0'; } } /* etc, including to interpret the subfunction number */ }
Note the several cases of not checking functions for success or failure. In the work of the rather many programmers who do not check for success or failure just as a point of basic discipline, buffer overflow bugs are no surprise.
Readers who do read 80x86 assembly-language mnemonics may want to follow for themselves the coding of the three routines described above:
The following table gives the Relative Virtual Address (RVA) of each routine, in each of the PROTO.OCM versions known to this study.
Version | Read Routine | Dispatch Routine | Buggy Routine |
---|---|---|---|
2.0 | 452D | 4299 | 41F8 |
2.1 | 453B | 42A7 | 4206 |
3.0 | 4A46 | 478E | 4560 |
So, the alleged buffer overflow bug does actually exist, not just as an inference from observations of the data flow, but actually as code that can be found in a disassembly and interpreted as an exercise in deduction. Next, consider AOL’s exploitation of the bug.