Skip to content

Instantly share code, notes, and snippets.

@hardening
Created November 30, 2022 15:41
Show Gist options
  • Save hardening/61bfb8bf0ad1266ed20123ee05f282ba to your computer and use it in GitHub Desktop.
Save hardening/61bfb8bf0ad1266ed20123ee05f282ba to your computer and use it in GitHub Desktop.
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SOCKS MarIO
*
* Copyright 2022 David Fort <contact@hardening-consulting.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <winpr/string.h>
#include <winpr/wlog.h>
#include <freerdp/types.h>
#include <plumber/mario_internal.h>
#include <plumber/mario_socks.h>
#define TAG "proxy"
/* SOCKS Proxy auth methods by rfc1928 */
enum
{
AUTH_M_NO_AUTH = 0,
AUTH_M_GSSAPI = 1,
AUTH_M_USR_PASS = 2
};
enum
{
SOCKS_CMD_CONNECT = 1,
SOCKS_CMD_BIND = 2,
SOCKS_CMD_UDP_ASSOCIATE = 3
};
enum
{
SOCKS_ADDR_IPV4 = 1,
SOCKS_ADDR_FQDN = 3,
SOCKS_ADDR_IPV6 = 4,
};
/** @brief connect context */
typedef struct {
MarioConnectContext base;
int nextState;
int nauthMethods;
size_t hostnlen;
BYTE buf[3 + 255 + 255]; /* biggest packet is user/pass auth */
BYTE checkVersionByte;
} SocksConnectContext;
/** @brief SOCKS MarIo */
typedef struct {
MarIo base;
SocksConnectContext connectContext;
char *proxyUsername;
char *proxyPassword;
PlumberSocksAddressType targetAddrType;
char *targetHost;
UINT16 targetPort;
struct sockaddr_storage targetAddr;
size_t targetAddrLen;
BYTE boundAddressType;
struct sockaddr_storage boundAddress;
CHAR boundDomainName[256];
UINT16 boundPort;
} MarIoSocks;
MarIoStatus statusFromSocksConnectError(BYTE res) {
switch(res) {
case 0:
return MARIO_SUCCESS;
case 3: /* Network unreachable */
case 4: /* Host unreachable */
return MARIO_NO_ROUTE;
case 5:
return MARIO_CONNECTION_REFUSED;
case 1: /* failure */
case 2: /* connection not allowed by ruleset */
case 6: /* TTL expired */
case 7: /* command not supported */
case 8: /* address type not supported */
default:
return MARIO_INTERNAL_ERROR;
}
}
static MarioConnectContext* socks_prepare_connect(MarIo* io, MarIoAsyncResult* res, UINT32 timeout, MarIoOpCb cb, void* userdata)
{
MarIoSocks* socks = (MarIoSocks*)io;
SocksConnectContext* connectContext = &socks->connectContext;
mario_prepare_connect_context(&connectContext->base, io, res, timeout, cb, userdata);
connectContext->checkVersionByte = 5;
connectContext->hostnlen = strnlen(socks->targetHost, 255);
connectContext->nextState = 0;
connectContext->nauthMethods = 1;
return &connectContext->base;
}
static BOOL socks_connect(MarIo* io, MarioConnectContext* bcontext);
static void connect_readwrite_cb(MarIo* io, MarIoAsyncResult* res, void* userdata)
{
SocksConnectContext* context = (SocksConnectContext*)userdata;
MarioConnectContext* bcontext = &context->base;
if (socks_connect(bcontext->io, bcontext))
bcontext->cb(bcontext->io, bcontext->res, bcontext->userdata);
}
static BOOL socks_connect(MarIo* io, MarioConnectContext* bcontext)
{
MarIoAsyncResult* res = bcontext->res;
SocksConnectContext* context = (SocksConnectContext*)bcontext;
MarIoSocks* socks = (MarIoSocks*)io;
int writeLen;
int targetIndex;
#define LABEL(N) case N: goto label_##N
switch (context->nextState) {
LABEL(0);
LABEL(1);
LABEL(2);
LABEL(3);
LABEL(4);
LABEL(5);
LABEL(6);
LABEL(7);
LABEL(10);
LABEL(11);
LABEL(12);
default:
WLog_ERR(TAG, "%s: Internal error, unknown jump target", __FUNCTION__);
res->status = MARIO_INTERNAL_ERROR;
return TRUE;
}
/* First connect to the server */
label_0:
context->nextState = 1;
if (!mario_connect(io->next, res, bcontext->timeout, connect_readwrite_cb, context))
return FALSE;
label_1:
if (res->status != MARIO_SUCCESS)
return TRUE;
writeLen = 3;
if (socks->proxyUsername && socks->proxyPassword)
{
context->nauthMethods++;
writeLen++;
}
/* select auth. method */
context->buf[0] = 5; /* SOCKS version */
context->buf[1] = context->nauthMethods; /* #of methods offered */
context->buf[2] = AUTH_M_NO_AUTH;
if (context->nauthMethods > 1)
context->buf[3] = AUTH_M_USR_PASS;
context->nextState = 2;
if (!mario_write(io->next, res, context->buf, writeLen, connect_readwrite_cb, context))
return FALSE;
label_2:
if (res->status != MARIO_SUCCESS)
return TRUE;
//status = recv_socks_reply(bufferedBio, buf, 2, "AUTH REQ", 5);
context->nextState = 3;
if (!mario_read(io->next, res, context->buf, 2, TRUE, connect_readwrite_cb, context))
return FALSE;
label_3:
if (res->status != MARIO_SUCCESS)
return TRUE;
switch (context->buf[1])
{
case AUTH_M_NO_AUTH:
WLog_DBG(TAG, "SOCKS Proxy: (NO AUTH) method was selected");
break;
case AUTH_M_USR_PASS:
if (!socks->proxyUsername || !socks->proxyPassword) {
res->status = MARIO_UNSUPPORTED_ITEM;
return TRUE;
}
int usernameLen = strnlen(socks->proxyUsername, 255);
int userpassLen = strnlen(socks->proxyPassword, 255);
BYTE* ptr;
if (context->nauthMethods < 2)
{
WLog_ERR(TAG, "SOCKS Proxy: USER/PASS method was not proposed to server");
res->status = MARIO_AUTH_ERROR;
return TRUE;
}
/* user/password v1 method */
ptr = &context->buf[2];
context->buf[0] = 1;
context->buf[1] = usernameLen;
memcpy(ptr, socks->proxyUsername, usernameLen);
ptr += usernameLen;
*ptr = userpassLen;
ptr++;
memcpy(ptr, socks->proxyPassword, userpassLen);
context->nextState = 4;
if (!mario_write(io->next, res, context->buf, 3 + usernameLen + userpassLen, connect_readwrite_cb, context))
return FALSE;
label_4:
if (res->status != MARIO_SUCCESS) {
WLog_ERR(TAG, "SOCKS Proxy: error writing user/password request");
return TRUE;
}
context->nextState = 5;
if (!mario_read(io->next, res, context->buf, 2, TRUE, connect_readwrite_cb, context))
return FALSE;
label_5:
//status = recv_socks_reply(bufferedBio, buf, 2, "AUTH REQ", 1);
if (res->status != MARIO_SUCCESS) {
WLog_ERR(TAG, "SOCKS Proxy: error writing user/password request");
return TRUE;
}
if (context->buf[1] != 0x00)
{
WLog_ERR(TAG, "SOCKS Proxy: invalid user/password");
res->status = MARIO_AUTH_ERROR;
return TRUE;
}
break;
default:
WLog_ERR(TAG, "SOCKS Proxy: unknown method 0x%x was selected by proxy", context->buf[1]);
res->status = MARIO_INTERNAL_ERROR;
return TRUE;
}
/* CONN request */
context->buf[0] = 5; /* SOCKS version */
context->buf[1] = SOCKS_CMD_CONNECT; /* command */
context->buf[2] = 0; /* 3rd octet is reserved x00 */
targetIndex = 4;
switch(socks->targetAddrType)
{
case SOCKS_CONNECT_BY_DOMAIN:
context->buf[3] = SOCKS_ADDR_FQDN; /* addr.type */
context->buf[4] = context->hostnlen; /* DST.ADDR */
memcpy(&context->buf[5], socks->targetHost, context->hostnlen);
targetIndex += 1 + context->hostnlen;
break;
case SOCKS_CONNECT_BY_ADDR:
if (socks->targetAddr.ss_family == AF_INET) {
struct sockaddr_in* addr = (struct sockaddr_in*)&socks->targetAddr;
context->buf[3] = SOCKS_ADDR_IPV4; /* addr.type */
memcpy(&context->buf[4], &addr->sin_addr, 4);
targetIndex += 4;
} else if (socks->targetAddr.ss_family == AF_INET6) {
struct sockaddr_in6* addr = (struct sockaddr_in6*)&socks->targetAddr;
context->buf[3] = SOCKS_ADDR_IPV6; /* addr.type */
memcpy(&context->buf[4], &addr->sin6_addr, 16);
targetIndex += 16;
}
break;
default:
res->status = MARIO_UNSUPPORTED_ITEM;
return TRUE;
}
/* follows DST.PORT in netw. format */
context->buf[targetIndex++] = (socks->targetPort >> 8) & 0xff;
context->buf[targetIndex++] = socks->targetPort & 0xff;
context->nextState = 6;
if (!mario_write(io->next, res, context->buf, targetIndex, connect_readwrite_cb, context))
return FALSE;
label_6:
if (res->status != MARIO_SUCCESS)
{
WLog_ERR(TAG, "SOCKS proxy: failed to write CONN REQ");
return TRUE;
}
context->nextState = 7;
//status = recv_socks_reply(bufferedBio, buf, sizeof(buf), "CONN REQ", 5);
if (!mario_read(io->next, res, context->buf, 4, TRUE, connect_readwrite_cb, context))
return FALSE;
label_7:
if (res->status != MARIO_SUCCESS)
{
WLog_ERR(TAG, "SOCKS proxy: unable to read CONN REQ");
return TRUE;
}
res->status = statusFromSocksConnectError(context->buf[1]);
if (res->status != MARIO_SUCCESS)
{
WLog_INFO(TAG, "SOCKS connection failed");
return TRUE;
}
int toRead;
BYTE* readTarget;
struct sockaddr_in* addr_in = (struct sockaddr_in*)&socks->boundAddress;
struct sockaddr_in6* addr_in6 = (struct sockaddr_in6*)&socks->boundAddress;
socks->boundAddressType = context->buf[3];
switch (socks->boundAddressType) {
case SOCKS_ADDR_IPV4:
/* IPv4 */
socks->boundAddress.ss_family = AF_INET;
readTarget = (BYTE*)&addr_in->sin_addr;
toRead = 4;
break;
case SOCKS_ADDR_FQDN:
/* domainName */
readTarget = context->buf;
toRead = 1;
break;
case SOCKS_ADDR_IPV6:
/* IPv6 */
socks->boundAddress.ss_family = AF_INET6;
readTarget = (BYTE*)&addr_in6->sin6_addr;
toRead = 16;
break;
default:
res->status = MARIO_UNSUPPORTED_ITEM;
return TRUE;
}
context->nextState = 10;
if (!mario_read(io->next, res, readTarget, toRead, TRUE, connect_readwrite_cb, context))
return FALSE;
label_10:
if (res->status != MARIO_SUCCESS)
{
WLog_INFO(TAG, "error retrieving bound address");
return TRUE;
}
if (socks->boundAddressType == SOCKS_ADDR_FQDN) {
/* read the content of the domainName */
context->nextState = 11;
toRead = context->buf[0];
socks->boundDomainName[toRead] = '\00';
if (!mario_read(io->next, res, (BYTE *)socks->boundDomainName, toRead, TRUE, connect_readwrite_cb, context))
return FALSE;
label_11:
if (res->status != MARIO_SUCCESS)
{
WLog_INFO(TAG, "unable to read domainName");
return TRUE;
}
socks->boundDomainName[res->treatedBytes] = '\x00';
}
/* Read BND.PORT */
context->nextState = 12;
if (!mario_read(io->next, res, (BYTE *)&socks->boundPort, 2, TRUE, connect_readwrite_cb, context))
return FALSE;
label_12:
if (res->status != MARIO_SUCCESS)
{
WLog_INFO(TAG, "unable to read BND.PORT");
return TRUE;
}
return TRUE;
}
MarIo* plumber_socks_client_new(MarIo* next, PlumberSocksVersion version, const char* username, const char* password)
{
MarIoSocks* ret = (MarIoSocks*)mario_base_new(sizeof(*ret), MARIO_KIND_SOCKS, FALSE);
if (!ret)
return NULL;
if (username && password)
{
ret->proxyUsername = _strdup(username);
if (!ret->proxyUsername)
goto error_username;
ret->proxyPassword = _strdup(password);
if (!ret->proxyPassword)
goto error_password;
}
ret->targetAddrType = SOCKS_CONNECT_NOT_SET;
MarIo* base = &ret->base;
base->next = next;
base->prepareConnect = socks_prepare_connect;
base->connect = socks_connect;
base->prepareRead = mario_standard_prepare_read_next;
base->read = mario_standard_read_next;
base->prepareWrite = mario_standard_prepare_write_next;
base->write = mario_standard_write_next;
return base;
error_password:
free(ret->proxyUsername);
error_username:
free(ret);
return NULL;
}
BOOL plumber_socks_client_set_by_addr(MarIo* io, const struct sockaddr* addr, size_t len)
{
ASSERT_VALID_MARIO(io);
MarIoSocks* socks = (MarIoSocks*)io;
if (io->kind != MARIO_KIND_SOCKS)
{
WLog_ERR(TAG, "io is not a SOCKS MarIo");
return FALSE;
}
socks->targetAddrType = SOCKS_CONNECT_BY_ADDR;
socks->targetAddrLen = MIN(sizeof(socks->targetAddr), len);
memcpy(&socks->targetAddr, addr, socks->targetAddrLen);
struct sockaddr_in* addr_in;
struct sockaddr_in6* addr_in6;
switch(addr->sa_family) {
case AF_INET:
addr_in = (struct sockaddr_in*)addr;
socks->targetPort = htons(addr_in->sin_port);
break;
case AF_INET6:
addr_in6 = (struct sockaddr_in6*)addr;
socks->targetPort = htons(addr_in6->sin6_port);
break;
}
return TRUE;
}
BOOL plumber_socks_client_set_by_name(MarIo* io, const char* target, UINT16 port)
{
ASSERT_VALID_MARIO(io);
MarIoSocks* socks = (MarIoSocks*)io;
if (io->kind != MARIO_KIND_SOCKS)
{
WLog_ERR(TAG, "io is not a SOCKS MarIo");
return FALSE;
}
socks->targetAddrType = SOCKS_CONNECT_BY_DOMAIN;
socks->targetHost = _strdup(target);
if (!socks->targetHost)
return FALSE;
socks->targetPort = port;
return TRUE;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment