pwntools 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,53 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/shellcraft/generators/x86/common/pushstr'
4
+ require 'pwnlib/shellcraft/generators/x86/linux/linux'
5
+ require 'pwnlib/shellcraft/generators/x86/linux/syscall'
6
+
7
+ module Pwnlib
8
+ module Shellcraft
9
+ module Generators
10
+ module X86
11
+ module Linux
12
+ # Opens a file and writes its contents to the specified file descriptor.
13
+ #
14
+ # @param [String] filename
15
+ # The filename.
16
+ # @param [Integer] fd
17
+ # The file descriptor to write the file contents.
18
+ #
19
+ # @example
20
+ # context.arch = 'amd64'
21
+ # puts shellcraft.cat('/etc/passwd')
22
+ # # /* push "/etc/passwd\x00" */
23
+ # # push 0x1010101 ^ 0x647773
24
+ # # xor dword ptr [rsp], 0x1010101
25
+ # # mov rax, 0x7361702f6374652f
26
+ # # push rax
27
+ # # /* call open("rsp", 0, "O_RDONLY") */
28
+ # # push 2 /* (SYS_open) */
29
+ # # pop rax
30
+ # # mov rdi, rsp
31
+ # # xor esi, esi /* 0 */
32
+ # # cdq /* rdx=0 */
33
+ # # syscall
34
+ # # /* call sendfile(1, "rax", 0, 2147483647) */
35
+ # # push 1
36
+ # # pop rdi
37
+ # # mov rsi, rax
38
+ # # push 0x28 /* (SYS_sendfile) */
39
+ # # pop rax
40
+ # # mov r10d, 0x7fffffff
41
+ # # cdq /* rdx=0 */
42
+ # # syscall
43
+ # #=> nil
44
+ def cat(filename, fd: 1)
45
+ abi = ::Pwnlib::ABI::ABI.syscall
46
+ cat Linux.open(filename, 'O_RDONLY')
47
+ cat Linux.syscall('SYS_sendfile', fd, abi.register_arguments.first, 0, 0x7fffffff)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/shellcraft/generators/x86/linux/linux'
4
+
5
+ module Pwnlib
6
+ module Shellcraft
7
+ module Generators
8
+ module X86
9
+ module Linux
10
+ # Exit syscall.
11
+ #
12
+ # @param [Integer] status
13
+ # Status code.
14
+ #
15
+ # @return [String]
16
+ # Assembly for invoking exit syscall.
17
+ #
18
+ # @example
19
+ # puts shellcraft.exit(1)
20
+ # # /* call exit(1) */
21
+ # # push 1 /* (SYS_exit) */
22
+ # # pop eax
23
+ # # push 1
24
+ # # pop ebx
25
+ # # int 0x80
26
+ def exit(status = 0)
27
+ cat Linux.syscall('SYS_exit', status)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/shellcraft/generators/x86/linux/linux'
4
+
5
+ module Pwnlib
6
+ module Shellcraft
7
+ module Generators
8
+ module X86
9
+ module Linux
10
+ # Push filename onto stack and perform open syscall.
11
+ #
12
+ # @param [String] filename
13
+ # The file to be opened.
14
+ # @param [String, Integer] flags
15
+ # Flags for opening a file.
16
+ # @param [Integer] mode
17
+ # If +filename+ doesn't exist and 'O_CREAT' is specified in +flags+,
18
+ # +mode+ will be used as the file permission for creating the file.
19
+ #
20
+ # @return [String]
21
+ # Assembly for syscall open.
22
+ #
23
+ # @example
24
+ # puts shellcraft.open('/etc/passwd', 'O_RDONLY')
25
+ # # /* push "/etc/passwd\x00" */
26
+ # # push 0x1010101
27
+ # # xor dword ptr [esp], 0x1657672 /* 0x1010101 ^ 0x647773 */
28
+ # # push 0x7361702f
29
+ # # push 0x6374652f
30
+ # # /* call open("esp", "O_RDONLY", 0) */
31
+ # # push 5 /* (SYS_open) */
32
+ # # pop eax
33
+ # # mov ebx, esp
34
+ # # xor ecx, ecx /* (O_RDONLY) */
35
+ # # cdq /* edx=0 */
36
+ # # int 0x80
37
+ def open(filename, flags = 'O_RDONLY', mode = 0)
38
+ abi = ::Pwnlib::ABI::ABI.syscall
39
+ cat Common.pushstr(filename)
40
+ cat Linux.syscall('SYS_open', abi.stack_pointer, flags, mode)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -3,6 +3,7 @@
3
3
  require 'singleton'
