asmrepl 1.1.0 → 1.2.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
  SHA256:
3
- metadata.gz: 7045e3fe0facfdc15e21bb3dfebcaf5232eb68c144ac07e6bf61588979ca3c7f
4
- data.tar.gz: 158f3aea1088ae25397dccfc6bf593449df3482f031c025c6dbf33ce372f6a53
3
+ metadata.gz: 4b7a42ce7b9651305349e0f3aa6eafe88755a7f5bce4224ea0fab31c7169a0cd
4
+ data.tar.gz: 95ef6c4ebe42d82341782e5014b21237f6ba436ce3b296041aec59c4fdc8ba38
5
5
  SHA512:
6
- metadata.gz: d039b567f9cb938f56f86c1ea02a9c16a3c266748fb16d37b5c710c66618010dba32b13755d778c9dded6a56bb521fb61198281297543aad9f3f6c39028510b4
7
- data.tar.gz: 7f37562a9568489d61d9eb3144310d3c372fd5fe7ab07b3b0786c268bc8a2b20077a52736723d0f684930924614083cc4df2a817e6f91f8f6b7afae438aeb0fc
6
+ metadata.gz: affb18c181d063ddf30f48006d980753573ca797f51d49e51e231cb6fbc9a4ffc4ec0204e806b02c5df895426cdae04185cbe12f605ee755d4ec35529879954e
7
+ data.tar.gz: bdfa0987327ef236a1e4a9fd991e00d864bac78e7408185f1c94ec93cb04b20fe82bc7336a214422b1067bbc02a12c8ac69ac132cf4cf842653f9e7e1d45ed8c
@@ -11,7 +11,11 @@ module ASMREPL
11
11
  l = if possibles.any? { |form| form.operands[1].type == n.to_s }
12
12
  fisk.lit(n)
13
13
  else
14
- fisk.imm(n)
14
+ if r.size == 64
15
+ fisk.imm32(n)
16
+ else
17
+ fisk.imm(n)
18
+ end
15
19
  end
16
20
  fisk.gen_with_insn insn, [r, l]
17
21
  in [:command, [:instruction, insn], [:register, r], [:register, r2]]
@@ -0,0 +1,24 @@
1
+ require "crabstone"
2
+
3
+ class Crabstone::Binding::Instruction
4
+ class << self
5
+ alias :old_release :release
6
+ end
7
+
8
+ # Squelch error in crabstone
9
+ def self.release obj
10
+ nil
11
+ end
12
+ end
13
+
14
+ module ASMREPL
15
+ module Disasm
16
+ def self.disasm buffer
17
+ binary = buffer.memory[0, buffer.pos]
18
+ cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64)
19
+ cs.disasm(binary, buffer.memory.to_i).each {|i|
20
+ puts "%s %s" % [i.mnemonic, i.op_str]
21
+ }
22
+ end
23
+ end
24
+ end
data/lib/asmrepl/linux.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "fisk/helpers"
2
+ require "asmrepl/thread_state"
2
3
 
3
4
  module ASMREPL
4
5
  module Linux
@@ -44,13 +45,13 @@ module ASMREPL
44
45
 
45
46
  # x86_64-linux-gnu/sys/ptrace.h
46
47
  PTRACE_GETREGS = 12
48
+ PTRACE_SETREGS = 13
47
49
 
48
50
  def self.traceme
49
51
  raise unless ptrace(PTRACE_TRACEME, 0, 0, 0).zero?
50
52
  end
51
53
 
52
- class ThreadState
53
- fields = (<<-eostruct).scan(/int ([^;]*);/).flatten
54
+ fields = (<<-eostruct).scan(/int ([^;]*);/).flatten
54
55
  struct user_regs_struct
55
56
  {
56
57
  __extension__ unsigned long long int r15;
@@ -81,82 +82,15 @@ struct user_regs_struct
81
82
  __extension__ unsigned long long int fs;
82
83
  __extension__ unsigned long long int gs;
83
84
  };
84
- eostruct
85
- fields.each_with_index do |field, i|
86
- define_method(field) do
87
- to_ptr[Fiddle::SIZEOF_INT64_T * i, Fiddle::SIZEOF_INT64_T].unpack1("l!")
88
- end
89
- end
90
-
91
- define_singleton_method(:sizeof) do
92
- fields.length * Fiddle::SIZEOF_INT64_T
93
- end
94
-
95
- def [] name
96
- idx = fields.index(name)
97
- return unless idx
98
- to_ptr[Fiddle::SIZEOF_INT64_T * idx, Fiddle::SIZEOF_INT64_T].unpack1("l!")
99
- end
100
-
101
- def self.malloc
102
- new Fiddle::Pointer.malloc sizeof
103
- end
104
-
105
- attr_reader :to_ptr
106
-
107
- def initialize buffer
108
- @to_ptr = buffer
109
- end
85
+ eostruct
110
86
 
