# quick tool to look at icmp tunnels based on ptunnel # this is a bit heavy # the ptunnel has a minimum length based on the header # 0 1 2 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 # +-------------------------------+-------------------------------+ # | Type | Code | Checksum | # +-------------------------------+-------------------------------+ # | Identifier | Sequence Number | # +-------------------------------+-------------------------------+ # # +-------------------------------+-------------------------------+ # | Magic Number | # +---------------------------------------------------------------+ # | IP | # +---------------------------------------------------------------+ # | Port | # +---------------------------------------------------------------+ # | State | # +---------------------------------------------------------------+ # | Acknowledgment Number | # +---------------------------------------------------------------+ # | Length | # +-------------------------------+-------------------------------+ # | Sequence Number | Reserved | # +-------------------------------+-------------------------------+ # | Data ... # +---------------------------------------------------- # # State codes: # ................... # Proxy_start = 0; # Proto_data = 1; # Proto_ack = 2; # Proto_close = 3; # Proto_authenticate = 4; # @load notice const i_log_file = open_log_file("icmp-tunnel") &redef; redef enum Notice += { ICMP_PTunnel_ID, # the notice for possible ptunnel identification }; const tunnel_state: table[int] of string = { [0] = "proxy-start", [1] = "data-forwarding", [2] = "ACK-data", [3] = "close-session", [4] = "authentication", }; const tunnel_direct: table[int] of string = { [64] = "client", [128] = "server", }; const ptunnel_min_size:count = 28 &redef; # ##### data structs ##### # type ptunnel_header: record { magic_num: string; IP: addr; prt: int; state: int; flag : int; ack: int; len: int; seq: int; }; type tflow_id: record { orig_h: addr; resp_h: addr; id: count; mn: string; }; type icmp_t_info: record { id: count; # id for logging - bro generated for logging auth: count; # flag for authentication proxy_addr: addr; # the address the icmp gateway will go to proxy_port: int; # the port the gateway will connect to state: int; # state of connection data_sent: int; # current data from init to recieve data_receive: int; # current data from recieve to init }; # we keep track of the tunnels via the ICMP flow ID and the magic number global icmp_t_flows: table[tflow_id] of icmp_t_info &read_expire = 60 sec; global tunnel_session: count = 1; # ##### Functions ##### # function hex_string_to_int(s: string) : int { local val:string = string_to_ascii_hex(sub_bytes(s,1,4)); #this is a new bif function #print fmt("%s", val); return to_xint(val); } function hex_string_to_2int(s: string) : int { local val:string = string_to_ascii_hex(sub_bytes(s,1,2)); #this is a new bif function return to_xint(val); } function process_state(s: string) : int { local val:string = string_to_ascii_hex(sub_bytes(s,2,3)); #this is a new bif function return to_xint(val); } function process_flag(s: string) : int { local val:string = string_to_ascii_hex(sub_bytes(s,1,1)); #this is a new bif function return to_xint(val); } function test_icmp_payload(payload: string) : int { # this will be used to sanity check the payload section # of the ICMP packet. I am sure that there are better ways # to go aboutthe test st. the string munching need not # happen twice. XXX revisit this! # # the logic here will let us bail out asap, so please # forgive the nasty return mess ... if ( |payload| < ptunnel_min_size ) return 0; # if the magic number == 0, we assume that the data is all # zero's and punt if ( hex_string_to_int( sub_bytes(payload,1,4) ) == 0 ) return 0; # test for a sane port value ( n < 65536 ) if ( hex_string_to_int(sub_bytes(payload,9,12)) > 65536 ) return 0; # test for a sane state value ( n < 5 ) if ( process_state(sub_bytes(payload,13,16)) > 4 ) return 0; # test for a sane length local p_len = hex_string_to_int(sub_bytes(payload,21,24)); if ( p_len > 65535 ) return 0; # finally, make sure that the flag is not zero as well if ( process_flag(sub_bytes(payload,13,16)) == 0 ) return 0; # for now, pass ... return 1; } function process_icmp_payload(payload: string) : ptunnel_header { # here we extract therelevant header info from the data section # local PH: ptunnel_header; # extract the header PH$magic_num = sub_bytes(payload,1,4); PH$IP = raw_bytes_to_v4_addr(sub_bytes(payload,5,8)); PH$prt = hex_string_to_int(sub_bytes(payload,9,12)); PH$state = process_state(sub_bytes(payload,13,16)); PH$flag = process_flag(sub_bytes(payload,13,16)); PH$ack = hex_string_to_int(sub_bytes(payload,17,20)); PH$len = hex_string_to_int(sub_bytes(payload,21,24)); PH$seq = hex_string_to_2int(sub_bytes(payload,25,28)); return PH; } function process_flow(PH: ptunnel_header, is_orig: bool, cid: conn_id, id: count) { # here we process the state for the flow info provided by PH local fid: tflow_id; local iti: icmp_t_info; fid$orig_h = cid$orig_h; fid$resp_h = cid$resp_h; fid$id = id; fid$mn = PH$magic_num; if ( fid !in icmp_t_flows) { iti$id = tunnel_session; iti$auth = 0; iti$proxy_addr = PH$IP; iti$proxy_port = PH$prt; iti$state = PH$state; iti$data_sent = 0; iti$data_receive = 0; if ( PH$state == 0 ) { # this is the point that we see the initialization of # the tunnel. Not only do we start a new session record # but we send ourt a notice as this sort of thing is interesting iti$auth = 1; print i_log_file, fmt("%.6f #%s %s %s -> %s -> %s:%s", network_time(), iti$id, tunnel_state[iti$state], fid$orig_h, fid$resp_h, iti$proxy_addr, iti$proxy_port); NOTICE([$note=ICMP_PTunnel_ID, $id=cid, $msg=fmt("(#%s) %s %s -> %s -> %s:%s", iti$id, tunnel_state[iti$state], fid$orig_h, fid$resp_h, iti$proxy_addr, iti$proxy_port)]); } icmp_t_flows[fid] = iti; ++tunnel_session; # as this is a new flow, I think that we are done here ... return; } else { iti = icmp_t_flows[fid]; } # now that we have the flow data # look for state change if ( iti$state != PH$state ) { # we want to skip the ACK-data -> data-forwarding and reverse # transitions as thy are boring... local transition:string = fmt("%s%s", iti$state, PH$state); if ( (strcmp(transition,"21") == 1) && (strcmp(transition,"12") == 1) ) { print i_log_file, fmt("%.6f #%s state-change %s -> %s", network_time(), iti$id, tunnel_state[iti$state], tunnel_state[PH$state]); } iti$state = PH$state; } # mod the data sizes if ( is_orig ) { # client iti$data_sent += PH$len; } else { iti$data_receive += PH$len; } # we seem to find ourselves here if ( iti$state == 3 ) { # close the session print i_log_file, fmt("%.6f #%d %s %s sent: %s receive: %s", network_time(), iti$id, tunnel_state[iti$state], tunnel_direct[PH$flag], iti$data_sent, iti$data_receive); } } # ##### Events ##### # event icmp_echo_reply(c: connection, icmp: icmp_conn, id: count, seq: count, payload: string) { local PH: ptunnel_header; #local tfid: flow_id; if ( test_icmp_payload(payload) == 1 ) { PH = process_icmp_payload(payload); } else return; # test to see if this is a reply from the tunnel ( flag == 128 ) or # part of the icmp echo reply ( flag == 64 ) if ( PH$flag == 128 ) { process_flow(PH, F, c$id, id); } } event icmp_echo_request(c: connection, icmp: icmp_conn, id: count, seq: count, payload: string) { local PH: ptunnel_header; if ( test_icmp_payload(payload) == 1 ) { PH = process_icmp_payload(payload); process_flow(PH, T, c$id, id); } else return; }