pwntools 1.0.1 → 1.1.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.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +4 -3
  3. data/Rakefile +3 -1
  4. data/lib/pwnlib/asm.rb +172 -2
  5. data/lib/pwnlib/constants/constants.rb +10 -3
  6. data/lib/pwnlib/context.rb +1 -3
  7. data/lib/pwnlib/elf/elf.rb +3 -3
  8. data/lib/pwnlib/errors.rb +30 -0
  9. data/lib/pwnlib/ext/helper.rb +1 -1
  10. data/lib/pwnlib/logger.rb +140 -2
  11. data/lib/pwnlib/pwn.rb +3 -0
  12. data/lib/pwnlib/reg_sort.rb +1 -1
  13. data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +9 -3
  14. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +6 -2
  15. data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +6 -2
  16. data/lib/pwnlib/shellcraft/generators/amd64/linux/cat.rb +23 -0
  17. data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +6 -4
  18. data/lib/pwnlib/shellcraft/generators/amd64/linux/exit.rb +23 -0
  19. data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +6 -2
  20. data/lib/pwnlib/shellcraft/generators/amd64/linux/open.rb +23 -0
  21. data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +6 -2
  22. data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +6 -4
  23. data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +9 -3
  24. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +6 -2
  25. data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +6 -2
  26. data/lib/pwnlib/shellcraft/generators/i386/linux/cat.rb +23 -0
  27. data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +8 -4
  28. data/lib/pwnlib/shellcraft/generators/i386/linux/exit.rb +23 -0
  29. data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +6 -2
  30. data/lib/pwnlib/shellcraft/generators/i386/linux/open.rb +23 -0
  31. data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +6 -2
  32. data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +8 -4
  33. data/lib/pwnlib/shellcraft/generators/x86/linux/cat.rb +53 -0
  34. data/lib/pwnlib/shellcraft/generators/x86/linux/exit.rb +33 -0
  35. data/lib/pwnlib/shellcraft/generators/x86/linux/open.rb +46 -0
  36. data/lib/pwnlib/shellcraft/shellcraft.rb +3 -2
  37. data/lib/pwnlib/timer.rb +5 -2
  38. data/lib/pwnlib/tubes/process.rb +153 -0
  39. data/lib/pwnlib/tubes/serialtube.rb +112 -0
  40. data/lib/pwnlib/tubes/sock.rb +24 -25
  41. data/lib/pwnlib/tubes/tube.rb +191 -39
  42. data/lib/pwnlib/util/packing.rb +3 -9
  43. data/lib/pwnlib/version.rb +1 -1
  44. data/test/asm_test.rb +85 -2
  45. data/test/constants/constants_test.rb +2 -2
  46. data/test/data/echo.rb +2 -7
  47. data/test/elf/elf_test.rb +10 -15
  48. data/test/files/use_pwn.rb +2 -6
  49. data/test/logger_test.rb +38 -0
  50. data/test/shellcraft/linux/cat_test.rb +86 -0
  51. data/test/shellcraft/linux/syscalls/exit_test.rb +56 -0
  52. data/test/shellcraft/linux/syscalls/open_test.rb +86 -0
  53. data/test/shellcraft/shellcraft_test.rb +5 -4
  54. data/test/test_helper.rb +22 -2
  55. data/test/timer_test.rb +19 -1
  56. data/test/tubes/process_test.rb +99 -0
  57. data/test/tubes/serialtube_test.rb +165 -0
  58. data/test/tubes/sock_test.rb +20 -21
  59. data/test/tubes/tube_test.rb +86 -16
  60. metadata +75 -13
@@ -2,30 +2,30 @@
2
2
 
3
3
  require 'socket'
4
4
 
5
+ require 'pwnlib/errors'
5
6
  require 'pwnlib/tubes/tube'
6
7
 
7
8
  module Pwnlib
8
9
  module Tubes
9
10
  # Socket!
10
11
  class Sock < Tube
12
+ attr_reader :sock # @return [TCPSocket] The socket object.
13
+
11
14
  # Instantiate a {Pwnlib::Tubes::Sock} object.
12
15
  #
13
16
  # @param [String] host
14
17
  # The host to connect.
15
18
  # @param [Integer] port
16
19
  # The port to connect.
17
- def initialize(host, port)
18
- super()
20
+ # @param [Float?] timeout
21
+ # See {Pwnlib::Tubes::Tube#initialize}.
22
+ def initialize(host, port, timeout: nil)
23
+ super(timeout: timeout)
19
24
  @sock = TCPSocket.new(host, port)
