seccomp-tools 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bd34a83058ba2aee13d7ddc08a6547fc70eac825
4
- data.tar.gz: 0e07f97dfc60b2a21a26a584eadaae86055c3578
3
+ metadata.gz: 52d71ec73ddfa5dab7d22d982b27b2d4c3610080
4
+ data.tar.gz: '08439390a2e4a08d8619eba08b7b66a2814372b1'
5
5
  SHA512:
6
- metadata.gz: 1415f2f48d7a626f3390f0a6705871da51448c92a086c6321bd201e7d5666be1f1691b561d0fbbf91265419905b44052fa1a89d7f59140ff922cba15c737549e
7
- data.tar.gz: c5496c8708fb0f88e8dca0b967bcedd5221bc66ccdc0fd2f93f45d3b76ed8e8446677f5694b1c34d2ce2b4bfa18a12fb56e433187c8ece9d2dc7f9f719e21c5b
6
+ metadata.gz: c69c741edae030e7a775dcad768fa983c627cda1d849947c079d45148453a018cbba249abd18faced059b4c4cdc793e746d707e178b2e845804e7bb9a4c70437
7
+ data.tar.gz: fc8cc5926cba54d561822b3a75b57c8721b1113ef16117f455c8a91f651dbad16ced7f6e09209d2adcd7a8183f4a1f8d327aa490652fe5f8ee7a3be187fe361a
data/README.md CHANGED
@@ -16,6 +16,7 @@ Some features might be CTF-specific, but still useful for analysis of seccomp in
16
16
  * Disasm - Convert bpf to human readable format.
17
17
  - Simple decompile.
18
18
  - Show syscall names.
19
+ * Asm - Write seccomp rules is so easy!
19
20
  * Emu - Emulate seccomp rules.
20
21
  * (TODO) Solve constraints for executing syscalls (e.g. `execve/open/read/write`).
21
22
  * Support multi-architectures.
@@ -39,6 +40,7 @@ $ seccomp-tools --help
39
40
  #
40
41
  # dump Automatically dump seccomp bpf from execution file.
41
42
  # disasm Disassemble seccomp bpf.
43
+ # asm Seccomp bpf assembler.
42
44
  # emu Emulate seccomp rules.
43
45
  #
44
46
  # See 'seccomp-tools --help <command>' to read about a specific subcommand.
@@ -111,7 +113,7 @@ $ seccomp-tools dump spec/binary/twctf-2016-diary -f raw | xxd
111
113
 
112
114
  ### disasm
113
115
 
114
- Disassemble the seccomp bpf.
116
+ Disassemble the seccomp from raw bpf.
115
117
  ```bash
116
118
  $ xxd spec/data/twctf-2016-diary.bpf | head -n 3
117
119
  # 00000000: 2000 0000 0000 0000 1500 0001 0200 0000 ...............
@@ -142,6 +144,65 @@ $ seccomp-tools disasm spec/data/twctf-2016-diary.bpf
142
144
 
143
145
  ```
144
146
 
