-
Notifications
You must be signed in to change notification settings - Fork 0
/
pear.rb
345 lines (323 loc) · 13.9 KB
/
pear.rb
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# frozen_string_literal: true
require 'os'
require 'pedump'
require 'digest/md5'
require 'pathname'
require 'action_view'
require 'colors'
include ActionView::Helpers::TextHelper
class Pear
attr_reader :path, :opts, :warnings
URL_REGEX = %r{\A(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:/[^\s]*)?\z}i.freeze
SCARY_RESOURCE_TYPES = %w[EXE DLL].freeze
# source: http://www.hexacorn.com/blog/2016/12/15/pe-section-names-re-visited/
POPULAR_SECTION_NAMES = {
'.00cfg': 'Control Flow Guard (CFG) section (added by newer versions of Visual Studio)',
'.apiset': 'a section present inside the apisetschema.dll',
'.arch': 'Alpha-architecture section',
'.autoload_text': 'cygwin/gcc; the Cygwin DLL uses a section to avoid copying certain data on fork.',
'.bindat': 'Binary data (also used by one of the downware installers based on LUA)',
'.bootdat': 'section that can be found inside Visual Studio files; contains palette entries',
'.bss': 'Uninitialized Data Section',
'.BSS': 'Uninitialized Data Section',
'.buildid': 'gcc/cygwin; Contains debug information (if overlaps with debug directory)',
'.CLR_UEF': '.CLR Unhandled Exception Handler section; see https: //github.com/dotnet/coreclr/blob/master/src/vm/excep.h',
'.code': 'Code Section',
'.cormeta': '.CLR Metadata Section',
'.complua': 'Binary data, most likely compiled LUA (also used by one of the downware installers based on LUA)',
'.CRT': 'Initialized Data Section (C RunTime)',
'.cygwin_dll_common': 'cygwin section containing flags representing Cygwin’s capabilities; refer to cygwin.sc and wincap.cc inside Cygwin run-time',
'.data': 'Data Section',
'.DATA': 'Data Section',
'.data1': 'Data Section',
'.data2': 'Data Section',
'.data3': 'Data Section',
'.debug': 'Debug info Section',
'.debug$F': 'Debug info Section (Visual C++ version <7.0)',
'.debug$P': 'Debug info Section (Visual C++ debug information: precompiled information)',
'.debug$S': 'Debug info Section (Visual C++ debug information: symbolic information)',
'.debug$T': 'Debug info Section (Visual C++ debug information: type information)',
'.drectve': 'directive section (temporary, linker removes it after processing it; should not appear in a final PE image)',
'.didat': 'Delay Import Section',
'.didata': 'Delay Import Section',
'.edata': 'Export Data Section',
'.eh_fram': 'gcc/cygwin; Exception Handler Frame section',
'.export': 'Alternative Export Data Section',
'.fasm': 'FASM flat Section',
'.flat': 'FASM flat Section',
'.gfids': 'section added by new Visual Studio (14.0); purpose unknown',
'.giats': 'section added by new Visual Studio (14.0); purpose unknown',
'.gljmp': 'section added by new Visual Studio (14.0); purpose unknown',
'.glue_7t': 'ARMv7 core glue functions (thumb mode)',
'.glue_7': 'ARMv7 core glue functions (32-bit ARM mode)',
'.idata': 'Initialized Data Section (Borland)',
'.idlsym': 'IDL Attributes (registered SEH)',
'.impdata': 'Alternative Import data section',
'.import': 'Alternative Import data section',
'.itext': 'Code Section (Borland)',
'.ndata': 'Nullsoft Installer section',
'.orpc': 'Code section inside rpcrt4.dll',
'.pdata': 'Exception Handling Functions Section (PDATA records)',
'.rdata': 'Read-only initialized Data Section (MS and Borland)',
'.reloc': 'Relocations Section',
'.rodata': 'Read-only Data Section',
'.rsrc': 'Resource section',
'.sbss': 'GP-relative Uninitialized Data Section',
'.script': 'Section containing script',
'.shared': 'Shared section',
'.sdata': 'GP-relative Initialized Data Section',
'.srdata': 'GP-relative Read-only Data Section',
'.stab': 'Created by Haskell compiler (GHC)',
'.stabstr': 'Created by Haskell compiler (GHC)',
'.sxdata': 'Registered Exception Handlers Section',
'.text': 'Code Section',
'.text0': 'Alternative Code Section',
'.text1': 'Alternative Code Section',
'.text2': 'Alternative Code Section',
'.text3': 'Alternative Code Section',
'.textbss': 'Section used by incremental linking',
'.tls': 'Thread Local Storage Section',
'.tls$': 'Thread Local Storage Section',
'.udata': 'Uninitialized Data Section',
'.vsdata': 'GP-relative Initialized Data',
'.xdata': 'Exception Information Section',
'.wixburn': 'Wix section; see https: //github.com/wixtoolset/wix3/blob/develop/src/burn/stub/StubSection.cpp',
'.wpp_sf ': 'section that is most likely related to WPP (Windows software trace PreProcessor); not sure how it is used though; the code inside the section is just a bunch of routines that call FastWppTraceMessage that in turn calls EtwTraceMessage',
'BSS': 'Uninitialized Data Section (Borland)',
'CODE': 'Code Section (Borland)',
'DATA': 'Data Section (Borland)',
'DGROUP': 'Legacy data group section',
'edata': 'Export Data Section',
'idata': 'Initialized Data Section (C RunTime)',
'INIT': 'INIT section (drivers)',
'minATL': 'Section that can be found inside some ARM PE files; purpose unknown; .exe files on Windows 10 also include this section as well; its purpose is unknown, but it contains references to ___pobjectentryfirst,___pobjectentrymid,___pobjectentrylast pointers used by Microsoft: : WRL: : Details: : ModuleBase: : … methods described e.g. here, and also referenced by .pdb symbols; so, looks like it is being used internally by Windows Runtime C++ Template Library (WRL) which is a successor of Active Template Library (ATL); further research needed',
'text': 'Alternative Code Section'
}.freeze
COMMON_PACKER_SECTION_NAMES = {
'.aspack': 'Aspack packer',
'.adata': 'Aspack packer/Armadillo packer',
'ASPack': 'Aspack packer',
'.ASPack': 'ASPAck Protector',
'.boom': 'The Boomerang List Builder (config+exe xored with a single byte key 0x77)',
'.ccg': 'CCG Packer (Chinese Packer)',
'.charmve': 'Added by the PIN tool',
'BitArts': 'Crunch 2.0 Packer',
'DAStub': 'DAStub Dragon Armor protector',
'!EPack': 'Epack packer',
'.ecode': 'Built with EPL',
'.edata': 'Built with EPL',
'.enigma1': 'Enigma Protector',
'.enigma2': 'Enigma Protector',
'FSG!': 'FSG packer (not a section name, but a good identifier)',
'.gentee': 'Gentee installer',
'kkrunchy': 'kkrunchy Packer',
'lz32.dll': 'Crinkler',
'.mackt': 'ImpRec-created section',
'.MaskPE': 'MaskPE Packer',
'MEW': 'MEW packer',
'.mnbvcx1': 'most likely associated with Firseria PUP downloaders',
'.mnbvcx2': 'most likely associated with Firseria PUP downloaders',
'.MPRESS1': 'Mpress Packer',
'.MPRESS2': 'Mpress Packer',
'.neolite': 'Neolite Packer',
'.neolit': 'Neolite Packer',
'.nsp1': 'NsPack packer',
'.nsp0': 'NsPack packer',
'.nsp2': 'NsPack packer',
'nsp1': 'NsPack packer',
'nsp0': 'NsPack packer',
'nsp2': 'NsPack packer',
'.packed': 'RLPack Packer (first section)',
'pebundle': 'PEBundle Packer',
'PEBundle': 'PEBundle Packer',
'PEC2TO': 'PECompact packer',
'PECompact2': 'PECompact packer (not a section name, but a good identifier)',
'PEC2': 'PECompact packer',
'pec': 'PECompact packer',
'pec1': 'PECompact packer',
'pec2': 'PECompact packer',
'pec3': 'PECompact packer',
'pec4': 'PECompact packer',
'pec5': 'PECompact packer',
'pec6': 'PECompact packer',
'PEC2MO': 'PECompact packer',
'PELOCKnt': 'PELock Protector',
'.perplex': 'Perplex PE-Protector',
'PESHiELD': 'PEShield Packer',
'.petite': 'Petite Packer',
'.pinclie': 'Added by the PIN tool',
'ProCrypt': 'ProCrypt Packer',
'.RLPack': 'RLPack Packer (second section)',
'.rmnet': 'Ramnit virus marker',
'RCryptor': 'RPCrypt Packer',
'.RPCrypt': 'RPCrypt Packer',
'.seau': 'SeauSFX Packer',
'.sforce3': 'StarForce Protection',
'.shrink1': 'Shrinker',
'.shrink2': 'Shrinker',
'.shrink3': 'Shrinker',
'.spack': 'Simple Pack (by bagie)',
'.svkp': 'SVKP packer',
'Themida': 'Themida Packer',
'.Themida': 'Themida Packer',
'.taz': 'Some version os PESpin',
'.tsuarch': 'TSULoader',
'.tsustub': 'TSULoader',
'PEPACK!!': 'Pepack',
'.Upack': 'Upack packer',
'.ByDwing': 'Upack Packer',
'UPX0': 'UPX packer',
'UPX1': 'UPX packer',
'UPX2': 'UPX packer',
'UPX3': 'UPX packer',
'UPX!': 'UPX packer',
'.UPX0': 'UPX Packer',
'.UPX1': 'UPX Packer',
'.UPX2': 'UPX Packer',
'.vmp0': 'VMProtect packer',
'.vmp1': 'VMProtect packer',
'.vmp2': 'VMProtect packer',
'VProtect': 'Vprotect Packer',
'.winapi': 'Added by API Override tool',
'WinLicen': 'WinLicense (Themida) Protector',
'_winzip_': 'WinZip Self-Extractor',
'.WWPACK': 'WWPACK Packer',
'.WWP32': 'WWPACK Packer (WWPack32)',
'.yP': 'Y0da Protector',
'.y0da': 'Y0da Protector'
}.freeze
def initialize(path, opts = [])
@path = path
@opts = opts
@warnings = []
end
def dump
@dump ||= PEdump.dump path
end
def hash
@hash ||= Digest::MD5.hexdigest(File.open(path).read)
end
def run_analysis
if static_analysis
log 'Static analysis completed successfully.', :success
else
log 'Static analysis failed to complete successfully.', :warn
end
if warnings.empty?
log '0 Warnings', :success
else
puts (pluralize(warnings.size, 'Warning').+':').hl(:yellow)
warnings.each { |warning| puts warning }
system OS.open_file_command, 'https://www.virustotal.com/gui/file/' + hash if opts.include? '-vt'
end
end
def verbose?
opts.include? '-v'
end
def log(message, level = :default)
colors = { warn: :red, suspicious: :yellow, success: :green, info: :blue }
message = (level == :default ? message : message.hl(colors[level].to_sym))
warnings << message if level.in? %i[warn suspicious]
puts message unless level == :default && !verbose?
end
def static_analysis
log "Starting analysis of #{path}", :info
unless dump.pe?
puts 'Not a PE file'.hl(:red)
return
end
analyze_headers
analyze_sections
analyze_imports
analyze_resources
analyze_strings
true
rescue StandardError => e
puts 'Error:'.hl(:red)
puts e&.hl(:red)
nil
end
def analyze_headers
log 'Analyzing File Header', :info
log 'Timestamp: ' + DateTime.strptime(dump.pe.image_file_header.TimeDateStamp.to_s, '%s').strftime('%x %T')
log 'Analyzing File Optional Header', :info
log "Image size: #{dump.pe.image_optional_header.SizeOfImage}"
debug = dump.pe.image_optional_header.DataDirectory.find { |d| d['type'] == 'DEBUG' }
log "Debug directory present at offset #{debug.va}" if debug&.va&.positive?
end
def analyze_sections
log "Analyzing #{pluralize(dump.pe.section_table.size, 'Sections')}", :info
dump.pe.section_table.each do |section|
section_name = section.Name
if section_name.in? Pear::POPULAR_SECTION_NAMES.keys.map(&:to_s)
log "Unsuspicious section name: #{section_name} (#{Pear::POPULAR_SECTION_NAMES[section_name.to_sym]})"
elsif section_name.in? Pear::COMMON_PACKER_SECTION_NAMES.keys.map(&:to_s)
log "Known packer section name: #{section_name} (#{Pear::COMMON_PACKER_SECTION_NAMES[section_name.to_sym]})", :suspicious
else
log "Unrecognized section name: #{section_name}", :warn
end
vsize = section.VirtualSize
rsize = section.SizeOfRawData
if rsize < vsize * 0.2
log "Vast discrepancy between virtual size (#{vsize}) and raw size (#{rsize}) of section #{section_name} (possible unpacking)!", :warn
else
log "Virtual size (#{vsize}) and raw size (#{rsize}) of section seem normal."
end
end
end
def analyze_imports
log "Analyzing #{pluralize(dump.imports.size, 'Imported Module')}", :info
imports = dump.imports
formatted_imports = []
imphash_failure = false
imports.each do |import|
import.first_thunk.each do |function|
imphash_name = import.module_name.split('.').first + '.' + (function.name || function.ordinal.to_s)
if function.name && !imphash_failure
log imphash_name
formatted_imports << imphash_name.downcase
else
unless imphash_failure
puts 'This script cannot resolve the name of ordinally linked functions. Imphash cannot be calculated.'.hl(:yellow)
imphash_failure = true
end
log imphash_name
end
end
end
log "Imphash: #{Digest::MD5.hexdigest formatted_imports.join(',')}" unless imphash_failure
end
def analyze_resources
log "Analyzing #{pluralize(dump.resources.size, 'Resource')}", :info
dump.resources.each do |resource|
msg = "#{resource.name} (#{resource.type}): #{resource.size} bytes"
if resource.type.in? PEdump::ROOT_RES_NAMES
log msg
elsif resource.type.in? SCARY_RESOURCE_TYPES
log msg + ' - dangerous resource type.', :warn
else
log msg + ' - unrecognized resource type.', :suspicious
end
end
end
def analyze_strings
log "Analyzing #{pluralize(dump.strings.size, 'String')}", :info
dump.strings.each do |string|
url = URL_REGEX.match string.value
if url
log "URL found in strings: #{string.value}", :warn
else
log string.value
end
end
end
end
puts 'Running PE Analyzer in Ruby (PEar) v1.0'.hl(:blue)
filepath, *opts = ARGV
pear = Pear.new(filepath, opts)
if Pathname.new(pear.path).exist?
pear.run_analysis
puts 'Terminating PEar successfully!'.hl(:green)
else
puts 'File not found: PEar terminating unsuccessfully.'.hl(:red)
end