20
25
  @sock.binmode
21
26
  @timeout = nil
22
- @closed = { recv: false, send: false }
23
- end
24
-
25
- def io
26
- @sock
27
+ @closed = { read: false, write: false }
27
28
  end
28
- alias sock io
29
29
 
30
30
  # Close the TCPSocket if no arguments passed.
31
31
  # Or close the direction in +sock+.
@@ -35,59 +35,58 @@ module Pwnlib
35
35
  # * Disallow further read in +sock+ if +:recv+ or +:read+ passed.
36
36
  # * Disallow further write in +sock+ if +:send+ or +:write+ passed.
37
37
  #
38
+ # @return [void]
39
+ #
38
40
  # @diff In pwntools-python, method +shutdown(direction)+ is for closing socket one side,
39
41
  # +close()+ is for closing both side. We merge these two methods into one here.
40
42
  def close(direction = :both)
41
- case direction
42
- when :both
43
+ if direction == :both
43
44
  return if @sock.closed?
44
- @closed[:recv] = @closed[:send] = true
45
+ @closed[:read] = @closed[:write] = true
45
46
  @sock.close
46
- when :recv, :read
47
- shutdown(:recv)
48
- when :send, :write
49
- shutdown(:send)
50
47
  else
51
- raise ArgumentError, 'Only allow :both, :recv, :read, :send and :write passed'
48
+ shutdown(*normalize_direction(direction))
52
49
  end
53
50
  end
54
51
 
55
52
  private
56
53
 
54
+ alias io_out sock
55
+
57
56
  def shutdown(direction)
58
57
  return if @closed[direction]
59
58
  @closed[direction] = true
60
59
 
61
- if direction.equal?(:recv)
60
+ if direction.equal?(:read)
62
61
  @sock.close_read
63
- elsif direction.equal?(:send)
62
+ elsif direction.equal?(:write)
64
63
  @sock.close_write
65
64
  end
66
65
  end
67
66
 
68
67
  def timeout_raw=(timeout)
69
- @timeout = timeout == :forever ? nil : timeout
68
+ @timeout = timeout
70
69
  end
71
70
 
72
71
  def send_raw(data)
73
- raise EOFError if @closed[:send]
72
+ raise ::Pwnlib::Errors::EndOfTubeError if @closed[:write]
74
73
  begin
75
74
  @sock.write(data)
76
75
  rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED
77
- shutdown(:send)
78
- raise EOFError
76
+ shutdown(:write)
77
+ raise ::Pwnlib::Errors::EndOfTubeError
79
78
  end
80
79
  end
81
80
 
82
81
  def recv_raw(size)
83
- raise EOFError if @closed[:recv]
82
+ raise ::Pwnlib::Errors::EndOfTubeError if @closed[:read]
84
83
  begin
85
84
  rs, = IO.select([@sock], [], [], @timeout)
86
85
  return if rs.nil?
87
86
  return @sock.readpartial(size)
88
87
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, EOFError
89
- shutdown(:recv)
90
- raise EOFError
88
+ shutdown(:read)
89
+ raise ::Pwnlib::Errors::EndOfTubeError
91
90
  end
92
91
  end
93
92
  end
@@ -1,19 +1,39 @@
1
1
  # encoding: ASCII-8BIT
2
2
 
3
3
  require 'pwnlib/context'
4
+ require 'pwnlib/errors'
4
5
  require 'pwnlib/logger'
5
6
  require 'pwnlib/timer'
6
7
  require 'pwnlib/tubes/buffer'
7
8
  require 'pwnlib/util/hexdump'
8
9
 
9
10
  module Pwnlib
11
+ # Module that contains all kinds of tubes.
10
12
  module Tubes
11
- # Things common to all tubes (sockets, tty, ...)
13
+ # @!macro [new] drop_definition
14
+ # @param [Boolean] drop
15
+ # Whether drop the ending.
16
+ #
12
17
  # @!macro [new] timeout_definition
13
18
  # @param [Float] timeout
14
19
  # Any positive floating number, indicates timeout in seconds.
15
20
  # Using +context.timeout+ if +timeout+ equals to +nil+.