147
+ ### asm
148
+
149
+ Assemble the seccomp rules into raw bytes.
150
+ Very useful when want to write custom seccomp rules.
151
+
152
+ Supports labels for jumping and use syscall names directly. See example below.
153
+ ```bash
154
+ $ seccomp-tools asm
155
+ # asm - Seccomp bpf assembler.
156
+ #
157
+ # Usage: seccomp-tools asm IN_FILE [options]
158
+ # -o, --output FILE Output result into FILE instead of stdout.
159
+ # -f, --format FORMAT Output format. FORMAT can only be one of <inspect|raw|carray>.
160
+ # Default: inspect
161
+ # -a, --arch ARCH Specify architecture.
162
+ # Supported architectures are <amd64|i386>.
163
+
164
+ # Input file for asm
165
+ $ cat spec/data/libseccomp.asm
166
+ # # check if arch is X86_64
167
+ # A = arch
168
+ # A == 0xc000003e ? next : dead
169
+ # A = sys_number
170
+ # A >= 0x40000000 ? dead : next
171
+ # A == write ? ok : next
172
+ # A == close ? ok : next
173
+ # A == dup ? ok : next
174
+ # A == exit ? ok : next
175
+ # return ERRNO(5)
176
+ # ok:
177
+ # return ALLOW
178
+ # dead:
179
+ # return KILL
180
+
181
+ $ seccomp-tools asm spec/data/libseccomp.asm
182
+ # " \x00\x00\x00\x04\x00\x00\x00\x15\x00\x00\b>\x00\x00\xC0 \x00\x00\x00\x00\x00\x00\x005\x00\x06\x00\x00\x00\x00@\x15\x00\x04\x00\x01\x00\x00\x00\x15\x00\x03\x00\x03\x00\x00\x00\x15\x00\x02\x00 \x00\x00\x00\x15\x00\x01\x00<\x00\x00\x00\x06\x00\x00\x00\x05\x00\x05\x00\x06\x00\x00\x00\x00\x00\xFF\x7F\x06\x00\x00\x00\x00\x00\x00\x00"
183
+
184
+ $ seccomp-tools asm spec/data/libseccomp.asm -f carray
185
+ # unsigned char bpf[] = {32,0,0,0,4,0,0,0,21,0,0,8,62,0,0,192,32,0,0,0,0,0,0,0,53,0,6,0,0,0,0,64,21,0,4,0,1,0,0,0,21,0,3,0,3,0,0,0,21,0,2,0,32,0,0,0,21,0,1,0,60,0,0,0,6,0,0,0,5,0,5,0,6,0,0,0,0,0,255,127,6,0,0,0,0,0,0,0};
186
+
187
+
188
+ # let's asm then disasm!
189
+ $ seccomp-tools asm spec/data/libseccomp.asm -f raw | seccomp-tools disasm -
190
+ # line CODE JT JF K
191
+ # =================================
192
+ # 0000: 0x20 0x00 0x00 0x00000004 A = arch
193
+ # 0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010
194
+ # 0002: 0x20 0x00 0x00 0x00000000 A = sys_number
195
+ # 0003: 0x35 0x06 0x00 0x40000000 if (A >= 0x40000000) goto 0010
196
+ # 0004: 0x15 0x04 0x00 0x00000001 if (A == write) goto 0009
197
+ # 0005: 0x15 0x03 0x00 0x00000003 if (A == close) goto 0009
198
+ # 0006: 0x15 0x02 0x00 0x00000020 if (A == dup) goto 0009
199
+ # 0007: 0x15 0x01 0x00 0x0000003c if (A == exit) goto 0009
200
+ # 0008: 0x06 0x00 0x00 0x00050005 return ERRNO
201
+ # 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
202
+ # 0010: 0x06 0x00 0x00 0x00000000 return KILL
203
+
204
+ ```
205
+
145
206
  ### Emu
146
207
 
147
208
  Emulate seccomp given `sys_nr`, `arg0`, `arg1`, etc.
