Statistics
| Branch: | Tag: | Revision:

root / lib / rex / arch / x86.rb @ master

History | View | Annotate | Download (11.5 kB)

1
#!/usr/bin/env ruby
2

    
3
module Rex
4
module Arch
5

    
6
#
7
# everything here is mostly stole from vlad's perl x86 stuff
8
#
9

    
10
module X86
11

    
12
        #
13
        # Register number constants
14
        #
15
        EAX = AL = AX = ES = 0
16
        ECX = CL = CX = CS = 1
17
        EDX = DL = DX = SS = 2
18
        EBX = BL = BX = DS = 3
19
        ESP = AH = SP = FS = 4
20
        EBP = CH = BP = GS = 5
21
        ESI = DH = SI =      6
22
        EDI = BH = DI =      7
23

    
24
        REG_NAMES32 = [ 'eax', 'ecx', 'edx', 'ebx',
25
                        'esp', 'ebp', 'esi', 'edi' ] # :nodoc:
26

    
27
        # Jump tp a specific register
28
        def self.jmp_reg(str)
29
                reg = reg_number(str)
30
                _check_reg(reg)
31
                "\xFF" + [224 + reg].pack('C')
32
        end
33

    
34
        # This method returns the opcodes that compose a jump instruction to the
35
        # supplied relative offset.
36
        def self.jmp(addr)
37
                "\xe9" + pack_dword(rel_number(addr))
38
        end
39

    
40
        #
41
        # This method adds/subs a packed long integer
42
        #
43
        def self.dword_adjust(dword, amount=0)
44
                pack_dword(dword.unpack('V')[0] + amount)
45
        end
46

    
47
        #
48
        # This method returns the opcodes that compose a tag-based search routine
49
        #
50
        def self.searcher(tag)
51
                "\xbe" + dword_adjust(tag,-1)+  # mov esi, Tag - 1
52
                "\x46" +                        # inc esi
53
                "\x47" +                        # inc edi (end_search:)
54
                "\x39\x37" +                    # cmp [edi],esi
55
                "\x75\xfb" +                    # jnz 0xa (end_search)
56
                "\x46" +                        # inc esi
57
                "\x4f" +                        # dec edi (start_search:)
58
                "\x39\x77\xfc" +                # cmp [edi-0x4],esi
59
                "\x75\xfa" +                    # jnz 0x10 (start_search)
60
                jmp_reg('edi')                  # jmp edi
61
        end
62

    
63
        #
64
        # Generates a buffer that will copy memory immediately following the stub
65
        # that is generated to be copied to the stack
66
        #
67
        def self.copy_to_stack(len)
68
                # four byte align
69
                len = (len + 3) & ~0x3
70

    
71
                stub =
72
                        "\xeb\x0f"+                # jmp _end
73
                        push_dword(len)+           # push n
74
                        "\x59"+                    # pop ecx
75
                        "\x5e"+                    # pop esi
76
                        "\x29\xcc"+                # sub esp, ecx
77
                        "\x89\xe7"+                # mov edi, esp
78
                        "\xf3\xa4"+                # rep movsb
79
                        "\xff\xe4"+                # jmp esp
80
                        "\xe8\xec\xff\xff\xff"     # call _start
81

    
82
                stub
83
        end
84

    
85
        #
86
        # This method returns the opcodes that compose a short jump instruction to
87
        # the supplied relative offset.
88
        #
89
        def self.jmp_short(addr)
90
                "\xeb" + pack_lsb(rel_number(addr, -2))
91
        end
92

    
93
        #
94
        # This method returns the opcodes that compose a relative call instruction
95
        # to the address specified.
96
        #
97
        def self.call(addr)
98
                "\xe8" + pack_dword(rel_number(addr, -5))
99
        end
100

    
101
        #
102
        # This method returns a number offset to the supplied string.
103
        #
104
        def self.rel_number(num, delta = 0)
105
                s = num.to_s
106

    
107
                case s[0, 2]
108
                        when '$+'
109
                                num = s[2 .. -1].to_i
110
                        when '$-'
111
                                num = -1 * s[2 .. -1].to_i
112
                        when '0x'
113
                                num = s.hex
114
                        else
115
                                delta = 0
116
                end
117

    
118
                return num + delta
119
        end
120

    
121
        #
122
        # This method returns the number associated with a named register.
123
        #
