[Format] MP4
0x00. Intro¶
Stagefright는 Android에서 사용하는 미디어 라이브러리이다. 해당 라이브러리는 Android에서 MP3, MP4등 미디어 파일 재생시 사용되는데 악성 MP3, MP4를 분석하는 과정에서 발생하는 취약점에 의해 RCE(Remote Code Execution)이 가능한 취약점을 통틀어 Stagefright 취약점이라 한다. 또한 이와 관련해 CVE-2015-1538 등 CVE 번호 기준 8개의 취약점을 Stagefright v1.0 취약점이라 칭한다. 그런데 최근 Stagefright v2.0이 발견되어 다시 Stagefright 취약점이 이슈가 되고 있다.
따라서 Stagefright 라이브러리 취약점을 언급하기 이전에 미디어 파일인 MP3/MP4 파일 포맷을 먼저 확인해 보자.
- 연관 취약점
- StageFright 1.0
- StageFright 2.0
- 개발 언어 : Python 2.7.x
- 개발 환경 : ipython notebook
- Specification : ISO/IEC 14496-14
- 사용 도구 : AtomicParsely, DAE_MMF_Parser_V0.7.0
- 개발 목표
- MP4 파일 내부 구조를 이해하고 출력할 수 있다.
0x01. 기본 정보¶
( 참조 : Tansee - What is MP4 Format Video? )
MP4 파일 포맷은 ISO 미디어 파일 포맷을 기반으로 작성되었으며 MP4 이외에 다음과 같은 파일 포맷 또한 ISO 미디어 파일 포맷을 기반으로 한다.
- MP3/MP4
- JPEG2000
- AVC
- QT
- mov
- 3gp, 3g2
0x10. 전체 구조¶
ISO 미디어 파일 포맷은 기본적으로 "Box" (또는 atom) 단위로 구성되어 있으며 Big-Endian으로 작성되어 있다. 따라서 이러한 특징을 기준으로 코드를 작성하면 다음과 같다.
In [13]:
class Box:
def __init__(self):
self.SIZE = 0
def parseBox(self, *args, **kwargs):
data = kwargs["data"]
offset = kwargs["offset"]
parent = kwargs["parent"]
MEMBER_NAME = ("size type data")
MEMBER_PATTERN = ">1l4s%ds" % (read32(data, offset) - 8)
size = MapSize(MEMBER_PATTERN)
self.SIZE += size
out = Map("tmp", data[offset:offset + size], MEMBER_NAME, MEMBER_PATTERN)
name = "".join([c for c in out.type if (c.isalpha() == True) or (c.isdigit() == True)])
out = out._replace(type=name)
out = addNamedtuple(out.type, out, "offset", offset)
out = addNamedtuple(out.type, out, "parent", parent)
return out, offset + self.SIZE
In [14]:
class MP4:
def __init__(self):
self.depth = 0
self.mp4Info = {}
def printBox(self, out):
offset = "[0x%08x] " % out.offset
size = "[%08d] " % out.size
depth = " " * self.depth * 2
buffer = offset + size + depth
if self.depth == 0:
buffer += "[+]"
else:
buffer += "-"
buffer += " %s" % out.type
print buffer
def recursive(self, data, absOffset=0, parent=""):
out = None
offset = 0
while offset < len(data):
obj = Box()
out, offset = obj.parseBox(data=data, offset=offset, parent=parent)
out = out._replace(offset=absOffset + out.offset)
self.mp4Info[out.type] = out
self.printBox(out)
if len(out.data) >= 16:
name_size = 4
for name_pos in [4, 8, 12]:
name = readBinary(out.data, name_pos, name_size)
if name in ["mvhd", "iods", "trak", "mdia", "mdhd", "hdlr", "minf", "smhd", "dinf", "dref", "url ", "stbl", "stsd",
"mp4a", "esds", "stts", "stsz", "stsc", "stco", "ctts", "udta", "meta", "tkhd"]:
self.depth += 2
parent += "/%s" % out.type
tmp = self.recursive(out.data[name_pos -4:], out.offset + name_pos + name_size, parent)
self.depth -= 2
out = addNamedtuple("%s" % out.type, out, tmp.type, tmp)
break
return out
def parseMP4(self, fullname):
mp4 = file(fullname, "rb").read()
out = self.recursive(mp4)
return self.mp4Info
In [15]:
from pprint import pprint
from struct import unpack, calcsize
from collections import namedtuple
import os
def read32(data, offset):
return unpack(">l", data[offset:offset + 4])[0]
def readBinary(data, offset, size):
return data[offset:offset + size]
def readBinaryEx(data, offset, size):
return readBinary(data, offset, size), offset + size
def MapSize(member_pattern):
return calcsize(member_pattern)
def Map(struct_name, buffer, member_name, member_pattern):
return namedtuple(struct_name, member_name)._make(unpack(member_pattern, buffer))
def addNamedtuple(new_name, namedtuple1, add_member_name, add_member_value):
return namedtuple(new_name, namedtuple1._fields+(add_member_name, ))(*(namedtuple1 + (add_member_value,)))
def mergeNamedtuple(new_name, namedtuple1, namedtuple2):
return namedtuple(new_name, namedtuple1._fields + namedtuple2._fields)(*(namedtuple1 + namedtuple2))
In [16]:
fdir = r"C:\Users\amanaksu\Dropbox\Development\Format\sample"
fname = "1.mp4_"
fullname = os.path.join(fdir, fname)
print "================================================================"
print "File Offset Box Size Box Type"
print "----------------------------------------------------------------"
obj = MP4()
mp4Info = obj.parseMP4(fullname)
print "================================================================"
In [17]:
class subMP4:
def __init__(self):
self.info = None
def __repr__(self, info):
if self.__call__(info) == False:
return False
print "[+] %s (offset: %08d, size: %08d)" % (self.info.type, self.info.offset, self.info.size)
print " " * 2 + "* Data: ",
if len(self.info.data) > 48:
outData = self.info.data[:48]
else:
outData = self.info.data
pprint(outData)
keys = [k for k in self.info._fields if k not in ["size", "type", "data", "offset"]]
for k in keys:
print " " * 4 + "- %s: %s" % (k, eval("self.info.%s" % k))
class ftyp(subMP4):
def __init__(self):
subMP4.__init__(self)
self.info = None
def __call__(self, info):
try:
MEMBER_NAME = ("major_brand major_version")
MEMBER_PATTERN = ">4s1l"
offset = 0
size = MapSize(MEMBER_PATTERN)
tmp1 = Map("tmp1", info.data[offset:offset + size], MEMBER_NAME, MEMBER_PATTERN)
offset += size
comp_brand = []
comp_brand_size = 4
while offset < len(info.data):
val, offset = readBinaryEx(info.data, offset, comp_brand_size)
comp_brand.append(val)
tmp2 = addNamedtuple("tmp2", tmp1, "comp_brand", comp_brand)
self.info = mergeNamedtuple("%s" % info.type, info, tmp2)
return True
except:
return False
In [18]:
def PrintInfo(info):
return eval("%s().__repr__(info)" % info.type)
PrintInfo(mp4Info["ftyp"])
댓글
댓글 쓰기