@@ -0,0 +1,34 @@
1
+ require 'seccomp-tools/asm/compiler'
2
+ require 'seccomp-tools/util'
3
+
4
+ module SeccompTools
5
+ # Assembler of seccomp bpf.
6
+ module Asm
7
+ module_function
8
+
9
+ # Assembler of seccomp bpf.
10
+ # @param [String] str
11
+ # @return [String]
12
+ # Raw bpf bytes.
13
+ # @example
14
+ # asm(<<EOS)
15
+ # # lines start with '#' are comments
16
+ # A = sys_number # here's a comment, too
17
+ # A >= 0x40000000 ? dead : next # 'next' is a keyword, denote the next instruction
18
+ # A == read ? ok : next # custom defined label 'dead' and 'ok'
19
+ # A == 1 ? ok : next # SYS_write = 1 in amd64
20
+ # return ERRNO(1)
21
+ # dead:
22
+ # return KILL
23
+ # ok:
24
+ # return ALLOW
25
+ # EOS
26
+ # #=> <raw binary bytes>
27
+ def asm(str, arch: nil)
28
+ arch = Util.system_arch if arch.nil? # TODO: show warning
29
+ compiler = Compiler.new(arch)
30
+ str.lines.each { |l| compiler.process(l) }
31
+ compiler.compile!.map(&:asm).join
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,221 @@
1
+ require 'seccomp-tools/asm/tokenizer'
2
+ require 'seccomp-tools/bpf'
3
+ require 'seccomp-tools/const'
4
+
5
+ module SeccompTools
6
+ module Asm
7
+ # Compile seccomp rules.
8
+ class Compiler
9
+ def initialize(arch)
10
+ @arch = arch
11
+ @insts = []
12
+ @labels = {}
13
+ @input = []
14
+ end
15
+
16
+ # Before compile assembly codes, process each lines.
17
+ #
18
+ # With this we can support label in seccomp rules.
19
+ # @param [String] line
20
+ # One line of seccomp rule.
21
+ # @return [void]
22
+ def process(line)
23
+ @input << line.strip
24
+ line = remove_comment(line)
25
+ @token = Tokenizer.new(line)
26
+ return if line.empty?
27
+ begin
28
+ res = case line
29
+ when /\?/ then cmp
30
+ when /^#{Tokenizer::LABEL_REGEXP}:/ then define_label
31
+ when /^return/ then ret
32
+ when /^(A|X)\s*=[^=]/ then assign
33
+ when /^A\s*.=/ then alu
34
+ end
35
+ rescue ArgumentError => e
36
+ invalid(@input.size - 1, e.message)
37
+ end
38
+ invalid(@input.size - 1) if res.nil?
39
+ if res.first == :label
40
+ @labels[res.last] = @insts.size
41
+ else
42
+ @insts << res
43
+ end
44
+ res
45
+ end
46
+
47
+ # @return [Array<SeccompTools::BPF>]
48
+ def compile!
49
+ @insts.map.with_index do |inst, idx|
50
+ @line = idx
51
+ case inst.first
52
+ when :assign then compile_assign(inst[1], inst[2])
53
+ when :alu then compile_alu(inst[1], inst[2])
54
+ when :ret then compile_ret(inst[1])
55
+ when :cmp then compile_cmp(inst[1], inst[2], inst[3], inst[4])
56
+ end
57
+ end
58
+ rescue ArgumentError => e
59
+ invalid(@line, e.message)
60
+ end
61
+
62
+ private
63
+
64
+ def emit(*args, k: 0, jt: 0, jf: 0)
65
+ code = 0
66
+ # bad idea, while keys are not duplicated so this is ok.
67
+ args.each do |a|
68
+ code |= Const::BPF::COMMAND.fetch(a, 0)
69
+ code |= Const::BPF::JMP.fetch(a, 0)
70
+ code |= Const::BPF::SRC.fetch(a, 0)
71
+ code |= Const::BPF::MODE.fetch(a, 0)
72
+ code |= Const::BPF::OP.fetch(a, 0)
73
+ code |= Const::BPF::MISCOP.fetch(a, 0)
74
+ end
75
+ BPF.new({ code: code, k: k, jt: jt, jf: jf }, @arch, @line)
76
+ end
77
+
78
+ # A = X / X = A
79
+ # <A|X> = mem[i]
80
+ # <A|X> = 123|sys_const
81
+ # A = args[i]|sys_number|arch
82
+ # A = data[4 * i]
83
+ def compile_assign(dst, src)
84
+ # misc txa / tax
85
+ return emit(:misc, :txa) if dst == :a && src == :x
86
+ return emit(:misc, :tax) if dst == :x && src == :a
87
+ src = evaluate(src)
88
+ # TODO: handle store case.
89
+ ld = dst == :x ? :ldx : :ld
90
+ # <A|X> = <immi>
91
+ return emit(ld, :imm, k: src) if src.is_a?(Integer)
92
+ # now src must be in form [:mem/:data, num]
93
+ return emit(ld, :mem, k: src.last) if src.first == :mem
94
+ # check if num is multiple of 4
95
+ raise ArgumentError, 'Index of data[] must be multiplication of 4' if src.last % 4 != 0
96
+ emit(ld, :abs, k: src.last)
97
+ end
98
+
99
+ def compile_alu(op, val)
100
+ val = evaluate(val)
101
+ src = val == :x ? :x : :k
102
+ val = 0 if val == :x
103
+ emit(:alu, op, src, k: val)
104
+ end
105
+
106
+ def compile_ret(val)
107
+ emit(:ret, k: val)
108
+ end
109
+
110
+ def compile_cmp(op, val, jt, jf)
111
+ jt = label_offset(jt)
112
+ jf = label_offset(jf)
113
+ val = evaluate(val)
114
+ src = val == :x ? :x : :k
115
+ val = 0 if val == :x
116
+ emit(:jmp, op, src, jt: jt, jf: jf, k: val)
117
+ end
118
+
119
+ def label_offset(label)
120
+ return label if label.is_a?(Integer)
121
+ return 0 if label == 'next'
122
+ raise ArgumentError, "Undefined label #{label.inspect}" if @labels[label].nil?
123
+ @labels[label] - @line - 1
124
+ end
125
+
126
+ def evaluate(val)
127
+ return val if val.is_a?(Integer) || val == :x
128
+ # keywords
129
+ val = case val
130
+ when 'sys_number' then [:data, 0]
131
+ when 'arch' then [:data, 4]
132
+ else val
133
+ end
134
+ return Const::Syscall.const_get(@arch.upcase.to_sym)[val.to_sym] if val.is_a?(String)
135
+ # remains are [:mem/:data/:args, <num>]
136
+ # first convert args to data
137
+ val = [:data, val.last * 8 + 16] if val.first == :args
138
+ val
139
+ end
140
+
141
+ attr_reader :token
142
+
143
+ # A <comparison> <sys_str|X|Integer> ? <label|Integer> : <label|Integer>
144
+ def cmp
145
+ token.fetch!('A')
146
+ op = token.fetch!(:comparison)
147
+ dst = token.fetch!(:sys_num_x)
148
+ token.fetch!('?')
149
+ jt = token.fetch!(:goto)
150
+ token.fetch!(':')
151
+ jf = token.fetch!(:goto)
152
+ convert = {
153
+ :< => :>=,
154
+ :<= => :>,
155
+ :!= => :==
156
+ }
157
+ if convert[op]
158
+ op = convert[op]
159
+ jt, jf = jf, jt
160
+ end
161
+ op = {
162
+ :>= => :jge,
163
+ :> => :jgt,
164
+ :== => :jeq
165
+ }[op]
166
+ [:cmp, op, dst, jt, jf]
167
+ end
168
+
169
+ def ret
170
+ token.fetch!('return')
171
+ [:ret, token.fetch!(:ret)]
172
+ end
173
+
174
+ # possible types after '=':
175
+ # A = X
176
+ # X = A
177
+ # A = 123
178
+ # A = data[i]
179
+ # A = mem[i]
180
+ # A = args[i]
181
+ # A = sys_number|arch
182
+ def assign
183
+ dst = token.fetch!(:ax)
184
+ token.fetch!('=')
185
+ src = token.fetch(:ax) ||
186
+ token.fetch(:sys_num_x) ||
187
+ token.fetch(:ary) ||
188
+ token.fetch('sys_number') ||
189
+ token.fetch('arch')
190
+ [:assign, dst, src]
191
+ end
192
+
193
+ def define_label
194
+ name = token.fetch!(:goto)
195
+ token.fetch(':')
196
+ [:label, name]
197
+ end
198
+
199
+ # A op= sys_num_x
200
+ def alu
201
+ token.fetch!('A')
202
+ op = token.fetch!(:alu_op)
203
+ token.fetch!('=')
204
+ src = token.fetch!(:sys_num_x)
205
+ [:alu, op, src]
206
+ end
207
+
208
+ def remove_comment(line)
209
+ line = line.to_s.dup
210
+ line.slice!(/#.*\Z/m)
211
+ line.strip
212
+ end
213
+
214
+ def invalid(line, extra_msg = nil)
215
+ message = "Invalid instruction at line #{line + 1}: #{@input[line].inspect}"
216
+ message += "\n" + 'Error: ' + extra_msg if extra_msg
217
+ raise ArgumentError, message
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,152 @@
1
+ require 'seccomp-tools/const'
2
+ require 'seccomp-tools/instruction/alu'
3
+
4
+ module SeccompTools
5
+ module Asm
6
+ # Fetch tokens from string.
7
+ # This class is for internel usage, used by {Compiler}.
8
+ class Tokenizer
9
+ # a valid label
10
+ LABEL_REGEXP = /[a-z_][a-z0-9_]+/
11
+ attr_accessor :cur
12
+
13
+ # @param [String] str
14
+ # @example
15
+ # Tokenizer.new('return ALLOW')
16
+ def initialize(str)
17
+ @str = str
18
+ @cur = @str.dup
19
+ end
20
+
21
+ # Fetch a token without raising errors.
22
+ def fetch(type)
23
+ fetch!(type)
24
+ rescue ArgumentError
25
+ nil
26
+ end
27
+
28
+ # Fetch a token. When expected token is not found,
29
+ # error with proper message would be raised.
30
+ #
31
+ # @param [String, Symbol] type
32
+ # @example
33
+ # tokenizer = Tokenizer.new('return ALLOW')
34
+ # tokenfizer.fetch!('return')
35
+ # #=> "return"
36
+ # tokenizer.fetch!(:ret)
37
+ # #=> 2147418112
38
+ def fetch!(type)
39
+ @last_match_size = 0
40
+ res = case type
41
+ when String then fetch_str(type) || raise_expected("token #{type.inspect}")
42
+ when :comparison then fetch_strs(COMPARISON).to_sym || raise_expected('a comparison operator')
43
+ when :sys_num_x then fetch_sys_num_x || raise_expected("a syscall number or 'X'")
44
+ when :goto then fetch_number || fetch_label || raise_expected('a number or label name')
45
+ when :ret then fetch_return || raise(ArgumentError, <<-EOS)
46
+ Invalid return type: #{cur.inspect}.
47
+ EOS
48
+ when :ax then fetch_ax || raise_expected("'A' or 'X'")
49
+ when :ary then fetch_ary || raise_expected('data[<num>], mem[<num>], or args[<num>]')
50
+ when :alu_op then fetch_alu || raise_expected('an ALU operator')
51
+ else raise ArgumentError, "Unsupported type: #{type.inspect}"
52
+ end
53
+ slice!
54
+ res
55
+ end
56
+
57
+ private
58
+
59
+ COMPARISON = %w[== != <= >= < >].freeze
60
+
61
+ def fetch_strs(strs)
62
+ strs.find(&method(:fetch_str))
63
+ end
64
+
65
+ def fetch_str(str)
66
+ return nil unless cur.start_with?(str)
67
+ @last_match_size = str.size
68
+ str
69
+ end
70
+
71
+ def fetch_ax
72
+ return :a if fetch_str('A')
73
+ return :x if fetch_str('X')
74
+ nil
75
+ end
76
+
77
+ def fetch_sys_num_x
78
+ return :x if fetch_str('X')
79
+ fetch_number || fetch_syscall
80
+ end
81
+
82
+ # Currently only supports 10-based decimal numbers.
83
+ def fetch_number
84
+ res = fetch_regexp(/^0x[0-9a-f]+/) || fetch_regexp(/^[0-9]+/)
85
+ return nil if res.nil?
86
+ Integer(res)
87
+ end
88
+
89
+ def fetch_syscall
90
+ sys = Const::Syscall::AMD64
91
+ sys = sys.merge(Const::Syscall::I386)
92
+ fetch_strs(sys.keys.map(&:to_s).sort_by(&:size).reverse)
93
+ end
94
+
95
+ def fetch_regexp(regexp)
96
+ idx = cur =~ regexp
97
+ return nil if idx.nil? || idx != 0
98
+ match = cur.match(regexp)[0]
99
+ @last_match_size = match.size
100
+ match
101
+ end
102
+
103
+ def fetch_label
104
+ fetch_regexp(LABEL_REGEXP)
105
+ end
106
+
107
+ # Convert <type>(num) into return value according to {Const::ACTION}
108
+ # @return [Integer, :a]
109
+ def fetch_return
110
+ regexp = /(#{Const::BPF::ACTION.keys.join('|')})(\([0-9]{1,5}\))?/
111
+ action = fetch_regexp(regexp)
112
+ return fetch_str('A') && :a if action.nil?
113
+ # check if action contains '('the next bytes are (<num>)
114
+ ret_val = 0
115
+ if action.include?('(')
116
+ action, val = action.split('(')
117
+ ret_val = val.to_i
118
+ end
119
+ Const::BPF::ACTION[action.to_sym] | ret_val
120
+ end
121
+
122
+ def fetch_ary
123
+ support_name = %w[data mem args]
124
+ regexp = /(#{support_name.join('|')})\[[0-9]{1,2}\]/
125
+ match = fetch_regexp(regexp)
126
+ return nil if match.nil?
127
+ res, val = match.split('[')
128
+ val = val.to_i
129
+ [res.to_sym, val]
130
+ end
131
+
132
+ def fetch_alu
133
+ ops = %w[+ - * / | & ^ << >>]
134
+ op = fetch_strs(ops)
135
+ return nil if op.nil?
136
+ Instruction::ALU::OP_SYM.invert[op.to_sym]
137
+ end
138
+
139
+ def slice!
140
+ ret = cur.slice!(0, @last_match_size)
141
+ cur.strip!
142
+ ret
143
+ end
144
+
145
+ def raise_expected(msg)
146
+ raise ArgumentError, <<-EOS
147
+ Expected #{msg}, while #{cur.split[0].inspect} occured.
148
+ EOS
149
+ end
150
+ end
151
+ end
152
+ end
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  require 'seccomp-tools/const'
2
4
  require 'seccomp-tools/instruction/instruction'
3
5
 
@@ -27,11 +29,18 @@ module SeccompTools
27
29
  # @param [Integer] line
28
30
  # Line number of this filter.
29
31
  def initialize(raw, arch, line)
30
- io = StringIO.new(raw)
31
- @code = io.read(2).unpack('S').first
32
- @jt = io.read(1).ord
33
- @jf = io.read(1).ord
34
- @k = io.read(4).unpack('L').first
32
+ if raw.is_a?(String)
33
+ io = StringIO.new(raw)
34
+ @code = io.read(2).unpack('S').first
35
+ @jt = io.read(1).ord
36
+ @jf = io.read(1).ord
37
+ @k = io.read(4).unpack('L').first
38
+ else
39
+ @code = raw[:code]
40
+ @jt = raw[:jt]
41
+ @jf = raw[:jf]
42
+ @k = raw[:k]
43
+ end
35
44
  @arch = arch
36
45
  @line = line
37
46
  @contexts = Set.new
@@ -44,6 +53,13 @@ module SeccompTools
44
53
  line, code, jt, jf, k, decompile)
45
54
  end
46
55
 
56
+ # Convert to raw bytes.
57
+ # @return [String]
58
+ # Raw bpf bytes.
59
+ def asm
60
+ [code].pack('S*') + [jt, jf].pack('C*') + [k].pack('L')
61
+ end
62
+
47
63
  # Command according to +code+.
48
64
  # @return [Symbol]
49
65
  # See {Const::BPF::COMMAND} for list of commands.
@@ -0,0 +1,54 @@
1
+ require 'seccomp-tools/cli/base'
2
+ require 'seccomp-tools/asm/asm'
3
+
4
+ module SeccompTools
5
+ module CLI
6
+ # Handle 'asm' command.
7
+ class Asm < Base
8
+ # Summary of this command.
9
+ SUMMARY = 'Seccomp bpf assembler.'.freeze
10
+ # Usage of this command.
11
+ USAGE = ('asm - ' + SUMMARY + "\n\n" + 'Usage: seccomp-tools asm IN_FILE [options]').freeze
12
+
13
+ def initialize(*)
14
+ super
15
+ option[:format] = :inspect
16
+ end
17
+
18
+ # Define option parser.
19
+ # @return [OptionParser]
20
+ def parser
21
+ @parser ||= OptionParser.new do |opt|
22
+ opt.banner = usage
23
+ opt.on('-o', '--output FILE', 'Output result into FILE instead of stdout.') do |o|
24
+ option[:ofile] = o
25
+ end
26
+
27
+ opt.on('-f', '--format FORMAT', %i[inspect raw carray],
28
+ 'Output format. FORMAT can only be one of <inspect|raw|carray>.',
29
+ 'Default: inspect') do |f|
30
+ option[:format] = f
31
+ end
32
+
33
+ option_arch(opt)
34
+ end
35
+ end
36
+
37
+ # Handle options.
38
+ # @return [void]
39
+ def handle
40
+ return unless super
41
+ option[:ifile] = argv.shift
42
+ return CLI.show(parser.help) if option[:ifile].nil?
43
+ res = SeccompTools::Asm.asm(input, arch: option[:arch])
44
+ output do
45
+ case option[:format]
46
+ when :inspect then res.inspect + "\n"
47
+ when :raw then res
48
+ when :carray then "unsigned char bpf[] = {#{res.bytes.join(',')}};\n"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -30,6 +30,14 @@ module SeccompTools
30
30
  true
31
31
  end
32
32
 
33
+ # If +option[:ifile]+ is '-', read from stdin,
34
+ # otherwise, read from file.
35
+ # @return [String]
36
+ # String read from file.
37
+ def input
38
+ option[:ifile] == '-' ? STDIN.read.force_encoding('ascii-8bit') : IO.binread(option[:ifile])
39
+ end
40
+
33
41
  # Write data to stdout or file(s).
34
42
  # @yieldreturn [String]
35
43
  # The data to be written.
@@ -1,3 +1,4 @@
1
+ require 'seccomp-tools/cli/asm'
1
2
  require 'seccomp-tools/cli/disasm'
2
3
  require 'seccomp-tools/cli/dump'
3
4
  require 'seccomp-tools/cli/emu'
@@ -10,6 +11,7 @@ module SeccompTools
10
11
  COMMANDS = {
11
12
  'dump' => SeccompTools::CLI::Dump,
12
13
  'disasm' => SeccompTools::CLI::Disasm,
14
+ 'asm' => SeccompTools::CLI::Asm,
13
15
  'emu' => SeccompTools::CLI::Emu
14
16
  }.freeze
15
17
 
@@ -3,7 +3,7 @@ require 'seccomp-tools/disasm/disasm'
3
3
 
4
4
  module SeccompTools
5
5
  module CLI
6
- # Handle 'dump' command.
6
+ # Handle 'disasm' command.
7
7
  class Disasm < Base
8
8
  # Summary of this command.
9
9
  SUMMARY = 'Disassemble seccomp bpf.'.freeze
@@ -29,7 +29,7 @@ module SeccompTools
29
29
  return unless super
30
30
  option[:ifile] = argv.shift
31
31
  return CLI.show(parser.help) if option[:ifile].nil?
32
- output { SeccompTools::Disasm.disasm(IO.binread(option[:ifile]), arch: option[:arch]) }
32
+ output { SeccompTools::Disasm.disasm(input, arch: option[:arch]) }
33
33
  end
34
34
  end
35
35
  end
@@ -42,7 +42,7 @@ module SeccompTools
42
42
  return unless super
43
43
  option[:ifile] = argv.shift
44
44
  return CLI.show(parser.help) if option[:ifile].nil?
45
- raw = IO.binread(option[:ifile])
45
+ raw = input
46
46
  insts = SeccompTools::Disasm.to_bpf(raw, option[:arch]).map(&:inst)
47
47
  disasm = SeccompTools::Disasm.disasm(raw, arch: option[:arch])
48
48
  sys, *args = argv
@@ -28,7 +28,7 @@ module SeccompTools
28
28
  # Run emulation!
29
29
  # @return [Hash{Symbol, Integer => Integer}]
30
30
  def run
31
- @values = { pc: 0 }
31
+ @values = { pc: 0, a: 0, x: 0 }
32
32
  loop do
33
33
  break if @values[:ret] # break when returned
34
34
  yield(@values) if block_given?
@@ -4,6 +4,20 @@ module SeccompTools
4
4
  module Instruction
5
5
  # Instruction alu.
6
6
  class ALU < Base
7
+ # Mapping from name to operator.
8
+ OP_SYM = {
9
+ add: :+,
10
+ sub: :-,
11
+ mul: :*,
12
+ div: :/,
13
+ or: :|,
14
+ and: :&,
15
+ lsh: :<<,
16
+ rsh: :>>,
17
+ # neg: :-, # should not be invoked
18
+ # mod: :%, # unsupported
19
+ xor: :^
20
+ }.freeze
7
21
  # Decompile instruction.
8
22
  def decompile
9
23
  return 'A = -A' if op == :neg
@@ -36,23 +50,15 @@ module SeccompTools
36
50
  end
37
51
 
38
52
  def op_sym
39
- case op
40
- when :add then :+
41
- when :sub then :-
42
- when :mul then :*
43
- when :div then :/
44
- when :or then :|
45
- when :and then :&
46
- when :lsh then :<<
47
- when :rsh then :>>
48
- # when :neg then :- # should not invoke this method
49
- # when :mod then :% # unsupported
50
- when :xor then :^
51
- end
53
+ OP_SYM[op]
52
54
  end
53
55
 
54
56
  def src_str
55
- src == :x ? 'X' : src.to_s
57
+ return 'X' if src == :x
58
+ case op
59
+ when :lsh, :rsh then src.to_s
60
+ else '0x' + src.to_s(16)
61
+ end
56
62
  end
57
63
 
58
64
  def src
@@ -1,4 +1,4 @@
1
1
  module SeccompTools
2
2
  # Gem version.
3
- VERSION = '1.0.0'.freeze
3
+ VERSION = '1.1.0'.freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: seccomp-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - david942j
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-10 00:00:00.000000000 Z
11
+ date: 2017-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: codeclimate-test-reporter
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.10'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -124,7 +138,11 @@ files:
124
138
  - ext/ptrace/extconf.rb
125
139
  - ext/ptrace/ptrace.c
126
140
  - lib/seccomp-tools.rb
141
+ - lib/seccomp-tools/asm/asm.rb
142
+ - lib/seccomp-tools/asm/compiler.rb
143
+ - lib/seccomp-tools/asm/tokenizer.rb
127
144
  - lib/seccomp-tools/bpf.rb
145
+ - lib/seccomp-tools/cli/asm.rb
128
146
  - lib/seccomp-tools/cli/base.rb
129
147
  - lib/seccomp-tools/cli/cli.rb
130
148
  - lib/seccomp-tools/cli/disasm.rb
@@ -170,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
170
188
  version: '0'
171
189
  requirements: []
172
190
  rubyforge_project:
173
- rubygems_version: 2.6.10
191
+ rubygems_version: 2.5.2
174
192
  signing_key:
175
193
  specification_version: 4
176
194
  summary: seccomp-tools