Class: Pwnlib::Tubes::Tube

Inherits:
Object
  • Object
show all
Includes:
Context, Logger
Defined in:
lib/pwnlib/tubes/tube.rb

Overview

Things common to all tubes (sockets, tty, …)

Direct Known Subclasses

Process, SerialTube, Sock

Constant Summary collapse

BUFSIZE =

Receive 4096 bytes each time.

4096

Instance Method Summary collapse

Constructor Details

#initialize(timeout: nil) ⇒ Tube

Instantiate a Pwnlib::Tubes::Tube object.

Parameters:

  • timeout (Float) (defaults to: nil)

    Any positive floating number, indicates timeout in seconds. Using context.timeout if timeout equals to nil.



43
44
45
46
# File 'lib/pwnlib/tubes/tube.rb', line 43

def initialize(timeout: nil)
  @timer = Timer.new(timeout)
  @buffer = Buffer.new
end

Instance Method Details

#gets(sep = context.newline, drop: false, timeout: nil) ⇒ String

Receives the next “line” from the tube; lines are separated by sep. The difference with IO#gets is using context.newline as default newline.

Examples:

Sock.new('127.0.0.1', 1337).gets
#=> "This is line one\n"

Sock.new('127.0.0.1', 1337).gets(drop: true)
#=> "This is line one"

Sock.new('127.0.0.1', 1337).gets 'line'
#=> "This is line"

Sock.new('127.0.0.1', 1337).gets ''
#=> "This is line"

Sock.new('127.0.0.1', 1337).gets(4)
#=> "This"

Parameters:

  • sep (String, Integer) (defaults to: context.newline)

    If String is given, use sep as the separator. If Integer is given, receive exactly sep bytes.

  • drop (Boolean) (defaults to: false)

    Whether drop the ending.

  • timeout (Float) (defaults to: nil)

    Any positive floating number, indicates timeout in seconds. Using context.timeout if timeout equals to nil.

Returns:

  • (String)

    The next "line".

Raises:



250
251
252
253
254
255
256
257
258
259
# File 'lib/pwnlib/tubes/tube.rb', line 250

def gets(sep = context.newline, drop: false, timeout: nil)
  case sep
  when Integer
    recvn(sep, timeout: timeout)
  when String
    recvuntil(sep, drop: drop, timeout: timeout)
  else
    raise ArgumentError, 'only Integer and String are supported'
  end
end

#interactObject

Does simultaneous reading and writing to the tube. In principle this just connects the tube to standard in and standard out.



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/pwnlib/tubes/tube.rb', line 357

def interact
  log.info('Switching to interactive mode')
  $stdout.write(@buffer.get)
  until io_out.closed?
    rs, = IO.select([$stdin, io_out])
    if rs.include?($stdin)
      s = $stdin.readpartial(BUFSIZE)
      write(s)
    end
    if rs.include?(io_out)
      s = recv
      $stdout.write(s)
    end
  end
rescue ::Pwnlib::Errors::EndOfTubeError
  log.info('Got EOF in interactive mode')
end

#puts(*objs) ⇒ Integer

Sends the given object(s). The difference with IO#puts is using context.newline as default newline.

Examples:

s.puts
puts client.recv
#
#=> nil
s.puts('shik', "hao\n", 123)
puts client.recv
# shik
# hao
# 123
#=> nil
s.puts(["darkhh\n\n", 'wei shi', 360])
puts client.recv
# darkhh
#
# wei shi
# 360
#=> nil

Parameters:

  • objs (Array<Object>)

    The objects to be sent.

Returns:

  • (Integer)

    Returns the number of bytes had been sent.



343
344
345
346
347
348
349
350
351
352
353
# File 'lib/pwnlib/tubes/tube.rb', line 343

def puts(*objs)
  return write(context.newline) if objs.empty?

  objs = *objs.flatten
  s = +''
  objs.map(&:to_s).each do |elem|
    s << elem
    s << context.newline unless elem.end_with?(context.newline)
  end
  write(s)
