DEV Community

Cover image for CanaryUsb
Carvilsi [Char]
Carvilsi [Char]

Posted on • Edited on

CanaryUsb

Introducing Canary Tokens

Some time ago I found Thinkst Canary and I fall in love with it.

It's already well know but, the idea behind Canary Tokens is to have a notification if you have security breach. The notification is via email, and you have different types of tokens, these will trigger a notification under certain situation, for example a URL token will be triggered when a URL is visited.

One of my favorites is the QR code token, it's meant for physical things; e.g. You can stick the QR code inside of your prototype case, and if someone scans it, at least you know that someone is snooping around.

Just want to mention, that when you create a new Canary Token, apart of email, where you want to receive the notification, you need to provide a reminder note to describe the token.

QR code token creation

You can check the different types of Canary tokens that Thinkst Canary have, and they explain all the things better than me.

CanaryUsb

So now that we now about CanaryTokens; Lets see what CanaryUsb is.

What is it?

CanaryUsb monitors the USB activity on a Linux system, and will call a CanaryToken when a not known USB is connected.

Here we are thinking about removable media threats like BadUSB or physical attacks to extract data e.g. on remote servers or on unattended laptops.

The code is in c and right now, and unless someone borrow me a Mac, canaryusb only works on Linux (is not even an idea to support Windows).

The DNS Canary token

CanaryUsb uses a CanaryToken of type DNS token that alerts when a hostname is requested. As it's described at the DNS token Documentation one of the features is that the token can carry a small extra data.
This is used by canaryusb to send the fingerprint of the connected usb; so the notification via mail will include this info.

A USB fingerprint

Since the total number of bytes that could be included among the DNS token is ~125 bytes, we need to restrict the amount of data that we take from from USB attributes.
We choose the next format for the usb fingerprint:

<vendor>:<product>-<product_name>-<serial>

e.g. for a USB with the next attributes:

vendor: 18d1
product: 4ee8
product name: OnePlus Nord
serial: d3115c34

The USB fingerprint will be: 18d1:4ee8-OnePlus_Nord-d3115c34

The struct that holds the usb fingerprint looks like:

typedef struct
{
        char *vendor;
        char *product;
        char *product_name;
        char *serial;
        char *syspath;
} UsbAttrs;
Enter fullscreen mode Exit fullscreen mode

Note that the syspath variable is there just for debugging pourposes and it's not used to define the usb fingerptint.
We initialize this struct as:

UsbAttrs usbattr = {"0000", "0000", "no", "no", "no-path"};
Enter fullscreen mode Exit fullscreen mode

So we fill the gaps in case of a missing attribute.
It's not guaranteed that the 4 attributes which with we want to build the usb fingerprint will be present.

Monitoring the USB devices

The USB attributes, to build the usb fingerprint, are retrieved with libsystemd, specifically usage of sd-device and monitoring the USB devices activity.

The monitor code is quite brief; failure handling and finishing actions are omitted for brevity:

void monitor_usb()
{
        sd_device_monitor *sddm = NULL;
        sd_device_monitor_new(&sddm);

        sd_device_monitor_filter_add_match_subsystem_devtype(sddm, SUBSYSTEM, DEVICE_TYPE);

        sd_device_monitor_start(sddm, usb_monitor_handler, NULL);

        sd_event *event = sd_device_monitor_get_event(sddm);
        sd_event_loop(event);

        /*
         * Deal with errors and exit actions
         * like stooping monitor and un-references
         */
}
Enter fullscreen mode Exit fullscreen mode

Once the event is triggered the handler function, usb_monitor_handler, takes care of the rest; retrieve the USB attributes to build the the usb fingerprint, build the DNS Canary Token and perform the DNS call.

Building the DNS Canary Token

We need to convert the usb fingerprint into base32 removing the padding '=' characters adding a dot after every 63-bytes and prepare the hostname call for DNS token, from the Documentation is something like:

<base32-string>.<base32-string>.G<2-random-digits>.<dns-token>

Also we'll strip the usb fingerprint if the length exceeds the maximum data to encode; 63 bytes for each base32-string block.

Calling the Canary

Once we have the DNS Canary Token, we just need to call it, using getaddrinfo (netdb.h):

int call_the_canary(const char *canary_dns_token)
{
        int canaryrsp;
        struct addrinfo hints, *res;
        memset(&hints, 0, sizeof(hints));
        canaryrsp = getaddrinfo(canary_dns_token, CANARY_PORT, &hints, &res);
        return canaryrsp;
}
Enter fullscreen mode Exit fullscreen mode

The parameter canary_dns_token it's the value that we get on previous step, and for CANARY_PORT we use 80.
We do not care about the response, variable res of getaddrinfo but the return integer of it, a nonzero means an error.

Here you can see the notification that I received from the above example of usb fingerprint:

Image description

Some other things

In order to avoid notifications from your own devices, canaryusb supports a usb trusted list, so only the devices that are not at this list will send a notification.
To make the life easer canaryusb could read values for the CanaryToken and the usb fingerprint trusted list from a config file. The format of the config file is toml and I found very useful tomlc99.

Top comments (0)