pwntools 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|