diff options
Diffstat (limited to 'iv/orodja/ldmitm/ldmitm.c')
-rw-r--r-- | iv/orodja/ldmitm/ldmitm.c | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/iv/orodja/ldmitm/ldmitm.c b/iv/orodja/ldmitm/ldmitm.c new file mode 100644 index 0000000..2b8b815 --- /dev/null +++ b/iv/orodja/ldmitm/ldmitm.c @@ -0,0 +1,262 @@ +/* +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()"); +} |