binproxy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,2 @@
1
+ require_relative 'binproxy/proxy'
2
+ require_relative 'binproxy/class_loader'
@@ -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