/* 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!!! */ #include #include #include #include #include #include #include #include #include #include #include #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; bool silenced; int id; // id in sqlite3 database }; typedef int (*accept_t)(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len); typedef int (*close_t)(int fd); static accept_t real_accept; static close_t real_close; static __thread 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, peer TEXT NOT NULL, accepted TEXT DEFAULT (strftime('%FT%R:%f', 'now')) NOT NULL, closed TEXT, silenced TEXT"); // accepted, closed and silenced are iso datestrings 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, 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]); } 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 >= 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!"); goto fail; } memset(streams_new+streams_sizeof, 0, (streams_sizeof_new-streams_sizeof)*sizeof *streams); streams = streams_new; streams_sizeof = streams_sizeof_new; } end_stream(ret); streams[ret].active = true; setup_db(); if (db) { sqlite3_stmt * stmt = NULL; int sqlret = sqlite3_prepare_v3(db, "insert into connections (peer) values (:p);", -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]; 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; } 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: %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()"); }