Base91 & Angler SWFs

If anyone is curious the encoding that Angler is using in their SWFs is base91. The encoding was hinted at in an excellent article by Palo Alto Networks but was only identified as a function named DecodeToByteArray. Below are my notes to decode and decompress the embedded SWF. 

___*____$ swfextract c34266299460225c0354df5438417924579641095ffd7588a42d8fae07ae8511 
Objects in file c34266299460225c0354df5438417924579641095ffd7588a42d8fae07ae8511:
 [-i] 1 MovieClip: ID(s) 4
 [-F] 1 Font: ID(s) 1
 [-b] 1 Binary: ID(s) 5
 [-f] 1 Frame: ID(s) 0

___*____$ swfextract c34266299460225c0354df5438417924579641095ffd7588a42d8fae07ae8511 -b 5
___*____$ ls
c34266299460225c0354df5438417924579641095ffd7588a42d8fae07ae8511  output.bin  xxxswf.py
 
___*____$ hexdump -C output.bin | head

00000000  40 5a 7a 55 7b 5a 78 30  46 3b 49 26 52 48 43 5d  |@ZzU{Zx0F;I&RHC]|
00000010  40 62 66 40 40 6d 32 7b  59 25 52 5d 75 75 62 55  |@bf@@m2{Y%R]uubU|
00000020  59 4d 53 61 30 34 2a 76  7b 5e 21 74 39 5a 5b 7d  |YMSa04*v{^!t9Z[}|
00000030  62 3f 38 42 3d 5f 51 6b  24 5b 23 3a 50 2c 2c 5e  |b?8B=_Qk$[#:P,,^|
00000040  22 7b 6e 6b 23 69 21 48  2b 35 54 60 24 22 2e 36  |"{nk#i!H+5T`$".6|
00000050  58 6c 75 6d 6d 4c 54 67  48 28 5a 6a 44 4b 30 63  |XlummLTgH(ZjDK0c|
00000060  37 2a 23 3f 53 78 6c 57  4a 67 68 60 48 45 76 67  |7*#?SxlWJgh`HEvg|
00000070  35 2e 79 4a 35 3c 46 6c  5b 47 46 3f 79 42 30 47  |5.yJ5<Fl[GF?yB0G|
00000080  35 6d 3c 67 2c 54 7b 59  42 2b 6a 4f 50 2b 3b 65  |5m<g,T{YB+jOP+;e|
00000090  79 26 26 3c 30 7c 65 59  7a 59 5e 57 22 4b 72 4b  |y&&<0|eYzY^W"KrK|

While reviewing the data I noticed all of the bytes were valid ASCII. This usually infers base64 but the characters '@'' or '$' meant it must be a modified version it. A mistake I made after deobfuscating the ActionScript was I only cursory looked at the decoder. The code and data had the patterns of base64 and I blindly assumed it was. If it was a modified version of base64 I could reconstruct all the chars from the table. This can be done by reading each character from the data into a set. From there I would need to find the right sequence of chars. Strangely, this hackish approach lead me to the encoding.

In [1]: f = open("output.bin", "rb")

In [2]: d = f.read()

In [3]: o = set([])

In [4]: for x in d:
            o.add(x)

In [5]: "".join(sorted(o))
Out[5]: '!"#$%&()*+,./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~'

In [6]: len(o)
Out[6]: 91

91?  As in Base91? Weird, never heard of that. A search lead me to some code written by Adrien Beraud which confirmed the ActionScript is indeed Base91.

After the data is decoded with base91 each byte is XORed. Initially the key is set to a hard coded value then the key becomes the previous byte that was encoded. Once the XOR loop is completed it is decompressed with zlib. The initial XOR key is not static. In PAN's write up the key is 91 and in mine it was 75. The key can be found with a decompiler (Trillix or JPEXS) or a disassembler (swfdump). The later can be done to extract the XOR key from the command line. swfdump -a can be used to get the assembly of the ActionScript. Searching for bitxor and pushint should provide the XOR key.

___*____$ swfdump -a c34266299460225c0354df5438417924579641095ffd7588a42d8fae07ae8511 > asm.as
___*____$ swfdump vi asm.as

        00045) + 2:1 callpropvoid <q>[public]::I1lllIII111I11, 1 params
        00046) + 0:1 pushint 75   <- KEY
        00047) + 1:1 convert_u
        00048) + 1:1 setlocal r4
        00049) + 0:1 pushint 0
        00050) + 1:1 setlocal r5
        00051) + 0:1 label
        00052) + 0:1 getlocal r5
        00053) + 1:1 getlocal r3
        00054) + 2:1 getlocal_0
        00055) + 3:1 getproperty <q>[private]::1Ill1III111I11
        00056) + 3:1 getproperty <q>[public]::+ll1III111I11
        00057) + 3:1 getproperty <l,multi>{[public]""}
        00058) + 2:1 lessthan
        00059) + 1:1 iffalse ->81
        00060) + 0:1 getlocal r3
        00061) + 1:1 getlocal r5
        00062) + 2:1 getproperty <l,multi>{[public]""}
        00063) + 1:1 getlocal r4
        00064) + 2:1 bitxor       <- XOR
        00065) + 1:1 convert_u


