pwntools 0.1.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +96 -15
- data/Rakefile +8 -2
- data/lib/pwn.rb +10 -7
- data/lib/pwnlib/abi.rb +61 -0
- data/lib/pwnlib/asm.rb +357 -0
- data/lib/pwnlib/constants/constant.rb +19 -3
- data/lib/pwnlib/constants/constants.rb +46 -20
- data/lib/pwnlib/constants/linux/amd64.rb +32 -1
- data/lib/pwnlib/constants/linux/i386.rb +2 -0
- data/lib/pwnlib/context.rb +128 -27
- data/lib/pwnlib/dynelf.rb +122 -54
- data/lib/pwnlib/elf/elf.rb +340 -0
- data/lib/pwnlib/errors.rb +31 -0
- data/lib/pwnlib/ext/array.rb +2 -1
- data/lib/pwnlib/ext/helper.rb +6 -5
- data/lib/pwnlib/ext/integer.rb +2 -1
- data/lib/pwnlib/ext/string.rb +3 -2
- data/lib/pwnlib/logger.rb +245 -0
- data/lib/pwnlib/memleak.rb +59 -29
- data/lib/pwnlib/pwn.rb +27 -9
- data/lib/pwnlib/reg_sort.rb +109 -110
- data/lib/pwnlib/runner.rb +53 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/common.rb +16 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb +35 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/mov.rb +131 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/nop.rb +18 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/popad.rb +28 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb +66 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/ret.rb +33 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/cat.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/exit.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/linux.rb +16 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/open.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/sleep.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/helper.rb +115 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/common.rb +16 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/memcpy.rb +34 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/mov.rb +93 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/nop.rb +18 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/pushstr.rb +41 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/cat.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/exit.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/linux.rb +16 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/open.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/sleep.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/common.rb +29 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/infloop.rb +24 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/memcpy.rb +17 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/mov.rb +17 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/pushstr.rb +17 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/pushstr_array.rb +86 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/setregs.rb +84 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/cat.rb +54 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/execve.rb +72 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/exit.rb +34 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/linux.rb +16 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/ls.rb +67 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/open.rb +47 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/sh.rb +53 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/sleep.rb +52 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/syscall.rb +52 -0
- data/lib/pwnlib/shellcraft/registers.rb +148 -0
- data/lib/pwnlib/shellcraft/shellcraft.rb +73 -0
- data/lib/pwnlib/timer.rb +67 -0
- data/lib/pwnlib/tubes/buffer.rb +99 -0
- data/lib/pwnlib/tubes/process.rb +155 -0
- data/lib/pwnlib/tubes/serialtube.rb +114 -0
- data/lib/pwnlib/tubes/sock.rb +101 -0
- data/lib/pwnlib/tubes/tube.rb +442 -0
- data/lib/pwnlib/ui.rb +21 -0
- data/lib/pwnlib/util/cyclic.rb +97 -94
- data/lib/pwnlib/util/fiddling.rb +288 -220
- data/lib/pwnlib/util/getdents.rb +85 -0
- data/lib/pwnlib/util/hexdump.rb +116 -112
- data/lib/pwnlib/util/lists.rb +58 -0
- data/lib/pwnlib/util/packing.rb +223 -228
- data/lib/pwnlib/util/ruby.rb +19 -0
- data/lib/pwnlib/version.rb +3 -1
- data/test/abi_test.rb +22 -0
- data/test/asm_test.rb +177 -0
- data/test/constants/constant_test.rb +2 -0
- data/test/constants/constants_test.rb +5 -2
- data/test/context_test.rb +14 -3
- data/test/data/assembly/aarch64.s +19 -0
- data/test/data/assembly/amd64.s +21 -0
- data/test/data/assembly/arm.s +9 -0
- data/test/data/assembly/i386.s +21 -0
- data/test/data/assembly/mips.s +16 -0
- data/test/data/assembly/mips64.s +6 -0
- data/test/data/assembly/powerpc.s +18 -0
- data/test/data/assembly/powerpc64.s +36 -0
- data/test/data/assembly/sparc.s +33 -0
- data/test/data/assembly/sparc64.s +5 -0
- data/test/data/assembly/thumb.s +37 -0
- data/test/data/echo.rb +16 -0
- data/test/data/elfs/Makefile +24 -0
- data/test/data/elfs/amd64.frelro.elf +0 -0
- data/test/data/elfs/amd64.frelro.pie.elf +0 -0
- data/test/data/elfs/amd64.nrelro.elf +0 -0
- data/test/data/elfs/amd64.prelro.elf +0 -0
- data/test/data/elfs/amd64.static.elf +0 -0
- data/test/data/elfs/i386.frelro.pie.elf +0 -0
- data/test/data/elfs/i386.prelro.elf +0 -0
- data/test/data/elfs/source.cpp +19 -0
- data/test/data/flag +1 -0
- data/test/data/lib32/ld.so.2 +0 -0
- data/test/data/lib32/libc.so.6 +0 -0
- data/test/data/lib64/ld.so.2 +0 -0
- data/test/data/lib64/libc.so.6 +0 -0
- data/test/dynelf_test.rb +62 -25
- data/test/elf/elf_test.rb +147 -0
- data/test/ext_test.rb +4 -2
- data/test/files/use_pwn.rb +3 -6
- data/test/files/use_pwnlib.rb +2 -1
- data/test/full_file_test.rb +6 -0
- data/test/logger_test.rb +120 -0
- data/test/memleak_test.rb +5 -33
- data/test/reg_sort_test.rb +4 -1
- data/test/runner_test.rb +32 -0
- data/test/shellcraft/infloop_test.rb +27 -0
- data/test/shellcraft/linux/cat_test.rb +87 -0
- data/test/shellcraft/linux/ls_test.rb +109 -0
- data/test/shellcraft/linux/sh_test.rb +120 -0
- data/test/shellcraft/linux/sleep_test.rb +68 -0
- data/test/shellcraft/linux/syscalls/execve_test.rb +137 -0
- data/test/shellcraft/linux/syscalls/exit_test.rb +57 -0
- data/test/shellcraft/linux/syscalls/open_test.rb +87 -0
- data/test/shellcraft/linux/syscalls/syscall_test.rb +84 -0
- data/test/shellcraft/memcpy_test.rb +50 -0
- data/test/shellcraft/mov_test.rb +99 -0
- data/test/shellcraft/nop_test.rb +27 -0
- data/test/shellcraft/popad_test.rb +30 -0
- data/test/shellcraft/pushstr_array_test.rb +92 -0
- data/test/shellcraft/pushstr_test.rb +109 -0
- data/test/shellcraft/registers_test.rb +33 -0
- data/test/shellcraft/ret_test.rb +31 -0
- data/test/shellcraft/setregs_test.rb +63 -0
- data/test/shellcraft/shellcraft_test.rb +30 -0
- data/test/test_helper.rb +61 -2
- data/test/timer_test.rb +42 -0
- data/test/tubes/buffer_test.rb +46 -0
- data/test/tubes/process_test.rb +105 -0
- data/test/tubes/serialtube_test.rb +162 -0
- data/test/tubes/sock_test.rb +68 -0
- data/test/tubes/tube_test.rb +320 -0
- data/test/ui_test.rb +18 -0
- data/test/util/cyclic_test.rb +3 -1
- data/test/util/fiddling_test.rb +12 -3
- data/test/util/getdents_test.rb +33 -0
- data/test/util/hexdump_test.rb +9 -10
- data/test/util/lists_test.rb +22 -0
- data/test/util/packing_test.rb +5 -3
- metadata +357 -37
@@ -0,0 +1,73 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'singleton'
|
5
|
+
|
6
|
+
require 'pwnlib/context'
|
7
|
+
require 'pwnlib/errors'
|
8
|
+
require 'pwnlib/logger'
|
9
|
+
|
10
|
+
module Pwnlib
|
11
|
+
# Implement shellcraft!
|
12
|
+
#
|
13
|
+
# All shellcode generators are defined under generators/*.
|
14
|
+
# While typing +Shellcraft::Generators::I386::Linux.sh+ is too annoying, we define an instance +shellcraft+ in this
|
15
|
+
# module, which let user invoke +shellcraft.sh+ directly.
|
16
|
+
module Shellcraft
|
17
|
+
# Singleton class.
|
18
|
+
class Shellcraft
|
19
|
+
include ::Singleton
|
20
|
+
|
21
|
+
# All files under generators/ will be required.
|
22
|
+
def initialize
|
23
|
+
Dir[File.join(__dir__, 'generators', '**', '*.rb')].sort.each do |f|
|
24
|
+
require f
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Will search modules/methods under {Shellcraft::Generators} according to current arch and os.
|
29
|
+
# i.e. +Shellcraft::Generators::${arch}::<Common|${os}>.${method}+.
|
30
|
+
#
|
31
|
+
# With this method, +context.local(arch: 'amd64') { shellcraft.sh }+ will invoke
|
32
|
+
# {Shellcraft::Generators::Amd64::Linux#sh}.
|
33
|
+
def method_missing(method, *args, **kwargs, &block)
|
34
|
+
mod = find_module_for(method)
|
35
|
+
return super if mod.nil?
|
36
|
+
|
37
|
+
mod.public_send(method, *args, **kwargs, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
# For +respond_to?+.
|
41
|
+
def respond_to_missing?(method, include_private = false)
|
42
|
+
return true if find_module_for(method)
|
43
|
+
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# @return [Module?]
|
50
|
+
# +nil+ for not found.
|
51
|
+
def find_module_for(method)
|
52
|
+
begin
|
53
|
+
arch_module = ::Pwnlib::Shellcraft::Generators.const_get(context.arch.capitalize)
|
54
|
+
rescue NameError
|
55
|
+
raise ::Pwnlib::Errors::UnsupportedArchError,
|
56
|
+
"Can't use shellcraft under architecture #{context.arch.inspect}."
|
57
|
+
end
|
58
|
+
# try search in Common module
|
59
|
+
common_module = arch_module.const_get(:Common)
|
60
|
+
return common_module if common_module.singleton_methods.include?(method)
|
61
|
+
|
62
|
+
# search in ${os} module
|
63
|
+
os_module = arch_module.const_get(context.os.capitalize)
|
64
|
+
return os_module if os_module.singleton_methods.include?(method)
|
65
|
+
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
include ::Pwnlib::Context
|
70
|
+
include ::Pwnlib::Logger
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/pwnlib/timer.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'time'
|
5
|
+
|
6
|
+
require 'pwnlib/context'
|
7
|
+
require 'pwnlib/errors'
|
8
|
+
|
9
|
+
module Pwnlib
|
10
|
+
# A simple timer class.
|
11
|
+
# TODO(Darkpi): Python pwntools seems to have many unreasonable codes in this class,
|
12
|
+
# not sure of the use case of this, check if everything is coded as
|
13
|
+
# intended after we have some use cases. (e.g. sock)
|
14
|
+
# NOTE(Darkpi): This class is actually quite weird, and expected to be used only in tubes.
|
15
|
+
class Timer
|
16
|
+
# @diff We just use nil for default and :forever for forever.
|
17
|
+
|
18
|
+
def initialize(timeout = nil)
|
19
|
+
@deadline = nil
|
20
|
+
@timeout = timeout
|
21
|
+
end
|
22
|
+
|
23
|
+
def started?
|
24
|
+
@deadline
|
25
|
+
end
|
26
|
+
|
27
|
+
def active?
|
28
|
+
started? && (@deadline == :forever || Time.now < @deadline)
|
29
|
+
end
|
30
|
+
|
31
|
+
def timeout
|
32
|
+
return @timeout || ::Pwnlib::Context.context.timeout unless started?
|
33
|
+
|
34
|
+
@deadline == :forever ? :forever : [@deadline - Time.now, 0].max
|
35
|
+
end
|
36
|
+
|
37
|
+
def timeout=(timeout)
|
38
|
+
raise "Can't change timeout when countdown" if started?
|
39
|
+
|
40
|
+
@timeout = timeout
|
41
|
+
end
|
42
|
+
|
43
|
+
# @diff We do NOT allow nested countdown with non-default value. This simplifies thing a lot.
|
44
|
+
# NOTE(Darkpi): timeout = nil means default value for the first time, and nop after that.
|
45
|
+
def countdown(timeout = nil)
|
46
|
+
raise ArgumentError, 'Need a block for countdown' unless block_given?
|
47
|
+
|
48
|
+
if started?
|
49
|
+
return yield if timeout.nil?
|
50
|
+
|
51
|
+
raise 'Nested countdown not permitted'
|
52
|
+
end
|
53
|
+
|
54
|
+
timeout ||= @timeout || ::Pwnlib::Context.context.timeout
|
55
|
+
|
56
|
+
@deadline = timeout == :forever ? :forever : Time.now + timeout
|
57
|
+
|
58
|
+
begin
|
59
|
+
yield
|
60
|
+
ensure
|
61
|
+
was_active = active?
|
62
|
+
@deadline = nil
|
63
|
+
raise ::Pwnlib::Errors::TimeoutError unless was_active
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Pwnlib
|
5
|
+
module Tubes
|
6
|
+
# Buffer that support deque-like string operations.
|
7
|
+
class Buffer
|
8
|
+
# Instantiate a {Pwnlib::Tubes::Buffer} object.
|
9
|
+
def initialize
|
10
|
+
@data = []
|
11
|
+
@size = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :size
|
15
|
+
alias length size
|
16
|
+
|
17
|
+
# Check whether the buffer is empty.
|
18
|
+
#
|
19
|
+
# @return [Boalean]
|
20
|
+
# Returns true if contains no elements.
|
21
|
+
def empty?
|
22
|
+
size.zero?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Python __contains__ and index is only correct with single-char input, which doesn't seems to
|
26
|
+
# be useful, and they're not used anywhere either. Just ignore them for now.
|
27
|
+
|
28
|
+
# Adds data to the buffer.
|
29
|
+
#
|
30
|
+
# @param [String] data
|
31
|
+
# Data to add.
|
32
|
+
def add(data)
|
33
|
+
case data
|
34
|
+
when Buffer
|
35
|
+
@data.concat(data.data)
|
36
|
+
else
|
37
|
+
data = data.to_s.dup
|
38
|
+
return if data.empty?
|
39
|
+
|
40
|
+
@data << data
|
41
|
+
end
|
42
|
+
@size += data.size
|
43
|
+
self
|
44
|
+
end
|
45
|
+
alias << add
|
46
|
+
|
47
|
+
# Places data at the front of the buffer.
|
48
|
+
#
|
49
|
+
# @param [String] data
|
50
|
+
# Data to place at the beginning of the buffer.
|
51
|
+
def unget(data)
|
52
|
+
case data
|
53
|
+
when Buffer
|
54
|
+
@data.unshift(*data.data)
|
55
|
+
else
|
56
|
+
data = data.to_s.dup
|
57
|
+
return if data.empty?
|
58
|
+
|
59
|
+
@data.unshift(data)
|
60
|
+
end
|
61
|
+
@size += data.size
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Retrieves bytes from the buffer.
|
66
|
+
#
|
67
|
+
# @param [Integer?] n
|
68
|
+
# Maximum number of bytes to fetch.
|
69
|
+
#
|
70
|
+
# @return [String]
|
71
|
+
# Data as string.
|
72
|
+
def get(n = nil)
|
73
|
+
if n.nil? || n >= size
|
74
|
+
data = @data.join
|
75
|
+
@size = 0
|
76
|
+
@data = []
|
77
|
+
else
|
78
|
+
have = 0
|
79
|
+
idx = 0
|
80
|
+
while have < n
|
81
|
+
have += @data[idx].size
|
82
|
+
idx += 1
|
83
|
+
end
|
84
|
+
data = @data.slice!(0...idx).join
|
85
|
+
if have > n
|
86
|
+
extra = data.slice!(n..-1)
|
87
|
+
@data.unshift(extra)
|
88
|
+
end
|
89
|
+
@size -= n
|
90
|
+
end
|
91
|
+
data
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
attr_reader :data
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'pwnlib/errors'
|
5
|
+
require 'pwnlib/tubes/tube'
|
6
|
+
|
7
|
+
module Pwnlib
|
8
|
+
module Tubes
|
9
|
+
# Launch a process.
|
10
|
+
class Process < Tube
|
11
|
+
# Default options for {#initialize}.
|
12
|
+
DEFAULT_OPTIONS = {
|
13
|
+
env: ENV,
|
14
|
+
in: :pipe,
|
15
|
+
out: :pipe,
|
16
|
+
raw: true,
|
17
|
+
aslr: true
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
# Instantiate a {Pwnlib::Tubes::Process} object.
|
21
|
+
#
|
22
|
+
# @param [Array<String>, String] argv
|
23
|
+
# List of arguments to pass to the spawned process.
|
24
|
+
#
|
25
|
+
# @option opts [Hash{String => String}] env (ENV)
|
26
|
+
# Environment variables. By default, inherits from Ruby's environment.
|
27
|
+
# @option opts [Symbol] in (:pipe)
|
28
|
+
# What kind of io should be used for +stdin+.
|
29
|
+
# Candidates are: +:pipe+, +:pty+.
|
30
|
+
# @option opts [Symbol] out (:pipe)
|
31
|
+
# What kind of io should be used for +stdout+.
|
32
|
+
# Candidates are: +:pipe+, +:pty+.
|
33
|
+
# See examples for more details.
|
34
|
+
# @option opts [Boolean] raw (true)
|
35
|
+
# Set the created PTY to raw mode. i.e. disable echo and control characters.
|
36
|
+
# If no pty is created, this has no effect.
|
37
|
+
# @option opts [Boolean] aslr (true)
|
38
|
+
# If +false+ is given, the ASLR of the target process will be disabled via +setarch -R+.
|
39
|
+
# @option opts [Float?] timeout (nil)
|
40
|
+
# See {Pwnlib::Tubes::Tube#initialize}.
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# io = Tubes::Process.new('ls')
|
44
|
+
# io.gets
|
45
|
+
# #=> "Gemfile\n"
|
46
|
+
#
|
47
|
+
# io = Tubes::Process.new('ls', out: :pty)
|
48
|
+
# io.gets
|
49
|
+
# #=> "Gemfile LICENSE\t\t\t README.md STYLE.md\t git-hooks pwntools.gemspec test\n"
|
50
|
+
# @example
|
51
|
+
# io = Tubes::Process.new('cat /proc/self/maps')
|
52
|
+
# io.gets
|
53
|
+
# #=> "55f8b8a10000-55f8b8a18000 r-xp 00000000 fd:00 9044035 /bin/cat\n"
|
54
|
+
# io.close
|
55
|
+
#
|
56
|
+
# io = Tubes::Process.new('cat /proc/self/maps', aslr: false)
|
57
|
+
# io.gets
|
58
|
+
# #=> "555555554000-55555555c000 r-xp 00000000 fd:00 9044035 /bin/cat\n"
|
59
|
+
# io.close
|
60
|
+
# @example
|
61
|
+
# io = Tubes::Process.new('env', env: { 'FOO' => 'BAR' })
|
62
|
+
# io.gets
|
63
|
+
# #=> "FOO=BAR\n"
|
64
|
+
def initialize(argv, **opts)
|
65
|
+
opts = DEFAULT_OPTIONS.merge(opts)
|
66
|
+
super(timeout: opts[:timeout])
|
67
|
+
argv = normalize_argv(argv, opts)
|
68
|
+
slave_i, slave_o = create_pipe(opts)
|
69
|
+
@pid = ::Process.spawn(opts[:env], *argv, in: slave_i, out: slave_o, unsetenv_others: true)
|
70
|
+
slave_i.close
|
71
|
+
slave_o.close unless slave_i == slave_o
|
72
|
+
end
|
73
|
+
|
74
|
+
# Close the IO.
|
75
|
+
#
|
76
|
+
# @param [:both, :recv, :read, :send, :write] direction
|
77
|
+
# Disallow further read/write of the process.
|
78
|
+
#
|
79
|
+
# @return [void]
|
80
|
+
def shutdown(direction = :both)
|
81
|
+
close_io(normalize_direction(direction))
|
82
|
+
end
|
83
|
+
|
84
|
+
# Kill the process.
|
85
|
+
#
|
86
|
+
# @return [void]
|
87
|
+
def kill
|
88
|
+
shutdown
|
89
|
+
::Process.kill('KILL', @pid)
|
90
|
+
::Process.wait(@pid)
|
91
|
+
end
|
92
|
+
alias close kill
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def io_out
|
97
|
+
@o
|
98
|
+
end
|
99
|
+
|
100
|
+
def close_io(dirs)
|
101
|
+
@o.close if dirs.include?(:read) && !@o.closed?
|
102
|
+
@i.close if dirs.include?(:write) && !@i.closed?
|
103
|
+
end
|
104
|
+
|
105
|
+
def normalize_argv(argv, opts)
|
106
|
+
# XXX(david942j): Set personality on child process will be better than using setarch
|
107
|
+
pre_cmd = opts[:aslr] ? '' : "setarch #{`uname -m`.strip} -R "
|
108
|
+
pre_cmd = pre_cmd.split if argv.is_a?(Array)
|
109
|
+
Array(pre_cmd + argv)
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_pipe(opts)
|
113
|
+
if [opts[:in], opts[:out]].include?(:pty)
|
114
|
+
# Require only when we need it.
|
115
|
+
# This prevents broken on Windows, which has no pty support.
|
116
|
+
require 'io/console'
|
117
|
+
require 'pty'
|
118
|
+
mpty, spty = PTY.open
|
119
|
+
mpty.raw! if opts[:raw]
|
120
|
+
end
|
121
|
+
@o, slave_o = pipe(opts[:out], mpty, spty)
|
122
|
+
slave_i, @i = pipe(opts[:in], spty, mpty)
|
123
|
+
[slave_i, slave_o]
|
124
|
+
end
|
125
|
+
|
126
|
+
# @return [(IO, IO)]
|
127
|
+
# IO pair.
|
128
|
+
def pipe(type, mst, slv)
|
129
|
+
case type
|
130
|
+
when :pipe then IO.pipe
|
131
|
+
when :pty then [mst, slv]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def send_raw(data)
|
136
|
+
@i.write(data)
|
137
|
+
rescue Errno::EIO, Errno::EPIPE, IOError
|
138
|
+
raise ::Pwnlib::Errors::EndOfTubeError
|
139
|
+
end
|
140
|
+
|
141
|
+
def recv_raw(size)
|
142
|
+
o, = IO.select([@o], [], [], @timeout)
|
143
|
+
return if o.nil?
|
144
|
+
|
145
|
+
@o.readpartial(size)
|
146
|
+
rescue Errno::EIO, Errno::EPIPE, IOError
|
147
|
+
raise ::Pwnlib::Errors::EndOfTubeError
|
148
|
+
end
|
149
|
+
|
150
|
+
def timeout_raw=(timeout)
|
151
|
+
@timeout = timeout
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'rubyserial'
|
5
|
+
|
6
|
+
require 'pwnlib/tubes/tube'
|
7
|
+
|
8
|
+
module Pwnlib
|
9
|
+
module Tubes
|
10
|
+
# @!macro [new] raise_eof
|
11
|
+
# @raise [Pwnlib::Errors::EndOfTubeError]
|
12
|
+
# If the request is not satisfied when all data is received.
|
13
|
+
|
14
|
+
# Serial Connections
|
15
|
+
class SerialTube < Tube
|
16
|
+
# Instantiate a {Pwnlib::Tubes::SerialTube} object.
|
17
|
+
#
|
18
|
+
# @param [String] port
|
19
|
+
# A device name for rubyserial to open, e.g. /dev/ttypUSB0
|
20
|
+
# @param [Integer] baudrate
|
21
|
+
# Baud rate.
|
22
|
+
# @param [Boolean] convert_newlines
|
23
|
+
# If +true+, convert any +context.newline+s to +"\\r\\n"+ before
|
24
|
+
# sending to remote. Has no effect on bytes received.
|
25
|
+
# @param [Integer] bytesize
|
26
|
+
# Serial character byte size. The '8' in '8N1'.
|
27
|
+
# @param [Symbol] parity
|
28
|
+
# Serial character parity. The 'N' in '8N1'.
|
29
|
+
def initialize(port = nil, baudrate: 115_200,
|
30
|
+
convert_newlines: true,
|
31
|
+
bytesize: 8, parity: :none)
|
32
|
+
super()
|
33
|
+
|
34
|
+
# go hunting for a port
|
35
|
+
port ||= Dir.glob('/dev/tty.usbserial*').first
|
36
|
+
port ||= '/dev/ttyUSB0'
|
37
|
+
|
38
|
+
@convert_newlines = convert_newlines
|
39
|
+
@conn = Serial.new(port, baudrate, bytesize, parity)
|
40
|
+
@serial_timer = Timer.new
|
41
|
+
end
|
42
|
+
|
43
|
+
# Closes the active connection
|
44
|
+
def close
|
45
|
+
@conn.close if @conn && !@conn.closed?
|
46
|
+
@conn = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# Implementation of the methods required for tube
|
50
|
+
private
|
51
|
+
|
52
|
+
# Gets bytes over the serial connection until some bytes are received, or
|
53
|
+
# +@timeout+ has passed. It is an error for it to return no data in less
|
54
|
+
# than +@timeout+ seconds. It is ok for it to return some data in less
|
55
|
+
# time.
|
56
|
+
#
|
57
|
+
# @param [Integer] numbytes
|
58
|
+
# An upper limit on the number of bytes to get.
|
59
|
+
#
|
60
|
+
# @return [String]
|
61
|
+
# A string containing read bytes.
|
62
|
+
#
|
63
|
+
# @!macro raise_eof
|
64
|
+
def recv_raw(numbytes)
|
65
|
+
raise ::Pwnlib::Errors::EndOfTubeError if @conn.nil?
|
66
|
+
|
67
|
+
@serial_timer.countdown do
|
68
|
+
data = ''
|
69
|
+
begin
|
70
|
+
while @serial_timer.active?
|
71
|
+
data += @conn.read(numbytes - data.length)
|
72
|
+
break unless data.empty?
|
73
|
+
|
74
|
+
sleep 0.1
|
75
|
+
end
|
76
|
+
# XXX(JonathanBeverley): should we reverse @convert_newlines here?
|
77
|
+
return data
|
78
|
+
rescue RubySerial::Error
|
79
|
+
close
|
80
|
+
raise ::Pwnlib::Errors::EndOfTubeError
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sends bytes over the serial connection. This call will block until all the bytes are sent or an error occurs.
|
86
|
+
#
|
87
|
+
# @param [String] data
|
88
|
+
# A string of the bytes to send.
|
89
|
+
#
|
90
|
+
# @return [Integer]
|
91
|
+
# The number of bytes successfully written.
|
92
|
+
#
|
93
|
+
# @!macro raise_eof
|
94
|
+
def send_raw(data)
|
95
|
+
raise ::Pwnlib::Errors::EndOfTubeError if @conn.nil?
|
96
|
+
|
97
|
+
data.gsub!(context.newline, "\r\n") if @convert_newlines
|
98
|
+
begin
|
99
|
+
@conn.write(data)
|
100
|
+
rescue RubySerial::Error
|
101
|
+
close
|
102
|
+
raise ::Pwnlib::Errors::EndOfTubeError
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Sets the +timeout+ to use for subsequent +recv_raw+ calls.
|
107
|
+
#
|
108
|
+
# @param [Float] timeout
|
109
|
+
def timeout_raw=(timeout)
|
110
|
+
@serial_timer.timeout = timeout
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|