end

#recv(num_bytes = nil, timeout: nil) ⇒ String Also known as: read

Receives up to num_bytes bytes of data from the tube, and returns as soon as any quantity of data is available.

Parameters:

  • num_bytes (Integer) (defaults to: nil)

    The maximum number of bytes to receive.

  • timeout (Float) (defaults to: nil)

    Any positive floating number, indicates timeout in seconds. Using context.timeout if timeout equals to nil.

Returns:

  • (String)

    A string contains bytes received from the tube, or " if a timeout occurred while waiting.

Raises:



61
62
63
64
65
# File 'lib/pwnlib/tubes/tube.rb', line 61

def recv(num_bytes = nil, timeout: nil)
  return '' if @buffer.empty? && !fillbuffer(timeout: timeout)

  @buffer.get(num_bytes)
end

#recvall(timeout: nil) ⇒ String Also known as: readall

Receives data until reaching EOF or a timeout is occurred.

Parameters:

  • timeout (Float) (defaults to: nil)

    Any positive floating number, indicates timeout in seconds. Using context.timeout if timeout equals to nil.

Returns:

  • (String)

    Returns the data received.



280
281
282
283
284
# File 'lib/pwnlib/tubes/tube.rb', line 280

def recvall(timeout: nil)
  recvn(1 << 63, timeout: timeout)
rescue ::Pwnlib::Errors::EndOfTubeError, ::Pwnlib::Errors::TimeoutError
  @buffer.get
end

#recvline(drop: false, timeout: nil) ⇒ String

Receives a single line from the tube. A “line” is any sequence of bytes terminated by the byte sequence set in context.newline, which defaults to “\n”.

Parameters:

  • drop (Boolean) (defaults to: false)

    Whether drop the ending.

  • timeout (Float) (defaults to: nil)

    Any positive floating number, indicates timeout in seconds. Using context.timeout if timeout equals to nil.

Returns:

  • (String)

    All bytes received over the tube until the first newline is received. Optionally retains the ending.



215
216
217
# File 'lib/pwnlib/tubes/tube.rb', line 215

def recvline(drop: false, timeout: nil)
  recvuntil(context.newline, drop: drop, timeout: timeout)
end

#recvn(num_bytes, timeout: nil) ⇒ String Also known as: readn

Receives exactly num_bytes bytes. If the request is not satisfied before timeout seconds pass, all data is buffered and an empty string is returned.

Parameters:

  • num_bytes (Integer)

    The number of bytes to receive.

  • timeout (Float) (defaults to: nil)

    Any positive floating number, indicates timeout in seconds. Using context.timeout if timeout equals to nil.

Returns:

  • (String)

    A string contains bytes received from the tube, or " if a timeout occurred while waiting.

Raises:



138
139
140
141
142
143
# File 'lib/pwnlib/tubes/tube.rb', line 138

def recvn(num_bytes, timeout: nil)
  @timer.countdown(timeout) do
    fillbuffer while @timer.active? && @buffer.size < num_bytes
    @buffer.size >= num_bytes ? @buffer.get(num_bytes) : ''
  end
end

#recvpred(timeout: nil) {|data| ... } ⇒ String

Receives one byte at a time from the tube, until the predicate evaluates to true.

Parameters:

  • timeout (Float) (defaults to: nil)

    Any positive floating number, indicates timeout in seconds. Using context.timeout if timeout equals to nil.

Yields:

  • A predicate to evaluate whether the data satisfy the condition.

Yield Parameters:

  • data (String)

    A string data to be validated by the predicate.

Yield Returns:

  • (Boolean)

    Whether the data satisfy the condition.

Returns:

  • (String)

    A string contains bytes received from the tube, or " if a timeout occurred while waiting.

Raises:



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/pwnlib/tubes/tube.rb', line 102

