AOL Bug Exploit: A Preliminary Report

The following report is adapted from e-mail sent to Noah Groth, Andrew Schulman, Richard Smith at 03:31 on Saturday 21st August 1999. In summary, it is asserted that:

Beyond the preceding introduction and one remark to follow, I have not attempted any speculation here regarding America Online or Microsoft or any other party involved in the allegations or in supposed confirmations. For now, I just say that I am unimpressed to varying depths with all of them.

Protocol Sketch

[We need some standard terminology and perhaps also a description in tables or pictures rather than words.]

From the AIM client’s perspective, much of the protocol centres on packets that begin with a 6-byte header. The first byte is necessarily 2Ah. The second byte is a packet type. There is then a word that serves as a serial number for the packet, and finally a word that gives the size, in bytes, of the packet’s contents.

Of concern here are packets of type 02h. For these, the packet header is followed by a SNAC header, generally of 10 bytes, being 3 words and a dword. For our purposes, the first two words may be taken as defining a SNAC type. The SNAC header is followed immediately by SNAC data, the format of which varies with the SNAC type. (The name SNAC is taken from the names of functions exported by OSCORE.DLL.)

Of concern here are SNAC headers whose first two words are 0001h and 0013h. For these, the SNAC data consists of a word followed optionally by data bundles. That first word is a bundle type. Each bundle that follows consists of two words plus some data. The first word is the data type. The second word gives the size, in bytes, of the data that follows.

In all the above, bytes have non-Intel ordering within words and dwords.

Packet Handling

The AIM module named PROTO.OCM has a C-language function that asks WinSock for data and then examines that data for packets beginning with a recognised 6-byte packet header. Let us name this function GetPackets.

When the GetPackets function is satisfied that it has a 6-byte packet header, it calls a subfunction that is to analyse the packet’s contents. Let us name this subfunction ProcessPacket.

When the ProcessPacket function sees that the packet type is 02h, it assumes that the packet’s contents begin with a SNAC header. (This sort of assumption is fairly typical of the coding. The packet header is checked fairly carefully for validity, but the packet’s further contents are pretty much assumed to be correctly formed.) If the first two words of the SNAC header are 0001h and 0013h, then further handling is passed to yet another subfunction. Let us name this subfunction Proto_13h.

The Proto_13h function assumes that the SNAC data is in the form sketched above. It assumes the presence of at least one word of SNAC data, described above as the bundle type. The function then proceeds to the bundles. For each bundle, at least two words are simply assumed to be present. The second word, no matter how implausible its value, is then taken to be the size of the data that follows. The data in a bundle is of interest to the Proto_13h function only if the data type is 000Bh. In this case, the function saves the data in a 0100h-byte buffer on the stack (and appends a null byte).

For reference and verifiability, the following table gives the addresses of the functions described above for each of the PROTO.OCM versions studied. Addresses are given on the assumption that PROTO.OCM is loaded at its preferred base address 11080000.

  2.0 2.1 3.0
GetPackets 1108452D 1108453B 11084A46
ProcessPacket 11084299 110842A7 1108478E
Proto_13h 110841F8 11084206 11084560

Call History

It is instructive to look at this sequence of calls in terms of its effect on the stack. We start where GetPackets prepares its call to ProcessPacket.

==== ESP when call to ProcessPacket prepared ==== 
4 arguments to ProcessPacket, irrelevant on return to GetPacket 
====
    return address from ProcessPacket to GetPacket
    ====
    EBP on entry to ProcessPacket
    ==== ebp while inside ProcessPacket ==== 
    44h bytes of local variables for ProcessPacket 
    ==== 
    3 arguments to Proto_13h, irrelevant on return to ProcessPacket 
    ====
        return address from Proto_13h to ProcessPacket
        ====
        EBP on entry to Proto_13h
        ==== ebp while inside Proto_13h ====
        08h bytes of local variables for Proto_13h
        ----
        0100h-byte local buffer for Proto_13h
        ====
        ESI on entry to Proto_13h
        ----
        EDI on entry to Proto_13h
        ==== ESP when bundle data read from packet to buffer ==== 

The ordinary path of return from Proto_13h would start with instructions:

pop     edi
pop     esi
leave
ret 

which brings execution back into ProcessPacket at the instruction immediately after the call to Proto_13h. The esi, edi and ebp registers are restored to the values they had immediately before the call to Proto_13h. The ProcessPacket function then removes the 3 Proto_13h arguments by executing something such as

add     esp,0Ch 

Alternative Return

