asmrepl 1.1.0 → 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
  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