ronin-code-asm 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.document +4 -0
  3. data/.editorconfig +11 -0
  4. data/.github/workflows/ruby.yml +31 -0
  5. data/.gitignore +11 -0
  6. data/.mailmap +1 -0
  7. data/.rspec +1 -0
  8. data/.ruby-version +1 -0
  9. data/.yardopts +1 -0
  10. data/COPYING.txt +165 -0
  11. data/ChangeLog.md +44 -0
  12. data/Gemfile +25 -0
  13. data/README.md +166 -0
  14. data/Rakefile +39 -0
  15. data/data/os/freebsd/amd64/syscalls.yml +415 -0
  16. data/data/os/freebsd/x86/syscalls.yml +415 -0
  17. data/data/os/linux/amd64/syscalls.yml +306 -0
  18. data/data/os/linux/x86/syscalls.yml +339 -0
  19. data/gemspec.yml +26 -0
  20. data/lib/ronin/code/asm/archs/amd64.rb +100 -0
  21. data/lib/ronin/code/asm/archs/x86.rb +170 -0
  22. data/lib/ronin/code/asm/archs.rb +22 -0
  23. data/lib/ronin/code/asm/config.rb +33 -0
  24. data/lib/ronin/code/asm/immediate_operand.rb +84 -0
  25. data/lib/ronin/code/asm/instruction.rb +66 -0
  26. data/lib/ronin/code/asm/memory_operand.rb +119 -0
  27. data/lib/ronin/code/asm/os/freebsd.rb +35 -0
  28. data/lib/ronin/code/asm/os/linux.rb +35 -0
  29. data/lib/ronin/code/asm/os/os.rb +47 -0
  30. data/lib/ronin/code/asm/os.rb +57 -0
  31. data/lib/ronin/code/asm/program.rb +509 -0
  32. data/lib/ronin/code/asm/register.rb +111 -0
  33. data/lib/ronin/code/asm/shellcode.rb +75 -0
  34. data/lib/ronin/code/asm/syntax/att.rb +164 -0
  35. data/lib/ronin/code/asm/syntax/common.rb +241 -0
  36. data/lib/ronin/code/asm/syntax/intel.rb +150 -0
  37. data/lib/ronin/code/asm/syntax.rb +22 -0
  38. data/lib/ronin/code/asm/version.rb +28 -0
  39. data/lib/ronin/code/asm.rb +68 -0
  40. data/ronin-code-asm.gemspec +62 -0
  41. data/spec/asm_spec.rb +14 -0
  42. data/spec/config_spec.rb +10 -0
  43. data/spec/immediate_operand_spec.rb +79 -0
  44. data/spec/instruction_spec.rb +62 -0
  45. data/spec/memory_operand_spec.rb +80 -0
  46. data/spec/os_spec.rb +68 -0
  47. data/spec/program_spec.rb +439 -0
  48. data/spec/register_spec.rb +112 -0
  49. data/spec/shellcode_spec.rb +58 -0
  50. data/spec/spec_helper.rb +7 -0
  51. data/spec/syntax/att_spec.rb +181 -0
  52. data/spec/syntax/common_spec.rb +42 -0
  53. data/spec/syntax/intel_spec.rb +174 -0
  54. metadata +143 -0