Quickly written Python code for decoding and extracting the second SWF. The key will likely need to be modified.

# The Base91 code is written by Adrien Beraud
# https://github.com/aberaud/base91-python/blob/master/base91.py

# Base91 encode/decode
#
# Copyright (c) 2012 Adrien Beraud
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions and the following disclaimer in the documentation
#     and/or other materials provided with the distribution.
#   * Neither the name of Adrien Beraud, Wisdom Vibes Pte. Ltd., nor the names
#     of its contributors may be used to endorse or promote products derived
#     from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#  

import struct
import sys
import zlib

base91_alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '#', '$',
 '%', '&', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=',
 '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '"']

decode_table = dict((v,k) for k,v in enumerate(base91_alphabet))

def decode(encoded_str):
    ''' Decode Base91 string to a bytearray '''
    v = -1
    b = 0
    n = 0
    out = bytearray()
    for strletter in encoded_str:
        if not strletter in decode_table:
            continue
        c = decode_table[strletter]
        if(v < 0):
            v = c
        else:
            v += c*91
            b |= v << n
            n += 13 if (v & 8191)>88 else 14
            while True:
                out += struct.pack('B', b&255)
                b >>= 8
                n -= 8
                if not n>7:
                    break
            v = -1
    if v+1:
        out += struct.pack('B', (b | v << n) & 255 )
    return out

def main():
    f = open(sys.argv[1], 'rb')
    x = f.read()
    d = decode(x)
    dd = ""
    key = 75
    for y in d:
        dd += chr(y ^ key)
        key = y
    o = zlib.decompress(dd)
    kk = open( sys.argv[1] + "-out.bin", "wb")
    kk.write(o)
    kk.close()

main()

output.bin is the binary data extracted using swfextract. The above Python code is stored in angler-decoder.py. After running the script the decoded SWF is saved to output.bin-out.bin. Then I use xxxswf.py to verify the SWF is present.


___*____$ python angler-decoder.py output.bin 
___*____$ ls
angler-decoder.py  c34266299460225c0354df5438417924579641095ffd7588a42d8fae07ae8511  output.bin  output.bin-out.bin  xxxswf.py
___*____$ python xxxswf.py output.bin-out.bin 

[SUMMARY] Potentially 1 SWF(s) in MD5 d41d8cd98f00b204e9800998ecf8427e:output.bin-out.bin
 [ADDR] SWF 1 at 0x0 - CWS Header
___*____$ python xxxswf.py -d output.bin-out.bin 

[SUMMARY] Potentially 1 SWF(s) in MD5 d41d8cd98f00b204e9800998ecf8427e:output.bin-out.bin
 [ADDR] SWF 1 at 0x0 - CWS Header
  [FILE] Carved SWF MD5: 5d4c794c3a3011da71cc31d5fd7015ce.swf

The extracted second SWF is also obfuscated.  

My "cleaned up" ActionScript

