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
@@ -71,9 +71,7 @@ module Pwnlib
71
71
  end
72
72
  else
73
73
  if is_all
74
- if number < 0
75
- raise ArgumentError, "Can't pack negative number with bits='all' and signed=false"
76
- end
74
+ raise ArgumentError, "Can't pack negative number with bits='all' and signed=false" if number < 0
77
75
  bits = number.zero? ? 8 : ((number.bit_length - 1) | 7) + 1
78
76
  end
79
77
 
@@ -136,9 +134,7 @@ module Pwnlib
136
134
  signed = context.signed
137
135
  bytes = (bits + 7) / 8
138
136
 
139
- unless data.size == bytes
140
- raise ArgumentError, "data.size=#{data.size} does not match with bits=#{bits}"
141
- end
137
+ raise ArgumentError, "data.size=#{data.size} does not match with bits=#{bits}" unless data.size == bytes
142
138
 
143
139
  data = data.reverse if endian == 'little'
144
140
  data = data.unpack('C*')
@@ -200,9 +196,7 @@ module Pwnlib
200
196
 
201
197
  bytes = bits / 8
202
198
 
203
- if data.size % bytes != 0
204
- raise ArgumentError, "data.size=#{data.size} must be a multiple of bytes=#{bytes}"
205
- end
199
+ raise ArgumentError, "data.size=#{data.size} must be a multiple of bytes=#{bytes}" if data.size % bytes != 0
206
200
  ret = []
207
201
  (data.size / bytes).times do |idx|
208
202
  x1 = idx * bytes
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Pwnlib
4
4
  # version of pwntools-ruby
5
- VERSION = '1.0.1'.freeze
5
+ VERSION = '1.1.0'.freeze
6
6
  end
@@ -10,11 +10,19 @@ class AsmTest < MiniTest::Test
10
10
  Asm = ::Pwnlib::Asm
11
11
 
12
12
  def setup
13
- skip 'Not test asm/disasm on Windows' if TTY::Platform.new.windows?
14
13
  @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance
15
14
  end
16
15
 
16
+ def skip_windows
17
+ skip 'Not test asm/disasm on Windows' if TTY::Platform.new.windows?
18
+ end
19
+
20
+ def linux_only
21
+ skip 'ELF can only be executed on Linux' unless TTY::Platform.new.linux?
22
+ end
23
+
17
24
  def test_i386_asm
25
+ skip_windows
18
26
  context.local(arch: 'i386') do
19
27
  assert_equal("\x90", Asm.asm('nop'))
20
28
  assert_equal("\xeb\xfe", Asm.asm(@shellcraft.infloop))
@@ -26,6 +34,7 @@ class AsmTest < MiniTest::Test
26
34
  end
27
35
 
28
36
  def test_amd64_asm
37
+ skip_windows
29
38
  context.local(arch: 'amd64') do
30
39
  assert_equal("\x90", Asm.asm('nop'))
31
40
  assert_equal("\xeb\xfe", Asm.asm(@shellcraft.infloop))
@@ -36,6 +45,7 @@ class AsmTest < MiniTest::Test
36
45
  end
37
46
 
38
47
  def test_i386_disasm
48
+ skip_windows
39
49
  context.local(arch: 'i386') do
40
50
  str = Asm.disasm("h\x01\x01\x01\x01\x814$ri\x01\x011\xd2"\
41
51
  "Rj\x04Z\x01\xe2R\x89\xe2jhh///sh/binj\x0bX\x89\xe3\x89\xd1\x99\xcd\x80")
@@ -66,6 +76,7 @@ class AsmTest < MiniTest::Test
66
76
  end
67
77
 
68
78
  def test_amd64_disasm
79
+ skip_windows
69
80
  context.local(arch: 'amd64') do
70
81
  str = Asm.disasm("hri\x01\x01\x814$\x01\x01\x01\x011\xd2" \
71
82
  "Rj\x08ZH\x01\xe2RH\x89\xe2jhH\xb8/bin///sPj;XH\x89\xe7H\x89\xd6\x99\x0f\x05", vma: 0xfff)
