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,52 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'pwnlib/shellcraft/generators/x86/common/setregs'
|
4
|
+
require 'pwnlib/shellcraft/generators/x86/linux/linux'
|
5
|
+
|
6
|
+
module Pwnlib
|
7
|
+
module Shellcraft
|
8
|
+
module Generators
|
9
|
+
module X86
|
10
|
+
module Linux
|
11
|
+
# Assembly of +syscall+.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# context.arch = 'i386'
|
15
|
+
# puts shellcraft.syscall('SYS_open', 'esp', 0, 0)
|
16
|
+
# # /* call open("esp", 0, 0) */
|
17
|
+
# # push 5 /* (SYS_open) */
|
18
|
+
# # pop eax
|
19
|
+
# # mov ebx, esp
|
20
|
+
# # xor ecx, ecx /* 0 */
|
21
|
+
# # cdq /* edx=0 */
|
22
|
+
# # int 0x80
|
23
|
+
# #=> nil
|
24
|
+
def syscall(*arguments)
|
25
|
+
abi = ::Pwnlib::ABI::ABI.syscall
|
26
|
+
syscall, arg0, arg1, arg2, arg3, arg4, arg5 = arguments
|
27
|
+
if syscall.respond_to?(:to_s) && syscall.to_s.start_with?('SYS_')
|
28
|
+
syscall_repr = syscall.to_s[4..-1] + '(%s)'
|
29
|
+
args = []
|
30
|
+
else
|
31
|
+
syscall_repr = 'syscall(%s)'
|
32
|
+
args = [syscall ? syscall.inspect : '?']
|
33
|
+
end
|
34
|
+
# arg0 to arg5
|
35
|
+
1.upto(6) do |i|
|
36
|
+
args.push(arguments[i] ? arguments[i].inspect : '?')
|
37
|
+
end
|
38
|
+
|
39
|
+
args.pop while args.last == '?'
|
40
|
+
syscall_repr = format(syscall_repr, args.join(', '))
|
41
|
+
registers = abi.register_arguments
|
42
|
+
arguments = [syscall, arg0, arg1, arg2, arg3, arg4, arg5]
|
43
|
+
reg_ctx = registers.zip(arguments).to_h
|
44
|
+
cat "/* call #{syscall_repr} */"
|
45
|
+
cat Common.setregs(reg_ctx) if arguments.any? { |v| !v.nil? }
|
46
|
+
cat abi.syscall_str
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'pwnlib/context'
|
2
|
+
|
3
|
+
module Pwnlib
|
4
|
+
module Shellcraft
|
5
|
+
# Define register names and methods for shellcode generators.
|
6
|
+
module Registers
|
7
|
+
X86_BASEREGS = %w(ax cx dx bx sp bp si di ip).freeze
|
8
|
+
|
9
|
+
I386 = (X86_BASEREGS.map { |r| "e#{r}" } +
|
10
|
+
X86_BASEREGS +
|
11
|
+
%w(eflags cs ss ds es fs gs)).freeze
|
12
|
+
|
13
|
+
AMD64 = (X86_BASEREGS.map { |r| "r#{r}" } +
|
14
|
+
(8..15).map { |r| "r#{r}" } +
|
15
|
+
(8..15).map { |r| "r#{r}d" } +
|
16
|
+
I386).freeze
|
17
|
+
|
18
|
+
# x86 registers in decreasing size
|
19
|
+
X86_ORDERED = ([
|
20
|
+
%w(rax eax ax al),
|
21
|
+
%w(rbx ebx bx bl),
|
22
|
+
%w(rcx ecx cx cl),
|
23
|
+
%w(rdx edx dx dl),
|
24
|
+
%w(rdi edi di),
|
25
|
+
%w(rsi esi si),
|
26
|
+
%w(rbp ebp bp),
|
27
|
+
%w(rsp esp sp)
|
28
|
+
] + (8..15).map { |r| ['', 'd', 'w', 'b'].map { |t| "r#{r}#{t}" } }).freeze
|
29
|
+
|
30
|
+
# class Register, currently only supports i386 and amd64.
|
31
|
+
class Register
|
32
|
+
# @return [String]
|
33
|
+
# Register's name.
|
34
|
+
attr_reader :name
|
35
|
+
attr_reader :bigger, :smaller, :ff00, :is64bit, :native64, :native32, :xor
|
36
|
+
attr_reader :size, :sizes
|
37
|
+
|
38
|
+
# Instantiate a {Register} object.
|
39
|
+
#
|
40
|
+
# Create a register by its name and size (in bits) for fetching other information. For example, for register
|
41
|
+
# 'ax', +#bigger+ contains 'rax' and 'eax'.
|
42
|
+
#
|
43
|
+
# Normally you don't need to create any {Register} object, use {.get_register} to get register by name.
|
44
|
+
#
|
45
|
+
# @param [String] name
|
46
|
+
# Register's name.
|
47
|
+
# @param [Integer] size
|
48
|
+
# Register size in bits.
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# Register.new('rax', 64)
|
52
|
+
# Register.new('bx', 16)
|
53
|
+
def initialize(name, size)
|
54
|
+
@name = name
|
55
|
+
@size = size
|
56
|
+
X86_ORDERED.each do |row|
|
57
|
+
next unless row.include?(name)
|
58
|
+
@bigger = row[0, row.index(name)]
|
59
|
+
@smaller = row[(row.index(name) + 1)..-1]
|
60
|
+
@native64 = row[0]
|
61
|
+
@native32 = row[1]
|
62
|
+
@sizes = row.each_with_object({}).with_index { |(r, h), i| h[64 >> i] = r }
|
63
|
+
@xor = @sizes[[size, 32].min]
|
64
|
+
break
|
65
|
+
end
|
66
|
+
@ff00 = name[1] + 'h' if @size >= 32 && @name.end_with?('x')
|
67
|
+
@is64bit = true if @name.start_with?('r')
|
68
|
+
end
|
69
|
+
|
70
|
+
def bits
|
71
|
+
size
|
72
|
+
end
|
73
|
+
|
74
|
+
def bytes
|
75
|
+
bits / 8
|
76
|
+
end
|
77
|
+
|
78
|
+
def fits(value)
|
79
|
+
size >= Registers.bits_required(value)
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_s
|
83
|
+
name
|
84
|
+
end
|
85
|
+
|
86
|
+
def inspect
|
87
|
+
format('Register(%s)', name)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
module_function
|
92
|
+
|
93
|
+
def registers
|
94
|
+
{
|
95
|
+
[32, 'i386', 'linux'] => ::Pwnlib::Shellcraft::Registers::I386,
|
96
|
+
[64, 'amd64', 'linux'] => ::Pwnlib::Shellcraft::Registers::AMD64
|
97
|
+
}[[context.bits, context.arch, context.os]]
|
98
|
+
end
|
99
|
+
|
100
|
+
INTEL = (X86_ORDERED.each_with_object({}) do |row, obj|
|
101
|
+
row.each_with_index do |reg, i|
|
102
|
+
obj[reg] = Register.new(reg, 64 >> i)
|
103
|
+
end
|
104
|
+
end).freeze
|
105
|
+
|
106
|
+
# Get a {Register} object by name.
|
107
|
+
#
|
108
|
+
# @param [String, Symbol, Register] name
|
109
|
+
# The name of register.
|
110
|
+
# If +name+ is already a {Register} object, +name+ itself will be returned.
|
111
|
+
#
|
112
|
+
# @return [Register?]
|
113
|
+
# Get the register with name +name+.
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
# Registers.get_register('eax')
|
117
|
+
# #=> Register(eax)
|
118
|
+
# Registers.get_register(:ebx)
|
119
|
+
# #=> Register(ebx)
|
120
|
+
# Registers.get_register('xdd')
|
121
|
+
# #=> nil
|
122
|
+
def get_register(name)
|
123
|
+
return name if name.instance_of?(Register)
|
124
|
+
return INTEL[name.to_s] if name.instance_of?(String) || name.instance_of?(Symbol)
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
|
128
|
+
def register?(obj)
|
129
|
+
!get_register(obj).nil?
|
130
|
+
end
|
131
|
+
|
132
|
+
def bits_required(value)
|
133
|
+
bits = 0
|
134
|
+
value = value.abs
|
135
|
+
while value > 0
|
136
|
+
bits += 8
|
137
|
+
value >>= 8
|
138
|
+
end
|
139
|
+
bits
|
140
|
+
end
|
141
|
+
|
142
|
+
include ::Pwnlib::Context
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
require 'pwnlib/context'
|
6
|
+
require 'pwnlib/logger'
|
7
|
+
|
8
|
+
module Pwnlib
|
9
|
+
# Implement shellcraft!
|
10
|
+
#
|
11
|
+
# All shellcode generators are defined under generators/*.
|
12
|
+
# While typing +Shellcraft::Generators::I386::Linux.sh+ is too annoying, we define an instance +shellcraft+ in this
|
13
|
+
# module, which let user invoke +shellcraft.sh+ directly.
|
14
|
+
module Shellcraft
|
15
|
+
# Singleton class.
|
16
|
+
class Shellcraft
|
17
|
+
include ::Singleton
|
18
|
+
|
19
|
+
# All files under generators/ will be required.
|
20
|
+
def initialize
|
21
|
+
Dir[File.join(__dir__, 'generators', '**', '*.rb')].each do |f|
|
22
|
+
require f
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Will search modules/methods under {Shellcraft::Generators} according to current arch and os.
|
27
|
+
# i.e. +Shellcraft::Generators::${arch}::<Common|${os}>.${method}+.
|
28
|
+
#
|
29
|
+
# With this method, +context.local(arch: 'amd64') { shellcraft.sh }+ will invoke
|
30
|
+
# {Shellcraft::Generators::Amd64::Linux#sh}.
|
31
|
+
def method_missing(method, *args, &block)
|
32
|
+
mod = find_module_for(method)
|
33
|
+
return super if mod.nil?
|
34
|
+
mod.public_send(method, *args, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
# For +respond_to?+.
|
38
|
+
def respond_to_missing?(method, include_private = false)
|
39
|
+
return true if find_module_for(method)
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# @return [Module?]
|
46
|
+
# +nil+ for not found.
|
47
|
+
def find_module_for(method)
|
48
|
+
begin
|
49
|
+
arch_module = ::Pwnlib::Shellcraft::Generators.const_get(context.arch.capitalize)
|
50
|
+
rescue NameError
|
51
|
+
log.error("Can't use shellcraft under architecture #{context.arch.inspect}.")
|
52
|
+
return nil
|
53
|
+
end
|
54
|
+
# try search in Common module
|
55
|
+
common_module = arch_module.const_get(:Common)
|
56
|
+
return common_module if common_module.singleton_methods.include?(method)
|
57
|
+
# search in ${os} module
|
58
|
+
os_module = arch_module.const_get(context.os.capitalize)
|
59
|
+
return os_module if os_module.singleton_methods.include?(method)
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
include ::Pwnlib::Context
|
64
|
+
include ::Pwnlib::Logger
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/pwnlib/timer.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
require 'pwnlib/context'
|
6
|
+
|
7
|
+
module Pwnlib
|
8
|
+
# A simple timer class.
|
9
|
+
# TODO(Darkpi): Python pwntools seems to have many unreasonable codes in this class,
|
10
|
+
# not sure of the use case of this, check if everything is coded as
|
11
|
+
# intended after we have some use cases. (e.g. sock)
|
12
|
+
# NOTE(Darkpi): This class is actually quite weird, and expected to be used only in tubes.
|
13
|
+
class Timer
|
14
|
+
# @diff We just use nil for default and :forever for forever.
|
15
|
+
|
16
|
+
def initialize(timeout = nil)
|
17
|
+
@deadline = nil
|
18
|
+
@timeout = timeout
|
19
|
+
end
|
20
|
+
|
21
|
+
def started?
|
22
|
+
@deadline
|
23
|
+
end
|
24
|
+
|
25
|
+
def active?
|
26
|
+
started? && (@deadline == :forever || Time.now < @deadline)
|
27
|
+
end
|
28
|
+
|
29
|
+
def timeout
|
30
|
+
return @timeout || Pwnlib::Context.context.timeout unless started?
|
31
|
+
@deadline == :forever ? :forever : [@deadline - Time.now, 0].max
|
32
|
+
end
|
33
|
+
|
34
|
+
def timeout=(timeout)
|
35
|
+
raise "Can't change timeout when countdown" if started?
|
36
|
+
@timeout = timeout
|
37
|
+
end
|
38
|
+
|
39
|
+
# @diff We do NOT allow nested countdown with non-default value. This simplifies thing a lot.
|
40
|
+
# NOTE(Darkpi): timeout = nil means default value for the first time, and nop after that.
|
41
|
+
def countdown(timeout = nil)
|
42
|
+
raise ArgumentError, 'Need a block for countdown' unless block_given?
|
43
|
+
if started?
|
44
|
+
return yield if timeout.nil?
|
45
|
+
raise 'Nested countdown not permitted'
|
46
|
+
end
|
47
|
+
|
48
|
+
timeout ||= @timeout
|
49
|
+
timeout ||= Pwnlib::Context.context.timeout
|
50
|
+
|
51
|
+
@deadline = timeout == :forever ? :forever : Time.now + timeout
|
52
|
+
|
53
|
+
begin
|
54
|
+
yield
|
55
|
+
ensure
|
56
|
+
@deadline = nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
module Pwnlib
|
4
|
+
module Tubes
|
5
|
+
# Buffer that support deque-like string operations.
|
6
|
+
class Buffer
|
7
|
+
# Instantiate a {Pwnlib::Tubes::Buffer} object.
|
8
|
+
def initialize
|
9
|
+
@data = []
|
10
|
+
@size = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :size
|
14
|
+
alias length size
|
15
|
+
|
16
|
+
# Check whether the buffer is empty.
|
17
|
+
#
|
18
|
+
# @return [Boalean]
|
19
|
+
# Returns true if contains no elements.
|
20
|
+
def empty?
|
21
|
+
size.zero?
|
22
|
+
end
|
23
|
+
|
24
|
+
# Python __contains__ and index is only correct with single-char input, which doesn't seems to
|
25
|
+
# be useful, and they're not used anywhere either. Just ignore them for now.
|
26
|
+
|
27
|
+
# Adds data to the buffer.
|
28
|
+
#
|
29
|
+
# @param [String] data
|
30
|
+
# Data to add.
|
31
|
+
def add(data)
|
32
|
+
case data
|
33
|
+
when Buffer
|
34
|
+
@data.concat(data.data)
|
35
|
+
else
|
36
|
+
data = data.to_s.dup
|
37
|
+
return if data.empty?
|
38
|
+
@data << data
|
39
|
+
end
|
40
|
+
@size += data.size
|
41
|
+
self
|
42
|
+
end
|
43
|
+
alias << add
|
44
|
+
|
45
|
+
# Places data at the front of the buffer.
|
46
|
+
#
|
47
|
+
# @param [String] data
|
48
|
+
# Data to place at the beginning of the buffer.
|
49
|
+
def unget(data)
|
50
|
+
case data
|
51
|
+
when Buffer
|
52
|
+
@data.unshift(*data.data)
|
53
|
+
else
|
54
|
+
data = data.to_s.dup
|
55
|
+
return if data.empty?
|
56
|
+
@data.unshift(data)
|
57
|
+
end
|
58
|
+
@size += data.size
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
# Retrieves bytes from the buffer.
|
63
|
+
#
|
64
|
+
# @param [Integer] n
|
65
|
+
# Maximum number of bytes to fetch.
|
66
|
+
#
|
67
|
+
# @return [String]
|
68
|
+
# Data as string.
|
69
|
+
def get(n = nil)
|
70
|
+
if n.nil? || n >= size
|
71
|
+
data = @data.join
|
72
|
+
@size = 0
|
73
|
+
@data = []
|
74
|
+
else
|
75
|
+
have = 0
|
76
|
+
idx = 0
|
77
|
+
while have < n
|
78
|
+
have += @data[idx].size
|
79
|
+
idx += 1
|
80
|
+
end
|
81
|
+
data = @data.slice!(0...idx).join
|
82
|
+
if have > n
|
83
|
+
extra = data.slice!(n..-1)
|
84
|
+
@data.unshift(extra)
|
85
|
+
end
|
86
|
+
@size -= n
|
87
|
+
end
|
88
|
+
data
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
attr_reader :data
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
require 'pwnlib/tubes/tube'
|
6
|
+
|
7
|
+
module Pwnlib
|
8
|
+
module Tubes
|
9
|
+
# Socket!
|
10
|
+
class Sock < Tube
|
11
|
+
# Instantiate a {Pwnlib::Tubes::Sock} object.
|
12
|
+
#
|
13
|
+
# @param [String] host
|
14
|
+
# The host to connect.
|
15
|
+
# @param [Integer] port
|
16
|
+
# The port to connect.
|
17
|
+
def initialize(host, port)
|
18
|
+
super()
|
19
|
+
@sock = TCPSocket.new(host, port)
|
20
|
+
@sock.binmode
|
21
|
+
@timeout = nil
|
22
|
+
@closed = { recv: false, send: false }
|
23
|
+
end
|
24
|
+
|
25
|
+
def io
|
26
|
+
@sock
|
27
|
+
end
|
28
|
+
alias sock io
|
29
|
+
|
30
|
+
# Close the TCPSocket if no arguments passed.
|
31
|
+
# Or close the direction in +sock+.
|
32
|
+
#
|
33
|
+
# @param [:both, :recv, :read, :send, :write] direction
|
34
|
+
# * Close the TCPSocket if +:both+ or no arguments passed.
|
35
|
+
# * Disallow further read in +sock+ if +:recv+ or +:read+ passed.
|
36
|
+
# * Disallow further write in +sock+ if +:send+ or +:write+ passed.
|
37
|
+
#
|
38
|
+
# @diff In pwntools-python, method +shutdown(direction)+ is for closing socket one side,
|
39
|
+
# +close()+ is for closing both side. We merge these two methods into one here.
|
40
|
+
def close(direction = :both)
|
41
|
+
case direction
|
42
|
+
when :both
|
43
|
+
return if @sock.closed?
|
44
|
+
@closed[:recv] = @closed[:send] = true
|
45
|
+
@sock.close
|
46
|
+
when :recv, :read
|
47
|
+
shutdown(:recv)
|
48
|
+
when :send, :write
|
49
|
+
shutdown(:send)
|
50
|
+
else
|
51
|
+
raise ArgumentError, 'Only allow :both, :recv, :read, :send and :write passed'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def shutdown(direction)
|
58
|
+
return if @closed[direction]
|
59
|
+
@closed[direction] = true
|
60
|
+
|
61
|
+
if direction.equal?(:recv)
|
62
|
+
@sock.close_read
|
63
|
+
elsif direction.equal?(:send)
|
64
|
+
@sock.close_write
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def timeout_raw=(timeout)
|
69
|
+
@timeout = timeout == :forever ? nil : timeout
|
70
|
+
end
|
71
|
+
|
72
|
+
def send_raw(data)
|
73
|
+
raise EOFError if @closed[:send]
|
74
|
+
begin
|
75
|
+
@sock.write(data)
|
76
|
+
rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED
|
77
|
+
shutdown(:send)
|
78
|
+
raise EOFError
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def recv_raw(size)
|
83
|
+
raise EOFError if @closed[:recv]
|
84
|
+
begin
|
85
|
+
rs, = IO.select([@sock], [], [], @timeout)
|
86
|
+
return if rs.nil?
|
87
|
+
return @sock.readpartial(size)
|
88
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, EOFError
|
89
|
+
shutdown(:recv)
|
90
|
+
raise EOFError
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|