-
Notifications
You must be signed in to change notification settings - Fork 9
/
pyi-deflate.py
executable file
·160 lines (145 loc) · 4.96 KB
/
pyi-deflate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/usr/bin/python
#
# Copyright (C) 2015 THREATSTREAM, Inc.
# This file is subject to the terms and conditions of the GNU General Public
# License version 2.
import re
import sys
import zlib
import subprocess
from pydoc import pager
try:
from PyInstaller.utils.cliutils import archive_viewer
except ImportError:
print('You must install PyInstaller >= 3.0 first. Hint: pip install PyInstaller')
exit()
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
__author__ = 'ThreatStream Labs'
__desc__ = 'Quickly extract Python Scripts from PyInstaller compiled binaries.'
__yara__ = '''
rule PyInstaller_Binary
{
meta:
author = "ThreatStream Labs"
desc = "Generic rule to identify PyInstaller Compiled Binaries"
strings:
$string0 = "zout00-PYZ.pyz"
$string1 = "python"
$string2 = "Python DLL"
$string3 = "Py_OptimizeFlag"
$string4 = "pyi_carchive"
$string5 = ".manifest"
$magic = { 00 4d 45 49 0c 0b 0a 0b 0e 00 }
condition:
all of them // and new_file
}
'''
normal_stdout = sys.stdout
def redirect_stdout():
'''Simply pulls in STDOUT so we can use PyInstallers tool instead of reinventing the wheel'''
if sys.stdout is normal_stdout:
sys.stdout = StringIO()
return
sys.stdout.seek(0)
r = sys.stdout.read()
sys.stdout = normal_stdout
return(r)
def patch_zlib(binary_data):
def zlibsearch(data):
r = []
zlib_header = b'\x78\x9c'
for offset in [zl.start() for zl in re.finditer(zlib_header, data)]:
r.append(offset)
return(r)
'''Observed: Some individuals manipulate zlib and magic fields to break extract. This helps.'''
r = ''
pyz = re.finditer(b'\x50\x59\x5a\x00', binary_data)
for line in pyz:
x = line.start() + 17
zlibcheck = binary_data[x:x + 2]
file_data = re.sub(zlibcheck, b'\x78\x9c', binary_data)
zlibs = zlibsearch(file_data)
for offset in zlibs:
f = file_data[offset:]
try:
test = zlib.decompress(f)
except:
continue
if (b'import ') in test:
if (b'\x01') in test:
continue
if (b'PyInst') in test or (b'pyi_') in test:
continue
r += test.decode()
return(r)
def malicious_check(script_contents):
'''Use this function to help determine if the script is malicious'''
critical = re.findall(
'ctypes|avlol|keylogger|backdoor|socket . socket|oo00oo',
script_contents, re.I) # spacing between period may indicate pyobfuscate
high = re.findall('hide|hidden', script_contents, re.I)
moderate = re.findall('socket.socket|base64.b64decode|subprocess', script_contents, re.I)
score = (len(critical) * 3 + len(high) * 2 + len(moderate))
if score >= 7:
return('Malicious')
if score >= 5:
return('Likely Malicious')
if score >= 3:
return('Potentially Malicious')
if score >= 2:
return('Highly Suspicious')
return('Likely Benign')
def screen_size():
try: # Get screen size (linux/osx/stdout redirect) to determine if we should page results.
rows = subprocess.Popen(['stty', 'size'], stdout=subprocess.PIPE).communicate()[0].split()[0]
except: # If we're unable to get screen size (windows), set it ourselves:
rows = 80
return(rows)
if __name__ == '__main__':
try:
filename = sys.argv[1]
except:
print('Usage: %s [filename]' % __file__)
sys.exit()
try:
fh = archive_viewer.get_archive(filename)
except Exception as err:
try:
manual_warn = ('# Looks like the package has been manipulated. Manually carved - maybe incomplete.')
manual_bin = open(filename, 'rb').read()
data = patch_zlib(manual_bin)
if data:
rating = malicious_check(data)
data = ('{}\n# Script: {} ({})\n'.format(
manual_warn, 'UNKNOWN_NAME', rating) +
'\n'.join(['\t' + indent for indent in data.split('\n')]))
rows = screen_size()
if len(data.split('\n')) > int(rows):
pager(data)
sys.exit()
print(data)
sys.exit()
except:
print('{}. Unable to carve anything manually.'.format(err))
sys.exit()
redirect_stdout()
archive_viewer.show(filename, fh)
d = redirect_stdout()
rows = screen_size()
scripts = [
x for x in re.findall('\([0-9]+, [0-9]+, [0-9]+, [0-9]+, \'s\', u?\'([^\']+)\'\)', d)
if 'pyi' not in x
]
for script_name in scripts:
x, data = fh.extract(script_name)
data = data.decode('utf-8')
rating = malicious_check(data)
if data:
data = '# Script: {} ({})\n'.format(script_name, rating) + '\n'.join(['\t' + indent for indent in data.split('\n')])
if len(data.split('\n')) > int(rows):
pager(data)
sys.exit()
print(data)