seccomp-tools 0.1.0 → 1.0.0

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.
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