21
+ #
22
+ # @!macro [new] send_return_definition
23
+ # @return [Integer]
24
+ # Returns the number of bytes had been sent.
25
+ #
26
+ # @!macro [new] raise_eof
27
+ # @raise [Pwnlib::Errors::EndOfTubeError]
28
+ # If the request is not satisfied when all data is received.
29
+ #
30
+ # @!macro [new] raise_timeout
31
+ # @raise [Pwnlib::Errors::TimeoutError]
32
+ # If the request is not satisfied when timeout exceeded.
33
+
34
+ # Things common to all tubes (sockets, tty, ...)
16
35
  class Tube
36
+ # Receive 4096 bytes each time.
17
37
  BUFSIZE = 4096
18
38
 
19
39
  # Instantiate a {Pwnlib::Tubes::Tube} object.
@@ -34,6 +54,9 @@ module Pwnlib
34
54
  # @return [String]
35
55
  # A string contains bytes received from the tube, or +''+ if a timeout occurred while
36
56
  # waiting.
57
+ #
58
+ # @!macro raise_eof
59
+ # @!macro raise_timeout
37
60
  def recv(num_bytes = nil, timeout: nil)
38
61
  return '' if @buffer.empty? && !fillbuffer(timeout: timeout)
39
62
  @buffer.get(num_bytes)
@@ -44,8 +67,12 @@ module Pwnlib
44
67
  #
45
68
  # @param [String] data
46
69
  # A string to put back.
70
+ #
71
+ # @return [Integer]
72
+ # The length of the put back data.
47
73
  def unrecv(data)
48
74
  @buffer.unget(data)
75
+ data.size
49
76
  end
50
77
 
51
78
  # Receives one byte at a time from the tube, until the predicate evaluates to +true+.
@@ -67,6 +94,9 @@ module Pwnlib
67
94
  #
68
95
  # @raise [ArgumentError]
69
96
  # If the block is not given.
97
+ #
98
+ # @!macro raise_eof
99
+ # @!macro raise_timeout
70
100
  def recvpred(timeout: nil)
71
101
  raise ArgumentError, 'Need a block for recvpred' unless block_given?
72
102
  @timer.countdown(timeout) do
@@ -75,12 +105,7 @@ module Pwnlib
75
105
  until yield(data)
76
106
  return '' unless @timer.active?
77
107
 
78
- begin
79
- # TODO(Darkpi): Some form of binary search to speed up?
80
- c = recv(1)
81
- rescue
82
- return ''
83
- end
108
+ c = recv(1)
84
109
 
85
110
  return '' if c.empty?
86
111
  data << c
@@ -103,25 +128,31 @@ module Pwnlib
103
128
  # @return [String]
104
129
  # A string contains bytes received from the tube, or +''+ if a timeout occurred while
105
130
  # waiting.
131
+ #
132
+ # @!macro raise_eof
133
+ # @!macro raise_timeout
106
134
  def recvn(num_bytes, timeout: nil)
107
135
  @timer.countdown(timeout) do
108
136
  fillbuffer while @timer.active? && @buffer.size < num_bytes
109
137
  @buffer.size >= num_bytes ? @buffer.get(num_bytes) : ''
110
138
  end
111
139
  end
140
+ alias readn recvn
112
141
 
113
- # Receive data until one of +delims+ is encountered. If the request is not satisfied before
142
+ # Receives data until one of +delims+ is encountered. If the request is not satisfied before
114
143
  # +timeout+ seconds pass, all data is buffered and an empty string is returned.
115
144
  #
116
145
  # @param [Array<String>] delims
117
146
  # String of delimiters characters, or list of delimiter strings.
118
- # @param [Boalean] drop
119
- # Whether drop the ending.
147
+ # @!macro drop_definition
120
148
  # @!macro timeout_definition
121
149
  #
122
150
  # @return [String]
123
151
  # A string contains bytes, which ends string in +delims+, received from the tube.
124
152
  #
153
+ # @!macro raise_eof
154
+ # @!macro raise_timeout
155
+ #
125
156
  # @diff We return the string that ends the earliest, rather then starts the earliest,
126
157
  # since the latter can't be done greedly. Still, it would be bad to call this
127
158
  # for case with ambiguity.
@@ -133,11 +164,7 @@ module Pwnlib
133
164
  matching = ''
134
165
  begin
135
166
  while @timer.active?
136
- begin
137
- s = recv(1)
138
- rescue # TODO(Darkpi): QQ
139
- return ''
140
- end
167
+ s = recv(1)
141
168
 
142
169
  return '' if s.empty?
143
170
  matching << s
@@ -169,12 +196,11 @@ module Pwnlib
169
196
  end
170
197
  end