@@ -98,7 +109,79 @@ class AsmTest < MiniTest::Test
98
109
 
99
110
  # To ensure coverage
100
111
  def test_require
101
- err = assert_raises(LoadError) { Asm.__send__(:require_message, 'no_such_lib', 'meow') }
112
+ err = assert_raises(::Pwnlib::Errors::DependencyError) do
113
+ Asm.__send__(:require_message, 'no_such_lib', 'meow')
114
+ end
102
115
  assert_match(/meow/, err.message)
103
116
  end
117
+
118
+ def make_elf_file(*args)
119
+ elf = Asm.make_elf(*args)
120
+ stream = StringIO.new(elf)
121
+ [elf, ::ELFTools::ELFFile.new(stream)]
122
+ end
123
+
124
+ def test_make_elf
125
+ # create ELF and use ELFTools to test it
126
+ data = 'not important'
127
+ elf, elf_file = make_elf_file(data)
128
+ assert_equal('Intel 80386', elf_file.machine)
129
+ # check entry point contains data
130
+ assert_equal(data, elf[elf_file.header.e_entry - 0x08048000, data.size])
131
+ segment = elf_file.segments.first
132
+ assert(segment.readable? && segment.executable?)
133
+
134
+ # test to_file
135
+ temp_path = Asm.make_elf(data, to_file: true)
136
+ assert_equal(elf, IO.binread(temp_path))
137
+ File.unlink(temp_path)
138
+
139
+ # test block form
140
+ temp_path = Asm.make_elf(data) do |path|
141
+ assert(File.file?(path))
142
+ path
143
+ end
144
+ # check file removed
145
+ refute(File.exist?(temp_path))
146
+
147
+ # test vma
148
+ custom_base = 0x1234000
149
+ _, elf_file = make_elf_file(data, vma: custom_base)
150
+ assert_equal(custom_base, elf_file.segments.first.header.p_vaddr)
151
+
152
+ context.local(arch: :arm) do
153
+ _, elf_file = make_elf_file(data)
154
+ assert_equal(32, elf_file.header.elf_class)
155
+ assert_equal('ARM', elf_file.machine)
156
+ assert_equal(0x8000, elf_file.segments.first.header.p_vaddr)
157
+ end
158
+
159
+ context.local(arch: :vax) do
160
+ err = assert_raises(::Pwnlib::Errors::UnsupportedArchError) do
161
+ Asm.make_elf('')
162
+ end
163
+ assert_equal('Unknown machine type of architecture "vax".', err.message)
164
+ end
165
+ end
166
+
167
+ # this test can be removed after method +run_shellcode+ being implemented
168
+ def test_make_elf_and_run
169
+ # run the ELF we created to make sure it works.
170
+ linux_only
171
+
172
+ # test supported architecture
173
+ {
174
+ i386: /08048000-08049000 r-xp/,
175
+ amd64: /00400000-00401000 r-xp/
176
+ }.each do |arch, regexp|
177
+ context.local(arch: arch) do
178
+ data = Asm.asm(@shellcraft.cat('/proc/self/maps') + @shellcraft.syscall('SYS_exit', 0))
179
+ Asm.make_elf(data) do |path|
180
+ Open3.popen2(path) do |_i, o, _t|
181
+ assert_match(regexp, o.gets)
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
104
187
  end
@@ -15,8 +15,8 @@ class ConstantsTest < MiniTest::Test
15
15
  assert_equal('__NR_arch_prctl', Constants.__NR_arch_prctl.to_s)
16
16
  assert_equal('Constant("(O_CREAT)", 0x40)', Constants.eval('O_CREAT').inspect)
17
17
  assert_equal('Constant("(O_CREAT | O_WRONLY)", 0x41)', Constants.eval('O_CREAT | O_WRONLY').inspect)
18
- err = assert_raises(NameError) { Constants.eval('rax') }
19
- assert_equal('no value provided for variables: rax', err.message)
18
+ err = assert_raises(::Pwnlib::Errors::ConstantNotFoundError) { Constants.eval('rax + rbx') }
19
+ assert_equal('Undefined constant(s): rax, rbx', err.message)
20
20
  end
21
21
  end
22
22
 
@@ -2,14 +2,9 @@
2
2
 
3
3
  require 'socket'
4
4
 
5
- if ARGV.empty?
6
- puts "Usage #{__FILE__} port"
7
- exit 1
8
- end
5
+ server = TCPServer.open('127.0.0.1', 0)
9
6
 
10
- server = TCPServer.open('127.0.0.1', ARGV[0].to_i)
11
-
12
- STDOUT.puts 'Start!'
7
+ STDOUT.puts "Start with port #{server.addr[1]}"
13
8
  STDOUT.flush
14
9
 
15
10
  client = server.accept
@@ -6,11 +6,13 @@ require 'pwnlib/elf/elf'
6
6
  require 'pwnlib/logger'
7
7
 
8
8
  class ELFTest < MiniTest::Test
9
- include ::Pwnlib::Logger
10
-
11
9
  def setup
12
10
  @path_of = ->(file) { File.join(__dir__, '..', 'data', 'elfs', file) }
13
- @elf = ::Pwnlib::ELF::ELF.new(@path_of.call('i386.prelro.elf'), checksec: false)
11
+ @elf = to_elf_silent('i386.prelro.elf')
12
+ end
13
+
14
+ def to_elf_silent(filename)
15
+ log_null { ::Pwnlib::ELF::ELF.new(@path_of.call(filename), checksec: false) }
14
16
  end
15
17
 
16
18
  # check stdout when loaded
@@ -43,7 +45,7 @@ NX: NX enabled
43
45
  PIE: No PIE (0x8048000)
44
46
  EOS
45
47
 
46
- nrelro_elf = ::Pwnlib::ELF::ELF.new(@path_of.call('amd64.nrelro.elf'), checksec: false)
48
+ nrelro_elf = to_elf_silent('amd64.nrelro.elf')
47
49
  assert_equal(<<-EOS.strip, nrelro_elf.checksec)
48
50
  RELRO: No RELRO
49
51
  Stack: Canary found
@@ -51,7 +53,7 @@ NX: NX enabled
51
53
  PIE: No PIE (0x400000)
52
54
  EOS
53
55
 
54
- frelro_elf = ::Pwnlib::ELF::ELF.new(@path_of.call('amd64.frelro.elf'), checksec: false)
56
+ frelro_elf = to_elf_silent('amd64.frelro.elf')
55
57
  assert_equal(<<-EOS.strip, frelro_elf.checksec)
56
58
  RELRO: Full RELRO
57
59
  Stack: Canary found
@@ -78,7 +80,7 @@ PIE: No PIE (0x400000)
78
80
  assert_same(0x80483b0, @elf.plt.printf)
79
81
  assert_same(0x80483f0, @elf.plt[:scanf])
80
82
 
81
- elf = ::Pwnlib::ELF::ELF.new(@path_of.call('amd64.frelro.pie.elf'), checksec: false)
83
+ elf = to_elf_silent('amd64.frelro.pie.elf')
82
84
  assert_nil(elf.plt)
83
85
  end
84
86
 
@@ -90,7 +92,7 @@ PIE: No PIE (0x400000)
90
92
  @elf.address = new_address
91
93
  assert_equal(old_main - old_address + new_address, @elf.symbols.main)
92
94
 
93
- elf = ::Pwnlib::ELF::ELF.new(@path_of.call('i386.frelro.pie.elf'), checksec: false)
95
+ elf = to_elf_silent('i386.frelro.pie.elf')
94
96
  assert_equal(0, elf.address)
95
97
  assert_same(0x6c2, elf.symbols.main)
96
98
  elf.address = 0xdeadbeef0000
@@ -99,7 +101,7 @@ PIE: No PIE (0x400000)
99
101
  end
100
102
 
101
103
  def test_static