@@ -0,0 +1,439 @@
1
+ require 'spec_helper'
2
+ require 'ronin/code/asm/program'
3
+
4
+ describe Ronin::Code::ASM::Program do
5
+ describe "#arch" do
6
+ it "must default to :x86" do
7
+ expect(subject.arch).to eq(:x86)
8
+ end
9
+ end
10
+
11
+ context "when :arch is :x86" do
12
+ subject { described_class.new(arch: :x86) }
13
+
14
+ it { expect(subject.word_size).to eq(4) }
15
+
16
+ describe "#stask_base" do
17
+ it "must be ebp" do
18
+ expect(subject.stack_base.name).to eq(:ebp)
19
+ end
20
+ end
21
+
22
+ describe "#stask_pointer" do
23
+ it "must be esp" do
24
+ expect(subject.stack_pointer.name).to eq(:esp)
25
+ end
26
+ end
27
+
28
+ describe "#stack_push" do
29
+ let(:value) { 0xff }
30
+
31
+ before { subject.stack_push(value) }
32
+
33
+ it "must add a 'push' instruction with a value" do
34
+ expect(subject.instructions[-1].name).to eq(:push)
35
+ expect(subject.instructions[-1].operands[0].value).to eq(value)
36
+ end
37
+ end
38
+
39
+ describe "#stack_pop" do
40
+ let(:register) { subject.register(:eax) }
41
+
42
+ before { subject.stack_pop(register) }
43
+
44
+ it "must add a 'pop' instruction with a register" do
45
+ expect(subject.instructions[-1].name).to eq(:pop)
46
+ expect(subject.instructions[-1].operands[0]).to eq(register)
47
+ end
48
+ end
49
+
50
+ describe "#register_clear" do
51
+ let(:name) { :eax }
52
+
53
+ before { subject.register_clear(name) }
54
+
55
+ it "must add a 'xor' instruction with a registers" do
56
+ expect(subject.instructions[-1].name).to eq(:xor)
57
+ expect(subject.instructions[-1].operands[0].name).to eq(name)
58
+ expect(subject.instructions[-1].operands[1].name).to eq(name)
59
+ end
60
+ end
61
+
62
+ describe "#register_set" do
63
+ let(:name) { :eax }
64
+ let(:value) { 0xff }
65
+
66
+ before { subject.register_set(name,value) }
67
+
68
+ it "must add a 'xor' instruction with a registers" do
69
+ expect(subject.instructions[-1].name).to eq(:mov)
70
+ expect(subject.instructions[-1].operands[0].value).to eq(value)
71
+ expect(subject.instructions[-1].operands[1].name).to eq(name)
72
+ end
73
+ end
74
+
75
+ describe "#register_save" do
76
+ let(:name) { :eax }
77
+
78
+ before { subject.register_save(name) }
79
+
80
+ it "must add a 'xor' instruction with a registers" do
81
+ expect(subject.instructions[-1].name).to eq(:push)
82
+ expect(subject.instructions[-1].operands[0].name).to eq(name)
83
+ end
84
+ end
85
+
86
+ describe "#register_save" do
87
+ let(:name) { :eax }
88
+
89
+ before { subject.register_load(name) }
90
+
91
+ it "must add a 'xor' instruction with a registers" do
92
+ expect(subject.instructions[-1].name).to eq(:pop)
93
+ expect(subject.instructions[-1].operands[0].name).to eq(name)
94
+ end
95
+ end
96
+
97
+ describe "#interrupt" do
98
+ let(:number) { 0x0a }
99
+
100
+ before { subject.interrupt(number) }
101
+
102
+ it "must add an 'int' instruction with the interrupt number" do
103
+ expect(subject.instructions[-1].name).to eq(:int)
104
+ expect(subject.instructions[-1].operands[0].value).to eq(number)
105
+ end
106
+ end
107
+
108
+ describe "#syscall" do
109
+ before { subject.syscall }
110
+
111
+ it "must add an 'int 0x80' instruction" do
112
+ expect(subject.instructions[-1].name).to eq(:int)
113
+ expect(subject.instructions[-1].operands[0].value).to eq(0x80)
114
+ end
115
+ end
116
+
117
+ context "when :os is :linux" do
118
+ subject { described_class.new(arch: :x86, os: :linux) }
119
+
120
+ it { expect(subject.syscalls).to_not be_empty }
121
+ end
122
+
123
+ context "when :os is :freebsd" do
124
+ subject { described_class.new(arch: :x86, os: :freebsd) }
125
+
126
+ it { expect(subject.syscalls).to_not be_empty }
127
+ end
128
+ end
129
+
130
+ context "when :arch is :amd64" do
131
+ subject { described_class.new(arch: :amd64) }
132
+
133
+ it { expect(subject.word_size).to eq(8) }
134
+
135
+ describe "#syscall" do
136
+ before { subject.syscall }
137
+
138
+ it "must add a 'syscall' instruction" do
139
+ expect(subject.instructions[-1].name).to eq(:syscall)
140
+ end
141
+ end
142
+
143
+ context "when :os is :linux" do
144
+ subject { described_class.new(arch: :amd64, os: :linux) }
145
+
146
+ it { expect(subject.syscalls).to_not be_empty }
147
+ end
148
+
149
+ context "when :os is :freebsd" do
150
+ subject { described_class.new(arch: :amd64, os: :freebsd) }
151
+
152
+ it { expect(subject.syscalls).to_not be_empty }
153
+ end
154
+ end
155
+
156
+ describe "#register?" do
157
+ it "must return true for existing registers" do
158
+ expect(subject.register?(:eax)).to be(true)
159
+ end
160
+
161
+ it "must return false for unknown registers" do
162
+ expect(subject.register?(:foo)).to be(false)
163
+ end
164
+ end
165
+
166
+ describe "#register" do
167
+ it "must return a Register" do
168
+ expect(subject.register(:eax)).to be_kind_of(Ronin::Code::ASM::Register)
169
+ end
170
+
171
+ it "must allocate the register" do
172
+ subject.register(:ebx)
173
+
174
+ expect(subject.allocated_registers).to include(:ebx)
175
+ end
176
+
177
+ context "when given an unknown register name" do
178
+ it "must raise an ArgumentError" do
179
+ expect {
180
+ subject.register(:foo)
181
+ }.to raise_error(ArgumentError)
182
+ end
183
+ end
184
+ end
185
+
186
+ describe "#instruction" do
187
+ it "must return an Instruction" do
188
+ expect(subject.instruction(:hlt)).to be_kind_of(Ronin::Code::ASM::Instruction)
189
+ end
190
+
191
+ it "must append the new Instruction" do
192
+ subject.instruction(:push, 1)
193
+
194
+ expect(subject.instructions.last.name).to eq(:push)
195
+ end
196
+ end
197
+
198
+ describe "#byte" do
199
+ it "must return a ImmedateOperand" do
200
+ expect(subject.byte(1)).to be_kind_of(Ronin::Code::ASM::ImmediateOperand)
201
+ end
202
+
203
+ it "must have width of 1" do
204
+ expect(subject.byte(1).width).to eq(1)
205
+ end
206
+
207
+ context "when given a MemoryOperand" do
208
+ let(:register) { Ronin::Code::ASM::Register.new(:eax, 4) }
209
+ let(:memory_operand) { Ronin::Code::ASM::MemoryOperand.new(register) }
210
+
211
+ it "must return a MemoryOperand" do
212
+ expect(subject.byte(memory_operand)).to be_kind_of(
213
+ Ronin::Code::ASM::MemoryOperand
214
+ )
215
+ end
216
+
217
+ it "must have a width of 1" do
218
+ expect(subject.byte(memory_operand).width).to eq(1)
219
+ end
220
+ end
221
+ end
222
+
223
+ describe "#word" do
224
+ it "must return a Ronin::Code::ASM::ImmediateOperand" do
225
+ expect(subject.word(1)).to be_kind_of(Ronin::Code::ASM::ImmediateOperand)
226
+ end
227
+
228
+ it "must have width of 2" do
229
+ expect(subject.word(1).width).to eq(2)
230
+ end
231
+
232
+ context "when given a MemoryOperand" do
233
+ let(:register) { Ronin::Code::ASM::Register.new(:eax, 4) }
234
+ let(:memory_operand) { Ronin::Code::ASM::MemoryOperand.new(register) }
235
+
236
+ it "must return a MemoryOperand" do
237
+ expect(subject.word(memory_operand)).to be_kind_of(
238
+ Ronin::Code::ASM::MemoryOperand
239
+ )
240
+ end
241
+
242
+ it "must have a width of 2" do
243
+ expect(subject.word(memory_operand).width).to eq(2)
244
+ end
245
+ end
246
+ end
247
+
248
+ describe "#dword" do
249
+ it "must return a Ronin::Code::ASM::ImmediateOperand" do
250
+ expect(subject.dword(1)).to be_kind_of(Ronin::Code::ASM::ImmediateOperand)
251
+ end
252
+
253
+ it "must have width of 4" do
254
+ expect(subject.dword(1).width).to eq(4)
255
+ end
256
+
257
+ context "when given a MemoryOperand" do
258
+ let(:register) { Ronin::Code::ASM::Register.new(:eax, 4) }
259
+ let(:memory_operand) { Ronin::Code::ASM::MemoryOperand.new(register) }
260
+
261
+ it "must return a MemoryOperand" do
262
+ expect(subject.dword(memory_operand)).to be_kind_of(
263
+ Ronin::Code::ASM::MemoryOperand
264
+ )
265
+ end
266
+
267
+ it "must have a width of 4" do
268
+ expect(subject.dword(memory_operand).width).to eq(4)
269
+ end
270
+ end
271
+ end
272
+
273
+ describe "#qword" do
274
+ it "must return a Ronin::Code::ASM::ImmediateOperand" do
275
+ expect(subject.qword(1)).to be_kind_of(Ronin::Code::ASM::ImmediateOperand)
276
+ end
277
+
278
+ it "must have width of 8" do
279
+ expect(subject.qword(1).width).to eq(8)
280
+ end
281
+
282
+ context "when given a MemoryOperand" do
283
+ let(:register) { Ronin::Code::ASM::Register.new(:eax, 4) }
284
+ let(:memory_operand) { Ronin::Code::ASM::MemoryOperand.new(register) }
285
+
286
+ it "must return a MemoryOperand" do
287
+ expect(subject.qword(memory_operand)).to be_kind_of(
288
+ Ronin::Code::ASM::MemoryOperand
289
+ )
290
+ end
291
+
292
+ it "must have a width of 8" do
293
+ expect(subject.qword(memory_operand).width).to eq(8)
294
+ end
295
+ end
296
+ end
297
+
298
+ describe "#label" do
299
+ let(:name) { :_start }
300
+
301
+ it "must return the label name" do
302
+ label = subject.label(name) { }
303
+
304
+ expect(label).to eq(name)
305
+ end
306
+
307
+ it "must add the label to the instructions" do
308
+ subject.label(name) { }
309
+
310
+ expect(subject.instructions.last).to eq(name)
311
+ end
312
+
313
+ it "must accept a block" do
314
+ subject.label(name) { push 2 }
315
+
316
+ expect(subject.instructions[-1].name).to eq(:push)
317
+ expect(subject.instructions[-2]).to eq(name)
318
+ end
319
+ end
320
+
321
+ describe "#method_missing" do
322
+ context "when called without a block" do
323
+ it "must add a new instruction" do
324
+ subject.pop
325
+
326
+ expect(subject.instructions[-1].name).to eq(:pop)
327
+ end
328
+ end
329
+
330
+ context "when called with one argument and a block" do
331
+ it "must add a new label" do
332
+ subject._loop { mov eax, ebx }
333
+
334
+ expect(subject.instructions[-2]).to eq(:_loop)
335
+ expect(subject.instructions[-1].name).to eq(:mov)
336
+ end
337
+ end
338
+ end
339
+
340
+ describe "#to_asm" do
341
+ subject do
342
+ described_class.new do
343
+ push eax
344
+ push ebx
345
+ push ecx
346
+
347
+ mov ebx, eax
348
+ mov ebx, eax+0
349
+ mov ebx, eax+4
350
+ mov ebx, eax+esi
351
+ mov ebx, eax+(esi*4)
352
+ mov ebx, eax+(esi*4)+10
353
+ end
354
+ end
355
+
356
+ it "must convert the program to Intel syntax" do
357
+ expect(subject.to_asm).to eq([
358
+ "BITS 32",
359
+ "section .text",
360
+ "_start:",
361
+ "\tpush\teax",
362
+ "\tpush\tebx",
363
+ "\tpush\tecx",
364
+ "\tmov\tebx,\teax",
365
+ "\tmov\tebx,\t[eax]",
366
+ "\tmov\tebx,\t[eax+0x4]",
367
+ "\tmov\tebx,\t[eax+esi]",
368
+ "\tmov\tebx,\t[eax+esi*0x4]",
369
+ "\tmov\tebx,\t[eax+esi*0x4+0xa]",
370
+ ""
371
+ ].join($/))
372
+ end
373
+
374
+ context "when given :att" do
375
+ it "must convert the program to ATT syntax" do
376
+ expect(subject.to_asm(:att)).to eq([
377
+ ".code32",
378
+ ".text",
379
+ "_start:",
380
+ "\tpushl\t%eax",
381
+ "\tpushl\t%ebx",
382
+ "\tpushl\t%ecx",
383
+ "\tmovl\t%eax,\t%ebx",
384
+ "\tmovl\t(%eax),\t%ebx",
385
+ "\tmovl\t0x4(%eax),\t%ebx",
386
+ "\tmovl\t(%eax,%esi),\t%ebx",
387
+ "\tmovl\t(%eax,%esi,4),\t%ebx",
388
+ "\tmovl\t0xa(%eax,%esi,4),\t%ebx",
389
+ ""
390
+ ].join($/))
391
+ end
392
+ end
393
+ end
394
+
395
+ describe "#assemble", integration: true do
396
+ subject do
397
+ described_class.new do
398
+ push eax
399
+ push ebx
400
+ push ecx
401
+
402
+ mov ebx, eax
403
+ mov ebx, eax+0
404
+ mov ebx, eax+4
405
+ mov ebx, eax+esi
406
+ mov ebx, eax+(esi*4)
407
+ mov ebx, eax+(esi*4)+10
408
+ end
409
+ end
410
+
411
+ let(:output) { Tempfile.new(['ronin-asm', '.o']).path }
412
+
413
+ before { subject.assemble(output) }
414
+
415
+ it "must write to the output file" do
416
+ expect(File.size(output)).to be > 0
417
+ end
418
+
419
+ context "when syntax: :intel is given" do
420
+ let(:output) { Tempfile.new(['ronin-asm', '.o']).path }
421
+
422
+ before { subject.assemble(output, syntax: :intel) }
423
+
424
+ it "must write to the output file" do
425
+ expect(File.size(output)).to be > 0
426
+ end
427
+ end
428
+
429
+ context "when syntax is unknown" do
430
+ let(:syntax) { :foo }
431
+
432
+ it do
433
+ expect {
434
+ subject.assemble(output, syntax: syntax)
435
+ }.to raise_error(ArgumentError,"unknown ASM syntax: #{syntax.inspect}")
436
+ end
437
+ end
438
+ end
439
+ end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ require 'ronin/code/asm/register'
4
+
5
+ describe Ronin::Code::ASM::Register do
6
+ let(:register) { described_class.new(:eax, 4) }
7
+
8
+ subject { register }
9
+
10
+ describe "#+" do
11
+ context "when given an Ronin::Code::ASM::MemoryOperand" do
12
+ let(:operand) do
13
+ Ronin::Code::ASM::MemoryOperand.new(nil,10,register,2)
14
+ end
15
+
16
+ subject { register + operand }
17
+
18
+ it { expect(subject).to be_kind_of(Ronin::Code::ASM::MemoryOperand) }
19
+
20
+ it "must set the base" do
21
+ expect(subject.base).to eq(register)
22
+ end
23
+
24
+ it "must preserve the offset, index and scale" do
25
+ expect(subject.offset).to eq(operand.offset)
26
+ expect(subject.index).to eq(operand.index)
27
+ expect(subject.scale).to eq(operand.scale)
28
+ end
29
+ end
30
+
31
+ context "when given a Register" do
32
+ subject { register + register }
33
+
34
+ it { expect(subject).to be_kind_of(Ronin::Code::ASM::MemoryOperand) }
35
+
36
+ it "must set the base" do
37
+ expect(subject.base).to eq(register)
38
+ end
39
+
40
+ it { expect(subject.offset).to eq(0) }
41
+
42
+ it "must set the index" do
43
+ expect(subject.index).to eq(register)
44
+ end
45
+ end
46
+
47
+ context "when given an Integer" do
48
+ let(:offset) { 10 }
49
+
50
+ subject { register + offset }
51
+
52
+ it { expect(subject).to be_kind_of(Ronin::Code::ASM::MemoryOperand) }
53
+
54
+ it "must set the base" do
55
+ expect(subject.base).to eq(register)
56
+ end
57
+
58
+ it "must set the offset" do
59
+ expect(subject.offset).to eq(offset)
60
+ end
61
+ end
62
+
63
+ context "otherwise" do
64
+ it "must raise a TypeError" do
65
+ expect {
66
+ register + Object.new
67
+ }.to raise_error(TypeError)
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "#-" do
73
+ let(:offset) { 10 }
74
+
75
+ subject { register - offset }
76
+
77
+ it { expect(subject).to be_kind_of(Ronin::Code::ASM::MemoryOperand) }
78
+
79
+ it "must set the base" do
80
+ expect(subject.base).to eq(register)
81
+ end
82
+
83
+ it "must set a negative offset" do
84
+ expect(subject.offset).to eq(-offset)
85
+ end
86
+ end
87
+
88
+ describe "#*" do
89
+ let(:scale) { 2 }
90
+
91
+ subject { register * scale }
92
+
93
+ it { expect(subject).to be_kind_of(Ronin::Code::ASM::MemoryOperand) }
94
+
95
+ it { expect(subject.base).to be_nil }
96
+ it { expect(subject.offset).to eq(0) }
97
+
98
+ it "must set the index" do
99
+ expect(subject.index).to eq(register)
100
+ end
101
+
102
+ it "must set the scale" do
103
+ expect(subject.scale).to eq(scale)
104
+ end
105
+ end
106
+
107
+ describe "#to_s" do
108
+ it "must return the register name" do
109
+ expect(subject.to_s).to eq(subject.name.to_s)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: US-ASCII
2
+
3
+ require 'spec_helper'
4
+ require 'ronin/code/asm/shellcode'
5
+
6
+ describe Ronin::Code::ASM::Shellcode do
7
+ describe "#assemble", integration: true do
8
+ subject do
9
+ described_class.new do
10
+ xor eax, eax
11
+ push eax
12
+ push 0x68732f2f
13
+ push 0x6e69622f
14
+ mov ebx, esp
15
+ push eax
16
+ push ebx
17
+ mov ecx, esp
18
+ xor edx, edx
19
+ mov al, 0xb
20
+ int 0x80
21
+ end
22
+ end
23
+
24
+ let(:shellcode) { "1\xC0Ph//shh/bin\x89\xE3PS\x89\xE11\xD2\xB0\v\xCD\x80" }
25
+
26
+ it "must assemble down to raw machine code" do
27
+ expect(subject.assemble).to eq(shellcode)
28
+ end
29
+
30
+ it "must return an ASCII-8bit encoded String" do
31
+ expect(subject.assemble.encoding).to eq(Encoding::ASCII_8BIT)
32
+ end
33
+
34
+ context "with :output" do
35
+ let(:output) do
36
+ Tempfile.new(['ronin-shellcode-custom-path', '.bin']).path
37
+ end
38
+
39
+ it "must write to the custom path" do
40
+ expect(subject.assemble(output: output)).to eq(shellcode)
41
+
42
+ File.binread(output)
43
+ end
44
+ end
45
+
46
+ context "with :syntax is :intel" do
47
+ it "assemble down to raw machine code" do
48
+ expect(subject.assemble(syntax: :intel)).to eq(shellcode)
49
+ end
50
+ end
51
+
52
+ context "with :syntax is :att" do
53
+ it "assemble down to raw machine code" do
54
+ expect(subject.assemble(syntax: :att)).to eq(shellcode)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+
5
+ RSpec.configure do |specs|
6
+ specs.filter_run_excluding :yasm
7
+ end