124
        def self.reg_number(str)
125
                return self.const_get(str.upcase)
126
        end
127

    
128
        #
129
        # This method returns the register named associated with a given register
130
        # number.
131
        #
132
        def self.reg_name32(num)
133
                _check_reg(num)
134
                return REG_NAMES32[num].dup
135
        end
136

    
137
        #
138
        # This method generates the encoded effective value for a register.
139
        #
140
        def self.encode_effective(shift, dst)
141
                return (0xc0 | (shift << 3) | dst)
142
        end
143

    
144
        #
145
        # This method generates the mod r/m character for a source and destination
146
        # register.
147
        #
148
        def self.encode_modrm(dst, src)
149
                _check_reg(dst, src)
150
                return (0xc0 | src | dst << 3).chr
151
        end
152

    
153
        #
154
        # This method generates a push byte instruction.
155
        #
156
        def self.push_byte(byte)
157
                # push byte will sign extend...
158
                if byte < 128 && byte >= -128
159
                        return "\x6a" + (byte & 0xff).chr
160
                end
161
                raise ::ArgumentError, "Can only take signed byte values!", caller()
162
        end
163

    
164
        #
165
        # This method generates a push word instruction.
166
        #
167
        def self.push_word(val)
168
                return "\x66\x68" + pack_word(val)
169
        end
170

    
171
        #
172
        # This method generates a push dword instruction.
173
        #
174
        def self.push_dword(val)
175
                return "\x68" + pack_dword(val)
176
        end
177

    
178
        #
179
        # This method generates a pop dword instruction into a register.
180
        #
181
        def self.pop_dword(dst)
182
                _check_reg(dst)
183
                return (0x58 | dst).chr
184
        end
185

    
186
        #
187
        # This method generates an instruction that clears the supplied register in
188
        # a manner that attempts to avoid bad characters, if supplied.
189
        #
190
        def self.clear(reg, badchars = '')
191
                _check_reg(reg)
192
                return set(reg, 0, badchars)
193
        end
194

    
195
        #
196
        # This method generates the opcodes that set the low byte of a given
197
        # register to the supplied value.
198
        #
199
        def self.mov_byte(reg, val)
200
                _check_reg(reg)
201
                # chr will raise RangeError if val not between 0 .. 255
202
                return (0xb0 | reg).chr + val.chr
203
        end
204

    
205
        #
206
        # This method generates the opcodes that set the low word of a given
207
        # register to the supplied value.
208
        #
209
        def self.mov_word(reg, val)
210
                _check_reg(reg)
211
                if val < 0 || val > 0xffff
212
                        raise RangeError, "Can only take unsigned word values!", caller()
213
                end
214
                return "\x66" + (0xb8 | reg).chr + pack_word(val)
215
        end
216

    
217
        #
218
        # This method generates the opcodes that set the a register to the
219
        # supplied value.
220
        #
221
        def self.mov_dword(reg, val)
222
                _check_reg(reg)
223
                return (0xb8 | reg).chr + pack_dword(val)
224
        end
225

    
226
        #
227
        # This method is a general way of setting a register to a value.  Depending
228
        # on the value supplied, different sets of instructions may be used.
229
        #
230
        # TODO: Make this moderatly intelligent so it chain instructions by itself
231
                #   (ie. xor eax, eax + mov al, 4 + xchg ah, al)
232
        def self.set(dst, val, badchars = '')
233
                _check_reg(dst)
234

    
235
                # If the value is 0 try xor/sub dst, dst (2 bytes)
236
                if(val == 0)
237
                        opcodes = Rex::Text.remove_badchars("\x29\x2b\x31\x33", badchars)
238
                        if !opcodes.empty?
239
                                return opcodes[rand(opcodes.length)].chr + encode_modrm(dst, dst)
240
                        end
241
# TODO: SHL/SHR
242
# TODO: AND
243
                end
244

    
245
                # try push BYTE val; pop dst (3 bytes)
246
                begin
247
                        return _check_badchars(push_byte(val) + pop_dword(dst), badchars)
248
                rescue ::ArgumentError, ::RuntimeError, ::RangeError
249
                end
250

    
251
                # try clear dst, mov BYTE dst (4 bytes)
252
                begin
253
                        # break if val == 0
254
                        return _check_badchars(clear(dst, badchars) + mov_byte(dst, val), badchars)