111
- define_method(:fields) do
112
- fields
113
- end
114
-
115
- def to_s
116
- buf = ""
117
- fields.first(8).zip(fields.drop(8).first(8)).each do |l, r|
118
- buf << "#{l.ljust(3)} #{sprintf("%#018x", send(l))}"
119
- buf << " "
120
- buf << "#{r.ljust(3)} #{sprintf("%#018x", send(r))}\n"
121
- end
87
+ class ThreadState < ASMREPL::ThreadState.build(fields)
88
+ private
122
89
 
123
- buf << "\n"
90
+ def read_flags; eflags; end
124
91
 
125
- fields.drop(16).each do |reg|
126
- buf << "#{reg.ljust(8)} #{sprintf("%#018x", send(reg))}\n"
127
- end
128
- buf
129
- end
130
-
131
- FLAGS = [
132
- ['CF', 'Carry Flag'],
133
- [nil, 'Reserved'],
134
- ['PF', 'Parity Flag'],
135
- [nil, 'Reserved'],
136
- ['AF', 'Adjust Flag'],
137
- [nil, 'Reserved'],
138
- ['ZF', 'Zero Flag'],
139
- ['SF', 'Sign Flag'],
140
- ['TF', 'Trap Flag'],
141
- ['IF', 'Interrupt Enable Flag'],
142
- ['DF', 'Direction Flag'],
143
- ['OF', 'Overflow Flag'],
144
- ['IOPL_H', 'I/O privilege level High bit'],
145
- ['IOPL_L', 'I/O privilege level Low bit'],
146
- ['NT', 'Nested Task Flag'],
147
- [nil, 'Reserved'],
148
- ]
149
-
150
- def flags
151
- flags = eflags
152
- f = []
153
- FLAGS.each do |abbrv, _|
154
- if abbrv && flags & 1 == 1
155
- f << abbrv
156
- end
157
- flags >>= 1
158
- end
159
- f
92
+ def other_registers
93
+ super - ["orig_rax"]
160
94
  end
161
95
  end
162
96
 
@@ -176,6 +110,12 @@ struct user_regs_struct
176
110
  state
177
111
  end
178
112
 
113
+ def state= state
114
+ raise unless Linux.ptrace(PTRACE_SETREGS, @pid, 0, state).zero?
115
+
116
+ state
117
+ end
118
+
179
119
  def continue
180
120
  unless Linux.ptrace(Linux::PTRACE_CONT, @pid, 1, 0).zero?
181
121
  raise
data/lib/asmrepl/macos.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "fisk/helpers"
2
+ require "asmrepl/thread_state"
2
3
 
3
4
  module ASMREPL
4
5
  module MacOS
@@ -26,6 +27,7 @@ module ASMREPL
26
27
  make_function "task_for_pid", [TYPE_VOIDP, TYPE_INT, TYPE_VOIDP], TYPE_INT
27
28
  make_function "task_threads", [TYPE_VOIDP, TYPE_VOIDP, TYPE_VOIDP], TYPE_INT
28
29
  make_function "thread_get_state", [TYPE_VOIDP, TYPE_INT, TYPE_VOIDP, TYPE_VOIDP], TYPE_INT