171
198
 
172
- # Receive a single line from the tube.
199
+ # Receives a single line from the tube.
173
200
  # A "line" is any sequence of bytes terminated by the byte sequence set in +context.newline+,
174
- # which defaults to +"\n"+.
201
+ # which defaults to +"\\n"+.
175
202
  #
176
- # @param [Boolean] drop
177
- # Whether drop the line ending.
203
+ # @!macro drop_definition
178
204
  # @!macro timeout_definition
179
205
  #
180
206
  # @return [String]
@@ -183,7 +209,48 @@ module Pwnlib
183
209
  def recvline(drop: false, timeout: nil)
184
210
  recvuntil(context.newline, drop: drop, timeout: timeout)
185
211
  end
186
- alias gets recvline
212
+
213
+ # Receives the next "line" from the tube; lines are separated by +sep+.
214
+ # The difference with +IO#gets+ is using +context.newline+ as default newline.
215
+ #
216
+ # @param [String, Integer] sep
217
+ # If +String+ is given, use +sep+ as the separator.
218
+ # If +Integer+ is given, receive exactly +sep+ bytes.
219
+ # @!macro drop_definition
220
+ # @!macro timeout_definition
221
+ #
222
+ # @return [String]
223
+ # The next "line".
224
+ #
225
+ # @raise [Pwnlib::Errors::EndOfTubeError]
226
+ # When the remaining data does not contain +sep+.
227
+ # When the size of the remaining data is less than +sep+.
228
+ #
229
+ # @example
230
+ # Sock.new('127.0.0.1', 1337).gets
231
+ # #=> "This is line one\n"
232
+ #
233
+ # Sock.new('127.0.0.1', 1337).gets(drop: true)
234
+ # #=> "This is line one"
235
+ #
236
+ # Sock.new('127.0.0.1', 1337).gets 'line'
237
+ # #=> "This is line"
238
+ #
239
+ # Sock.new('127.0.0.1', 1337).gets ''
240
+ # #=> "This is line"
241
+ #
242
+ # Sock.new('127.0.0.1', 1337).gets(4)
243
+ # #=> "This"
244
+ def gets(sep = context.newline, drop: false, timeout: nil)
245
+ case sep
246
+ when Integer
247
+ recvn(sep, timeout: timeout)
248
+ when String
249
+ recvuntil(sep, drop: drop, timeout: timeout)
250
+ else
251
+ raise ArgumentError, 'only Integer and String are supported'
252
+ end
253
+ end
187
254
 
188
255
  # Wrapper around +recvpred+, which will return when a regex matches the string in the buffer.
189
256
  #
@@ -198,73 +265,158 @@ module Pwnlib
198
265
  recvpred(timeout: timeout) { |data| data =~ regex }
199
266
  end
200
267
 
201
- # Sends data
268
+ # Sends data.
202
269
  #
203
270
  # @param [String] data
204
- # The +data+ string to send.
271
+ # The +data+ string to be sent.
272
+ #
273
+ # @!macro send_return_definition
205
274
  def send(data)
206
275
  data = data.to_s
207
276
  log.debug(format('Sent %#x bytes:', data.size))
208
- log.indented(hexdump(data), level: DEBUG)
277
+ log.indented(::Pwnlib::Util::HexDump.hexdump(data), level: DEBUG)
209
278
  send_raw(data)
279
+ data.size
210
280
  end
211
281
  alias write send
212
282
 
213
- # Sends data with +context.newline+.
283
+ # Sends the given object with +context.newline+.
214
284
  #
215
- # @param [String] data
216
- # The +data+ string to send.
217
- def sendline(data)
218
- # Logged by +write+, not +send_raw+
219
- write(data.to_s + context.newline)
285
+ # @param [Object] obj
286
+ # The object to be sent.
287
+ #
288
+ # @!macro send_return_definition
289
+ def sendline(obj)
290
+ s = obj.to_s + context.newline
291
+ write(s)
292
+ end
293
+
294
+ # Sends the given object(s).
295
+ # The difference with +IO#puts+ is using +context.newline+ as default newline.
296
+ #
297
+ # @param [Array<Object>] objs
298
+ # The objects to be sent.
299
+ #
300
+ # @!macro send_return_definition
301
+ #
302
+ # @example
303
+ # s.puts
304
+ # puts client.recv
305
+ # #
306
+ # #=> nil
307
+ #
308
+ # @example
309
+ # s.puts('shik', "hao\n", 123)
310
+ # puts client.recv
311
+ # # shik
312
+ # # hao
313
+ # # 123
314
+ # #=> nil
315
+ #
316
+ # @example
317
+ # s.puts(["darkhh\n\n", 'wei shi', 360])
318
+ # puts client.recv
319
+ # # darkhh
320
+ # #
321
+ # # wei shi
322
+ # # 360
323
+ # #=> nil
324
+ def puts(*objs)
325
+ return write(context.newline) if objs.empty?
326
+ objs = *objs.flatten
327
+ s = ''
328
+ objs.map(&:to_s).each do |elem|
329
+ s << elem
330
+ s << context.newline unless elem.end_with?(context.newline)
331
+ end
332
+ write(s)
220
333
  end
