Skip to content

Instantly share code, notes, and snippets.

@maliciousgroup

maliciousgroup/proxy.tcl Secret

Created Apr 8, 2021
Embed
What would you like to do?
#!/usr/bin/env tclsh
# Debugged by d3d using TCLPro Debugger
proc help {} {
puts "TCLproxy v0.0.3"
puts ""
puts "Usage: tclsh $::argv0 \[-L address]... \[-D address]..."
puts ""
puts "Proxy server implementation. Binary protocols are supported."
puts ""
puts " -L \[bind_address:]port:remote_host:remote_port"
puts " Forward a remote port to a local port."
puts " Multiple connections and multiple forwards are supported."
puts ""
puts " -D \[bind_address:]port"
puts " Launch a SOCKS4a proxy server."
puts ""
puts " Forwarding between VRF tables:"
puts " -D \[VRF_table_for_listening@]\[bind_address]:port\[@VRF_table_for_outbound_connections]"
puts " -L \[VRF_table_for_listening@]\[bind_address]:port\[@VRF_table_for_outbound_connections]:remote_host:remote_port"
puts ""
puts " optional arguments:"
puts " -f, --disable-eof-check Speed increases by 1-15 KB/s, but connections don't close automatically. Dangerous!"
puts " -h, --help Show this help message and exit."
puts " -q, --disable-output Quite mode. In this mode, you can disconnect from the console without script termination. Dangerous!"
puts " -l, --low-ports Use privileged source ports. Required for NFS (source port increments from 1 to 1023 every connection)"
puts " -n, --disable-dns Do not resolve DNS names in SOCKS mode"
puts ""
puts " The effect of --disable-eof-check and --disable-output options depends on hardware architecture and firmware version."
puts " TCLproxy will not work for port scanning, use tclmap.tcl instead."
puts ""
puts " example:"
puts " cisco# tclsh tclproxy.tcl -h"
puts " cisco# tclsh tclproxy.tcl -L 5901:10.0.0.1:445 -D :5902@enterpriseVRF -D 5900"
puts " ..."
puts " cisco# del flash:/tclproxy.tcl"
}
set argc [llength $argv]
if {$argc == 0} {
help
puts ""
puts "no command line argument passed"
return
}
# Configuration
set default_fconfigure_options [list -translation {binary binary} -encoding binary -buffering none]
# Options
set L_option FALSE
set L_option_list [list]
set D_option FALSE
set D_disable_dns FALSE
set D_option_list [list]
set disable_eof_check 0
set low_ports 0
# Debug
set DEBUG 1
proc debug {str} {
global DEBUG
if {$DEBUG >= 1} {
puts "debug: $str"
}
}
proc bgerror {error} {
catch {debug $error}
}
# Parsing command-line options
# /**
# * Auxiliary function for parsing -L and -D options
# *
# * Splits $str by $char,
# * if lenght (explode($char, $str)) == $num, return splitted list
# * if lenght (explode($char, $str)) == ($num - 1), insert empty string by $insert_pos and return the list
# *
# * @param str Arg string
# * @param char Delimiter
# * @param num Number of options
# * @param insert_pos Optional element position
# *
# * @return Prepared list
# */
proc specific_parse_arg_string {str char num insert_pos} {
set option_list [split $str $char]
set len [llength $option_list]
if {$len == $num} {
return $option_list
} elseif {$len == ($num - 1)} {
set option_list [linsert $option_list $insert_pos ""]
return $option_list
}
return ""
}
# /**
# * Parse a string as a -D argument
# *
# * @param str String to parse
# *
# * @return [list
# * /* Listen socket options */
# * /* New connection socket options */
# * /* Debug string */
# * ]
# */
proc parse_D_option {str} {
set listen_chain_args [list]
set new_connection_chain_args [list]
set debug ""
set plist [specific_parse_arg_string $str ":" 2 0]
if {$plist == ""} {
return ""
}
set part1 [specific_parse_arg_string [lindex $plist 0] "@" 2 0]
set part2 [specific_parse_arg_string [lindex $plist 1] "@" 2 1]
if {$part1 != ""} {
set listen_vrf [lindex $part1 0]
set listen_bind_addr [lindex $part1 1]
if {$listen_vrf != ""} {
lappend listen_chain_args -myvrf $listen_vrf
append debug "VRF \"$listen_vrf\" "
}
if {$listen_bind_addr != ""} {
lappend listen_chain_args -myaddr $listen_bind_addr
append debug "$listen_bind_addr"
}
} else {
append debug "0.0.0.0"
}
if {$part2 == ""} {
return ""
}
set listen_bind_port [lindex $part2 0]
set new_connection_vrf [lindex $part2 1]
lappend listen_chain_args $listen_bind_port
append debug ":$listen_bind_port"
if {$new_connection_vrf != ""} {
lappend new_connection_chain_args -myvrf $new_connection_vrf
append debug " VRF \"$new_connection_vrf\""
}
return [list $listen_chain_args $new_connection_chain_args $debug]
}
# /**
# * Parse a string as a -L argument
# *
# * @param str String to parse
# *
# * @return [list
# /* Listen socket options */
# /* New connection socket options */
# /* Debug string */
# ]
# */
proc parse_L_option {str} {
set plist [specific_parse_arg_string $str ":" 4 0]
if {$plist == ""} {
return ""
}
set bind_options [join [lrange $plist 0 1] ":"]
set remote_host [lindex $plist 2]
set remote_port [lindex $plist 3]
set part1 [parse_D_option $bind_options]
set listen_chain_args [lindex $part1 0]
set new_connection_chain_args [lindex $part1 1]
set debug [lindex $part1 2]
lappend new_connection_chain_args $remote_host $remote_port
append debug " => $remote_host:$remote_port"
return [list $listen_chain_args $new_connection_chain_args $debug]
}
# Parsing command-line options
for {set i 0} {$i < $argc} {incr i} {
set arg [lindex $argv $i]
if {$arg == "-L"} {
set L_option TRUE
incr i
if {$argc == $i} {
puts "option requires an argument -- $arg"
return
}
set option_list [parse_L_option [lindex $argv $i]]
if {$option_list == ""} {
puts "Bad local forwarding specification '[lindex $argv $i]'"
return
}
lappend L_option_list $option_list
} elseif {$arg == "-D"} {
set D_option TRUE
incr i
if {$argc == $i} {
puts "option requires an argument -- $arg"
return
}
set option_list [parse_D_option [lindex $argv $i]]
if {$option_list == ""} {
puts "Bad dynamic forwarding specification '[lindex $argv $i]'"
return
}
lappend D_option_list $option_list
} elseif {$arg == "-h" || $arg == "--help"} {
help
return
} elseif {$arg == "-q" || $arg == "--disable-output"} {
set DEBUG 0
} elseif {$arg == "-f" || $arg == "--disable-eof-check"} {
set disable_eof_check 1
} elseif {$arg == "-r" || $arg == "--low-ports"} {
set low_ports 1
} elseif {$arg == "-n" || $arg == "--disable-dns" } {
set D_disable_dns TRUE
} else {
puts "$arg: invalid option"
return
}
}
# Functions for -l, --low-ports
# /**
# * Iterate numbers from 1 to 1023
# *
# * @return a number
# */
set source_port_inc 0
proc generate_source_port {} {
global source_port_inc
incr source_port_inc
if {$source_port_inc >= 1024} {
set source_port_inc 1
}
return $source_port_inc
}
# Port forwarding
# /**
# * Read data from first socket and put its to another one
# *
# * @param a Input chain
# * @param b Output chain
# * @param debug Debug string
# *
# */
proc read_chan_from_a_to_b {a b debug} {
if {[catch {puts -nonewline $b [read $a 4096]} error]} {
debug "$error for $debug"
close $a
close $b
return
}
if {[eof $a] || [eof $b]} {
close $a
close $b
}
}
# /**
# * Read data from first socket and put its to another one without EOF checking
# *
# * @param a Input chain
# * @param b Output chain
# * @param debug Debug string
# *
# */
proc read_chan_from_a_to_b_without_eof {a b} {
puts -nonewline $b [read $a 4096]
}
# /**
# * Handler for client connections (-L option)
# * Arguments for this function are produced by "parse_D_option" or "parse_L_option", and "fileevent" accordingly
# *
# * @param new_connection_chain_args
# * @param debug
# * @param chan_client
# * @param client_addr
# * @param client_port
# *
# */
proc forward_port_handler {new_connection_chain_args debug chan_client client_addr client_port} {
global default_fconfigure_options
global disable_eof_check
global low_ports
if {$low_ports == 1} {
set source_port [generate_source_port]
set source_port_debug "sp $source_port "
set new_connection_chain_args [linsert $new_connection_chain_args 0 -myport $source_port]
} else {
set source_port_debug ""
}
set debug_str "$client_addr:$client_port $source_port_debug=> $debug"
debug "TCP $debug_str"
if {[catch {set chan_remote_host [eval socket $new_connection_chain_args]} error]} {
debug "$error for connect to remote address ($debug)"
close $chan_client
return
}
eval fconfigure $chan_client $default_fconfigure_options -blocking 0
eval fconfigure $chan_remote_host $default_fconfigure_options -blocking 0
if {$disable_eof_check == 1} {
fileevent $chan_client readable [list read_chan_from_a_to_b_without_eof $chan_client $chan_remote_host ]
fileevent $chan_remote_host readable [list read_chan_from_a_to_b_without_eof $chan_remote_host $chan_client]
} else {
fileevent $chan_client readable [list read_chan_from_a_to_b $chan_client $chan_remote_host $debug]
fileevent $chan_remote_host readable [list read_chan_from_a_to_b $chan_remote_host $chan_client $debug]
}
}
# /**
# * Listen a local port and forward any connections to a remote port
# * Arguments for this function are produced by "parse_D_option" or "parse_L_option" functions
# *
# * @param listen_chain_args
# * @param new_connection_chain_args
# * @param debug
# */
proc forward_port {listen_chain_args new_connection_chain_args debug} {
set server_handler [list forward_port_handler $new_connection_chain_args $debug]
debug "Forward listener: $debug"
set listen_chain_args [linsert $listen_chain_args 0 -server $server_handler]
if {[catch {eval socket $listen_chain_args} err]} {
debug "$err $debug"
}
}
# SOCKS proxy
# /**
# * Convert an ip from "binary scan c4" format to a text
# *
# * @param ip Ip-address
# *
# * @return Ip-address debug
# */
proc ip_c4_to_text {ip} {
set ret [list]
foreach octet $ip {
lappend ret [expr {$octet & 0xFF}]
}
return [join $ret "."]
}
# /**
# * Handler for client connections (-D option)
# * Arguments for this function are produced by "parse_D_option" or "parse_L_option", and "fileevent" accordingly
# *
# * @param new_connection_chain_args
# * @param debug
# * @param chan_client
# * @param client_addr
# * @param client_port
# *
# */
proc dynamic_forward_port_handler {new_connection_chain_args debug chan_client client_addr client_port} {
global D_disable_dns
global default_fconfigure_options
global disable_eof_check
global low_ports
set protocol_error "Client $client_addr:$client_port: protocol mismatch"
set socks4_error "Error while reading SOCKS4 header (connection from $client_addr:$client_port). "
append socks4_error "The header must be in one TCP frame without an application data."
eval fconfigure $chan_client $default_fconfigure_options -blocking 1
if {![binary scan [read $chan_client 1] "c" socks_version]} {
debug "Client $client_addr:$client_port has been disconnected before transferring"
close $chan_client
return
}
if {$socks_version == 4} { ;# SOCKS4
if {![binary scan [read $chan_client 7] "cSc4" connection_type remote_port ip]} {
debug $protocol_error
close $chan_client
return
}
if {$connection_type == 2} { ;# Bind port
debug "Client $client_addr:$client_port: Bind operation is not supported, use -L flag"
close $chan_client
return
} elseif {$connection_type == 1} { ;# TCP/IP connection
eval fconfigure $chan_client -blocking 0
set c_strings [split [read $chan_client 256] "\x00"] ;# Read identity string or domain name
set c_strings_len [llength $c_strings]
if {[lindex $ip 0] == 0 && [lindex $ip 1] == 0 && [lindex $ip 2] == 0 && [lindex $ip 3] != 0} { ;# SOCKS4a
set protocol "SOCKS4a"
if {$c_strings_len != 3} {
debug $socks4_error
close $chan_client
return
}
set remote_host [lindex $c_strings 1]
if {$D_disable_dns} { ;# -n, --disable-dns
puts -nonewline $chan_client "\x00\x5B\xE2\xE4\xE7\xE5\xF2\xF4" ;# request rejected or failed
debug "Client $client_addr:$client_port: DNS resolve request is rejected, host $remote_host"
close $chan_client
return
}
} else { ;# SOCKS4
set protocol "SOCKS4"
if {$c_strings_len != 2} {
debug $socks4_error
close $chan_client
return
}
set remote_host [ip_c4_to_text $ip]
}
if {$low_ports == 1} {
set source_port [generate_source_port]
set source_port_debug "sp $source_port "
lappend new_connection_chain_args -myport $source_port
} else {
set source_port_debug " "
}
debug "TCP $client_addr:$client_port => $protocol $debug $source_port_debug=> $remote_host:$remote_port"
if {[catch {set chan_remote_host [eval socket $new_connection_chain_args $remote_host $remote_port]} error]} {
debug "TCP $client_addr:$client_port => $debug => $remote_host:$remote_port: $error"
puts -nonewline $chan_client "\x00\x5B\xE2\xE4\xE7\xE5\xF2\xF4" ;# request rejected or failed
close $chan_client
return
}
eval fconfigure $chan_remote_host $default_fconfigure_options -blocking 0
puts -nonewline $chan_client "\x00\x5A\xE2\xE4\xE7\xE5\xF2\xF4" ;# request granted
if {$disable_eof_check == 1} {
fileevent $chan_client readable [list read_chan_from_a_to_b_without_eof $chan_client $chan_remote_host ]
fileevent $chan_remote_host readable [list read_chan_from_a_to_b_without_eof $chan_remote_host $chan_client]
} else {
fileevent $chan_client readable [list read_chan_from_a_to_b $chan_client $chan_remote_host $debug]
fileevent $chan_remote_host readable [list read_chan_from_a_to_b $chan_remote_host $chan_client $debug]
}
} else {
debug $protocol_error
close $chan_client
return
}
} elseif {$socks_version == 5} { ;# SOCKS5
debug "Client $client_addr:$client_port: SOCKS5 protocol is not supported, use SOCKS4a"
close $chan_client
return
} else {
debug $protocol_error
close $chan_client
}
}
# /**
# * Launch SOCKS4a server
# * Arguments for this function are produced by "parse_D_option" or "parse_L_option"
# *
# * @param listen_chain_args
# * @param new_connection_chain_args
# * @param debug
# */
proc dynamic_forward_port {listen_chain_args new_connection_chain_args debug} {
set server_handler [list dynamic_forward_port_handler $new_connection_chain_args $debug]
debug "SOCKS listener: $debug"
set listen_chain_args [linsert $listen_chain_args 0 -server $server_handler]
if {[catch {eval socket $listen_chain_args} err]} {
debug "$err $debug"
}
}
if {$L_option} {
foreach item $L_option_list {
eval forward_port $item
}
}
if {$D_option} {
foreach item $D_option_list {
eval dynamic_forward_port $item
}
}
if {$L_option || $D_option} {
if {[catch {vwait forever} error]} {
catch {debug "$error"}
}
}
return
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment