Module: PWN::Plugins::Debugger

Defined in:
lib/pwn/plugins/debugger.rb

Overview

Structured GDB/MI driver with a handle registry that survives pwn_eval.

Class Method Summary collapse

Class Method Details

.attach(opts = {}) ⇒ Object

Raises:

  • (ArgumentError)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/pwn/plugins/debugger.rb', line 30

public_class_method def self.attach(opts = {})
  if opts[:host] || opts[:port]
    host = (opts[:host] || '127.0.0.1').to_s
    port = (opts[:port] || 1234).to_i
    argv = ['gdb', '--interpreter=mi2', '--quiet', '--nx']
    tube = PWN::Plugins::ProcessTube.spawn(cmd: argv, name: opts[:handle])
    row = store_session(handle: tube[:id], tube: tube, gdbstub: "#{host}:#{port}")
    drain_gdb(handle: row[:handle])
    parse_mi(raw: mi(handle: row[:handle], cmd: "-target-select remote #{host}:#{port}"))
    return row
  end
  pid = opts[:pid].to_i
  raise ArgumentError, 'pid is required' unless pid.positive?

  argv = ['gdb', '--interpreter=mi2', '--quiet', '--nx', '-p', pid.to_s]
  tube = PWN::Plugins::ProcessTube.spawn(cmd: argv, name: opts[:handle])
  row = store_session(handle: tube[:id], tube: tube, pid: pid)
  drain_gdb(handle: row[:handle])
  row
end

.authorsObject



117
118
119
# File 'lib/pwn/plugins/debugger.rb', line 117

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <[email protected]>\n"
end

.backtrace(opts = {}) ⇒ Object



90
91
92
# File 'lib/pwn/plugins/debugger.rb', line 90

public_class_method def self.backtrace(opts = {})
  parse_mi(raw: mi(opts.merge(cmd: '-stack-list-frames')))
end

.break(opts = {}) ⇒ Object

Raises:

  • (ArgumentError)


55
56
57
58
59
60
# File 'lib/pwn/plugins/debugger.rb', line 55

public_class_method def self.break(opts = {})
  loc = (opts[:addr_or_sym] || opts[:location] || opts[:addr] || opts[:symbol]).to_s
  raise ArgumentError, 'addr_or_sym is required' if loc.empty?

  parse_mi(raw: mi(opts.merge(cmd: "-break-insert #{loc}")))
end

.checksec(opts = {}) ⇒ Object



94
95
96
# File 'lib/pwn/plugins/debugger.rb', line 94

public_class_method def self.checksec(opts = {})
  PWN::Plugins::GDB.mitigations(binary: opts[:bin] || opts[:binary] || session!(opts)[:bin])
end

.close(opts = {}) ⇒ Object



110
111
112
113
114
115
# File 'lib/pwn/plugins/debugger.rb', line 110

public_class_method def self.close(opts = {})
  handle = (opts[:handle] || opts[:id]).to_s
  sess = @sessions.delete(handle)
  PWN::Plugins::ProcessTube.close(id: handle) if sess
  { closed: handle }
end

.continue(opts = {}) ⇒ Object



62
63
64
# File 'lib/pwn/plugins/debugger.rb', line 62

public_class_method def self.continue(opts = {})
  parse_mi(raw: mi(opts.merge(cmd: '-exec-continue')))
end

.cyclic(opts = {}) ⇒ Object



98
99
100
# File 'lib/pwn/plugins/debugger.rb', line 98

public_class_method def self.cyclic(opts = {})
  PWN::Plugins::ExploitDev.cyclic(length: opts[:length] || opts[:n] || 200)
end

.cyclic_find(opts = {}) ⇒ Object



102
103
104
# File 'lib/pwn/plugins/debugger.rb', line 102

public_class_method def self.cyclic_find(opts = {})
  PWN::Plugins::ExploitDev.cyclic_find(value: opts[:value] || opts[:pattern], length: opts[:length] || 8_192)
end

.helpObject



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/pwn/plugins/debugger.rb', line 121