255
                rescue ::ArgumentError, ::RuntimeError, ::RangeError
256
                end
257

    
258
                # try mov DWORD dst (5 bytes)
259
                begin
260
                        return _check_badchars(mov_dword(dst, val), badchars)
261
                rescue ::ArgumentError, ::RuntimeError, ::RangeError
262
                end
263

    
264
                # try push DWORD, pop dst (6 bytes)
265
                begin
266
                        return _check_badchars(push_dword(val) + pop_dword(dst), badchars)
267
                rescue ::ArgumentError, ::RuntimeError, ::RangeError
268
                end
269

    
270
                # try clear dst, mov WORD dst (6 bytes)
271
                begin
272
                        # break if val == 0
273
                        return _check_badchars(clear(dst, badchars) + mov_word(dst, val), badchars)
274
                rescue ::ArgumentError, ::RuntimeError, ::RangeError
275
                end
276

    
277
                raise RuntimeError, "No valid set instruction could be created!", caller()
278
        end
279

    
280
        #
281
        # Builds a subtraction instruction using the supplied operand
282
        # and register.
283
        #
284
        def self.sub(val, reg, badchars = '', add = false, adjust = false, bits = 0)
285
                opcodes = []
286
                shift   = (add == true) ? 0 : 5
287

    
288
                if (bits <= 8 and val >= -0x7f and val <= 0x7f)
289
                        opcodes <<
290
                                ((adjust) ? '' : clear(reg, badchars)) +
291
                                "\x83" +
292
                                [ encode_effective(shift, reg) ].pack('C') +
293
                                [ val.to_i ].pack('C')
294
                end
295

    
296
                if (bits <= 16 and val >= -0xffff and val <= 0)
297
                        opcodes <<
298
                                ((adjust) ? '' : clear(reg, badchars)) +
299
                                "\x66\x81" +
300
                                [ encode_effective(shift, reg) ].pack('C') +
301
                                [ val.to_i ].pack('v')
302
                end
303

    
304
                opcodes <<
305
                        ((adjust) ? '' : clear(reg, badchars)) +
306
                        "\x81" +
307
                        [ encode_effective(shift, reg) ].pack('C') +
308
                        [ val.to_i ].pack('V')
309

    
310
                # Search for a compatible opcode
311
                opcodes.each { |op|
312
                        begin
313
                                _check_badchars(op, badchars)
314
                        rescue
315
                                next
316
                        end
317

    
318
                        return op
319
                }
320

    
321
                if opcodes.empty?
322
                        raise RuntimeError, "Could not find a usable opcode", caller()
323
                end
324
        end
325

    
326
        #
327
        # This method generates the opcodes equivalent to subtracting with a
328
        # negative value from a given register.
329
        #
330
        def self.add(val, reg, badchars = '', adjust = false, bits = 0)
331
                sub(val, reg, badchars, true, adjust, bits)
332
        end
333

    
334
        #
335
        # This method wrappers packing a short integer as a little-endian buffer.
336
        #
337
        def self.pack_word(num)
338
                [num].pack('v')
339
        end
340

    
341
        #
342
        # This method wrappers packing an integer as a little-endian buffer.
343
        #
344
        def self.pack_dword(num)
345
                [num].pack('V')
346
        end
347

    
348
        #
349
        # This method returns the least significant byte of a packed dword.
350
        #
351
        def self.pack_lsb(num)
352
                pack_dword(num)[0,1]
353
        end
354

    
355
        #
356
        # This method adjusts the value of the ESP register by a given amount.
357
        #
358
        def self.adjust_reg(reg, adjustment)
359
                if (adjustment > 0)
360
                        sub(adjustment, reg, '', false, false, 32)
361
                else
362
                        add(adjustment, reg, '', true, 32)
363
                end
364
        end
365

    
366
        def self._check_reg(*regs) # :nodoc:
367
                regs.each { |reg|
368
                        if reg > 7 || reg < 0
369
                                raise ArgumentError, "Invalid register #{reg}", caller()
370
                        end
371
                }
372
                return nil
373
        end
374

    
375
        def self._check_badchars(data, badchars) # :nodoc:
376
                idx = Rex::Text.badchar_index(data, badchars)
377
                if idx
378
                        raise RuntimeError, "Bad character at #{idx}", caller()
379
                end
380
                return data
381
        end