102
- elf = ::Pwnlib::ELF::ELF.new(@path_of.call('amd64.static.elf'), checksec: false)
104
+ elf = to_elf_silent('amd64.static.elf')
103
105
  assert_equal(<<-EOS.strip, elf.checksec)
104
106
  RELRO: Partial RELRO
105
107
  Stack: Canary found
@@ -124,11 +126,4 @@ PIE: No PIE (0x400000)
124
126
  assert_equal([0x1234001, 0x1392613], elf.search('ELF').to_a)
125
127
  assert_equal(0x138d00b, elf.find('/bin/sh').next)
126
128
  end
127
-
128
- def log_stdout
129
- old = log.instance_variable_get(:@logdev)
130
- log.instance_variable_set(:@logdev, $stdout)
131
- yield
132
- log.instance_variable_set(:@logdev, old)
133
- end
134
129
  end
@@ -8,12 +8,8 @@ require 'pwn'
8
8
  context[arch: 'amd64']
9
9
 
10
10
  raise 'pack fail' unless pack(1) == "\x01\0\0\0\0\0\0\0"
11
- unless ::Pwnlib::Util::Fiddling.__send__(:context).object_id == context.object_id
12
- raise 'not unique context'
13
- end
14
- unless ::Pwnlib::Context.context.object_id == context.object_id
15
- raise 'not unique context'
16
- end
11
+ raise 'not unique context' unless ::Pwnlib::Util::Fiddling.__send__(:context).object_id == context.object_id
12
+ raise 'not unique context' unless ::Pwnlib::Context.context.object_id == context.object_id
17
13
 
18
14
  # Make sure things aren't polluting Object
19
15
  begin
@@ -58,4 +58,42 @@ class LoggerTest < MiniTest::Test
58
58
  meowmeowmeow
59
59
  EOS
60
60
  end
61
+
62
+ def test_dump
63
+ x = 2
64
+ y = 3
65
+ assert_equal(<<-EOS, @logger.dump(x + y, x * y))
66
+ [DUMP] (x + y) = 5, (x * y) = 6
67
+ EOS
68
+
69
+ libc = 0x7fc0bdd13000
70
+ # check if source code parsing works good
71
+ msg = @logger.dump(
72
+ libc # comment is ok
73
+ .to_s(16),
74
+ libc - libc
75
+ )
76
+ assert_equal(<<-EOS, msg)
77
+ [DUMP] libc.to_s(16) = "7fc0bdd13000", (libc - libc) = 0
78
+ EOS
79
+
80
+ libc = 0x7fc0bdd13000
81
+ assert_equal(<<-EOS, @logger.dump { libc.to_s(16) })
82
+ [DUMP] libc.to_s(16) = "7fc0bdd13000"
83
+ EOS
84
+
85
+ res = @logger.dump do
86
+ libc = 12_345_678
87
+ libc <<= 12
88
+ # comments will be ignored
89
+ libc.to_s # dummy line
90
+ libc.to_s(16)
91
+ end
92
+ assert_equal(<<-EOS, res)
93
+ [DUMP] libc = 12345678
94
+ libc = (libc << 12)
95
+ libc.to_s
96
+ libc.to_s(16) = "bc614e000"
97
+ EOS
98
+ end
61
99
  end