221
- alias puts sendline
222
334
 
223
335
  # Does simultaneous reading and writing to the tube. In principle this just connects the tube
224
336
  # to standard in and standard out.
225
337
  def interact
226
338
  log.info('Switching to interactive mode')
227
339
  $stdout.write(@buffer.get)
228
- until io.closed?
229
- rs, = IO.select([$stdin, io])
340
+ until io_out.closed?
341
+ rs, = IO.select([$stdin, io_out])
230
342
  if rs.include?($stdin)
231
343
  s = $stdin.readpartial(BUFSIZE)
232
344
  write(s)
233
345
  end
234
- if rs.include?(io)
346
+ if rs.include?(io_out)
235
347
  s = recv
236
348
  $stdout.write(s)
237
349
  end
238
350
  end
351
+ rescue ::Pwnlib::Errors::EndOfTubeError
352
+ log.info('Got EOF in interactive mode')
239
353
  end
240
354
 
241
355
  private
242
356
 
357
+ # Normalize direction.
358
+ #
359
+ # @param [Symbol] direction
360
+ #
361
+ # @return [Array<Symbol>]
362
+ # If +direction+ equals to
363
+ # * +:both+, returns +[:read, :write]+
364
+ # * +:read+ or +:recv+, returns [:read]
365
+ # * +:write+ or +:send+, returns [:write]
366
+ # Otherwise, raise +ArgumentError+.
367
+ def normalize_direction(direction)
368
+ case direction
369
+ when :both then %i[read write]
370
+ when :read, :recv then [:read]
371
+ when :write, :send then [:write]
372
+ else
373
+ raise ArgumentError, 'Only allow :both, :recv, :read, :send and :write passed'
374
+ end
375
+ end
376
+
243
377
  def fillbuffer(timeout: nil)
244
378
  data = @timer.countdown(timeout) do
245
- self.timeout_raw = @timer.timeout
379
+ self.timeout_raw = (@timer.timeout == :forever ? nil : @timer.timeout)
246
380
  recv_raw(BUFSIZE)
247
381
  end
248
382
  if data
249
383
  @buffer << data
250
384
  log.debug(format('Received %#x bytes:', data.size))
251
- log.indented(hexdump(data), level: DEBUG)
385
+ log.indented(::Pwnlib::Util::HexDump.hexdump(data), level: DEBUG)
252
386
  end
253
387
  data
254
388
  end
255
389
 
256
- def send_raw(_data); raise NotImplementedError, 'Not implemented'
390
+ # The IO object of output, will be used for IO.select([io_out]) in interactive mode.
391
+ #
392
+ # @return [IO]
393
+ def io_out
394
+ raise NotImplementedError, 'Not implemented'
257
395
  end
258
396
 
259
- def recv_raw(_size); raise NotImplementedError, 'Not implemented'
397
+ # @param [String] _data
398
+ #
399
+ # @return [void]
400
+ def send_raw(_data)
401
+ raise NotImplementedError, 'Not implemented'
260
402
  end
261
403
 
262
- def timeout_raw=(_timeout); raise NotImplementedError, 'Not implemented'
404
+ # @param [Integer] _size
405
+ #
406
+ # @return [String]
407
+ def recv_raw(_size)
408
+ raise NotImplementedError, 'Not implemented'
409
+ end
410
+
411
+ # @param [Float?] _timeout
412
+ #
413
+ # @return [void]
414
+ def timeout_raw=(_timeout)
415
+ raise NotImplementedError, 'Not implemented'
263
416
  end
264
417
 
265
418
  include ::Pwnlib::Context
266
419
  include ::Pwnlib::Logger
267
- include ::Pwnlib::Util::HexDump
268
420
  end
269
421
  end
270
422
  end