seccomp-tools 1.1.1 → 1.2.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: e486ade31fe2b1da262f3636060b4d0b3947fb29
4
- data.tar.gz: c65f8074f25cb4b3806726a04d2c2377d44b3ec4
3
+ metadata.gz: 5c6965417440352f339d587caf0ee612604d7d0e
4
+ data.tar.gz: 294d2c627f26b1bf03581675d35412d13296f27b
5
5
  SHA512:
6
- metadata.gz: 4fbb16ae849bcfc0d17eb8bbcb36f4e7906adb3da00f8bcc31712b69a299e3d3a4f659227e1668f8809433f5c4805170fd79e4934c174a4c455438bca6973361
7
- data.tar.gz: a3aba5b768d38fe50f4533a60f7f9cc9d592429803a17728101f721d1aaaf767fcec270ffe59d08bd36763e542e48af48774a707d30dcba71d3920fc6a05123c
6
+ metadata.gz: cfc63e0881c0cb4c7852051a4a0b98f93935523674582a75d5fb4f904cbd81673cb86d0be9f31cb76ce2515fa29a42f53a8e81321f3063d0bddce3257cac9b95
7
+ data.tar.gz: '090dd0f577b52bc5494121420d8600b12018f857bcccd05be15849fd774de4e02abfdc1c36610d8908910f79cfe4153b2f0dd613d015e5aaa86e2f984844ab1f'
data/README.md CHANGED
@@ -6,16 +6,17 @@
6
6
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](http://choosealicense.com/licenses/mit/)
7
7
 
8
8
  # Seccomp Tools
9
- Provides powerful tools for seccomp analysis.
9
+ Provide powerful tools for seccomp analysis.
10
10
 
11
11
  This project is targeted to (but not limited to) analyze seccomp sandbox in CTF pwn challenges.
12
- Some features might be CTF-specific, but still useful for analysis of seccomp in real-case.
12
+ Some features might be CTF-specific, but still useful for analyzing seccomp in real-case.
13
13
 
14
14
  ## Features
15
- * Dump - Automatically dump seccomp-bpf from binary.
15
+ * Dump - Automatically dump seccomp-bpf from execution file(s).
16
16
  * Disasm - Convert bpf to human readable format.
17
17
  - Simple decompile.
18
18
  - Show syscall names.
19
+ - Colorful!
19
20
  * Asm - Write seccomp rules is so easy!
20
21
  * Emu - Emulate seccomp rules.
21
22
  * (TODO) Solve constraints for executing syscalls (e.g. `execve/open/read/write`).
@@ -40,13 +41,13 @@ $ seccomp-tools --help
40
41
  #
41
42
  # asm Seccomp bpf assembler.
42
43
  # disasm Disassemble seccomp bpf.
43
- # dump Automatically dump seccomp bpf from execution file.
44
+ # dump Automatically dump seccomp bpf from execution file(s).
44
45
  # emu Emulate seccomp rules.
45
46
  #
46
47
  # See 'seccomp-tools <command> --help' to read about a specific subcommand.
47
48
 
48
49
  $ seccomp-tools dump --help
49
- # dump - Automatically dump seccomp bpf from execution file.
50
+ # dump - Automatically dump seccomp bpf from execution file(s).
50
51
  #
51
52
  # Usage: seccomp-tools dump [exec] [options]
52
53
  # -c, --sh-exec <command> Executes the given command (via sh).
@@ -166,7 +167,7 @@ $ seccomp-tools asm
166
167
  $ cat spec/data/libseccomp.asm
167
168
  # # check if arch is X86_64
168
169
  # A = arch
169
- # A == 0xc000003e ? next : dead
170
+ # A == ARCH_X86_64 ? next : dead
170
171
  # A = sys_number
171
172
  # A >= 0x40000000 ? dead : next
172
173
  # A == write ? ok : next
@@ -246,6 +247,7 @@ $ seccomp-tools emu spec/data/libseccomp.bpf 0x3
246
247
  ![emu](https://github.com/david942j/seccomp-tools/blob/master/examples/emu-amigo.png?raw=true)
247
248
 
248
249
  ## I Need You
250
+
249
251
  Any suggestion or feature request is welcome!
250
252
  Feel free to file an issue or send a pull request.
251
- And, if you like this work, I'll be happy to be [stared](https://github.com/david942j/seccomp-tools/stargazers) :grimacing:
253
+ And, if you like this work, I'll be happy to be [starred](https://github.com/david942j/seccomp-tools/stargazers) :grimacing:
@@ -42,7 +42,6 @@ module SeccompTools
42
42
  else
43
43
  @insts << res
44
44
  end
45
- res
46
45
  end
47
46
 
48
47
  # @return [Array<SeccompTools::BPF>]
@@ -84,11 +83,10 @@ module SeccompTools
84
83
  # mem[i] = <A|X>
85
84
  def compile_assign(dst, src)
86
85
  # misc txa / tax
87
- return emit(:misc, :txa) if dst == :a && src == :x
88
- return emit(:misc, :tax) if dst == :x && src == :a
89
- src = evaluate(src)
86
+ return compile_assign_misc(dst, src) if (dst == :a && src == :x) || (dst == :x && src == :a)
90
87
  # case of st / stx
91
- return emit(src == :x ? :stx : :st, k: dst.last) if dst[0] == :mem
88
+ return emit(src == :x ? :stx : :st, k: dst.last) if dst.is_a?(Array) && dst.first == :mem
89
+ src = evaluate(src)
92
90
  ld = dst == :x ? :ldx : :ld
93
91
  # <A|X> = <immi>
94
92
  return emit(ld, :imm, k: src) if src.is_a?(Integer)
@@ -99,6 +97,10 @@ module SeccompTools
99
97
  emit(ld, :abs, k: src.last)
100
98
  end
101
99
 
100
+ def compile_assign_misc(dst, _src)
101
+ emit(:misc, dst == :a ? :txa : :tax)
102
+ end
103
+
102
104
  def compile_alu(op, val)
103
105
  val = evaluate(val)
104
106
  src = val == :x ? :x : :k
@@ -138,13 +140,17 @@ module SeccompTools
138
140
  when 'arch' then [:data, 4]
139
141
  else val
140
142
  end
141
- return Const::Syscall.const_get(@arch.upcase.to_sym)[val.to_sym] if val.is_a?(String)
143
+ return eval_constants(val) if val.is_a?(String)
142
144
  # remains are [:mem/:data/:args, <num>]
143
145
  # first convert args to data
144
146
  val = [:data, val.last * 8 + 16] if val.first == :args
145
147
  val
146
148
  end
147
149
 
150
+ def eval_constants(str)
151
+ Const::Syscall.const_get(@arch.upcase.to_sym)[str.to_sym] || Const::Audit::ARCH[str]
152
+ end
153
+
148
154
  attr_reader :token
149
155
 
150
156
  # A <comparison> <sys_str|X|Integer> ? <label|Integer> : <label|Integer>
@@ -40,7 +40,7 @@ module SeccompTools
40
40
  res = case type
41
41
  when String then fetch_str(type) || raise_expected("token #{type.inspect}")
42
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'")
43
+ when :sys_num_x then fetch_sys_num_arch_x || raise_expected("a syscall number or 'X'")
44
44
  when :goto then fetch_number || fetch_label || raise_expected('a number or label name')
45
45
  when :ret then fetch_return || raise(ArgumentError, <<-EOS)
46
46
  Invalid return type: #{cur.inspect}.
@@ -74,9 +74,9 @@ Invalid return type: #{cur.inspect}.
74
74
  nil
75
75
  end
76
76
 
77
- def fetch_sys_num_x
77
+ def fetch_sys_num_arch_x
78
78
  return :x if fetch_str('X')
79
- fetch_number || fetch_syscall
79
+ fetch_number || fetch_syscall || fetch_arch
80
80
  end
81
81
 
82
82
  # Currently only supports 10-based decimal numbers.
@@ -92,6 +92,10 @@ Invalid return type: #{cur.inspect}.
92
92
  fetch_strs(sys.keys.map(&:to_s).sort_by(&:size).reverse)
93
93
  end
94
94
 
95
+ def fetch_arch
96
+ fetch_strs(Const::Audit::ARCH.keys)
97
+ end
98
+
95
99
  def fetch_regexp(regexp)
96
100
  idx = cur =~ regexp
97
101
  return nil if idx.nil? || idx != 0
@@ -121,7 +125,7 @@ Invalid return type: #{cur.inspect}.
121
125
 
122
126
  def fetch_ary
123
127
  support_name = %w[data mem args]
124
- regexp = /(#{support_name.join('|')})\[[0-9]{1,9}\]/
128
+ regexp = /(#{support_name.join('|')})\[[0-9]{1,2}\]/
125
129
  match = fetch_regexp(regexp)
126
130
  return nil if match.nil?
127
131
  res, val = match.split('[')
@@ -1,4 +1,5 @@
1
1
  require 'set'
2
+ require 'stringio'
2
3
 
3
4
  require 'seccomp-tools/const'
4
5
  require 'seccomp-tools/instruction/instruction'
@@ -30,7 +31,7 @@ module SeccompTools
30
31
  # Line number of this filter.
31
32
  def initialize(raw, arch, line)
32
33
  if raw.is_a?(String)
33
- io = StringIO.new(raw)
34
+ io = ::StringIO.new(raw)
34
35
  @code = io.read(2).unpack('S').first
35
36
  @jt = io.read(1).ord
36
37
  @jf = io.read(1).ord
@@ -23,7 +23,7 @@ module SeccompTools
23
23
 
24
24
  # Handle show help message.
25
25
  # @return [Boolean]
26
- # For decestors to check if needs to conitnue.
26
+ # For decestors to check if need to continue.
27
27
  def handle
28
28
  return CLI.show(parser.help) if argv.empty? || %w[-h --help].any? { |h| argv.include?(h) }
29
29
  parser.parse!(argv)
@@ -7,7 +7,7 @@ module SeccompTools
7
7
  # Handle 'dump' command.
8
8
  class Dump < Base
9
9
  # Summary of this command.
10
- SUMMARY = 'Automatically dump seccomp bpf from execution file.'.freeze
10
+ SUMMARY = 'Automatically dump seccomp bpf from execution file(s).'.freeze
11
11
  # Usage of this command.
12
12
  USAGE = ('dump - ' + SUMMARY + "\n\n" + 'Usage: seccomp-tools dump [exec] [options]').freeze
13
13
 
@@ -44,7 +44,6 @@ module SeccompTools
44
44
  return CLI.show(parser.help) if option[:ifile].nil?
45
45
  raw = input
46
46
  insts = SeccompTools::Disasm.to_bpf(raw, option[:arch]).map(&:inst)
47
- disasm = SeccompTools::Disasm.disasm(raw, arch: option[:arch])
48
47
  sys, *args = argv
49
48
  sys = Integer(sys) if sys
50
49
  args.map! { |v| Integer(v) }
@@ -54,26 +53,36 @@ module SeccompTools
54
53
  end
55
54
 
56
55
  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" }
56
+ disasm = SeccompTools::Disasm.disasm(raw, arch: option[:arch]).lines
57
+ output_emulate_path(disasm, trace, res)
70
58
  end
71
59
  output do
72
- ret_type = Const::BPF::ACTION.invert[res[:ret] & 0x7fff0000]
73
- errno = ret_type == :ERRNO ? "(#{res[:ret] & 0xffff})" : ''
60
+ ret_type = Const::BPF::ACTION.invert[res[:ret] & Const::BPF::SECCOMP_RET_ACTION_FULL]
61
+ errno = ret_type == :ERRNO ? "(#{res[:ret] & Const::BPF::SECCOMP_RET_DATA})" : ''
74
62
  format("return %s%s at line %04d\n", ret_type, errno, res[:pc])
75
63
  end
76
64
  end
65
+
66
+ private
67
+
68
+ # output the path during emulation
69
+ # @param [Array<String>] disasm
70
+ # @param [Set] trace
71
+ # @param [{Symbol => Object}] result
72
+ def output_emulate_path(disasm, trace, result)
73
+ output { disasm.shift }
74
+ output { disasm.shift }
75
+ disasm.each_with_index do |line, idx|
76
+ output do
77
+ next line if trace.member?(idx)
78
+ Util.colorize(line, t: :gray)
79
+ end
80
+ # Too much remain, omit them.
81
+ rem = disasm.size - idx - 1
82
+ break output { Util.colorize("... (omitting #{rem} lines)\n", t: :gray) } if rem > 3 && idx > result[:pc] + 4
83
+ end
84
+ output { "\n" }
85
+ end
77
86
  end
78
87
  end
79
88
  end
@@ -12,6 +12,16 @@ module SeccompTools
12
12
  # filter mode
13
13
  SECCOMP_MODE_FILTER = 2
14
14
 
15
+ # For syscall +seccomp+
16
+ SECCOMP_SET_MODE_FILTER = 1
17
+
18
+ # Masks for the return value sections.
19
+
20
+ # mask of return action
21
+ SECCOMP_RET_ACTION_FULL = 0xffff0000
22
+ # mask of return data
23
+ SECCOMP_RET_DATA = 0x0000ffff
24
+
15
25
  # bpf command classes
16
26
  COMMAND = {
17
27
  ld: 0x0,
@@ -42,11 +52,13 @@ module SeccompTools
42
52
 
43
53
  # seccomp action values
44
54
  ACTION = {
45
- KILL: 0x00000000,
46
- TRAP: 0x00030000,
47
- ERRNO: 0x00050000,
48
- TRACE: 0x7ff00000,
49
- ALLOW: 0x7fff0000
55
+ KILL_PROCESS: 0x80000000,
56
+ KILL_THREAD: 0x00000000,
57
+ KILL: 0x00000000, # alias of KILL_THREAD
58
+ TRAP: 0x00030000,
59
+ ERRNO: 0x00050000,
60
+ TRACE: 0x7ff00000,
61
+ ALLOW: 0x7fff0000
50
62
  }.freeze
51
63
 
52
64
  # mode used in ld / ldx
@@ -74,7 +74,7 @@ module SeccompTools
74
74
  end
75
75
  !limit.zero?
76
76
  end
77
- syscalls.keys.each { |cpid| Process.kill('KILL', cpid) if alive?(cpid) }
77
+ syscalls.each_key { |cpid| Process.kill('KILL', cpid) if alive?(cpid) }
78
78
  collect
79
79
  end
80
80
 
@@ -95,9 +95,9 @@ module SeccompTools
95
95
  cont = yield(child)
96
96
  end
97
97
  Ptrace.syscall(child, 0, 0) unless status.exited?
98
- return cont
98
+ cont
99
99
  rescue Errno::ECHILD
100
- return false
100
+ false
101
101
  end
102
102
 
103
103
  # @return [SeccompTools::Syscall]
@@ -119,9 +119,9 @@ module SeccompTools
119
119
  def handle_child(*args)
120
120
  Ptrace.traceme_and_stop
121
121
  exec(*args)
122
- rescue # exec fail
122
+ rescue # rubocop:disable Style/RescueStandardError # exec fail
123
123
  # TODO: use logger
124
- $stderr.puts("Failed to execute #{args.join(' ')}")
124
+ warn("Failed to execute #{args.join(' ')}")
125
125
  exit(1)
126
126
  end
127
127
  end
@@ -1,7 +1,7 @@
1
1
  require 'seccomp-tools/const'
2
2
 
3
3
  module SeccompTools
4
- # For emulation seccomp.
4
+ # For emulating seccomp.
5
5
  class Emulator
6
6
  # Instantiate a {Emulator} object.
7
7
  #
@@ -89,7 +89,7 @@ module SeccompTools
89
89
  def cmp(op, src, jt, jf)
90
90
  src = get(:x) if src == :x
91
91
  a = get(:a)
92
- val = a.send(op, src)
92
+ val = a.__send__(op, src)
93
93
  val = (val != 0) if val.is_a?(Integer) # handle & operator
94
94
  j = val ? jt : jf
95
95
  set(:pc, get(:pc) + j + 1)
@@ -100,7 +100,7 @@ module SeccompTools
100
100
  set(:a, 2**32 - get(:a))
101
101
  else
102
102
  src = get(:x) if src == :x
103
- set(:a, get(:a).send(op, src))
103
+ set(:a, get(:a).__send__(op, src))
104
104
  end
105
105
  end
106
106
 
@@ -47,7 +47,7 @@ module SeccompTools
47
47
 
48
48
  %i(code jt jf k arch line contexts).each do |sym|
49
49
  define_method(sym) do
50
- @bpf.send(sym)
50
+ @bpf.__send__(sym)
51
51
  end
52
52
  end
53
53
  end
@@ -9,14 +9,13 @@ module SeccompTools
9
9
  def decompile
10
10
  return goto(k) if jop == :none
11
11
  # if jt == 0 && jf == 0 => no-op # should not happen
12
- # jt == 0 => if(!) goto jf
12
+ # jt == 0 => if(!) goto jf;
13
13
  # jf == 0 => if() goto jt;
14
14
  # otherwise => if () goto jt; else goto jf;
15
15
  return '/* no-op */' if jt.zero? && jf.zero?
16
16
  return goto(jt) if jt == jf
17
- return if_str + goto(jt) + ' else ' + goto(jf) unless jt.zero? || jf.zero?
18
- return if_str + goto(jt) if jf.zero?
19
- if_str(true) + goto(jf)
17
+ return if_str(true) + goto(jf) if jt.zero?
18
+ if_str + goto(jt) + (jf.zero? ? '' : ' else ' + goto(jf))
20
19
  end
21
20
 
22
21
  # See {Instruction::Base#symbolize}.
@@ -27,8 +27,8 @@ module SeccompTools
27
27
  def ret_str
28
28
  _, type = symbolize
29
29
  return 'A' if type == :a
30
- str = ACTION.invert[type & 0x7fff0000].to_s
31
- str += "(#{type & 0xffff})" if str == 'ERRNO'
30
+ str = ACTION.invert[type & SECCOMP_RET_ACTION_FULL].to_s
31
+ str << "(#{type & SECCOMP_RET_DATA})" if str == 'ERRNO'
32
32
  str
33
33
  end
34
34
  end
@@ -6,7 +6,7 @@ module SeccompTools
6
6
  class Syscall
7
7
  # Syscall arguments offset of +struct user+ in different arch.
8
8
  ABI = {
9
- amd64: { number: 120, args: [112, 104, 96, 56, 72, 44], ret: 80, SYS_prctl: 157 },
9
+ amd64: { number: 120, args: [112, 104, 96, 56, 72, 44], ret: 80, SYS_prctl: 157, SYS_seccomp: 317 },
10
10
  i386: { number: 120, args: [40, 88, 96, 104, 112, 32], ret: 80, SYS_prctl: 172 }
11
11
  }.freeze
12
12
 
@@ -33,11 +33,13 @@ module SeccompTools
33
33
  @ret = peek(abi[:ret])
34
34
  end
35
35
 
36
- # Is this a +prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, addr)+ syscall?
36
+ # Is this a +seccomp(SECCOMP_MODE_FILTER, addr)+/+prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, addr)+ syscall?
37
+ #
37
38
  # @return [Boolean]
38
39
  # +true+ for is a seccomp installation syscall.
39
40
  def set_seccomp?
40
- # TODO: handle SECCOMP_MODE_STRICT
41
+ # TODO: handle SECCOMP_MODE_SET_STRICT / SECCOMP_MODE_STRICT
42
+ return true if number == abi[:SYS_seccomp] && args[0] == Const::BPF::SECCOMP_SET_MODE_FILTER
41
43
  number == abi[:SYS_prctl] && args[0] == Const::BPF::PR_SET_SECCOMP && args[1] == Const::BPF::SECCOMP_MODE_FILTER
42
44
  end
43
45
 
@@ -7,7 +7,9 @@ module SeccompTools
7
7
  # @return [Array<Symbol>]
8
8
  # Architectures.
9
9
  def supported_archs
10
- @archs ||= Dir.glob(File.join(__dir__, 'consts', '*.rb')).map { |f| File.basename(f, '.rb').to_sym }.sort
10
+ @supported_archs ||= Dir.glob(File.join(__dir__, 'consts', '*.rb'))
11
+ .map { |f| File.basename(f, '.rb').to_sym }
12
+ .sort
11
13
  end
12
14
 
13
15
  # Detect system architecture.
@@ -1,4 +1,4 @@
1
1
  module SeccompTools
2
2
  # Gem version.
3
- VERSION = '1.1.1'.freeze
3
+ VERSION = '1.2.0'.freeze
4
4
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: seccomp-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.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-12-01 00:00:00.000000000 Z
11
+ date: 2018-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: codeclimate-test-reporter
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '0.6'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '0.6'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: pry
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -72,42 +58,42 @@ dependencies:
72
58
  requirements:
73
59
  - - "~>"
74
60
  - !ruby/object:Gem::Version
75
- version: '3.5'
61
+ version: '3.7'
76
62
  type: :development
77
63
  prerelease: false
78
64
  version_requirements: !ruby/object:Gem::Requirement
79
65
  requirements:
80
66
  - - "~>"
81
67
  - !ruby/object:Gem::Version
82
- version: '3.5'
68
+ version: '3.7'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: rubocop
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
73
  - - "~>"
88
74
  - !ruby/object:Gem::Version
89
- version: '0.49'
75
+ version: '0.54'
90
76
  type: :development
91
77
  prerelease: false
92
78
  version_requirements: !ruby/object:Gem::Requirement
93
79
  requirements:
94
80
  - - "~>"
95
81
  - !ruby/object:Gem::Version
96
- version: '0.49'
82
+ version: '0.54'
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: simplecov
99
85
  requirement: !ruby/object:Gem::Requirement
100
86
  requirements:
101
87
  - - "~>"
102
88
  - !ruby/object:Gem::Version
103
- version: 0.13.0
89
+ version: 0.16.0
104
90
  type: :development
105
91
  prerelease: false
106
92
  version_requirements: !ruby/object:Gem::Requirement
107
93
  requirements:
108
94
  - - "~>"
109
95
  - !ruby/object:Gem::Version
110
- version: 0.13.0
96
+ version: 0.16.0
111
97
  - !ruby/object:Gem::Dependency
112
98
  name: yard
113
99
  requirement: !ruby/object:Gem::Requirement
@@ -188,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
188
174
  version: '0'
189
175
  requirements: []
190
176
  rubyforge_project:
191
- rubygems_version: 2.6.13
177
+ rubygems_version: 2.6.14
192
178
  signing_key:
193
179
  specification_version: 4
194
180
  summary: seccomp-tools