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,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
|