pwntools 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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