4
4
 
5
5
  require 'pwnlib/context'
6
+ require 'pwnlib/errors'
6
7
  require 'pwnlib/logger'
7
8
 
8
9
  module Pwnlib
@@ -48,8 +49,8 @@ module Pwnlib
48
49
  begin
49
50
  arch_module = ::Pwnlib::Shellcraft::Generators.const_get(context.arch.capitalize)
50
51
  rescue NameError
51
- log.error("Can't use shellcraft under architecture #{context.arch.inspect}.")
52
- return nil
52
+ raise ::Pwnlib::Errors::UnsupportedArchError,
53
+ "Can't use shellcraft under architecture #{context.arch.inspect}."
53
54
  end
54
55
  # try search in Common module
55
56
  common_module = arch_module.const_get(:Common)
@@ -3,6 +3,7 @@
3
3
  require 'time'
4
4
 
5
5
  require 'pwnlib/context'
6
+ require 'pwnlib/errors'
6
7
 
7
8
  module Pwnlib
8
9
  # A simple timer class.
@@ -27,7 +28,7 @@ module Pwnlib
27
28
  end
28
29
 
29
30
  def timeout
30
- return @timeout || Pwnlib::Context.context.timeout unless started?
31
+ return @timeout || ::Pwnlib::Context.context.timeout unless started?
31
32
  @deadline == :forever ? :forever : [@deadline - Time.now, 0].max
32
33
  end
33
34
 
@@ -46,14 +47,16 @@ module Pwnlib
46
47
  end
47
48
 
48
49
  timeout ||= @timeout
49
- timeout ||= Pwnlib::Context.context.timeout
50
+ timeout ||= ::Pwnlib::Context.context.timeout
50
51
 
51
52
  @deadline = timeout == :forever ? :forever : Time.now + timeout
52
53
 
53
54
  begin
54
55
  yield
55
56
  ensure
57
+ was_active = active?
56
58
  @deadline = nil
59
+ raise ::Pwnlib::Errors::TimeoutError unless was_active
57
60
  end
58
61
  end
59
62
  end
@@ -0,0 +1,153 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/errors'
4
+ require 'pwnlib/tubes/tube'
5
+
6
+ module Pwnlib
7
+ module Tubes
8
+ # Launch a process.
9
+ class Process < Tube
10
+ # Default options for {#initialize}.
11
+ DEFAULT_OPTIONS = {
12
+ env: ENV,
13
+ in: :pipe,
14
+ out: :pipe,
15
+ raw: true,
16
+ aslr: true
17
+ }.freeze
18
+
19
+ # Instantiate a {Pwnlib::Tubes::Process} object.
20
+ #
21
+ # @param [Array<String>, String] argv
22
+ # List of arguments to pass to the spawned process.
23
+ #
24
+ # @option opts [Hash{String => String}] env (ENV)
25
+ # Environment variables. By default, inherits from Ruby's environment.
26
+ # @option opts [Symbol] in (:pipe)
27
+ # What kind of io should be used for +stdin+.
28
+ # Candidates are: +:pipe+, +:pty+.
29
+ # @option opts [Symbol] out (:pipe)
30
+ # What kind of io should be used for +stdout+.
31
+ # Candidates are: +:pipe+, +:pty+.
32
+ # See examples for more details.
33
+ # @option opts [Boolean] raw (true)
34
+ # Set the created PTY to raw mode. i.e. disable echo and control characters.
35
+ # If no pty is created, this has no effect.
36
+ # @option opts [Boolean] aslr (true)
37
+ # If +false+ is given, the ASLR of the target process will be disabled via +setarch -R+.
38
+ # @option opts [Float?] timeout (nil)
39
+ # See {Pwnlib::Tubes::Tube#initialize}.
40
+ #
41
+ # @example
42
+ # io = Tubes::Process.new('ls')
43
+ # io.gets
44
+ # #=> "Gemfile\n"
45
+ #
46
+ # io = Tubes::Process.new('ls', out: :pty)
47
+ # io.gets
48
+ # #=> "Gemfile LICENSE\t\t\t README.md STYLE.md\t git-hooks pwntools.gemspec test\n"
49
+ # @example
50
+ # io = Tubes::Process.new('cat /proc/self/maps')
51
+ # io.gets
52
+ # #=> "55f8b8a10000-55f8b8a18000 r-xp 00000000 fd:00 9044035 /bin/cat\n"
53
+ # io.close
54
+ #
55
+ # io = Tubes::Process.new('cat /proc/self/maps', aslr: false)
56
+ # io.gets
57
+ # #=> "555555554000-55555555c000 r-xp 00000000 fd:00 9044035 /bin/cat\n"
58
+ # io.close
59
+ # @example
60
+ # io = Tubes::Process.new('env', env: { 'FOO' => 'BAR' })
61
+ # io.gets
62
+ # #=> "FOO=BAR\n"
63
+ def initialize(argv, **opts)
64
+ opts = DEFAULT_OPTIONS.merge(opts)
65
+ super(timeout: opts[:timeout])
66
+ argv = normalize_argv(argv, opts)
67
+ slave_i, slave_o = create_pipe(opts)
68
+ @pid = ::Process.spawn(opts[:env], *argv, in: slave_i, out: slave_o, unsetenv_others: true)
69
+ slave_i.close
70
+ slave_o.close unless slave_i == slave_o
71
+ end
72
+
73
+ # Close the IO.
74
+ #
75
+ # @param [:both, :recv, :read, :send, :write] direction
76
+ # Disallow further read/write of the process.
77
+ #
78
+ # @return [void]
79
+ def shutdown(direction = :both)
80
+ close_io(normalize_direction(direction))
81
+ end
82
+
83
+ # Kill the process.
84
+ #
85
+ # @return [void]
86
+ def kill
87
+ shutdown
88
+ ::Process.kill('KILL', @pid)
89
+ ::Process.wait(@pid)
90
+ end
91
+ alias close kill
92
+
93
+ private
94
+
95
+ def io_out
96
+ @o
97
+ end
98
+
99
+ def close_io(dirs)
100
+ @o.close if dirs.include?(:read) && !@o.closed?
101
+ @i.close if dirs.include?(:write) && !@i.closed?
102
+ end
103
+
104
+ def normalize_argv(argv, opts)
105
+ # XXX(david942j): Set personality on child process will be better than using setarch
106
+ pre_cmd = opts[:aslr] ? '' : "setarch #{`uname -m`.strip} -R "
107
+ pre_cmd = pre_cmd.split if argv.is_a?(Array)
108
+ Array(pre_cmd + argv)
109
+ end
110
+
111
+ def create_pipe(opts)
112
+ if [opts[:in], opts[:out]].include?(:pty)
113
+ # Require only when we need it.
114
+ # This prevents broken on Windows, which has no pty support.
115
+ require 'io/console'
116
+ require 'pty'
117
+ mpty, spty = PTY.open
118
+ mpty.raw! if opts[:raw]
119
+ end
120
+ @o, slave_o = pipe(opts[:out], mpty, spty)
121
+ slave_i, @i = pipe(opts[:in], spty, mpty)
122
+ [slave_i, slave_o]
123
+ end
124
+
125
+ # @return [(IO, IO)]
126
+ # IO pair.
127
+ def pipe(type, mst, slv)
128
+ case type
129
+ when :pipe then IO.pipe
130
+ when :pty then [mst, slv]
131
+ end
132
+ end
133
+
134
+ def send_raw(data)
135
+ @i.write(data)
136
+ rescue Errno::EIO, Errno::EPIPE, IOError
137
+ raise ::Pwnlib::Errors::EndOfTubeError
138
+ end
139
+
140
+ def recv_raw(size)
141
+ o, = IO.select([@o], [], [], @timeout)
142
+ return if o.nil?
143
+ @o.readpartial(size)
144
+ rescue Errno::EIO, Errno::EPIPE, IOError
145
+ raise ::Pwnlib::Errors::EndOfTubeError
146
+ end
147
+
148
+ def timeout_raw=(timeout)
149
+ @timeout = timeout
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,112 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'rubyserial'
4
+
5
+ require 'pwnlib/tubes/tube'
6
+
7
+ module Pwnlib
8
+ module Tubes
9
+ # @!macro [new] raise_eof
10
+ # @raise [Pwnlib::Errors::EndOfTubeError]
11
+ # If the request is not satisfied when all data is received.
12
+
13
+ # Serial Connections
14
+ class SerialTube < Tube
15
+ # Instantiate a {Pwnlib::Tubes::SerialTube} object.
16
+ #
17
+ # @param [String] port
18
+ # A device name for rubyserial to open, e.g. /dev/ttypUSB0
19
+ # @param [Integer] baudrate
20
+ # Baud rate.
21
+ # @param [Boolean] convert_newlines
22
+ # If +true+, convert any +context.newline+s to +"\\r\\n"+ before
23
+ # sending to remote. Has no effect on bytes received.
24
+ # @param [Integer] bytesize
25
+ # Serial character byte size. The '8' in '8N1'.
26
+ # @param [Symbol] parity
27
+ # Serial character parity. The 'N' in '8N1'.
28
+ def initialize(port = nil, baudrate: 115_200,
29
+ convert_newlines: true,
30
+ bytesize: 8, parity: :none)
31
+ super()
32
+
33
+ # go hunting for a port
34
+ port ||= Dir.glob('/dev/tty.usbserial*').first
35
+ port ||= '/dev/ttyUSB0'
36
+
37
+ @convert_newlines = convert_newlines
38
+ @conn = Serial.new(port, baudrate, bytesize, parity)
39
+ @serial_timer = Timer.new
40
+ end
41
+
42
+ # Closes the active connection
43
+ def close
44
+ @conn.close if @conn && !@conn.closed?
45
+ @conn = nil
46
+ end
47
+
48
+ # Implementation of the methods required for tube
49
+ private
50
+
51
+ # Gets bytes over the serial connection until some bytes are received, or
52
+ # +@timeout+ has passed. It is an error for it to return no data in less
53
+ # than +@timeout+ seconds. It is ok for it to return some data in less
54
+ # time.
55
+ #
56
+ # @param [Integer] numbytes
57
+ # An upper limit on the number of bytes to get.
58
+ #
59
+ # @return [String]
60
+ # A string containing read bytes.
61
+ #
62
+ # @!macro raise_eof
63
+ def recv_raw(numbytes)
64
+ raise ::Pwnlib::Errors::EndOfTubeError if @conn.nil?
65
+
66
+ @serial_timer.countdown do
67
+ data = ''
68
+ begin
69
+ while @serial_timer.active?
70
+ data += @conn.read(numbytes - data.length)
71
+ break unless data.empty?
72
+ sleep 0.1
73
+ end
74
+ # XXX(JonathanBeverley): should we reverse @convert_newlines here?
75
+ return data
76
+ rescue RubySerial::Error
77
+ close
78
+ raise ::Pwnlib::Errors::EndOfTubeError
79
+ end
80
+ end
81
+ end
82
+
83
+ # Sends bytes over the serial connection. This call will block until all the bytes are sent or an error occurs.
84
+ #
85
+ # @param [String] data
86
+ # A string of the bytes to send.
87
+ #
88
+ # @return [Integer]
89
+ # The number of bytes successfully written.
90
+ #
91
+ # @!macro raise_eof
92
+ def send_raw(data)
93
+ raise ::Pwnlib::Errors::EndOfTubeError if @conn.nil?
94
+
95
+ data.gsub!(context.newline, "\r\n") if @convert_newlines
96
+ begin
97
+ return @conn.write(data)
98
+ rescue RubySerial::Error
99
+ close
100
+ raise ::Pwnlib::Errors::EndOfTubeError
101
+ end
102
+ end
103
+
104
+ # Sets the +timeout+ to use for subsequent +recv_raw+ calls.
105
+ #
106
+ # @param [Float] timeout
107
+ def timeout_raw=(timeout)
108
+ @serial_timer.timeout = timeout
109
+ end
110
+ end
111
+ end
112
+ end