pwntools 1.0.1 → 1.1.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 +5 -5
- data/README.md +4 -3
- data/Rakefile +3 -1
- data/lib/pwnlib/asm.rb +172 -2
- data/lib/pwnlib/constants/constants.rb +10 -3
- data/lib/pwnlib/context.rb +1 -3
- data/lib/pwnlib/elf/elf.rb +3 -3
- data/lib/pwnlib/errors.rb +30 -0
- data/lib/pwnlib/ext/helper.rb +1 -1
- data/lib/pwnlib/logger.rb +140 -2
- data/lib/pwnlib/pwn.rb +3 -0
- data/lib/pwnlib/reg_sort.rb +1 -1
- data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +9 -3
- data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +6 -2
- data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +6 -2
- data/lib/pwnlib/shellcraft/generators/amd64/linux/cat.rb +23 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +6 -4
- data/lib/pwnlib/shellcraft/generators/amd64/linux/exit.rb +23 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +6 -2
- data/lib/pwnlib/shellcraft/generators/amd64/linux/open.rb +23 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +6 -2
- data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +6 -4
- data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +9 -3
- data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +6 -2
- data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +6 -2
- data/lib/pwnlib/shellcraft/generators/i386/linux/cat.rb +23 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +8 -4
- data/lib/pwnlib/shellcraft/generators/i386/linux/exit.rb +23 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +6 -2
- data/lib/pwnlib/shellcraft/generators/i386/linux/open.rb +23 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +6 -2
- data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +8 -4
- data/lib/pwnlib/shellcraft/generators/x86/linux/cat.rb +53 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/exit.rb +33 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/open.rb +46 -0
- data/lib/pwnlib/shellcraft/shellcraft.rb +3 -2
- data/lib/pwnlib/timer.rb +5 -2
- data/lib/pwnlib/tubes/process.rb +153 -0
- data/lib/pwnlib/tubes/serialtube.rb +112 -0
- data/lib/pwnlib/tubes/sock.rb +24 -25
- data/lib/pwnlib/tubes/tube.rb +191 -39
- data/lib/pwnlib/util/packing.rb +3 -9
- data/lib/pwnlib/version.rb +1 -1
- data/test/asm_test.rb +85 -2
- data/test/constants/constants_test.rb +2 -2
- data/test/data/echo.rb +2 -7
- data/test/elf/elf_test.rb +10 -15
- data/test/files/use_pwn.rb +2 -6
- data/test/logger_test.rb +38 -0
- data/test/shellcraft/linux/cat_test.rb +86 -0
- data/test/shellcraft/linux/syscalls/exit_test.rb +56 -0
- data/test/shellcraft/linux/syscalls/open_test.rb +86 -0
- data/test/shellcraft/shellcraft_test.rb +5 -4
- data/test/test_helper.rb +22 -2
- data/test/timer_test.rb +19 -1
- data/test/tubes/process_test.rb +99 -0
- data/test/tubes/serialtube_test.rb +165 -0
- data/test/tubes/sock_test.rb +20 -21
- data/test/tubes/tube_test.rb +86 -16
- metadata +75 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7c323ddeb8d8f4ea3d4d087fc69eb53ee7594f64c2ee01dfb57ada9e2a9eb111
|
4
|
+
data.tar.gz: b9177cb71feb8ecc038680cf850f0b654eea567c7f6774d8ad35cdb1b8949e93
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76cbcd91657ecaee5b8bfae49bfe378539937718a1eb46fbe517c8559919877a153cffb0e5beeb06dde68fe64cd7ab9e1fb30af507547771f49331e7bb90c93b
|
7
|
+
data.tar.gz: 1aedd2cb397654aa4bc4e13761a1587431f7a67ecec2150c5e6393f1275dfd14801089e0de7234ddd887b2fb99c9635c9bc438ba183c6fcf8d021331269d99b5
|
data/README.md
CHANGED
@@ -57,7 +57,7 @@ gem install pwntools
|
|
57
57
|
```sh
|
58
58
|
git clone https://github.com/peter50216/pwntools-ruby
|
59
59
|
cd pwntools-ruby
|
60
|
-
|
60
|
+
bundle install && bundle exec rake install
|
61
61
|
```
|
62
62
|
|
63
63
|
### optional
|
@@ -101,9 +101,10 @@ sudo make install
|
|
101
101
|
- [x] elf
|
102
102
|
- [x] dynelf
|
103
103
|
- [x] logger
|
104
|
-
- [
|
104
|
+
- [x] tube
|
105
105
|
- [x] sock
|
106
|
-
- [
|
106
|
+
- [x] process
|
107
|
+
- [x] serialtube
|
107
108
|
- [ ] fmtstr
|
108
109
|
- [x] util
|
109
110
|
- [x] pack
|
data/Rakefile
CHANGED
@@ -7,8 +7,10 @@ require 'rake/testtask'
|
|
7
7
|
require 'rubocop/rake_task'
|
8
8
|
require 'yard'
|
9
9
|
|
10
|
+
import 'tasks/shellcraft/x86.rake'
|
11
|
+
|
10
12
|
RuboCop::RakeTask.new(:rubocop) do |task|
|
11
|
-
task.patterns = ['lib/**/*.rb', 'test/**/*.rb']
|
13
|
+
task.patterns = ['lib/**/*.rb', 'tasks/**/*.rake', 'test/**/*.rb']
|
12
14
|
end
|
13
15
|
|
14
16
|
task default: %i(install_git_hooks rubocop test)
|
data/lib/pwnlib/asm.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
# encoding: ASCII-8BIT
|
2
2
|
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
require 'elftools'
|
3
6
|
require 'keystone_engine/keystone_const'
|
4
7
|
|
5
8
|
require 'pwnlib/context'
|
9
|
+
require 'pwnlib/errors'
|
6
10
|
require 'pwnlib/util/ruby'
|
7
11
|
|
8
12
|
module Pwnlib
|
@@ -11,6 +15,15 @@ module Pwnlib
|
|
11
15
|
module Asm
|
12
16
|
module_function
|
13
17
|
|
18
|
+
# Default virtaul memory base address of architectures.
|
19
|
+
#
|
20
|
+
# This address may be different by using different linker.
|
21
|
+
DEFAULT_VMA = {
|
22
|
+
i386: 0x08048000,
|
23
|
+
amd64: 0x400000,
|
24
|
+
arm: 0x8000
|
25
|
+
}.freeze
|
26
|
+
|
14
27
|
# Disassembles a bytestring into human readable assembly.
|
15
28
|
#
|
16
29
|
# {.disasm} depends on another open-source project - capstone, error will be raised if capstone is not intalled.
|
@@ -22,7 +35,7 @@ module Pwnlib
|
|
22
35
|
# @return [String]
|
23
36
|
# Disassemble result with nice typesetting.
|
24
37
|
#
|
25
|
-
# @raise [
|
38
|
+
# @raise [Pwnlib::Errors::DependencyError]
|
26
39
|
# If libcapstone is not installed.
|
27
40
|
#
|
28
41
|
# @example
|
@@ -85,6 +98,80 @@ module Pwnlib
|
|
85
98
|
KeystoneEngine::Ks.new(ks_arch, ks_mode).asm(code)[0]
|
86
99
|
end
|
87
100
|
|
101
|
+
# Builds an ELF file from executable code.
|
102
|
+
#
|
103
|
+
# @param [String] data
|
104
|
+
# Assembled code.
|
105
|
+
# @param [Integer?] vma
|
106
|
+
# The load address for the ELF file.
|
107
|
+
# If +nil+ is given, default address will be used.
|
108
|
+
# See {DEFAULT_VMA}.
|
109
|
+
# @param [Boolean] to_file
|
110
|
+
# Returns ELF content or the path to the ELF file.
|
111
|
+
# If +true+ is given, the ELF will be saved into a temp file.
|
112
|
+
#
|
113
|
+
# @return [String, Object]
|
114
|
+
# Without block
|
115
|
+
# - If +to_file+ is +false+ (default), returns the content of ELF.
|
116
|
+
# - Otherwise, a file is created and the path is returned.
|
117
|
+
# With block given, an ELF file will be created and its path will be yielded.
|
118
|
+
# This method will return what the block returned, and the ELF file will be removed after the block yielded.
|
119
|
+
#
|
120
|
+
# @yieldparam [String] path
|
121
|
+
# The path to the created ELF file.
|
122
|
+
#
|
123
|
+
# @yieldreturn [Object]
|
124
|
+
# Whatever you want.
|
125
|
+
#
|
126
|
+
# @raise [::Pwnlib::Errors::UnsupportedArchError]
|
127
|
+
# Raised when don't know how to create an ELF under architecture +context.arch+.
|
128
|
+
#
|
129
|
+
# @diff
|
130
|
+
# Unlike pwntools-python uses cross-compiler to compile code into ELF, we create ELFs in pure Ruby
|
131
|
+
# implementation. Therefore, we have higher flexibility and less binary dependencies.
|
132
|
+
#
|
133
|
+
# @example
|
134
|
+
# bin = make_elf(asm(shellcraft.sh))
|
135
|
+
# bin[0, 4]
|
136
|
+
# #=> "\x7FELF"
|
137
|
+
# @example
|
138
|
+
# path = make_elf(asm(shellcraft.cat('/proc/self/maps')), to_file: true)
|
139
|
+
# puts `#{path}`
|
140
|
+
# # 08048000-08049000 r-xp 00000000 fd:01 27671233 /tmp/pwn20180129-3411-7klnng.elf
|
141
|
+
# # f77c7000-f77c9000 r--p 00000000 00:00 0 [vvar]
|
142
|
+
# # f77c9000-f77cb000 r-xp 00000000 00:00 0 [vdso]
|
143
|
+
# # ffda6000-ffdc8000 rwxp 00000000 00:00 0 [stack]
|
144
|
+
# @example
|
145
|
+
# # no need 'to_file' parameter if block is given
|
146
|
+
# make_elf(asm(shellcraft.cat('/proc/self/maps'))) do |path|
|
147
|
+
# puts `#{path}`
|
148
|
+
# # 08048000-08049000 r-xp 00000000 fd:01 27671233 /tmp/pwn20180129-3411-7klnng.elf
|
149
|
+
# # f77c7000-f77c9000 r--p 00000000 00:00 0 [vvar]
|
150
|
+
# # f77c9000-f77cb000 r-xp 00000000 00:00 0 [vdso]
|
151
|
+
# # ffda6000-ffdc8000 rwxp 00000000 00:00 0 [stack]
|
152
|
+
# end
|
153
|
+
def make_elf(data, vma: nil, to_file: false)
|
154
|
+
to_file ||= block_given?
|
155
|
+
vma ||= DEFAULT_VMA[context.arch.to_sym]
|
156
|
+
vma &= -0x1000
|
157
|
+
# ELF header
|
158
|
+
# Program headers
|
159
|
+
# <data>
|
160
|
+
headers = create_elf_headers(vma)
|
161
|
+
ehdr = headers[:elf_header]
|
162
|
+
phdr = headers[:program_header]
|
163
|
+
entry = ehdr.num_bytes + phdr.num_bytes
|
164
|
+
ehdr.e_entry = entry + phdr.p_vaddr
|
165
|
+
ehdr.e_phoff = ehdr.num_bytes
|
166
|
+
phdr.p_filesz = phdr.p_memsz = entry + data.size
|
167
|
+
elf = ehdr.to_binary_s + phdr.to_binary_s + data
|
168
|
+
return elf unless to_file
|
169
|
+
path = Dir::Tmpname.create(['pwn', '.elf']) do |temp|
|
170
|
+
File.open(temp, 'wb', 0o750) { |f| f.write(elf) }
|
171
|
+
end
|
172
|
+
block_given? ? yield(path).tap { File.unlink(path) } : path
|
173
|
+
end
|
174
|
+
|
88
175
|
::Pwnlib::Util::Ruby.private_class_method_block do
|
89
176
|
def cap_arch
|
90
177
|
{
|
@@ -118,7 +205,7 @@ module Pwnlib
|
|
118
205
|
def require_message(lib, msg)
|
119
206
|
require lib
|
120
207
|
rescue LoadError => e
|
121
|
-
raise
|
208
|
+
raise ::Pwnlib::Errors::DependencyError, e.message + "\n\n" + msg
|
122
209
|
end
|
123
210
|
|
124
211
|
def install_crabstone_guide
|
@@ -140,6 +227,89 @@ https://github.com/keystone-engine/keystone/tree/master/docs
|
|
140
227
|
|
141
228
|
EOS
|
142
229
|
end
|
230
|
+
|
231
|
+
# build headers according to context.arch/bits/endian
|
232
|
+
def create_elf_headers(vma)
|
233
|
+
elf_header = create_elf_header
|
234
|
+
# we only need one LOAD segment
|
235
|
+
program_header = create_program_header(vma)
|
236
|
+
elf_header.e_phentsize = program_header.num_bytes
|
237
|
+
elf_header.e_phnum = 1
|
238
|
+
{
|
239
|
+
elf_header: elf_header,
|
240
|
+
program_header: program_header
|
241
|
+
}
|
242
|
+
end
|
243
|
+
|
244
|
+
def create_elf_header
|
245
|
+
header = ::ELFTools::Structs::ELF_Ehdr.new(endian: endian)
|
246
|
+
# this decide size of entries
|
247
|
+
header.elf_class = context.bits
|
248
|
+
header.e_ident.magic = ::ELFTools::Constants::ELFMAG
|
249
|
+
header.e_ident.ei_class = { 32 => 1, 64 => 2 }[context.bits]
|
250
|
+
header.e_ident.ei_data = { little: 1, big: 2 }[endian]
|
251
|
+
# Not sure what version field means, seems it can be any value.
|
252
|
+
header.e_ident.ei_version = 1
|
253
|
+
header.e_ident.ei_padding = "\x00" * 7
|
254
|
+
header.e_type = ::ELFTools::Constants::ET::ET_EXEC
|
255
|
+
header.e_machine = e_machine
|
256
|
+
# XXX(david942j): is header.e_flags important?
|
257
|
+
header.e_ehsize = header.num_bytes
|
258
|
+
header
|
259
|
+
end
|
260
|
+
|
261
|
+
def create_program_header(vma)
|
262
|
+
header = ::ELFTools::Structs::ELF_Phdr[context.bits].new(endian: endian)
|
263
|
+
header.p_type = ::ELFTools::Constants::PT::PT_LOAD
|
264
|
+
header.p_offset = 0
|
265
|
+
header.p_vaddr = vma
|
266
|
+
header.p_paddr = vma
|
267
|
+
header.p_flags = 4 | 1 # r-x
|
268
|
+
header.p_align = arch_align
|
269
|
+
header
|
270
|
+
end
|
271
|
+
|
272
|
+
# Not sure how this field is used, remove this if it is not important.
|
273
|
+
# This table is collected by cross-compiling and see the align in LOAD segment.
|
274
|
+
def arch_align
|
275
|
+
case context.arch.to_sym
|
276
|
+
when :i386, :amd64 then 0x1000
|
277
|
+
when :arm then 0x8000
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Mapping +context.arch+ to +::ELFTools::Constants::EM::EM_*+.
|
282
|
+
ARCH_EM = {
|
283
|
+
aarch64: 'AARCH64',
|
284
|
+
alpha: 'ALPHA',
|
285
|
+
amd64: 'X86_64',
|
286
|
+
arm: 'ARM',
|
287
|
+
cris: 'CRIS',
|
288
|
+
i386: '386',
|
289
|
+
ia64: 'IA_64',
|
290
|
+
m68k: '68K',
|
291
|
+
mips64: 'MIPS',
|
292
|
+
mips: 'MIPS',
|
293
|
+
powerpc64: 'PPC64',
|
294
|
+
powerpc: 'PPC',
|
295
|
+
s390: 'S390',
|
296
|
+
sparc64: 'SPARCV9',
|
297
|
+
sparc: 'SPARC'
|
298
|
+
}.freeze
|
299
|
+
|
300
|
+
def e_machine
|
301
|
+
const = ARCH_EM[context.arch.to_sym]
|
302
|
+
if const.nil?
|
303
|
+
raise ::Pwnlib::Errors::UnsupportedArchError,
|
304
|
+
"Unknown machine type of architecture #{context.arch.inspect}."
|
305
|
+
end
|
306
|
+
::ELFTools::Constants::EM.const_get("EM_#{const}")
|
307
|
+
end
|
308
|
+
|
309
|
+
def endian
|
310
|
+
context.endian.to_sym
|
311
|
+
end
|
312
|
+
|
143
313
|
include ::Pwnlib::Context
|
144
314
|
end
|
145
315
|
end
|
@@ -4,6 +4,7 @@ require 'dentaku'
|
|
4
4
|
|
5
5
|
require 'pwnlib/constants/constant'
|
6
6
|
require 'pwnlib/context'
|
7
|
+
require 'pwnlib/errors'
|
7
8
|
|
8
9
|
module Pwnlib
|
9
10
|
# Module containing constants.
|
@@ -36,17 +37,23 @@ module Pwnlib
|
|
36
37
|
# @return [Constant]
|
37
38
|
# The evaluated result.
|
38
39
|
#
|
40
|
+
# @raise [Pwnlib::Errors::ConstantNotFoundError]
|
41
|
+
# Raised when undefined constant(s) provided.
|
42
|
+
#
|
39
43
|
# @example
|
40
|
-
# eval('O_CREAT')
|
44
|
+
# Constants.eval('O_CREAT')
|
41
45
|
# #=> Constant('(O_CREAT)', 0x40)
|
42
|
-
# eval('O_CREAT | O_APPEND')
|
46
|
+
# Constants.eval('O_CREAT | O_APPEND')
|
43
47
|
# #=> Constant('(O_CREAT | O_APPEND)', 0x440)
|
48
|
+
# @example
|
49
|
+
# Constants.eval('meow')
|
50
|
+
# # Pwnlib::Errors::ConstantNotFoundError: Undefined constant(s): meow
|
44
51
|
def eval(str)
|
45
52
|
return str unless str.instance_of?(String)
|
46
53
|
begin
|
47
54
|
val = calculator.evaluate!(str.strip).to_i
|
48
55
|
rescue Dentaku::UnboundVariableError => e
|
49
|
-
raise
|
56
|
+
raise ::Pwnlib::Errors::ConstantNotFoundError, "Undefined constant(s): #{e.unbound_variables.join(', ')}"
|
50
57
|
end
|
51
58
|
::Pwnlib::Constants::Constant.new("(#{str})", val)
|
52
59
|
end
|
data/lib/pwnlib/context.rb
CHANGED
@@ -274,9 +274,7 @@ module Pwnlib
|
|
274
274
|
when true, false
|
275
275
|
signed = value
|
276
276
|
end
|
277
|
-
if signed.nil?
|
278
|
-
raise ArgumentError, "signed must be boolean or one of #{SIGNEDNESSES.keys.sort.inspect}"
|
279
|
-
end
|
277
|
+
raise ArgumentError, "signed must be boolean or one of #{SIGNEDNESSES.keys.sort.inspect}" if signed.nil?
|
280
278
|
@attrs[:signed] = signed
|
281
279
|
end
|
282
280
|
|
data/lib/pwnlib/elf/elf.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
require 'elftools'
|
2
1
|
require 'ostruct'
|
2
|
+
|
3
|
+
require 'elftools'
|
3
4
|
require 'rainbow'
|
4
5
|
|
5
6
|
require 'pwnlib/logger'
|
@@ -40,7 +41,7 @@ module Pwnlib
|
|
40
41
|
# #=> #<Pwnlib::ELF::ELF:0x00559bd670dcb8>
|
41
42
|
def initialize(path, checksec: true)
|
42
43
|
path = File.realpath(path)
|
43
|
-
@elf_file = ELFTools::ELFFile.new(File.open(path, 'rb'))
|
44
|
+
@elf_file = ELFTools::ELFFile.new(File.open(path, 'rb'))
|
44
45
|
load_got
|
45
46
|
load_plt
|
46
47
|
load_symbols
|
@@ -67,7 +68,6 @@ module Pwnlib
|
|
67
68
|
[@got, @plt, @symbols].compact.each do |tbl|
|
68
69
|
tbl.each_pair { |k, _| tbl[k] += val - old }
|
69
70
|
end
|
70
|
-
val
|
71
71
|
end
|
72
72
|
|
73
73
|
# Return the protection information, wrapper with color codes.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
module Pwnlib
|
4
|
+
# Generic {Pwnlib} exception class.
|
5
|
+
class Error < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Pnwlib Errors
|
9
|
+
module Errors
|
10
|
+
# Raised by some IO operations in tubes.
|
11
|
+
class EndOfTubeError < ::Pwnlib::Error
|
12
|
+
end
|
13
|
+
|
14
|
+
# Raised when a dependent file fails to load.
|
15
|
+
class DependencyError < ::Pwnlib::Error
|
16
|
+
end
|
17
|
+
|
18
|
+
# Raised when a given constant is invalid or undefined.
|
19
|
+
class ConstantNotFoundError < ::Pwnlib::Error
|
20
|
+
end
|
21
|
+
|
22
|
+
# Raised when timeout exceeded.
|
23
|
+
class TimeoutError < ::Pwnlib::Error
|
24
|
+
end
|
25
|
+
|
26
|
+
# Raised when method doesn't support under current architecture.
|
27
|
+
class UnsupportedArchError < ::Pwnlib::Error
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/pwnlib/ext/helper.rb
CHANGED
data/lib/pwnlib/logger.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
# encoding: ASCII-8BIT
|
2
2
|
|
3
3
|
require 'logger'
|
4
|
+
|
5
|
+
require 'method_source/code_helpers' # don't require 'method_source', it pollutes Method/Proc classes.
|
4
6
|
require 'rainbow'
|
7
|
+
require 'ruby2ruby'
|
8
|
+
require 'ruby_parser'
|
5
9
|
|
6
10
|
require 'pwnlib/context'
|
7
11
|
|
@@ -13,6 +17,12 @@ module Pwnlib
|
|
13
17
|
# The type for logger which inherits Ruby builtin Logger.
|
14
18
|
# Main difference is using +context.log_level+ instead of +level+ in logging methods.
|
15
19
|
class LoggerType < ::Logger
|
20
|
+
# To use method +expression_at+.
|
21
|
+
#
|
22
|
+
# XXX(david942j): move this extension if other modules need +expression_at+ as well.
|
23
|
+
extend ::MethodSource::CodeHelpers
|
24
|
+
|
25
|
+
# Color codes for pretty logging.
|
16
26
|
SEV_COLOR = {
|
17
27
|
'DEBUG' => '#ff5f5f',
|
18
28
|
'INFO' => '#87ff00',
|
@@ -24,8 +34,8 @@ module Pwnlib
|
|
24
34
|
# Instantiate a {Pwnlib::Logger::LoggerType} object.
|
25
35
|
def initialize
|
26
36
|
super(STDOUT)
|
27
|
-
@formatter = proc do |severity, _datetime,
|
28
|
-
format("[%s] %s\n", Rainbow(severity).color(SEV_COLOR[severity]), msg)
|
37
|
+
@formatter = proc do |severity, _datetime, progname, msg|
|
38
|
+
format("[%s] %s\n", Rainbow(progname || severity).color(SEV_COLOR[severity]), msg)
|
29
39
|
end
|
30
40
|
end
|
31
41
|
|
@@ -43,6 +53,70 @@ module Pwnlib
|
|
43
53
|
true
|
44
54
|
end
|
45
55
|
|
56
|
+
# Log the arguments and their evalutated results.
|
57
|
+
#
|
58
|
+
# This method has same severity as +INFO+.
|
59
|
+
#
|
60
|
+
# The difference between using arguments and passing a block is the block will be executed if the logger's level
|
61
|
+
# is sufficient to log a message.
|
62
|
+
#
|
63
|
+
# @param [Array<#inspect>] args
|
64
|
+
# Anything. See examples.
|
65
|
+
#
|
66
|
+
# @yieldreturn [#inspect]
|
67
|
+
# See examples.
|
68
|
+
# Block will be invoked only if +args+ is empty.
|
69
|
+
#
|
70
|
+
# @return See ::Logger#add.
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# x = 2
|
74
|
+
# y = 3
|
75
|
+
# log.dump(x + y, x * y)
|
76
|
+
# # [DUMP] (x + y) = 5, (x * y) = 6
|
77
|
+
# @example
|
78
|
+
# libc = 0x7fc0bdd13000
|
79
|
+
# log.dump libc.hex
|
80
|
+
# # [DUMP] libc.hex = "0x7fc0bdd13000"
|
81
|
+
#
|
82
|
+
# libc = 0x7fc0bdd13000
|
83
|
+
# log.dump { libc.hex }
|
84
|
+
# # [DUMP] libc.hex = "0x7fc0bdd13000"
|
85
|
+
# log.dump { libc = 12345678; libc.hex }
|
86
|
+
# # [DUMP] libc = 12345678
|
87
|
+
# # libc.hex = "0xbc614e"
|
88
|
+
# @example
|
89
|
+
# log.dump do
|
90
|
+
# meow = 123
|
91
|
+
# # comments will be ignored
|
92
|
+
# meow <<= 1 # this is a comment
|
93
|
+
# meow
|
94
|
+
# end
|
95
|
+
# # [DUMP] meow = 123
|
96
|
+
# # meow = (meow << 1)
|
97
|
+
# # meow = 246
|
98
|
+
#
|
99
|
+
# @note
|
100
|
+
# This method does NOT work in a REPL shell (such as irb and pry).
|
101
|
+
#
|
102
|
+
# @note
|
103
|
+
# The source code where invoked +log.dump+ will be parsed by using +ruby_parser+,
|
104
|
+
# therefore this method fails in some situations, such as:
|
105
|
+
# log.dump(&something) # will fail in souce code parsing
|
106
|
+
# log.dump { 1 }; log.dump { 2 } # 1 will be logged two times
|
107
|
+
def dump(*args)
|
108
|
+
severity = INFO
|
109
|
+
# Don't invoke the block if it's unnecessary.
|
110
|
+
return true if severity < context.log_level
|
111
|
+
caller_ = caller_locations(1, 1).first
|
112
|
+
src = source_of(caller_.absolute_path, caller_.lineno)
|
113
|
+
results = args.empty? ? [[yield, source_of_block(src)]] : args.zip(source_of_args(src))
|
114
|
+
msg = results.map { |res, expr| "#{expr.strip} = #{res.inspect}" }.join(', ')
|
115
|
+
# do indent if msg contains multiple lines
|
116
|
+
first, *remain = msg.split("\n")
|
117
|
+
add(severity, ([first] + remain.map { |r| '[DUMP] '.gsub(/./, ' ') + r }).join("\n"), 'DUMP')
|
118
|
+
end
|
119
|
+
|
46
120
|
private
|
47
121
|
|
48
122
|
def add(severity, message = nil, progname = nil)
|
@@ -51,6 +125,70 @@ module Pwnlib
|
|
51
125
|
super(severity, message, progname)
|
52
126
|
end
|
53
127
|
|
128
|
+
def source_of(path, line_number)
|
129
|
+
File.open(path) { |f| LoggerType.expression_at(f, line_number) }
|
130
|
+
end
|
131
|
+
|
132
|
+
# Find the content of block that invoked by log.dump { ... }.
|
133
|
+
#
|
134
|
+
# @param [String] source
|
135
|
+
#
|
136
|
+
# @return [String]
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
# source_of_block("log.dump do\n123\n456\nend")
|
140
|
+
# #=> "123\n456\n"
|
141
|
+
def source_of_block(source)
|
142
|
+
parse_and_search(source, [:iter, [:call, nil, :dump]]) { |sexp| ::Ruby2Ruby.new.process(sexp.last) }
|
143
|
+
end
|
144
|
+
|
145
|
+
# Find the arguments passed to log.dump(...).
|
146
|
+
#
|
147
|
+
# @param [String] source
|
148
|
+
#
|
149
|
+
# @return [Array<String>]
|
150
|
+
#
|
151
|
+
# @example
|
152
|
+
# source_of_args("log.dump(x, y, x + y)")
|
153
|
+
# #=> ["x", "y", "(x + y)"]
|
154
|
+
def source_of_args(source)
|
155
|
+
parse_and_search(source, [:call, nil, :dump]) do |sexp|
|
156
|
+
sexp[3..-1].map { |s| ::Ruby2Ruby.new.process(s) }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# This method do the following things:
|
161
|
+
# 1. Parse the source code to `Sexp` (using `ruby_parser`)
|
162
|
+
# 2. Traverse the sexp to find the block/arguments (according to target) when calling `dump`
|
163
|
+
# 3. Convert the sexp of block back to Ruby code (using gem `ruby2ruby`)
|
164
|
+
#
|
165
|
+
# @yieldparam [Sexp] sexp
|
166
|
+
# The found Sexp according to +target+.
|
167
|
+
def parse_and_search(source, target)
|
168
|
+
sexp = ::RubyParser.new.process(source)
|
169
|
+
sexp = search_sexp(sexp, target)
|
170
|
+
return nil if sexp.nil?
|
171
|
+
yield sexp
|
172
|
+
end
|
173
|
+
|
174
|
+
# depth-first search
|
175
|
+
def search_sexp(sexp, target)
|
176
|
+
return nil unless sexp.is_a?(::Sexp)
|
177
|
+
return sexp if match_sexp?(sexp, target)
|
178
|
+
sexp.find do |e|
|
179
|
+
f = search_sexp(e, target)
|
180
|
+
break f if f
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def match_sexp?(sexp, target)
|
185
|
+
target.zip(sexp.entries).all? do |t, s|
|
186
|
+
next true if t.nil?
|
187
|
+
next match_sexp?(s, t) if t.is_a?(Array)
|
188
|
+
s == t
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
54
192
|
include ::Pwnlib::Context
|
55
193
|
end
|
56
194
|
|