def recvpred(timeout: nil)
  raise ArgumentError, 'Need a block for recvpred' unless block_given?

  @timer.countdown(timeout) do
    data = +''
    begin
      until yield(data)
        return '' unless @timer.active?

        c = recv(1)

        return '' if c.empty?

        data << c
      end
      data.slice!(0..-1)
    ensure
      unrecv(data)
    end
  end
end

#recvregex(regex, timeout: nil) ⇒ String

Wrapper around recvpred, which will return when a regex matches the string in the buffer.

Parameters:

  • regex (Regexp)

    A regex to match.

  • timeout (Float) (defaults to: nil)

    Any positive floating number, indicates timeout in seconds. Using context.timeout if timeout equals to nil.

Returns:

  • (String)

    A string contains bytes received from the tube, or " if a timeout occurred while waiting.



270
271
272
# File 'lib/pwnlib/tubes/tube.rb', line 270

def recvregex(regex, timeout: nil)
  recvpred(timeout: timeout) { |data| data =~ regex }
end

#recvuntil(delims, drop: false, timeout: nil) ⇒ String

Receives data until one of delims is encountered. If the request is not satisfied before timeout seconds pass, all data is buffered and an empty string is returned.

Parameters:

  • delims (Array<String>)

    String of delimiters characters, or list of delimiter strings.

  • drop (Boolean) (defaults to: false)

    Whether drop the ending.

  • timeout (Float) (defaults to: nil)

    Any positive floating number, indicates timeout in seconds. Using context.timeout if timeout equals to nil.

Returns:

  • (String)

    A string contains bytes, which ends string in delims, received from the tube.

Raises:

Difference with Python pwntools:

  • We return the string that ends the earliest, rather then starts the earliest, since the latter can't be done greedly. Still, it would be bad to call this for case with ambiguity.



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
# File 'lib/pwnlib/tubes/tube.rb', line 163

def recvuntil(delims, drop: false, timeout: nil)
  delims = Array(delims)
  max_len = delims.map(&:size).max
  @timer.countdown(timeout) do
    data = Buffer.new
    matching = +''
    begin
      while @timer.active?
        s = recv(1)

        return '' if s.empty?

        matching << s

        sidx = matching.size
        match_len = 0
        delims.each do |d|
          idx = matching.index(d)
          next unless idx

          if idx + d.size <= sidx + match_len
            sidx = idx
            match_len = d.size
          end
        end

        if sidx < matching.size
          r = data.get + matching.slice!(0, sidx + match_len)
          r.slice!(-match_len..-1) if drop
          return r
        end

        data << matching.slice!(0...-max_len) if matching.size > max_len
      end
      ''
    ensure
      unrecv(matching)
      unrecv(data)
    end
  end
end

#send(data) ⇒ Integer Also known as: write

Sends data.

Parameters:

  • data (String)

    The data string to be sent.

Returns:

  • (Integer)

    Returns the number of bytes had been sent.



293
294
295
296
297
298
299
# File 'lib/pwnlib/tubes/tube.rb', line 293

def send(data)
  data = data.to_s
  log.debug(format('Sent %#x bytes:', data.size))
  log.indented(::Pwnlib::Util::HexDump.hexdump(data), level: DEBUG)
  send_raw(data)
  data.size
end

#sendline(obj) ⇒ Integer

Sends the given object with context.newline.

Parameters:

  • obj (Object)

    The object to be sent.

Returns:

  • (Integer)

    Returns the number of bytes had been sent.



308
309
310
311
# File 'lib/pwnlib/tubes/tube.rb', line 308

def sendline(obj)
  s = obj.to_s + context.newline
  write(s)
end

#unrecv(data) ⇒ Integer

Puts the specified data back at the beginning of the receive buffer.

Parameters:

  • data (String)

    A string to put back.

Returns:

  • (Integer)

    The length of the put back data.



75
76
77
78
# File 'lib/pwnlib/tubes/tube.rb', line 75

def unrecv(data)
  @buffer.unget(data)
  data.size
end