diff options
Diffstat (limited to 'iv/orodja/ldmitm')
-rw-r--r-- | iv/orodja/ldmitm/.gitignore | 1 | ||||
-rw-r--r-- | iv/orodja/ldmitm/Makefile | 20 | ||||
-rw-r--r-- | iv/orodja/ldmitm/ldmitm.c | 262 | ||||
-rw-r--r-- | iv/orodja/ldmitm/tcp_times.c | 114 | ||||
-rw-r--r-- | iv/orodja/ldmitm/tcp_times.h | 27 | ||||
-rw-r--r-- | iv/orodja/ldmitm/tcp_times_example.c | 80 |
6 files changed, 504 insertions, 0 deletions
diff --git a/iv/orodja/ldmitm/.gitignore b/iv/orodja/ldmitm/.gitignore new file mode 100644 index 0000000..3ce51cd --- /dev/null +++ b/iv/orodja/ldmitm/.gitignore @@ -0,0 +1 @@ +tcp_times_example diff --git a/iv/orodja/ldmitm/Makefile b/iv/orodja/ldmitm/Makefile new file mode 100644 index 0000000..1ef8a92 --- /dev/null +++ b/iv/orodja/ldmitm/Makefile @@ -0,0 +1,20 @@ +obj-m += tcp_times.o + +all: allmods tcp_times_example ldmitm.so + +%.so: %.c + cc -shared -fPIC -ldl $< -o $@ + +%: %.c + cc -g -Wall -Wextra -pedantic -Wformat -Wformat-security -o$@ $< + +allmods: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean + rm ldmitm.so tcp_times_example + +prepare: + apt install libsqlite3-dev linux-headers-generic + +.PHONY: allmods clean prepare 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()"); +} diff --git a/iv/orodja/ldmitm/tcp_times.c b/iv/orodja/ldmitm/tcp_times.c new file mode 100644 index 0000000..02a067a --- /dev/null +++ b/iv/orodja/ldmitm/tcp_times.c @@ -0,0 +1,114 @@ +/* +Prevajanje: make +Namestitev v jedro: insmod tcp_times.ko +Uporaba v C: +#include <stdint.h> +#include "tcp_times.h" +int tcp_times = open("/proc/tcp_times", O_RDWR); +if (tcp_times == -1) { + perror("open tcp_times"); + break; +} +int tcpsock = accept(boundsocket, &address, &addrlen); +struct tcp_times tt = { + .fd = tcpsock +}; +if (ioctl(tcp_times, 0, &tt) == -1) { + perror("ioctl tcp_times"); + break; +} +// sedaj so polja v tt populirana +*/ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/proc_fs.h> +#include <linux/net.h> +#include <linux/tcp.h> +#include <linux/version.h> +#include "tcp_times.h" +MODULE_AUTHOR("Anton Luka Šijanec <anton@sijanec.eu>"); +MODULE_DESCRIPTION("tcp last received tsval, rtt procfs ioctl driver"); +MODULE_LICENSE(""); +static struct proc_dir_entry * ent; +static long myioctl (struct file * filep, unsigned int cmd, unsigned long arg) { + switch(cmd) { + case 0: + struct tcp_times tt; + if (copy_from_user(&tt, (void *) arg, sizeof tt)) + return -EFAULT; + struct fd f = fdget(tt.fd); + if (!f.file) + return -EBADF; + struct socket * sock = sock_from_file(f.file); + if (!sock) { + fdput(f); + return -ENOTSOCK; + } + if (!(sock->type & SOCK_STREAM)) { + fdput(f); + return -ENOSTR; + } + if (!sock->sk) { + fdput(f); + return -EBADFD; + } + if (sock->sk->sk_protocol != IPPROTO_TCP) { + fdput(f); + return -ESOCKTNOSUPPORT; + } + struct tcp_sock * tp = tcp_sk(sock->sk); + tt.ts_recent_stamp = tp->rx_opt.ts_recent_stamp; + tt.ts_recent = tp->rx_opt.ts_recent; + tt.rcv_tsval = tp->rx_opt.rcv_tsval; + tt.rcv_tsecr = tp->rx_opt.rcv_tsecr; + tt.saw_tstamp = tp->rx_opt.saw_tstamp; + tt.tstamp_ok = tp->rx_opt.tstamp_ok; + tt.dsack = tp->rx_opt.dsack; + tt.wscale_ok = tp->rx_opt.wscale_ok; + tt.sack_ok = tp->rx_opt.sack_ok; + tt.smc_ok = tp->rx_opt.smc_ok; + tt.snd_wscale = tp->rx_opt.snd_wscale; + tt.rcv_wscale = tp->rx_opt.rcv_wscale; + tt.advmss = tp->advmss; + tt.rttvar_us = tp->rttvar_us; + tt.srtt_us = tp->srtt_us; + tt.rcv_rtt_est.rtt_us = tp->rcv_rtt_est.rtt_us; + tt.rcv_rtt_est.seq = tp->rcv_rtt_est.seq; + tt.rcv_rtt_est.time = tp->rcv_rtt_est.time; + tt.rtt_us = tp->rack.rtt_us; + tt.mdev_max_us = tp->mdev_max_us; + fdput(f); + if (copy_to_user((void *) arg, &tt, sizeof tt)) + return -EFAULT; + return 0; + default: + return -EINVAL; + } +} +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,6,0) +static const struct file_operations ops = { + .owner = THIS_MODULE, + .unlocked_ioctl = myioctl, +}; +#else +static const struct proc_ops ops = { + .proc_ioctl = myioctl, +}; +#endif +static int __init custom_init (void) { + ent = proc_create("tcp_times", 0666, NULL, &ops); + if (!ent) { + printk(KERN_INFO "tcp_times failed to create procfs entry."); + return -EINVAL; + } + printk(KERN_INFO "tcp_times kernel module loaded."); + return 0; +} +static void __exit custom_exit (void) { + proc_remove(ent); + printk(KERN_INFO "tcp_times kernel module exiting ..."); +} +module_init(custom_init); +module_exit(custom_exit); diff --git a/iv/orodja/ldmitm/tcp_times.h b/iv/orodja/ldmitm/tcp_times.h new file mode 100644 index 0000000..3368c7b --- /dev/null +++ b/iv/orodja/ldmitm/tcp_times.h @@ -0,0 +1,27 @@ +#define TCP_TIMES_PRINTF_FORMAT "fd: %d, ts_recent_stamp: %d, ts_recent: %u, rcv_tsval: %u, rcv_tsecr: %u, saw_tstamp: %u, tstamp_ok: %u, dsack: %u, wscale_ok: %u, sack_ok: %u, snd_wscale: %u, rcv_wscale: %u, advmss: %u, rttvar_us: %u, srtt_us: %u, rcv_rtt_est.rtt_us: %u, rcv_rtt_est.seq: %u, rcv_rtt_est.time: %lu, rtt_us: %u, mdev_max_us: %u" +#define TCP_TIMES_PRINTF_VARIABLES(tt) tt fd, tt ts_recent_stamp, tt ts_recent, tt rcv_tsval, tt rcv_tsecr, tt saw_tstamp, tt tstamp_ok, tt dsack, tt wscale_ok, tt sack_ok, tt snd_wscale, tt rcv_wscale, tt advmss, tt rttvar_us, tt srtt_us, tt rcv_rtt_est.rtt_us, tt rcv_rtt_est.seq, tt rcv_rtt_est.time, tt rtt_us, tt mdev_max_us +struct tcp_times { // Polja so pobrana iz jedra. Glej https://elixir.bootlin.com/linux/v6.11-rc4/source/include/linux/tcp.h#L302 + int fd; + int ts_recent_stamp;/* Time we stored ts_recent (for aging) */ + uint32_t ts_recent; /* Time stamp to echo next */ + uint32_t rcv_tsval; /* Time stamp value */ + uint32_t rcv_tsecr; /* Time stamp echo reply */ + uint16_t saw_tstamp : 1, /* Saw TIMESTAMP on last packet */ + tstamp_ok : 1, /* TIMESTAMP seen on SYN packet */ + dsack : 1, /* D-SACK is scheduled */ + wscale_ok : 1, /* Wscale seen on SYN packet */ + sack_ok : 3, /* SACK seen on SYN packet */ + smc_ok : 1, /* SMC seen on SYN packet */ + snd_wscale : 4, /* Window scaling received from sender */ + rcv_wscale : 4; /* Window scaling to send to receiver */ + uint16_t advmss; /* Advertised MSS */ + uint32_t rttvar_us; /* smoothed mdev_max */ + uint32_t srtt_us; /* smoothed round trip time << 3 in usecs */ + struct { + uint32_t rtt_us; + uint32_t seq; + uint64_t time; + } rcv_rtt_est; + uint32_t rtt_us; /* Associated RTT */ + uint32_t mdev_max_us; /* maximal mdev for the last rtt period */ +}; // Ne vem, kaj veliko polj tu pomeni, vendar jih dodajam. Mogoče bodo uporabna. diff --git a/iv/orodja/ldmitm/tcp_times_example.c b/iv/orodja/ldmitm/tcp_times_example.c new file mode 100644 index 0000000..707bfce --- /dev/null +++ b/iv/orodja/ldmitm/tcp_times_example.c @@ -0,0 +1,80 @@ +/* +Posluša na TCP vratih 6969, prejme eno povezavo, vsako sekundo nanjo izpiše LF in piše statistiko, dobljeno iz jedrnega modula tcp_times. +*/ +#include <stdint.h> +#include "tcp_times.h" +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <unistd.h> +#include <stdbool.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <signal.h> +int samomor = 0; +void handler (int sig __attribute__((unused))) { + samomor = 1; +} +int main (void) { + if (signal(SIGINT, handler) == SIG_ERR) { + perror("signal"); + return 1; + } + if (signal(SIGHUP, handler) == SIG_ERR) { + perror("signal"); + return 1; + } + if (signal(SIGTERM, handler) == SIG_ERR) { + perror("signal"); + return 1; + } + int tcp_socket = socket(AF_INET6, SOCK_STREAM, 0); + if (tcp_socket == -1) { + perror("socket"); + return 1; + } + struct sockaddr_in6 sa6 = { + .sin6_family = AF_INET6, + .sin6_port = htons(6969), + .sin6_addr = IN6ADDR_ANY_INIT, + }; + if (bind(tcp_socket, (struct sockaddr *) &sa6, sizeof sa6) == -1) { + perror("bind"); + return 1; + } + if (listen(tcp_socket, 1 /* only one client is handled*/) == -1) { + perror("listen"); + goto die; + } + int flow = accept(tcp_socket, NULL, NULL); + if (flow == -1) { + perror("accept"); + goto die; + } + int tcp_times = open("/proc/tcp_times", O_RDWR); + struct tcp_times tt = { + .fd = flow, + }; + char buf = '\n'; + while (true) { + if (ioctl(tcp_times, 0, &tt) == -1) { + perror("ioctl"); + break; + } + printf(TCP_TIMES_PRINTF_FORMAT "\n", TCP_TIMES_PRINTF_VARIABLES(tt.)); + if (send(flow, &buf, 1, MSG_NOSIGNAL) == -1) { + perror("write"); + break; + } + if (samomor) + break; + sleep(1); + } +die: + close(tcp_times); + close(flow); + close(tcp_socket); + return 1; +} |