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.
@@ -0,0 +1,14 @@
1
+
2
+ class ChatDemo < BinData::Record
3
+ endian :little
4
+ uint32 :timestamp
5
+ string :user, length: 8, trim_padding: true
6
+ bit1 :emote_flag
7
+ bit1 :private_flag
8
+ string :recipient, length: 8, trim_padding: true, onlyif: :private_flag
9
+ stringz :message
10
+
11
+ def summary
12
+ "#{emote_flag ? 'emote' : 'message'} from #{user}"
13
+ end
14
+ end
@@ -0,0 +1,70 @@
1
+ # DNS Parser for BinProxy
2
+ # use Dns::Packet class for UDP traffic, or Dns::TcpPacket for TCP.
3
+
4
+ require 'bindata'
5
+ require 'binproxy/bd_util'
6
+
7
+ module Dns
8
+
9
+ class Header < BinData::Record
10
+ endian :big
11
+
12
+ uint16 :id
13
+ bit1 :qr
14
+ bit4 :opcode
15
+ bit1 :aa
16
+ bit1 :tc
17
+ bit1 :rd
18
+ bit1 :ra
19
+ bit3 :z
20
+ bit4 :rcode
21
+ uint16 :qdcount
22
+ uint16 :ancount
23
+ uint16 :nscount
24
+ uint16 :arcount
25
+ end
26
+
27
+ class NameElement < BinData::Record; end
28
+
29
+ class NameSeq < BinData::Array
30
+ default_parameter read_until: lambda { element.flag != 0x0 or element.val == '' }
31
+ name_element
32
+ end
33
+ class NameElement < BinData::Record
34
+ bit2 :flag
35
+ choice :val, selection: :flag do
36
+ pascal_string 0x0, size_type: :bit6
37
+ pointer 0x3, ptr_type: :bit14, val_type: :name_seq, seek_offset: 2 #XXX
38
+ end
39
+ end
40
+
41
+ class DnsRecord < BinData::Record
42
+ endian :big
43
+ name_seq :name
44
+ uint16 :type
45
+ uint16 :rclass
46
+ end
47
+
48
+ class Question < DnsRecord; end
49
+
50
+ class ResourceRecord < DnsRecord
51
+ uint32 :ttl
52
+ uint16 :rdlength
53
+ string :rdata, read_length: :rdlength
54
+ end
55
+
56
+ #XXX can't use Packet for UDP currently b/c of hardcoded 2 byte adjustment to pointers (due to length pre-header in TCP packets)
57
+ class Packet < BinData::Record
58
+ header :header
59
+ array :questions, type: :question, initial_length: lambda { header.qdcount }
60
+ array :answers, type: :resource_record, initial_length: lambda { header.ancount }
61
+ array :authorities, type: :resource_record, initial_length: lambda { header.nscount }
62
+ array :addl_records, type: :resource_record, initial_length: lambda { header.arcount }
63
+ end
64
+
65
+
66
+ class TcpPacket < BinData::Record
67
+ uint16be :len
68
+ packet :pkt
69
+ end
70
+ end
@@ -0,0 +1,28 @@
1
+ # A stupid HTTP BinData class to demonstrate the proxy
2
+ require_relative '../bd_util'
3
+
4
+ module DumbHttp
5
+ class Headers < BinData::Array
6
+ default_parameter read_until: lambda { element == '' }
7
+ line line_end: "\r\n"
8
+ end
9
+
10
+ class BaseMessage < BinData::Record
11
+ headers :headers
12
+
13
+ def body_len
14
+ (headers.snapshot.grep(/\AContent-Length: (\d+)/) { $1.to_i }).first || 0
15
+ end
16
+
17
+ def summary; headers.first.sub(/HTTP\/\d\.\d/,'').strip; end
18
+ end
19
+
20
+ class Message < BaseMessage
21
+ string :body, display_as: 'multiline', onlyif: lambda { body_len > 0 }, length: :body_len
22
+ end
23
+
24
+ class BinMessage < BaseMessage
25
+ string :body, display_as: 'hexdump', onlyif: lambda { body_len > 0 }, length: :body_len
26
+ end
27
+
28
+ end
@@ -0,0 +1,56 @@
1
+ require 'bindata'
2
+
3
+ class MsgPack
4
+ class MpNil < BinData::Primitive
5
+ def get; nil; end
6
+ def set(v); raise unless v.nil? end;
7
+ end
8
+ class MpTrue < BinData::Primitive
9
+ def get; true; end
10
+ def set(v); raise unless v; end
11
+ end
12
+ class MpFalse < BinData::Primitive
13
+ def get; false; end
14
+ def set(v); raise unless !v; end
15
+ end
16
+ class
17
+
18
+
19
+ class TypedValue < BinData::Choice
20
+ positive_fixint :positive_fixint
21
+ fixmap :fixmap
22
+ fixarray :fixarray
23
+ fixstr :fixstr
24
+ mp_nil :nil
25
+ mp_true :true
26
+ mp_false :false
27
+ bin8 :bin8
28
+ end
29
+
30
+ uint8 :type_byte
31
+ typed_value :value, selection: lambda do
32
+ case type_byte
33
+ when 0..0x7f
34
+ :positive_fixint
35
+ when 0x80..0x8f
36
+ :fixmap
37
+ when 0x90..0x9f
38
+ :fixarray
39
+ when 0xa0..0xbf
40
+ :fixstr
41
+ when 0xc0
42
+ :nil
43
+ when 0xc1
44
+ :unused
45
+ when 0xc2
46
+ :true
47
+ when 0xc3
48
+ :false
49
+ when 0xc4
50
+ :bin8
51
+ else
52
+ raise 'todo'
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,4 @@
1
+ require 'bindata'
2
+ class PlainText < BinData::Rest
3
+ default_parameter display_as: 'multiline'
4
+ end
@@ -0,0 +1,4 @@
1
+ require 'bindata'
2
+ class RawMessage < BinData::Record
3
+ rest :data, display_as: 'hexdump'
4
+ end
@@ -0,0 +1,134 @@
1
+ require 'bindata'
2
+
3
+ module X11
4
+ end
5
+
6
+ class X11SetupReq < BinData::Record
7
+ endian :little #totally cheating here!
8
+ uint8 :byte_order
9
+ skip length: 1
10
+ uint16 :proto_major
11
+ uint16 :proto_minor
12
+ uint16 :auth_proto_name_len, value: lambda { auth_proto_name.length }
13
+ uint16 :auth_proto_data_len, value: lambda { auth_proto_data.length }
14
+ string :auth_proto_name, read_length: :auth_proto_name_len
15
+ skip length: lambda { (4 - (auth_proto_name_len % 4)) % 4 }
16
+ string :auth_proto_data, read_length: :auth_proto_data_len
17
+ skip length: lambda { (4 - (auth_proto_data_len % 4)) % 4 }
18
+ skip length: 2 #???
19
+ end
20
+
21
+ class X11StdReqBody < BinData::Record
22
+ endian :little
23
+ uint16 :req_len
24
+ string :data, read_length: lambda {(req_len - 1) * 4}
25
+ end
26
+
27
+ class X11ExtReqBody < BinData::Record
28
+ rest :data
29
+ end
30
+
31
+ class X11StdReq < BinData::Record
32
+ endian :little
33
+ uint8 :opcode
34
+ uint8 :subcode
35
+ choice :req_type, selection: lambda { opcode <= 128 } do
36
+ x11_std_req_body true
37
+ x11_ext_req_body false
38
+ end
39
+ end
40
+
41
+ class X11Req < BinData::Record
42
+ choice :msg2, display_as: 'anon', selection: lambda { current_state[:connected] } do
43
+ x11_setup_req false
44
+ x11_std_req true
45
+ end
46
+ end
47
+
48
+ class X11SetupRes < BinData::Record
49
+ endian :little #more cheating
50
+ uint8 :status
51
+ skip length: 1
52
+ uint16 :proto_major
53
+ uint16 :proto_minor
54
+ uint16 :addl_data_dwords
55
+ uint32 :release_num
56
+ uint32 :res_id_base, display_as: 'hex'
57
+ uint32 :res_id_mask, display_as: 'hex'
58
+ uint32 :motion_buffer_size
59
+ uint16 :vendor_len, value: lambda { vendor.length }
60
+ uint16 :max_req_len
61
+ uint8 :num_screens
62
+ uint8 :num_formats
63
+ uint8 :image_byte_order
64
+ uint8 :bitmap_bit_order
65
+ uint8 :bitmap_format_scanline_unit
66
+ uint8 :bitmap_format_scanlin_pad
67
+ uint8 :min_keycode
68
+ uint8 :max_keycode
69
+ skip length: 4
70
+ string :vendor, read_length: :vendor_len
71
+ string :vpad, read_length: lambda { (4 - ( vendor.length % 4 )) % 4 }
72
+ rest :listofformatandroots, display_as: 'hexdump'
73
+ end
74
+
75
+ class X11Reply < BinData::Record
76
+ endian :little
77
+ uint8 :detail
78
+ uint16 :seq_num
79
+ uint32 :extra_len
80
+ string :data, length: lambda { 24 + (4 * extra_len) }, display_as: 'hexdump'
81
+ end
82
+
83
+ class X11Event < BinData::Record
84
+ endian :little
85
+ uint8 :detail
86
+ string :data, length: 30, display_as: 'hexdump'
87
+ end
88
+
89
+ class X11Error < BinData::Record
90
+ endian :little
91
+ uint8 :detail
92
+ string :data, length: 30, display_as: 'hexdump'
93
+ end
94
+
95
+ class X11StdRes < BinData::Record
96
+ endian :little
97
+ uint8 :res_type
98
+ choice :res_detail, selection: :res_type do
99
+ x11_error 0
100
+ x11_reply 1
101
+ x11_event :default
102
+ end
103
+ end
104
+
105
+
106
+ class X11Res < BinData::Record
107
+ choice :msg2, display_as: 'anon', selection: lambda { current_state[:connected] } do
108
+ x11_setup_res false
109
+ x11_std_res true
110
+ end
111
+ end
112
+
113
+ class X11Proto < BinData::Record
114
+ def self.initial_state; { connected: false, endian: nil }; end
115
+
116
+ #XXX wrap this in something that dups the current state so we're unable to mutate it by accident
117
+ def update_state
118
+ c = current_state.dup
119
+ if eval_parameter(:src) == 'server' #hack, assume we're connected after 1st c-s-c exchange
120
+ c[:connected] = true
121
+ end
122
+ c
123
+ end
124
+
125
+ def summary
126
+ msg.msg2.summary
127
+ end
128
+
129
+ choice :msg, selection: :src, display_as: 'anon' do
130
+ x11_req 'client'
131
+ x11_res 'server'
132
+ end
133
+ end
134
+
@@ -0,0 +1,62 @@
1
+ # ZeroMQ (ZMTPv3) Parser for BinProxy
2
+
3
+ module ZMQ
4
+ # this greeting1/2/3 business is due to the fact that
5
+ # zmq sends the greeting in parts and waits for the reply
6
+ # from the other endpoint in order to do version detection.
7
+ class Greeting1 < BinData::Record
8
+ uint8 :sig_start, asserted_value: 0xFF
9
+ string :sig_padding, length: 8
10
+ uint8 :sig_end, asserted_value: 0x7F
11
+ end
12
+
13
+ class Greeting2 < BinData::Record
14
+ uint8 :version_major, asserted_value: 0x03
15
+ end
16
+
17
+ class Greeting3 < BinData::Record
18
+ uint8 :version_minor, asserted_value: 0x00
19
+
20
+ string :mechanism, length: 20
21
+
22
+ uint8 :as_server
23
+
24
+ string :filler, length: 31 #pad to 64 bytes
25
+ end
26
+
27
+ class Frame < BinData::Record
28
+ bit5 :reserved
29
+ bit1 :is_command
30
+ bit1 :is_long
31
+ bit1 :has_more
32
+ choice :body_size, selection: :is_long, display_as: 'anon' do
33
+ uint8 0
34
+ uint64be 1
35
+ end
36
+ string :body, read_length: :body_size
37
+ end
38
+
39
+ class Message < BinData::Record
40
+ # States are (G)reeting 1/2/3 (see above), (H)andshake, and (T)raffic.
41
+ def next_state(s); {'G1'=>'G2','G2'=>'G3','G3'=>'H', 'H'=>'T', 'T'=>'T'}[s]; end
42
+ def self.initial_state; {'client'=>'G1','server'=>'G1'}; end
43
+
44
+ # Protocol is symmetrical. Each endpoint has its own state.
45
+ # This is a bit clunky and maybe should be abstracted into a module?
46
+ # Or update parser.rb to differentiate between proto-shared and endpoint-separate state?
47
+ def update_state
48
+ current_state.dup.tap do |s|
49
+ src = eval_parameter :src
50
+ s[src] = next_state s[src]
51
+ end
52
+ end
53
+
54
+ choice :msg_type, selection: lambda { current_state[eval_parameter(:src) ] }, display_as: 'anon' do
55
+ greeting1 'G1'
56
+ greeting2 'G2'
57
+ greeting3 'G3'
58
+ frame 'H' #contains a command w/ arbitrary content
59
+ frame 'T' #can contain command or message
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,242 @@
1
+ require 'eventmachine'
2
+ require 'observer'
3
+ require 'bindata'
4
+ require 'active_support/core_ext/object/blank'
5
+ require_relative 'bindata'
6
+ require_relative 'connection'
7
+ require_relative 'session'
8
+ require_relative 'parser'
9
+ require_relative 'logger'
10
+
11
+ module BinProxy
12
+ class Proxy
13
+ include BinProxy::Logger
14
+ include Observable
15
+ attr_accessor :hold
16
+ attr_reader :sessions
17
+ attr_reader :buffer
18
+
19
+ def initialize(root, opts)
20
+ @class_loader = BinProxy::ClassLoader.new(root)
21
+ @options = {}
22
+ @sessions = []
23
+ @buffer = []
24
+ @filter_pids = []
25
+ configure(opts)
26
+ end
27
+
28
+ def configure(new_opts = {})
29
+ old_opts = @options.dup
30
+ new_opts = old_opts.merge new_opts
31
+
32
+ @hold = !! new_opts[:hold];
33
+ @inbound_filters = []
34
+ @upstream_filters = []
35
+
36
+ if new_opts[:socks_proxy]
37
+ @inbound_filters << BinProxy::Connection::Filters::Socks
38
+ elsif new_opts[:http_proxy]
39
+ @inbound_filters << BinProxy::Connection::Filters::HTTPConnect
40
+ else
41
+ @inbound_filters << BinProxy::Connection::Filters::StaticUpstream
42
+ end
43
+
44
+ if new_opts[:tls]
45
+ @inbound_filters << BinProxy::Connection::Filters::InboundTLS
46
+ @upstream_filters << BinProxy::Connection::Filters::UpstreamTLS
47
+ end
48
+
49
+ if new_opts[:debug_extra]
50
+ @inbound_filters << BinProxy::Connection::Filters::Logger
51
+ @upstream_filters << BinProxy::Connection::Filters::Logger
52
+ end
53
+
54
+
55
+
56
+ #XXX this reloads the class on every config change
57
+ #XXX bad, but reloading in web console relies on it
58
+ new_opts[:class] = @class_loader.load_class(new_opts[:class_name],new_opts[:class_file])
59
+ if old_opts[:class] != new_opts[:class]
60
+ @parser_class = Parser.subclass(self, new_opts[:class])
61
+ @parser_class.validate = new_opts[:validate_parser]
62
+ elsif new_opts[:class] == nil
63
+ raise "Option :class must be specified"
64
+ end
65
+
66
+ [:lhost, :lport, :dhost, :dport].each do |o|
67
+ # let people get away with it if it's already blank
68
+ if old_opts[o].present? and new_opts[o].blank?
69
+ raise "Option :#{o} must be specified"
70
+ end
71
+ end
72
+
73
+ # This will not handle an exception if the server is started asynchronously, but
74
+ # the only time that should happen is the initial configuration from command line args,
75
+ # in which case we don't have existing opts to revert to anyway.
76
+ begin
77
+ @options = new_opts
78
+ if old_opts[:lhost] != new_opts[:lhost] or old_opts[:lport] != new_opts[:lport]
79
+ restart_server!
80
+ end
81
+ rescue Exception => e
82
+ @options = old_opts
83
+ raise e
84
+ end
85
+ end
86
+
87
+ # XXX below is incorrect, we do call it on first configuration
88
+ # We'll only call this internally if the server's host or port has changed; there's still the possiblility
89
+ # that a quick a->b->a change, or manually calling this could fail because the OS hasn't released the port yet;
90
+ # we'll punt on that for now, though.
91
+ def restart_server!
92
+ if stop_server!
93
+ #server was indeed running
94
+ # TODO investigate whether this is actually necessary, or could be reduced/removed
95
+ sleep 5
96
+ end
97
+ start_server!
98
+ end
99
+
100
+ def start_server!
101
+ if EM.reactor_running?
102
+ # Prefer to do this synchronously, so we can catch errors.
103
+ start_server_now!
104
+ else
105
+ # Have to defer this until Sinatra/Thin starts EM running.
106
+ EM.next_tick { start_server_now! }
107
+ end
108
+ end
109
+
110
+ def stop_server!
111
+ if @server_id
112
+ @filter_pids.each do |pid|
113
+ log.debug "Killing #{pid}"
114
+ Process.kill "INT", pid #TODO invstigate ncat signal handling
115
+ end
116
+ @filter_pids = [];
117
+ Process.waitall
118
+ EM.stop_server @server_id
119
+ @server_id = nil
120
+ true
121
+ else
122
+ false
123
+ end
124
+ end
125
+
126
+ def status
127
+ @server_id.nil? ? 'stopped' : 'running'
128
+ end
129
+
130
+ def history_size
131
+ @buffer.length
132
+ end
133
+
134
+ def message_received(message)
135
+ log.debug "proxy handling message_received"
136
+ message.id = @buffer.length
137
+ @buffer.push(message)
138
+
139
+ if hold
140
+ message.disposition = 'Held'
141
+ else
142
+ send_message(message, :auto)
143
+ end
144
+
145
+ changed
146
+ notify_observers(:message_received, message)
147
+ end
148
+
149
+ def update_message_from_hash(hash)
150
+ log.debug "updating message from hash"
151
+ orig = @buffer[hash[:head][:message_id]]
152
+ orig.update!(hash[:body][:snapshot])
153
+ orig
154
+ rescue Exception => e
155
+ puts '', e.class, e.message, e.backtrace, ''
156
+ on_bindata_error('deserialization', e)
157
+ end
158
+
159
+ def send_message(message, reason)
160
+ log.debug "proxy.send_message #{message.inspect}"
161
+ message.forward!(reason)
162
+ rescue Exception => e
163
+ puts '', e.class, e.message, e.backtrace, ''
164
+ on_bindata_error('forwarding', e)
165
+ end
166
+
167
+ def drop_message(message, reason)
168
+ log.debug "proxy.drop_message #{message.inspect}"
169
+ message.drop!(reason)
170
+ rescue Exception => e
171
+ puts '', e.class, e.message, e.backtrace, ''
172
+ on_bindata_error('dropping', e)
173
+ end
174
+
175
+ def session_closed(session, closing_peer, reason)
176
+ pe = BinProxy::ProxyEvent.new("Connection closed (#{reason||'no error'})")
177
+ pe.id = @buffer.length
178
+ pe.src = closing_peer
179
+ pe.session = session
180
+ @buffer.push(pe)
181
+
182
+ session.delete_observer(self)
183
+ changed
184
+ notify_observers(:session_event, pe)
185
+ end
186
+
187
+ def ssl_handshake_completed(session, peer)
188
+ changed
189
+ notify_observers(:ssl_handshake_completed, session, peer)
190
+ end
191
+
192
+ def on_bindata_error(operation, err)
193
+ changed
194
+ notify_observers(:bindata_error, operation, err)
195
+ end
196
+
197
+ private
198
+ def create_session(inbound_conn, upstream_conn)
199
+ id = @sessions.length
200
+ session = Session.new(id, inbound_conn, upstream_conn, @parser_class)
201
+ session.add_observer(self, :send)
202
+ @sessions << session
203
+
204
+ pe = BinProxy::ProxyEvent.new "Connection opened"
205
+ pe.id = @buffer.length
206
+ pe.src = :client
207
+ pe.session = session
208
+ @buffer.push(pe)
209
+
210
+ changed
211
+ notify_observers(:session_event, pe)
212
+ rescue Exception => e
213
+ puts e, e.backtrace
214
+ end
215
+
216
+ def start_server_now!
217
+ lhost = @options[:lhost]
218
+ lport = @options[:lport].to_i #force port to_i, or EM may assume different method signature
219
+
220
+ #These may be nil and are ignored in socks mode
221
+ dhost = @options[:dhost]
222
+ dport = @options[:dport].to_i
223
+
224
+ log.info "(re)starting proxy on #{lhost}:#{lport}"
225
+ upstream_args = {
226
+ peer: :server,
227
+ filter_classes: @upstream_filters
228
+ }
229
+ inbound_args = {
230
+ peer: :client,
231
+ session_callback: self.method(:create_session),
232
+ tls_args: { cert_chain_file: @options[:tls_cert], private_key_file: @options[:tls_key] },
233
+ filter_classes: @inbound_filters,
234
+ upstream_host: dhost,
235
+ upstream_port: dport,
236
+ upstream_args: upstream_args
237
+ }
238
+ @server_id = EM.start_server(lhost, lport, Connection, inbound_args)
239
+ end
240
+
241
+ end
242
+ end