Tuesday, January 27, 2009
After I told the FFmpeg maintainers about the bug, they developed the following patch:[42]
--- a/libavformat/4xm.c +++ b/libavformat/4xm.c @@ −166,12 +166,13 @@ static int fourxm_read_header(AVFormatContext *s, goto fail; } current_track = AV_RL32(&header[i + 8]);+ if((unsigned)current_track >= UINT_MAX / sizeof(AudioTrack) - 1){
+ av_log(s, AV_LOG_ERROR, "current_track too large\n");
+ ret= −1;
+ goto fail;
+ }
if (current_track + 1 > fourxm->track_count) { fourxm->track_count = current_track + 1;- if((unsigned)fourxm->track_count >= UINT_MAX / sizeof(AudioTrack)){
- ret= −1;
- goto fail;
- }
fourxm->tracks = av_realloc(fourxm->tracks, fourxm->track_count * sizeof(AudioTrack)); if (!fourxm->tracks) {
The patch applies a new length check that restricts the maximum value for current_track
to 0x09249247
.
(UINT_MAX / sizeof(AudioTrack) - 1) - 1 = maximum allowed value for current_track (0xffffffff / 0x1c - 1) - 1 = 0x09249247
When the patch is in place, current_track
can’t become negative, and the vulnerability is indeed fixed.
This patch eliminated the vulnerability at the source code level. There’s also a generic exploit mitigation technique that would make it much harder to exploit the bug. To gain control of the execution flow, I had to overwrite a memory location to gain control over EIP
. In this example, I used a GOT entry. The RELRO mitigation technique has an operation mode called Full RELRO that (re)maps the GOT as read-only, thus making it impossible to use the described GOT overwrite technique to gain control of the execution flow of FFmpeg. However, other exploitation techniques that are not mitigated by RELRO would still allow control over EIP
.
See Section C.2 for more information on the RELRO mitigation technique.
To make use of the Full RELRO mitigation technique, the FFmpeg binary would need to be recompiled with the following additional linker options: -Wl,-z,relro,-z,now
.
Example of recompiling FFmpeg with Full RELRO support:
linux$./configure --extra-ldflags="-Wl,-z,relro,-z,now"
linux$make
Get GOT entry of memalign()
:
linux$ objdump -R ./ffmpeg_g | grep memalign
0855ffd0 R_386_JUMP_SLOT posix_memalign
Adjust Example 4-1 and use brute force to get the value for current_track
:
linux$ ./addr_brute_force
Value for 'current_track': 806ab330
Make a new proof-of-concept file (poc_relro.4xm) and test it in the debugger (see Section B.4 for a description of the following debugger commands):
linux$gdb -q ./ffmpeg_g
(gdb)set disassembly-flavor intel
(gdb)run -i poc_relro.4xm
Starting program: /home/tk/BHD/ffmpeg_relro/ffmpeg_g -i poc_relro.4xm FFmpeg version SVN-r16556, Copyright (c) 2000-2009 Fabrice Bellard, et al. configuration: --extra-ldflags=-Wl,-z,relro,-z,now libavutil 49.12. 0 / 49.12. 0 libavcodec 52.10. 0 / 52.10. 0 libavformat 52.23. 1 / 52.23. 1 libavdevice 52. 1. 0 / 52. 1. 0 built on Jan 24 2009 09:07:58, gcc: 4.3.3 Program received signal SIGSEGV, Segmentation fault. 0x0809c89d in fourxm_read_header (s=0xa836330, ap=0xbfb19674) at libavformat/4xm.c:178 178 fourxm->tracks[current_track].adpcm = AV_RL32(&header[i + 12]);
FFmpeg crashed again while trying to parse the malformed media file. To see what exactly caused the crash, I asked the debugger to display the current register values as well as the last instruction executed by FFmpeg:
(gdb)info registers
eax 0xbbbbbbbb −1145324613 ecx 0xa83f3e0 176419808 edx 0x0 0 ebx 0x806ab330 −2140490960 esp 0xbfb194f0 0xbfb194f0 ebp 0x855ffc0 0x855ffc0 esi 0xa83f3a0 176419744 edi 0xa83f330 176419632 eip 0x809c89d 0x809c89d <fourxm_read_header+509> eflags 0x10206 [ PF IF RF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb)x/1i $eip
0x809c89d <fourxm_read_header+509>: mov DWORD PTR [edx+ebp*1+0x10],eax
I also displayed the address where FFmpeg had attempted to store the value of EAX
:
(gdb) x/1x $edx+$ebp+0x10
0x855ffd0 <_GLOBAL_OFFSET_TABLE_+528>: 0xb7dd4d40
As expected, FFmpeg tried to write the value of EAX
to the supplied address (0x855ffd0
) of memalign()
’s GOT entry.
(gdb)shell cat /proc/$(pidof ffmpeg_g)/maps
08048000-0855f000 r-xp 00000000 08:01 101582 /home/tk/BHD/ffmpeg_relro/ffmpeg_g0855f000-08560000 r--p 00516000 08:01 101582 /home/tk/BHD/ffmpeg_relro/ffmpeg_g
08560000-0856c000 rw-p 00517000 08:01 101582 /home/tk/BHD/ffmpeg_relro/ffmpeg_g 0856c000-0888c000 rw-p 0856c000 00:00 0 0a834000-0a855000 rw-p 0a834000 00:00 0 [heap] b7d60000-b7d61000 rw-p b7d60000 00:00 0 b7d61000-b7ebd000 r-xp 00000000 08:01 148202 /lib/tls/i686/cmov/libc-2.9.so b7ebd000-b7ebe000 ---p 0015c000 08:01 148202 /lib/tls/i686/cmov/libc-2.9.so b7ebe000-b7ec0000 r--p 0015c000 08:01 148202 /lib/tls/i686/cmov/libc-2.9.so b7ec0000-b7ec1000 rw-p 0015e000 08:01 148202 /lib/tls/i686/cmov/libc-2.9.so b7ec1000-b7ec5000 rw-p b7ec1000 00:00 0 b7ec5000-b7ec7000 r-xp 00000000 08:01 148208 /lib/tls/i686/cmov/libdl-2.9.so b7ec7000-b7ec8000 r--p 00001000 08:01 148208 /lib/tls/i686/cmov/libdl-2.9.so b7ec8000-b7ec9000 rw-p 00002000 08:01 148208 /lib/tls/i686/cmov/libdl-2.9.so b7ec9000-b7eed000 r-xp 00000000 08:01 148210 /lib/tls/i686/cmov/libm-2.9.so b7eed000-b7eee000 r--p 00023000 08:01 148210 /lib/tls/i686/cmov/libm-2.9.so b7eee000-b7eef000 rw-p 00024000 08:01 148210 /lib/tls/i686/cmov/libm-2.9.so b7efc000-b7efe000 rw-p b7efc000 00:00 0 b7efe000-b7eff000 r-xp b7efe000 00:00 0 [vdso] b7eff000-b7f1b000 r-xp 00000000 08:01 130839 /lib/ld-2.9.so b7f1b000-b7f1c000 r--p 0001b000 08:01 130839 /lib/ld-2.9.so b7f1c000-b7f1d000 rw-p 0001c000 08:01 130839 /lib/ld-2.9.so bfb07000-bfb1c000 rw-p bffeb000 00:00 0 [stack]
This time FFmpeg crashed with a segmentation fault while trying to overwrite the read-only GOT entry (see the r--p
permissions of the GOT at 0855f000-08560000
). It seems that Full RELRO can indeed successfully mitigate GOT overwrites.