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.
- checksums.yaml +4 -4
- data/README.md +88 -11
- data/Rakefile +5 -1
- data/lib/pwn.rb +9 -7
- data/lib/pwnlib/abi.rb +60 -0
- data/lib/pwnlib/asm.rb +146 -0
- data/lib/pwnlib/constants/constant.rb +16 -2
- data/lib/pwnlib/constants/constants.rb +35 -19
- data/lib/pwnlib/constants/linux/amd64.rb +30 -1
- data/lib/pwnlib/context.rb +25 -17
- data/lib/pwnlib/dynelf.rb +117 -54
- data/lib/pwnlib/elf/elf.rb +267 -0
- data/lib/pwnlib/ext/helper.rb +4 -4
- data/lib/pwnlib/logger.rb +87 -0
- data/lib/pwnlib/memleak.rb +58 -29
- data/lib/pwnlib/pwn.rb +19 -8
- data/lib/pwnlib/reg_sort.rb +102 -108
- data/lib/pwnlib/shellcraft/generators/amd64/common/common.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +17 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb +31 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/mov.rb +127 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/nop.rb +16 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/popad.rb +27 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb +64 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/ret.rb +32 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +21 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/linux.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +21 -0
- data/lib/pwnlib/shellcraft/generators/helper.rb +106 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/common.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +17 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/mov.rb +90 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/nop.rb +16 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/pushstr.rb +39 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/linux.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/common.rb +26 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/infloop.rb +22 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/mov.rb +15 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/pushstr.rb +15 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/pushstr_array.rb +85 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/setregs.rb +82 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/execve.rb +69 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/linux.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/ls.rb +66 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/sh.rb +52 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/syscall.rb +52 -0
- data/lib/pwnlib/shellcraft/registers.rb +145 -0
- data/lib/pwnlib/shellcraft/shellcraft.rb +67 -0
- data/lib/pwnlib/timer.rb +60 -0
- data/lib/pwnlib/tubes/buffer.rb +96 -0
- data/lib/pwnlib/tubes/sock.rb +95 -0
- data/lib/pwnlib/tubes/tube.rb +270 -0
- data/lib/pwnlib/util/cyclic.rb +95 -94
- data/lib/pwnlib/util/fiddling.rb +256 -220
- data/lib/pwnlib/util/getdents.rb +83 -0
- data/lib/pwnlib/util/hexdump.rb +109 -108
- data/lib/pwnlib/util/lists.rb +55 -0
- data/lib/pwnlib/util/packing.rb +226 -228
- data/lib/pwnlib/util/ruby.rb +18 -0
- data/lib/pwnlib/version.rb +2 -1
- data/test/abi_test.rb +21 -0
- data/test/asm_test.rb +104 -0
- data/test/constants/constant_test.rb +1 -0
- data/test/constants/constants_test.rb +4 -2
- data/test/context_test.rb +1 -0
- data/test/data/echo.rb +20 -0
- data/test/data/elfs/Makefile +22 -0
- data/test/data/elfs/amd64.frelro.elf +0 -0
- data/test/data/elfs/amd64.frelro.pie.elf +0 -0
- data/test/data/elfs/amd64.nrelro.elf +0 -0
- data/test/data/elfs/amd64.prelro.elf +0 -0
- data/test/data/elfs/i386.frelro.pie.elf +0 -0
- data/test/data/elfs/i386.prelro.elf +0 -0
- data/test/data/elfs/source.cpp +19 -0
- data/test/data/flag +1 -0
- data/test/data/lib32/ld.so.2 +0 -0
- data/test/data/lib32/libc.so.6 +0 -0
- data/test/data/lib64/ld.so.2 +0 -0
- data/test/data/lib64/libc.so.6 +0 -0
- data/test/dynelf_test.rb +59 -24
- data/test/elf/elf_test.rb +120 -0
- data/test/ext_test.rb +3 -2
- data/test/files/use_pwnlib.rb +1 -1
- data/test/logger_test.rb +61 -0
- data/test/memleak_test.rb +4 -33
- data/test/reg_sort_test.rb +3 -1
- data/test/shellcraft/infloop_test.rb +26 -0
- data/test/shellcraft/linux/ls_test.rb +108 -0
- data/test/shellcraft/linux/sh_test.rb +119 -0
- data/test/shellcraft/linux/syscalls/execve_test.rb +136 -0
- data/test/shellcraft/linux/syscalls/syscall_test.rb +83 -0
- data/test/shellcraft/memcpy_test.rb +35 -0
- data/test/shellcraft/mov_test.rb +98 -0
- data/test/shellcraft/nop_test.rb +26 -0
- data/test/shellcraft/popad_test.rb +29 -0
- data/test/shellcraft/pushstr_array_test.rb +91 -0
- data/test/shellcraft/pushstr_test.rb +108 -0
- data/test/shellcraft/registers_test.rb +32 -0
- data/test/shellcraft/ret_test.rb +30 -0
- data/test/shellcraft/setregs_test.rb +62 -0
- data/test/shellcraft/shellcraft_test.rb +28 -0
- data/test/test_helper.rb +12 -1
- data/test/timer_test.rb +23 -0
- data/test/tubes/buffer_test.rb +45 -0
- data/test/tubes/sock_test.rb +68 -0
- data/test/tubes/tube_test.rb +241 -0
- data/test/util/cyclic_test.rb +2 -1
- data/test/util/fiddling_test.rb +2 -1
- data/test/util/getdents_test.rb +32 -0
- data/test/util/hexdump_test.rb +7 -9
- data/test/util/lists_test.rb +21 -0
- data/test/util/packing_test.rb +4 -3
- 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
|
data/lib/pwnlib/util/hexdump.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
79
|
-
|
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
|
-
|
82
|
-
|
83
|
-
last_chunk = ''
|
81
|
+
marker = style[:marker].call(MARKER)
|
82
|
+
spacer = ' '
|
84
83
|
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
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(
|
124
|
+
y << format("%08x %s #{MARKER}%s#{MARKER}", start_byte_index, hex_bytes, printable)
|
125
125
|
end
|
126
|
-
end
|
127
126
|
|
128
|
-
|
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
|
-
|
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
|
data/lib/pwnlib/util/packing.rb
CHANGED
@@ -5,7 +5,7 @@ require 'pwnlib/context'
|
|
5
5
|
module Pwnlib
|
6
6
|
module Util
|
7
7
|
# Methods for integer pack/unpack.
|
8
|
-
#
|
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
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
86
|
-
|
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
|
-
|
84
|
+
end
|
85
|
+
|
86
|
+
number &= (1 << bits) - 1
|
87
|
+
bytes = (bits + 7) / 8
|
94
88
|
|
95
|
-
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
139
|
+
unless data.size == bytes
|
140
|
+
raise ArgumentError, "data.size=#{data.size} does not match with bits=#{bits}"
|
141
|
+
end
|
141
142
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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
|
-
|
195
|
-
|
195
|
+
context.local(bits: bits, endian: endian, signed: signed) do
|
196
|
+
bits = context.bits
|
196
197
|
|
197
|
-
|
198
|
-
|
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
|
-
|
201
|
+
bytes = bits / 8
|
201
202
|
|
202
|
-
|
203
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
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
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
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
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
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
|
-
|
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
|
-
|
277
|
+
# TODO(Darkpi): fit! Which seems super useful.
|
278
|
+
|
279
|
+
include ::Pwnlib::Context
|
282
280
|
end
|
283
281
|
end
|
284
282
|
end
|