Skip to content

Commit

Permalink
Added a DNS endpoint collection plugin.
Browse files Browse the repository at this point in the history
We can now collect all DNS questions and answers as seen on the client
in the monitoring event logs.
  • Loading branch information
scudette committed Jan 1, 2019
1 parent 5dee885 commit d762fdc
Show file tree
Hide file tree
Showing 8 changed files with 509 additions and 6 deletions.
29 changes: 29 additions & 0 deletions artifacts/definitions/windows/events/dns.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Windows.Events.DNSQueries
description: |
Monitor all DNS Queries and responses.
This artifact monitors all DNS queries and their responses seen on
the endpoint. DNS is a critical source of information for intrusion
detection and the best place to collect it is on the endpoint itself
(Perimeter collection can only see DNS requests while the endpoint
or laptop is inside the enterprise network).
It is recommended to collect this artifact and just archive the
results. When threat intelligence emerges about a watering hole or a
bad C&C you can use this archive to confirm if any of your endpoints
have contacted this C&C.
parameters:
- name: whitelistRegex
description: We ignore DNS names that match this regex.
default: wpad.home

sources:
- precondition:
SELECT OS from info() where OS = "windows"

queries:
- |
SELECT timestamp(epoch=Time) As Time, EventType, Name, CNAME, Answers
FROM dns()
WHERE not Name =~ whitelistRegex
6 changes: 6 additions & 0 deletions bin/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ func evalQueryToTable(scope *vfilter.Scope, vql *vfilter.VQL) *tablewriter.Table
defer cancel()

output_chan := vql.Eval(ctx, scope)
scope.AddDesctructor(func() {
cancel()
})

table := tablewriter.NewWriter(os.Stdout)

