-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
7 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# encoding: ASCII-8BIT | ||
|
||
require 'rubyserial' | ||
|
||
require 'pwnlib/tubes/tube' | ||
|
||
module Pwnlib | ||
module Tubes | ||
# @!macro [new] raise_eof | ||
# @raise [Pwnlib::Errors::EndOfTubeError] | ||
# If the request is not satisfied when all data is received. | ||
|
||
# Serial Connections | ||
class SerialTube < Tube | ||
# Instantiate a {Pwnlib::Tubes::SerialTube} object. | ||
# | ||
# @param [String] port | ||
# A device name for rubyserial to open, e.g. /dev/ttypUSB0 | ||
# @param [Integer] baudrate | ||
# Baud rate. | ||
# @param [Boolean] convert_newlines | ||
# If +true+, convert any +context.newline+s to +"\\r\\n"+ before | ||
# sending to remote. Has no effect on bytes received. | ||
# @param [Integer] bytesize | ||
# Serial character byte size. The '8' in '8N1'. | ||
# @param [Symbol] parity | ||
# Serial character parity. The 'N' in '8N1'. | ||
def initialize(port = nil, baudrate: 115_200, | ||
convert_newlines: true, | ||
bytesize: 8, parity: :none) | ||
super() | ||
|
||
# go hunting for a port | ||
port ||= Dir.glob('/dev/tty.usbserial*').first | ||
port ||= '/dev/ttyUSB0' | ||
|
||
@convert_newlines = convert_newlines | ||
@conn = Serial.new(port, baudrate, bytesize, parity) | ||
@serial_timer = Timer.new | ||
end | ||
|
||
# Closes the active connection | ||
def close | ||
@conn.close if @conn && !@conn.closed? | ||
@conn = nil | ||
end | ||
|
||
# Implementation of the methods required for tube | ||
private | ||
|
||
# Gets bytes over the serial connection until some bytes are received, or | ||
# +@timeout+ has passed. It is an error for it to return no data in less | ||
# than +@timeout+ seconds. It is ok for it to return some data in less | ||
# time. | ||
# | ||
# @param [Integer] numbytes | ||
# An upper limit on the number of bytes to get. | ||
# | ||
# @return [String] | ||
# A string containing read bytes. | ||
# | ||
# @!macro raise_eof | ||
def recv_raw(numbytes) | ||
raise ::Pwnlib::Errors::EndOfTubeError if @conn.nil? | ||
|
||
@serial_timer.countdown do | ||
data = '' | ||
begin | ||
while @serial_timer.active? | ||
data += @conn.read(numbytes - data.length) | ||
break unless data.empty? | ||
sleep 0.1 | ||
end | ||
# XXX(JonathanBeverley): should we reverse @convert_newlines here? | ||
return data | ||
rescue RubySerial::Error | ||
close | ||
raise ::Pwnlib::Errors::EndOfTubeError | ||
end | ||
end | ||
end | ||
|
||
# Sends bytes over the serial connection. This call will block until all the bytes are sent or an error occurs. | ||
# | ||
# @param [String] data | ||
# A string of the bytes to send. | ||
# | ||
# @return [Integer] | ||
# The number of bytes successfully written. | ||
# | ||
# @!macro raise_eof | ||
def send_raw(data) | ||
raise ::Pwnlib::Errors::EndOfTubeError if @conn.nil? | ||
|
||
data.gsub!(context.newline, "\r\n") if @convert_newlines | ||
begin | ||
return @conn.write(data) | ||
rescue RubySerial::Error | ||
close | ||
raise ::Pwnlib::Errors::EndOfTubeError | ||
end | ||
end | ||
|
||
# Sets the +timeout+ to use for subsequent +recv_raw+ calls. | ||
# | ||
# @param [Float] timeout | ||
def timeout_raw=(timeout) | ||
@serial_timer.timeout = timeout | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
# encoding: ASCII-8BIT | ||
|
||
require 'open3' | ||
|
||
require 'test_helper' | ||
|
||
require 'pwnlib/tubes/serialtube' | ||
|
||
module Pwnlib | ||
module Tubes | ||
class SerialTube | ||
def break_encapsulation | ||
@conn.close | ||
end | ||
end | ||
end | ||
end | ||
|
||
class SerialTest < MiniTest::Test | ||
include ::Pwnlib::Tubes | ||
|
||
def skip_windows | ||
skip 'Not test tube/serialtube on Windows' if TTY::Platform.new.windows? | ||
end | ||
|
||
def open_pair | ||
Open3.popen3('socat -d -d pty,raw,echo=0 pty,raw,echo=0') do |_i, _o, stderr, thread| | ||
devs = [] | ||
2.times do | ||
devs << stderr.readline.chomp.split.last | ||
# First pattern matches Linux, second is macOS | ||
raise IOError, 'Could not create serial crosslink' if devs.last !~ %r{^(/dev/pts/[0-9]+|/dev/ttys[0-9]+)$} | ||
end | ||
# To ensure socat have finished setup | ||
stderr.gets('starting data transfer loop') | ||
|
||
serial = SerialTube.new devs[1], convert_newlines: false | ||
|
||
begin | ||
File.open devs[0], 'r+' do |file| | ||
file.set_encoding 'default'.encoding | ||
yield file, serial, thread | ||
end | ||
ensure | ||
::Process.kill('SIGTERM', thread.pid) if thread.alive? | ||
end | ||
end | ||
end | ||
|
||
def random_string(length) | ||
Random.rand(36**length).to_s(36).rjust(length, '0') | ||
end | ||
|
||
def test_raise | ||
skip_windows | ||
open_pair do |_file, serial, thread| | ||
::Process.kill('SIGTERM', thread.pid) | ||
# ensure the process has been killed | ||
thread.value | ||
assert_raises(Pwnlib::Errors::EndOfTubeError) { serial.puts('a') } | ||
end | ||
open_pair do |_file, serial| | ||
serial.break_encapsulation | ||
assert_raises(Pwnlib::Errors::EndOfTubeError) { serial.recv(1, timeout: 2) } | ||
end | ||
end | ||
|
||
def test_recv | ||
skip_windows | ||
open_pair do |file, serial| | ||
# recv, recvline | ||
rs = random_string 24 | ||
file.puts rs | ||
result = serial.recv 8, timeout: 1 | ||
|
||
assert_equal(rs[0...8], result) | ||
result = serial.recv 8 | ||
assert_equal(rs[8...16], result) | ||
result = serial.recvline.chomp | ||
assert_equal(rs[16..-1], result) | ||
|
||
assert_raises(Pwnlib::Errors::TimeoutError) { serial.recv(1, timeout: 0.2) } | ||
|
||
# recvpred | ||
rs = random_string 12 | ||
file.print rs | ||
result = serial.recvpred do |data| | ||
data[-6..-1] == rs[-6..-1] | ||
end | ||
assert_equal rs, result | ||
|
||
assert_raises(Pwnlib::Errors::TimeoutError) { serial.recv(1, timeout: 0.2) } | ||
|
||
# recvn | ||
rs = random_string 6 | ||
file.print rs | ||
result = '' | ||
assert_raises(Pwnlib::Errors::TimeoutError) do | ||
result = serial.recvn 120, timeout: 1 | ||
end | ||
assert_empty result | ||
file.print rs | ||
result = serial.recvn 12 | ||
assert_equal rs * 2, result | ||
|
||
assert_raises(Pwnlib::Errors::TimeoutError) { serial.recv(1, timeout: 0.2) } | ||
|
||
# recvuntil | ||
rs = random_string 12 | ||
file.print rs + '|' | ||
result = serial.recvuntil('|').chomp('|') | ||
assert_equal rs, result | ||
|
||
assert_raises(Pwnlib::Errors::TimeoutError) { serial.recv(1, timeout: 0.2) } | ||
|
||
# gets | ||
rs = random_string 24 | ||
file.puts rs | ||
result = serial.gets 12 | ||
assert_equal rs[0...12], result | ||
result = serial.gets.chomp | ||
assert_equal rs[12..-1], result | ||
|
||
assert_raises(Pwnlib::Errors::TimeoutError) { serial.recv(1, timeout: 0.2) } | ||
end | ||
end | ||
|
||
def test_send | ||
skip_windows | ||
open_pair do |file, serial| | ||
# send, sendline | ||
rs = random_string 24 | ||
# rubocop:disable Style/Send | ||
# Justification: This isn't Object#send, false positive. | ||
serial.send rs[0...12] | ||
# rubocop:enable Style/Send | ||
serial.sendline rs[12...24] | ||
result = file.readline.chomp | ||
assert_equal rs, result | ||
|
||
# puts | ||
r1 = random_string 4 | ||
r2 = random_string 4 | ||
r3 = random_string 4 | ||
serial.puts r1, r2, r3 | ||
result = '' | ||
3.times do | ||
result += file.readline.chomp | ||
end | ||
assert_equal r1 + r2 + r3, result | ||
end | ||
end | ||
|
||
def test_close | ||
skip_windows | ||
open_pair do |_file, serial| | ||
serial.close | ||
assert_raises(::Pwnlib::Errors::EndOfTubeError) { serial.puts(514) } | ||
assert_raises(::Pwnlib::Errors::EndOfTubeError) { serial.puts(514) } | ||
assert_raises(::Pwnlib::Errors::EndOfTubeError) { serial.recv } | ||
assert_raises(::Pwnlib::Errors::EndOfTubeError) { serial.recv } | ||
assert_raises(ArgumentError) { serial.close(:hh) } | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters