pwntools 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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