asmrepl 1.0.1 → 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 +4 -4
- data/README.md +43 -36
- data/asmrepl.gemspec +1 -1
- data/lib/asmrepl/assembler.rb +5 -1
- data/lib/asmrepl/disasm.rb +24 -0
- data/lib/asmrepl/linux.rb +15 -75
- data/lib/asmrepl/macos.rb +31 -86
- data/lib/asmrepl/repl.rb +106 -31
- data/lib/asmrepl/thread_state.rb +108 -0
- data/lib/asmrepl/version.rb +1 -1
- data/lib/asmrepl.rb +8 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b7a42ce7b9651305349e0f3aa6eafe88755a7f5bce4224ea0fab31c7169a0cd
|
4
|
+
data.tar.gz: 95ef6c4ebe42d82341782e5014b21237f6ba436ce3b296041aec59c4fdc8ba38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: affb18c181d063ddf30f48006d980753573ca797f51d49e51e231cb6fbc9a4ffc4ec0204e806b02c5df895426cdae04185cbe12f605ee755d4ec35529879954e
|
7
|
+
data.tar.gz: bdfa0987327ef236a1e4a9fd991e00d864bac78e7408185f1c94ec93cb04b20fe82bc7336a214422b1067bbc02a12c8ac69ac132cf4cf842653f9e7e1d45ed8c
|
data/README.md
CHANGED
@@ -2,11 +2,6 @@
|
|
2
2
|
|
3
3
|
This is a REPL for assembly language.
|
4
4
|
|
5
|
-
## Linux requirements
|
6
|
-
```
|
7
|
-
$ sudo apt-get install libcapstone-dev
|
8
|
-
```
|
9
|
-
|
10
5
|
## Usage
|
11
6
|
|
12
7
|
Install the gem:
|
@@ -27,16 +22,16 @@ When the REPL starts, it will display all register values and flags:
|
|
27
22
|
|
28
23
|
```
|
29
24
|
================== CPU STATE ===================
|
30
|
-
rax 000000000000000000 r8
|
31
|
-
rbx 000000000000000000 r9
|
32
|
-
rcx
|
33
|
-
rdx
|
34
|
-
rdi
|
35
|
-
rsi
|
36
|
-
rbp
|
37
|
-
rsp
|
38
|
-
|
39
|
-
rip
|
25
|
+
rax 000000000000000000 r8 0x00007f89d0f04640
|
26
|
+
rbx 000000000000000000 r9 0x0000000000000004
|
27
|
+
rcx 0x00007f89d0f04a50 r10 000000000000000000
|
28
|
+
rdx 0x..fc611d3f0aa2900d4 r11 0x00000001033a4000
|
29
|
+
rdi 0x00007ff7bd126148 r12 000000000000000000
|
30
|
+
rsi 000000000000000000 r13 0x00007ff7bd125dc0
|
31
|
+
rbp 0x00007ff7bd125c40 r14 000000000000000000
|
32
|
+
rsp 0x00007ff7bd125c38 r15 000000000000000000
|
33
|
+
|
34
|
+
rip 0x00000001033a4001
|
40
35
|
rflags 0x0000000000000246
|
41
36
|
cs 0x000000000000002b
|
42
37
|
fs 000000000000000000
|
@@ -44,46 +39,58 @@ gs 000000000000000000
|
|
44
39
|
|
45
40
|
FLAGS: ["PF", "ZF", "IF"]
|
46
41
|
|
47
|
-
|
42
|
+
(rip 0x00000001033a4001)>
|
48
43
|
```
|
49
44
|
|
50
45
|
Then you can issue commands and inspect register values. Let's write to the
|
51
46
|
`rax` register and inspect its value:
|
52
47
|
|
53
48
|
```
|
54
|
-
|
55
|
-
|
49
|
+
(rip 0x00000001033a4001)> mov rax, 5
|
50
|
+
=============== REGISTER CHANGES ===============
|
51
|
+
rax 000000000000000000 => 0x0000000000000005
|
52
|
+
|
53
|
+
(rip 0x00000001033a4009)> rax
|
56
54
|
0x0000000000000005
|
57
|
-
|
55
|
+
(rip 0x00000001033a4009)>
|
58
56
|
```
|
59
57
|
|
60
58
|
Now let's write to the `rbx` register and add the two values:
|
61
59
|
|
62
60
|
```
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
(rip 0x00000001033a4009)> mov rbx, 3
|
62
|
+
=============== REGISTER CHANGES ===============
|
63
|
+
rbx 000000000000000000 => 0x0000000000000003
|
64
|
+
|
65
|
+
(rip 0x00000001033a4011)> add rax, rbx
|
66
|
+
=============== REGISTER CHANGES ===============
|
67
|
+
rax 0x0000000000000005 => 0x0000000000000008
|
68
|
+
rflags 0x0000000000000246 => 0x0000000000000202
|
69
|
+
|
70
|
+
FLAGS: ["IF"]
|
71
|
+
|
72
|
+
(rip 0x00000001033a4015)> rax
|
66
73
|
0x0000000000000008
|
67
|
-
|
74
|
+
(rip 0x00000001033a4015)> rbx
|
68
75
|
0x0000000000000003
|
69
|
-
|
76
|
+
(rip 0x00000001033a4015)>
|
70
77
|
```
|
71
78
|
|
72
79
|
Finally, lets check all values in the CPU:
|
73
80
|
|
74
81
|
```
|
75
|
-
|
82
|
+
(rip 0x00000001033a4015)> cpu
|
76
83
|
================== CPU STATE ===================
|
77
|
-
rax 0x0000000000000008 r8
|
78
|
-
rbx 0x0000000000000003 r9
|
79
|
-
rcx
|
80
|
-
rdx
|
81
|
-
rdi
|
82
|
-
rsi
|
83
|
-
rbp
|
84
|
-
rsp
|
85
|
-
|
86
|
-
rip
|
84
|
+
rax 0x0000000000000008 r8 0x00007f89d0f04640
|
85
|
+
rbx 0x0000000000000003 r9 0x0000000000000004
|
86
|
+
rcx 0x00007f89d0f04a50 r10 000000000000000000
|
87
|
+
rdx 0x..fc611d3f0aa2900d4 r11 0x00000001033a4000
|
88
|
+
rdi 0x00007ff7bd126148 r12 000000000000000000
|
89
|
+
rsi 000000000000000000 r13 0x00007ff7bd125dc0
|
90
|
+
rbp 0x00007ff7bd125c40 r14 000000000000000000
|
91
|
+
rsp 0x00007ff7bd125c38 r15 000000000000000000
|
92
|
+
|
93
|
+
rip 0x00000001033a4015
|
87
94
|
rflags 0x0000000000000202
|
88
95
|
cs 0x000000000000002b
|
89
96
|
fs 000000000000000000
|
@@ -91,5 +98,5 @@ gs 000000000000000000
|
|
91
98
|
|
92
99
|
FLAGS: ["IF"]
|
93
100
|
|
94
|
-
|
101
|
+
(rip 0x00000001033a4015)>
|
95
102
|
```
|
data/asmrepl.gemspec
CHANGED
@@ -20,5 +20,5 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.add_development_dependency 'minitest', '~> 5.14'
|
21
21
|
s.add_development_dependency 'crabstone', '~> 4.0'
|
22
22
|
s.add_development_dependency 'rake', '~> 13.0'
|
23
|
-
s.add_dependency 'fisk', '~> 2'
|
23
|
+
s.add_dependency 'fisk', '~> 2.3.1'
|
24
24
|
end
|
data/lib/asmrepl/assembler.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
112
|
-
|
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
|
-
|
90
|
+
def read_flags; eflags; end
|
124
91
|
|
125
|
-
|
126
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
78
|
+
class ThreadState < ASMREPL::ThreadState.build(fields)
|
79
|
+
private
|
97
80
|
|
98
|
-
def
|
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
|
-
|
180
|
-
|
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
|
-
|
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
|
|
@@ -27,13 +29,40 @@ module ASMREPL
|
|
27
29
|
end
|
28
30
|
|
29
31
|
def display_state state
|
30
|
-
puts " CPU STATE ".center(48, "=")
|
32
|
+
puts bold(" CPU STATE ".center(48, "="))
|
31
33
|
puts state
|
32
34
|
puts
|
33
35
|
puts "FLAGS: #{state.flags.inspect}"
|
34
36
|
puts
|
35
37
|
end
|
36
38
|
|
39
|
+
def display_state_change last_state, state
|
40
|
+
puts bold(" REGISTER CHANGES ".center(48, "="))
|
41
|
+
show_flags = false
|
42
|
+
|
43
|
+
state.fields.each do |field|
|
44
|
+
next if field == "rip"
|
45
|
+
|
46
|
+
if last_state[field] != state[field]
|
47
|
+
print "#{field.ljust(6)} "
|
48
|
+
print sprintf("%#018x", last_state[field] & MAXINT)
|
49
|
+
print " => "
|
50
|
+
puts bold(sprintf("%#018x", state[field] & MAXINT))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
if last_state.flags != state.flags
|
55
|
+
puts
|
56
|
+
puts "FLAGS: #{state.flags.inspect}"
|
57
|
+
end
|
58
|
+
|
59
|
+
puts
|
60
|
+
end
|
61
|
+
|
62
|
+
def bold string
|
63
|
+
"\e[1m#{string}\e[0m"
|
64
|
+
end
|
65
|
+
|
37
66
|
def start
|
38
67
|
pid = fork {
|
39
68
|
CFuncs.traceme
|
@@ -42,49 +71,95 @@ module ASMREPL
|
|
42
71
|
|
43
72
|
tracer = CFuncs::Tracer.new pid
|
44
73
|
should_cpu = true
|
74
|
+
last_state = nil
|
75
|
+
|
45
76
|
while tracer.wait
|
46
77
|
state = tracer.state
|
47
78
|
|
48
79
|
# Show CPU state once on boot
|
49
|
-
if
|
80
|
+
if last_state.nil?
|
50
81
|
display_state state
|
51
|
-
|
82
|
+
last_state = state
|
83
|
+
else
|
84
|
+
display_state_change last_state, state
|
85
|
+
last_state = state
|
52
86
|
end
|
53
87
|
|
54
|
-
# Move the JIT buffer to the current instruction pointer
|
55
|
-
pos = (state.rip - @buffer.memory.to_i)
|
56
|
-
@buffer.seek pos
|
57
88
|
use_history = true
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
89
|
+
begin
|
90
|
+
loop do
|
91
|
+
cmd = nil
|
92
|
+
prompt = sprintf("(rip %#018x)> ", state.rip)
|
93
|
+
text = Reline.readmultiline(prompt, use_history) do |multiline_input|
|
94
|
+
case multiline_input
|
95
|
+
when /\Adisasm\Z/
|
96
|
+
cmd = :disasm
|
97
|
+
when /\A\s*(\w+)\s*\Z/
|
98
|
+
register = $1
|
99
|
+
cmd = [:read, register]
|
100
|
+
when /\A\s*(\w+)\s*=\s*(\d+)\Z/
|
101
|
+
register = $1
|
102
|
+
cmd = [:write, register, $2.to_i]
|
103
|
+
else
|
104
|
+
cmd = :run
|
105
|
+
end
|
106
|
+
true
|
66
107
|
end
|
67
|
-
true
|
68
|
-
end
|
69
108
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
119
|
+
in :run
|
120
|
+
break if text.chomp.empty?
|
121
|
+
begin
|
122
|
+
parser_result = @parser.parse text.chomp
|
123
|
+
rescue
|
124
|
+
puts "Invalid intruction"
|
125
|
+
next
|
126
|
+
end
|
127
|
+
|
128
|
+
begin
|
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
|
135
|
+
binary.bytes.each { |byte| @buffer.putc byte }
|
136
|
+
state.rip -= 1
|
137
|
+
tracer.state = state
|
138
|
+
rescue Fisk::Errors::InvalidInstructionError => e
|
139
|
+
# Print an error message when the instruction is invalid
|
140
|
+
puts e.message
|
141
|
+
next
|
142
|
+
end
|
143
|
+
break
|
144
|
+
in [:write, reg, val]
|
145
|
+
state[reg] = val
|
146
|
+
tracer.state = state
|
147
|
+
in [:read, "cpu"]
|
148
|
+
display_state state
|
149
|
+
in [:read, reg]
|
150
|
+
val = state[reg]
|
151
|
+
if val
|
152
|
+
puts sprintf("%#018x", state[reg])
|
153
|
+
else
|
154
|
+
puts "Unknown command: "
|
155
|
+
puts " " + text
|
156
|
+
end
|
82
157
|
else
|
83
|
-
puts "Unknown command: "
|
84
|
-
puts " " + text
|
85
158
|
end
|
86
|
-
else
|
87
159
|
end
|
160
|
+
rescue Interrupt
|
161
|
+
puts ""
|
162
|
+
exit 0
|
88
163
|
end
|
89
164
|
tracer.continue
|
90
165
|
end
|
@@ -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
|
data/lib/asmrepl/version.rb
CHANGED
data/lib/asmrepl.rb
CHANGED
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.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-
|
11
|
+
date: 2021-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 2.3.1
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 2.3.1
|
69
69
|
description: Tired of writing assembly and them assembling it? Now you can write assembly
|
70
70
|
and evaluate it!
|
71
71
|
email: tenderlove@ruby-lang.org
|
@@ -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
|