@@ -0,0 +1,86 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'test_helper'
4
+
5
+ require 'pwnlib/context'
6
+ require 'pwnlib/shellcraft/shellcraft'
7
+
8
+ class CatTest < MiniTest::Test
9
+ include ::Pwnlib::Context
10
+
11
+ def setup
12
+ @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance
13
+ end
14
+
15
+ def test_amd64
16
+ context.local(arch: 'amd64') do
17
+ assert_equal(<<-'EOS', @shellcraft.cat('flag'))
18
+ /* push "flag\x00" */
19
+ push 0x67616c66
20
+ /* call open("rsp", "O_RDONLY", 0) */
21
+ push 2 /* (SYS_open) */
22
+ pop rax
23
+ mov rdi, rsp
24
+ xor esi, esi /* (O_RDONLY) */
25
+ cdq /* rdx=0 */
26
+ syscall
27
+ /* call sendfile(1, "rax", 0, 2147483647) */
28
+ push 1
29
+ pop rdi
30
+ mov rsi, rax
31
+ push 0x28 /* (SYS_sendfile) */
32
+ pop rax
33
+ mov r10d, 0x7fffffff
34
+ cdq /* rdx=0 */
35
+ syscall
36
+ EOS
37
+ assert_equal(<<-'EOS', @shellcraft.cat('flag', fd: 2))
38
+ /* push "flag\x00" */
39
+ push 0x67616c66
40
+ /* call open("rsp", "O_RDONLY", 0) */
41
+ push 2 /* (SYS_open) */
42
+ pop rax
43
+ mov rdi, rsp
44
+ xor esi, esi /* (O_RDONLY) */
45
+ cdq /* rdx=0 */
46
+ syscall
47
+ /* call sendfile(2, "rax", 0, 2147483647) */
48
+ push 2
49
+ pop rdi
50
+ mov rsi, rax
51
+ push 0x28 /* (SYS_sendfile) */
52
+ pop rax
53
+ mov r10d, 0x7fffffff
54
+ cdq /* rdx=0 */
55
+ syscall
56
+ EOS
57
+ end
58
+ end
59
+
60
+ def test_i386
61
+ context.local(arch: 'i386') do
62
+ assert_equal(<<-'EOS', @shellcraft.cat('flag'))
63
+ /* push "flag\x00" */
64
+ push 1
65
+ dec byte ptr [esp]
66
+ push 0x67616c66
67
+ /* call open("esp", "O_RDONLY", 0) */
68
+ push 5 /* (SYS_open) */
69
+ pop eax
70
+ mov ebx, esp
71
+ xor ecx, ecx /* (O_RDONLY) */
72
+ cdq /* edx=0 */
73
+ int 0x80
74
+ /* call sendfile(1, "eax", 0, 2147483647) */
75
+ push 1
76
+ pop ebx
77
+ mov ecx, eax
78
+ xor eax, eax
79
+ mov al, 0xbb /* (SYS_sendfile) */
80
+ mov esi, 0x7fffffff
81
+ cdq /* edx=0 */
82
+ int 0x80
83
+ EOS
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'test_helper'
4
+
5
+ require 'pwnlib/context'
6
+ require 'pwnlib/shellcraft/shellcraft'
7
+
8
+ class ExitTest < MiniTest::Test
9
+ include ::Pwnlib::Context
10
+
11
+ def setup
12
+ @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance
13
+ end
14
+
15
+ def test_amd64
16
+ context.local(arch: 'amd64') do
17
+ assert_equal(<<-'EOS', @shellcraft.exit)
18
+ /* call exit(0) */
19
+ push 0x3c /* (SYS_exit) */
20
+ pop rax
21
+ xor edi, edi /* 0 */
22
+ syscall
23
+ EOS
24
+
25
+ assert_equal(<<-'EOS', @shellcraft.exit(1))
26
+ /* call exit(1) */
27
+ push 0x3c /* (SYS_exit) */
28
+ pop rax
29
+ push 1
30
+ pop rdi
31
+ syscall
32
+ EOS
33
+ end
34
+ end
35
+
36
+ def test_i386
37
+ context.local(arch: 'i386') do
38
+ assert_equal(<<-'EOS', @shellcraft.exit)
39
+ /* call exit(0) */
40
+ push 1 /* (SYS_exit) */
41
+ pop eax
42
+ xor ebx, ebx /* 0 */
43
+ int 0x80
44
+ EOS
45
+
46
+ assert_equal(<<-'EOS', @shellcraft.exit(1))
47
+ /* call exit(1) */
48
+ push 1 /* (SYS_exit) */
49
+ pop eax
50
+ push 1
51
+ pop ebx
52
+ int 0x80
53
+ EOS
54
+ end
55
+ end
56
+ end