Wednesday, January 28, 2009

Exploitable Userland NULL Pointer Dereference

Today I released a security advisory (TKADV2009-004) that describes the details of a very interesting vulnerability I found in FFmpeg. FFmpeg is a software solution to record, convert and stream audio and video. The FFmpeg libraries are used by a lot of popular software projects like VLC, Mplayer, Perian and Xine.

As I said, this vulnerability is quite interesting as it is another example for an exploitable NULL pointer dereference in an userland application.

The vulnerability

The initial vulnerability is a type conversion bug while converting an user controlled unsigned int value to a signed int.

..
fourxm->track_count = 0;
..
current_track = AV_RL32(&header[i + 8]);
..
The AV_RL32() macro in the above code snippet reads an unsigned int value from the media file and stores the value in the signed int variable "current_track".

Later on "current_track" is checked against another value. If "current_track" is greater than this value a heap buffer pointed to by "fourxm->tracks" gets (re)allocated.
..
fourxm->tracks = NULL;  <-- [4]
..
if (current_track + 1 > fourxm->track_count) {
  fourxm->track_count = current_track + 1;  <-- [1]
  if((unsigned)fourxm->track_count >= UINT_MAX /  <-- [3]
        sizeof(AudioTrack))
    return -1;
  fourxm->tracks = av_realloc(fourxm->tracks,
  fourxm->track_count * sizeof(AudioTrack));  <-- [2]
  if (!fourxm->tracks) {
    av_free(header);
    return AVERROR(ENOMEM);
  }
}
..
If "current_track" is greater than "fourxm->track_count" the user controlled value of "current_track" is stored into "fourxm->track_count" (see [1]). The value of "fourxm->track_count" is then used to calculate the size of the heap buffer (see [2]). To avoid an integer overflow, the size of "fourxm->track_count" is checked before the allocation takes place (see [3]).

Now what happens if we supply a value >= 0x80000000 for "current_track"? Well, as there is an unchecked type conversion between unsigned and signed, "current_track" will become negative. If "current_track" is negative, the if statement shown above will always return false and the heap buffer will never be allocated. This results in "fourxm->tracks" still pointing to NULL (see [4]).

Directly after the if statement the following write operations are performed:
..
fourxm->tracks[current_track].adpcm = AV_RL32(&header[i + 12]);
fourxm->tracks[current_track].channels = AV_RL32(&header[i + 36]);
fourxm->tracks[current_track].sample_rate = AV_RL32(&header[i+40]);
fourxm->tracks[current_track].bits = AV_RL32(&header[i + 44]);
..
As "fourxm->tracks" is pointing to NULL these write operations are leading to four classical NULL pointer dereferences. But as NULL is dereferenced by the user controlled value of "current_track" it is possible to write user controlled data to a wide range of memory locations.

Result:
NULL[current_track].offset = user_controlled_data;

As there are four write operations, four memory locations can be overwritten with arbitrary data.
Short summary:
  1. "fourxm->tracks" is initialized with NULL 
  2. The type conversion bug allows us to avoid the allocation of "fourxm->tracks" 
  3. "fourxm->tracks" still points to NULL 
  4. The resulting NULL pointer is then dereferenced by the user controlled value of "current_track" and four 32bit values of user controlled data are assigned to the dereferenced location(s). 
  5. It is therefore possible to overwrite four user controlled memory locations with four user controlled data bytes each.
As I said, that's a beautiful bug.

Exploitability

In my advisory I wrote: "A malicious party may exploit this issue to execute arbitrary code by overwriting a sensitive memory location (such as a GOT/IAT entry, a return address, buffer length or boolean variable)". To see if this nice bug is indeed exploitable I chose the following two test cases:
  • VLC 0.9.8a under Windows XP SP3
  • Mplayer under openSUSE 11.1
As VLC and Mplayer are both using the FFmpeg libraries they are vulnerable to this bug.

Result: Reliable EIP control confirmed under both test cases

Here is the result for VLC under Windows XP SP3:



Here is the result for Mplayer under openSUSE 11.1:
tk@linux-0skt:~/Documents/ffmpeg-checkout-2009-01-02> gdb mplayer
GNU gdb (GDB; openSUSE 11.1) 6.8.50.20081120-cvs
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i586-suse-linux".
For bug reporting instructions, please see:
<http://bugs.opensuse.org/>...
(no debugging symbols found)

(gdb) run ex_eip_control_mplayer.avi
Starting program: /usr/bin/mplayer ex_eip_control_mplayer.avi
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
[Thread debugging using libthread_db enabled]
(no debugging symbols found)
[...]
MPlayer dev-SVN-r27637-4.3-openSUSE Linux 11.1 (i686)-Packman (C) 2000-2008 MPlayer Team
CPU: Intel(R) Core(TM)2 Duo CPU     T7500  @ 2.20GHz (Family: 6, Model: 15, Stepping: 11)
CPUflags:  MMX: 1 MMX2: 1 3DNow: 0 3DNow2: 0 SSE: 1 SSE2: 1
Compiled with runtime CPU detection.
Can't open joystick device /dev/input/js0: No such file or directory
Can't init input joystick
mplayer: could not connect to socket
mplayer: No such file or directory
Failed to open LIRC support. You will not be able to use your remote control.

Playing ex_eip_control_mplayer.avi.
libavformat file format detected.

Program received signal SIGSEGV, Segmentation fault.
0x55555555 in ?? ()

(gdb) bt
#0  0x55555555 in ?? ()
#1  0x77777777 in ?? ()
Cannot access memory at address 0x6666666a

(gdb) i r
eax            0x0      0
ecx            0x8d4cce0        148163808
edx            0x160    352
ebx            0x806e13f6       -2140269578
esp            0xbfffcfbc       0xbfffcfbc
ebp            0x8998f38        0x8998f38
esi            0x160    352
edi            0x8d59db0        148217264
eip            0x55555555       0x55555555
eflags         0x10297  [ CF PF AF SF IF RF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     5
As a bonus I developed a full working exploit (executes calc.exe) using VLC in version 0.9.8a as an injection vector.

Here is the result under Windows XP SP3.
Roundup:
  1. This is a very nice bug ;) 
  2. Reliable EIP control confirmed while using Mplayer as injection vector under openSUSE 11.1 and VLC 0.9.8a under Windows XP SP3 
  3. Reliable code execution confirmed while using VLC 0.9.8a as injection vector under Windows XP SP3