summaryrefslogblamecommitdiffstats
path: root/iv/orodja/ldmitm/ldmitm.c
blob: 2b8b815d375dcfdc7a3e77e3abed889f860d1890 (plain) (tree)
1
2
3
4
5
6
7
8






                                                                                              
                                                                                                                                                                                                                                                                                                    











                       

                       




                                                                                                               



                                                                                                      


                                                                                                        




                                                                                  









































                                                                                                                                                                                                                   

                                                                                                                                                                                                                                                                                                        





































                                                                                                                                               



































































                                                                                                                               















                                                                                             



                                       

                                   
                                    


                                           
                                                                                                                                           




                                                                                                
                                                  

                                                                                                                           
















                                                                                                                               




















                                                                                         
/*
DISKUSIJA
Bolje bi bilo uporabiti frida gadget: https://frida.re/docs/gadget/
Kako deluje: https://tbrindus.ca/correct-ld-preload-hooking-libc/
Prevedi z: gcc -shared -fPIC -ldl ldmitm.c -o ldmitm.so
Poženi z: LD_PRELOAD=$PWD/ldmitm.so php -S 0:1234
A je treba beležit execve in fopen? Po mojem ne. Odtekanje flagov itak vidimo v TCP sessionu.
TODO: add mutex locks, openssl bio mitm, read, write, rtt, timestamps (if possible), namesto trenutnega mtu rajši dobi advertised mss iz tcp_info, dobi last ack timestamp option value: https://elixir.bootlin.com/linux/v6.11-rc3/source/include/linux/tcp.h#L302 <= tu notri je mogoče z BPF???
*/
#include <stdio.h>
#include <dlfcn.h>
#include <sys/socket.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sqlite3.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/param.h>
#include <netinet/ip.h>
#if SQLITE_VERSION_NUMBER < 3037000
#error "we require sqlite newer than 3037000, yours is " #SQLITE_VERSION_NUMBER " --- because of STRICT!"
#endif
#define LOG(x, ...) if (getenv("LDMITM_LOG")) printf("[ldmitm %s] " x "\n", __func__ __VA_OPT__(,) __VA_ARGS__)
struct stream {
	bool active; // only stream connections are true, listening sockets and inactive fds are false
	bool silenced; // only makes sense for stream connections, true to silence traffic
	int id; // id in sqlite3 database -- only makes sense for stream connections
	int bound; // for listening sockets: port, for stream sockets: fd of listening socket
};
typedef int (*accept_t)(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
typedef int (*close_t)(int fd);
typedef int (*bind_t)(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
static accept_t real_accept = NULL;
static close_t real_close = NULL;
static bind_t real_bind = NULL;
static sqlite3 * db = NULL;
struct stream * streams = NULL;
int streams_sizeof = 0;
static void setup_db () { // should be able to ignore being called in case db is already set up on this thread
	if (db)
		return;
	int ret = sqlite3_open_v2(getenv("LDMITM_DB") ? getenv("LDMITM_DB") : ":memory:", &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_EXRESCODE, NULL);
	if (ret != SQLITE_OK) {
		LOG("failed to open db: %s", sqlite3_errstr(ret));
		goto fail;
	}
	sqlite3_stmt * stmt = NULL;
	ret = sqlite3_prepare_v3(db, "PRAGMA foreign_keys = ON;", -1, 0, &stmt, NULL);
	if (ret != SQLITE_OK) {
		LOG("failed to prepare pragma foreign_keys: %s", sqlite3_errstr(ret));
		if (stmt)
			sqlite3_finalize(stmt);
		stmt = NULL;
		goto fail;
	}
	ret = sqlite3_step(stmt);
	sqlite3_finalize(stmt);
	stmt = NULL;
	if (ret != SQLITE_DONE) {
		LOG("failed to step pragma foreign_keys: %s", sqlite3_errstr(ret));
		goto fail;
	}
#define CREATE_TABLE(name, columns) \
	ret = sqlite3_prepare_v3(db, "create table if not exists " name " (" columns ") STRICT;", -1, 0, &stmt, NULL); \
	if (ret != SQLITE_OK) { \
		LOG("failed to prepare create " name ": %s, statement: %s", sqlite3_errstr(ret), "create table if not exists " name " (" columns ") STRICT;"); \
		if (stmt) \
			sqlite3_finalize(stmt); \
		stmt = NULL; \
		goto fail; \
	} \
	ret = sqlite3_step(stmt); \
	sqlite3_finalize(stmt); \
	stmt = NULL; \
	if (ret != SQLITE_DONE) { \
		LOG("failed to step create " name ": %s", sqlite3_errstr(ret)); \
		goto fail; \
	}
	CREATE_TABLE("connections", "id INTEGER PRIMARY KEY, bound INTEGER NOT NULL, peer TEXT NOT NULL, accepted TEXT DEFAULT (strftime('%FT%R:%f', 'now')) NOT NULL, closed TEXT, silenced TEXT, mtu INTEGER NOT NULL"); // accepted, closed and silenced are iso datestrings, bound is listening port
	CREATE_TABLE("messages", "connection INTEGER NOT NULL, direction INTEGER NOT NULL CHECK (direction IN (0, 1)), time TEXT DEFAULT (strftime('%FT%R:%f', 'now')) NOT NULL, rtt INTEGER NOT NULL, FOREIGN KEY(connection) REFERENCES connections(id)");
	return;
fail:
	if (db)
		sqlite3_close_v2(db);
	db = NULL;
	return;
}
static void end_stream (int fd) { // cleanup function, should be able to handle being called on already ended stream
	if (fd >= streams_sizeof || !streams[fd].active)
		return;
	setup_db();
	if (db) {
		sqlite3_stmt * stmt = NULL;
		int ret = sqlite3_prepare_v3(db, "update connections set closed=strftime('%FT%R:%f', 'now') WHERE id=:i;", -1, 0, &stmt, NULL);
		if (ret != SQLITE_OK) {
			LOG("failed to prepare update connections set closed: %s", sqlite3_errstr(ret));
			sqlite3_finalize(stmt);
			stmt = NULL;
			goto fail;
		}
		ret = sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, ":i"), streams[fd].id);
		if (ret != SQLITE_OK) {
			LOG("failed to bind update connections set closed: %s", sqlite3_errstr(ret));
			sqlite3_finalize(stmt);
			stmt = NULL;
			goto fail;
		}
		ret = sqlite3_step(stmt);
		sqlite3_finalize(stmt);
		stmt = NULL;
		if (ret != SQLITE_DONE) {
			LOG("failed to step update connections set closed: %s", sqlite3_errstr(ret));
			goto fail;
		}
	}
fail:
	memset(&(streams[fd]), 0, sizeof streams[fd]);
}
static bool more_fds (int largest_fd) {
	if (largest_fd >= streams_sizeof) {
		int streams_sizeof_new;
		if (streams_sizeof == 0)
			streams_sizeof_new = 128;
		else
			streams_sizeof_new = streams_sizeof * 2;
		struct stream * streams_new = realloc(streams, streams_sizeof_new*sizeof *streams);
		if (!streams_new) {
			LOG("ENOMEM realloc streams!");
			return false;
		}
		memset(streams_new+streams_sizeof, 0, (streams_sizeof_new-streams_sizeof)*sizeof *streams);
		streams = streams_new;
		streams_sizeof = streams_sizeof_new;
	}
	if (largest_fd < streams_sizeof)
		return true;
	return more_fds(largest_fd);
}
static int port_from_sa (const struct sockaddr * sa) { // gets port from sockaddr
	switch (sa->sa_family) {
		case AF_INET:
			return ntohs(((struct sockaddr_in *) sa)->sin_port);
		case AF_INET6:
			return ntohs(((struct sockaddr_in6 *) sa)->sin6_port);
		default:
			return -1;
	}
}
static void str_from_sa (char * peeraddress, const struct sockaddr * address) {
	switch (address->sa_family) {
		case AF_INET:
			if (!inet_ntop(AF_INET, &(((struct sockaddr_in *) address)->sin_addr), peeraddress, *address_len)) {
				int myerrno = errno;
				strcpy(peeraddress, "!");
				strcat(peeraddress, strerror(myerrno));
			}
			sprintf(peeraddress+strlen(peeraddress), "/%d", ntohs(((struct sockaddr_in *) address)->sin_port));
			break;
		case AF_INET6:
			if (!inet_ntop(AF_INET6, &(((struct sockaddr_in6 *) address)->sin6_addr), peeraddress, *address_len)) {
				int myerrno = errno;
				strcpy(peeraddress, "!");
				strcat(peeraddress, strerror(myerrno));
			}
			sprintf(peeraddress+strlen(peeraddress), "/%d", ntohs(((struct sockaddr_in6 *) address)->sin6_port));
			break;
		default:
			strcpy(peeraddress, "unknown family");
			break;
	}
}
int bind (int sockfd, const struct sockaddr * addr, socklen_t addrlen) {
	if (!real_bind)
		real_bind = dlsym(RTLD_NEXT, "bind");
	LOG("called bind()");
	int ret = real_bind(sockfd, addr, addrlen);
	int old_errno = errno;
	if (ret == -1)
		goto fail;
	if (!more_fds(sockfd))
		goto fail; // enomem
	streams[sockfd].bound = port_from_sa(addr);
fail:
	errno = old_errno;
	return ret;
}
int close (int fd) {
	if (!real_close)
		real_close = dlsym(RTLD_NEXT, "close");
	LOG("called close()");
	int ret = real_close(fd);
	int old_errno = errno;
	end_stream(fd);
	errno = old_errno;
	return ret;
}
int accept (int socket, struct sockaddr *restrict address, socklen_t *restrict address_len) {
	if (!real_accept)
		real_accept = dlsym(RTLD_NEXT, "accept");
	LOG("called accept()");
	int ret = real_accept(socket, address, address_len);
	int saved_errno = errno;
	if (ret == -1)
		goto fail;
	if (!more_fds(MAX(ret, socket))
		goto fail; // enomem
	end_stream(ret);
	streams[ret].active = true;
	streams[ret].bound = socket;
	setup_db();
	if (db) {
		sqlite3_stmt * stmt = NULL;
		int sqlret = sqlite3_prepare_v3(db, "insert into connections (peer, bound, mtu) values (:p, :b, :m);", -1, 0, &stmt, NULL);
		if (sqlret != SQLITE_OK) {
			LOG("failed to prepare insert connections: %s", sqlite3_errstr(sqlret));
			goto sqlfail;
		}
		char peeraddress[INET_ADDRSTRLEN+128];
		str_from_sa(peeraddress, address);
		sqlret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":p"), peeraddress, -1, SQLITE_STATIC);
		if (sqlret != SQLITE_OK) {
			LOG("failed to bind insert connection text: %s", sqlite3_errstr(sqlret));
			goto sqlfail;
		}
		sqlret = sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, ":b"), streams[streams[ret].bound].bound);
		if (sqlret != SQLITE_OK) {
			LOG("failed to bind insert connection bound: %s", sqlite3_errstr(sqlret));
			goto sqlfail;
		}
		int mtu;
		int mtusize = sizeof mtu;
		if (getsockopt(socket, IPPROTO_IP, IP_MTU, &mtu, &mtusize) == -1) {
			mtu = -errno;
			LOG("failed to get MTU: %s", -mtu);
		}
		sqlret = sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, ":m"), mtu);
		if (sqlret != SQLITE_OK) {
			LOG("failed to bind insert connection mtu: %s", sqlite3_errstr(sqlret));
			goto sqlfail;
		}
		sqlret = sqlite3_step(stmt);
		sqlite3_finalize(stmt);
		stmt = NULL;
		if (sqlret != SQLITE_DONE) {
			LOG("failed to step insert connection: %s", sqlite3_errstr(ret));
			goto sqlfail;
		}
sqlfail:
		sqlite3_finalize(stmt);
		stmt = NULL;
		goto fail;
	}
fail:
	errno = saved_errno;
	return ret;
}
__attribute__((constructor)) static void setup (void) {
	LOG("called setup()");
}