seccomp-tools 0.1.0 → 1.0.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: afb07d55f6cf7b437b6829709c0897af34ae112e
4
- data.tar.gz: 72b290bd53662794f8b588d59912733096d7d79d
3
+ metadata.gz: bd34a83058ba2aee13d7ddc08a6547fc70eac825
4
+ data.tar.gz: 0e07f97dfc60b2a21a26a584eadaae86055c3578
5
5
  SHA512:
6
- metadata.gz: 0d38df515ef8dc6474b3b407be4c3e0959cd2e28227e863c3826d8b437154c65ca7e5b385d1db4ba6ae1010f1058c25b089d2918bbe6b6b805a9bbdbe438ad07
7
- data.tar.gz: 54730dafe8e3a77743f0d2eaa8cb947767d00b9aa3b5f2cd4a8d77386eb65decc664f74d84998d7630c7216a985cf8d8e7ca7345a63eae99aa3d9ae449d495db
6
+ metadata.gz: 1415f2f48d7a626f3390f0a6705871da51448c92a086c6321bd201e7d5666be1f1691b561d0fbbf91265419905b44052fa1a89d7f59140ff922cba15c737549e
7
+ data.tar.gz: c5496c8708fb0f88e8dca0b967bcedd5221bc66ccdc0fd2f93f45d3b76ed8e8446677f5694b1c34d2ce2b4bfa18a12fb56e433187c8ece9d2dc7f9f719e21c5b
data/README.md CHANGED
@@ -16,14 +16,16 @@ 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
- - (TODO) Simplify disassemble result.
19
+ * Emu - Emulate seccomp rules.
20
20
  * (TODO) Solve constraints for executing syscalls (e.g. `execve/open/read/write`).
21
21
  * Support multi-architectures.
22
22
 
23
23
  ## Installation
24
24
 
25
- Will be available on RubyGems.org!
26
- (TODO)
25
+ Available on RubyGems.org!
26
+ ```
27
+ $ gem install seccomp-tools
28
+ ```
27
29
 
28
30
  ## Command Line Interface
29
31
 
@@ -36,7 +38,8 @@ $ seccomp-tools --help
36
38
  # List of commands:
37
39
  #
38
40
  # dump Automatically dump seccomp bpf from execution file.
39
- # disasm Disassembly seccomp bpf.
41
+ # disasm Disassemble seccomp bpf.
42
+ # emu Emulate seccomp rules.
40
43
  #
41
44
  # See 'seccomp-tools --help <command>' to read about a specific subcommand.
42
45
 
@@ -139,11 +142,46 @@ $ seccomp-tools disasm spec/data/twctf-2016-diary.bpf
139
142
 
