binproxy 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|