pwntools 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -11
  3. data/Rakefile +5 -1
  4. data/lib/pwn.rb +9 -7
  5. data/lib/pwnlib/abi.rb +60 -0
  6. data/lib/pwnlib/asm.rb +146 -0
  7. data/lib/pwnlib/constants/constant.rb +16 -2
  8. data/lib/pwnlib/constants/constants.rb +35 -19
  9. data/lib/pwnlib/constants/linux/amd64.rb +30 -1
  10. data/lib/pwnlib/context.rb +25 -17
  11. data/lib/pwnlib/dynelf.rb +117 -54
  12. data/lib/pwnlib/elf/elf.rb +267 -0
  13. data/lib/pwnlib/ext/helper.rb +4 -4
  14. data/lib/pwnlib/logger.rb +87 -0
  15. data/lib/pwnlib/memleak.rb +58 -29
  16. data/lib/pwnlib/pwn.rb +19 -8
  17. data/lib/pwnlib/reg_sort.rb +102 -108
  18. data/lib/pwnlib/shellcraft/generators/amd64/common/common.rb +14 -0
  19. data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +17 -0
  20. data/lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb +31 -0
  21. data/lib/pwnlib/shellcraft/generators/amd64/common/mov.rb +127 -0
  22. data/lib/pwnlib/shellcraft/generators/amd64/common/nop.rb +16 -0
  23. data/lib/pwnlib/shellcraft/generators/amd64/common/popad.rb +27 -0
  24. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb +64 -0
  25. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +19 -0
  26. data/lib/pwnlib/shellcraft/generators/amd64/common/ret.rb +32 -0
  27. data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +19 -0
  28. data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +21 -0
  29. data/lib/pwnlib/shellcraft/generators/amd64/linux/linux.rb +14 -0
  30. data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +19 -0
  31. data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +19 -0
  32. data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +21 -0
  33. data/lib/pwnlib/shellcraft/generators/helper.rb +106 -0
  34. data/lib/pwnlib/shellcraft/generators/i386/common/common.rb +14 -0
  35. data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +17 -0
  36. data/lib/pwnlib/shellcraft/generators/i386/common/mov.rb +90 -0
  37. data/lib/pwnlib/shellcraft/generators/i386/common/nop.rb +16 -0
  38. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr.rb +39 -0
  39. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +19 -0
  40. data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +19 -0
  41. data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +19 -0
  42. data/lib/pwnlib/shellcraft/generators/i386/linux/linux.rb +14 -0
  43. data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +19 -0
  44. data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +19 -0
  45. data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +19 -0
  46. data/lib/pwnlib/shellcraft/generators/x86/common/common.rb +26 -0
  47. data/lib/pwnlib/shellcraft/generators/x86/common/infloop.rb +22 -0
  48. data/lib/pwnlib/shellcraft/generators/x86/common/mov.rb +15 -0
  49. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr.rb +15 -0
  50. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr_array.rb +85 -0
  51. data/lib/pwnlib/shellcraft/generators/x86/common/setregs.rb +82 -0
  52. data/lib/pwnlib/shellcraft/generators/x86/linux/execve.rb +69 -0
  53. data/lib/pwnlib/shellcraft/generators/x86/linux/linux.rb +14 -0
  54. data/lib/pwnlib/shellcraft/generators/x86/linux/ls.rb +66 -0
  55. data/lib/pwnlib/shellcraft/generators/x86/linux/sh.rb +52 -0
  56. data/lib/pwnlib/shellcraft/generators/x86/linux/syscall.rb +52 -0
  57. data/lib/pwnlib/shellcraft/registers.rb +145 -0
  58. data/lib/pwnlib/shellcraft/shellcraft.rb +67 -0
  59. data/lib/pwnlib/timer.rb +60 -0
  60. data/lib/pwnlib/tubes/buffer.rb +96 -0
  61. data/lib/pwnlib/tubes/sock.rb +95 -0
  62. data/lib/pwnlib/tubes/tube.rb +270 -0
  63. data/lib/pwnlib/util/cyclic.rb +95 -94
  64. data/lib/pwnlib/util/fiddling.rb +256 -220
  65. data/lib/pwnlib/util/getdents.rb +83 -0
  66. data/lib/pwnlib/util/hexdump.rb +109 -108
  67. data/lib/pwnlib/util/lists.rb +55 -0
  68. data/lib/pwnlib/util/packing.rb +226 -228
  69. data/lib/pwnlib/util/ruby.rb +18 -0
  70. data/lib/pwnlib/version.rb +2 -1
  71. data/test/abi_test.rb +21 -0
  72. data/test/asm_test.rb +104 -0
  73. data/test/constants/constant_test.rb +1 -0
  74. data/test/constants/constants_test.rb +4 -2
  75. data/test/context_test.rb +1 -0
  76. data/test/data/echo.rb +20 -0
  77. data/test/data/elfs/Makefile +22 -0
  78. data/test/data/elfs/amd64.frelro.elf +0 -0
  79. data/test/data/elfs/amd64.frelro.pie.elf +0 -0
  80. data/test/data/elfs/amd64.nrelro.elf +0 -0
  81. data/test/data/elfs/amd64.prelro.elf +0 -0
  82. data/test/data/elfs/i386.frelro.pie.elf +0 -0
  83. data/test/data/elfs/i386.prelro.elf +0 -0
  84. data/test/data/elfs/source.cpp +19 -0
  85. data/test/data/flag +1 -0
  86. data/test/data/lib32/ld.so.2 +0 -0
  87. data/test/data/lib32/libc.so.6 +0 -0
  88. data/test/data/lib64/ld.so.2 +0 -0
  89. data/test/data/lib64/libc.so.6 +0 -0
  90. data/test/dynelf_test.rb +59 -24
  91. data/test/elf/elf_test.rb +120 -0
  92. data/test/ext_test.rb +3 -2
  93. data/test/files/use_pwnlib.rb +1 -1
  94. data/test/logger_test.rb +61 -0
  95. data/test/memleak_test.rb +4 -33
  96. data/test/reg_sort_test.rb +3 -1
  97. data/test/shellcraft/infloop_test.rb +26 -0
  98. data/test/shellcraft/linux/ls_test.rb +108 -0
  99. data/test/shellcraft/linux/sh_test.rb +119 -0
  100. data/test/shellcraft/linux/syscalls/execve_test.rb +136 -0
  101. data/test/shellcraft/linux/syscalls/syscall_test.rb +83 -0
  102. data/test/shellcraft/memcpy_test.rb +35 -0
  103. data/test/shellcraft/mov_test.rb +98 -0
  104. data/test/shellcraft/nop_test.rb +26 -0
  105. data/test/shellcraft/popad_test.rb +29 -0
  106. data/test/shellcraft/pushstr_array_test.rb +91 -0
  107. data/test/shellcraft/pushstr_test.rb +108 -0
  108. data/test/shellcraft/registers_test.rb +32 -0
  109. data/test/shellcraft/ret_test.rb +30 -0
  110. data/test/shellcraft/setregs_test.rb +62 -0
  111. data/test/shellcraft/shellcraft_test.rb +28 -0
  112. data/test/test_helper.rb +12 -1
  113. data/test/timer_test.rb +23 -0
  114. data/test/tubes/buffer_test.rb +45 -0
  115. data/test/tubes/sock_test.rb +68 -0
  116. data/test/tubes/tube_test.rb +241 -0
  117. data/test/util/cyclic_test.rb +2 -1
  118. data/test/util/fiddling_test.rb +2 -1
  119. data/test/util/getdents_test.rb +32 -0
  120. data/test/util/hexdump_test.rb +7 -9
  121. data/test/util/lists_test.rb +21 -0
  122. data/test/util/packing_test.rb +4 -3
  123. metadata +215 -25
@@ -0,0 +1,21 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/shellcraft/generators/amd64/linux/linux'
4
+ require 'pwnlib/shellcraft/generators/x86/linux/syscall'
5
+
6
+ module Pwnlib
7
+ module Shellcraft
8
+ module Generators
9
+ module Amd64
10
+ module Linux
11
+ # See {Generators::X86::Linux#syscall}.
12
+ def syscall(*arguments)
13
+ context.local(arch: 'amd64') do
14
+ cat X86::Linux.syscall(*arguments)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,106 @@
1
+ require 'pwnlib/abi'
2
+ require 'pwnlib/constants/constants'
3
+ require 'pwnlib/context'
4
+ require 'pwnlib/reg_sort'
5
+ require 'pwnlib/shellcraft/registers'
6
+ require 'pwnlib/util/fiddling'
7
+ require 'pwnlib/util/lists'
8
+ require 'pwnlib/util/packing'
9
+
10
+ module Pwnlib
11
+ module Shellcraft
12
+ module Generators
13
+ # Define methods for generator modules.
14
+ #
15
+ # This module must and can only be extended by Common or Linux module under {Generators}.
16
+ module Helper
17
+ # Provide a 'sandbox' for generators.
18
+ class Runner
19
+ class << self
20
+ def label_num
21
+ @label_num ||= 0
22
+ @label_num += 1
23
+ end
24
+ end
25
+
26
+ def clear
27
+ @_output = StringIO.new
28
+ end
29
+
30
+ # Indent each line 2 spaces.
31
+ def typesetting
32
+ indent = @_output.string.lines.map do |line|
33
+ next line.strip + "\n" if label_str?(line.strip)
34
+ line == "\n" ? line : ' ' * 2 + line.lstrip
35
+ end
36
+ indent.join
37
+ end
38
+
39
+ private
40
+
41
+ def cat(str)
42
+ @_output.puts(str)
43
+ end
44
+
45
+ # @example
46
+ # get_label('infloop') #=> 'infloop_1'
47
+ def get_label(str)
48
+ "#{str}_#{self.class.label_num}"
49
+ end
50
+
51
+ def okay(s, *a, **kw)
52
+ s = pack(s, *a, **kw) if s.is_a?(Integer)
53
+ !(s.include?("\x00") || s.include?("\n"))
54
+ end
55
+
56
+ def evaluate(item)
57
+ return item if register?(item)
58
+ Constants.eval(item)
59
+ end
60
+
61
+ # @param [Constants::Constant, String, Integer] n
62
+ def pretty(n)
63
+ case n
64
+ when Constants::Constant
65
+ format('%s /* %s */', pretty(n.to_i), n)
66
+ when Integer
67
+ n.abs < 10 ? n.to_s : hex(n)
68
+ else
69
+ n.inspect
70
+ end
71
+ end
72
+
73
+ def label_str?(str)
74
+ str.match(/\A\w+_\d+:\Z/) != nil
75
+ end
76
+
77
+ include ::Pwnlib::Context
78
+ include ::Pwnlib::RegSort
79
+ include ::Pwnlib::Shellcraft::Registers
80
+ include ::Pwnlib::Util::Fiddling
81
+ include ::Pwnlib::Util::Lists
82
+ include ::Pwnlib::Util::Packing
83
+ end
84
+
85
+ class << self
86
+ # Define a corresponding singleton method whenever an instance method is defined.
87
+ def extended(mod)
88
+ class << mod
89
+ define_method(:method_added) do |m|
90
+ # Define singleton methods, so we can invoke +Generators::X86::Common.pushstr_array+.
91
+ # Each method runs in an independent 'runner', so methods would not effect each other.
92
+ runner = Runner.new
93
+ method = instance_method(m).bind(runner)
94
+ define_singleton_method(m) do |*args|
95
+ runner.clear
96
+ method.call(*args)
97
+ runner.typesetting
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,14 @@
1
+ require 'pwnlib/shellcraft/generators/helper'
2
+
3
+ module Pwnlib
4
+ module Shellcraft
5
+ module Generators
6
+ module I386
7
+ # For non os-related methods.
8
+ module Common
9
+ extend ::Pwnlib::Shellcraft::Generators::Helper
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ require 'pwnlib/shellcraft/generators/i386/common/common'
2
+ require 'pwnlib/shellcraft/generators/x86/common/infloop'
3
+
4
+ module Pwnlib
5
+ module Shellcraft
6
+ module Generators
7
+ module I386
8
+ module Common
9
+ # See {X86::Common#infloop}.
10
+ def infloop
11
+ cat X86::Common.infloop
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,90 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/shellcraft/generators/i386/common/common'
4
+
5
+ module Pwnlib
6
+ module Shellcraft
7
+ module Generators
8
+ module I386
9
+ module Common
10
+ # Move +src+ into +dst+ without newlines and null bytes.
11
+ #
12
+ # See {Amd64::Common#mov} for parameters' details.
13
+ def mov(dst, src, stack_allowed: true)
14
+ raise ArgumentError, "#{dst} is not a register" unless register?(dst)
15
+ dst = get_register(dst)
16
+ raise ArgumentError, "cannot use #{dst} on i386" if dst.size > 32 || dst.is64bit
17
+ if register?(src)
18
+ src = get_register(src)
19
+ raise ArgumentError, "cannot use #{src} on i386" if src.size > 32 || src.is64bit
20
+ if dst.size < src.size && !dst.bigger.include?(src.name)
21
+ raise ArgumentError, "cannot mov #{dst}, #{src}: dst is smaller than src"
22
+ end
23
+ else
24
+ context.local(arch: 'i386') { src = evaluate(src) }
25
+ raise ArgumentError, format('cannot mov %s, %d: dst is smaller than src', dst, src) unless dst.fits(src)
26
+
27
+ # Calculate the packed version
28
+ srcp = pack(src & ((1 << dst.size) - 1), bits: dst.size)
29
+
30
+ # Calculate the unsigned and signed versions
31
+ srcu = unpack(srcp, bits: dst.size, signed: false)
32
+ srcs = unpack(srcp, bits: dst.size, signed: true)
33
+ srcp_neg = p32(-src)
34
+ srcp_not = p32(src ^ 0xffffffff)
35
+ end
36
+ if register?(src)
37
+ if src == dst || dst.bigger.include?(src.name)
38
+ cat "/* moving #{src} into #{dst}, but this is a no-op */"
39
+ elsif dst.size > src.size
40
+ cat "movzx #{dst}, #{src}"
41
+ else
42
+ cat "mov #{dst}, #{src}"
43
+ end
44
+ elsif src.is_a?(Numeric) # Constant or immi
45
+ xor = ->(reg) { "xor #{reg.xor}, #{reg.xor}" }
46
+ if src.zero? # special case for zeroes
47
+ cat "xor #{dst}, #{dst} /* #{src} */"
48
+ elsif stack_allowed && dst.size == 32 && src == 10
49
+ cat "push 9 /* mov #{dst}, '\\n' */"
50
+ cat "pop #{dst}"
51
+ cat "inc #{dst}"
52
+ elsif stack_allowed && dst.size == 32 && (-2**7 <= srcs && srcs < 2**7) && okay(srcp[0])
53
+ cat "push #{pretty(src)}"
54
+ cat "pop #{dst}"
55
+ elsif okay(srcp)
56
+ # Easy case. This implies that the register size and value are the same.
57
+ cat "mov #{dst}, #{pretty(src)}"
58
+ elsif srcu < 2**8 && okay(srcp[0]) && dst.sizes.include?(8)
59
+ # Move 8-bit value into reg.
60
+ cat xor[dst]
61
+ cat "mov #{dst.sizes[8]}, #{pretty(src)}"
62
+ elsif srcu == srcu & 0xff00 && okay(srcp[1]) && dst.ff00
63
+ # Target value is a 16-bit value with no data in the low 8 bits, we can use the 'AH' style register.
64
+ cat xor[dst]
65
+ cat "mov #{dst.ff00}, #{pretty(src)} >> 8"
66
+ elsif srcu < 2**16 && okay(srcp[0, 2])
67
+ # Target value is a 16-bit value, use a 16-bit mov.
68
+ cat xor[dst]
69
+ cat "mov #{dst.sizes[16]}, #{pretty(src)}"
70
+ elsif okay(srcp_neg)
71
+ cat "mov #{dst}, -#{pretty(src)}"
72
+ cat "neg #{dst}"
73
+ elsif okay(srcp_not)
74
+ cat "mov #{dst}, (-1) ^ #{pretty(src)}"
75
+ cat "not #{dst}"
76
+ else
77
+ # All else has failed. Use some XOR magic to move things around.
78
+ a, b = xor_pair(srcp, avoid: "\x00\n")
79
+ a = hex(unpack(a, bits: dst.size))
80
+ b = hex(unpack(b, bits: dst.size))
81
+ cat "mov #{dst}, #{a}"
82
+ cat "xor #{dst}, #{b} /* #{hex(src)} == #{a} ^ #{b} */"
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,16 @@
1
+ require 'pwnlib/shellcraft/generators/i386/common/common'
2
+
3
+ module Pwnlib
4
+ module Shellcraft
5
+ module Generators
6
+ module I386
7
+ module Common
8
+ # A no-op instruction.
9
+ def nop
10
+ cat 'nop'
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/shellcraft/generators/i386/common/common'
4
+
5
+ module Pwnlib
6
+ module Shellcraft
7
+ module Generators
8
+ module I386
9
+ module Common
10
+ # Push a string to stack.
11
+ #
12
+ # See {Amd64::Common#pushstr} for parameters' details.
13
+ def pushstr(str, append_null: true)
14
+ # This will not affect callee's +str+.
15
+ str += "\x00" if append_null && !str.end_with?("\x00")
16
+ return if str.empty?
17
+ padding = str[-1].ord >= 128 ? "\xff" : "\x00"
18
+ cat "/* push #{str.inspect} */"
19
+ group(4, str, underfull_action: :fill, fill_value: padding).reverse_each do |word|
20
+ sign = u32(word, endian: 'little', signed: true)
21
+ if [0, 0xa].include?(sign) # simple forbidden byte case
22
+ cat "push #{pretty(sign + 1)}"
23
+ cat 'dec byte ptr [esp]'
24
+ elsif sign >= -128 && sign <= 127
25
+ cat "push #{pretty(sign)}"
26
+ elsif okay(word)
27
+ cat "push #{pretty(sign)}"
28
+ else
29
+ a = u32(xor_pair(word).first, endian: 'little', signed: false)
30
+ cat "push #{pretty(a)}"
31
+ cat "xor dword ptr [esp], #{pretty(a ^ sign)} /* #{pretty(a)} ^ #{pretty(sign)} */"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,19 @@
1
+ require 'pwnlib/shellcraft/generators/i386/common/common'
2
+ require 'pwnlib/shellcraft/generators/x86/common/pushstr_array'
3
+
4
+ module Pwnlib
5
+ module Shellcraft
6
+ module Generators
7
+ module I386
8
+ module Common
9
+ # See {Pwnlib::Shellcraft::Generators::X86::Common#pushstr_array}.
10
+ def pushstr_array(*args)
11
+ context.local(arch: 'i386') do
12
+ cat X86::Common.pushstr_array(*args)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ require 'pwnlib/shellcraft/generators/i386/common/common'
2
+ require 'pwnlib/shellcraft/generators/x86/common/setregs'
3
+
4
+ module Pwnlib
5
+ module Shellcraft
6
+ module Generators
7
+ module I386
8
+ module Common
9
+ # See {Generators::X86::Common#setregs}.
10
+ def setregs(*args)
11
+ context.local(arch: 'i386') do
12
+ cat X86::Common.setregs(*args)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ require 'pwnlib/shellcraft/generators/i386/linux/linux'
2
+ require 'pwnlib/shellcraft/generators/x86/linux/execve'
3
+
4
+ module Pwnlib
5
+ module Shellcraft
6
+ module Generators
7
+ module I386
8
+ module Linux
9
+ # See {Generators::X86::Linux#execve}.
10
+ def execve(*arguments)
11
+ context.local(arch: 'i386') do
12
+ cat X86::Linux.execve(*arguments)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ require 'pwnlib/shellcraft/generators/helper'
2
+
3
+ module Pwnlib
4
+ module Shellcraft
5
+ module Generators
6
+ module I386
7
+ # For os-related methods.
8
+ module Linux
9
+ extend ::Pwnlib::Shellcraft::Generators::Helper
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ require 'pwnlib/shellcraft/generators/i386/linux/linux'
2
+ require 'pwnlib/shellcraft/generators/x86/linux/ls'
3
+
4
+ module Pwnlib
5
+ module Shellcraft
6
+ module Generators
7
+ module I386
8
+ module Linux
9
+ # See #{Generators::X86::Linux#ls}.
10
+ def ls(*args)
11
+ context.local(arch: 'i386') do
12
+ cat X86::Linux.ls(*args)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ require 'pwnlib/shellcraft/generators/i386/linux/linux'
2
+ require 'pwnlib/shellcraft/generators/x86/linux/sh'
3
+
4
+ module Pwnlib
5
+ module Shellcraft
6
+ module Generators
7
+ module I386
8
+ module Linux
9
+ # See #{Generators::X86::Linux#sh}.
10
+ def sh(*args)
11
+ context.local(arch: 'i386') do
12
+ cat X86::Linux.sh(*args)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ require 'pwnlib/shellcraft/generators/i386/linux/linux'
2
+ require 'pwnlib/shellcraft/generators/x86/linux/syscall'
3
+
4
+ module Pwnlib
5
+ module Shellcraft
6
+ module Generators
7
+ module I386
8
+ module Linux
9
+ # See {Generators::X86::Linux#syscall}.
10
+ def syscall(*arguments)
11
+ context.local(arch: 'i386') do
12
+ cat X86::Linux.syscall(*arguments)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end