382

    
383
        #
384
        # This method returns an array of 'safe' FPU instructions
385
        #
386
        def self.fpu_instructions
387
                fpus = []
388

    
389
                0xe8.upto(0xee) { |x| fpus << "\xd9" + x.chr }
390
                0xc0.upto(0xcf) { |x| fpus << "\xd9" + x.chr }
391
                0xc0.upto(0xdf) { |x| fpus << "\xda" + x.chr }
392
                0xc0.upto(0xdf) { |x| fpus << "\xdb" + x.chr }
393
                0xc0.upto(0xc7) { |x| fpus << "\xdd" + x.chr }
394

    
395
                fpus << "\xd9\xd0"
396
                fpus << "\xd9\xe1"
397
                fpus << "\xd9\xf6"
398
                fpus << "\xd9\xf7"
399
                fpus << "\xd9\xe5"
400

    
401
                # This FPU instruction seems to fail consistently on Linux
402
                #fpus << "\xdb\xe1"
403

    
404
                fpus
405
        end
406

    
407
        #
408
        # This method returns an array containing a geteip stub, a register, and an offset
409
        # This method will return nil if the getip generation fails
410
        #
411
        def self.geteip_fpu(badchars)
412

    
413
                #
414
                # Default badchars to an empty string
415
                #
416
                badchars ||= ''
417

    
418
                #
419
                # Bail out early if D9 is restricted
420
                #
421
                return nil if badchars.index("\xd9")
422

    
423
                #
424
                # Create a list of FPU instructions
425
                #
426
                fpus = *self.fpu_instructions
427
                bads = []
428
                badchars.each_byte  do |c|
429
                        fpus.each do |str|
430
                                bads << str if (str.index(c.chr))
431
                        end
432
                end
433
                bads.each { |str| fpus.delete(str) }
434
                return nil if fpus.length == 0
435

    
436
                #
437
                # Create a list of registers to use for fnstenv
438
                #
439
                dsts = []
440
                0.upto(7) do |c|
441
                        dsts << c if (not badchars.index( (0x70+c).chr ))
442
                end
443

    
444
                if (dsts.include?(ESP) and badchars.index("\x24"))
445
                        dsts.delete(ESP)
446
                end
447

    
448
                return nil if dsts.length == 0
449

    
450
                #
451
                # Grab a random FPU instruction
452
                #
453
                fpu = fpus[ rand(fpus.length) ]
454

    
455
                #
456
                # Grab a random register from dst
457
                #
458
                while(dsts.length > 0)
459
                        buf = ''
460
                        dst = dsts[ rand(dsts.length) ]
461
                        dsts.delete(dst)
462

    
463
                        # If the register is not ESP, copy ESP
464
                        if (dst != ESP)
465
                                next if badchars.index( (0x70 + dst).chr )
466

    
467
                                if !(badchars.index("\x89") or badchars.index( (0xE0+dst).chr ))
468
                                        buf << "\x89" + (0xE0 + dst).chr
469
                                else
470
                                        next if badchars.index("\x54")
471
                                        next if badchars.index( (0x58+dst).chr )
472
                                        buf << "\x54" + (0x58 + dst).chr
473
                                end
474
                        end
475

    
476
                        pad = 0
477
                        while (pad < (128-12) and badchars.index( (256-12-pad).chr))
478
                                pad += 4
479
                        end
480

    
481
                        # Give up on finding a value to use here
482
                        if (pad == (128-12))
483
                                return nil
484
                        end
485

    
486
                        out = buf + fpu + "\xd9" + (0x70 + dst).chr
487
                        out << "\x24" if dst == ESP
488
                        out << (256-12-pad).chr
489

    
490
                        regs = [*(0..7)]
491
                        while (regs.length > 0)
492
                                reg = regs[ rand(regs.length) ]
493
                                regs.delete(reg)
494
                                next if reg == ESP
495
                                next if badchars.index( (0x58 + reg).chr )
496

    
497
                                # Pop the value back out
498
                                0.upto(pad / 4) { |c| out << (0x58 + reg).chr }
499

    
500
                                # Fix the value to point to self
501
                                gap = out.length - buf.length
502

    
503
                                return [out, REG_NAMES32[reg].upcase, gap]
504
                        end
505
                end
506

    
507
                return nil
508
        end
509

    
510
end
511

    
512
end end
513