Why?
You want to know when your defenses have failed and an attacker is running commands in your environment.
When?
You are defending a Linux host which has egress to the internet. You can hook into provisioning infrastructure automation to instrument the host. Notification via canarytokens.org is sufficient. Binary patching is allowed (i.e. binary hash differences won't trigger other forms of alerting noise). You have identified high signal-to-noise commands to hook into (i.e. ones that you expect only a hacker to run, e.g. nc
, whoami
, or even bash
).
How?
This PoC is not automated, and relies on patching entries to include our canary library by overwriting the .dynamic section DEBUG entry, so it relies on binaries already using dynamic libraries.
Once we have patched a binary to load our canary library, all we need to do is ensure that our canary library exercises the canary token on startup.
We're using a 64-bit nc
binary for this example.
Inspecting ELF
First, we inspect our binary with readelf
:
$ readelf -d `which nc`
Dynamic section at offset 0x8ba8 contains 29 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libbsd.so.0]
...
0x0000000000000015 (DEBUG) 0x0
...
There are three things of note here:
-
0x8ba8
is the start of the.dynamic
section, -
libbsd.so.0
is the first entry, and - a
DEBUG
entry exists.
Patching ELF
The corresponding code from elf.h
in Linux is:
#define DT_NEEDED 1
#define DT_DEBUG 21
typedef struct {
Elf64_Sxword d_tag; /* entry tag value */
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
We want to find entries by their d_tag
, override a d_tag
of DEBUG (21, i.e 0x15) with NEEDED (1, i.e. 0x01), and provide a valid d_ptr
value.
We find both easily enough with hexdump
:
$ hexdump -C `which nc` | grep -EA1 '^00008[bcde].. .* (01|15)'
00008ba0 70 4b 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |pK..............|
00008bb0 af 02 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00008bc0 bb 02 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00008bd0 ca 02 00 00 00 00 00 00 0c 00 00 00 00 00 00 00 |................|
--
00008c80 18 00 00 00 00 00 00 00 15 00 00 00 00 00 00 00 |................|
00008c90 00 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 |................|
Our first entry has a d_tag
of 01 00 00 00 00 00 00 00
and a d_ptr
of af 02 00 00 00 00 00 00
(resolving to "libbsd.so.0").
Our debug entry has a d_tag
of 15 00 00 00 00 00 00 00
and d_ptr
of 00 00 00 00 00 00 00 00
.
We can patch our binary with this python code:
with open('nc.patched', 'rb+') as f:
f.seek(0x8ba8)
dt_needed_entry = bytearray(f.read(0x10))
dt_needed_entry[8] = dt_needed_entry[8]+3 # eliminate "lib" prefix
f.seek(0x8c88)
f.write(dt_needed_entry)
We copy nc with: cp $(which nc) nc.patched
, and patch it with python3 patch_nc.py
.
ldd
will now show us that nc.patched
loads bsd.so.0
:
$ ldd nc.patched
...snip...
bsd.so.0 => not found
...snip...
So far so good!
Creating our canary shared library
Having create a "Web bug / URL token" at canarytokens.org, we embed a minimal HTTP GET in our shared library:
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
const char *canary_token_host = "canarytokens.com";
const char *canary_token = "YOUR_TOKEN";
void init(int argc, char **argv, char **envp) {
int sock;
if((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
return;
}
struct sockaddr_in target;
target.sin_family = AF_INET;
struct hostent *h = gethostbyname(canary_token_host);
char *target_ip = inet_ntoa(*((struct in_addr *)h->h_addr));
if (inet_pton(AF_INET, target_ip, (void *)(&(target.sin_addr.s_addr))) <= 0) {
return;
}
target.sin_port = htons(80);
if(connect(sock, (struct sockaddr *)&target, sizeof(struct sockaddr)) < 0) {
return;
}
char payload[300];
sprintf(payload,
"GET /tags/articles/%s/index.html HTTP/1.1\r\nHost: %s\r\nUser-Agent: %s\r\n\r\n",
canary_token, canary_token_host, argv[0]);
send(sock, payload, strlen(payload), 0);
char buf[100];
read(sock, buf, 100);
close(sock);
}
__attribute__((section(".init_array"))) typeof(init) *__init = init;
We verify it works correctly with a manual LD_PRELOAD
:
$ gcc -shared -fPIC canary.c -o canary.so
$ LD_PRELOAD=`pwd`/canary.so ls
...snip...
$ curl -s 'http://canarytokens.org/download?fmt=incidentlist_json&token=YOUR_TOKEN&auth=YOUR_AUTH' | jq '.[].useragent' | tail -n 1
"ls"
Excellent! (The above URL is the Export link on the Manage Your Token page)
Wiring it all together for the PoC
We put our canary.so
in place under bsd.so.0
and fire up nc.patched
!
$ sudo cp canary.so /lib/x86_64-linux-gnu/bsd.so.0
$ ./nc.patched -h
...snip...
$ curl -s 'http://canarytokens.org/download?fmt=incidentlist_json&token=YOUR_TOKEN&auth=YOUR_AUTH' | jq '.[].useragent' | tail -n 1
"nc.patched"
Success!
Top comments (0)