140
143
  ```
141
144
 
145
+ ### Emu
146
+
147
+ Emulate seccomp given `sys_nr`, `arg0`, `arg1`, etc.
148
+ ```bash
149
+ $ seccomp-tools emu --help
150
+ # emu - Emulate seccomp rules.
151
+ #
152
+ # Usage: seccomp-tools emu [options] BPF_FILE [sys_nr [arg0 [arg1 ... arg5]]]
153
+ # -a, --arch ARCH Specify architecture.
154
+ # Supported architectures are <amd64|i386>.
155
+ # -q, --[no-]quiet Run quietly, only show emulation result.
156
+
157
+ $ seccomp-tools emu spec/data/libseccomp.bpf 0x3
158
+ # line CODE JT JF K
159
+ # =================================
160
+ # 0000: 0x20 0x00 0x00 0x00000004 A = arch
161
+ # 0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010
162
+ # 0002: 0x20 0x00 0x00 0x00000000 A = sys_number
163
+ # 0003: 0x35 0x06 0x00 0x40000000 if (A >= 0x40000000) goto 0010
164
+ # 0004: 0x15 0x04 0x00 0x00000001 if (A == write) goto 0009
165
+ # 0005: 0x15 0x03 0x00 0x00000003 if (A == close) goto 0009
166
+ # 0006: 0x15 0x02 0x00 0x00000020 if (A == dup) goto 0009
167
+ # 0007: 0x15 0x01 0x00 0x0000003c if (A == exit) goto 0009
168
+ # 0008: 0x06 0x00 0x00 0x00050005 return ERRNO
169
+ # 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
170
+ # 0010: 0x06 0x00 0x00 0x00000000 return KILL
171
+ #
172
+ # return ALLOW at line 0009
173
+
174
+ ```
175
+
142
176
  ## Screenshots
143
177
 
144
178
  ### Dump
145
179
  ![dump](https://github.com/david942j/seccomp-tools/blob/master/examples/dump-diary.png?raw=true)
146
180
 
181
+ ### Emu
182
+ ![emu](https://github.com/david942j/seccomp-tools/blob/master/examples/emu-libseccomp.png?raw=true)
183
+
184
+ ![emu](https://github.com/david942j/seccomp-tools/blob/master/examples/emu-amigo.png?raw=true)
147
185
 
148
186
  ## I Need You
149
187
  Any suggestion or feature request is welcome!
@@ -4,10 +4,22 @@ require 'seccomp-tools/instruction/instruction'
4
4
  module SeccompTools
5
5
  # Define the +struct sock_filter+, while more powerful.
6
6
  class BPF
7
- attr_reader :line, :code, :jt, :jf, :k, :arch
8
- # @return [Array<SeccompTools::Context>]
7
+ # @return [Integer] Line number.
8
+ attr_reader :line
9
+ # @return [Integer] BPF code.
10
+ attr_reader :code
11
+ # @return [Integer] BPF JT.
12
+ attr_reader :jt
13
+ # @return [Integer] BPF JF.
14
+ attr_reader :jf
15
+ # @return [Integer] BPF K.
16
+ attr_reader :k
17
+ # @return [Symbol] Architecture.
18
+ attr_reader :arch
19
+ # @return [Set<Context>] Possible contexts before this instruction.
9
20
  attr_accessor :contexts
10
21
 
22
+ # Instantiate a {BPF} object.
11
23
  # @param [String] raw
12
24
  # One +struct sock_filter+ in bytes, should exactly 8 bytes.
13
25
  # @param [Symbol] arch
@@ -22,6 +34,7 @@ module SeccompTools
22
34
  @k = io.read(4).unpack('L').first
23
35
  @arch = arch
24
36
  @line = line
37
+ @contexts = Set.new
25
38
  end
26
39
 
27
40
  # Pretty display the disassemble result.
@@ -31,12 +44,16 @@ module SeccompTools
31
44
  line, code, jt, jf, k, decompile)
32
45
  end
33
46
 
47
+ # Command according to +code+.
34
48
  # @return [Symbol]
49
+ # See {Const::BPF::COMMAND} for list of commands.
35
50
  def command
36
51
  Const::BPF::COMMAND.invert[code & 7]
37
52
  end
38
53
 
54
+ # Decompile.
39
55
  # @return [String]
56
+ # Decompile string.
40
57
  def decompile
41
58
  inst.decompile
42
59
  end
@@ -49,12 +66,11 @@ module SeccompTools
49
66
  # Context after this instruction.
50
67
  # @return [void]
51
68
  def branch(context, &block)
52
- # TODO: consider alu
53
69
  inst.branch(context).each(&block)
54
70
  end
55
71
 
56
- private
57
-
72
+ # Corresponding instruction object.
73
+ # @return [SeccompTools::Instruction::Base]
58
74
  def inst
59
75
  @inst ||= case command
60
76
  when :alu then SeccompTools::Instruction::ALU
@@ -1,10 +1,19 @@
1
1
  require 'optparse'
2
2
 
3
+ require 'seccomp-tools/util'
4
+
3
5
  module SeccompTools
4
6
  module CLI
5
7
  # Base class for handlers.
6
8
  class Base
7
- attr_reader :option, :argv
9
+ # @return [Hash{Symbol => Object}] Options.
10
+ attr_reader :option
11
+ # @return [Array<String>] Arguments array.
12
+ attr_reader :argv
13
+
14
+ # Instantiate a {Base} object.
15
+ # @param [Array<String>] argv
16
+ # Arguments array.
8
17
  def initialize(argv)
9
18
  @option = {}
10
19
  @argv = argv
@@ -22,15 +31,19 @@ module SeccompTools
22
31
  end
23
32
 
24
33
  # Write data to stdout or file(s).
25
- # @param [String] data
26
- # Data.
34
+ # @yieldreturn [String]
35
+ # The data to be written.
27
36
  # @return [void]
28
- def output(data)
37
+ def output
29
38
  # if file name not present, just output to stdout.
30
- return $stdout.write(data) if option[:ofile].nil?
39
+ return $stdout.write(yield) if option[:ofile].nil?
31
40
  # times of calling output
32
41
  @serial ||= 0
33
- IO.binwrite(file_of(option[:ofile], @serial), data)
42
+ # Write to file, we should disable colorize
43
+ enabled = Util.colorize_enabled?
44
+ Util.disable_color! if enabled
45
+ IO.binwrite(file_of(option[:ofile], @serial), yield)
46
+ Util.enable_color! if enabled
34
47
  @serial += 1
35
48
  end
36
49
 
@@ -62,6 +75,14 @@ module SeccompTools
62
75
  def usage
63
76
  self.class.const_get(:USAGE)
64
77
  end
78
+
79
+ def option_arch(opt)
80
+ supported = Util.supported_archs
81
+ opt.on('-a', '--arch ARCH', supported, 'Specify architecture.',
82
+ "Supported architectures are <#{supported.join('|')}>.") do |a|
83
+ option[:arch] = a
84
+ end
85
+ end
65
86
  end
66
87
  end
67
88
  end
@@ -1,5 +1,6 @@
1
1
  require 'seccomp-tools/cli/disasm'
2
2
  require 'seccomp-tools/cli/dump'
3
+ require 'seccomp-tools/cli/emu'
3
4
  require 'seccomp-tools/version'
4
5
 
5
6
  module SeccompTools
@@ -8,7 +9,8 @@ module SeccompTools
8
9
  # Handled commands
9
10
  COMMANDS = {
10
11
  'dump' => SeccompTools::CLI::Dump,
11
- 'disasm' => SeccompTools::CLI::Disasm
12
+ 'disasm' => SeccompTools::CLI::Disasm,
13
+ 'emu' => SeccompTools::CLI::Emu
12
14
  }.freeze
13
15
 
14
16
  # Main usage message.
@@ -1,13 +1,12 @@
1
1
  require 'seccomp-tools/cli/base'
2
- require 'seccomp-tools/disasm'
3
- require 'seccomp-tools/util'
2
+ require 'seccomp-tools/disasm/disasm'
4
3
 
5
4
  module SeccompTools
6
5
  module CLI
7
6
  # Handle 'dump' command.
8
7
  class Disasm < Base
9
8
  # Summary of this command.
10
- SUMMARY = 'Disassembly seccomp bpf.'.freeze
9
+ SUMMARY = 'Disassemble seccomp bpf.'.freeze
11
10
  # Usage of this command.
12
11
  USAGE = ('disasm - ' + SUMMARY + "\n\n" + 'Usage: seccomp-tools disasm BPF_FILE [options]').freeze
13
12
 
@@ -20,11 +19,7 @@ module SeccompTools
20
19
  option[:ofile] = o
21
20
  end
22
21
 
23
- supported = Util.supported_archs
24
- opt.on('-a', '--arch ARCH', supported, 'Specify architecture.',
25
- "Supported architectures are <#{supported.join('|')}>.") do |a|
26
- option[:arch] = a
27
- end
22
+ option_arch(opt)
28
23
  end
29
24
  end
30
25
 
@@ -34,7 +29,7 @@ module SeccompTools
34
29
  return unless super
35
30
  option[:ifile] = argv.shift
36
31
  return CLI.show(parser.help) if option[:ifile].nil?
37
- output(SeccompTools::Disasm.disasm(IO.binread(option[:ifile]), arch: option[:arch]))
32
+ output { SeccompTools::Disasm.disasm(IO.binread(option[:ifile]), arch: option[:arch]) }
38
33
  end
39
34
  end
40
35
  end
@@ -1,5 +1,5 @@
1
1
  require 'seccomp-tools/cli/base'
2
- require 'seccomp-tools/disasm'
2
+ require 'seccomp-tools/disasm/disasm'
3
3
  require 'seccomp-tools/dumper'
4
4
 
5
5
  module SeccompTools
@@ -55,9 +55,9 @@ module SeccompTools
55
55
  option[:command] = argv.shift unless argv.empty?
56
56
  SeccompTools::Dumper.dump('/bin/sh', '-c', option[:command], limit: option[:limit]) do |bpf, arch|
57
57
  case option[:format]
58
- when :inspect then output('"' + bpf.bytes.map { |b| format('\\x%02X', b) }.join + "\"\n")
59
- when :raw then output(bpf)
60
- when :disasm then output(SeccompTools::Disasm.disasm(bpf, arch: arch))
58
+ when :inspect then output { '"' + bpf.bytes.map { |b| format('\\x%02X', b) }.join + "\"\n" }
59
+ when :raw then output { bpf }
60
+ when :disasm then output { SeccompTools::Disasm.disasm(bpf, arch: arch) }
61
61
  end
62
62
  end
63
63
  end
@@ -0,0 +1,79 @@
1
+ require 'set'
2
+
3
+ require 'seccomp-tools/cli/base'
4
+ require 'seccomp-tools/disasm/disasm'
5
+ require 'seccomp-tools/emulator'
6
+ require 'seccomp-tools/util'
7
+
8
+ module SeccompTools
9
+ module CLI
10
+ # Handle 'emu' command.
11
+ class Emu < Base
12
+ # Summary of this command.
13
+ SUMMARY = 'Emulate seccomp rules.'.freeze
14
+ # Usage of this command.
15
+ USAGE = ('emu - ' +
16
+ SUMMARY +
17
+ "\n\n" \
18
+ 'Usage: seccomp-tools emu [options] BPF_FILE [sys_nr [arg0 [arg1 ... arg5]]]').freeze
19
+
20
+ def initialize(*)
21
+ super
22
+ option[:verbose] = 1
23
+ end
24
+
25
+ # Define option parser.
26
+ # @return [OptionParser]
27
+ def parser
28
+ @parser ||= OptionParser.new do |opt|
29
+ opt.banner = usage
30
+
31
+ option_arch(opt)
32
+
33
+ opt.on('-q', '--[no-]quiet', 'Run quietly, only show emulation result.') do |v|
34
+ option[:verbose] = 0 if v
35
+ end
36
+ end
37
+ end
38
+
39
+ # Handle options.
40
+ # @return [void]
41
+ def handle
42
+ return unless super
43
+ option[:ifile] = argv.shift
44
+ return CLI.show(parser.help) if option[:ifile].nil?
45
+ raw = IO.binread(option[:ifile])
46
+ insts = SeccompTools::Disasm.to_bpf(raw, option[:arch]).map(&:inst)
47
+ disasm = SeccompTools::Disasm.disasm(raw, arch: option[:arch])
48
+ sys, *args = argv
49
+ sys = Integer(sys) if sys
50
+ args.map! { |v| Integer(v) }
51
+ trace = Set.new
52
+ res = SeccompTools::Emulator.new(insts, sys_nr: sys, args: args, arch: option[:arch]).run do |ctx|
53
+ trace << ctx[:pc]
54
+ end
55
+
56
+ if option[:verbose] >= 1
57
+ disasm = disasm.lines
58
+ output { disasm.shift }
59
+ output { disasm.shift }
60
+ disasm.each_with_index do |line, idx|
61
+ output do
62
+ next line if trace.member?(idx)
63
+ Util.colorize(line, t: :gray)
64
+ end
65
+ # Too much remain, omit them.
66
+ rem = disasm.size - idx - 1
67
+ break output { Util.colorize("... (omitting #{rem} lines)\n", t: :gray) } if rem > 3 && idx > res[:pc] + 4
68
+ end
69
+ output { "\n" }
70
+ end
71
+ output do
72
+ ret_type = Const::BPF::ACTION.invert[res[:ret] & 0x7fff0000]
73
+ errno = ret_type == :ERRNO ? "(#{res[:ret] & 0xffff})" : ''
74
+ format("return %s%s at line %04d\n", ret_type, errno, res[:pc])
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -87,11 +87,18 @@ module SeccompTools
87
87
  module_function
88
88
 
89
89
  # To dynamically fetch constants from files.
90
+ # @param [Symbol] cons
91
+ # Name of const.
92
+ # @return [Object]
93
+ # Value of that +cons+.
90
94
  def const_missing(cons)
91
95
  load_const(cons) || super
92
96
  end
93
97
 
94
98
  # Load from file and define const value.
99
+ # @param [Symbol] cons
100
+ # Name of const.
101
+ # @return [Object]
95
102
  def load_const(cons)
96
103
  arch = cons.to_s.downcase
97
104
  filename = File.join(__dir__, 'consts', "#{arch}.rb")
@@ -0,0 +1,80 @@
1
+ module SeccompTools
2
+ module Disasm
3
+ # Context for disassembler to analyze.
4
+ #
5
+ # This context only care if +reg/mem+ can be one of +data[*]+.
6
+ class Context
7
+ # @return [Hash{Integer, Symbol => Integer?}] Records reg and mem values.
8
+ attr_reader :values
9
+
10
+ # Instantiate a {Context} object.
11
+ # @param [Integer?] a
12
+ # Value to be set to +A+ register.
13
+ # @param [Integer?] x
14
+ # Value to be set to +X+ register.
15
+ # @param [Hash{Integer => Integer?}] mem
16
+ # Value to be set to +mem+.
17
+ def initialize(a: nil, x: nil, mem: {})
18
+ @values = mem
19
+ 16.times { |i| @values[i] ||= nil } # make @values always has all keys
20
+ @values[:a] = a
21
+ @values[:x] = x
22
+ end
23
+
24
+ # Implement a deep dup.
25
+ # @return [Context]
26
+ def dup
27
+ Context.new(a: a, x: x, mem: values.dup)
28
+ end
29
+
30
+ # Register A.
31
+ # @return [Integer?]
32
+ def a
33
+ values[:a]
34
+ end
35
+
36
+ # Register X.
37
+ # @return [Integer?]
38
+ def x
39
+ values[:x]
40
+ end
41
+
42
+ # For conveniently get instance variable.
43
+ # @param [String, Symbol, Integer] key
44
+ # @return [Integer?]
45
+ def [](key)
46
+ return values[key] if key.is_a?(Integer) # mem
47
+ values[key.downcase.to_sym]
48
+ end
49
+
50
+ # For conveniently set instance variable.
51
+ # @param [#downcase, Integer] key
52
+ # Can be +'A', 'a', :a, 'X', 'x', :x+ or an integer.
53
+ # @param [Integer?] val
54
+ # Value to set.
55
+ # @return [void]
56
+ def []=(key, val)
57
+ if key.is_a?(Integer)
58
+ raise RangeError, "Expect 0 <= key < 16, got #{key}." unless key.between?(0, 15)
59
+ raise RangeError, "Expect 0 <= val < 64, got #{val}." unless val.nil? || val.between?(0, 63)
60
+ values[key] = val
61
+ else
62
+ values[key.downcase.to_sym] = val
63
+ end
64
+ end
65
+
66
+ # For +Set+ to compare two {Context} object.
67
+ # @param [Context] other
68
+ # @return [Boolean]
69
+ def eql?(other)
70
+ values.eql?(other.values)
71
+ end
72
+
73
+ # For +Set+ to get hash key.
74
+ # @return [Integer]
75
+ def hash
76
+ values.hash
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,46 @@
1
+ require 'set'
2
+
3
+ require 'seccomp-tools/bpf'
4
+ require 'seccomp-tools/disasm/context'
5
+ require 'seccomp-tools/util'
6
+
7
+ module SeccompTools
8
+ # Disassembler of seccomp bpf.
9
+ module Disasm
10
+ module_function
11
+
12
+ # Disassemble bpf codes.
13
+ # @param [String] raw
14
+ # The raw bpf bytes.
15
+ # @param [Symbol] arch
16
+ # Architecture.
17
+ def disasm(raw, arch: nil)
18
+ codes = to_bpf(raw, arch)
19
+ contexts = Array.new(codes.size) { Set.new }
20
+ contexts[0].add(Context.new)
21
+ # all we care is if A is exactly one of data[*]
22
+ dis = codes.zip(contexts).map do |code, ctxs|
23
+ ctxs.each do |ctx|
24
+ code.branch(ctx) do |pc, c|
25
+ contexts[pc].add(c) unless pc >= contexts.size
26
+ end
27
+ end
28
+ code.contexts = ctxs
29
+ code.disasm
30
+ end.join("\n")
31
+ <<EOS + dis + "\n"
32
+ line CODE JT JF K
33
+ =================================
34
+ EOS
35
+ end
36
+
37
+ # Convert raw bpf string to array of {BPF}.
38
+ # @param [String] raw
39
+ # @param [Symbol] arch
40
+ # @return [Array<BPF>]
41
+ def to_bpf(raw, arch)
42
+ arch ||= Util.system_arch
43
+ raw.scan(/.{8}/m).map.with_index { |b, i| BPF.new(b, arch, i) }
44
+ end
45
+ end
46
+ end
@@ -30,8 +30,6 @@ module SeccompTools
30
30
  # dump('spec/binary/twctf-2016-diary') { |c| c[0, 10] }
31
31
  # #=> [" \x00\x00\x00\x00\x00\x00\x00\x15\x00"]
32
32
  # @todo
33
- # Detect execution file architecture to know which syscall number should be traced.
34
- # @todo
35
33
  # +timeout+ option.
36
34
  def dump(*args, limit: 1, &block)
37
35
  pid = fork { handle_child(*args) }
@@ -40,6 +38,9 @@ module SeccompTools
40
38
 
41
39
  # Do the tracer things.
42
40
  class Handler
41
+ # Instantiate a {Handler} object.
42
+ # @param [Integer] pid
43
+ # The process id after fork.
43
44
  def initialize(pid)
44
45
  Process.waitpid(pid)
45
46
  opt = Ptrace::O_TRACESYSGOOD | Ptrace::O_TRACECLONE | Ptrace::O_TRACEFORK | Ptrace::O_TRACEVFORK
@@ -0,0 +1,155 @@
1
+ require 'seccomp-tools/const'
2
+
3
+ module SeccompTools
4
+ # For emulation seccomp.
5
+ class Emulator
6
+ # Instantiate a {Emulator} object.
7
+ #
8
+ # All parameters except +instructions+ are optional, while a warning will be shown if unset data being accessed.
9
+ # @param [Array<Instruction::Base>] instructions
10
+ # @param [Integer] sys_nr
11
+ # Syscall number.
12
+ # @param [Array<Integer>] args
13
+ # Syscall arguments
14
+ # @param [Integer] instruction_pointer
15
+ # Program counter address when this syscall invoked.
16
+ # @param [Symbol] arch
17
+ # If not given, use system architecture as default.
18
+ #
19
+ # See {SeccompTools::Util.supported_archs} for list of supported architectures.
20
+ def initialize(instructions, sys_nr: nil, args: [], instruction_pointer: nil, arch: nil)
21
+ @instructions = instructions
22
+ @sys_nr = sys_nr
23
+ @args = args
24
+ @ip = instruction_pointer
25
+ @arch = audit(arch || Util.system_arch)
26
+ end
27
+
28
+ # Run emulation!
29
+ # @return [Hash{Symbol, Integer => Integer}]
30
+ def run
31
+ @values = { pc: 0 }
32
+ loop do
33
+ break if @values[:ret] # break when returned
34
+ yield(@values) if block_given?
35
+ inst = @instructions[pc]
36
+ op, *args = inst.symbolize
37
+ case op
38
+ when :ret then ret(args.first) # ret
39
+ when :ld then ld(args[0], args[1]) # ld/ldx
40
+ when :st then st(args[0], args[1]) # st/stx
41
+ when :jmp then jmp(args[0]) # directly jmp
42
+ when :cmp then cmp(*args[0, 4]) # jmp with comparsion
43
+ when :alu then alu(args[0], args[1]) # alu
44
+ when :misc then misc(args[0]) # misc: txa/tax
45
+ end
46
+ set(:pc, get(:pc) + 1) if %i[ld st alu misc].include?(op)
47
+ end
48
+ @values
49
+ end
50
+
51
+ private
52
+
53
+ def pc
54
+ @values[:pc]
55
+ end
56
+
57
+ def audit(arch)
58
+ type = case arch
59
+ when :amd64 then 'ARCH_X86_64'
60
+ when :i386 then 'ARCH_I386'
61
+ end
62
+ Const::Audit::ARCH[type]
63
+ end
64
+
65
+ def ret(num)
66
+ set(:ret, num == :a ? get(:a) : num)
67
+ end
68
+
69
+ # @param [:a, :x] dst
70
+ # @param [{rel: <:mem, :immi, :data>, val: Integer}] src
71
+ def ld(dst, src)
72
+ val = case src[:rel]
73
+ when :immi then src[:val]
74
+ when :mem then get(:mem, src[:val])
75
+ when :data then get(:data, src[:val])
76
+ end
77
+ set(dst, val)
78
+ end
79
+
80
+ def st(reg, index)
81
+ raise IndexError, "Expect 0 <= index < 16, got: #{index}" unless index.between?(0, 15)
82
+ set(:mem, index, get(reg))
83
+ end
84
+
85
+ def jmp(k)
86
+ set(:pc, get(:pc) + k + 1)
87
+ end
88
+
89
+ def cmp(op, src, jt, jf)
90
+ src = get(:x) if src == :x
91
+ a = get(:a)
92
+ val = a.send(op, src)
93
+ val = (val != 0) if val.is_a?(Integer) # handle & operator
94
+ j = val ? jt : jf
95
+ set(:pc, get(:pc) + j + 1)
96
+ end
97
+
98
+ def alu(op, src)
99
+ if op == :neg
100
+ set(:a, 2**32 - get(:a))
101
+ else
102
+ src = get(:x) if src == :x
103
+ set(:a, get(:a).send(op, src))
104
+ end
105
+ end
106
+
107
+ def misc(op)
108
+ case op
109
+ when :txa then set(:a, get(:x))
110
+ when :tax then set(:x, get(:a))
111
+ end
112
+ end
113
+
114
+ def set(*arg, val)
115
+ if arg.size == 1
116
+ arg = arg.first
117
+ raise ArgumentError, "Invalid #{arg}" unless %i[a x pc ret].include?(arg)
118
+ @values[arg] = val & 0xffffffff
119
+ else
120
+ raise ArgumentError, arg.to_s unless arg.first == :mem
121
+ raise IndexError, "Invalid index: #{arg[1]}" unless arg[1].between?(0, 15)
122
+ @values[arg[1]] = val & 0xffffffff
123
+ end
124
+ end
125
+
126
+ def get(*arg)
127
+ if arg.size == 1
128
+ arg = arg.first
129
+ raise ArgumentError, "Invalid #{arg}" unless %i[a x pc ret].include?(arg)
130
+ undefined(arg.upcase) if @values[arg].nil?
131
+ return @values[arg]
132
+ end
133
+ return @values[arg[1]] if arg.first == :mem
134
+ data_of(arg[1])
135
+ end
136
+
137
+ def data_of(index)
138
+ raise IndexError, "Invalid index: #{index}" unless (index & 3).zero? && index.between?(0, 63)
139
+ index /= 4
140
+ case index
141
+ when 0 then @sys_nr || undefined('sys_number')
142
+ when 1 then @arch || undefined('arch')
143
+ when 2 then @ip & 0xffffffff || undefined('instruction_pointer')
144
+ when 3 then @ip >> 32 || undefined('instruction_pointer')
145
+ else
146
+ val = @args[(index - 4) / 2] || undefined("args[#{(index - 4) / 2}]")
147
+ (val >> (index.even? ? 0 : 32)) & 0xffffffff
148
+ end
149
+ end
150
+
151
+ def undefined(var)
152
+ raise format("Undefined Variable\n\t%04d: %s <- `%s` is undefined", pc, @instructions[pc].decompile, var)
153
+ end
154
+ end
155
+ end
@@ -7,7 +7,24 @@ module SeccompTools
7
7
  # Decompile instruction.
8
8
  def decompile
9
9
  return 'A = -A' if op == :neg
10
- "A #{op_sym}= #{src}"
10
+ "A #{op_sym}= #{src_str}"
11
+ end
12
+
13
+ # See {Instruction::Base#symbolize}.
14
+ # @return [[:alu, Symbol, (:x, Integer, nil)]]
15
+ def symbolize
16
+ return [:alu, :neg, nil] if op == :neg
17
+ [:alu, op_sym, src]
18
+ end
19
+
20
+ # See {Base#branch}.
21
+ # @param [Context] context
22
+ # Current context.
23
+ # @return [Array<(Integer, Context)>]
24
+ def branch(context)
25
+ ctx = context.dup
26
+ ctx[:a] = nil
27
+ [[line + 1, ctx]]
11
28
  end
12
29
 
13
30
  private
@@ -34,8 +51,12 @@ module SeccompTools
34
51
  end
35
52
  end
36
53
 
54
+ def src_str
55
+ src == :x ? 'X' : src.to_s
56
+ end
57
+
37
58
  def src
38
- SRC.invert[code & 0x8] == :k ? k : 'X'
59
+ SRC.invert[code & 0x8] == :k ? k : :x
39
60
  end
40
61
  end
41
62
  end
@@ -7,17 +7,42 @@ module SeccompTools
7
7
  class Base
8
8
  include SeccompTools::Const::BPF
9
9
 
10
+ # Instantiate a {Base} object.
10
11
  # @param [SeccompTools::BPF] bpf
11
12
  # An instruction.
12
13
  def initialize(bpf)
13
14
  @bpf = bpf
14
15
  end
15
16
 
17
+ # Helper to raise exception with message.
18
+ # @param [String] msg
19
+ # Error message.
16
20
  # @raise [ArgumentError]
17
21
  def invalid(msg = 'unknown')
18
22
  raise ArgumentError, "Line #{line} is invalid: #{msg}"
19
23
  end
20
24
 
25
+ # Return the possible branches after executing this instruction.
26
+ # @param [Context] _context
27
+ # Current context.
28
+ # @return [Array<(Integer, Context)>]
29
+ # @example
30
+ # # For ALU, LD, LDX, ST, STX
31
+ # inst.line #=> 10
32
+ # inst.branch(ctx)
33
+ # #=> [[11, ctx]]
34
+ def branch(_context); raise NotImplmentedError
35
+ end
36
+
37
+ # Return tokens stand for this instruction.
38
+ # @return [Array<Symbol, Integer>]
39
+ # @example
40
+ # ret_a.symbolize #=> [:ret, :a]
41
+ # ret_k.symbolize #=> [:ret, 0x7fff0000]
42
+ # jeq.symbolize #=> [:cmp, :==, 0, 0, 1]
43
+ def symbolize; raise NotImplmentedError
44
+ end
45
+
21
46
  private
22
47
 
23
48
  %i(code jt jf k arch line contexts).each do |sym|
@@ -19,6 +19,16 @@ module SeccompTools
19
19
  if_str(true) + goto(jf)
20
20
  end
21
21
 
22
+ # See {Instruction::Base#symbolize}.
23
+ # @return [[:cmp, Symbol, (:x, Integer), Integer, Integer], [:jmp, Integer]]
24
+ def symbolize
25
+ return [:jmp, k] if jop == :none
26
+ [:cmp, jop, src, jt, jf]
27
+ end
28
+
29
+ # See {Base#branch}.
30
+ # @param [Context] context
31
+ # Current context.
22
32
  # @return [Array<(Integer, Context)>]
23
33
  def branch(context)
24
34
  return [[at(k), context]] if jop == :none
@@ -40,19 +50,24 @@ module SeccompTools
40
50
  end
41
51
 
42
52
  def src_str
43
- return 'X' if SRC.invert[code & 8] == :x
53
+ return 'X' if src == :x
44
54
  # if A in all contexts are same
45
55
  a = contexts.map(&:a).uniq
46
56
  return k.to_s if a.size != 1
47
57
  a = a[0]
48
- return k.to_s unless a.instance_of?(Array) && a.first == :data
49
- case a.last
50
- when 0 then Util.colorize((Const::Syscall.const_get(arch.upcase.to_sym).invert[k] || k).to_s, t: :syscall)
51
- when 4 then Util.colorize(Const::Audit::ARCH.invert[k] || k.to_s(16), t: :arch)
52
- else '0x' + k.to_s(16)
58
+ return k.to_s if a.nil?
59
+ hex = '0x' + k.to_s(16)
60
+ case a
61
+ when 0 then Util.colorize(Const::Syscall.const_get(arch.upcase.to_sym).invert[k] || hex, t: :syscall)
62
+ when 4 then Util.colorize(Const::Audit::ARCH.invert[k] || hex, t: :arch)
63
+ else hex
53
64
  end
54
65
  end
55
66
 
67
+ def src
68
+ SRC.invert[code & 8] == :x ? :x : k
69
+ end
70
+
56
71
  def goto(off)
57
72
  format('goto %04d', at(off))
58
73
  end
@@ -7,26 +7,35 @@ module SeccompTools
7
7
  # Decompile instruction.
8
8
  def decompile
9
9
  ret = reg + ' = '
10
- type = load_val
10
+ _, _reg, type = symbolize
11
11
  return ret + type[:val].to_s if type[:rel] == :immi
12
12
  return ret + "mem[#{type[:val]}]" if type[:rel] == :mem
13
13
  ret + seccomp_data_str
14
14
  end
15
15
 
16
+ # @return [void]
17
+ def symbolize
18
+ type = load_val
19
+ [:ld, reg.downcase.to_sym, type]
20
+ end
21
+
16
22
  # Accumulator register.
17
23
  # @return ['A']
18
24
  def reg
19
25
  'A'
20
26
  end
21
27
 
28
+ # See {Base#branch}.
29
+ # @param [Context] context
30
+ # Current context.
22
31
  # @return [Array<(Integer, Context)>]
23
32
  def branch(context)
24
33
  nctx = context.dup
25
34
  type = load_val
26
35
  nctx[reg] = case type[:rel]
27
- when :immi then type[:val]
28
- when :mem then context.mem[type[:val]]
29
- when :data then [:data, type[:val]]
36
+ when :immi then nil
37
+ when :mem then context[type[:val]]
38
+ when :data then type[:val]
30
39
  end
31
40
  [[line + 1, nctx]]
32
41
  end
@@ -58,6 +67,7 @@ module SeccompTools
58
67
  when 0 then 'sys_number'
59
68
  when 4 then 'arch'
60
69
  when 8 then 'instruction_pointer'
70
+ when 12 then 'instruction_pointer >> 32'
61
71
  else
62
72
  idx = Array.new(12) { |i| i * 4 + 16 }.index(k)
63
73
  return 'INVALID' if idx.nil?
@@ -12,6 +12,25 @@ module SeccompTools
12
12
  end
13
13
  end
14
14
 
15
+ # See {Instruction::Base#symbolize}.
16
+ # @return [[:misc, (:tax, :txa)]]
17
+ def symbolize
18
+ [:misc, op]
19
+ end
20
+
21
+ # See {Base#branch}.
22
+ # @param [Context] context
23
+ # Current context.
24
+ # @return [Array<(Integer, Context)>]
25
+ def branch(context)
26
+ ctx = context.dup
27
+ case op
28
+ when :txa then ctx['A'] = ctx['X']
29
+ when :tax then ctx['X'] = ctx['A']
30
+ end
31
+ [[line + 1, ctx]]
32
+ end
33
+
15
34
  private
16
35
 
17
36
  def op
@@ -6,11 +6,19 @@ module SeccompTools
6
6
  class RET < Base
7
7
  # Decompile instruction.
8
8
  def decompile
9
- return 'return A' if code & 0x18 == SRC[:a]
10
- "return #{ACTION.invert[k & 0x7fff0000]}"
9
+ _, type = symbolize
10
+ "return #{type == :a ? 'A' : ACTION.invert[type & 0x7fff0000]}"
11
11
  end
12
12
 
13
+ # See {Instruction::Base#symbolize}.
14
+ # @return [[:ret, (:a, Integer)]]
15
+ def symbolize
16
+ [:ret, code & 0x18 == SRC[:a] ? :a : k]
17
+ end
18
+
19
+ # See {Base#branch}.
13
20
  # @return [[]]
21
+ # Always return an empty array.
14
22
  def branch(*)
15
23
  []
16
24
  end
@@ -9,10 +9,16 @@ module SeccompTools
9
9
  "mem[#{k}] = #{reg}"
10
10
  end
11
11
 
12
+ # See {Instruction::Base#symbolize}.
13
+ # @return [[:misc, (:a, :x), Integer]]
14
+ def symbolize
15
+ [:st, reg.downcase.to_sym, k]
16
+ end
17
+
12
18
  # @return [Array<(Integer, Context)>]
13
19
  def branch(context)
14
20
  ctx = context.dup
15
- ctx.mem[k] = ctx[reg]
21
+ ctx[k] = ctx[reg]
16
22
  [[line + 1, ctx]]
17
23
  end
18
24
  end
@@ -10,7 +10,18 @@ module SeccompTools
10
10
  i386: { number: 120, args: [40, 88, 96, 104, 112, 32], ret: 80, SYS_prctl: 172 }
11
11
  }.freeze
12
12
 
13
- attr_reader :pid, :abi, :number, :args, :ret
13
+ # @return [Integer] Process id.
14
+ attr_reader :pid
15
+ # @return [Hash{Symbol => Integer, Array<Integer>}] See {ABI}.
16
+ attr_reader :abi
17
+ # @return [Integer] Syscall number.
18
+ attr_reader :number
19
+ # @return [Integer] Syscall arguments.
20
+ attr_reader :args
21
+ # @return [Integer] Syscall return value.
22
+ attr_reader :ret
23
+
24
+ # Instantiate a {Syscall} object.
14
25
  # @param [String] pid
15
26
  # Process-id.
16
27
  def initialize(pid)
@@ -20,6 +20,14 @@ module SeccompTools
20
20
  end
21
21
  end
22
22
 
23
+ # Enable colorize.
24
+ # @return [void]
25
+ def enable_color!
26
+ @disable_color = false
27
+ end
28
+
29
+ # Disable colorize.
30
+ # @return [void]
23
31
  def disable_color!
24
32
  @disable_color = true
25
33
  end
@@ -34,13 +42,14 @@ module SeccompTools
34
42
  COLOR_CODE = {
35
43
  esc_m: "\e[0m",
36
44
  syscall: "\e[38;5;120m", # light green
37
- arch: "\e[38;5;230m" # light yellow
45
+ arch: "\e[38;5;230m", # light yellow
46
+ gray: "\e[2m"
38
47
  }.freeze
39
48
  # Wrapper color codes.
40
49
  # @param [String] s
41
50
  # Contents to wrapper.
42
- # @param [Symbol?] sev
43
- # Specific which kind of color to use, valid symbols are defined in +#COLOR_CODE+.
51
+ # @param [Symbol?] t
52
+ # Specific which kind of color to use, valid symbols are defined in {Util.COLOR_CODE}.
44
53
  # @return [String]
45
54
  # Wrapper with color codes.
46
55
  def colorize(s, t: nil)
@@ -48,7 +57,7 @@ module SeccompTools
48
57
  return s unless colorize_enabled?
49
58
  cc = COLOR_CODE
50
59
  color = cc[t]
51
- "#{color}#{s.sub(cc[:esc_m], color)}#{cc[:esc_m]}"
60
+ "#{color}#{s.sub(cc[:esc_m], cc[:esc_m] + color)}#{cc[:esc_m]}"
52
61
  end
53
62
  end
54
63
  end
@@ -1,4 +1,4 @@
1
1
  module SeccompTools
2
2
  # Gem version.
3
- VERSION = '0.1.0'.freeze
3
+ VERSION = '1.0.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: 0.1.0
4
+ version: 1.0.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-08 00:00:00.000000000 Z
11
+ date: 2017-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: codeclimate-test-reporter
@@ -108,7 +108,9 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0.9'
111
- description: ''
111
+ description: |
112
+ Provide useful tools to analyze seccomp rules.
113
+ Visit https://github.com/david942j/seccomp-tools for more details.
112
114
  email:
113
115
  - david942j@gmail.com
114
116
  executables:
@@ -127,12 +129,14 @@ files:
127
129
  - lib/seccomp-tools/cli/cli.rb
128
130
  - lib/seccomp-tools/cli/disasm.rb
129
131
  - lib/seccomp-tools/cli/dump.rb
132
+ - lib/seccomp-tools/cli/emu.rb
130
133
  - lib/seccomp-tools/const.rb
131
134
  - lib/seccomp-tools/consts/amd64.rb
132
135
  - lib/seccomp-tools/consts/i386.rb
133
- - lib/seccomp-tools/context.rb
134
- - lib/seccomp-tools/disasm.rb
136
+ - lib/seccomp-tools/disasm/context.rb
137
+ - lib/seccomp-tools/disasm/disasm.rb
135
138
  - lib/seccomp-tools/dumper.rb
139
+ - lib/seccomp-tools/emulator.rb
136
140
  - lib/seccomp-tools/instruction/alu.rb
137
141
  - lib/seccomp-tools/instruction/base.rb
138
142
  - lib/seccomp-tools/instruction/instruction.rb
@@ -1,31 +0,0 @@
1
- module SeccompTools
2
- # The context when emulating.
3
- #
4
- # @todo
5
- # No lambda value, not support ALU instructions.
6
- class Context
7
- attr_accessor :a, :x, :mem
8
- def initialize(a: nil, x: nil, mem: {})
9
- @a = a
10
- @x = x
11
- @mem = mem
12
- end
13
-
14
- # Implement a deep dup.
15
- # @return [Context]
16
- def dup
17
- Context.new(a: a, x: x, mem: mem.dup)
18
- end
19
-
20
- # For conveniently get instance variable.
21
- # @param [String, Symbol] key
22
- def [](key)
23
- instance_variable_get(('@' + key.downcase).to_sym)
24
- end
25
-
26
- # For conveniently set instance variable.
27
- def []=(key, val)
28
- instance_variable_set(('@' + key.downcase).to_sym, val)
29
- end
30
- end
31
- end
@@ -1,37 +0,0 @@
1
- require 'seccomp-tools/bpf'
2
- require 'seccomp-tools/context'
3
- require 'seccomp-tools/util'
4
-
5
- module SeccompTools
6
- # Disassembler of seccomp bpf.
7
- module Disasm
8
- module_function
9
-
10
- # Disassemble bpf codes.
11
- # @param [String] bpf
12
- # The bpf codes.
13
- # @param [Symbol] arch
14
- # Architecture.
15
- # @todo
16
- # Detect system architecture as default.
17
- def disasm(bpf, arch: nil)
18
- arch ||= Util.system_arch
19
- codes = bpf.scan(/.{8}/m).map.with_index { |b, i| BPF.new(b, arch, i) }
20
- contexts = Array.new(codes.size) { [] }
21
- contexts[0].push(Context.new)
22
- dis = codes.zip(contexts).map do |code, ctxs|
23
- ctxs.each do |ctx|
24
- code.branch(ctx) do |pc, c|
25
- contexts[pc].push(c) unless c.nil? || pc >= contexts.size
26
- end
27
- end
28
- code.contexts = ctxs
29
- code.disasm
30
- end.join("\n")
31
- <<EOS + dis + "\n"
32
- line CODE JT JF K
33
- =================================
34
- EOS
35
- end
36
- end
37
- end