pwntools 0.1.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +96 -15
  3. data/Rakefile +8 -2
  4. data/lib/pwn.rb +10 -7
  5. data/lib/pwnlib/abi.rb +61 -0
  6. data/lib/pwnlib/asm.rb +357 -0
  7. data/lib/pwnlib/constants/constant.rb +19 -3
  8. data/lib/pwnlib/constants/constants.rb +46 -20
  9. data/lib/pwnlib/constants/linux/amd64.rb +32 -1
  10. data/lib/pwnlib/constants/linux/i386.rb +2 -0
  11. data/lib/pwnlib/context.rb +128 -27
  12. data/lib/pwnlib/dynelf.rb +122 -54
  13. data/lib/pwnlib/elf/elf.rb +340 -0
  14. data/lib/pwnlib/errors.rb +31 -0
  15. data/lib/pwnlib/ext/array.rb +2 -1
  16. data/lib/pwnlib/ext/helper.rb +6 -5
  17. data/lib/pwnlib/ext/integer.rb +2 -1
  18. data/lib/pwnlib/ext/string.rb +3 -2
  19. data/lib/pwnlib/logger.rb +245 -0
  20. data/lib/pwnlib/memleak.rb +59 -29
  21. data/lib/pwnlib/pwn.rb +27 -9
  22. data/lib/pwnlib/reg_sort.rb +109 -110
  23. data/lib/pwnlib/runner.rb +53 -0
  24. data/lib/pwnlib/shellcraft/generators/amd64/common/common.rb +16 -0
  25. data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +24 -0
  26. data/lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb +35 -0
  27. data/lib/pwnlib/shellcraft/generators/amd64/common/mov.rb +131 -0
  28. data/lib/pwnlib/shellcraft/generators/amd64/common/nop.rb +18 -0
  29. data/lib/pwnlib/shellcraft/generators/amd64/common/popad.rb +28 -0
  30. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb +66 -0
  31. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +24 -0
  32. data/lib/pwnlib/shellcraft/generators/amd64/common/ret.rb +33 -0
  33. data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +24 -0
  34. data/lib/pwnlib/shellcraft/generators/amd64/linux/cat.rb +24 -0
  35. data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +24 -0
  36. data/lib/pwnlib/shellcraft/generators/amd64/linux/exit.rb +24 -0
  37. data/lib/pwnlib/shellcraft/generators/amd64/linux/linux.rb +16 -0
  38. data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +24 -0
  39. data/lib/pwnlib/shellcraft/generators/amd64/linux/open.rb +24 -0
  40. data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +24 -0
  41. data/lib/pwnlib/shellcraft/generators/amd64/linux/sleep.rb +24 -0
  42. data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +24 -0
  43. data/lib/pwnlib/shellcraft/generators/helper.rb +115 -0
  44. data/lib/pwnlib/shellcraft/generators/i386/common/common.rb +16 -0
  45. data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +24 -0
  46. data/lib/pwnlib/shellcraft/generators/i386/common/memcpy.rb +34 -0
  47. data/lib/pwnlib/shellcraft/generators/i386/common/mov.rb +93 -0
  48. data/lib/pwnlib/shellcraft/generators/i386/common/nop.rb +18 -0
  49. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr.rb +41 -0
  50. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +24 -0
  51. data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +24 -0
  52. data/lib/pwnlib/shellcraft/generators/i386/linux/cat.rb +24 -0
  53. data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +24 -0
  54. data/lib/pwnlib/shellcraft/generators/i386/linux/exit.rb +24 -0
  55. data/lib/pwnlib/shellcraft/generators/i386/linux/linux.rb +16 -0
  56. data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +24 -0
  57. data/lib/pwnlib/shellcraft/generators/i386/linux/open.rb +24 -0
  58. data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +24 -0
  59. data/lib/pwnlib/shellcraft/generators/i386/linux/sleep.rb +24 -0
  60. data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +24 -0
  61. data/lib/pwnlib/shellcraft/generators/x86/common/common.rb +29 -0
  62. data/lib/pwnlib/shellcraft/generators/x86/common/infloop.rb +24 -0
  63. data/lib/pwnlib/shellcraft/generators/x86/common/memcpy.rb +17 -0
  64. data/lib/pwnlib/shellcraft/generators/x86/common/mov.rb +17 -0
  65. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr.rb +17 -0
  66. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr_array.rb +86 -0
  67. data/lib/pwnlib/shellcraft/generators/x86/common/setregs.rb +84 -0
  68. data/lib/pwnlib/shellcraft/generators/x86/linux/cat.rb +54 -0
  69. data/lib/pwnlib/shellcraft/generators/x86/linux/execve.rb +72 -0
  70. data/lib/pwnlib/shellcraft/generators/x86/linux/exit.rb +34 -0
  71. data/lib/pwnlib/shellcraft/generators/x86/linux/linux.rb +16 -0
  72. data/lib/pwnlib/shellcraft/generators/x86/linux/ls.rb +67 -0
  73. data/lib/pwnlib/shellcraft/generators/x86/linux/open.rb +47 -0
  74. data/lib/pwnlib/shellcraft/generators/x86/linux/sh.rb +53 -0
  75. data/lib/pwnlib/shellcraft/generators/x86/linux/sleep.rb +52 -0
  76. data/lib/pwnlib/shellcraft/generators/x86/linux/syscall.rb +52 -0
  77. data/lib/pwnlib/shellcraft/registers.rb +148 -0
  78. data/lib/pwnlib/shellcraft/shellcraft.rb +73 -0
  79. data/lib/pwnlib/timer.rb +67 -0
  80. data/lib/pwnlib/tubes/buffer.rb +99 -0
  81. data/lib/pwnlib/tubes/process.rb +155 -0
  82. data/lib/pwnlib/tubes/serialtube.rb +114 -0
  83. data/lib/pwnlib/tubes/sock.rb +101 -0
  84. data/lib/pwnlib/tubes/tube.rb +442 -0
  85. data/lib/pwnlib/ui.rb +21 -0
  86. data/lib/pwnlib/util/cyclic.rb +97 -94
  87. data/lib/pwnlib/util/fiddling.rb +288 -220
  88. data/lib/pwnlib/util/getdents.rb +85 -0
  89. data/lib/pwnlib/util/hexdump.rb +116 -112
  90. data/lib/pwnlib/util/lists.rb +58 -0
  91. data/lib/pwnlib/util/packing.rb +223 -228
  92. data/lib/pwnlib/util/ruby.rb +19 -0
  93. data/lib/pwnlib/version.rb +3 -1
  94. data/test/abi_test.rb +22 -0
  95. data/test/asm_test.rb +177 -0
  96. data/test/constants/constant_test.rb +2 -0
  97. data/test/constants/constants_test.rb +5 -2
  98. data/test/context_test.rb +14 -3
  99. data/test/data/assembly/aarch64.s +19 -0
  100. data/test/data/assembly/amd64.s +21 -0
  101. data/test/data/assembly/arm.s +9 -0
  102. data/test/data/assembly/i386.s +21 -0
  103. data/test/data/assembly/mips.s +16 -0
  104. data/test/data/assembly/mips64.s +6 -0
  105. data/test/data/assembly/powerpc.s +18 -0
  106. data/test/data/assembly/powerpc64.s +36 -0
  107. data/test/data/assembly/sparc.s +33 -0
  108. data/test/data/assembly/sparc64.s +5 -0
  109. data/test/data/assembly/thumb.s +37 -0
  110. data/test/data/echo.rb +16 -0
  111. data/test/data/elfs/Makefile +24 -0
  112. data/test/data/elfs/amd64.frelro.elf +0 -0
  113. data/test/data/elfs/amd64.frelro.pie.elf +0 -0
  114. data/test/data/elfs/amd64.nrelro.elf +0 -0
  115. data/test/data/elfs/amd64.prelro.elf +0 -0
  116. data/test/data/elfs/amd64.static.elf +0 -0
  117. data/test/data/elfs/i386.frelro.pie.elf +0 -0
  118. data/test/data/elfs/i386.prelro.elf +0 -0
  119. data/test/data/elfs/source.cpp +19 -0
  120. data/test/data/flag +1 -0
  121. data/test/data/lib32/ld.so.2 +0 -0
  122. data/test/data/lib32/libc.so.6 +0 -0
  123. data/test/data/lib64/ld.so.2 +0 -0
  124. data/test/data/lib64/libc.so.6 +0 -0
  125. data/test/dynelf_test.rb +62 -25
  126. data/test/elf/elf_test.rb +147 -0
  127. data/test/ext_test.rb +4 -2
  128. data/test/files/use_pwn.rb +3 -6
  129. data/test/files/use_pwnlib.rb +2 -1
  130. data/test/full_file_test.rb +6 -0
  131. data/test/logger_test.rb +120 -0
  132. data/test/memleak_test.rb +5 -33
  133. data/test/reg_sort_test.rb +4 -1
  134. data/test/runner_test.rb +32 -0
  135. data/test/shellcraft/infloop_test.rb +27 -0
  136. data/test/shellcraft/linux/cat_test.rb +87 -0
  137. data/test/shellcraft/linux/ls_test.rb +109 -0
  138. data/test/shellcraft/linux/sh_test.rb +120 -0
  139. data/test/shellcraft/linux/sleep_test.rb +68 -0
  140. data/test/shellcraft/linux/syscalls/execve_test.rb +137 -0
  141. data/test/shellcraft/linux/syscalls/exit_test.rb +57 -0
  142. data/test/shellcraft/linux/syscalls/open_test.rb +87 -0
  143. data/test/shellcraft/linux/syscalls/syscall_test.rb +84 -0
  144. data/test/shellcraft/memcpy_test.rb +50 -0
  145. data/test/shellcraft/mov_test.rb +99 -0
  146. data/test/shellcraft/nop_test.rb +27 -0
  147. data/test/shellcraft/popad_test.rb +30 -0
  148. data/test/shellcraft/pushstr_array_test.rb +92 -0
  149. data/test/shellcraft/pushstr_test.rb +109 -0
  150. data/test/shellcraft/registers_test.rb +33 -0
  151. data/test/shellcraft/ret_test.rb +31 -0
  152. data/test/shellcraft/setregs_test.rb +63 -0
  153. data/test/shellcraft/shellcraft_test.rb +30 -0
  154. data/test/test_helper.rb +61 -2
  155. data/test/timer_test.rb +42 -0
  156. data/test/tubes/buffer_test.rb +46 -0
  157. data/test/tubes/process_test.rb +105 -0
  158. data/test/tubes/serialtube_test.rb +162 -0
  159. data/test/tubes/sock_test.rb +68 -0
  160. data/test/tubes/tube_test.rb +320 -0
  161. data/test/ui_test.rb +18 -0
  162. data/test/util/cyclic_test.rb +3 -1
  163. data/test/util/fiddling_test.rb +12 -3
  164. data/test/util/getdents_test.rb +33 -0
  165. data/test/util/hexdump_test.rb +9 -10
  166. data/test/util/lists_test.rb +22 -0
  167. data/test/util/packing_test.rb +5 -3
  168. 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
@@ -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