Note that this usual path relies on two dwords to be correctly preserved on the stack during the execution of Proto_13h and of any subfunctions. These dwords are the ones described above as EBP on entry to Proto_13h and return address from Proto_13h to ProcessPacket. It is possible to return without knowing these dwords, but in exchange, one must know how much space to allow for the 3 arguments to Proto_13h and for the 44h bytes of local variables for ProcessPacket.

The following instructions produce exactly the same result as above

pop     edi
pop     esi
leave
add     esp,10h         ; omit RET and skip 3 arguments to Proto_13h
mov     ebp,esp
add     ebp,44h         ; skip local variables for ProcessPacket 

They leave the ProcessPacket function ready to return in its ordinary way to GetPackets.

Delayed Return

Suppose we do not want to return immediately to GetPackets. Suppose instead that we want to execute some different C-language function, passing it three arguments. While the stack and registers are set up for a return to GetPackets, we could do the following, were we able to just execute extra instructions. Note especially that ebp points to the place marked above as ebp while inside ProcessPacket.

mov     [ebp+10h],arg3
mov     [ebp+0Ch],arg2
mov     [ebp+08h],arg1
leave
jmp     different_function 

As far as the different function is concerned, it has 3 arguments on the stack and looks set to return to GetPackets. This different function will do whatever its work may be. When it is done, it returns to GetPackets. As far as GetPackets is concerned, it has just been returned to from ProcessPacket.

Summary

Let’s collect the instructions that return from Proto_13h to GetPackets by way of 1) not assuming preservation of an ebp and a return address on the Proto_13h stack and 2) executing some different function along the way.

pop     edi     \
pop     esi     - usual clean up in Proto_13h
leave           / 
add     esp,10h
mov     ebp,esp
add     ebp,44h
mov     [ebp+10h],arg3
mov     [ebp+0Ch],arg2
mov     [ebp+08h],arg1
leave
jmp     different_function 

Please remember this last set for later.

Buffer Overflow

You may ask what is this concern about not assuming preservation of an ebp and return address on the Proto_13h stack.

Look back carefully at the description of how the Proto_13h function processes the SNAC data. See that for data type 000Bh, the data that follows gets copied to a 0100h-byte buffer on the Proto_13h stack. However, the amount of data to copy is not limited to 0100h. The function copies as many bytes as seem to be declared by the SNAC data (plus a null byte, as if to terminate a string). If the SNAC data declares there to be 0110h bytes or more (including that null byte), then both the stacked ebp and the stacked return address get overwritten. They get corrupted.

This bug is remarkably common. [There is much scope for editorial comment here.]

The Phil Bucking Packet

[Insert history of communication to Richard Smith from Phil Bucking.]

It has been observed that some AIM clients receive packets with packet type 02h, SNAC header beginning with 0001h and 0013h, and then SNAC data with bundle type 00FFh followed by the one bundle with data type 000Bh, data size 0118h and then 0118h bytes of data.

This packet triggers the buffer overflow bug, corrupting two dwords of the Proto_13h function’s local variables, the stacked EBP and return address and two of the three Proto_13h arguments.

Despite this corruption, the AIM client appears to continue normally.

Let’s look at those 0118h bytes.

0000  83 C4 10 4F 8D 94 24 E4-FE FF FF 8B EC 03 AA F8
0010  00 00 00 90 90 90 90 8B-82 F0 00 00 00 8B 00 89
0020  82 4E 00 00 00 8B 4D 04-03 8A F4 00 00 00 8D 82
0030  42 00 00 00 89 45 10 B8-10 00 00 00 89 45 0C C9
0040  FF E1 00 01 00 20 00 00-00 00 00 00 00 04 00 00
0050  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
0060  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
0070  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
0080  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
0090  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
00A0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
00B0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
00C0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
00D0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
00E0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
00F0  19 10 08 11 29 EC FF FF-44 00 00 00 00 00 00 00
0100  FF 00 00 00 08 01 00 00-00 00 00 00 90 47 40 00
0110  F8 E9 EA FE FF FF 00 00 

They begin with 42h bytes that Phil Bucking described as “valid (and coherent) assembler”. At least after the above analysis, they may seem familiar:

add     esp,10h 
dec     edi 
lea     edx,[esp-0000011Ch] 
mov     ebp,esp 
add     ebp,[edx+000000F8h] 
nop 
nop 
nop 
nop 
mov     eax,[edx+000000F0h] 
mov     eax,[eax] 
mov     [edx+4Fh],eax 
mov     ecx,[ebp+04h] 
add     ecx,[edx+000000F4h] 
lea     eax,[edx+42h] 
mov     [ebp+10h],eax 
mov     eax,10h 
mov     [ebp+0Ch],eax 
leave 
jmp     ecx 
add     esp,10h 


