___*____$ 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 } }
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
ReplyDeleteChecking 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.
DeleteThanks 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.
ReplyDeleteGreat analysis Alexander - thanks for sharing!
ReplyDelete