pwntools 0.1.0 → 1.0.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 (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,83 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'bindata'
4
+
5
+ require 'pwnlib/context'
6
+
7
+ module Pwnlib
8
+ module Util
9
+ # Helper methods related to getdents syscall.
10
+ module Getdents
11
+ # For inverse mapping of +linux_dirent#d_type+. +man getdents+ to see more information.
12
+ DT_TYPE_INVERSE = {
13
+ 0 => 'UNKNOWN',
14
+ 1 => 'FIFO',
15
+ 2 => 'CHR',
16
+ 4 => 'DIR',
17
+ 6 => 'BLK',
18
+ 8 => 'REG',
19
+ 10 => 'LNK',
20
+ 12 => 'SOCK'
21
+ }.freeze
22
+
23
+ # The +linux_dirent+ structure.
24
+ class Dirent < ::BinData::Record
25
+ attr_accessor :bits
26
+ # struct linux_dirent {
27
+ # unsigned long d_ino; /* Inode number */
28
+ # unsigned long d_off; /* Offset to next linux_dirent */
29
+ # unsigned short d_reclen; /* Length of this linux_dirent */
30
+ # char d_name[]; /* Filename (null-terminated) */
31
+ # /* length is actually (d_reclen - 2 -
32
+ # offsetof(struct linux_dirent, d_name)) */
33
+ # /*
34
+ # char pad; // Zero padding byte
35
+ # char d_type; // File type (only since Linux
36
+ # // 2.6.4); offset is (d_reclen - 1)
37
+ # */
38
+ # }
39
+ endian :big_and_little
40
+ choice :d_ino, selection: :bits, choices: { 32 => :uint32, 64 => :uint64 }
41
+ choice :d_off, selection: :bits, choices: { 32 => :uint32, 64 => :uint64 }
42
+ uint16 :d_reclen
43
+ string :d_name, read_length: -> { d_reclen - d_ino.num_bytes - d_off.num_bytes - 4 }
44
+ int8 :pad
45
+ int8 :d_type
46
+ end
47
+
48
+ module_function
49
+
50
+ # Parse the output of getdents syscall.
51
+ # For users to handle the shit-like output by +shellcraft.ls+ (e.g. {Shellcraft::Generators::X86::Linux#ls}).
52
+ #
53
+ # @param [String] binstr
54
+ # The content returns by getdents syscall.
55
+ #
56
+ # @return [String]
57
+ # Formatted output of filenames with file types.
58
+ #
59
+ # @example
60
+ # context.arch = 'i386'
61
+ # Util::Getdents.parse("\x92\x22\x0e\x01\x8f\x4a\xb3\x41" \
62
+ # "\x18\x00\x52\x45\x41\x44\x4d\x45" \
63
+ # "\x2e\x6d\x64\x00\x00\x00\x00\x08" \
64
+ # "\xb5\x10\x34\x01\xff\xff\xff\x7f" \
65
+ # "\x10\x00\x6c\x69\x62\x00\x00\x04")
66
+ # #=> "REG README.md\nDIR lib\n"
67
+ def parse(binstr)
68
+ str = StringIO.new(binstr)
69
+ result = StringIO.new
70
+ until str.eof?
71
+ ent = Dirent.new(endian: context.endian.to_sym)
72
+ ent.bits = context.bits
73
+ ent.read(str)
74
+ # Note: d_name might contains garbage after first "\x00", so we use gsub(/\x00.*/) instead of delete("\x00").
75
+ result.puts(DT_TYPE_INVERSE[ent.d_type] + ' ' + ent.d_name.gsub(/\x00.*/, ''))
76
+ end
77
+ result.string
78
+ end
79
+
80
+ include ::Pwnlib::Context
81
+ end
82
+ end
83
+ end
@@ -1,12 +1,14 @@
1
1
  # encoding: ASCII-8BIT
2
+
2
3
  require 'rainbow'
3
4
 
4
5
  module Pwnlib
5
6
  module Util
6
7
  # Method for output a pretty hexdump.
7
8
  # Since this may be used in log module, to avoid cyclic dependency, it is put in a separate module as {Fiddling}
8
- # See {ClassMethods} for method details.
9
- # @todo Control coloring by context?
9
+ #
10
+ # @todo Control coloring by context.
11
+ #
10
12
  # @example Call by specifying full module path.
11
13
  # require 'pwnlib/util/hexdump'
12
14
  # Pwnlib::Util::HexDump.hexdump('217')
@@ -16,130 +18,129 @@ module Pwnlib
16
18
  # hexdump('217')
17
19
  # #=> "00000000 32 31 37 │217│\n00000003"
18
20
  module HexDump
19
- # @note Do not create and call instance method here. Instead, call module method on {HexDump}.
20
- module ClassMethods
21
- MARKER = "\u2502".freeze
22
- HIGHLIGHT_STYLE = ->(s) { Rainbow(s).bg(:red) }
23
- DEFAULT_STYLE = {
24
- 0x00 => ->(s) { Rainbow(s).red },
25
- 0x0a => ->(s) { Rainbow(s).red },
26
- 0xff => ->(s) { Rainbow(s).green },
27
- marker: ->(s) { Rainbow(s).dimgray },
28
- printable: ->(s) { s },
29
- unprintable: ->(s) { Rainbow(s).dimgray }
30
- }.freeze
21
+ MARKER = "\u2502".freeze
22
+ HIGHLIGHT_STYLE = ->(s) { Rainbow(s).bg(:red) }
23
+ DEFAULT_STYLE = {
24
+ 0x00 => ->(s) { Rainbow(s).red },
25
+ 0x0a => ->(s) { Rainbow(s).red },
26
+ 0xff => ->(s) { Rainbow(s).green },
27
+ marker: ->(s) { Rainbow(s).dimgray },
28
+ printable: ->(s) { s },
29
+ unprintable: ->(s) { Rainbow(s).dimgray }
30
+ }.freeze
31
31
 
32
- # @!macro [new] hexdump_options
33
- # Color is provided using +rainbow+ gem and only when output is a tty.
34
- # To force enable/disable coloring, call <tt>Rainbow.enabled = true / false</tt>.
35
- # @param [Integer] width
36
- # The max number of characters per line.
37
- # @param [Boolean] skip
38
- # Whether repeated lines should be replaced by a +"*"+.
39
- # @param [Integer] offset
40
- # Offset of the first byte to print in the left column.
41
- # @param [Hash{Integer, Symbol => Proc}] style
42
- # Color scheme to use.
43
- #
44
- # Possible keys are:
45
- # * <tt>0x00..0xFF</tt>, for specified byte.
46
- # * +:marker+, for the separator in right column.
47
- # * +:printable+, for printable bytes that don't have style specified.
48
- # * +:unprintable+, for unprintable bytes that don't have style specified.
49
- # The proc is called with a single argument, the string to be formatted.
50
- # @param [String] highlight
51
- # Convenient argument to highlight (red background) some bytes in style.
32
+ module_function
52
33
 
53
- # Yields lines of a hexdump-dump of a string. Unless you have massive
54
- # amounts of data you probably want to use {#hexdump}.
55
- # Returns an Enumerator if no block given.
56
- #
57
- # @param [#read] io
58
- # The object to be dumped.
59
- # @!macro hexdump_options
60
- # @return [Enumerator<String>]
61
- # The resulting hexdump, line by line.
62
- def hexdump_iter(io, width: 16, skip: true, offset: 0, style: {}, highlight: '')
63
- Enumerator.new do |y|
64
- style = DEFAULT_STYLE.merge(style)
65
- highlight.bytes.each { |b| style[b] = HIGHLIGHT_STYLE }
66
- (0..255).each do |b|
67
- next if style.include?(b)
68
- style[b] = (b.chr =~ /[[:print:]]/ ? style[:printable] : style[:unprintable])
69
- end
34
+ # @!macro [new] hexdump_params
35
+ # Color is provided using +rainbow+ gem and only when output is a tty.
36
+ # To force enable/disable coloring, call <tt>Rainbow.enabled = true / false</tt>.
37
+ # @param [Integer] width
38
+ # The max number of characters per line.
39
+ # @param [Boolean] skip
40
+ # Whether repeated lines should be replaced by a +"*"+.
41
+ # @param [Integer] offset
42
+ # Offset of the first byte to print in the left column.
43
+ # @param [Hash{Integer, Symbol => Proc}] style
44
+ # Color scheme to use.
45
+ #
46
+ # Possible keys are:
47
+ # * <tt>0x00..0xFF</tt>, for specified byte.
48
+ # * +:marker+, for the separator in right column.
49
+ # * +:printable+, for printable bytes that don't have style specified.
50
+ # * +:unprintable+, for unprintable bytes that don't have style specified.
51
+ # The proc is called with a single argument, the string to be formatted.
52
+ # @param [String] highlight
53
+ # Convenient argument to highlight (red background) some bytes in style.
70
54
 
71
- styled_bytes = (0..255).map do |b|
72
- left_hex = format('%02x', b)
73
- c = b.chr
74
- right_char = (c =~ /[[:print:]]/ ? c : "\u00b7")
75
- [style[b].call(left_hex), style[b].call(right_char)]
76
- end
55
+ # Yields lines of a hexdump-dump of a string.
56
+ # Unless you have massive amounts of data you probably want to use {.hexdump}.
57
+ # Returns an Enumerator if no block given.
58
+ #
59
+ # @param [#read] io
60
+ # The object to be dumped.
61
+ # @!macro hexdump_params
62
+ #
63
+ # @return [Enumerator<String>]
64
+ # The resulting hexdump, line by line.
65
+ def hexdump_iter(io, width: 16, skip: true, offset: 0, style: {}, highlight: '')
66
+ Enumerator.new do |y|
67
+ style = DEFAULT_STYLE.merge(style)
68
+ highlight.bytes.each { |b| style[b] = HIGHLIGHT_STYLE }
69
+ (0..255).each do |b|
70
+ next if style.include?(b)
71
+ style[b] = (b.chr =~ /[[:print:]]/ ? style[:printable] : style[:unprintable])
72
+ end
77
73
 
78
- marker = style[:marker].call(MARKER)
79
- spacer = ' '
74
+ styled_bytes = (0..255).map do |b|
75
+ left_hex = format('%02x', b)
76
+ c = b.chr
77
+ right_char = (c =~ /[[:print:]]/ ? c : "\u00b7")
78
+ [style[b].call(left_hex), style[b].call(right_char)]
79
+ end
80
80
 
81
- byte_index = offset
82
- skipping = false
83
- last_chunk = ''
81
+ marker = style[:marker].call(MARKER)
82
+ spacer = ' '
84
83
 
85
- loop do
86
- # We assume that chunk is in ASCII-8BIT encoding.
87
- chunk = io.read(width)
88
- break unless chunk
89
- chunk_bytes = chunk.bytes
90
- start_byte_index = byte_index
91
- byte_index += chunk_bytes.size
84
+ byte_index = offset
85
+ skipping = false
86
+ last_chunk = ''
92
87
 
93
- # Yield * once for repeated lines.
94
- if skip && last_chunk == chunk
95
- y << '*' unless skipping
96
- skipping = true
97
- next
98
- end
99
- skipping = false
100
- last_chunk = chunk
88
+ loop do
89
+ # We assume that chunk is in ASCII-8BIT encoding.
90
+ chunk = io.read(width)
91
+ break unless chunk
92
+ chunk_bytes = chunk.bytes
93
+ start_byte_index = byte_index
94
+ byte_index += chunk_bytes.size
101
95
 
102
- hex_bytes = ''
103
- printable = ''
104
- chunk_bytes.each_with_index do |b, i|
105
- left_hex, right_char = styled_bytes[b]
106
- hex_bytes << left_hex
107
- printable << right_char
108
- if i % 4 == 3 && i != chunk_bytes.size - 1
109
- hex_bytes << spacer
110
- printable << marker
111
- end
112
- hex_bytes << ' '
113
- end
96
+ # Yield * once for repeated lines.
97
+ if skip && last_chunk == chunk
98
+ y << '*' unless skipping
99
+ skipping = true
100
+ next
101
+ end
102
+ skipping = false
103
+ last_chunk = chunk
114
104
 
115
- if chunk_bytes.size < width
116
- padded_hex_length = 3 * width + (width - 1) / 4
117
- hex_length = 3 * chunk_bytes.size + (chunk_bytes.size - 1) / 4
118
- hex_bytes << ' ' * (padded_hex_length - hex_length)
105
+ hex_bytes = ''
106
+ printable = ''
107
+ chunk_bytes.each_with_index do |b, i|
108
+ left_hex, right_char = styled_bytes[b]
109
+ hex_bytes << left_hex
110
+ printable << right_char
111
+ if i % 4 == 3 && i != chunk_bytes.size - 1
112
+ hex_bytes << spacer
113
+ printable << marker
119
114
  end
115
+ hex_bytes << ' '
116
+ end
120
117
 
121
- y << format("%08x %s #{MARKER}%s#{MARKER}", start_byte_index, hex_bytes, printable)
118
+ if chunk_bytes.size < width
119
+ padded_hex_length = 3 * width + (width - 1) / 4
120
+ hex_length = 3 * chunk_bytes.size + (chunk_bytes.size - 1) / 4
121
+ hex_bytes << ' ' * (padded_hex_length - hex_length)
122
122
  end
123
123
 
124
- y << format('%08x', byte_index)
124
+ y << format("%08x %s #{MARKER}%s#{MARKER}", start_byte_index, hex_bytes, printable)
125
125
  end
126
- end
127
126
 
128
- # Returns a hexdump-dump of a string.
129
- #
130
- # @param [String] str
131
- # The string to be hexdumped.
132
- # @!macro hexdump_options
133
- # @return [String]
134
- # The resulting hexdump.
135
- def hexdump(str, width: 16, skip: true, offset: 0, style: {}, highlight: '')
136
- hexdump_iter(StringIO.new(str),
137
- width: width, skip: skip, offset: offset, style: style,
138
- highlight: highlight).to_a.join("\n")
127
+ y << format('%08x', byte_index)
139
128
  end
140
129
  end
141
130
 
142
- extend ClassMethods
131
+ # Returns a hexdump-dump of a string.
132
+ #
133
+ # @param [String] str
134
+ # The string to be hexdumped.
135
+ # @!macro hexdump_params
136
+ #
137
+ # @return [String]
138
+ # The resulting hexdump.
139
+ def hexdump(str, width: 16, skip: true, offset: 0, style: {}, highlight: '')
140
+ hexdump_iter(StringIO.new(str),
141
+ width: width, skip: skip, offset: offset, style: style,
142
+ highlight: highlight).to_a.join("\n")
143
+ end
143
144
  end
144
145
  end
145
146
  end
@@ -0,0 +1,55 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/context'
4
+
5
+ module Pwnlib
6
+ module Util
7
+ # Methods related to group / slice string into lists.
8
+ module Lists
9
+ module_function
10
+
11
+ # Split sequence into subsequences of given size. If the values cannot be evenly distributed among into groups,
12
+ # then the last group will either be dropped or padded with the value specified in +fill_value+.
13
+ #
14
+ # @param [Integer] n
15
+ # The desired size of each subsequences.
16
+ # @param [String] str
17
+ # The sequence to be grouped.
18
+ # @param [:ignore, :drop, :fill] underfull_action
19
+ # Action to take when size of +str+ is not a mulitple of +n+.
20
+ # @param [String] fill_value
21
+ # The padding byte.
22
+ # Only meaningful when +str+ cannot be grouped equally and +underfull_action == :fill+.
23
+ #
24
+ # @return [Array<String>]
25
+ # The split result.
26
+ #
27
+ # @example
28
+ # slice(2, 'ABCDE') #=> ['AB', 'CD', 'E']
29
+ # slice(2, 'ABCDE', underfull_action: :fill, fill_value: 'X')
30
+ # => ['AB', 'CD', 'EX']
31
+ # slice(2, 'ABCDE', underfull_action: :drop)
32
+ # => ['AB', 'CD']
33
+ #
34
+ # @diff
35
+ # This method named +group+ in python-pwntools, but this is more similar to +Array#each_slice+ in ruby.
36
+ def slice(n, str, underfull_action: :ignore, fill_value: nil)
37
+ unless %i(ignore drop fill).include?(underfull_action)
38
+ raise ArgumentError, 'underfull_action expect to be one of :ignore, :drop, and :fill'
39
+ end
40
+ sliced = str.chars.each_slice(n).map(&:join)
41
+ case underfull_action
42
+ when :drop
43
+ sliced.pop unless sliced.last.size == n
44
+ when :fill
45
+ remain = n - sliced.last.size
46
+ fill_value = fill_value.to_s
47
+ raise ArgumentError, 'fill_value must be a character' unless fill_value.size == 1
48
+ sliced.last.concat(fill_value * remain)
49
+ end
50
+ sliced
51
+ end
52
+ alias group slice
53
+ end
54
+ end
55
+ end
@@ -5,7 +5,7 @@ require 'pwnlib/context'
5
5
  module Pwnlib
6
6
  module Util
7
7
  # Methods for integer pack/unpack.
8
- # See {ClassMethods} for method details.
8
+ #
9
9
  # @example Call by specifying full module path.
10
10
  # require 'pwnlib/util/packing'
11
11
  # Pwnlib::Util::Packing.p8(217) #=> "\xD9"
@@ -13,272 +13,270 @@ module Pwnlib
13
13
  # require 'pwn'
14
14
  # p8(217) #=> "\xD9"
15
15
  module Packing
16
- # @note Do not create and call instance method here. Instead, call module method on {Packing}.
17
- module ClassMethods
18
- # Pack arbitrary-sized integer.
19
- #
20
- # +bits+ indicates number of bits that packed output should use.
21
- # The output would be padded to be byte-aligned.
22
- #
23
- # +bits+ can also be the string 'all',
24
- # indicating that the result should be long enough to hold all bits of the number.
25
- #
26
- # @param [Integer] number
27
- # Number to be packed.
28
- # @param [Integer, 'all'] bits
29
- # Number of bits the output should have,
30
- # or +'all'+ for all bits.
31
- # Default to +context.bits+.
32
- # @param [String] endian
33
- # Endian to use when packing.
34
- # Can be any value accepted by context (See {Context::ContextType}).
35
- # Default to +context.endian+.
36
- # @param [Boolean, String] signed
37
- # Whether the input number should be considered signed when +bits+ is +'all'+.
38
- # Can be any value accepted by context (See {Context::ContextType}).
39
- # Default to +context.signed+.
40
- # @return [String]
41
- # The packed string.
42
- # @raise [ArgumentError]
43
- # When input integer can't be packed into the size specified by +bits+ and +signed+.
44
- #
45
- # @example
46
- # pack(0x34, bits: 8) #=> '4'
47
- # pack(0x1234, bits: 16, endian: 'little') #=> "4\x12"
48
- # pack(0xFF, bits: 'all', signed: false) #=> "\xFF"
49
- # pack(0xFF, bits: 'all', endian: 'big', signed: true) #=> "\x00\xFF"
50
- def pack(number, bits: nil, endian: nil, signed: nil)
51
- if bits == 'all'
52
- bits = nil
53
- is_all = true
54
- else
55
- is_all = false
56
- end
16
+ module_function
57
17
 
58
- context.local(bits: bits, endian: endian, signed: signed) do
59
- bits = context.bits
60
- endian = context.endian
61
- signed = context.signed
18
+ # Pack arbitrary-sized integer.
19
+ #
20
+ # +bits+ indicates number of bits that packed output should use.
21
+ # The output would be padded to be byte-aligned.
22
+ #
23
+ # +bits+ can also be the string 'all', indicating that the result should be long enough to hold all bits of the
24
+ # number.
25
+ #
26
+ # @param [Integer] number
27
+ # Number to be packed.
28
+ # @param [Integer, 'all'] bits
29
+ # Number of bits the output should have, or +'all'+ for all bits.
30
+ # Default to +context.bits+.
31
+ # @param [String] endian
32
+ # Endian to use when packing.
33
+ # Can be any value accepted by context (See {Context::ContextType}).
34
+ # Default to +context.endian+.
35
+ # @param [Boolean, String] signed
36
+ # Whether the input number should be considered signed when +bits+ is +'all'+.
37
+ # Can be any value accepted by context (See {Context::ContextType}).
38
+ # Default to +context.signed+.
39
+ #
40
+ # @return [String]
41
+ # The packed string.
42
+ #
43
+ # @raise [ArgumentError]
44
+ # When input integer can't be packed into the size specified by +bits+ and +signed+.
45
+ #
46
+ # @example
47
+ # pack(0x34, bits: 8) #=> '4'
48
+ # pack(0x1234, bits: 16, endian: 'little') #=> "4\x12"
49
+ # pack(0xFF, bits: 'all', signed: false) #=> "\xFF"
50
+ # pack(0xFF, bits: 'all', endian: 'big', signed: true) #=> "\x00\xFF"
51
+ def pack(number, bits: nil, endian: nil, signed: nil)
52
+ if bits == 'all'
53
+ bits = nil
54
+ is_all = true
55
+ else
56
+ is_all = false
57
+ end
62
58
 
63
- # Verify that bits make sense
64
- if signed
65
- bits = (number.bit_length | 7) + 1 if is_all
59
+ context.local(bits: bits, endian: endian, signed: signed) do
60
+ bits = context.bits
61
+ endian = context.endian
62
+ signed = context.signed
66
63
 
67
- limit = 1 << (bits - 1)
68
- unless -limit <= number && number < limit
69
- raise ArgumentError, "signed number=#{number} does not fit within bits=#{bits}"
70
- end
71
- else
72
- if is_all
73
- if number < 0
74
- raise ArgumentError, "Can't pack negative number with bits='all' and signed=false"
75
- end
76
- bits = number.zero? ? 8 : ((number.bit_length - 1) | 7) + 1
77
- end
64
+ # Verify that bits make sense
65
+ if signed
66
+ bits = (number.bit_length | 7) + 1 if is_all
78
67
 
79
- limit = 1 << bits
80
- unless 0 <= number && number < limit
81
- raise ArgumentError, "unsigned number=#{number} does not fit within bits=#{bits}"
68
+ limit = 1 << (bits - 1)
69
+ unless -limit <= number && number < limit
70
+ raise ArgumentError, "signed number=#{number} does not fit within bits=#{bits}"
71
+ end
72
+ else
73
+ if is_all
74
+ if number < 0
75
+ raise ArgumentError, "Can't pack negative number with bits='all' and signed=false"
82
76
  end
77
+ bits = number.zero? ? 8 : ((number.bit_length - 1) | 7) + 1
83
78
  end
84
79
 
85
- number &= (1 << bits) - 1
86
- bytes = (bits + 7) / 8
87
-
88
- out = []
89
- bytes.times do
90
- out << (number & 0xFF)
91
- number >>= 8
80
+ limit = 1 << bits
81
+ unless 0 <= number && number < limit
82
+ raise ArgumentError, "unsigned number=#{number} does not fit within bits=#{bits}"
92
83
  end
93
- out = out.pack('C*')
84
+ end
85
+
86
+ number &= (1 << bits) - 1
87
+ bytes = (bits + 7) / 8
94
88
 
95
- endian == 'little' ? out : out.reverse
89
+ out = []
90
+ bytes.times do
91
+ out << (number & 0xFF)
92
+ number >>= 8
96
93
  end
94
+ out = out.pack('C*')
95
+
96
+ endian == 'little' ? out : out.reverse
97
97
  end
98
+ end
98
99
 
99
- # Unpack packed string back to integer.
100
- #
101
- # +bits+ indicates number of bits that should be used from input data.
102
- #
103
- # +bits+ can also be the string +'all'+,
104
- # indicating that all bytes from input should be used.
105
- #
106
- # @param [String] data
107
- # String to be unpacked.
108
- # @param [Integer, 'all'] bits
109
- # Number of bits to be used from +data+,
110
- # or +'all'+ for all bits.
111
- # Default to +context.bits+
112
- # @param [String] endian
113
- # Endian to use when unpacking.
114
- # Can be any value accepted by context (See {Context::ContextType}).
115
- # Default to +context.endian+.
116
- # @param [Boolean, String] signed
117
- # Whether the output number should be signed.
118
- # Can be any value accepted by context (See {Context::ContextType}).
119
- # Default to +context.signed+.
120
- # @return [Integer]
121
- # The unpacked number.
122
- # @raise [ArgumentError]
123
- # When +data.size+ doesn't match +bits+.
124
- #
125
- # @example
126
- # unpack('4', bits: 8) #=> 52
127
- # unpack("\x3F", bits: 6, signed: false) #=> 63
128
- # unpack("\x3F", bits: 6, signed: true) #=> -1
129
- def unpack(data, bits: nil, endian: nil, signed: nil)
130
- bits = data.size * 8 if bits == 'all'
100
+ # Unpack packed string back to integer.
101
+ #
102
+ # +bits+ indicates number of bits that should be used from input data.
103
+ #
104
+ # +bits+ can also be the string +'all'+, indicating that all bytes from input should be used.
105
+ #
106
+ # @param [String] data
107
+ # String to be unpacked.
108
+ # @param [Integer, 'all'] bits
109
+ # Number of bits to be used from +data+, or +'all'+ for all bits.
110
+ # Default to +context.bits+
111
+ # @param [String] endian
112
+ # Endian to use when unpacking.
113
+ # Can be any value accepted by context (See {Context::ContextType}).
114
+ # Default to +context.endian+.
115
+ # @param [Boolean, String] signed
116
+ # Whether the output number should be signed.
117
+ # Can be any value accepted by context (See {Context::ContextType}).
118
+ # Default to +context.signed+.
119
+ #
120
+ # @return [Integer]
121
+ # The unpacked number.
122
+ #
123
+ # @raise [ArgumentError]
124
+ # When +data.size+ doesn't match +bits+.
125
+ #
126
+ # @example
127
+ # unpack('4', bits: 8) #=> 52
128
+ # unpack("\x3F", bits: 6, signed: false) #=> 63
129
+ # unpack("\x3F", bits: 6, signed: true) #=> -1
130
+ def unpack(data, bits: nil, endian: nil, signed: nil)
131
+ bits = data.size * 8 if bits == 'all'
131
132
 
132
- context.local(bits: bits, endian: endian, signed: signed) do
133
- bits = context.bits
134
- endian = context.endian
135
- signed = context.signed
136
- bytes = (bits + 7) / 8
133
+ context.local(bits: bits, endian: endian, signed: signed) do
134
+ bits = context.bits
135
+ endian = context.endian
136
+ signed = context.signed
137
+ bytes = (bits + 7) / 8
137
138
 
138
- unless data.size == bytes
139
- raise ArgumentError, "data.size=#{data.size} does not match with bits=#{bits}"
140
- end
139
+ unless data.size == bytes
140
+ raise ArgumentError, "data.size=#{data.size} does not match with bits=#{bits}"
141
+ end
141
142
 
142
- data = data.reverse if endian == 'little'
143
- data = data.unpack('C*')
144
- number = 0
145
- data.each { |c| number = (number << 8) + c }
146
- number &= (1 << bits) - 1
147
- if signed
148
- signbit = number & (1 << (bits - 1))
149
- number -= 2 * signbit
150
- end
151
- number
143
+ data = data.reverse if endian == 'little'
144
+ data = data.unpack('C*')
145
+ number = 0
146
+ data.each { |c| number = (number << 8) + c }
147
+ number &= (1 << bits) - 1
148
+ if signed
149
+ signbit = number & (1 << (bits - 1))
150
+ number -= 2 * signbit
152
151
  end
152
+ number
153
153
  end
154
+ end
154
155
 
155
- # Split the data into chunks, and unpack each element.
156
- #
157
- # +bits+ indicates how many bits each chunk should be.
158
- # This should be a multiple of 8,
159
- # and size of +data+ should be divisible by +bits / 8+.
160
- #
161
- # +bits+ can also be the string +'all'+,
162
- # indicating that all bytes from input would be used,
163
- # and result would be an array with one element.
164
- #
165
- # @param [String] data
166
- # String to be unpacked.
167
- # @param [Integer, 'all'] bits
168
- # Number of bits to be used for each chunk of +data+,
169
- # or +'all'+ for all bits.
170
- # Default to +context.bits+
171
- # @param [String] endian
172
- # Endian to use when unpacking.
173
- # Can be any value accepted by context (See {Context::ContextType}).
174
- # Default to +context.endian+.
175
- # @param [Boolean, String] signed
176
- # Whether the output number should be signed.
177
- # Can be any value accepted by context (See {Context::ContextType}).
178
- # Default to +context.signed+.
179
- # @return [Array<Integer>]
180
- # The unpacked numbers.
181
- # @raise [ArgumentError]
182
- # When +bits+ isn't divisible by 8 or +data.size+ isn't divisible by +bits / 8+.
183
- #
184
- # @todo
185
- # Support +bits+ not divisible by 8, if ever found this useful.
186
- #
187
- # @example
188
- # unpack_many('haha', bits: 8) #=> [104, 97, 104, 97]
189
- # unpack_many("\xFF\x01\x02\xFE", bits: 16, endian: 'little', signed: true) #=> [511, -510]
190
- # unpack_many("\xFF\x01\x02\xFE", bits: 16, endian: 'big', signed: false) #=> [65281, 766]
191
- def unpack_many(data, bits: nil, endian: nil, signed: nil)
192
- return [unpack(data, bits: bits, endian: endian, signed: signed)] if bits == 'all'
156
+ # Split the data into chunks, and unpack each element.
157
+ #
158
+ # +bits+ indicates how many bits each chunk should be.
159
+ # This should be a multiple of 8, and size of +data+ should be divisible by +bits / 8+.
160
+ #
161
+ # +bits+ can also be the string +'all'+, indicating that all bytes from input would be used, and result would be
162
+ # an array with one element.
163
+ #
164
+ # @param [String] data
165
+ # String to be unpacked.
166
+ # @param [Integer, 'all'] bits
167
+ # Number of bits to be used for each chunk of +data+,
168
+ # or +'all'+ for all bits.
169
+ # Default to +context.bits+
170
+ # @param [String] endian
171
+ # Endian to use when unpacking.
172
+ # Can be any value accepted by context (See {Context::ContextType}).
173
+ # Default to +context.endian+.
174
+ # @param [Boolean, String] signed
175
+ # Whether the output number should be signed.
176
+ # Can be any value accepted by context (See {Context::ContextType}).
177
+ # Default to +context.signed+.
178
+ #
179
+ # @return [Array<Integer>]
180
+ # The unpacked numbers.
181
+ #
182
+ # @raise [ArgumentError]
183
+ # When +bits+ isn't divisible by 8 or +data.size+ isn't divisible by +bits / 8+.
184
+ #
185
+ # @todo
186
+ # Support +bits+ not divisible by 8, if ever found this useful.
187
+ #
188
+ # @example
189
+ # unpack_many('haha', bits: 8) #=> [104, 97, 104, 97]
190
+ # unpack_many("\xFF\x01\x02\xFE", bits: 16, endian: 'little', signed: true) #=> [511, -510]
191
+ # unpack_many("\xFF\x01\x02\xFE", bits: 16, endian: 'big', signed: false) #=> [65281, 766]
192
+ def unpack_many(data, bits: nil, endian: nil, signed: nil)
193
+ return [unpack(data, bits: bits, endian: endian, signed: signed)] if bits == 'all'
193
194
 
194
- context.local(bits: bits, endian: endian, signed: signed) do
195
- bits = context.bits
195
+ context.local(bits: bits, endian: endian, signed: signed) do
196
+ bits = context.bits
196
197
 
197
- # TODO(Darkpi): Support this if found useful.
198
- raise ArgumentError, 'bits must be a multiple of 8' if bits % 8 != 0
198
+ # TODO(Darkpi): Support this if found useful.
199
+ raise ArgumentError, 'bits must be a multiple of 8' if bits % 8 != 0
199
200
 
200
- bytes = bits / 8
201
+ bytes = bits / 8
201
202
 
202
- if data.size % bytes != 0
203
- raise ArgumentError, "data.size=#{data.size} must be a multiple of bytes=#{bytes}"
204
- end
205
- ret = []
206
- (data.size / bytes).times do |idx|
207
- x1 = idx * bytes
208
- x2 = x1 + bytes
209
- # We already set local context, no need to pass things down.
210
- ret << unpack(data[x1...x2], bits: bits)
211
- end
212
- ret
203
+ if data.size % bytes != 0
204
+ raise ArgumentError, "data.size=#{data.size} must be a multiple of bytes=#{bytes}"
213
205
  end
206
+ ret = []
207
+ (data.size / bytes).times do |idx|
208
+ x1 = idx * bytes
209
+ x2 = x1 + bytes
210
+ # We already set local context, no need to pass things down.
211
+ ret << unpack(data[x1...x2], bits: bits)
212
+ end
213
+ ret
214
214
  end
215
+ end
215
216
 
216
- { 8 => 'c', 16 => 's', 32 => 'l', 64 => 'q' }.each do |sz, ch|
217
- define_method("p#{sz}") do |num, **kwargs|
218
- context.local(**kwargs) do
219
- c = context.signed ? ch : ch.upcase
220
- arrow = context.endian == 'little' ? '<' : '>'
221
- arrow = '' if sz == 8
222
- [num].pack("#{c}#{arrow}")
223
- end
217
+ { 8 => 'c', 16 => 's', 32 => 'l', 64 => 'q' }.each do |sz, ch|
218
+ define_method("p#{sz}") do |num, **kwargs|
219
+ context.local(**kwargs) do
220
+ c = context.signed ? ch : ch.upcase
221
+ arrow = context.endian == 'little' ? '<' : '>'
222
+ arrow = '' if sz == 8
223
+ [num].pack("#{c}#{arrow}")
224
224
  end
225
+ end
225
226
 
226
- define_method("u#{sz}") do |data, **kwargs|
227
- context.local(**kwargs) do
228
- c = context.signed ? ch : ch.upcase
229
- arrow = context.endian == 'little' ? '<' : '>'
230
- arrow = '' if sz == 8
231
- data.unpack("#{c}#{arrow}")[0]
232
- end
227
+ define_method("u#{sz}") do |data, **kwargs|
228
+ context.local(**kwargs) do
229
+ c = context.signed ? ch : ch.upcase
230
+ arrow = context.endian == 'little' ? '<' : '>'
231
+ arrow = '' if sz == 8
232
+ data.unpack("#{c}#{arrow}")[0]
233
233
  end
234
234
  end
235
+ end
235
236
 
236
- # TODO(Darkpi): pwntools-python have this for performance reason,
237
- # but current implementation doesn't offer that much performance
238
- # relative to what pwntools-python do. Maybe we should initialize
239
- # those functions (p8lu, ...) like in pwntools-python?
240
- [%w(pack p), %w(unpack u)].each do |v1, v2|
241
- define_method("make_#{v1}er") do |bits: nil, endian: nil, signed: nil|
242
- context.local(bits: bits, endian: endian, signed: signed) do
243
- bits = context.bits
244
- endian = context.endian
245
- signed = context.signed
237
+ # TODO(Darkpi):
238
+ # pwntools-python have this for performance reason, but current implementation doesn't offer that much
239
+ # performance relative to what pwntools-python do. Maybe we should initialize those functions (p8lu, ...)
240
+ # like in pwntools-python?
241
+ [%w(pack p), %w(unpack u)].each do |v1, v2|
242
+ define_method("make_#{v1}er") do |bits: nil, endian: nil, signed: nil|
243
+ context.local(bits: bits, endian: endian, signed: signed) do
244
+ bits = context.bits
245
+ endian = context.endian
246
+ signed = context.signed
246
247
 
247
- if [8, 16, 32, 64].include?(bits)
248
- ->(num) { public_send("#{v2}#{bits}", num, endian: endian, signed: signed) }
249
- else
250
- ->(num) { public_send(v1, num, bits: bits, endian: endian, signed: signed) }
251
- end
248
+ if [8, 16, 32, 64].include?(bits)
249
+ ->(num) { ::Pwnlib::Util::Packing.public_send("#{v2}#{bits}", num, endian: endian, signed: signed) }
250
+ else
251
+ ->(num) { ::Pwnlib::Util::Packing.public_send(v1, num, bits: bits, endian: endian, signed: signed) }
252
252
  end
253
253
  end
254
254
  end
255
+ end
255
256
 
256
- def flat(*args, **kwargs, &preprocessor)
257
- ret = []
258
- p = make_packer(**kwargs)
259
- args.each do |it|
260
- if preprocessor && !it.is_a?(Array)
261
- r = preprocessor[it]
262
- it = r unless r.nil?
263
- end
264
- v = case it
265
- when Array then flat(*it, **kwargs, &preprocessor)
266
- when Integer then p[it]
267
- when String then it.force_encoding('ASCII-8BIT')
268
- else
269
- raise ArgumentError, "flat does not support values of type #{it.class}"
270
- end
271
- ret << v
257
+ def flat(*args, **kwargs, &preprocessor)
258
+ ret = []
259
+ p = make_packer(**kwargs)
260
+ args.each do |it|
261
+ if preprocessor && !it.is_a?(Array)
262
+ r = preprocessor[it]
263
+ it = r unless r.nil?
272
264
  end
273
- ret.join
265
+ v = case it
266
+ when Array then flat(*it, **kwargs, &preprocessor)
267
+ when Integer then p[it]
268
+ when String then it.force_encoding('ASCII-8BIT')
269
+ else
270
+ raise ArgumentError, "flat does not support values of type #{it.class}"
271
+ end
272
+ ret << v
274
273
  end
275
-
276
- # TODO(Darkpi): fit! Which seems super useful.
277
-
278
- include ::Pwnlib::Context
274
+ ret.join
279
275
  end
280
276
 
281
- extend ClassMethods
277
+ # TODO(Darkpi): fit! Which seems super useful.
278
+
279
+ include ::Pwnlib::Context
282
280
  end
283
281
  end
284
282
  end