mov     ebp,esp 
add     ebp,44h 










mov     [ebp+10h],arg3 

mov     [ebp+0Ch],arg2 
leave 
jmp     different_function 

Let us assume that the matching of the packet’s instructions with ours is no coincidence! Then the third instruction aligns edx to the start of the 0118h bytes. See then that the dword at offset F8h in those bytes has the value 00000044h, completing our match.

At the end of the sequence, we can see that our different function is prepared by taking the return address from ProcessPacket to GetPacket and adding the dword from offset F4h in the bytes. The value of this dword is FFFFEC29h, which is -13D7h. In observed versions of PROTO.OCM, adding this to the return address from ProcessPacket to GetPacket gives us the address of a function that PROTO.OCM uses for sending SNAC data, duly wrapped up with a packet header. Let us name that function SendSNACData.

Inspection of PROTO.OCM shows that this SendSNACData function takes three arguments. The first is the same sort of handle that is passed as the first of the ProcessPacket arguments. The second and third are respectively the length and address of the SNAC (both header and the data that follows the header).

Looking again at the code sequence from the Phil Bucking packet, we see that SendSNACData will get the same first argument as was passed to ProcessPacket, but that its second and third arguments will be respectively 10h and an address 42h bytes into the 0118h bytes.

Thus, executing this code as an alternate return from Proto_13h will have the effect of sending a SNAC (with packet header). The SNAC header and data will total 10h bytes and be taken from offset 42h in the original 0118h bytes. Thus, 0001h, 0020h, 0000h, 00000000h for the SNAC header, and 0004h and then the four bytes 8Bh, 44h, 24h, 04h for the data.

That last dword is not supplied in the original 0118h bytes but is prepared by instructions in the code sequence. Specifically, the dword supplied at offset F0h in the 0118h bytes is interpreted as an address from which to take a dword for insertion at offset 4Eh, where it becomes that last dword in the sent SNAC data. Note that the dword at offset F0h is 11081019h. This is an address very near the start of the PROTO.OCM code segment. The instruction there is

mov     eax,[esp+04h] 

with opcodes 8Bh, 44h, 24h, 04h.

Remaining

The above discussion explains how receipt of the Phil Bucking packet triggers the buffer overflow bug but lets PROTO.OCM resume execution instead of crashing. It also explains how PROTO.OCM responds to the Phil Bucking packet by sending a packet whose SNAC header begins with 0001h, 0020h—and it explains the slight difference between the SNAC header and data as sent and the bytes at offset 42h as originally received in the 0118h bytes.

All that is left is to explain how the code at the start of those 0118h bytes ever gets to execute.

The return address on the Proto_13h stack will have been corrupted from offset 010Ch in the 0118h bytes. When the Proto_13h function attempts its usual return, execution will instead go to the address that was supplied at offset 010Ch in the 0118h bytes. Were the buffer overflow an ordinary bug, with the corrupt return address being an essentially random value, the typical consequence would be a CPU exception and the termination of the AIM process.

To get the Phil Bucking packet to execute, the corrupt return address must be prepared specially. Note that the bytes immediately after offset 010Ch are a CLC and then a JMP back to the start of the 0118h bytes. Given this, the simplest way to get from the Proto_13h function’s RET instruction to the start of the 0118h bytes is for there to be a CALL ESP instruction at the corrupt return address.

The CALL ESP instruction is represented by the 2-byte opcode FFh, D4h. All that is required for AOL to execute the downloaded Phil Bucking packet on the AOL client’s machine is that AOL know of an address, whether code or data, at which the presence of the two bytes FFh and D4h is certain.

The most certain addresses in the address space of the AIM process are those of AIM.EXE itself, which will be loaded with a base address of 00400000. The resource section of AIM.EXE has some 20 occurrences of the 2-byte sequence FFh D4h. In the Phil Bucking packet, the corrupt return address is 00404790. For version 2.0.912 of the AIM client software, but not for version 2.1.1236, this corrupt return address is indeed that of a 2-byte sequence FFh D4h in the AIM.EXE resource section. .

This page was created on 23rd August 1999.

Copyright © 1999. Geoff Chappell. All rights reserved.

[Home][Programming Samples][Application Notes][Security Notes][Editorial][Consultation][Contacts]