Files
2020-04-20 14:59:10 +01:00

701 lines
16 KiB
C++

/*
* MIT License
*
* Copyright(c) 2018 Balazs Bucsay
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files(the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions :
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
TODO List:
- verbose/debug mode +others
- anti-timeout
- check socket activity and close after timeout
*/
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <windows.h>
#include <wtsapi32.h>
#include <pchannel.h>
#include <crtdbg.h>
#include <stdio.h>
#include <strsafe.h>
#include <assert.h>
#include "SocksOverRDP-Server.h"
#include "SocksServer.h"
#pragma comment(lib, "wtsapi32.lib")
#pragma comment(lib, "Ws2_32.lib")
#define SocksOverRDP_CHANNEL_NAME "SocksChannel"
#define DEBUG_PRINT_BUFFER_SIZE 1024
#define BUF_SIZE 4096
extern DWORD WINAPI HandleClient(void *param);
DWORD OpenDynamicChannel(LPCSTR szChannelName, HANDLE *phFile);
struct arguments {
WCHAR *ip;
WCHAR *port;
BYTE priority;
} running_args;
struct threadhandles {
HANDLE hRDP = NULL;
SOCKET sock = NULL;
HANDLE pipe = NULL;
};
struct threads *ThreadHead = NULL;
int CTRLC = 0;
HANDLE ghMutex, ghLLMutex;
HANDLE hChannel;
HANDLE hWTSHandle = NULL;
BOOL bVerbose = FALSE, bDebug = FALSE;
VOID usage(WCHAR *cmdname)
{
wprintf(L"Usage: %s [-v] [-t timeout_val]\n"
"-h\t\tThis help\n"
"-v\t\tVerbose Mode\n",
// "-t n\tTimeout on threads. Kills any thread and corresponding connection after n seconds\n",
cmdname);
return;
}
BOOL parse_argv(INT argc, __in_ecount(argc) WCHAR **argv)
{
int num = 0;
while (num < argc - 1)
{
num++;
if (wcsncmp(argv[num], L"-", 1))
{
wprintf(L"[-] Invalid argument: %s\n", argv[num]);
usage(argv[0]);
return FALSE;
}
switch (argv[num][1])
{
case 'h':
case '?':
usage(argv[0]);
return FALSE;
case 'v':
bVerbose = TRUE;
break;
case 'd':
bDebug = TRUE;
break;
//case 't':
// num++;
//dwTimeout = atoi(argv[num]);
//printf("timeout: %ld\n", dwTimeout);
break;
default:
wprintf(L"[-] Invalid argument: %s\n", argv[num]);
usage(argv[0]);
return FALSE;
}
}
return TRUE;
}
VOID DebugPrint(HRESULT hrDbg, __in_z LPWSTR fmt, ...)
{
HRESULT hr;
TCHAR Buffer[DEBUG_PRINT_BUFFER_SIZE];
size_t Len;
hr = StringCchPrintf(Buffer, DEBUG_PRINT_BUFFER_SIZE, TEXT("[hr=0x%8x]"), hrDbg);
assert(SUCCEEDED(hr)); // buffer is sure to be big enough
hr = StringCchLength(Buffer, DEBUG_PRINT_BUFFER_SIZE, &Len);
assert(SUCCEEDED(hr)); // StringCchPrintf is supposed to always NULL term
va_list argptr;
va_start(argptr, fmt);
hr = StringCchVPrintf(Buffer + Len, DEBUG_PRINT_BUFFER_SIZE - Len,
fmt, argptr);
// the above could fail but we don't care since we
// should get a NULL terminated partial string
// insert terminating eol (despite failure)
hr = StringCchLength(Buffer, DEBUG_PRINT_BUFFER_SIZE, &Len);
assert(SUCCEEDED(hr)); // again there should be a NULL term
if (Len < DEBUG_PRINT_BUFFER_SIZE - 1)
{
Len++;
Buffer[Len] = TEXT('\0');
}
Buffer[Len - 1] = TEXT('\n');
//OutputDebugString(Buffer);
wprintf(Buffer);
}
// Add thread to linked list
struct threads *AddThread(DWORD dwThreadId, DWORD dwRemoteThreadId, HANDLE hSlot_r, HANDLE hSlot_w)
{
struct threads *rolling;
struct threads *ThreadStruct;
DWORD dwWaitResult = WaitForSingleObject(ghLLMutex, INFINITE);
switch (dwWaitResult)
{
case WAIT_OBJECT_0:
ThreadStruct = (threads *)malloc(sizeof(struct threads));
ThreadStruct->dwThreadId = dwThreadId;
ThreadStruct->dwRemoteThreadId = dwRemoteThreadId;
ThreadStruct->hThread = NULL;
ThreadStruct->run = TRUE;
ThreadStruct->hSlot_r = hSlot_r;
ThreadStruct->hSlot_w = hSlot_w;
ThreadStruct->hSlot_event = NULL;
ThreadStruct->next = NULL;
if (ThreadHead == NULL)
{
ThreadHead = ThreadStruct;
}
else
{
rolling = ThreadHead;
while (rolling->next)
{
rolling = rolling->next;
}
rolling->next = ThreadStruct;
}
if (!ReleaseMutex(ghLLMutex))
{
printf("Release failed\n");
}
return ThreadStruct;
break;
case WAIT_ABANDONED:
printf("%08X: AddThread lock abandoned\n", dwThreadId);
return NULL;
}
return NULL;
}
// Look up thread and return a pointer to it by local ThreadId
struct threads *LookupThread(DWORD dwThreadId)
{
struct threads *rolling;
DWORD dwWaitResult = WaitForSingleObject(ghLLMutex, INFINITE);
switch (dwWaitResult)
{
case WAIT_OBJECT_0:
rolling = ThreadHead;
if (rolling == NULL)
{
if (!ReleaseMutex(ghLLMutex))
{
DebugPrint(0, L"[-] LookupThread Release Failed0: %08X", GetCurrentThreadId());
}
return NULL;
}
while (rolling)
{
if (rolling->dwThreadId == dwThreadId)
{
if (!ReleaseMutex(ghLLMutex))
{
DebugPrint(0, L"[-] LookupThread Release Failed1: %08X", GetCurrentThreadId());
}
return rolling;
}
rolling = rolling->next;
}
if (!ReleaseMutex(ghLLMutex))
{
DebugPrint(0, L"[-] LookupThread Release Failed2: %08X", GetCurrentThreadId());
}
return NULL;
break;
case WAIT_ABANDONED:
printf("%08X: LookupThread lock abandoned\n", dwThreadId);
return NULL;
}
return NULL;
}
// Look up thread and return a pointer to it by remote threadid
struct threads *LookupThreadRemote(DWORD dwRemoteThreadId)
{
struct threads *rolling;
DWORD dwWaitResult = WaitForSingleObject(ghLLMutex, INFINITE);
switch (dwWaitResult)
{
case WAIT_OBJECT_0:
rolling = ThreadHead;
if (rolling == NULL)
{
if (!ReleaseMutex(ghLLMutex))
{
DebugPrint(0, L"[-] LookupThreadRemote Release Failed0: %08X", GetCurrentThreadId());
}
return NULL;
}
while (rolling)
{
if (rolling->dwRemoteThreadId == dwRemoteThreadId)
{
if (!ReleaseMutex(ghLLMutex))
{
DebugPrint(0, L"[-] LookupThreadRemote Release Failed1: %08X", GetCurrentThreadId());
}
return rolling;
}
rolling = rolling->next;
}
if (!ReleaseMutex(ghLLMutex))
{
DebugPrint(0, L"[-] LookupThreadRemote Release Failed2: %08X", GetCurrentThreadId());
}
return NULL;
break;
case WAIT_ABANDONED:
printf("%08X: LookupThreadRemote lock abandoned\n", GetCurrentThreadId());
return NULL;
}
return NULL;
}
// Remove thread from linked list
VOID DeleteThread(DWORD dwThreadId)
{
struct threads *rolling;
struct threads *prev = NULL;
//printf("%08X: DeleteThread lock wait\n", dwThreadId);
DWORD dwWaitResult = WaitForSingleObject(ghLLMutex, INFINITE);
switch (dwWaitResult)
{
case WAIT_OBJECT_0:
if (!ThreadHead)
{
if (!ReleaseMutex(ghLLMutex))
{
DebugPrint(0, L"[-] Trying to delete an empty list. %ld", GetCurrentThreadId());
}
return;
}
rolling = ThreadHead;
while (rolling->dwThreadId != dwThreadId)
{
prev = rolling;
rolling = rolling->next;
}
if (prev)
{
if (rolling->next)
{
prev->next = rolling->next;
}
else
{
prev->next = NULL;
}
}
else
{
if (ThreadHead->next)
{
ThreadHead = ThreadHead->next;
}
else
{
ThreadHead = NULL;
}
}
rolling->dwThreadId = 0xffffffff;
rolling->next = NULL;
rolling->run = FALSE;
rolling->hSlot_w = NULL;
rolling->hSlot_r = NULL;
rolling->hSlot_event = NULL;
rolling->hSlot_r = NULL;
free(rolling);
rolling = NULL;
if (!ReleaseMutex(ghLLMutex))
{
printf("Release failed\n");
}
return;
break;
case WAIT_ABANDONED:
printf("%08X: DeleteThread lock abandoned\n", dwThreadId);
return;
}
}
// If CTRL+C pressed, all threads should be stopped and safely exit
// Double press: closing channel and leaving the rest to the OS.
BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)
{
struct threads *rolling;
switch (fdwCtrlType)
{
case CTRL_C_EVENT:
if (CTRLC)
{
wprintf(L"[*] Forced terminating\n");
WTSVirtualChannelClose(hWTSHandle);
exit(0);
}
wprintf(L"[*] CTRL+C pressed. Closing down.\n");
CTRLC = 1;
if (!ThreadHead)
{
WTSVirtualChannelClose(hWTSHandle);
exit(0);
}
rolling = ThreadHead;
do
{
rolling->run = FALSE;
SetEvent(rolling->hSlot_event);
} while (rolling = rolling->next);
wprintf(L"[*] All threads are signaled to stop\n");
rolling = ThreadHead;
wprintf(L"[*] Closing channel\n");
WTSVirtualChannelClose(hWTSHandle);
exit(0);
return TRUE;
default:
return FALSE;
}
}
INT _cdecl wmain(INT argc, __in_ecount(argc) WCHAR **argv)
{
WSADATA wsaData;
BOOL bSucc, ofused, bClose = FALSE;
HANDLE hEvent, hSlot_r, hSlot_w = NULL;
OVERLAPPED Overlapped, Overlapped_MailSlot;
DWORD dwRecvdLen, dwRead, dwWritten, bufWritelen, dwOverflow = 0;
DWORD dwRemoteThreadId, dwThreadId;
ULONG cbFullSize;
BYTE ReadBuffer[CHANNEL_PDU_LENGTH];
CHANNEL_PDU_HEADER *pHdr = (CHANNEL_PDU_HEADER *)ReadBuffer;
char *buf, *bufWrite, szOverflow[BUF_SIZE * 4], szMailSlotName[32];
int ret;
struct threadhandles threadhandle;
struct threads *pta;
running_args.port = L"1080";
running_args.priority = 4;
running_args.ip = L"127.0.0.1";
wprintf(L"Socks Over RDP by Balazs Bucsay [[@xoreipeip]]\n\n");
if (argc > 1)
if (!parse_argv(argc, argv))
return -1;
if ((ret = OpenDynamicChannel(SocksOverRDP_CHANNEL_NAME, &hChannel)) != ERROR_SUCCESS)
{
if (ret == 31)
wprintf(L"[-] Could not open Dynamic Virtual Channel, plugin was not loaded on the client side: %ld\n", ret);
else
wprintf(L"[-] Could not open Dynamic Virtual Channel: %ld %08X\n", ret, ret);
return -1;
}
wprintf(L"[*] Channel opened over RDP\n");
ghMutex = CreateMutex(NULL, FALSE, NULL);
ghLLMutex = CreateMutex(NULL, FALSE, NULL);
Overlapped_MailSlot = { 0 };
hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
Overlapped = { 0 };
Overlapped.hEvent = hEvent;
if ((ret = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0)
{
wprintf(L"[-] WSAStartup() failed with error: %ld\n", ret);
return -1;
}
// set handler for Ctrl+C
SetConsoleCtrlHandler(CtrlHandler, TRUE);
// whatever arrives to the channel, it needs to be parsed and written to
// the corresponding mailslot
while (TRUE)
{
bSucc = ReadFile(hChannel, ReadBuffer, sizeof(ReadBuffer), &dwRead, &Overlapped);
if (!bSucc)
{
if (GetLastError() == ERROR_IO_PENDING)
{
WaitForSingleObject(Overlapped.hEvent, INFINITE);
bSucc = GetOverlappedResult(hChannel, &Overlapped, &dwRead, FALSE);
}
}
if (!bSucc)
{
wprintf(L"[-] ReadFile()/WaitForSingleObject() error: %ld\n", GetLastError());
return -1;
}
// no need to pass the header.
bufWrite = (char *)(pHdr + 1);
bufWritelen = dwRead - sizeof(CHANNEL_PDU_HEADER);
buf = (char*)bufWrite;
cbFullSize = bufWritelen;
ofused = FALSE;
// if overflow was present from previous call, we append it to the
// saved bytes
if (dwOverflow)
{
if (bDebug) printf("[*] Overflow was present from previous call. Stored bytes: %ld. Received bytes: %ld == %ld \n", dwOverflow, bufWritelen, dwOverflow+bufWritelen);
memcpy_s(szOverflow + dwOverflow, BUF_SIZE * 4, bufWrite, bufWritelen);
buf = szOverflow;
cbFullSize = bufWritelen + dwOverflow;
dwOverflow = 0;
ofused = TRUE;
}
// run while anything left in buffer
while (cbFullSize)
{
// edge case, less than necesary data in buffer
if (cbFullSize < 9)
{
if (ofused)
{
// overflow was used and still not enough data
if (bDebug) printf("[*] Not enough data, ofused: %ld\n", cbFullSize);
dwOverflow = cbFullSize;
}
else
{
// no overflow was used, saving the data to overflow
if (bDebug) printf("[*] Not enough data, no ofused: %ld\n", cbFullSize);
memcpy_s(szOverflow, BUF_SIZE * 4, buf, cbFullSize);
dwOverflow = cbFullSize;
}
// exit loop since not enough data
cbFullSize = 0;
break;
}
// parsing header
memcpy(&dwRemoteThreadId, buf, sizeof(DWORD));
memcpy(&dwRecvdLen, buf + sizeof(DWORD), sizeof(DWORD));
// corresponding thread lookup
if ((pta = LookupThreadRemote(dwRemoteThreadId)) == NULL)
{
// failed to find existing thread, creating one
memset(szMailSlotName, 0, 32);
snprintf(szMailSlotName, 32, "\\\\.\\mailslot\\RDPSocks_%08X", dwRemoteThreadId);
hSlot_r = CreateMailslotA(szMailSlotName, 0, MAILSLOT_WAIT_FOREVER, (LPSECURITY_ATTRIBUTES)NULL);
if (hSlot_r == INVALID_HANDLE_VALUE)
{
printf("CreateMailslot_r failed with %d\n", GetLastError());
break;
}
hSlot_w = CreateFileA(szMailSlotName, GENERIC_WRITE, FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL);
if (hSlot_w == INVALID_HANDLE_VALUE)
{
printf("CreateMailslot_w failed with %d.\n", GetLastError());
break;
}
pta = AddThread(0, dwRemoteThreadId, hSlot_r, hSlot_w);
HANDLE hDummyThread = CreateThread(
NULL,
0,
&HandleClient,
(void *)pta,
0,
&dwThreadId);
pta->dwThreadId = dwThreadId;
pta->hThread = hDummyThread;
if (bDebug) printf("[*] %08X: Thread not found, Mailslot created\n", dwRemoteThreadId);
}
else
{
if (bDebug) printf("[*] %08X: Thread found, Mailslot found\n", dwRemoteThreadId);
hSlot_w = pta->hSlot_w;
}
// Is data in buffer bigger than expected data + header?
if (dwRecvdLen <= cbFullSize - (sizeof(DWORD) + sizeof(DWORD) + sizeof(BYTE)))
{
bSucc = WriteFile(hSlot_w, buf + sizeof(DWORD) + sizeof(DWORD), dwRecvdLen + 1, &dwWritten, &Overlapped_MailSlot);
if (!bSucc)
{
if (GetLastError() == ERROR_IO_PENDING)
{
WaitForSingleObject(Overlapped_MailSlot.hEvent, INFINITE);
bSucc = GetOverlappedResult(hChannel, &Overlapped_MailSlot, &dwWritten, TRUE);
}
}
if (!bSucc)
{
if (bDebug) printf("[-] %08X: WriteChannel error: %ld\n", dwRemoteThreadId, GetLastError());
cbFullSize = 0;
}
else
{
if (bDebug) printf("[+] %08X: no error, written: %ld, should have written: %ld | cbFullSize: %ld\n", dwRemoteThreadId, dwWritten, dwRecvdLen + 1, cbFullSize);
cbFullSize -= (dwWritten + sizeof(DWORD) + sizeof(DWORD));
buf += (dwWritten + sizeof(DWORD) + sizeof(DWORD));
}
}
else
{
// less data then expected, saving to szOverflow
if (bDebug) printf("[*] !!! OVERFLOW HAPPENED %ld < %ld -9\n", dwRecvdLen, cbFullSize);
if (ofused)
{
if (bDebug) printf("[*] Data left in buffer, ofused: %ld\n", cbFullSize);
memmove_s(szOverflow, BUF_SIZE * 4, buf, cbFullSize);
dwOverflow = cbFullSize;
}
else
{
if (bDebug) printf("[*] Data left in buffer, not ofused: %ld\n", cbFullSize);
memcpy_s(szOverflow, BUF_SIZE * 4, buf, cbFullSize);
dwOverflow = cbFullSize;
}
cbFullSize = 0;
}
}
}
CloseHandle(ghMutex);
CloseHandle(hChannel);
return 0;
}
/*
* Open a dynamic channel with the name given in szChannelName.
* The output file handle can be used in ReadFile/WriteFile calls.
*/
DWORD OpenDynamicChannel(LPCSTR szChannelName, HANDLE *phFile)
{
HANDLE hWTSFileHandle;
PVOID vcFileHandlePtr = NULL;
DWORD len;
DWORD rc = ERROR_SUCCESS;
BOOL fSucc;
hWTSHandle = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, (LPSTR)szChannelName,
WTS_CHANNEL_OPTION_DYNAMIC | running_args.priority);
if (NULL == hWTSHandle)
{
rc = GetLastError();
goto exitpt;
}
fSucc = WTSVirtualChannelQuery(hWTSHandle, WTSVirtualFileHandle,
&vcFileHandlePtr, &len);
if (!fSucc)
{
rc = GetLastError();
goto exitpt;
}
if (len != sizeof(HANDLE))
{
rc = ERROR_INVALID_PARAMETER;
goto exitpt;
}
hWTSFileHandle = *(HANDLE *)vcFileHandlePtr;
fSucc = DuplicateHandle(GetCurrentProcess(), hWTSFileHandle,
GetCurrentProcess(), phFile, 0, FALSE, DUPLICATE_SAME_ACCESS);
if (!fSucc)
{
rc = GetLastError();
goto exitpt;
}
rc = ERROR_SUCCESS;
exitpt:
if (vcFileHandlePtr)
{
WTSFreeMemory(vcFileHandlePtr);
}
if (hWTSHandle)
{
WTSVirtualChannelClose(hWTSHandle);
}
return rc;
}