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