public_class_method def self.help
  puts "USAGE:
    # List host binaries this module expects to be installed.
    #{self}.required_bins

    # Launch a binary under gdb MI and keep the handle in-process.
    #{self}.launch(
      bin: 'required - filesystem path of the binary to debug',
      binary: 'optional - alias for bin',
      args: 'optional - Array of argv strings after the binary',
      handle: 'optional - ProcessTube name for the session',
      run: 'optional - true issues -exec-run after gdb starts'
    )

    # Attach gdb MI to an existing pid or gdbstub.
    #{self}.attach(
      pid: 'optional - integer process id to attach',
      host: 'optional - gdbstub host for remote attach',
      port: 'optional - gdbstub TCP port',
      handle: 'optional - ProcessTube name for the session'
    )

    # Run the inferior (-exec-run).
    #{self}.run(
      handle: 'required - session handle from launch or attach'
    )

    # Insert a breakpoint at a symbol or address.
    #{self}.break(
      handle: 'required - session handle from launch or attach',
      addr_or_sym: 'required - symbol or address',
      location: 'optional - alias for addr_or_sym',
      addr: 'optional - alias for addr_or_sym',
      symbol: 'optional - alias for addr_or_sym'
    )

    # Continue execution until the next stop.
    #{self}.continue(
      handle: 'required - session handle from launch or attach'
    )

    # Step one instruction or source line.
    #{self}.step(
      handle: 'required - session handle from launch or attach',
      into: 'optional - true to step into rather than next'
    )

    # Read memory bytes at an address.
    #{self}.read_mem(
      handle: 'required - session handle from launch or attach',
      addr: 'required - address to read',
      address: 'optional - alias for addr',
      len: 'optional - byte count (defaults to 64)',
      length: 'optional - alias for len'
    )

    # Write raw bytes to inferior memory.
    #{self}.write_mem(
      handle: 'required - session handle from launch or attach',
      addr: 'required - address to write',
      address: 'optional - alias for addr',
      data: 'required - String or byte Array to write',
      bytes: 'optional - alias for data'
    )

    # Return register values as a parsed MI hash.
    #{self}.regs(
      handle: 'required - session handle from launch or attach'
    )

    # Return a parsed backtrace hash.
    #{self}.backtrace(
      handle: 'required - session handle from launch or attach'
    )

    # Return binary mitigations via GDB.mitigations.
    #{self}.checksec(
      handle: 'optional - session handle whose binary should be probed',
      bin: 'optional - filesystem path of the binary',
      binary: 'optional - alias for bin'
    )

    # Generate a cyclic de Bruijn pattern.
    #{self}.cyclic(
      length: 'optional - pattern length in bytes',
      n: 'optional - alias for length'
    )

    # Find the offset of a packed register value in a cyclic pattern.
    #{self}.cyclic_find(
      value: 'required - leaked register value or substring',
      pattern: 'optional - alias for value',
      length: 'optional - haystack length (defaults to 8192)'
    )

    # Return both a cyclic pattern and the recovered offset.
    #{self}.to_pwntools_offsets(
      value: 'optional - leaked register value for cyclic_find',
      length: 'optional - pattern length in bytes',
      n: 'optional - alias for length'
    )

    # Close a debugger session handle.
    #{self}.close(
      handle: 'required - session handle from launch or attach',
      id: 'optional - alias for handle'
    )

    # Print the AUTHOR(S) string for this module.
    #{self}.authors
  "
  constants.sort
end

.launch(opts = {}) ⇒ Object

Raises:

  • (ArgumentError)


15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/pwn/plugins/debugger.rb', line 15

public_class_method def self.launch(opts = {})
  bin = (opts[:bin] || opts[:binary]).to_s
  raise ArgumentError, 'bin is required' if bin.empty?

  args = Array(opts[:args]).map(&:to_s)
  argv = ['gdb', '--interpreter=mi2', '--quiet', '--nx', '--args', bin, *args]
  tube = PWN::Plugins::ProcessTube.spawn(cmd: argv, name: opts[:handle])
  row = store_session(handle: tube[:id], tube: tube, bin: bin)
  drain_gdb(handle: row[:handle])
  mi(handle: row[:handle], cmd: '-gdb-set confirm off')
  mi(handle: row[:handle], cmd: '-gdb-set pagination off')
  mi(handle: row[:handle], cmd: '-exec-run') if opts[:run] == true
  row
end

.read_mem(opts = {}) ⇒ Object

Raises:

  • (ArgumentError)


70
71
72
73
74
75
# File 'lib/pwn/plugins/debugger.rb', line 70

public_class_method def self.read_mem(opts = {})
  addr = (opts[:addr] || opts[:address]).to_s
  raise ArgumentError, 'addr is required' if addr.empty?

  parse_mi(raw: mi(opts.merge(cmd: "-data-read-memory-bytes #{addr} #{(opts[:len] || opts[:length] || 64).to_i}")))
end

.regs(opts = {}) ⇒ Object



86
87
88
# File 'lib/pwn/plugins/debugger.rb', line 86

public_class_method def self.regs(opts = {})
  parse_mi(raw: mi(opts.merge(cmd: '-data-list-register-values x')))
end

.required_binsObject



11
12
13
# File 'lib/pwn/plugins/debugger.rb', line 11

public_class_method def self.required_bins
  %w[gdb]
end

.run(opts = {}) ⇒ Object



51
52
53
# File 'lib/pwn/plugins/debugger.rb', line 51

public_class_method def self.run(opts = {})
  parse_mi(raw: mi(opts.merge(cmd: '-exec-run')))
end

.step(opts = {}) ⇒ Object



66
67
68
# File 'lib/pwn/plugins/debugger.rb', line 66

public_class_method def self.step(opts = {})
  parse_mi(raw: mi(opts.merge(cmd: (opts[:into] ? '-exec-step' : '-exec-next'))))
end

.to_pwntools_offsets(opts = {}) ⇒ Object



106
107
108
# File 'lib/pwn/plugins/debugger.rb', line 106

public_class_method def self.to_pwntools_offsets(opts = {})
  { cyclic: cyclic(opts), offset: cyclic_find(opts) }
end

.write_mem(opts = {}) ⇒ Object

Raises:

  • (ArgumentError)


77
78
79
80
81
82
83
84
# File 'lib/pwn/plugins/debugger.rb', line 77

public_class_method def self.write_mem(opts = {})
  addr = (opts[:addr] || opts[:address]).to_s
  data = opts[:data] || opts[:bytes]
  raise ArgumentError, 'addr and data are required' if addr.empty? || data.nil?

  hex = data.is_a?(String) ? data.unpack1('H*') : Array(data).pack('C*').unpack1('H*')
  parse_mi(raw: mi(opts.merge(cmd: "-data-write-memory-bytes #{addr} #{hex}")))
end