ronin-code-asm 1.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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