package 
{
    import flash.display.*;
    import flash.system.*;

    public class ExtendedMovieClipFunction extends MovieClip
    {
        private var DEUNCOMPRESSED_BUFFER:Object;
        private var _CLASS_BUFFER:Class;
        private var FuncNameToStrInstance:AssignFuncNameToString;
        private var int_0:uint = 0;
        private var _uint_0:uint = 0;
        private var _uint_255:uint = 255;
        private var _object2:Object;
        private var _object3:Object;

        public function ExtendedMovieClipFunction(param1:Object = null)
        {
            this.FuncNameToStrInstance = new AssignFuncNameToString();
            #  a SWF file from other domains than that of the Loader object can call Security.allowDomain() to
            #  permit a specific domain
            Security[this.FuncNameToStrInstance.allowDomain]("*");
            var _loc_3:* = ApplicationDomain[this.FuncNameToStrInstance.currentDomain];
            var  ldr:Loader :* = _loc_3[this.FuncNameToStrInstance.getDefinition](this.FuncNameToStrInstance.flash.display.Loader) as Class;
            this.DEUNCOMPRESSED_BUFFER = new  ldr:Loader ;
            this._CLASS_BUFFER = _loc_3[this.FuncNameToStrInstance.getDefinition](this.FuncNameToStrInstance.flash.utils.ByteArray) as Class;
            
            ## The Stage class represents the main drawing area.
            if (this[this.FuncNameToStrInstance.stage])
            {
                this.FuncEventListener();
            }
            else
            {
                this[this.FuncNameToStrInstance.addEventListener](this.FuncNameToStrInstance.addedToStage, this.FuncEventListener);
            }
            return;
        }// end function

        public function 1_object1(param1:Object, param2:int) : void
        {
            param2++;
            return;
        }// end function

        private function FuncEventListener(param1:Object = null) : void
        {
            this[this.FuncNameToStrInstance.removeEventListener](this.FuncNameToStrInstance.addedToStage, this.FuncEventListener);
            this[this.FuncNameToStrInstance.addEventListener](this.FuncNameToStrInstance.enterFrame, this.I1111IIIlllIl1);
            var _loc_2:* = new ExtendedByteArrayFunction();
            var DECODE_BUFFER:* = new this._CLASS_BUFFER();
            this.CONSTRUCT_KEY();
            this.BASE91(_loc_2, _loc_2[this.FuncNameToStrInstance.length], DECODE_BUFFER);
            this.func_123(DECODE_BUFFER);
            var _loc_4:* = 75;
            var INDEX:* = 0;
            
            // XOR loop 
            if (INDEX < DECODE_BUFFER[this.FuncNameToStrInstance.length])
            {
                var _loc_6:* = DECODE_BUFFER[INDEX] ^ _loc_4;
                _loc_4 = DECODE_BUFFER[INDEX];
                DECODE_BUFFER[INDEX] = _loc_6;
                INDEX++;
                ;
            }
            # XORs the data then uncompresses 
            DECODE_BUFFER[this.FuncNameToStrInstance.uncompress]();
            this.DEUNCOMPRESSED_BUFFER[this.FuncNameToStrInstance.loadBytes](DECODE_BUFFER);
            this[this.FuncNameToStrInstance.addChild](this.DEUNCOMPRESSED_BUFFER);
            ;
            var _loc_8:* = null;
            return;
            ;
            return;
        }// end function

        private function I1111IIIlllIl1(param1) : void
        {
            if (this.currentFrame == 200)
            {
                this.I1ll1III111I11(new Number(2));
                return;
            }
            return;
        }// end function

        # Create Key  
        private function CONSTRUCT_KEY() : void
        {
            this._object2 = new this._CLASS_BUFFER();
            this._object3 = new this._CLASS_BUFFER();
            var _loc_2:* = 0;
            _loc_2 = 65;
            
            if (_loc_2 < 91)
            {
                this._object3[this.FuncNameToStrInstance.writeByte](_loc_2);
                _loc_2++;
                ;
            }
            _loc_2 = 97;
            
            if (_loc_2 < 123)
            {
                this._object3[this.FuncNameToStrInstance.writeByte](_loc_2);
                _loc_2++;
                ;
            }
            _loc_2 = 48;
            
            if (_loc_2 < 58)
            {
                this._object3[this.FuncNameToStrInstance.writeByte](_loc_2);
                _loc_2++;
                ;
            }
            _loc_2 = 33;
            
            if (_loc_2 < 48)
            {
                
                
                if (_loc_2 == 34 || _loc_2 == 39 || _loc_2 == 45)
                {
                }
                else
                {
                    this._object3[this.FuncNameToStrInstance.writeByte](_loc_2);
                }
                _loc_2++;
                ;
            }
            _loc_2 = 58;
            
            if (_loc_2 < 65)
            {
                this._object3[this.FuncNameToStrInstance.writeByte](_loc_2);
                _loc_2++;
                ;
            }
            _loc_2 = 91;
            
            if (_loc_2 < 97)
            {
                if (_loc_2 == 92)
                {
                }
                else
                {
                    this._object3[this.FuncNameToStrInstance.writeByte](_loc_2);
                }
                _loc_2++;
                ;
            }
            _loc_2 = 123;
            
            if (_loc_2 < 127)
            {
                this._object3[this.FuncNameToStrInstance.writeByte](_loc_2);
                _loc_2++;
                ;
            }
            this._object3[this.FuncNameToStrInstance.writeByte](34);
            
            var _loc_3:* = 0;
            _loc_3 = 0;
            
            if (_loc_3 < 255)
            {
                this._object2[_loc_3] = 255;
                _loc_3++;
                ;
            }
            _loc_3 = 0;
            
            if (_loc_3 < this._object3[this.FuncNameToStrInstance.length])
            {
                this._object2[this._object3[_loc_3]] = _loc_3;
                _loc_3++;
                ;
            }
            return;
        }// end function

        public function func_123(param1) : uint
        {
            var _loc_2:* = 0;
            if (this._uint_255 != 255)
            {
                param1[param1[this.FuncNameToStrInstance.length]] = this.int_0 | this._uint_255 << this._uint_0;
                _loc_2 = _loc_2 + 1;
            }
            return _loc_2;
            return;
        }// end function

        public function BASE91(param1, _length:uint, param3) : uint
        {
            var _loc_4:* = 0;
            var _loc_5:* = 0;
            var _int_8191:* = 8191;
            _INDEX = 0;
            
            # previously IF
            while (_INDEX < _length)
            {
                if (this._object2[param1[_INDEX]] == 255)
                {
                }
                else
                {
                    if (this._uint_255 == 255)
                    {
                        this._uint_255 = this._object2[param1[_INDEX]];
                    }
                    else
                    {
                        #   _uint_255 =  _uint_255 + * len(_object3)
                        this._uint_255 = this._uint_255 + this._object2[param1[_INDEX]] * this._object3[this.FuncNameToStrInstance.length];
                        this.int_0 = this.int_0 | this._uint_255 << this._uint_0;
                        
                        this._uint_0 = this._uint_0 + ((this._uint_255 & _int_8191) > 88 ? (13) : (// label, 14));
                        
                        # increament _loc_8
                        var _loc_8:* = _loc_5;
                        _loc_5 = _loc_5 + 1;
                        
                        # move to out buffer
                        param3[_loc_8] = this.int_0 & 255;
                        
                        this.int_0 = this.int_0 >> 8;
                        this._uint_0 = this._uint_0 - 8;
                        if (this._uint_0 > 7) goto 160;
                        this._uint_255 = 255;
                    }
                }
                _INDEX++;
                ;
            }
            return _loc_5;
            return;
        }// end function

    }
}


5 comments:

  1. Hi, I tried the same method and 2nd SWF is indeed obfuscated. But when i try to swfextract 2nd file i get 4 binary ? how can proceed

    ReplyDelete
    Replies
    1. Checking out the ActionScript code to figure out the algorithm for the second SWF will be your best bet. I'd also recommend opening up the binary files in a hex editor. You might be able to identify the encoding. Cheers.

      Delete
  2. Thanks loaded 2nd SWF (header FWS) in JPEXS shows 4 binary under binary object folder and more than 21 script where to start any help will be appreciated.

    ReplyDelete
  3. Great analysis Alexander - thanks for sharing!

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete