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,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