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