columns := vql.Columns(scope)
Expand Down Expand Up @@ -112,6 +116,8 @@ func doQuery() {
scope := artifacts.MakeScope(repository).AppendVars(env)
defer scope.Close()

InstallSignalHandler(scope)

scope.Logger = log.New(os.Stderr, "velociraptor: ", log.Lshortfile)
for _, query := range *queries {
vql, err := vfilter.Parse(query)
Expand Down
20 changes: 20 additions & 0 deletions bin/utils.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package main

import (
"os"
"os/signal"
"strings"

vfilter "www.velocidex.com/golang/vfilter"
)

func hard_wrap(text string, colBreak int) string {
Expand All @@ -17,3 +21,19 @@ func hard_wrap(text string, colBreak int) string {

return wrapped
}

func InstallSignalHandler(
scope *vfilter.Scope) {

// Wait for signal. When signal is received we shut down the
// server.
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)

go func() {
// Wait for the signal on this channel.
<-quit
scope.Log("Shutting down due to interrupt.")
scope.Close()
}()
}
4 changes: 3 additions & 1 deletion flows/foreman.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ func (self *Foreman) ProcessEventTables(
Version: config_obj.Events.Version,
}
for _, name := range config_obj.Events.Artifacts {
vql_collector_args := &actions_proto.VQLCollectorArgs{}
vql_collector_args := &actions_proto.VQLCollectorArgs{
MaxWait: 100,
}
artifact, pres := repository.Get(name)
if !pres {
return errors.New("Unknown artifact " + name)
Expand Down
9 changes: 4 additions & 5 deletions gui/static/angular-components/core/wizard-form.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
<div class="Wizard FormData">
<div class="WizardPage">
<div class="WizardBar modal-header">
<button type="button" class="close"
ng-click="controller.reject()" aria-hidden="true">
x
</button>

<h3>{$ title $} -
<span class="Description">
{$ controller.currentPage.title $}
Expand All @@ -19,6 +14,10 @@ <h3>{$ title $} -
Step {$ controller.currentPageIndex + 1 $} out of {$ controller.pages.length $}
</span>
</h3>
<button type="button" class="close"
ng-click="controller.reject()" aria-hidden="true">
x
</button>
</div>

<ng-transclude></ng-transclude>
Expand Down
223 changes: 223 additions & 0 deletions vql/windows/dns/dns.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

// Link to ws2_32.lib
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define MAX_PACKET 65535

void process_dns(void *ctx, char *buff, int length);

struct iphdr {
uint8_t ihl:4;
uint8_t version:4;
uint8_t tos;
uint16_t tot_len;
uint16_t id;
uint16_t frag_off;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
uint32_t saddr;
uint32_t daddr;
};

struct udphdr {
uint16_t source;
uint16_t dest;
uint16_t len;
uint16_t checksum;
};


// Accounting we need on the C side.
typedef struct {
void *go_ctx;

// The main raw socket we read DNS packets on.
SOCKET s;
int running;

// Hold onto the promisc sockets so we can close them at shutdown.
SOCKET promisc[50];
int number_of_promisc;
} watcher_context;


// Sets all the interfaces into promiscuous mode. We can chek like
// this: Get-NetAdapter | Format-List -Property PromiscuousMode
//
// We only need to set any socket on the interface as promiscuous to
// allow all packets to reach any of the listening sockets. We dont
// actually read from this socket so we do not have to filter through
// all the uninteresting packets.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ee309610(v=vs.85).aspx
void setPromiscuous(watcher_context *ctx) {
int rc;
SOCKADDR_IN if0;
SOCKET_ADDRESS_LIST *slist=NULL;
char buf[2048];
DWORD dwBytesRet;
int i;
int promisc_idx = 0;

ctx->number_of_promisc = 0;

// Create the raw socket
ctx->promisc[promisc_idx] = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
if (ctx->promisc[promisc_idx] == INVALID_SOCKET) {
goto error;
}
ctx->number_of_promisc++;

rc = WSAIoctl(ctx->promisc[promisc_idx],
SIO_ADDRESS_LIST_QUERY, NULL, 0, buf,
sizeof(buf), &dwBytesRet, NULL, NULL);
if (rc == SOCKET_ERROR) {
return;
}

slist = (SOCKET_ADDRESS_LIST *)buf;
for (i=0; i<slist->iAddressCount; i++) {
if0.sin_addr.s_addr = ((SOCKADDR_IN *)slist->Address[i].
lpSockaddr)->sin_addr.s_addr;
if0.sin_family = AF_INET;
if0.sin_port = htons(0);

promisc_idx ++;
if (promisc_idx > sizeof(ctx->promisc)) {
return;
}

ctx->promisc[promisc_idx] = socket(
AF_INET, SOCK_RAW, IPPROTO_IP);
if (ctx->promisc[promisc_idx] == INVALID_SOCKET) {
goto error;
}
ctx->number_of_promisc++;

rc = bind(ctx->promisc[promisc_idx],
(SOCKADDR *)&if0, sizeof(if0));
if (rc == SOCKET_ERROR) {
continue;
}

DWORD flag = RCVALL_ON ;
DWORD ret = 0;
rc = WSAIoctl(ctx->promisc[promisc_idx],
SIO_RCVALL, &flag, sizeof(flag),
NULL, 0, &ret, NULL, NULL);
}

error:
// Cant really do anything in the case of failure - just move on.
return;
}


// These functions are called from Go to create and destroy an event
// watcher context.
void *watchDNS(void *go_ctx) {
watcher_context *ctx = (watcher_context *)calloc(sizeof(watcher_context), 1);
WSADATA wsd;
struct addrinfo *dest=NULL, *local=NULL;
int rc;
struct sockaddr_in if0;

// Create a new C context
ctx->go_ctx = go_ctx;
ctx->running = 1;

// Load Winsock
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) {
goto error;
}

// Set all interfaces to be promiscuous.
setPromiscuous(ctx);

// Create the raw socket for UDP
ctx->s = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
if (ctx->s == INVALID_SOCKET) {
goto error;
}

// We only care about DNS
if0.sin_family = AF_INET;
if0.sin_addr.s_addr = INADDR_ANY;
if0.sin_port = htons(53);

rc = bind(ctx->s, (SOCKADDR *)&if0, sizeof(if0));
if (rc == SOCKET_ERROR) {
closesocket(ctx->s);
goto error;
}

return ctx;

error:
return NULL;
}

void destroyDNS(void *c_ctx) {
watcher_context *ctx = (watcher_context *)c_ctx;
int i;

// Close all the sockets.
closesocket(ctx->s);
for(i=0; i<ctx->number_of_promisc;i++) {
closesocket(ctx->promisc[i]);
};

ctx->running = 0;
}


void runDNS(void *c_ctx) {
watcher_context *ctx = (watcher_context *)c_ctx;
unsigned char buf[MAX_PACKET];

// Post the first overlapped receive
for (;;) {
SOCKADDR_STORAGE safrom;
int fromlen = sizeof(safrom);
int rc;

if (!ctx->running) {
goto exit;
}

rc = recvfrom(ctx->s, buf, MAX_PACKET, 0, (SOCKADDR *)&safrom, &fromlen);
// Wait for a response
if (rc == SOCKET_ERROR) {
goto exit;

} else {
if (rc > 28) {
struct iphdr *ip_header = (struct iphdr *)buf;
int iphdr_size = ip_header->ihl * 4;
struct udphdr *udp_header = (struct udphdr *)(buf + iphdr_size);

// We only care about DNS packets.
if (ntohs(udp_header->source) == 53 ||
ntohs(udp_header->dest) == 53) {
int start_of_dns = iphdr_size + sizeof(struct udphdr);
if (start_of_dns < rc) {
process_dns(ctx->go_ctx,
buf + start_of_dns,
rc - start_of_dns);
}
}
}
}
}

exit:
closesocket(ctx->s);
free(c_ctx);
}
Loading

0 comments on commit d762fdc

Please sign in to comment.