binproxy 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 +7 -0
- data/bin/binproxy +135 -0
- data/lib/binproxy.rb +2 -0
- data/lib/binproxy/bd_util.rb +267 -0
- data/lib/binproxy/bindata.rb +44 -0
- data/lib/binproxy/class_loader.rb +64 -0
- data/lib/binproxy/connection.rb +105 -0
- data/lib/binproxy/connection/filters.rb +190 -0
- data/lib/binproxy/logger.rb +17 -0
- data/lib/binproxy/parser.rb +76 -0
- data/lib/binproxy/parsers/chat_demo.rb +14 -0
- data/lib/binproxy/parsers/dns.rb +70 -0
- data/lib/binproxy/parsers/dumb_http.rb +28 -0
- data/lib/binproxy/parsers/msgpack.rb +56 -0
- data/lib/binproxy/parsers/plain_text.rb +4 -0
- data/lib/binproxy/parsers/raw_message.rb +4 -0
- data/lib/binproxy/parsers/x11_proto.rb +134 -0
- data/lib/binproxy/parsers/zmq.rb +62 -0
- data/lib/binproxy/proxy.rb +242 -0
- data/lib/binproxy/proxy_event.rb +57 -0
- data/lib/binproxy/proxy_message.rb +191 -0
- data/lib/binproxy/session.rb +47 -0
- data/lib/binproxy/web_console.rb +194 -0
- data/public/bright_squares.png +0 -0
- data/public/ui/app.js +54910 -0
- data/public/ui/fixed-data-table.css +509 -0
- data/views/application.scss +335 -0
- data/views/common.scss +90 -0
- data/views/config.haml +54 -0
- data/views/config.scss +15 -0
- data/views/index.haml +15 -0
- metadata +325 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 44001220659cdaf6bbfc717584005b4f23942966
|
4
|
+
data.tar.gz: 9dd5864e6a3f35b5cdec8d87d8bf13d85a8572d3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 87561464e03a4db14d7fa9814d2e46ca0236cedf449f74aafa0a58eeb5121dfbcbe9c872f93d1b66980ffb46a84f0d44588c59a45392309ffe0887112fdcc9ed
|
7
|
+
data.tar.gz: c0e2f7a2fdf416ca685702028f435ff7f75307c0bcc6261219501ab8f5fc9fc72909006c1d9b4a3de7e22daf06fa74ee7a852fa4b8635f714951a8f1d5877dce
|
data/bin/binproxy
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
if RUBY_VERSION < "2.3"
|
4
|
+
throw "This program requires ruby 2.3 or later, but RUBY_VERSION is #{RUBY_VERSION}"
|
5
|
+
end
|
6
|
+
Encoding.default_external = 'BINARY'
|
7
|
+
|
8
|
+
require 'binproxy'
|
9
|
+
require 'binproxy/logger'
|
10
|
+
require 'binproxy/web_console'
|
11
|
+
require 'active_support/inflector'
|
12
|
+
require 'active_support/core_ext/hash/keys'
|
13
|
+
require 'active_support/core_ext/object/blank'
|
14
|
+
require 'json'
|
15
|
+
require 'trollop'
|
16
|
+
require 'haml'
|
17
|
+
require 'sass'
|
18
|
+
require 'colorize'
|
19
|
+
require 'tempfile'
|
20
|
+
|
21
|
+
opts = Trollop::options do
|
22
|
+
banner <<END
|
23
|
+
Usage: #{$0} [opts] [listen-host listen-port dest-host dest-port]
|
24
|
+
If listen-host is omitted, it is assumed to be localhost.
|
25
|
+
In --socks mode, dest-host and dest-port are optional and ignored.
|
26
|
+
|
27
|
+
[opts] are:
|
28
|
+
END
|
29
|
+
opt :class_name, "parser class", default: "RawMessage", short: 'c'
|
30
|
+
opt :class_file, "class file (default: guess from class name)", type: String, short: 'C'
|
31
|
+
|
32
|
+
opt :tls, "Unwrap TLS/SSL"
|
33
|
+
opt :tls_cert, "TLS/SSL certificate file (PEM format)", type: String, short: 'e'
|
34
|
+
opt :tls_key, "TLS/SSL private key file (PEM format)", type: String, short: 'k'
|
35
|
+
|
36
|
+
opt :http_host, "Run web server on this addr", default: 'localhost', short: 'o'
|
37
|
+
opt :http_port, "Run web server on this port", default: 4567, short: 'p'
|
38
|
+
|
39
|
+
opt :debug, "print debugging data to stdout", default: false, short: 'd'
|
40
|
+
opt :debug_extra, "as above, but extra verbose", default: false, short: 'D'
|
41
|
+
|
42
|
+
opt :socks_proxy, 'act as a SOCKSv4 proxy', default: false, short: 'S'
|
43
|
+
opt :http_proxy, 'act as an HTTP proxy (CONNECT only)', default: false, short: 'H'
|
44
|
+
#XXX the above two probably should be mutually exclusive
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
binproxy_root = File.absolute_path(File.join(File.dirname(__FILE__),'..'))
|
49
|
+
|
50
|
+
log = BinProxy::Logger::log
|
51
|
+
log.progname = 'BinProxy'
|
52
|
+
formatter = Logger::Formatter.new
|
53
|
+
log.formatter = lambda do |sev,date,prog,msg|
|
54
|
+
colors = {
|
55
|
+
DEBUG: :default,
|
56
|
+
INFO: :light_white,
|
57
|
+
WARN: :light_yellow,
|
58
|
+
ERROR: :red,
|
59
|
+
FATAL: :red,
|
60
|
+
}
|
61
|
+
#formatter.call(sev,date,prog,msg).colorize(colors[sev.to_sym] ||:light_blue)
|
62
|
+
line = caller.find {|l| !l.match /logger\.rb/ }
|
63
|
+
cm = line.match(%r|^.*/([^/]+):(\d+):in `(.*)'$|) or raise "Couldn't parse caller() line"
|
64
|
+
msg = msg.gsub /\r?\n/, ("\n" + " " * 25)
|
65
|
+
sprintf( "%-18s %-5s %s\n",
|
66
|
+
"#{cm[1]}:#{cm[2]}", sev, msg
|
67
|
+
).colorize(colors[sev.to_sym] ||:light_blue)
|
68
|
+
end
|
69
|
+
log.level = case
|
70
|
+
when opts[:debug_extra] then Logger::DEBUG
|
71
|
+
when opts[:debug] then Logger::INFO
|
72
|
+
else Logger::WARN
|
73
|
+
end
|
74
|
+
|
75
|
+
if opts[:tls]
|
76
|
+
if opts[:tls_cert] or opts[:tls_key]
|
77
|
+
Trollop::die :tls_cert, "is required when :tls_key is given" unless opts[:tls_cert]
|
78
|
+
Trollop::die :tls_key, "is required when :tls_cert is given" unless opts[:tls_key]
|
79
|
+
Trollop::die :tls_cert, "must be an existing file" unless File.exists?(opts[:tls_cert])
|
80
|
+
Trollop::die :tls_key, "must be an existing file" unless File.exists?(opts[:tls_key])
|
81
|
+
else
|
82
|
+
log.info("generating a self-signed TLS/SSL cert")
|
83
|
+
#EM currently only supports files, not in-memory strings for cert/key
|
84
|
+
#XXX confirm this no longer leaks files
|
85
|
+
opts[:tls_cert] = Tempfile.create('cert').path
|
86
|
+
opts[:tls_key] = Tempfile.create('key').path
|
87
|
+
out = "yes '' | openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout '#{opts[:ssl_key]}' -out '#{opts[:ssl_cert]}' >/dev/null"
|
88
|
+
log << out if log.debug?
|
89
|
+
system(out) #XXX
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
if [1,3].include? ARGV.length
|
94
|
+
log.debug 'assuming listen host is localhost'
|
95
|
+
ARGV.unshift 'localhost'
|
96
|
+
end
|
97
|
+
|
98
|
+
if ARGV.empty?
|
99
|
+
log.warn "No hosts/ports specified; please configure via the web interface."
|
100
|
+
elsif (opts[:socks_proxy] or opts[:http_proxy]) and ARGV.length == 2
|
101
|
+
opts.merge! Hash[([ :lhost, :lport ].zip(ARGV))]
|
102
|
+
log.info "Proxying from #{opts[:lhost]}:#{opts[:lport]} to dynamic destination"
|
103
|
+
elsif ARGV.length == 4
|
104
|
+
opts.merge! Hash[([ :lhost, :lport, :dhost, :dport ].zip(ARGV))]
|
105
|
+
log.info "Proxying from #{opts[:lhost]}:#{opts[:lport]} to #{opts[:dhost]}:#{opts[:dport]}"
|
106
|
+
else
|
107
|
+
Trollop::die "Unexpected number of host/port arguments"
|
108
|
+
end
|
109
|
+
|
110
|
+
log.info "Constructing Proxy..."
|
111
|
+
proxy = BinProxy::Proxy.new(binproxy_root, opts)
|
112
|
+
|
113
|
+
log.info "Constructing Web Console..."
|
114
|
+
web_console = Sinatra.new(BinProxy::WebConsole) do
|
115
|
+
set opts: opts
|
116
|
+
set proxy: proxy
|
117
|
+
set root: binproxy_root
|
118
|
+
set logging: nil unless opts[:debug_extra]
|
119
|
+
end
|
120
|
+
|
121
|
+
EventMachine.error_handler do |e|
|
122
|
+
log.err_trace e
|
123
|
+
end
|
124
|
+
|
125
|
+
log.info "Starting Server..."
|
126
|
+
EventMachine.run do
|
127
|
+
Rack::Server.start({
|
128
|
+
app: web_console,
|
129
|
+
server: 'thin',
|
130
|
+
Host: opts[:http_host],
|
131
|
+
Port: opts[:http_port],
|
132
|
+
})
|
133
|
+
Signal.trap('INT') { EM.stop_event_loop }
|
134
|
+
EM.next_tick { log.info "BinProxy is ready" }
|
135
|
+
end
|
data/lib/binproxy.rb
ADDED
@@ -0,0 +1,267 @@
|
|
1
|
+
require 'bindata'
|
2
|
+
|
3
|
+
=begin
|
4
|
+
class BinData::Base
|
5
|
+
def real_inspect
|
6
|
+
"#<#{self.class}:#{self.object_id} #{self.instance_variables.map do |v|
|
7
|
+
"#{v}=#{
|
8
|
+
tmp = self.instance_variable_get v
|
9
|
+
if tmp.respond_to? :real_inspect
|
10
|
+
tmp.real_inspect
|
11
|
+
else
|
12
|
+
tmp.inspect
|
13
|
+
end
|
14
|
+
}"
|
15
|
+
end.join ' '}>"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
=end
|
19
|
+
|
20
|
+
class BinData::Line < BinData::BasePrimitive
|
21
|
+
default_parameter line_end: "\n"
|
22
|
+
|
23
|
+
def sensible_default; ""; end
|
24
|
+
|
25
|
+
#XXX not sure if this does the right thing wrt non-ascii input
|
26
|
+
# can you slip CR or LF in via other encodings?
|
27
|
+
def value_to_binary_string(v)
|
28
|
+
v.to_s.chomp.dup.force_encoding('BINARY').chomp + eval_parameter(:line_end)
|
29
|
+
end
|
30
|
+
|
31
|
+
def read_and_return_value(io)
|
32
|
+
ch = nil
|
33
|
+
str = ''
|
34
|
+
loop do
|
35
|
+
ch = io.readbytes(1)
|
36
|
+
if ch == "\n"
|
37
|
+
break
|
38
|
+
elsif ch == "\r"
|
39
|
+
ch = io.readbytes(1)
|
40
|
+
io.raw_io.ungetbyte(ch) unless ch == "\n"
|
41
|
+
break
|
42
|
+
end
|
43
|
+
str << ch
|
44
|
+
end
|
45
|
+
str
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module BinData
|
50
|
+
class ArgProcessorWrapper
|
51
|
+
def initialize(original_processor, &blk)
|
52
|
+
@original = original_processor
|
53
|
+
@block = blk
|
54
|
+
end
|
55
|
+
|
56
|
+
def sanitize_parameters!(obj_class, obj_params)
|
57
|
+
@original.sanitize_parameters!(obj_class, obj_params)
|
58
|
+
@block.call(obj_class, obj_params)
|
59
|
+
end
|
60
|
+
|
61
|
+
def method_missing(sym, *args)
|
62
|
+
@original.send(sym, *args)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module ClassParametersPlugin
|
67
|
+
module ClassMethods
|
68
|
+
def class_parameter(sym)
|
69
|
+
( @class_params ||= [] ) << sym
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
def self.included(obj_class)
|
74
|
+
# Wrap the obj_class's existing arg_processor
|
75
|
+
old_arg_processor = obj_class.arg_processor
|
76
|
+
new_arg_processor = ArgProcessorWrapper.new(old_arg_processor) do |obj_class,params|
|
77
|
+
class_params = obj_class.class_eval {@class_params} || []
|
78
|
+
class_params.each do |type|
|
79
|
+
if params.needs_sanitizing?(type)
|
80
|
+
t,p = params[type]
|
81
|
+
params[type] = params.create_sanitized_object_prototype(t,p)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
obj_class.class_eval do
|
86
|
+
@arg_processor = new_arg_processor
|
87
|
+
end
|
88
|
+
|
89
|
+
# add class_parameter(:foo) to our class methods / DSL
|
90
|
+
obj_class.extend ClassMethods
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
=begin
|
98
|
+
I'm not sure there's a good way to make this work without a LOT of hackery...
|
99
|
+
|
100
|
+
# This class basically exists so we can have type params in DSL-based classes.
|
101
|
+
# See PascalString for an example
|
102
|
+
class BinData::Variant < BinData::BasePrimitive
|
103
|
+
extend HasClassParameters
|
104
|
+
mandatory_parameter :type
|
105
|
+
class_parameter :type
|
106
|
+
|
107
|
+
def initialize_instance
|
108
|
+
@obj = get_parameter(:type).instantiate(nil,self)
|
109
|
+
@default = @obj.snapshot #should test if reference types need dup here TODO
|
110
|
+
end
|
111
|
+
|
112
|
+
def value_to_binary_string(v)
|
113
|
+
@obj.assign(v)
|
114
|
+
@obj.to_binary_s
|
115
|
+
end
|
116
|
+
|
117
|
+
def read_and_return_value(io)
|
118
|
+
@obj.read(io)
|
119
|
+
@obj.snapshot
|
120
|
+
end
|
121
|
+
|
122
|
+
def sensible_default
|
123
|
+
@default
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
=end
|
128
|
+
|
129
|
+
# Like the Pascal String class shown in the docs, but with a variable type of
|
130
|
+
# length specifier.
|
131
|
+
class BinData::PascalString < BinData::BasePrimitive
|
132
|
+
include ::BinData::ClassParametersPlugin
|
133
|
+
default_parameter size_type: :uint8
|
134
|
+
class_parameter :size_type
|
135
|
+
|
136
|
+
def initialize_instance
|
137
|
+
@len_obj = get_parameter(:size_type).instantiate(nil,self)
|
138
|
+
end
|
139
|
+
|
140
|
+
# override this instead of using value_to_binary_string, so that
|
141
|
+
# we don't have to start out byte aligned (the string portion
|
142
|
+
# will still be aligned, though).
|
143
|
+
def do_write(io)
|
144
|
+
@len_obj.assign(_value.bytes.count)
|
145
|
+
@len_obj.write(io)
|
146
|
+
io.writebytes(_value)
|
147
|
+
end
|
148
|
+
|
149
|
+
# again, override for proper bit handling
|
150
|
+
def do_num_bytes
|
151
|
+
@len_obj.num_bytes + _value.bytes.count
|
152
|
+
end
|
153
|
+
|
154
|
+
# only called by do_write and do_num_bytes, which we've overridden, so
|
155
|
+
# shouldn't ever be called.
|
156
|
+
def value_to_binary_string(v)
|
157
|
+
raise RuntimeError.new "this should never be called"
|
158
|
+
end
|
159
|
+
|
160
|
+
def read_and_return_value(io)
|
161
|
+
@len_obj.read(io)
|
162
|
+
s = BinData::String.new(read_length: @len_obj).read(io)
|
163
|
+
s.snapshot
|
164
|
+
end
|
165
|
+
|
166
|
+
def sensible_default
|
167
|
+
""
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class BinData::Peek < BinData::BasePrimitive
|
172
|
+
include BinData::ClassParametersPlugin
|
173
|
+
default_parameter type: :uint8
|
174
|
+
class_parameter :type
|
175
|
+
|
176
|
+
def initialize_instance
|
177
|
+
@proto = get_parameter :type
|
178
|
+
end
|
179
|
+
|
180
|
+
def value_to_binary_string(val)
|
181
|
+
''
|
182
|
+
end
|
183
|
+
|
184
|
+
def read_and_return_value(io)
|
185
|
+
#TODO - make this work rather than punting on it
|
186
|
+
if io.instance_eval {@rnbits} > 0
|
187
|
+
raise "cannot peek when not byte aligned!"
|
188
|
+
end
|
189
|
+
|
190
|
+
obj = get_parameter(:type).instantiate(nil,self)
|
191
|
+
|
192
|
+
|
193
|
+
io = io.raw_io
|
194
|
+
pos = io.pos
|
195
|
+
begin
|
196
|
+
obj.read(io)
|
197
|
+
ensure
|
198
|
+
io.pos = pos
|
199
|
+
end
|
200
|
+
|
201
|
+
obj
|
202
|
+
end
|
203
|
+
|
204
|
+
def sensible_default
|
205
|
+
0
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
#Need this for positioning underlying IO, which
|
210
|
+
#we use to avoid altering BD::IO state (i.e. bits read)
|
211
|
+
#TODO find a less hacky way to do all this
|
212
|
+
module BinData::IO::Read::SeekableStream
|
213
|
+
attr_reader :initial_pos
|
214
|
+
public :raw_io
|
215
|
+
end
|
216
|
+
|
217
|
+
class BinData::Pointer < BinData::BasePrimitive
|
218
|
+
include BinData::ClassParametersPlugin
|
219
|
+
mandatory_parameter :ptr_type
|
220
|
+
class_parameter :ptr_type
|
221
|
+
|
222
|
+
#TODO - support seek from current pos?
|
223
|
+
#default_parameter ptr_from: :start
|
224
|
+
|
225
|
+
mandatory_parameter :val_type
|
226
|
+
class_parameter :val_type
|
227
|
+
|
228
|
+
default_parameter seek_from: :start #one of :start, :raw_start, or :current
|
229
|
+
default_parameter seek_offset: 0 #TODO possibly what I want is to name a specific parent object to count from
|
230
|
+
|
231
|
+
attr_reader :ptr, :val
|
232
|
+
|
233
|
+
def read_and_return_value(io)
|
234
|
+
ptr_obj = get_parameter(:ptr_type).instantiate(nil,self)
|
235
|
+
val_obj = get_parameter(:val_type).instantiate(nil,self)
|
236
|
+
ptr = ptr_obj.read(io)
|
237
|
+
|
238
|
+
rio = io.raw_io
|
239
|
+
pos = rio.pos
|
240
|
+
|
241
|
+
base = case get_parameter(:seek_from)
|
242
|
+
when :raw_start then 0
|
243
|
+
when :start then io.initial_pos
|
244
|
+
when :current then pos + ptr
|
245
|
+
else raise "Invalid seek_from parameter"
|
246
|
+
end
|
247
|
+
|
248
|
+
rio.seek base + get_parameter(:seek_offset) + ptr
|
249
|
+
val_obj.read(rio)
|
250
|
+
rio.seek pos
|
251
|
+
|
252
|
+
#{p: ptr_obj, v: val_obj}
|
253
|
+
val_obj
|
254
|
+
end
|
255
|
+
|
256
|
+
#XXX hacky below, handles reading only
|
257
|
+
|
258
|
+
def value_to_binary_string(v)
|
259
|
+
v.to_binary_s
|
260
|
+
end
|
261
|
+
|
262
|
+
def sensible_default
|
263
|
+
#{p: 0, v: nil}
|
264
|
+
get_parameter(:val_type).instantiate(nil,self)
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'bindata'
|
2
|
+
#This file collects various monkey-patches
|
3
|
+
|
4
|
+
# Monkey-patch no-ops for state handling; user can override if needed. Note:
|
5
|
+
# can't use nil as parameter value, so use empty string as placeholder.
|
6
|
+
class BinData::Base
|
7
|
+
def self.initial_state; ''; end
|
8
|
+
def current_state; eval_parameter(:protocol_state) || @parent && @parent.current_state ; end
|
9
|
+
def update_state; current_state; end
|
10
|
+
def summary
|
11
|
+
"#{self.class}: #{num_bytes} bytes of data"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
class BinData::Choice
|
15
|
+
def summary
|
16
|
+
current_choice.summary
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
=begin
|
21
|
+
class BinData::LazyEvaluator
|
22
|
+
#This is similar to a simplified lazy_eval, but starts w/ @obj, not @obj.parent
|
23
|
+
def lazy_eval_self(val)
|
24
|
+
if val.is_a? Symbol
|
25
|
+
@obj.send(val)
|
26
|
+
elsif val.respond_to? :arity
|
27
|
+
@obj.instance_exec(&val) #??
|
28
|
+
else
|
29
|
+
val
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class BinData::DSLMixin::DSLParser
|
35
|
+
def summary(arg=nil, &b)
|
36
|
+
raise "expected one arg or block" unless arg.nil? ^ b.nil?
|
37
|
+
@the_class.class_exec do
|
38
|
+
define_method(:summary) do
|
39
|
+
lazy_evaluator.lazy_eval_self(arg || b)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
=end
|