30
+ make_function "thread_set_state", [TYPE_VOIDP, TYPE_INT, TYPE_VOIDP, TYPE_INT], TYPE_INT
29
31
  make_function "mmap", [TYPE_VOIDP,
30
32
  TYPE_SIZE_T,
31
33
  TYPE_INT,
@@ -47,8 +49,7 @@ module ASMREPL
47
49
  raise unless ptrace(PT_TRACE_ME, 0, 0, 0).zero?
48
50
  end
49
51
 
50
- class ThreadState
51
- fields = (<<-eostruct).scan(/uint64_t ([^;]*);/).flatten
52
+ fields = (<<-eostruct).scan(/uint64_t ([^;]*);/).flatten
52
53
  struct x86_thread_state64_t {
53
54
  uint64_t rax;
54
55
  uint64_t rbx;
@@ -72,83 +73,12 @@ struct x86_thread_state64_t {
72
73
  uint64_t fs;
73
74
  uint64_t gs;
74
75
  }
75
- eostruct
76
- fields.each_with_index do |field, i|
77
- define_method(field) do
78
- to_ptr[Fiddle::SIZEOF_INT64_T * i, Fiddle::SIZEOF_INT64_T].unpack1("l!")
79
- end
80
- end
81
-
82
- define_singleton_method(:sizeof) do
83
- fields.length * Fiddle::SIZEOF_INT64_T
84
- end
85
-
86
- def [] name
87
- idx = fields.index(name)
88
- return unless idx
89
- to_ptr[Fiddle::SIZEOF_INT64_T * idx, Fiddle::SIZEOF_INT64_T].unpack1("l!")
90
- end
91
-
92
- def self.malloc
93
- new Fiddle::Pointer.malloc sizeof
94
- end
76
+ eostruct
95
77
 
96
- attr_reader :to_ptr
78
+ class ThreadState < ASMREPL::ThreadState.build(fields)
79
+ private
97
80
 
98
- def initialize buffer
99
- @to_ptr = buffer
100
- end
101
-
102
- define_method(:fields) do
103
- fields
104
- end
105
-
106
- def to_s
107
- buf = ""
108
- fields.first(8).zip(fields.drop(8).first(8)).each do |l, r|
109
- buf << "#{l.ljust(3)} #{sprintf("%#018x", send(l))}"
110
- buf << " "
111
- buf << "#{r.ljust(3)} #{sprintf("%#018x", send(r))}\n"
112
- end
113
-
114
- buf << "\n"
115
-
116
- fields.drop(16).each do |reg|
117
- buf << "#{reg.ljust(6)} #{sprintf("%#018x", send(reg))}\n"
118
- end
119
- buf
120
- end
121
-
122
- FLAGS = [
123
- ['CF', 'Carry Flag'],
124
- [nil, 'Reserved'],
125
- ['PF', 'Parity Flag'],
126
- [nil, 'Reserved'],
127
- ['AF', 'Adjust Flag'],
128
- [nil, 'Reserved'],
129
- ['ZF', 'Zero Flag'],
130
- ['SF', 'Sign Flag'],
131
- ['TF', 'Trap Flag'],
132
- ['IF', 'Interrupt Enable Flag'],
133
- ['DF', 'Direction Flag'],
134
- ['OF', 'Overflow Flag'],
135
- ['IOPL_H', 'I/O privilege level High bit'],
136
- ['IOPL_L', 'I/O privilege level Low bit'],
137
- ['NT', 'Nested Task Flag'],
138
- [nil, 'Reserved'],
139
- ]
140
-
141
- def flags
142
- flags = rflags
143
- f = []
144
- FLAGS.each do |abbrv, _|
145
- if abbrv && flags & 1 == 1
146
- f << abbrv
147
- end
148
- flags >>= 1
149
- end
150
- f
151
- end
81
+ def read_flags; rflags; end
152
82
  end
153
83
 
154
84
  PT_TRACE_ME = 0
@@ -176,9 +106,30 @@ struct x86_thread_state64_t {
176
106
  end
177
107
 
178
108
  def state
179
- # Probably should use this for something
180
- # count = thread_count[0]
109
+ 3.times do
110
+ # Probably should use this for something
111
+ # count = thread_count[0]
112
+
113
+ # I can't remember what header I found this in, but it's from a macOS header
114
+ # :sweat-smile:
115
+ x86_THREAD_STATE64_COUNT = ThreadState.sizeof / Fiddle::SIZEOF_INT
116
+
117
+ # Same here
118
+ x86_THREAD_STATE64 = 4
119
+
120
+ state_count = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT64_T)
121
+ state_count[0, Fiddle::SIZEOF_INT64_T] = [x86_THREAD_STATE64_COUNT].pack("l!")
181
122
 
123
+ state = ThreadState.malloc
124
+ if MacOS.thread_get_state(@thread, x86_THREAD_STATE64, state, state_count).zero?
125
+ return state
126
+ end
127
+ end
128
+
129
+ raise "Couldn't get CPU state"
130
+ end
131
+
132
+ def state= new_state
182
133
  # I can't remember what header I found this in, but it's from a macOS header
183
134
  # :sweat-smile:
184
135
  x86_THREAD_STATE64_COUNT = ThreadState.sizeof / Fiddle::SIZEOF_INT
@@ -186,13 +137,7 @@ struct x86_thread_state64_t {
186
137
  # Same here
187
138
  x86_THREAD_STATE64 = 4
188
139
 
189
- state_count = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT64_T)
190
- state_count[0, Fiddle::SIZEOF_INT64_T] = [x86_THREAD_STATE64_COUNT].pack("l!")
191
-
192
- state = ThreadState.malloc
193
- raise unless MacOS.thread_get_state(@thread, x86_THREAD_STATE64, state, state_count).zero?
194
-
195
- state
140
+ raise unless MacOS.thread_set_state(@thread, x86_THREAD_STATE64, new_state, x86_THREAD_STATE64_COUNT).zero?
196
141
  end
197
142
 
198
143
  def continue
data/lib/asmrepl/repl.rb CHANGED
@@ -9,6 +9,8 @@ else
9
9
  end
10
10
 
11
11
  module ASMREPL
12
+ MAXINT = 0xFFFFFFFFFFFFFFFF
13
+
12
14
  class REPL
13
15
  include Fiddle
14
16
 
@@ -43,9 +45,9 @@ module ASMREPL
43
45
 
44
46
  if last_state[field] != state[field]
45
47
  print "#{field.ljust(6)} "
46
- print sprintf("%#018x", last_state[field])
48
+ print sprintf("%#018x", last_state[field] & MAXINT)
47
49
  print " => "
48
- puts bold(sprintf("%#018x", state[field]))
50
+ puts bold(sprintf("%#018x", state[field] & MAXINT))
49
51
  end
50
52
  end
51
53
 
@@ -83,18 +85,21 @@ module ASMREPL
83
85
  last_state = state
84
86
  end
85
87
 
86
- # Move the JIT buffer to the current instruction pointer
87
- pos = (state.rip - @buffer.memory.to_i)
88
- @buffer.seek pos
89
88
  use_history = true
90
89
  begin
91
90
  loop do
92
91
  cmd = nil
93
92
  prompt = sprintf("(rip %#018x)> ", state.rip)
94
93
  text = Reline.readmultiline(prompt, use_history) do |multiline_input|
95
- if multiline_input =~ /\A\s*(\w+)\s*\Z/
94
+ case multiline_input
95
+ when /\Adisasm\Z/
96
+ cmd = :disasm
97
+ when /\A\s*(\w+)\s*\Z/
96
98
  register = $1
97
99
  cmd = [:read, register]
100
+ when /\A\s*(\w+)\s*=\s*(\d+)\Z/
101
+ register = $1
102
+ cmd = [:write, register, $2.to_i]
98
103
  else
99
104
  cmd = :run
100
105
  end
@@ -102,6 +107,15 @@ module ASMREPL
102
107
  end
103
108
 
104
109
  case cmd
110
+ in :disasm
111
+ # disassembles the JIT buffer. This is just for development,
112
+ # I don't want to make a hard dependency on crabstone right now.
113
+ # If you want to use this, install crabstone
114
+ begin
115
+ require "asmrepl/disasm"
116
+ ASMREPL::Disasm.disasm @buffer
117
+ rescue
118
+ end
105
119
  in :run
106
120
  break if text.chomp.empty?
107
121
  begin
@@ -113,13 +127,23 @@ module ASMREPL
113
127
 
114
128
  begin
115
129
  binary = @assembler.assemble parser_result
130
+
131
+ # Move the JIT buffer to the current instruction pointer, but
132
+ # rewind RIP so that we write over the int3
133
+ pos = (state.rip - @buffer.memory.to_i - 1)
134
+ @buffer.seek pos
116
135
  binary.bytes.each { |byte| @buffer.putc byte }
136
+ state.rip -= 1
137
+ tracer.state = state
117
138
  rescue Fisk::Errors::InvalidInstructionError => e
118
139
  # Print an error message when the instruction is invalid
119
140
  puts e.message
120
141
  next
121
142
  end
122
143
  break
144
+ in [:write, reg, val]
145
+ state[reg] = val
146
+ tracer.state = state
123
147
  in [:read, "cpu"]
124
148
  display_state state
125
149
  in [:read, reg]
@@ -0,0 +1,108 @@
1
+ require "fiddle"
2
+
3
+ module ASMREPL
4
+ class ThreadState
5
+ def self.sizeof
6
+ fields.length * Fiddle::SIZEOF_INT64_T
7
+ end
8
+
9
+ def self.malloc
10
+ new Fiddle::Pointer.malloc sizeof
11
+ end
12
+
13
+ FLAGS = [
14
+ ['CF', 'Carry Flag'],
15
+ [nil, 'Reserved'],
16
+ ['PF', 'Parity Flag'],
17
+ [nil, 'Reserved'],
18
+ ['AF', 'Adjust Flag'],
19
+ [nil, 'Reserved'],
20
+ ['ZF', 'Zero Flag'],
21
+ ['SF', 'Sign Flag'],
22
+ ['TF', 'Trap Flag'],
23
+ ['IF', 'Interrupt Enable Flag'],
24
+ ['DF', 'Direction Flag'],
25
+ ['OF', 'Overflow Flag'],
26
+ ['IOPL_H', 'I/O privilege level High bit'],
27
+ ['IOPL_L', 'I/O privilege level Low bit'],
28
+ ['NT', 'Nested Task Flag'],
29
+ [nil, 'Reserved'],
30
+ ]
31
+
32
+ attr_reader :to_ptr
33
+
34
+ def initialize buffer
35
+ @to_ptr = buffer
36
+ end
37
+
38
+ def [] name
39
+ idx = fields.index(name)
40
+ return unless idx
41
+ to_ptr[Fiddle::SIZEOF_INT64_T * idx, Fiddle::SIZEOF_INT64_T].unpack1("l!")
42
+ end
43
+
44
+ def []= name, val
45
+ idx = fields.index(name)
46
+ return unless idx
47
+ to_ptr[Fiddle::SIZEOF_INT64_T * idx, Fiddle::SIZEOF_INT64_T] = [val].pack("l!")
48
+ end
49
+
50
+ def flags
51
+ flags = read_flags
52
+ f = []
53
+ FLAGS.each do |abbrv, _|
54
+ if abbrv && flags & 1 == 1
55
+ f << abbrv
56
+ end
57
+ flags >>= 1
58
+ end
59
+ f
60
+ end
61
+
62
+ def to_s
63
+ buf = ""
64
+ display_registers.first(8).zip(display_registers.drop(8)).each do |l, r|
65
+ buf << "#{l.ljust(3)} #{sprintf("%#018x", self[l] & MAXINT)}"
66
+ buf << " "
67
+ buf << "#{r.ljust(3)} #{sprintf("%#018x", self[r] & MAXINT)}\n"
68
+ end
69
+
70
+ buf << "\n"
71
+
72
+ other_registers.each do |reg|
73
+ buf << "#{reg.ljust(7)} #{sprintf("%#018x", self[reg] & MAXINT)}\n"
74
+ end
75
+ buf
76
+ end
77
+
78
+ def display_registers
79
+ %w{ rax rbx rcx rdx rdi rsi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 }
80
+ end
81
+
82
+ def other_registers
83
+ fields - display_registers
84
+ end
85
+
86
+ def self.build fields
87
+ Class.new(ThreadState) do
88
+ define_method(:fields) do
89
+ fields
90
+ end
91
+
92
+ define_singleton_method(:fields) do
93
+ fields
94
+ end
95
+
96
+ fields.each_with_index do |field, i|
97
+ define_method(field) do
98
+ to_ptr[Fiddle::SIZEOF_INT64_T * i, Fiddle::SIZEOF_INT64_T].unpack1("l!")
99
+ end
100
+
101
+ define_method("#{field}=") do |v|
102
+ to_ptr[Fiddle::SIZEOF_INT64_T * i, Fiddle::SIZEOF_INT64_T] = [v].pack("l!")
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -1,3 +1,3 @@
1
1
  module ASMREPL
2
- VERSION = '1.1.0'
2
+ VERSION = '1.2.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asmrepl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Patterson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-02 00:00:00.000000000 Z
11
+ date: 2021-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -83,12 +83,14 @@ files:
83
83
  - bin/asmrepl
84
84
  - lib/asmrepl.rb
85
85
  - lib/asmrepl/assembler.rb
86
+ - lib/asmrepl/disasm.rb
86
87
  - lib/asmrepl/linux.rb
87
88
  - lib/asmrepl/macos.rb
88
89
  - lib/asmrepl/parser.rb
89
90
  - lib/asmrepl/parser.tab.rb
90
91
  - lib/asmrepl/parser.y
91
92
  - lib/asmrepl/repl.rb
93
+ - lib/asmrepl/thread_state.rb
92
94
  - lib/asmrepl/version.rb
93
95
  - test/asmrepl_test.rb
94
96
  - test/helper.rb