├── .gitignore ├── README.md ├── RIG ├── landing.py └── rigswf.py ├── Sundown └── landing.py ├── angler └── landing.py ├── neutrino └── neutrino.py └── nuclear ├── chain.py └── landing.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ekdeco 2 | Scripts for dealing with various ek's 3 | -------------------------------------------------------------------------------- /RIG/landing.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import pprint 3 | import hashlib 4 | import argparse 5 | import sys 6 | import re 7 | import os 8 | import itertools 9 | 10 | apr = argparse.ArgumentParser(description='RIG landing page decoder') 11 | apr.add_argument('file', type=str, nargs='?', help='File path') 12 | apr.add_argument('-d', '--dir', help='Output dir', default='/tmp') 13 | apr.add_argument( 14 | '-o', '--out', help="prefix for filename used to store decoded chunks") 15 | args = None 16 | 17 | 18 | def clear_comments(d): 19 | return re.sub('/\*[^/]*\*/', '', d).replace('"+"', '') 20 | 21 | 22 | def decode_fdata(data): 23 | alphabet = ('za1sd0123456789abcdefghijklmnopqrstuvwxyz' 24 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[5:]) 25 | mixtbl = ('za1sdLMNOPWXY3defghiQRSTUVjklmnABC012' 26 | 'DEFopq456789abcrstuvwxyzGHIJKZ'[5:]) 27 | r = ''.join([alphabet[mixtbl.index(c)] 28 | for c in data]).decode('hex').strip('\x00') 29 | x = ord(r[1]) ^ ord(';') 30 | r = ''.join(chr(ord(c) ^ x) for c in r) 31 | return r.strip("\x00") 32 | 33 | 34 | def write_payload(t, payload): 35 | if args and args.out: 36 | h = hashlib.sha256(payload).hexdigest() 37 | with open(args.dir + os.sep + '.'.join([args.out, h, t]), 'w') as f: 38 | f.write(payload) 39 | 40 | 41 | def extract_from_swfpage(d): 42 | r = {} 43 | data = d.split('"') 44 | r['flash_url'] = data[-6] 45 | r['flash_drop_1'] = data[-4] 46 | r['drop_key'] = data[-2] 47 | return r 48 | 49 | 50 | def do_rige(d): 51 | r = {'vb_drops': []} 52 | for s in re.findall('', d, re.S): 53 | # if not s: continue 54 | 55 | d = clear_comments(s) 56 | xxx = re.findall( 57 | '"([/A-Za-z0-9=+]+)"\.replace\((/|"|\')([^/"\']+)("|/|\')', d) 58 | if xxx: 59 | basep, _, g, _ = xxx[0] 60 | else: 61 | # last month changes... 62 | basep = re.findall('"([/A-Za-z0-9=+]{65,})";', d)[0] 63 | g = '' 64 | payload = clear_comments(basep.replace(g, '').decode('base64')) 65 | if 'Dim' in payload: 66 | x = 'http' + re.findall('"http(.*?)"', payload)[0] 67 | r['vb_drops'].append(x) 68 | t = 'exploit.vbs' 69 | elif 'application/x-shockwave-flash' in payload: 70 | d = payload.split(';')[-2].split('"') 71 | flash_url = d[1] 72 | r['flash_url'] = flash_url 73 | try: 74 | id, key, url = decode_fdata(d[-2]).split(';') 75 | r['flash_drop_%s' % id] = url 76 | r['drop_key'] = key 77 | except: 78 | r.update(extract_from_swfpage(payload)) 79 | t = 'flash_drop.html' 80 | else: 81 | t = 'exploit.js' 82 | 83 | write_payload(t, payload) 84 | r['type'] = 'rig-e' 85 | return r 86 | 87 | 88 | def do_rigv(d): 89 | r = {'vb_drops': []} 90 | # this gonna be fun... 91 | for s in re.findall('', d, re.S): 92 | 93 | vars = re.findall('="([^"]+)".split\((\'|")([^\'"]+)(\'|")', s) 94 | repl = re.findall('="(.+)";for', s)[0] 95 | x = [] 96 | for v, _, e, _ in vars: 97 | x.append(v.split(e)) 98 | payload = ''.join( 99 | a + b for a, b in zip(x[1], itertools.cycle(x[0][::-1]))) 100 | for i in range(0, len(repl) - 4, 2): 101 | payload = payload.replace(repl[i], repl[i + 1]) 102 | 103 | x = re.findall('var [a-z]+ = ([^;,]+)(;|,)', payload)[0][0] 104 | payload = ast.literal_eval(''.join(x.split("'+'"))).decode('base64') 105 | if 'redim' in payload.lower(): 106 | x = 'http' + re.findall('"http(.*?)"', payload)[0] 107 | r['vb_drops'].append(x) 108 | t = 'exploit.vbs' 109 | 110 | elif 'application/x-shockwave-flash' in payload: 111 | r.update(extract_from_swfpage(payload)) 112 | t = 'flash_drop.html' 113 | 114 | else: 115 | t = 'exploit.js' 116 | 117 | write_payload(t, payload) 118 | r['type'] = 'rig-v' 119 | 120 | return r 121 | 122 | 123 | def doit(d): 124 | try: 125 | r = do_rige(d) 126 | except Exception as e: 127 | print repr(e) 128 | r = do_rigv(d) 129 | return r 130 | 131 | 132 | if __name__ == '__main__': 133 | args = apr.parse_args() 134 | with open(sys.argv[1]) as f: 135 | d = f.read() 136 | r = doit(d) 137 | pprint.pprint(r) 138 | -------------------------------------------------------------------------------- /RIG/rigswf.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import struct 4 | from StringIO import StringIO 5 | from swf.stream import SWFStream 6 | from swf.movie import SWF, SWFHeader 7 | 8 | from Crypto.Cipher import ARC4 as RC4 9 | from Crypto.Cipher import AES 10 | 11 | 12 | def get_keys(data): 13 | f = StringIO(data) if isinstance(data, basestring) else data 14 | ret = [] 15 | for i in range(0, ord(f.read(1))): 16 | ret.append(f.read(16)) 17 | return ret 18 | 19 | 20 | class xSWF(SWF): 21 | 22 | class SS(SWFStream): 23 | 24 | def _read_bytes_aligned(self, bytes): 25 | buf = map(ord, self.f.read(bytes)) 26 | return reduce(lambda x, y: x << 8 | y, buf, 0) 27 | 28 | @property 29 | def binary_data(self): 30 | if not hasattr(self, '_bd'): 31 | self._bd = self.build_dictionary() 32 | return self._bd 33 | 34 | @property 35 | def symbols(self): 36 | if not hasattr(self, '_sc'): 37 | for s in self.tags: 38 | if s.name == 'SymbolClass': 39 | self._sc = s 40 | break 41 | return self._sc.symbols 42 | 43 | @property 44 | def script(self): 45 | if not hasattr(self, '_s'): 46 | for s in self.tags: 47 | if s.name in ['DoABC', 'DoAction']: 48 | self._s = s 49 | break 50 | return self._s 51 | 52 | @property 53 | def strings(self, data=None): 54 | """get a list of strings from abcFile, whose length larger than 5 55 | """ 56 | if not hasattr(self, '_ss'): 57 | self._ss = re.findall('[a-zA-Z0-9]{5,}', data or self.script.bytes) 58 | return self._ss 59 | 60 | def tag_by_name(self, name): 61 | for s in self.symbols: 62 | if s.name.endswith(name): 63 | return self.binary_data[s.tagId] 64 | return None 65 | 66 | # fix problem with pyswf... 67 | def parse(self, data): 68 | import io 69 | self._data = data = data if isinstance( 70 | data, SWFStream) else self.SS(data) 71 | self._header = SWFHeader(self._data) 72 | if self._header.compressed: 73 | temp = io.BytesIO() 74 | if self._header.compressed_zlib: 75 | import zlib 76 | data = data.f.read() 77 | zip = zlib.decompressobj() 78 | temp.write(zip.decompress(data)) 79 | else: 80 | import pylzma 81 | data.readUI32() # consume compressed length 82 | data = data.f.read() 83 | temp.write(pylzma.decompress(data)) 84 | temp.seek(0) 85 | data = self.SS(temp) 86 | self._header._frame_size = data.readRECT() 87 | self._header._frame_rate = data.readFIXED8() 88 | self._header._frame_count = data.readUI16() 89 | self.parse_tags(data) 90 | 91 | 92 | class RIG(xSWF): 93 | # assume 94 | # 1 - strings 95 | # 2 - key for string 96 | # 3 - shellcode 97 | # 4 - key for shellcode 98 | 99 | def read_blob(self, data): 100 | f = StringIO(data) if isinstance(data, basestring) else data 101 | cnt = struct.unpack('>I', f.read(4))[0] 102 | for i in range(cnt): 103 | size = struct.unpack('>I', f.read(4))[0] 104 | yield f.read(size) 105 | 106 | @property 107 | def type(self): 108 | if not hasattr(self, '_type'): 109 | self._type = 'rig-v' if len(rig.binary_data) == 2 else 'rig-e' 110 | return self._type 111 | 112 | @property 113 | def enc_strings(self): 114 | if not hasattr(self, 'rc4keys'): 115 | self.rc4keys = get_keys(self.binary_data[2].data) 116 | if not hasattr(self, '_enc_strings'): 117 | s = [] 118 | for i, _s in enumerate(self.read_blob(self.binary_data[1].data)): 119 | s.append(RC4.new(self.rc4keys[i % 120 | len(self.rc4keys)]).decrypt(_s)) 121 | self._enc_strings = s 122 | return self._enc_strings 123 | 124 | def get_clean_shellcode(self): 125 | if not hasattr(self, '_shellcode'): 126 | raise Exception('[-] decrypt it frist') 127 | if self._shellcode[9:13] != "\x49\x80\x34\x08": 128 | raise Exception('[-] Diffrent obfuscation your are on your own') 129 | 130 | xorb = ord(self._shellcode[13]) 131 | size = struct.unpack('I', self._shellcode[5:9])[0] 132 | hdr = "\x90" * 0x19 133 | sh = ''.join( 134 | chr(ord(self._shellcode[0x19 + i]) ^ xorb) for i in range(size)) 135 | rest = self._shellcode[0x19 + size:] 136 | self.shellcode = hdr + sh + rest 137 | 138 | @property 139 | def config(self): 140 | if self.type == 'rig-v': 141 | print '[-] for know i dont kwnow where config is' 142 | return None 143 | 144 | if not hasattr(self, '_cfgkeys'): 145 | self._cfgkeys = get_keys(self.binary_data[4].data) 146 | 147 | if not hasattr(self, '_config'): 148 | cfg = list(self.read_blob(self.binary_data[3].data)) 149 | if len(cfg) == 1: 150 | print '[*] new version, only shelcode is here' 151 | self._config = None 152 | self._shellcode = RC4.new( 153 | self._cfgkeys[0]).decrypt(cfg[0]).decode('hex') 154 | self.get_clean_shellcode() 155 | else: 156 | c = [] 157 | for i, x in enumerate(cfg): 158 | c.append( 159 | unpad(AES.new( 160 | self._cfgkeys[i % len(self._cfgkeys)]).decrypt(x))) 161 | self._config = c 162 | return self._config 163 | 164 | 165 | def unpad(d): 166 | return d[:-ord(d[-1])] 167 | 168 | 169 | if __name__ == '__main__': 170 | rig = xSWF(open(sys.argv[1])) 171 | data = rig.binary_data[1].data.decode('zlib') 172 | x = ord(data[1]) ^ ord('W') 173 | y = ord(data[2]) ^ ord('S') 174 | 175 | if x != y: 176 | print '[-] cant do it' 177 | sys.exit(-1) 178 | 179 | emb1 = 'CWS' + ''.join(map(lambda x: chr(ord(x) ^ y), data[3:])) 180 | with open('a.swf', 'w') as f: 181 | f.write(emb1) 182 | rig = xSWF(StringIO(emb1)) 183 | data = rig.binary_data[31].data.decode('zlib') 184 | key = data[-16:] 185 | data = ''.join((chr(ord(c) ^ ord(key[i % 16])) 186 | for i, c in enumerate(data[:-16].decode('base64')))) 187 | with open('b.swf', 'w') as f: 188 | f.write(data) 189 | rig = RIG(StringIO(data)) 190 | 191 | print rig.type 192 | print rig.enc_strings 193 | if rig.config: 194 | print rig.config 195 | with open('s.bin', 'w') as f: 196 | f.write(rig._shellcode) 197 | with open('sc.bin', 'w') as f: 198 | f.write(rig.shellcode) 199 | -------------------------------------------------------------------------------- /Sundown/landing.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import hashlib 4 | import argparse 5 | from lxml.etree import HTML 6 | from Crypto.Cipher import ARC4 as RC4 7 | 8 | def decode_payloads(js): 9 | funs = re.findall('function ([a-z]+)\(',js,re.I) 10 | data = key = None 11 | for f in funs: 12 | if data: 13 | key = re.findall(f + '\("([a-z0-9]+)",',js,re.I) 14 | else: 15 | data =re.findall(f + '\(\s*([a-z0-9]+)\s*\)\s*\)\s*;',js,re.I) 16 | data = re.findall('var\s+%s\s*=\s*"([a-zA-Z0-9+/=]+)";'%data[0],js)[0] 17 | 18 | if key and data: 19 | payload = RC4.new(key[0]).decrypt(data.decode('base64')) 20 | yield payload 21 | key = data = None 22 | 23 | def decode_first(d): 24 | h = HTML(d) 25 | inner_js=''.join(h.xpath('//div/text()')).replace('_','') 26 | inner_js=inner_js.replace('&','').replace('%','') 27 | inner_js=inner_js.replace('=','').replace('undefined','') 28 | inner_js=inner_js.decode('hex') 29 | return inner_js 30 | 31 | apr = argparse.ArgumentParser(description='Sundown landig page decoder') 32 | apr.add_argument('file',type=str, nargs='?', help='File path') 33 | apr.add_argument('-d','--dir',help='Output dir',default='/tmp') 34 | apr.add_argument('-s','--save',help='save exploits',default=False,action='store_true') 35 | args = None 36 | 37 | def doit(d): 38 | if ' old_l: 41 | ## this is the same script with encoded data... 42 | ## there are three posibilites as seens so far, 43 | ### 1. separet variables 44 | ### 2. array initialized index by index 45 | ### 3. array initialized at once 46 | 47 | for enc in re.findall("([_a-z0-9]+(\[[0-9]+\])?\s*=\s*('|\")[-_. %a-z0-9/+=:]+(\"|')\s*)(,|;)",scr,re.I): 48 | ## this covers 1 and 2 49 | var,val = enc[0].split('=',1) 50 | var = var.strip() 51 | val = val.strip()[1:-1] 52 | #print `var`,`val` 53 | print '[+] found posible configuration var', var 54 | ENC_DATA[var]=val 55 | for enc in re.findall("var\s+([_a-z0-9]+)\s+=\s+\[((\s*('|\")[-_. %a-z0-9/+=:]+(\"|'),?)+)\s*\];",scr,re.I): 56 | ### this takes care of 3rd case 57 | var = enc[0] 58 | val = enc[1] 59 | sep = enc[-1] + ',' + enc[-1] 60 | print '[+] found configuration table', var 61 | 62 | for i,e in enumerate(val.split(sep)): 63 | ENC_DATA[var+'[%d]'%i] = e.strip().strip(enc[-1]) 64 | 65 | 66 | # print '-'*20 67 | # print scr 68 | return r 69 | 70 | 71 | def xtea_decrypt_block(key,block,n=32,endian="<"): 72 | v0,v1 = struct.unpack(endian+"2L",block) 73 | k = struct.unpack(endian+"4L",key) 74 | delta,mask = 0x9e3779b9L,0xffffffffL 75 | sum = (delta * n) & mask 76 | for round in range(n): 77 | v1 = (v1 - (((v0<<4 ^ v0>>5) + v0) ^ (sum + k[sum>>11 & 3]))) & mask 78 | sum = (sum - delta) & mask 79 | v0 = (v0 - (((v1<<4 ^ v1>>5) + v1) ^ (sum + k[sum & 3]))) & mask 80 | return struct.pack(endian+"2L",v0,v1) 81 | def xtea_worker(f,data): 82 | _len = len(data) 83 | assert len(data)% 8 == 0 84 | return ''.join(map(f,chunks(data,8))) 85 | 86 | def xtea_decrypt(data,xkey): 87 | return xtea_worker(lambda c:xtea_decrypt_block(xkey,c),data) 88 | 89 | 90 | def shuffle(data,key): 91 | key = [ key.index(c) for c in sorted(key) ] 92 | ks = len(key) 93 | data += ' ' * (ks - len(data)%ks) 94 | r = [] 95 | for ch in chunks(data,ks): 96 | r.append(''.join([ ch[key[i]] for i in range(len(ch))])) 97 | return ''.join(r).strip() 98 | 99 | 100 | def method_0(text,key): 101 | txt = re.sub(r'\s+|\.','',text) 102 | txt = txt.decode('base64') 103 | txt = xor(txt,key.decode('base64')) 104 | return txt 105 | 106 | def method_1(text,key): 107 | txt = re.sub(r'\s+','%',text) 108 | txt = unquote(txt) 109 | txt = xtea_decrypt(txt,key[:16]) 110 | #@ print `txt` 111 | return txt 112 | 113 | def method_2(text,key): 114 | txt = dehtml(text.replace(u"\xa0",' ')) 115 | txt = shuffle(txt,key) 116 | # print txt 117 | return txt 118 | 119 | def method_3(text,key): 120 | key = key.decode('hex') 121 | text = text.decode('hex') 122 | return xor(text,key) 123 | 124 | def method_4(text,key): 125 | text = re.sub(r"(:+|=|\]\[)",'%',text.replace("\r\n",'')) 126 | text = re.sub('%[A-F0-9]{2}',lambda g: g.group(0)[1:].decode('hex'),text) 127 | text = xtea_decrypt(text,key[:16]) 128 | return text 129 | 130 | 131 | DEOBF_METHODS = sorted(filter(lambda x:x.startswith('method_'),globals())) 132 | def decode_page(t,k): 133 | # print globals() 134 | tlen = len(t) 135 | epsi = tlen /2 136 | for f in DEOBF_METHODS: 137 | f = globals()[f] 138 | try: 139 | x=f(t,k) 140 | #print len(t),len(x) 141 | ## there are some constant names in 142 | ## angler payload lets look for them 143 | if 'cryptKey' in x: 144 | return x 145 | elif 'getKolaio' in x: 146 | return x 147 | elif 'xTrue' in x and 'xFalse' in x: 148 | return x 149 | 150 | ## some basic heuristic.... 151 | # if tlen - len(x) < epsi: 152 | # print 'fop' 153 | # return x 154 | except: 155 | pass 156 | return None 157 | 158 | def get_num(x): 159 | return int(re.search('[0-9]+$',x).group(0)) 160 | 161 | 162 | if __name__ == '__main__': 163 | args = apr.parse_args() 164 | h = HTML(open(args.file).read().replace('
','')) 165 | key_var = None 166 | for key in get_keys(h): 167 | print '[*] testing key:',key 168 | stream = ''; txt = None 169 | for el in h.xpath('//*[@id or @ui or @di]'): 170 | if el.text: 171 | txt = decode_page(el.text,key) 172 | # print txt 173 | if not txt: 174 | continue 175 | 176 | if 'cryptKey' in txt: 177 | 178 | key_var = re.findall('var cryptKey = ([_a-z0-9]+(\[\s*[0-9]+\s*\])?),',txt,re.I)[0][0] 179 | key_var = re.sub('\s+','',key_var) 180 | print '[+] found key_var',key_var 181 | #txt = method_3(stream,key) 182 | #print txt 183 | if args.out: 184 | uid = el.attrib.get('id',None) 185 | uid = uid or el.attrib.get('di',None) 186 | uid = uid or el.attrib.get('ui',None) 187 | fname = os.path.join(args.dir,'.'.join([args.out,uid,'js'])) 188 | print '[*] saving decoded chunk to',fname 189 | with open(fname,'w') as f: f.write(txt) 190 | 191 | if key_var: break 192 | #print txt 193 | 194 | 195 | if not key_var or key_var not in ENC_DATA: 196 | print '[-] err cant find key variable' 197 | sys.exit(1) 198 | 199 | print '[+] decoding using %s as key' % ENC_DATA[key_var] 200 | 201 | #ENC_DATA[key_var] = ENC_DATA[key_var].decode('base64') 202 | for k in ENC_DATA: 203 | if k == key_var: continue 204 | if not key_var.startswith(k[:2]) and \ 205 | not key_var[1:].startswith(k[1:3]): 206 | continue 207 | 208 | v = ENC_DATA[k] 209 | data=shuffle(v,ENC_DATA[key_var]) 210 | try: 211 | data_b= unquote(data).decode('base64') 212 | except: 213 | data_b = None 214 | 215 | if data_b and isprintable(data_b): 216 | data = data_b 217 | print '[+] decoded data:', data 218 | 219 | 220 | 221 | ## this looks strange and needs more testing... 222 | # elif el.attrib.get('value',None): 223 | # if not re.match('^[0-9a-f]+$',key,re.I): 224 | # break 225 | # val = el.attrib['value'] 226 | # ns = stream+val 227 | # if len(ns)%2: 228 | # stream = ns 229 | # continue 230 | 231 | # tmp = method_3(ns,key) 232 | # if not isprintable(tmp): 233 | # txt = method_3(stream,key) 234 | # stream = val 235 | # else: 236 | # txt = '' 237 | # stream =ns 238 | -------------------------------------------------------------------------------- /neutrino/neutrino.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import zlib 4 | import json 5 | import hashlib 6 | import argparse 7 | import StringIO 8 | from swf.movie import SWF 9 | from Crypto.Cipher import ARC4 10 | 11 | rc4_decrypt = lambda d,k : ARC4.new(k).decrypt(d) 12 | apr = argparse.ArgumentParser(description='Neutrino swf decoder') 13 | apr.add_argument('file',type=str, nargs='?', help='File path') 14 | apr.add_argument('-d','--dir',help='Output dir',default='/tmp') 15 | apr.add_argument('-e','--exploits',help='save exploits',default=False,action='store_true') 16 | apr.add_argument('-i','--intermediate',help='save second swf',default=False,action='store_true') 17 | apr.add_argument('-s','--second',help='second swf',default=False,action='store_true') 18 | 19 | class Neutrino(SWF): 20 | 21 | 22 | @property 23 | def binary_data(self): 24 | if not hasattr(self,'_bd'): 25 | self._bd = self.build_dictionary() 26 | return self._bd 27 | 28 | 29 | @property 30 | def symbols(self): 31 | if not hasattr(self,'_sc'): 32 | for s in self.tags: 33 | if s.name == 'SymbolClass': 34 | self._sc = s 35 | break 36 | return self._sc.symbols 37 | 38 | @property 39 | def script(self): 40 | if not hasattr(self,'_s'): 41 | for s in self.tags: 42 | if s.name in ['DoABC','DoAction']: 43 | self._s = s 44 | break 45 | return self._s 46 | 47 | @property 48 | def strings(self): 49 | """get a list of strings from abcFile, whose length larger than 5 50 | """ 51 | if not hasattr(self,'_ss'): 52 | self._ss = re.findall('[a-zA-Z0-9]{5,}', self.script.bytes) 53 | return self._ss 54 | 55 | def tag_by_name(self,name): 56 | for s in self.symbols: 57 | if s.name.endswith(name): 58 | return self.binary_data[s.tagId] 59 | return None 60 | 61 | def get_exploits(self): 62 | for sym in self.symbols: 63 | if not self.binary_data.has_key(sym.tagId): 64 | continue 65 | n = sym.name[:sym.name.find('$')] 66 | d = rc4_decrypt(self.binary_data[sym.tagId].data,self.ek_key) 67 | if d[:3] not in ['ZWS','CWS','FWS']: 68 | try: 69 | d = zlib.decompress(d,-15) 70 | except Exception, e: 71 | pass 72 | yield n, d 73 | 74 | def get_keys(self, cfg=None): 75 | the_cfg = cfg if cfg else self.get_cfg() 76 | data_id = [ s.tagId for s in self.symbols if 'html_rc4' in s.name ][0] 77 | for k in self.strings: 78 | if not hasattr(self, 'ek_key'): 79 | try: 80 | d = rc4_decrypt(self.binary_data[data_id].data,k) 81 | d = zlib.decompress(d,-15) 82 | if '= 3: 114 | strs = re.findall('[a-zA-Z0-9]{5,}',s.script.bytes) 115 | beg = strs.index('writeBytes') 116 | old = True 117 | try: 118 | end = strs.index('getDefinitionByName') 119 | old= False 120 | except: 121 | end = strs.index('Loader') 122 | resources = [strs[beg-1]] + strs[beg+1:end] 123 | if old and len(resources) < 5: 124 | ## this is older version with one letter-names... 125 | idx = self.script.bytes.index('writeBytes') 126 | h= re.findall('([a-z])\nwriteBytes((\x01[a-z])+)\x06Loader',self.script.bytes,re.M)[0] 127 | resources = [h[0]] + h[1].split("\x01")[1:] 128 | 129 | swf_bytes = ''.join([ get_data(r) for r in resources]) 130 | 131 | for k in [self.binary_data[k].data for k in self.binary_data if len(self.binary_data[k].data) < 0x50]: 132 | d = rc4_decrypt(swf_bytes,k) 133 | if d[:3] in ['ZWS','CWS','FWS']: 134 | return d 135 | elif len(self.symbols) == 2 and ('Loader' in self.strings or self.symbols[0].name == 'EmbedFile'): 136 | # new version(2b19b310887bb1beac421843671e5541f2e369b2815fcc3dc4dfdfc71e059a0d) only has two symbols: 137 | # [, ] 138 | ### beter use funcion name which is using to set some bitmap, that is used to store payload 139 | ## see 7cb253e6997cf06e33eb8d0c74da9e10 140 | 141 | for k in self.strings: 142 | try: 143 | d = rc4_decrypt(self.binary_data[1].data, k) 144 | if d[:3] in ['ZWS','CWS','FWS']: 145 | return d 146 | except Exception, e: 147 | pass 148 | else: 149 | return None 150 | 151 | def get_cfg(self): 152 | for i in self.binary_data: 153 | x=self.binary_data[i].data[:3] 154 | try: 155 | return self.binary_data[i].data[3:int('0x'+x,16)+3] 156 | except: 157 | pass 158 | 159 | 160 | if __name__ == '__main__': 161 | args = apr.parse_args() 162 | s = Neutrino(open(args.file)) 163 | cfg_r = s.get_cfg() 164 | 165 | if not args.second: 166 | swf = s.get_second_swf() 167 | 168 | if not swf: sys.exit("[-] can't extract second swf, bailing") 169 | h = hashlib.sha256(swf).hexdigest() 170 | sys.stderr.write('[+] embeded swf (SHA256: %s) extracted'%h) 171 | if args.intermediate: 172 | p = '%s/%s.swf' % (args.dir,h) 173 | with open(p,'w') as f: f.write(swf) 174 | sys.stderr.write(',and saved to %s\n' % p) 175 | else: 176 | sys.stderr.write('\n') 177 | 178 | s2 = Neutrino(StringIO.StringIO(swf)) 179 | else: 180 | s2 = s 181 | 182 | if not s2.get_keys(cfg_r): 183 | print >> sys.stderr,'[-] failed to get keys' 184 | exit(-1) 185 | 186 | if not cfg_r: 187 | cfg_r = s2.get_cfg() # get config from 2nd layer swf 188 | print >> sys.stderr,'[+] cfg key: %s, exploit key: %s' % (s2.cfg_key,s2.ek_key) 189 | cfg = json.loads(rc4_decrypt(cfg_r,s2.cfg_key)) 190 | import pprint 191 | pprint.pprint(cfg) 192 | if args.exploits: 193 | for n, ek in s2.get_exploits(): 194 | h = hashlib.sha256(ek).hexdigest() 195 | p = '%s/%s_%s.ek.bin' % (args.dir,n,h) 196 | with open(p,'w') as f: f.write(ek) 197 | print >> sys.stderr, '[+] Exploit saved to %s' %p 198 | -------------------------------------------------------------------------------- /nuclear/chain.py: -------------------------------------------------------------------------------- 1 | import sys,os 2 | import hashlib 3 | #import requests 4 | import urlparse 5 | import argparse 6 | ### 7 | sys.path.append(os.path.dirname(__file__)) 8 | import landing 9 | import requesocks as requests 10 | 11 | 12 | apr = argparse.ArgumentParser(description='Nuclear payload downloader') 13 | apr.add_argument('url',type=str, nargs='?', help='fist url that is starting infection chain') 14 | apr.add_argument('-d','--dir',help='Output dir',default='/tmp') 15 | apr.add_argument('-o','--out',help="prefix for filename used to store decoded chunks") 16 | args = apr.parse_args() 17 | 18 | 19 | def xor(x,y): 20 | if len(x)>len(y): 21 | y = y * (len(x)/len(y)) 22 | return ''.join(map(lambda x: chr(ord(x[0])^ord(x[1])),zip(x,y))) 23 | 24 | 25 | 26 | 27 | UA = 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)' 28 | 29 | hdrs = {} 30 | #hdrs['Accept-Language']='pl' 31 | hdrs['Accept']='image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/x-ms-application, application/x-ms-xbap, application/vnd.ms-xpsdocument, application/xaml+xml, */*' 32 | hdrs['User-Agent'] = UA 33 | 34 | s = requests.Session() 35 | #s.proxies={'http':'http://localhost:8118','https':'http://localhost:8118'} 36 | 37 | s.headers = hdrs 38 | 39 | ## lets do it... 40 | BASE_URL = sys.argv[1] 41 | r=s.get(BASE_URL) 42 | if r.status_code == 444: 43 | print "[-] zed's dead, either blackisted ip or wrong country..." 44 | sys.exit(1) 45 | 46 | url = urlparse.urlparse(r.url) 47 | payload = {} 48 | ## decode landing 49 | for txt in landing.decode_first_js(r.content): 50 | payload.update(landing.analyze_payload(txt)) 51 | 52 | if 'encryption' not in payload: 53 | print '[-] sorry, i cant deal with it...' 54 | sys.exit(1) 55 | 56 | ## this looks like a bug in nuclerear we dont need to 57 | ## be exploited to get payload... 58 | if payload['encryption'] == 'xor': 59 | ## right now only xor is handled 60 | r = s.get(payload['sh-url']) 61 | if r.ok: 62 | data = xor(r.content,payload['sh-key']) 63 | h = hashlib.md5(data).hexdigest() 64 | if data[:2] == 'MZ': 65 | print '[+] hooray, we got our payload, saving to %s' 66 | with open(fname,'w') as f: 67 | f.write(data) 68 | 69 | elif payload['encryption'] == 'DH+rc4': 70 | print '[-] not dh for now' 71 | sys.exit(1) 72 | else: 73 | print '[-] imposible...' 74 | -------------------------------------------------------------------------------- /nuclear/landing.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | import struct 5 | import string 6 | import argparse 7 | from urllib import unquote 8 | 9 | from lxml.etree import HTML 10 | 11 | printset = set(string.printable) 12 | isprintable = lambda yourstring: set(yourstring).issubset(printset) 13 | xor = lambda xk,d : ''.join(map(lambda x: chr(ord(x)^xk),d)) 14 | 15 | apr = argparse.ArgumentParser(description='Nuclear landing page decoder') 16 | apr.add_argument('file',type=str, nargs='?', help='File path') 17 | apr.add_argument('-d','--dir',help='Output dir',default='/tmp') 18 | apr.add_argument('-o','--out',help="prefix for filename used to store decoded chunks") 19 | 20 | 21 | def get_off(h): 22 | r = [] 23 | for el in h.xpath('//script'): 24 | for hit in re.finditer("('|\")>('|\")\s*\)\s*\+\s*([0-9]+)",el.text): 25 | try: 26 | r.append(int(hit.group(3))-1) 27 | except: 28 | pass 29 | return r 30 | def decode64(p): 31 | for s in ['','=','==']: 32 | try: 33 | r = (p.replace('-','/').replace('_','+').replace('!','0')+s).decode('base64') 34 | return r 35 | except: 36 | pass 37 | return None 38 | 39 | 40 | def decode_payload(offsets,p): 41 | for off in offsets: 42 | r = decode64(p[off:]) 43 | if r and isprintable(r): 44 | return r 45 | return None 46 | 47 | def decode_bin_payload(sh): 48 | sh = sh.decode('hex') 49 | if sh.startswith("\x60\xeb\x11\x58\xb9"): 50 | off = struct.unpack('I',sh[5:9])[0]+4 51 | xk = ord(sh[off+0x19+1]) ^ ord(';') 52 | if ord(sh[off+0x19])^xk in [0x32,0x31]: 53 | r=xor(xk,sh[off+0x19:]).split("\x00")[0].split(';')[:3] 54 | return r 55 | return None 56 | 57 | def decode_shellcode(pay,r): 58 | for _line in pay.splitlines(): 59 | try: 60 | data = _line.split('=')[1].strip().split(';')[0][1:-1] 61 | if re.match("^[0-9A-Fa-f]+$",data): 62 | id,key,url = decode_bin_payload(data) 63 | if id == '1': 64 | typ = 'standalone' 65 | elif id == '2': 66 | typ = 'service' 67 | r[0]={'sh-key':key,'sh-url':url,'type':typ} 68 | print '[+] %s binary from %s - with key:%s' % (typ,url,key) 69 | return True 70 | except Exception as e: 71 | pass 72 | return False 73 | 74 | def analyze_payload(txt,args=None): 75 | enc = None 76 | ret = {} 77 | for line in txt.splitlines(): 78 | ext = None 79 | if line.startswith('var') and ("'" in line or '"' in line): 80 | try: 81 | if 'decodeBase64' in line: 82 | line = line.replace('decodeBase64(','').replace(')','') 83 | 84 | name,pay = line.split('=',1) 85 | pay = pay.strip()[1:-2].decode('base64') 86 | except: 87 | continue 88 | 89 | if 'class' in pay or 'Randomize' in pay or 'end sub' in pay or 'Dim' in pay: 90 | print '[*] found vbs payload' 91 | ext = 'vbs' 92 | 93 | elif 'catch' in pay: 94 | print '[*] found js payload' 95 | ext = 'js' 96 | r = [{}] 97 | if 'powMod' in pay and 'rc4' in pay and 'str2bigInt' in pay: 98 | enc = 'DH+rc4' 99 | elif decode_shellcode(pay,r): 100 | enc = 'xor' 101 | ret.update(r[0]) 102 | 103 | elif line.startswith('return') and re.match(r"^return\s+('|\")[A-Z0-9+/=]+(\"|');?$",line,re.I): 104 | data=line.split(' ',1)[1].lstrip(';')[1:-1].decode('base64') 105 | if data.startswith('http'): 106 | print '[+] payload-data',data 107 | 108 | elif line.startswith('flash_run'): 109 | data= line.lstrip(';')[11:-2].split(',') 110 | flsh_u = data[0][1:-1] 111 | param1 = decode64(data[1][1:-1])#.decode('base64') 112 | print 'flash-payload',flsh_u 113 | print 'p1 (%d)'%len(param1),`param1` 114 | 115 | if len(data) >2: 116 | param2 = unquote(data[2][1:-1]).decode('base64') 117 | ret['binary'] = param2 118 | print 'binary-url',param2 119 | 120 | ret['flash'] = flsh_u 121 | 122 | if ext and args and args.out: 123 | uid = name.split(' ')[1] 124 | fname = os.path.join(args.dir,'.'.join([args.out,'inner',uid,ext])) 125 | print '[*] saving decoded chunk to',fname 126 | with open(fname,'w') as f: f.write(pay) 127 | if enc: 128 | print '[*] payload delivery encryption:',enc 129 | ret['encryption']=enc 130 | return ret 131 | 132 | def decode_first_js(data): 133 | h = HTML(data) 134 | off = get_off(h) 135 | off.append(0) 136 | for el in h.xpath('//*[@id]'): 137 | if el.text: 138 | txt = decode_payload(off,el.text) 139 | if not txt: 140 | continue 141 | yield txt 142 | 143 | 144 | 145 | if __name__ == '__main__': 146 | args = apr.parse_args() 147 | for txt in decode_first_js(open(args.file).read()): 148 | if args.out: 149 | fname = os.path.join(args.dir,'.'.join([args.out,el.attrib['id'],'js'])) 150 | print '[*] saving decoded chunk to',fname 151 | with open(fname,'w') as f: f.write(txt) 152 | analyze_payload(txt,args) 153 | 154 | 155 | --------------------------------------------------------------------------------