pedump 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ class PEdump
2
+ IMAGE_TLS_DIRECTORY32 = IOStruct.new 'V6',
3
+ :StartAddressOfRawData,
4
+ :EndAddressOfRawData,
5
+ :AddressOfIndex,
6
+ :AddressOfCallBacks,
7
+ :SizeOfZeroFill,
8
+ :Characteristics
9
+
10
+ IMAGE_TLS_DIRECTORY64 = IOStruct.new 'Q4V2',
11
+ :StartAddressOfRawData,
12
+ :EndAddressOfRawData,
13
+ :AddressOfIndex,
14
+ :AddressOfCallBacks,
15
+ :SizeOfZeroFill,
16
+ :Characteristics
17
+ end
@@ -0,0 +1,26 @@
1
+ require 'pedump'
2
+ require 'pedump/unpacker/aspack'
3
+ require 'pedump/unpacker/upx'
4
+
5
+ module PEdump::Unpacker
6
+ class << self
7
+ def find io
8
+ if io.is_a?(String)
9
+ return File.open(io,"rb"){ |f| find(f) }
10
+ end
11
+
12
+ pedump = PEdump.new(io)
13
+ packer = Array(pedump.packers).first
14
+ return nil unless packer
15
+
16
+ case packer.name
17
+ when /UPX/
18
+ UPX
19
+ when /ASPack/i
20
+ ASPack
21
+ else
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,858 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: binary
3
+ require 'pedump/loader'
4
+ require 'pedump/cli'
5
+
6
+ module PEdump::Unpacker; end
7
+
8
+ class PEdump::Unpacker::ASPack
9
+
10
+ def self.unpack src_fname, dst_fname, log = ''
11
+ File.open(src_fname, "rb") do |f|
12
+ if ldr = new(f).unpack
13
+ File.open(dst_fname,"wb"){ |fo| ldr.dump(fo) }
14
+ return ldr # looks like 'true'
15
+ else
16
+ return false
17
+ end
18
+ end
19
+ end
20
+
21
+ ########################################################################
22
+ attr_accessor :logger
23
+
24
+ def initialize io, params = {}
25
+ params[:logger] ||= PEdump::Logger.create(params)
26
+
27
+ # XXX aspack unpacker code does not distinguish RVA from VA, so set
28
+ # image base to zero for RVA be equal VA
29
+ params[:image_base] ||= 0
30
+
31
+ @logger = params[:logger]
32
+ @ldr = PEdump::Loader.new(io, params)
33
+ @io = io
34
+
35
+ @e8e9_mode = @e8e9_cmp = @e8e9_flag = @ebp = nil
36
+ end
37
+
38
+ ########################################################################
39
+
40
+ DATA_ROOT = File.dirname(File.dirname(File.dirname(File.dirname(__FILE__))))
41
+ UNLZX_SRC = File.join(DATA_ROOT, "misc", "aspack", "aspack_unlzx.c")
42
+ UNLZXes = [
43
+ # default path for RVM
44
+ File.join(DATA_ROOT, "misc", "aspack", "aspack_unlzx"),
45
+ # XXX path for normal linux installs
46
+ File.expand_path("~/.pedump_unlzx")
47
+ ]
48
+
49
+ ########################################################################
50
+
51
+ def self.code2re code
52
+ idx = -1
53
+ was_any = false
54
+ Regexp.new(
55
+ code.strip.
56
+ split("\n").map{|line| line.strip.split(' ',2).first}.join("\n").
57
+ gsub(/\.{2,}/){ |x| x.split('').join(' ') }.
58
+ split.map do |x|
59
+ idx += 1
60
+ case x
61
+ when /\A[a-f0-9]{2}\Z/i
62
+ x = x.to_i(16)
63
+ if block_given?
64
+ x = yield(x,idx)
65
+ if x == :any
66
+ was_any = true
67
+ '.'
68
+ else
69
+ Regexp.escape(x.chr)
70
+ end
71
+ else
72
+ Regexp.escape(x.chr)
73
+ end
74
+ else
75
+ if was_any && (x.count('.') > 1 || x[/[+*?{}]/])
76
+ raise "[!] cannot use :any with more-than-1-char-long #{x.inspect}"
77
+ end
78
+ x
79
+ end
80
+ end.join, Regexp::MULTILINE
81
+ )
82
+ end
83
+ def code2re code, &block; self.class.code2re(code, &block); end
84
+
85
+ def code2re_dw code, shift=0, mode=nil
86
+ raise "shift must be in 0..3, got #{shift.inspect}" unless (0..3).include?(shift)
87
+ Regexp.new(
88
+ (
89
+ 'X '*shift +
90
+ code.strip.
91
+ split("\n").map{|line| line.strip.split(' ',2).first}.join("\n")
92
+ ).gsub(/\.{2,}/){ |x| x.split('').join(' ') }.
93
+ split.each_slice(4).map do |a|
94
+ a.map! do |x|
95
+ case x
96
+ when /\A[a-f0-9]{2}\Z/i
97
+ x.to_i(16)
98
+ else
99
+ x
100
+ end
101
+ end
102
+ dw = a.reverse.inject(0){ |x,y| (x<<8) + (y.is_a?(Numeric) ? y : 0)}
103
+ dw = yield(dw)
104
+ if dw.is_a?(Array)
105
+ # value + mask, mask = number of exact bytes in dw
106
+ (dw[1]..[3,a.size-1].min).each{ |i| a[i] = '.' }
107
+ dw = dw[0]
108
+ end
109
+ dw <<= 8
110
+
111
+ if mode == :add
112
+ # ADD mode
113
+ if a.all?{ |x| x.is_a?(Numeric)}
114
+ # all bytes are known
115
+ a.map do |x|
116
+ dw >>= 8
117
+ Regexp::escape((dw & 0xff).chr)
118
+ end
119
+ else
120
+ # some bytes are masked
121
+ # => ALL bytes after FIRST MASKED byte should be masked too
122
+ # due to carry flag when doing ADD or SUB
123
+ was_mask = false
124
+ a.map do |x|
125
+ dw >>= 8
126
+ if x.is_a?(Numeric)
127
+ was_mask ? '.' : Regexp::escape((dw & 0xff).chr)
128
+ else
129
+ was_mask = true
130
+ x
131
+ end
132
+ end
133
+ end
134
+ else
135
+ # generic mode, applicable for XOR
136
+ a.map do |x|
137
+ dw >>= 8
138
+ x.is_a?(Numeric) ? Regexp::escape((dw & 0xff).chr) : x
139
+ end
140
+ end
141
+ end.join[shift..-1], Regexp::MULTILINE
142
+ )
143
+ end
144
+
145
+ @@xordetect_codes = []
146
+
147
+ OBJ_TBL_CODE = <<-EOC
148
+ 8D B5 (....) lea esi, [ebp+442A5Ah] ; obj_tbl
149
+ 83 3E 00 cmp dword ptr [esi], 0
150
+ 0F 84 . . 00 00 jz no_obj_tbl
151
+ .{0,6} lea esi, [ebp+442A5Ah] ; obj_tbl
152
+ 6A 04 push 4
153
+ 68 00 10 00 00 push 1000h
154
+ 68 00 18 00 00 push 1800h
155
+ 6A 00 push 0
156
+ FF .{2,5} call dword ptr [ebp+4429B9h] ; [41503d]
157
+ 89 85 .... mov [ebp+4429B5h], eax ; [415039]
158
+ 8B 46 04 mov eax, [esi+4]
159
+ EOC
160
+
161
+ VIRTUALPROTECT_RE = code2re <<-EOC
162
+ 50 push eax
163
+ FF .{2,5} call dword ptr [ebp+6Ah] ; VirtualProtect
164
+ 59 pop ecx
165
+ AD lodsd
166
+ AD lodsd
167
+ 89 47 24 mov [edi+24h], eax
168
+ EOC
169
+
170
+ # CODE1 = <<-EOC
171
+ # 8B 44 24 10 mov eax, [esp+arg_C]
172
+ # 81 EC 54 03 00 00 sub esp, 354h
173
+ # 8D 4C 24 04 lea ecx, [esp+354h+var_350]
174
+ # 50 push eax
175
+ # E8 A8 03 00 00 call sub_465A28
176
+ # 8B 8C 24 5C 03 00 00 mov ecx, [esp+354h+arg_4]
177
+ # 8B 94 24 58 03 00 00 mov edx, [esp+354h+arg_0]
178
+ # 51 push ecx
179
+ # 52 push edx
180
+ # 8D 4C 24 0C lea ecx, [esp+35Ch+var_350]
181
+ # E8 0D 04 00 00 call sub_465AA6
182
+ # 84 C0 test al, al
183
+ # 75 0A jnz short loc_4656A7
184
+ # 83 C8 FF or eax, 0FFFFFFFFh
185
+ # 81 C4 54 03 00 00 add esp, 354h
186
+ # C3 retn
187
+ # EOC
188
+
189
+ E8_CODE = <<-EOC
190
+ 8B 06 mov eax, [esi]
191
+ EB (.) jmp short ?? ; ModE8E9
192
+ 80 3E (.) cmp byte ptr [esi], ?? ; CmpE8E9
193
+ 75 F3 jnz short loc_450141
194
+ 24 00 and al, 0
195
+ C1 C0 18 rol eax, 18h
196
+ 2B C3 sub eax, ebx
197
+ 89 06 mov [esi], eax
198
+ 83 C3 05 add ebx, 5
199
+ 83 C6 04 add esi, 4
200
+ 83 E9 05 sub ecx, 5
201
+ EB jmp short loc_450130
202
+ EOC
203
+ E8_RE = code2re E8_CODE
204
+ @@xordetect_codes << E8_CODE
205
+
206
+ E8_FLAG_RE_IMM1 = code2re <<-EOC
207
+ B3 (.) mov bl, ?
208
+ 80 FB 00 cmp bl, 0
209
+ 75 . jnz short loc_465163
210
+ FE 85 .... inc byte ptr [ebp+0ECh]
211
+ 8B 3E mov edi, [esi]
212
+ 03 BD .... add edi, [ebp+422h]
213
+ FF 37 push dword ptr [edi]
214
+ C6 07 C3 mov byte ptr [edi], 0C3h
215
+ FF D7 call edi
216
+ 8F 07 pop dword ptr [edi]
217
+ EOC
218
+
219
+ E8_FLAG_RE_IMM2 = code2re <<-EOC
220
+ B3 (.) mov bl, 0
221
+ 80 FB 00 cmp bl, 0
222
+ 75 . jnz short loc_4C6155
223
+ FE 85 .... inc byte ptr [ebp+0EFh]
224
+ 50 push eax
225
+ 51 push ecx
226
+ 56 push esi
227
+ 53 push ebx
228
+ 8B C8 mov ecx, eax
229
+ EOC
230
+
231
+ E8_FLAG_RE_EBP = code2re <<-EOC
232
+ 80 BD (....) 00 cmp byte ptr [ebp+443A11h], 0
233
+ 75 . jnz short loc_465163
234
+ FE 85 .... inc byte ptr [ebp+0ECh]
235
+ 8B 3E mov edi, [esi]
236
+ 03 BD .... add edi, [ebp+422h]
237
+ FF 37 push dword ptr [edi]
238
+ C6 07 C3 mov byte ptr [edi], 0C3h
239
+ FF D7 call edi
240
+ 8F 07 pop dword ptr [edi]
241
+ EOC
242
+
243
+ OEP_CODE1 = <<-EOC
244
+ B8 (....) mov eax, 101Ah
245
+ 50 push eax
246
+ 03 85 .... add eax, [ebp+444A28h]
247
+ 59 pop ecx
248
+ 0B C9 or ecx, ecx
249
+ 89 85 .... mov [ebp+443CF1h], eax
250
+ 61 popa
251
+ 75 08 jnz short loc_40A3C0
252
+ B8 01 00 00 00 mov eax, 1
253
+ C2 0C 00 retn 0Ch
254
+ EOC
255
+ OEP_RE1 = code2re OEP_CODE1
256
+ @@xordetect_codes << OEP_CODE1
257
+
258
+ OEP_CODE2 = <<-EOC
259
+ 8B 85 (....) mov eax, [ebp+442A4Eh] ; 004150D2
260
+ 50 push eax
261
+ 03 85 .... add eax, [ebp+4437E0h] ; [415e64] = self_base
262
+ 59 pop ecx
263
+ 0B C9 or ecx, ecx
264
+ 89 85 .... mov [ebp+442E7Bh], eax ; offset of '0' of 'push 0' after 'retn 0Ch'
265
+ 61 popa
266
+ 75 08 jnz short loc_4154FE
267
+ B8 01 00 00 00 mov eax, 1
268
+ C2 0C 00 retn 0Ch
269
+ EOC
270
+ OEP_RE2 = code2re OEP_CODE2
271
+ @@xordetect_codes << OEP_CODE2
272
+
273
+ IMPORTS_CODE1 = <<-EOC
274
+ EB F1 jmp ...
275
+ BE (....) mov esi, 55000h ; immediate imports rva
276
+ 8B 95 .... mov edx, [ebp+422h]
277
+ 03 F2 add esi, edx
278
+ 8B 46 0C mov eax, [esi+0Ch]
279
+ 85 C0 test eax, eax
280
+ 0F 84 . . 00 00 jz ep_rva
281
+ 03 C2 add eax, edx
282
+ 8B D8 mov ebx, eax
283
+ 50 push eax
284
+ FF 95 (....) call dword ptr [ebp+0F4Dh]
285
+ 85 C0 test eax, eax
286
+ EOC
287
+ IMPORTS_RE1 = code2re IMPORTS_CODE1
288
+ @@xordetect_codes << IMPORTS_CODE1
289
+
290
+ IMPORTS_CODE2 = <<-EOC
291
+ EB F1 jmp ...
292
+ 8B B5 (....) mov esi, [ebp+442A4Ah] ; [0x4150CE] = imports_rva
293
+ 8B 95 .... mov edx, [ebp+4437E0h] ; [0x415e64] = image_base
294
+ 03 F2 add esi, edx
295
+ 8B 46 0C mov eax, [esi+0Ch]
296
+ 85 C0 test eax, eax
297
+ 0F 84 . . 00 00 jz ep_rva
298
+ 03 C2 add eax, edx
299
+ 8B D8 mov ebx, eax
300
+ 50 push eax
301
+ FF 95 (....) call dword ptr [ebp+4438F4h] ; 415f78 = GetModuleHandleA
302
+ 85 C0 test eax, eax
303
+ EOC
304
+ IMPORTS_RE2 = code2re IMPORTS_CODE2
305
+ @@xordetect_codes << IMPORTS_CODE2
306
+
307
+ RELOCS_RE = code2re <<-EOC
308
+ 2B D0 sub edx, eax
309
+ 74 79 jz short exit_relocs_loop
310
+ 8B C2 mov eax, edx
311
+ C1 E8 10 shr eax, 10h
312
+ 33 DB xor ebx, ebx
313
+ 8B B5 (....) mov esi, [ebp+539h] ; relocs_rva
314
+ 03 B5 .... add esi, [ebp+422h] ; image_base
315
+ 83 3E 00 cmp dword ptr [esi], 0
316
+ 74 jz short exit_relocs_loop
317
+ EOC
318
+
319
+ SECTION_INFO = IOStruct.new 'VlV', :va, :size, :flags
320
+
321
+ ########################################################################
322
+
323
+ def _decrypt
324
+ @data = @data.dup
325
+ @data.size.times do |j|
326
+ @data[j] = (yield(@data[j].ord,j)&0xff).chr
327
+ end
328
+ @data
329
+ end
330
+
331
+ def _decrypt_dw shift=0
332
+ orig_size = @data.size
333
+ @data = @data.dup
334
+ i = shift # FIXME: first 'shift' bytes of data is not decrypted!
335
+ while i < @data.size
336
+ t = @data[i,4]
337
+ t<<"\x00" while t.size < 4
338
+ dw = t.unpack('V').first
339
+ dw = yield(dw)
340
+ @data[i,4] = [dw].pack('V')
341
+ i += 4
342
+ end
343
+ @data = @data[0,orig_size] if @data.size != orig_size
344
+ @data
345
+ end
346
+
347
+ def check_re data, comment = '', re = E8_RE
348
+ if m = data.match(re)
349
+ logger.debug "[.] E8_RE %s found at %4x : %-20s" % [comment, m.begin(0), m[1..-1].inspect]
350
+ m
351
+ end
352
+ end
353
+
354
+ def decrypt
355
+ r=nil
356
+ # check raw
357
+ return r if r=check_re(@data)
358
+
359
+ (1..255).each do |i|
360
+ # check byte add
361
+ if check_re(@data, "[add b,#{i}]", code2re(E8_CODE){ |x| (x+i)&0xff })
362
+ return check_re(_decrypt{|x| x-i})
363
+ end
364
+
365
+ # check byte xor
366
+ if check_re(@data, "[xor b,#{i}]", code2re(E8_CODE){ |x| x^i })
367
+ return check_re(_decrypt{|x| x^i})
368
+ end
369
+ end
370
+
371
+ # check dword dec
372
+ 4.times do |shift|
373
+ re = code2re_dw(E8_CODE,shift){ |dw| dw+1 }
374
+ if r=check_re(@data, "[dec dw:#{shift}]", re)
375
+ shift = (r.begin(0)-shift)%4
376
+ return check_re(_decrypt_dw(shift){ |x| x-1 })
377
+ end
378
+ end
379
+
380
+ # detect dword xor
381
+ h = xordetect
382
+ if h && h.size == 4
383
+ h.keys.permutation.each do |xor_bytes|
384
+ xor_dw = xor_bytes.inject{ |x,y| (x<<8) + y}
385
+ re = code2re_dw(E8_CODE){ |dw| dw^xor_dw }
386
+ if r=check_re(@data, "[xor dw,#{xor_dw.to_s(16)}]", re)
387
+ return check_re(_decrypt_dw(r.begin(0)%4){ |dw| dw^xor_dw })
388
+ end
389
+ end
390
+ end
391
+
392
+ # detect dword add
393
+ if add_dw = add_detect
394
+ 4.times do |shift|
395
+ re = code2re_dw(E8_CODE,shift, :add){ |dw| dw-add_dw }
396
+ if r=check_re(@data, "[add dw:#{shift},#{add_dw.to_s(16)}]", re)
397
+ return check_re(_decrypt_dw((r.begin(0)+shift)%4){ |dw| dw+add_dw })
398
+ end
399
+ end
400
+ end
401
+
402
+ # failed
403
+ false
404
+ end
405
+
406
+ # detects if code is crypted by a dword-xor
407
+ # @data must be original, not modified!
408
+ def xordetect
409
+ logger.info "[*] guessing DWORD-XOR key..."
410
+ h = Hash.new{ |k,v| k[v] = 0 }
411
+ @@xordetect_codes.each do |code|
412
+ 4.times do |shift|
413
+ 0x100.times do |x1|
414
+ re = code2re(code.tr('()','')){ |x,idx| idx%4 == shift ? x^x1 : :any }
415
+ @data.scan(re).each do
416
+ logger.debug "[.] %02x: %2d : %s" % [x1, ($~.begin(0)+shift)%4, re.inspect]
417
+ h[x1] += 1
418
+ end
419
+ end
420
+ end
421
+ end
422
+ case h.size
423
+ when 0
424
+ logger.debug "[?] %s: no matches" % __method__
425
+ when 1..3
426
+ logger.info "[?] %s: not xored, or %d-byte xor key: %s" % [__method__, h.size, h.inspect]
427
+ when 4
428
+ logger.info "[*] %s: FOUND xor key bytes: [%02x %02x %02x %02x]" % [__method__, *h.keys].flatten
429
+ else
430
+ logger.info "[?] %s: %d possible bytes: %s" % [__method__, h.size, h.inspect]
431
+ end
432
+ h
433
+ end
434
+
435
+ def add_detect known_bytes = [], step = 1
436
+ s = known_bytes.map{ |x| "%02x" % x}.join(' ')
437
+ logger.info "[*] guessing DWORD-ADD key... [#{s}]"
438
+ h = Hash.new{ |k,v| k[v] = 0 }
439
+ dec = known_bytes.reverse.inject(0){ |x,y| (x<<8) + y}
440
+ @@xordetect_codes.each do |code|
441
+ 4.times do |shift|
442
+ 0x100.times do |x1|
443
+ #re = code2re_dw(code.tr('()',''),shift){ |x,idx| idx%4 == shift ? ((x-x1)&0xff) : :any }
444
+ re = code2re_dw(code.tr('()',''),shift) do |x|
445
+ [x-dec-(x1<<(known_bytes.size*8)), known_bytes.size+1]
446
+ end
447
+ @data.scan(re).each do
448
+ logger.debug "[.] %02x: %2d : %s" % [x1, ($~.begin(0)+shift)%4, re.inspect[0,75]]
449
+ h[x1] += 1
450
+ end
451
+ end
452
+ end
453
+ end
454
+ if h.any?
455
+ known_bytes << h.sort_by(&:last).last[0] # most frequent byte
456
+ end
457
+ if known_bytes.size == step && step < 4
458
+ add_detect known_bytes, step+1
459
+ else
460
+ kb = known_bytes
461
+ case kb.size
462
+ when 0
463
+ logger.debug "[?] %s: no matches" % __method__
464
+ when 1..3
465
+ logger.info "[?] %s: not 'add' or %d-byte key: %s" % [__method__, kb.size, kb.inspect]
466
+ when 4
467
+ logger.info "[*] %s: FOUND 'add' key bytes: [%02x %02x %02x %02x]" % [__method__, *kb].flatten
468
+ return known_bytes.reverse.inject(0){ |x,y| (x<<8) + y}
469
+ else
470
+ logger.info "[?] %s: %d possible bytes: %s" % [__method__, kb.size, kb.inspect]
471
+ end
472
+ return nil
473
+ end
474
+ end
475
+
476
+ def _scan_obj_tbl
477
+ unless @ebp
478
+ logger.warn "[?] %s: EBP undefined, skipping" % __method__
479
+ return
480
+ end
481
+
482
+ re = code2re OBJ_TBL_CODE
483
+ va = nil
484
+ if m = @data.match(re)
485
+ a = m[1..-1].map{|x| x.unpack('V').first }
486
+ logger.debug "[d] OBJ_TBL_RE found at %4x : %s" % [m.begin(0), a.map{|x| x.to_s(16)}.join(', ')]
487
+ va = (a[0] + @ebp) & 0xffff_ffff
488
+ logger.debug "[.] obj_tbl VA = %4x (using EBP)" % va
489
+ else
490
+ logger.error "[!] cannot find obj_tbl"
491
+ return
492
+ end
493
+
494
+ # obj_tbl contains flags if there is a call to VirtualProtect in loader code
495
+ record_size = (@data['VirtualProtect'] && @data[VIRTUALPROTECT_RE]) ? 4*3 : 4*2
496
+
497
+ # @ldr[va-0x3c,0x3c].unpack('V*').each do |x|
498
+ # printf("%8x\n",x);
499
+ # end
500
+
501
+ r = []
502
+ while true
503
+ obj = SECTION_INFO.new(*@ldr[va, record_size].unpack(SECTION_INFO::FORMAT))
504
+ break if obj.va == 0
505
+ unless @ldr.va2section(obj.va)
506
+ logger.error "[!] can't get section for obj %4x : %4x" % [obj.va, obj.size]
507
+ end
508
+ va += record_size
509
+ r << obj
510
+ if r.size > 0x200
511
+ logger.error "[!] stopped obj_tbl parsing. too many sections!"
512
+ break
513
+ end
514
+ end
515
+ r
516
+ end
517
+
518
+ ########################################################################
519
+
520
+ def find_e8e9
521
+ if m = @data.match(E8_RE)
522
+ @e8e9_mode, @e8e9_cmp = m[1].ord, m[2].ord
523
+ else
524
+ logger.error "[!] can't find E8/E9 patch sub! unpacked code may be invalid!"
525
+ end
526
+
527
+ if m = (@data.match(E8_FLAG_RE_IMM1) || @data.match(E8_FLAG_RE_IMM2))
528
+ @e8e9_flag = m[1].ord
529
+ elsif m = @data.match(E8_FLAG_RE_EBP)
530
+ offset = m[1].unpack('V').first
531
+ @e8e9_flag = @ldr[(@ebp + offset) & 0xffff_ffff, 1].ord
532
+ else
533
+ logger.error "[!] can't find E8/E9 flag! unpacked code may be invalid!"
534
+ raise
535
+ end
536
+
537
+ logger.debug "[.] E8/E9: flag=%s, mode=%s, cmp=%s" % [@e8e9_flag||'???', @e8e9_mode, @e8e9_cmp]
538
+ end
539
+
540
+ def find_obj_tbl
541
+ if @obj_tbl = _scan_obj_tbl
542
+ if logger.level <= ::Logger::INFO
543
+ @obj_tbl.each do |obj|
544
+ if obj.flags
545
+ logger.info "[.] ASP::SECTION va: %8x size: %8x flags: %8x" % [
546
+ obj.va, obj.size&0xffff_ffff, obj.flags]
547
+ else
548
+ logger.info "[.] ASP::SECTION va: %8x size: %8x" % [
549
+ obj.va, obj.size&0xffff_ffff]
550
+ end
551
+ end
552
+ end
553
+ end
554
+ end
555
+
556
+ def find_oep
557
+ @oep = nil
558
+ if m = @data.match(OEP_RE1)
559
+ logger.debug "[.] OEP_RE1 found at %4x" % m.begin(0)
560
+ @oep = m[1].unpack('V').first
561
+ elsif @ebp && m = @data.match(OEP_RE2)
562
+ logger.debug "[.] OEP_RE2 found at %4x (using EBP)" % m.begin(0)
563
+ offset = m[1].unpack('V').first
564
+ @oep = @ldr[(@ebp + offset) & 0xffff_ffff, 4].unpack('V').first
565
+ end
566
+
567
+ if @oep
568
+ logger.info "[.] OEP = %8x" % @oep
569
+ else
570
+ logger.error "[!] cannot find EntryPoint"
571
+ end
572
+ end
573
+
574
+ def find_imports
575
+ @imports_rva = nil
576
+ if m = @data.match(IMPORTS_RE1)
577
+ a = m[1..-1].map{|x| x.unpack('V').first }
578
+ @imports_rva = a[0]
579
+ elsif m = @data.match(IMPORTS_RE2)
580
+ a = m[1..-1].map{|x| x.unpack('V').first }
581
+ else
582
+ logger.error "[!] cannot find imports"
583
+ return
584
+ end
585
+ logger.debug "[d] IMPORTS_REx found at %4x : %s" % [m.begin(0), a.map{|x| x.to_s(16)}.join(', ')]
586
+
587
+ # actually following code is not necessary for IMPORTS_RE1
588
+ # using it to get EBP register value
589
+
590
+ f = @ldr.pedump.imports.map(&:first_thunk).flatten.compact.find{ |x| x.name == "GetModuleHandleA"}
591
+ unless f
592
+ logger.error "[!] GetModuleHandleA not found"
593
+ return
594
+ end
595
+ vaGetModuleHandle = f.va
596
+ logger.debug "[d] GetModuleHandle is at %x" % vaGetModuleHandle
597
+ @ebp = (f.va - a[1]) & 0xffff_ffff
598
+ logger.debug "[d] assume EBP = %x" % @ebp
599
+
600
+ # @imports_rva may already be filled by IMPORTS_RE1
601
+ @imports_rva ||= @data[(@ebp + a[0] - @section.va) & 0xffff_ffff, 4].unpack('V').first
602
+ logger.info "[.] imports RVA = %x" % @imports_rva
603
+ end
604
+
605
+ def find_relocs
606
+ @relocs_rva = nil
607
+ if m = @data.match(RELOCS_RE)
608
+ a = m[1..-1].map{|x| x.unpack('V').first }
609
+ else
610
+ logger.error "[!] cannot find relocs"
611
+ raise
612
+ return
613
+ end
614
+ @relocs_rva ||= @ldr[(@ebp + a[0]) & 0xffff_ffff, 4].unpack('V').first
615
+ logger.info "[.] relocs RVA = %x" % @relocs_rva
616
+ end
617
+
618
+ ########################################################################
619
+
620
+ def rebuild_imports
621
+ return unless @imports_rva
622
+
623
+ iids = []
624
+
625
+ va = @imports_rva
626
+ sz = PEdump::IMAGE_IMPORT_DESCRIPTOR::SIZE
627
+ while true
628
+ iid = PEdump::IMAGE_IMPORT_DESCRIPTOR.read(@ldr[va,sz])
629
+ va += sz # increase ptr before breaking, req'd 4 saving total import table size in data dir
630
+ break if iid.Name.to_i == 0
631
+
632
+ [:original_first_thunk, :first_thunk].each do |tbl|
633
+ camel = tbl.capitalize.to_s.gsub(/_./){ |char| char[1..-1].upcase}
634
+ iid[tbl] ||= []
635
+ if (va1 = iid[camel].to_i) != 0
636
+ while true
637
+ # intentionally include zero terminator in table to count IAT size
638
+ t = @ldr[va1,4].unpack('V').first
639
+ iid[tbl] << t
640
+ break if t == 0
641
+ va1 += 4
642
+ end
643
+ end
644
+ end
645
+ iids << iid
646
+ end
647
+ @ldr.pe_hdr.ioh.DataDirectory[PEdump::IMAGE_DATA_DIRECTORY::IMPORT].tap do |dd|
648
+ dd.va = @imports_rva
649
+ dd.size = va-@imports_rva
650
+ end
651
+ if iids.any?
652
+ iids.sort_by!(&:FirstThunk)
653
+ @ldr.pe_hdr.ioh.DataDirectory[PEdump::IMAGE_DATA_DIRECTORY::IAT].tap do |dd|
654
+ # Points to the beginning of the first Import Address Table (IAT).
655
+ dd.va = iids.first.FirstThunk
656
+ # The Size field indicates the total size of all the IATs.
657
+ dd.size = iids.last.FirstThunk - iids.first.FirstThunk + iids.last.first_thunk.size*4
658
+ # ... to temporarily mark the IATs as read-write during import resolution.
659
+ # http://msdn.microsoft.com/en-us/magazine/bb985997.aspx
660
+ end
661
+ end
662
+ end
663
+
664
+ def rebuild_relocs
665
+ return if @relocs_rva.to_i == 0
666
+
667
+ va = @relocs_rva
668
+ while true
669
+ a = @ldr[va,4*2].to_s.unpack('V*')
670
+ break if a[0] == 0 || a[1] == 0
671
+ va += a[1]
672
+ end
673
+
674
+ @ldr.pe_hdr.ioh.DataDirectory[PEdump::IMAGE_DATA_DIRECTORY::BASERELOC].tap do |dd|
675
+ dd.va = @relocs_rva
676
+ dd.size = va-@relocs_rva
677
+ end
678
+ end
679
+
680
+ def rebuild_tls h = {}
681
+ dd = @ldr.pe_hdr.ioh.DataDirectory[PEdump::IMAGE_DATA_DIRECTORY::TLS]
682
+ return if dd.va.to_i == 0 || dd.size.to_i == 0
683
+
684
+ case h[:step]
685
+ when 1 # store @tls_data
686
+ @tls_data = @ldr[dd.va, dd.size]
687
+ when 2 # search in unpacked sections
688
+ return unless @tls_data if h[:step] == 2
689
+ # search for original TLS data in all unpacked sections
690
+ @ldr.sections.each do |section|
691
+ if offset = section.data.index(@tls_data)
692
+ # found a TLS section
693
+ dd.va = section.va + offset
694
+ return
695
+ end
696
+ end
697
+ logger.error "[!] can't find TLS section"
698
+ else
699
+ raise "invalid step"
700
+ end
701
+ end
702
+
703
+ def compile_unlzx dest
704
+ logger.info "[*] compiling #{File.basename(dest)} .."
705
+ system("gcc", UNLZX_SRC, "-o", dest)
706
+ unless File.file?(dest) && File.executable?(dest)
707
+ logger.fatal "[!] %s compile failed, please compile it yourself at %s" % [
708
+ File.basename(dest), File.dirname(dest)
709
+ ]
710
+ end
711
+ end
712
+
713
+ def unlzx_pathname
714
+ UNLZXes.each do |unlzx|
715
+ return unlzx if File.file?(unlzx) && File.executable?(unlzx)
716
+ end
717
+
718
+ # nothing found, try to compile
719
+ UNLZXes.each do |unlzx|
720
+ compile_unlzx unlzx
721
+ return unlzx if File.file?(unlzx) && File.executable?(unlzx)
722
+ end
723
+
724
+ # all compiles failed
725
+ raise "no aspack_unlzx binary"
726
+ end
727
+
728
+ def unpack_section data, packed_size, unpacked_size
729
+ data = IO.popen("#{unlzx_pathname} #{packed_size.to_i} #{unpacked_size.to_i}","r+") do |f|
730
+ f.write data
731
+ f.close_write
732
+ f.read
733
+ end
734
+ raise $?.inspect unless $?.success?
735
+ data
736
+ end
737
+
738
+ def decode_e8e9 data
739
+ return if !data || data.size < 6
740
+ return if [@e8e9_flag, @e8e9_mode, @e8e9_cmp].any?(&:nil?)
741
+ return if @e8e9_flag != 0
742
+
743
+ size = data.size - 6
744
+ offs = 0
745
+ while size > 0
746
+ b0 = data[offs]
747
+ if b0 != "\xE8" && b0 != "\xE9"
748
+ size-=1; offs+=1
749
+ next
750
+ end
751
+
752
+ dw = data[offs+1,4].unpack('V').first
753
+ if @e8e9_mode == 0
754
+ if (dw & 0xff) != @e8e9_cmp
755
+ size-=1; offs+=1
756
+ next
757
+ end
758
+ # dw &= 0xffffff00; dw = ROL(dw, 24)
759
+ dw >>= 8
760
+ end
761
+
762
+ t = (dw-offs) & 0xffffffff # keep value in 32 bits
763
+ #logger.debug "[d] data[%6x] = %8x" % [offs+1, t]
764
+ data[offs+1,4] = [t].pack('V')
765
+ offs += 5; size -= [size, 5].min
766
+ end
767
+ end
768
+
769
+ ########################################################################
770
+
771
+ def unpack
772
+ if @section = @ldr.va2section(@ldr.ep)
773
+ @data = @section.data
774
+ logger.debug "[.] EP section: #{@section.inspect}"
775
+ else
776
+ logger.fatal "[!] cannot determine EP section"
777
+ return
778
+ end
779
+
780
+ decrypt # must be called before any other finds
781
+
782
+ find_imports # also fills @ebp for other finds
783
+ find_e8e9
784
+ find_obj_tbl
785
+ find_oep
786
+ find_relocs
787
+
788
+ ###
789
+
790
+ rebuild_tls :step => 1
791
+ sorted_obj_tbl = @obj_tbl.sort_by{ |x| @ldr.pedump.va2file(x.va) }
792
+ sorted_obj_tbl.each_with_index do |obj,idx|
793
+ # restore section flags, if any
794
+ @ldr.va2section(obj.va).flags = obj.flags if obj.flags
795
+
796
+ next if obj.size < 0 # empty section
797
+ #file_offset = @ldr.pedump.va2file(obj.va)
798
+ #@io.seek file_offset
799
+ packed_size =
800
+ if idx == sorted_obj_tbl.size - 1
801
+ # last obj
802
+ obj.size
803
+ else
804
+ # subtract this file_offset from next object file_offset
805
+ @ldr.pedump.va2file(sorted_obj_tbl[idx+1].va) - @ldr.pedump.va2file(obj.va)
806
+ end
807
+ #packed_data = @io.read packed_size
808
+ packed_data = @ldr[obj.va, packed_size]
809
+ unpacked_data = unpack_section(packed_data, packed_data.size, obj.size).force_encoding('binary')
810
+ # decode e8/e9 only on 1st section?
811
+ decode_e8e9(unpacked_data) if obj == @obj_tbl.first
812
+ @ldr[obj.va, unpacked_data.size] = unpacked_data
813
+ logger.debug "[.] %8x: %8x -> %8x" % [obj.va, packed_size, unpacked_data.size]
814
+ end
815
+
816
+ rebuild_imports
817
+ rebuild_relocs
818
+ rebuild_tls :step => 2
819
+
820
+ @ldr.pe_hdr.ioh.AddressOfEntryPoint = @oep.to_i
821
+ @ldr
822
+ end
823
+ end
824
+
825
+ ##########################################################################
826
+
827
+ if __FILE__ == $0
828
+ fnames =
829
+ if ARGV.empty?
830
+ Dir['samples/*.{dll,exe,bin,ocx}']
831
+ else
832
+ ARGV
833
+ end
834
+
835
+ require 'pp'
836
+ fnames.each do |fname|
837
+ @fname = fname
838
+ File.open(fname,"rb") do |f|
839
+ pedump = PEdump.new :log_level => Logger::DEBUG
840
+ next unless packer = Array(pedump.packer(f)).first
841
+ next unless packer.name =~ /aspack/i
842
+
843
+ STDERR.puts "\n=== #{fname}".green
844
+
845
+ f.rewind
846
+ unpacker = PEdump::Unpacker::ASPack.new(f,
847
+ :log_level => Logger::DEBUG,
848
+ :color => true)
849
+ if l = unpacker.unpack
850
+ # returns PEdump::Loader with unpacked data
851
+ File.open("unpacked.exe","wb") do |f|
852
+ l.dump(f)
853
+ end
854
+ end
855
+ end
856
+ end
857
+ end
858
+