diff --git a/build_client.sh b/build_client.sh deleted file mode 100755 index 4434300..0000000 --- a/build_client.sh +++ /dev/null @@ -1,5 +0,0 @@ -PROJ_ROOT=`pwd` - -# build client -mkdir -p $PROJ_ROOT/client/build -cd $PROJ_ROOT/client/build && cmake .. && make diff --git a/build_server.sh b/build_server.sh deleted file mode 100755 index 91e1c0e..0000000 --- a/build_server.sh +++ /dev/null @@ -1,8 +0,0 @@ -PROJ_ROOT=`pwd` - -# build server -mkdir -p $PROJ_ROOT/server/build -cd $PROJ_ROOT/server/build && cmake .. && make - -#build driver -cd $PROJ_ROOT/driver && make && cd $PROJ_ROOT diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt deleted file mode 100644 index 1caeaca..0000000 --- a/client/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.5) -project(remramd_client VERSION 1.0 LANGUAGES CXX) - -set(DEBUG "-g") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DEBUG}") - -add_executable( remramd_client - ./src/client_core.cpp - ./src/remramd_client.cpp -) diff --git a/client/inc/Client.hpp b/client/inc/Client.hpp new file mode 100644 index 0000000..642bd91 --- /dev/null +++ b/client/inc/Client.hpp @@ -0,0 +1,14 @@ +#pragma once +#include +#include + +namespace remramd { + class Client { + private: + void run_netcat_tcp_listener(const std::uint16_t reverse_shell_port); + public: + void connect(const std::string &server_ip, const std::uint16_t server_port); + Client(const Client&) = delete; + Client& operator = (const Client&) = delete; + }; +} \ No newline at end of file diff --git a/client/include/client_core.hpp b/client/include/client_core.hpp deleted file mode 100644 index 52211ae..0000000 --- a/client/include/client_core.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include -#include - -#define MIN_TCP_PORT 1024 -#define MAX_TCP_PORT 49152 - -enum { - SERV_DECL, - SERV_ACC -}; - -uint16_t brute_valid_port(); -uint16_t send_random_port(const std::string &, const uint16_t); -uint16_t wait_server_response(uint16_t); -void invoke_tcp_listener(const std::string, uint16_t, char **); - diff --git a/client/remramd_client b/client/remramd_client new file mode 100755 index 0000000..2896840 Binary files /dev/null and b/client/remramd_client differ diff --git a/client/src/Client.cpp b/client/src/Client.cpp new file mode 100644 index 0000000..2164066 --- /dev/null +++ b/client/src/Client.cpp @@ -0,0 +1,21 @@ +#include "../inc/Client.hpp" +#include "../../shared/inc/Protocol.hpp" +#include "../../shared/inc/remramd_exception.hpp" +#include +#include + +namespace remramd { + void Client::connect(const std::string &server_ip, const std::uint16_t server_port) { + std::cout << "Server => IP: " << server_ip << " | Port: " << server_port << std::endl; + const auto reverse_shell_port { internal::Protocol::request_connection(server_ip, server_port) }; + run_netcat_tcp_listener(reverse_shell_port); + } + + void Client::run_netcat_tcp_listener(const std::uint16_t reverse_shell_port) { + const std::string nc_path { "/bin/nc" }, + nc_flags { "-nvlp" }; + const char* const args[] { nc_path.c_str(), nc_flags.c_str(), std::to_string(reverse_shell_port).c_str(), nullptr }; + execve(nc_path.c_str(), (char* const*)args, nullptr); + std::cerr << "FAIL\n"; + } +} diff --git a/client/src/client_core.cpp b/client/src/client_core.cpp deleted file mode 100644 index 3f98b16..0000000 --- a/client/src/client_core.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../include/client_core.hpp" - -uint16_t brute_valid_port() { - std::default_random_engine rand_gen { std::random_device()() }; - std::uniform_int_distribution ushort_distribution(MIN_TCP_PORT, MAX_TCP_PORT); - uint16_t random_port {}; - int check_port {}; - do { - random_port = ushort_distribution(rand_gen); - int test_sock = socket(AF_INET, SOCK_STREAM, 0); - if (test_sock == -1) { - std::cerr << "Cannot open socket fd" << std::endl; - exit(EXIT_FAILURE); - } - struct sockaddr_in test_sock_info {}; - test_sock_info.sin_addr.s_addr = INADDR_ANY; - test_sock_info.sin_port = htons(random_port); - test_sock_info.sin_family = AF_INET; - check_port = bind(test_sock, (struct sockaddr *)&test_sock_info, sizeof(struct sockaddr_in)); - shutdown(test_sock, SHUT_RDWR); - close(test_sock); - } while (check_port); - return random_port; -} - -uint16_t send_random_port(const std::string &server_ip, const uint16_t server_port) { - int ping_fd = socket(AF_INET, SOCK_STREAM, 0); - if (ping_fd == -1) { - std::cerr << "Cannot create socket fd" << std::endl; - exit(EXIT_FAILURE); - } - struct sockaddr_in server_info {}; - server_info.sin_family = AF_INET; - server_info.sin_port = htons(server_port); - if (inet_pton(AF_INET, server_ip.data(), &server_info.sin_addr) != 1) { - std::cerr << "inet_pton IP address convertion failure" << std::endl; - exit(EXIT_FAILURE); - } - if (connect(ping_fd, (struct sockaddr *)&server_info, sizeof(struct sockaddr_in))) { - fprintf(stderr, "Cannot connect to the server %s that listening on port %hu, the server is probably down at this moment\n", server_ip.data(), - server_port); - exit(EXIT_FAILURE); - } - // 2) Randomize TCP listening port - uint16_t random_port { brute_valid_port() }; - // 3) Send the randomized TCP port to the server and close the connection - uint16_t bigend_rand_port { htons(random_port) }; // host byte order (either big or little endian) to network byte order (always big endian) - std::cout << "Random port for remote shell is " << random_port << std::endl; - if (write(ping_fd, &bigend_rand_port, sizeof(uint16_t)) == -1) { - std::cerr << "Cannot send random port to the server" << std::endl; - exit(EXIT_FAILURE); - } - shutdown(ping_fd, SHUT_RDWR); - close(ping_fd); - return random_port; -} - -uint16_t wait_server_response(uint16_t random_port) { - int resp_sock_fd = socket(AF_INET, SOCK_STREAM, 0); - if (resp_sock_fd == -1) { - std::cerr << "Cannot open socket fd" << std::endl; - exit(EXIT_FAILURE); - } - struct sockaddr_in curr_client_info {}; - curr_client_info.sin_addr.s_addr = INADDR_ANY; - curr_client_info.sin_port = htons(random_port); - curr_client_info.sin_family = AF_INET; - if (bind(resp_sock_fd, (struct sockaddr *)&curr_client_info, sizeof(struct sockaddr))) { - fprintf(stderr, "Cannot bind port #%hu\n", random_port); - exit(EXIT_FAILURE); - } - if (listen(resp_sock_fd, 10)) { - std::cerr << "Cannot listen\n" << std::endl; - exit(EXIT_FAILURE); - } - socklen_t socklen = sizeof(struct sockaddr_in); - std::cout << "Waiting for connection approval..." << std::endl; - int serv_resp_fd = accept(resp_sock_fd, (struct sockaddr *)&curr_client_info, &socklen); - if (serv_resp_fd == -1) { - std::cerr << "Cannot accept the incoming connection" << std::endl; - exit(EXIT_FAILURE); - } - uint16_t serv_resp {}; - if (read(serv_resp_fd, &serv_resp, sizeof(uint16_t)) == -1) { - fprintf(stderr, "Cannot read the server response from the socket fd #%hu\n", random_port); - exit(EXIT_FAILURE); - } - serv_resp = ntohs(serv_resp); - shutdown(serv_resp_fd, SHUT_RDWR); - shutdown(resp_sock_fd, SHUT_RDWR); - close(serv_resp_fd); - close(resp_sock_fd); - return serv_resp; -} - -void invoke_tcp_listener(const std::string server_ip, uint16_t random_port, char **envp) { - std::cout << "The server has accepted your connection request! Wait for the remote shell..." << std::endl; - const std::string random_port_str { std::to_string(random_port) }, - nc_flags { "-nvlp" }; - // run netcat TCP listener - const char* const args[] = { "/bin/nc", nc_flags.data(), random_port_str.data(), nullptr }; - execve("/bin/nc", (char* const *)args, envp); - std::cerr << "client's execve failed, check if netcat is installed" << std::endl; - exit(EXIT_FAILURE); -} - diff --git a/client/src/client_main.cpp b/client/src/client_main.cpp new file mode 100644 index 0000000..122e1c7 --- /dev/null +++ b/client/src/client_main.cpp @@ -0,0 +1,18 @@ +#include "../inc/Client.hpp" +#include "../../shared/inc/Connection.hpp" +#include "../../shared/inc/remramd_exception.hpp" +#include + +int main(int argc, char **argv) { + if (argc != 3) { + std::cerr << "Usage: " << argv[0] << " \n"; + exit(1); + } + remramd::Client client_obj {}; + try { + client_obj.connect(argv[1], std::stoi(argv[2])); + } catch (const remramd::exception &e) { + std::cerr << e.what() << std::endl; + } + return 0; +} \ No newline at end of file diff --git a/client/src/remramd_client.cpp b/client/src/remramd_client.cpp deleted file mode 100644 index 5302c78..0000000 --- a/client/src/remramd_client.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "../include/client_core.hpp" -#include - -int main(int argc, char **argv, char **envp) { - if (argc != 3) { - fprintf(stderr, "Usage: %s \n", argv[0]); - exit(EXIT_FAILURE); - } - uint16_t server_port { static_cast(std::stoul(argv[2])) }; - std::string server_ip = argv[1]; - uint16_t random_port = send_random_port(server_ip, server_port); - uint16_t server_resp = wait_server_response(random_port); - if (server_resp == SERV_ACC) { - invoke_tcp_listener(server_ip, random_port, envp); - } - std::cerr << "The server has refused to establish the connection, sorry." << std::endl; - return 1; -} diff --git a/install_client.sh b/install_client.sh new file mode 100755 index 0000000..7e43285 --- /dev/null +++ b/install_client.sh @@ -0,0 +1 @@ +g++ client/src/*.cpp shared/src/*.cpp server/src/PipeWrapper.cpp -lpthread -std=c++17 -O3 -o client/remramd_client diff --git a/install_server.sh b/install_server.sh new file mode 100755 index 0000000..2836076 --- /dev/null +++ b/install_server.sh @@ -0,0 +1,8 @@ +if [[ $EUID -ne 0 ]]; then + echo "The server installer must be run as root" + exit 1 +fi + +g++ server/src/*.cpp shared/src/*.cpp -lpthread -std=c++17 -O3 -o server/remramd_server + +cd driver && make && insmod ramd.ko && cd .. diff --git a/scripts/chroot_jail_builder.py b/scripts/chroot_jail_builder.py deleted file mode 100755 index fadd518..0000000 --- a/scripts/chroot_jail_builder.py +++ /dev/null @@ -1,61 +0,0 @@ -import subprocess, os, re, sys - -def copy_misc_stuff(jail_path): - os.system("cp --parents /etc/resolv.conf {}".format(jail_path)) - os.system("mkdir -p {}/dev".format(jail_path)) - os.system("mknod {}/dev/null c 1 2".format(jail_path)) - os.system("mknod {}/dev/tty c 1 3".format(jail_path)) - os.system("chmod 666 {}/dev/null {}/dev/tty".format(jail_path, jail_path)) - -def copy_to_chroot_jail(so_dependencies, cmd_line_args): - cp_so_deps_cmd = "cp --parents " - jail_path = cmd_line_args[1] - for so in so_dependencies: - cp_so_deps_cmd += (so + " ") - - cp_so_deps_cmd += jail_path - stripped_usr_dir_cmd = cp_so_deps_cmd.replace("/usr", "") + " " + jail_path + " 2>/dev/null" - os.system(cp_so_deps_cmd) - os.system(stripped_usr_dir_cmd) - for arg_idx in range(2, len(cmd_line_args)): - os.system("cp --parents {} {} 2>/dev/null".format(cmd_line_args[arg_idx], jail_path)) - if len(jail_path) > 0: - os.system("rm -rf {}/home".format(jail_path)) - -def parse_ldd_output(bin_path): - output = subprocess.check_output(['ldd', bin_path]) - lines = output.splitlines() - filtered_splitted_lines = [] - - for x in lines: - curr_line = x.decode() - curr_line = curr_line.strip() - if re.match(r'.*(/lib/.*|/usr/lib/.*|/lib64/.*|/usr/lib64/.*).+', curr_line) is not None: - splitted_curr_line = curr_line.split() - filtered_splitted_lines.append(splitted_curr_line) - - libs = [] - - for token in filtered_splitted_lines: - for string in token: - if re.match(r'^(/lib/.*|/usr/lib/.*|/lib64/.*|/usr/lib64/.*).+', string) is not None: - libs.append(string) - return libs - -def print_usage(script_name): - print("Usage: python {} ".format(script_name)) - sys.exit(1) - -if __name__ == "__main__": - if len(sys.argv) < 2 or not os.path.isdir(sys.argv[1]): - print_usage(sys.argv[0]) - - for arg_idx in range(2, len(sys.argv)): - if os.path.isdir(sys.argv[arg_idx]): - print_usage(sys.argv[0]) - - for bin_idx in range(2, len(sys.argv)): - so_dependencies = parse_ldd_output(sys.argv[bin_idx]) - copy_to_chroot_jail(so_dependencies, sys.argv) - - copy_misc_stuff(sys.argv[1]) diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt deleted file mode 100644 index 81f7327..0000000 --- a/server/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.5) -project(remramd_client VERSION 1.0 LANGUAGES CXX) - -set(THREADLIB "-lpthread") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${THREADLIB}") - -add_executable( remramd_server - ./src/remramd_server.cpp - ./src/remramd_server_main.cpp -) diff --git a/server/inc/PipeWrapper.hpp b/server/inc/PipeWrapper.hpp new file mode 100644 index 0000000..2b6da14 --- /dev/null +++ b/server/inc/PipeWrapper.hpp @@ -0,0 +1,81 @@ +#pragma once +#include +#include "../../shared/inc/remramd_exception.hpp" +#include +#include +#include +#include + +namespace remramd { + namespace internal { + class PipeWrapper { + public: + enum class Action { + READ, + WRITE + }; + + + PipeWrapper(); + // disallow copy constructor and assignment + PipeWrapper(const PipeWrapper&) = delete; + PipeWrapper& operator = (const PipeWrapper&) = delete; + // allow move constructor and operator = + PipeWrapper(PipeWrapper&&) noexcept = default; + PipeWrapper& operator = (PipeWrapper&&) noexcept = default; + ~PipeWrapper(); + + void close_pipe_end(const Action &io_action); // V + int get_pipe_fd(const Action &io_action) const; + + template + void write(const T data) const; + + template + T read() const; + private: + // 11 - default fd_ctx_bitmap + // 2 LSB bits are parent fd ctx + // 2 MSB bits are child fd ctx + // ex: 0110 - @child - read end closed, write end opened; @parent - read end opened, write end closed + std::bitset<2> fd_ctx_bitmap; + enum CtxBitPos : std::uint8_t { + WRITE_END, + READ_END + }; + int pipe_fds[2]; + const bool validate_requested_fd(const Action &io_action) const; + }; + + template + void PipeWrapper::write(const T data) const { + static_assert(std::is_enum::value, "Only enum and enum class data type are allowed to be written into the bidirectional pipe"); + + if (validate_requested_fd(Action::WRITE)) { + if (::write(pipe_fds[1], &data, sizeof(data)) != sizeof(data)) { + throw exception("Cannot write data to a pipe"); + } + } else { + throw exception("Pipe write file descriptor is closed for this process"); + } + + } + + template + T PipeWrapper::read() const { + static_assert(std::is_enum::value, "Only enum and enum class data types are allowed to be read from the bidirectional pipe"); + + if (validate_requested_fd(Action::READ)) { + T data {}; + + if (::read(pipe_fds[0], &data, sizeof(data)) != sizeof(data)) { + throw exception("Cannot read data from a pipe"); + } + + return data; + } + + throw exception("Pipe read file descriptor is closed for this process"); + } + } +} \ No newline at end of file diff --git a/server/inc/Server.hpp b/server/inc/Server.hpp new file mode 100644 index 0000000..dfdc1fe --- /dev/null +++ b/server/inc/Server.hpp @@ -0,0 +1,64 @@ +#pragma once +#include "../../shared/inc/Utils.hpp" +#include "../../shared/inc/Protocol.hpp" +#include "PipeWrapper.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace remramd { + class Server { + private: + using clients_map_t = std::map; + using conn_queue_t = std::queue; + std::mutex pend_conn_q_mut; + std::mutex clients_map_mut; + conn_queue_t conn_q; + clients_map_t c_map; + std::string jail_path; + std::atomic server_is_on, conn_recv_worker_is_on; + enum PromptChoice : unsigned { + SHOW_PEND_CONN = 1, + SHOW_CURR_CLIENTS = 2, + ACCEPT_PEND_CONN = 3, + DECLINE_PEND_CONN = 4, + DECLINE_ALL_CURR_CONN = 5, + DROP_SPECIFIC_CLIENT = 6, + DROP_ALL_CLIENTS = 7, + EXIT = 8 + }; + const unsigned backlog; + const std::uint16_t port; + void connection_receiver_worker(); + void erase_jail_dir(); + void print_current_connections(); + std::optional obtain_pending_connection(); + void show_pending_connection(); + void display_current_clients(); + void handle_pending_connection(const bool accept); + void decline_all_pending_connections(); + void drop_specific_client(); + void drop_all_clients(); + void enjail_new_client(internal::Protocol::ClientData &new_client, internal::PipeWrapper &pipe); + void add_new_client(internal::Protocol::ClientData &&pend_conn); + friend std::ostream& operator << (std::ostream &os, const internal::Protocol::ClientData &pend_conn); + friend std::ostream& operator << (std::ostream &os, clients_map_t &clients_map); + public: + Server(std::string chroot_jail_path, + const std::uint16_t server_port, + const unsigned queued_conn_num); + ~Server(); + Server(const Server&) = delete; + Server& operator = (const Server&) = delete; + Server(Server&&) noexcept = default; + Server& operator = (Server&&) noexcept = default; + void add_client_to_map(const std::string &ip_addr, const internal::Protocol::ClientData &&new_client) noexcept; + void run(); + }; +} \ No newline at end of file diff --git a/server/include/remramd_server.hpp b/server/include/remramd_server.hpp deleted file mode 100644 index 3641c70..0000000 --- a/server/include/remramd_server.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class Server { - private: - // this struct contains all required client data - struct ClientMetadata { - std::string ip_address; - unsigned short port; - pid_t client_pid; - int uid; - int gid; - std::string chroot_path; - std::vector exposed_binaries; - }; - - struct PendingClient { - std::string ip; - unsigned short port; - }; - - using pending_conn_queue = std::queue; - - struct ConnectionQueue { - pending_conn_queue client_queue; - std::mutex queue_mutex; - }; - // All connections are stored in map where key is an IP address and value is ClientMetadata struct - using clients_map_t = std::map; - using clients_map_iterator = clients_map_t::iterator; - // prompt returns pair of client map iterator and numerical response flag - using prompt_response = std::pair; - // prompt responses' numeric codes - enum { - RQ_PEND_CONN = 1, - RQ_INFO, - RQ_CONN_CLIENT, - RQ_DROP_PEND_CONN, - RQ_DROP_CLIENT, - RQ_EXIT - }; - - enum { - CHLD_SUCCESS, - CHLD_TERM, - }; - - enum { - CONN_DENY, - CONN_ACC - }; - - const uint16_t server_port; - const std::string py_interp_path; - const unsigned backlog_capacity; - std::string chroot_jail_builder_path; - void rq_list_pend_conn(ConnectionQueue &) const; - void add_pending_connection(ConnectionQueue &); - void rq_info_handler(clients_map_t &); - clients_map_iterator rq_conn_handler(clients_map_t &, ConnectionQueue &); - void rq_drop_handler(clients_map_t &); - void build_chroot_jail(ClientMetadata &, const std::string &, const std::string &); - template - void process_input(InputGenType &, const std::string &&); - prompt_response prompt(clients_map_t &, ConnectionQueue &); - void client_process(ClientMetadata &, int, const std::string, const std::string); - void server_process(clients_map_t &, clients_map_iterator, int, pid_t); - void send_conn_response(int, const PendingClient &); - public: - Server(uint16_t, const std::string, std::string); - ~Server() = default; - void run(); -}; - -// Generic input handling procedure -template -void Server::process_input(InputGenType &var_to_process, const std::string &&prompt_str) { - bool bad_input {}; - do { - std::cout << prompt_str; - if (!(std::cin >> var_to_process)) { - bad_input = true; - std::cerr << "Bad input, try again" << std::endl; - std::cin.clear(); - std::cin.ignore(std::numeric_limits::max(), '\n'); - sleep(1); - } else { - bad_input = false; - } - } while (bad_input); -} diff --git a/server/remramd_server b/server/remramd_server new file mode 100755 index 0000000..d0d084e Binary files /dev/null and b/server/remramd_server differ diff --git a/server/src/PipeWrapper.cpp b/server/src/PipeWrapper.cpp new file mode 100644 index 0000000..e948825 --- /dev/null +++ b/server/src/PipeWrapper.cpp @@ -0,0 +1,46 @@ +#include "../inc/PipeWrapper.hpp" +#include "../../shared/inc/remramd_exception.hpp" +#include +#include + +namespace remramd { + namespace internal { + PipeWrapper::PipeWrapper() : fd_ctx_bitmap(11) { + if (pipe(pipe_fds)) { + throw exception("Cannot create parent_to_child pipe"); + } + } + + PipeWrapper::~PipeWrapper() { + close(pipe_fds[0]); + close(pipe_fds[1]); + } + + int PipeWrapper::get_pipe_fd(const Action &io_action) const { + if (validate_requested_fd(io_action)) { + return io_action == Action::READ ? pipe_fds[0] : pipe_fds[1]; + } + + throw exception("Invalid pipe file descriptor"); + } + + void PipeWrapper::close_pipe_end(const Action &io_action) { // V + if (io_action == Action::READ) { + close(pipe_fds[0]); + fd_ctx_bitmap &= ~(1 << CtxBitPos::READ_END); + } else { + close(pipe_fds[1]); + fd_ctx_bitmap &= ~(1 << CtxBitPos::WRITE_END); + } + } + + const bool PipeWrapper::validate_requested_fd(const Action &io_action) const { + if (fd_ctx_bitmap == 11) { + throw exception("Close the unnecessary pipe side before usage"); + } + + return io_action == Action::READ ? static_cast((fd_ctx_bitmap >> CtxBitPos::READ_END).to_ulong() & 0x1) : + static_cast((fd_ctx_bitmap >> CtxBitPos::WRITE_END).to_ulong() & 0x1); + } + } +} \ No newline at end of file diff --git a/server/src/Server.cpp b/server/src/Server.cpp new file mode 100644 index 0000000..5244c31 --- /dev/null +++ b/server/src/Server.cpp @@ -0,0 +1,403 @@ +#include "../inc/Server.hpp" +#include "../../shared/inc/remramd_exception.hpp" +#include "../../shared/inc/Connection.hpp" +#include "../inc/PipeWrapper.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace remramd { + // Server ctor builds a chroot jail for further connections + Server::Server(std::string chroot_jail_path, + const std::uint16_t server_port, + const unsigned queued_conn_num) + : jail_path(std::move(chroot_jail_path)), + backlog(queued_conn_num), + port(server_port), + server_is_on(true), + conn_recv_worker_is_on(false) { + + if (getuid()) { + throw exception("Server needs to be run as root"); + } + + if (!jail_path.size()) { + throw exception("Chroot jail path is empty"); + } + + std::filesystem::create_directory(jail_path); + + if (!std::filesystem::exists(jail_path)) { + throw exception("Cannot create chroot jail directory"); + } + + } + + Server::~Server() { + drop_all_clients(); + // but before that drop all clients + erase_jail_dir(); + } + + std::ostream& operator << (std::ostream &os, const internal::Protocol::ClientData &pend_conn) { + os << "IP: " << pend_conn.ip << '\n'; + return os; + } + + std::ostream& operator << (std::ostream &os, Server::clients_map_t &clients_map) { + unsigned long clients_cnt {}; + + auto c_map_iter { clients_map.cbegin() }; + + while (c_map_iter != clients_map.cend()) { + auto tmp_map_iter { c_map_iter }; + if (!kill(c_map_iter->second.pid, 0)) { + os << ++clients_cnt << ") IP: " << c_map_iter->first << '\n' + << "Reverse shell port: " << c_map_iter->second.reverse_shell_port << '\n' + << "PID: " << c_map_iter->second.pid << '\n' + << "UID: " << c_map_iter->second.uid << '\n' + << "GID: " << c_map_iter->second.gid << '\n' + << "Exposed binaries:\n"; + unsigned long bin_cnt {}; + for (const auto &exposed_binary : c_map_iter->second.exposed_binaries) { + os << ++bin_cnt << ") " << exposed_binary << '\n'; + } + os << '\n'; + c_map_iter++; + } else { + kill(c_map_iter->second.pid, SIGKILL); + std::filesystem::remove_all(c_map_iter->second.fakeroot_path); + c_map_iter++; + clients_map.erase(tmp_map_iter); + } + } + + return os; + } + + void Server::erase_jail_dir() { + if (std::filesystem::exists(jail_path)) { + std::filesystem::remove_all(jail_path); + } + } + + void Server::connection_receiver_worker() { + auto init_server = [this]() -> int { + int server_fd { socket(AF_INET, SOCK_STREAM, 0) }; + + if (server_fd == -1) { + throw exception("Cannot open socket for connection receiver worker"); + } + + struct sockaddr_in serv_addr {}; + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); + serv_addr.sin_port = htons(port); + + if (bind(server_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) { + close(server_fd); + throw exception("Cannot bind a socket to port #" + std::to_string(port)); + } + + if (listen(server_fd, backlog) != 0) { + close(server_fd); + throw exception("Cannot listen on a binded socket #" + std::to_string(server_fd) + " backlog: " + std::to_string(backlog)); + } + + return server_fd; + }; + + int server_fd {}; + + try { + + server_fd = init_server(); + + } catch (const exception &e) { + std::cerr << e.what() << std::endl; + exit(1); + } + + while (conn_recv_worker_is_on.load()) { + // listen for a new connections + auto curr_pend_conn { internal::Protocol::wait_new_connection_request(server_fd, 10) }; + + if (curr_pend_conn.has_value()) { + auto reverse_shell_port { internal::Protocol::receive_data(*curr_pend_conn) }; + + if (reverse_shell_port.has_value()) { + curr_pend_conn->reverse_shell_port = ntohs(*reverse_shell_port); + std::lock_guard lock(pend_conn_q_mut); + conn_q.push(std::move(*curr_pend_conn)); + } + + } + + } + + close(server_fd); + } + + void Server::show_pending_connection() { + pend_conn_q_mut.lock(); + if (conn_q.size()) { + std::cout << conn_q.front(); + } else { + pend_conn_q_mut.unlock(); + std::cerr << "No pending connections at this moment\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + return; + } + pend_conn_q_mut.unlock(); + } + + std::optional Server::obtain_pending_connection() { + std::lock_guard lock(pend_conn_q_mut); + if (conn_q.size()) { + auto pend_conn { std::move(conn_q.front()) }; + conn_q.pop(); + return pend_conn; + } + return {}; + } + + void Server::display_current_clients() { + clients_map_mut.lock(); + unsigned long client_cnt {}; + if (c_map.size()) { + std::cout << c_map; + } else { + clients_map_mut.unlock(); + std::cerr << "No connected clients at this moment\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + return; // prevents possible double-unlock + } + clients_map_mut.unlock(); + } + + void Server::enjail_new_client(internal::Protocol::ClientData &new_client, + internal::PipeWrapper &pipe) { + + try { + + Connection conn_obj(new_client, pipe, *this); + + } catch (const exception &e) { + std::cerr << e.what() << std::endl; + pipe.write(internal::Protocol::PipeResponse::CHILD_FAILED); + exit(EXIT_FAILURE); + } + } + + void Server::add_new_client(internal::Protocol::ClientData &&new_client) { + internal::PipeWrapper pipe {}; + + std::cout << "New client | IP: " << new_client.ip << std::endl; + internal::Utils::process_input(new_client.uid, "UID: "); + internal::Utils::process_input(new_client.gid, "GID: "); + std::string exposed_binary {}; + std::cout << "Provide a full path to all binaries that need to be exposed for current client (enter 'stop' to stop):\n"; + + for (;;) { + std::string exposed_binary {}; + internal::Utils::process_input(exposed_binary, "Enter absolute path: "); + if (exposed_binary != "stop") { + new_client.exposed_binaries.push_back(std::move(exposed_binary)); + } else { + break; + } + } + + new_client.fakeroot_path = internal::Utils::populate_client_jail(new_client, jail_path); + + pid_t child_pid { fork() }; + + switch (child_pid) { + case -1: + throw exception("Fork failed"); + case 0: // child process + // close parent -> child write + pipe.close_pipe_end(internal::PipeWrapper::Action::READ); + // close child -> parent read + enjail_new_client(new_client, pipe); + break; + default: { // parent process + std::signal(SIGCHLD,SIG_IGN); + pipe.close_pipe_end(internal::PipeWrapper::Action::WRITE); + // wait for a response + const auto child_response { pipe.read() }; + // sleep for 1.5 seconds to be sure that execve actually has been invoked + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + // check if process is aliove (will fail if execve in child process failed) + if (!kill(child_pid, 0) && child_response == internal::Protocol::PipeResponse::CHILD_SUCCESS) { + new_client.pid = child_pid; + std::lock_guard lock(clients_map_mut); + c_map[new_client.ip] = std::move(new_client); + } else { + std::cerr << "Cannot enjail a client\n"; + std::filesystem::remove_all(new_client.fakeroot_path); + } + close(new_client.pend_conn_sock_fd); + } + break; + } + } + + void Server::handle_pending_connection(const bool accept) { + auto pend_conn { obtain_pending_connection() }; + if (pend_conn.has_value()) { + // if the pending connection should be accepted + if (accept) { + clients_map_mut.lock(); + if (c_map.find(pend_conn->ip) != c_map.end()) { + clients_map_mut.unlock(); + std::cerr << "Client with IP " << pend_conn->ip << " is already connected\n"; + std::this_thread::sleep_for(std::chrono::seconds(1)); + return; + } + clients_map_mut.unlock(); + internal::Protocol::send_data(*pend_conn, internal::Protocol::ServerResponse::YEP); + add_new_client(std::move(*pend_conn)); + } else { // if the pending connection should be declined + internal::Protocol::send_data(*pend_conn, internal::Protocol::ServerResponse::NOPE); + close(pend_conn->pend_conn_sock_fd); + } + } else { + std::cerr << "No pending connection at this moment\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + } + } + + void Server::decline_all_pending_connections() { + std::lock_guard lock(pend_conn_q_mut); + while (!conn_q.empty()) { + auto curr_pend_conn { std::move(conn_q.front()) }; + close(curr_pend_conn.pend_conn_sock_fd); + conn_q.pop(); + } + } + + void Server::drop_specific_client() { + display_current_clients(); + clients_map_mut.lock(); + if (c_map.size()) { + clients_map_mut.unlock(); + std::string ip_addr {}; + internal::Utils::process_input(ip_addr, "Enter the client's IP address: "); + clients_map_mut.lock(); + clients_map_t::const_iterator found_client_iter { c_map.find(ip_addr) }; + if (found_client_iter != c_map.cend()) { + close(found_client_iter->second.reverse_shell_port); + kill(found_client_iter->second.pid, SIGKILL); + std::filesystem::remove_all(jail_path + '/' + found_client_iter->second.ip); + c_map.erase(found_client_iter); + } else { + clients_map_mut.unlock(); + std::cerr << "No client with such IP address\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + return; // prevents possible mutex double unlock + } + } + clients_map_mut.unlock(); + } + + void Server::drop_all_clients() { + clients_map_mut.lock(); + if (c_map.size()) { + auto map_iter { c_map.cbegin() }; + while (map_iter != c_map.cend()) { + // close the client's socket + close(map_iter->second.reverse_shell_port); + kill(map_iter->second.pid, SIGKILL); + std::filesystem::remove_all(jail_path + '/' + map_iter->second.ip); + // save the current iterator + auto tmp_iter { map_iter }; + // move the main iterator to the next map node + map_iter++; + // erase the irrelevant node via the previously saved iterator + c_map.erase(tmp_iter); + } + } else { + clients_map_mut.unlock(); + std::cerr << "No clients at this moments\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + return; + } + clients_map_mut.unlock(); + } + + void Server::run() { + // disable stdout buffering + std::setvbuf(stdout, NULL, _IONBF, 0); + + conn_recv_worker_is_on.store(true); + + std::thread conn_work_t(&Server::connection_receiver_worker, this); + conn_work_t.detach(); + + std::thread dir_sweeper_thread([this] { + for (;;) { + { + std::lock_guard lock(clients_map_mut); + for (const auto &client : c_map) { + if (kill(client.second.pid, 0)) { + close(client.second.reverse_shell_port); + std::filesystem::remove_all(client.second.fakeroot_path); + } + } + } + std::this_thread::sleep_for(std::chrono::seconds(30)); + } + }); + + dir_sweeper_thread.detach(); + + while (server_is_on.load() && conn_recv_worker_is_on.load()) { + unsigned choice {}; + internal::Utils::process_input(choice, internal::Utils::menu_msg); + + switch (choice) { + case 1://PromptChoice::SHOW_PEND_CONN: + show_pending_connection(); // V + break; + case 2://PromptChoice::SHOW_CURR_CLIENTS: + display_current_clients(); // V + break; + case 3://PromptChoice::ACCEPT_PEND_CONN: + handle_pending_connection(true); // VX + break; + case 4://PromptChoice::DECLINE_PEND_CONN: + handle_pending_connection(false); + break; + case 5://PromptChoice::DECLINE_ALL_CURR_CONN: + decline_all_pending_connections(); + break; + case 6://PromptChoice::DROP_SPECIFIC_CLIENT: + drop_specific_client(); + break; + case 7://PromptChoice::DROP_ALL_CLIENTS: + drop_all_clients(); + break; + case 8://PromptChoice::EXIT: + server_is_on.store(false); + conn_recv_worker_is_on.store(false); + break; + default: + std::cout << "Invalid option\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + break; + } + + } + + } +} \ No newline at end of file diff --git a/server/src/remramd_server.cpp b/server/src/remramd_server.cpp deleted file mode 100644 index 54e90db..0000000 --- a/server/src/remramd_server.cpp +++ /dev/null @@ -1,459 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../include/remramd_server.hpp" - -Server::Server(uint16_t port, - const std::string py_interp_path, - std::string chroot_jail_builder_path) - : server_port(port), - backlog_capacity(10), - py_interp_path(py_interp_path), - chroot_jail_builder_path(chroot_jail_builder_path) {} - -// this function will be called within a thread and will accept new clients -void Server::add_pending_connection(ConnectionQueue &conn_queue) { - int sock_server_fd {}; - struct sockaddr_in server_addr {}; - sock_server_fd = socket(AF_INET, SOCK_STREAM, 0); - if (sock_server_fd == -1) { - std::cerr << "Listening thread socket fd cannot be created" << std::endl; - exit(EXIT_FAILURE); - } - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(server_port); - server_addr.sin_addr.s_addr = INADDR_ANY; - if (bind(sock_server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr))) { - fprintf(stderr, "Port %hu cannot be bound\n", server_port); - exit(EXIT_FAILURE); - } - if (listen(sock_server_fd, backlog_capacity)) { - std::cerr << "Listening thread cannot listen" << std::endl; - exit(EXIT_FAILURE); - } - for (;;) { - int conn_fd {}; - struct sockaddr_in conn_addr {}; - PendingClient new_conn {}; - socklen_t sin_size { sizeof(struct sockaddr_in) }; - uint16_t client_port {}; - conn_fd = accept(sock_server_fd, (struct sockaddr *)&conn_addr, &sin_size); - read(conn_fd, &client_port, sizeof(uint16_t)); - new_conn.ip = inet_ntoa(conn_addr.sin_addr); - new_conn.port = ntohs(client_port); - conn_queue.queue_mutex.lock(); - fprintf(stdout, "New client connection request from [ %s | %hu ]\n", new_conn.ip.data(), new_conn.port); - conn_queue.client_queue.push(new_conn); - close(conn_fd); - conn_queue.queue_mutex.unlock(); - } -} - -// Drops a given client -// Arguments: -// 1) reference to map of clients (key: client IP, value: ClientMetadata struct) -void Server::rq_drop_handler(clients_map_t &clients_map) { - std::string ip {}; - process_input(ip, "Enter the IP address of the client to drop: "); - auto client_iter { clients_map.find(ip) }; - if (client_iter != clients_map.cend()) { - kill(client_iter->second.client_pid, SIGKILL); - fprintf(stderr, "[IP: %s | port: %hu] has been successfully dropped\n", client_iter->first.data(), - client_iter->second.port); - system(("rm -rf " + client_iter->second.chroot_path).data()); - clients_map.erase(client_iter); - } else { - std::cerr << "IP address is invalid or the client has been already disconnected\n"; - } - sleep(2); -} - -// This function sends an approval or denial code message to the current pending connection -void Server::send_conn_response(int conn_resp, const PendingClient &pending_conn) { - int resp_sock_fd = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in client_info {}; - client_info.sin_port = htons(pending_conn.port); - client_info.sin_family = AF_INET; - inet_pton(AF_INET, pending_conn.ip.data(), &client_info.sin_addr); - connect(resp_sock_fd, (struct sockaddr *)&client_info, sizeof(client_info)); - uint16_t response = static_cast(conn_resp); - response = htons(response); - write(resp_sock_fd, &response, sizeof(uint16_t)); - close(resp_sock_fd); -} - -// Connection establishment -// Arguments: -// 1) reference to map of clients (key: client IP, value: ClientMetadata struct) -// 2) Queue of pending connections -// Return value: iterator to clients map -Server::clients_map_iterator Server::rq_conn_handler(clients_map_t &clients_map, ConnectionQueue &conn_queue) { - ClientMetadata new_client {}; - conn_queue.queue_mutex.lock(); - auto &curr_conn { conn_queue.client_queue.front() }; - fprintf(stdout, "The client to be connected -> [ %s | %hu ]\n", curr_conn.ip.data(), curr_conn.port); - new_client.ip_address = curr_conn.ip; - new_client.port = curr_conn.port; - conn_queue.client_queue.pop(); - conn_queue.queue_mutex.unlock(); - send_conn_response(CONN_ACC, {new_client.ip_address, new_client.port}); - process_input(new_client.chroot_path, "Client's chroot jail path (DON'T use home or root directory for this, RAM disk mount point is strongly recommended): "); - std::string &curr_chroot_path = new_client.chroot_path; - char last_chroot_path_char { curr_chroot_path.at(curr_chroot_path.size() - 1) }; - if (last_chroot_path_char != '/') { - curr_chroot_path.resize(curr_chroot_path.size() + 1); - curr_chroot_path.at(curr_chroot_path.size() - 1) = '/'; - } - process_input(new_client.uid, "Client's UID (please, NOT 0): "); - process_input(new_client.gid, "Client's GID: "); - std::cout << "Desired exposed binaries to the client (press Enter twice to stop or once to keep the default set of binaries):\n"; - // default exposed binaries for the client for minimal interactive remote shell - new_client.exposed_binaries.push_back("/bin/bash"); - new_client.exposed_binaries.push_back("/bin/rm"); - new_client.exposed_binaries.push_back("/bin/touch"); - new_client.exposed_binaries.push_back("/bin/cat"); - new_client.exposed_binaries.push_back("/bin/ls"); - std::cout << "Default exposed binaries: "; - for (const auto &bin : new_client.exposed_binaries) { - std::cout << bin << ' '; - } - std::cout << std::endl; - for (;;) { - std::string bin_path {}; - std::cout << "Provide the ABSOLUTE path to the binary to be exposed for the client: "; - // check if there is a redundant newline in std::cin - if (std::cin.peek() == '\n') { - std::cin.ignore(std::numeric_limits::max(), '\n'); - } - std::getline(std::cin, bin_path); - if (bin_path[0] == '\0') { - break; - } - if (bin_path.at(bin_path.size() - 1) == '/') { - bin_path.resize(bin_path.size() - 1); - } - new_client.exposed_binaries.push_back(bin_path); - } - auto new_client_iter_bool_pair { clients_map.insert({ new_client.ip_address, new_client }) }; - // this iterator will be used in main function in order to add client's pid to the ClientMetadata struct - return new_client_iter_bool_pair.first; -} - -// Prints out data about all connected clients -// arguments: -// 1) reference to map of clients (key: client IP address, value: ClientMetadata struct) -void Server::rq_info_handler(clients_map_t &clients_map) { - if (clients_map.empty()) { - std::cout << "No existing connections\n"; - } else { - auto map_iter { clients_map.cbegin() }; - while (map_iter != clients_map.cend()) { - if (kill(map_iter->second.client_pid, 0)) { - system(("rm -rf " + map_iter->second.chroot_path).data()); - // post-increment allows to avoid invalid iterator usage in remaining loop - clients_map.erase(map_iter++); - } else { - map_iter++; - } - } - if (map_iter == clients_map.cbegin()) { - std::cout << "No existing connections\n"; - } else { - unsigned client_cnt {}; - // displays the information about all current clients - for (auto map_iter { clients_map.cbegin() }; map_iter != clients_map.cend(); ++map_iter) { - std::cout << "Client #" << ++client_cnt << '\n'; - std::cout << "IP: " << map_iter->second.ip_address << '\n'; - std::cout << "Port: " << map_iter->second.port << '\n'; - std::cout << "Chroot path: " << map_iter->second.chroot_path << '\n'; - std::cout << "UID: " << map_iter->second.uid << '\n'; - std::cout << "GID: " << map_iter->second.gid << '\n'; - std::cout << "PID: " << map_iter->second.client_pid << '\n'; - std::cout << "Exposed binaries: "; - for (const auto &exp_bins : map_iter->second.exposed_binaries) { - std::cout << exp_bins << ' '; - } - std::cout << "\n\n"; - } - } - } - sleep(2); -} - -// show pending connection -// Argument: pending connections queue -void Server::rq_list_pend_conn(ConnectionQueue & conn_queue) const { - conn_queue.queue_mutex.lock(); - if (conn_queue.client_queue.empty()) { - std::cout << "No pending connections at this moment\n"; - } else { - auto &conn { conn_queue.client_queue.front() }; - fprintf(stdout, "IP: %s | port %hu\n", conn.ip.data(), conn.port); - } - conn_queue.queue_mutex.unlock(); - sleep(2); -} - -// Asks the server to choose an appropriate action to perform -// arguments: -// 1) reference to map of clients (key: client IP, value: ClientMetadata struct) -// return value: pair of map iterator and numeric prompt response (int via enum) -// 2) pending connections queue -Server::prompt_response Server::prompt(clients_map_t &clients_map, ConnectionQueue &conn_queue) { - prompt_response curr_response {}; - // by default, the response returns iterator to the end of the map - // it will return iterator with actual data only via RQ_CONN_CLIENT - curr_response.first = clients_map.end(); - std::string prompt_str { "Choose an option (1-6):\n" }; - prompt_str += "1) Show current pending connection\n"; - prompt_str += "2) List existing connections\n"; - prompt_str += "3) Connect new client\n"; - prompt_str += "4) Drop pending connection\n"; - prompt_str += "5) Drop existing connction\n"; - prompt_str += "6) Exit\n\n> "; - int option {}; - do { - process_input(option, std::move(prompt_str)); - switch (option) { - case RQ_PEND_CONN: - conn_queue.queue_mutex.lock(); - if (!conn_queue.client_queue.empty()) { - conn_queue.queue_mutex.unlock(); - rq_list_pend_conn(conn_queue); - curr_response.second = RQ_PEND_CONN; - } else { - conn_queue.queue_mutex.unlock(); - std::cerr << "No pending connections at this moment" << std::endl; - sleep(2); - } - break; - case RQ_INFO: - rq_info_handler(clients_map); - curr_response.second = RQ_INFO; - break; - case RQ_CONN_CLIENT: - conn_queue.queue_mutex.lock(); - if (conn_queue.client_queue.empty()) { - conn_queue.queue_mutex.unlock(); - std::cerr << "No clients to connect at this moment\n"; - sleep(2); - } else { - conn_queue.queue_mutex.unlock(); - curr_response.first = rq_conn_handler(clients_map, conn_queue); - curr_response.second = RQ_CONN_CLIENT; - } - break; - case RQ_DROP_PEND_CONN: { - conn_queue.queue_mutex.lock(); - if (conn_queue.client_queue.empty()) { - std::cerr << "No pending connections to drop\n"; - } else { - auto curr_pending_conn { conn_queue.client_queue.front() }; - conn_queue.queue_mutex.unlock(); - send_conn_response(CONN_DENY, curr_pending_conn); - fprintf(stdout, "[ %s | %hu ] connection has been denied\n", curr_pending_conn.ip.data(), - curr_pending_conn.port); - conn_queue.queue_mutex.lock(); - conn_queue.client_queue.pop(); - curr_response.second = RQ_DROP_PEND_CONN; - } - conn_queue.queue_mutex.unlock(); - sleep(2); - } - break; - case RQ_DROP_CLIENT: - rq_drop_handler(clients_map); - curr_response.second = RQ_DROP_CLIENT; - break; - case RQ_EXIT: - curr_response.second = RQ_EXIT; - break; - default: - std::cerr << "Invalid option\n"; - sleep(2); - break; - } - } while (option < RQ_INFO && option > RQ_EXIT); - return curr_response; -} - -void Server::build_chroot_jail(ClientMetadata &curr_connection_data, - const std::string &python_interp, - const std::string &script_path) { - std::string client_jail_dir { curr_connection_data.chroot_path + - curr_connection_data.ip_address + ':' + - std::to_string(curr_connection_data.port)}; - curr_connection_data.chroot_path = client_jail_dir; - // make sure the chroot jail builder script is executable - system(("chmod +x " + script_path).data()); - std::string create_cleint_jail_root_subdir { "mkdir -p " + client_jail_dir }; - system(create_cleint_jail_root_subdir.data()); - std::string bin_copy_cmd { python_interp + ' ' + script_path + ' ' }; - bin_copy_cmd += (client_jail_dir + ' '); - for (const auto &bin : curr_connection_data.exposed_binaries) { - bin_copy_cmd += (bin + ' '); - } - std::cout << "Executing: " << bin_copy_cmd << std::endl; - system(bin_copy_cmd.data()); -} - -// This code runs in client process after each fork syscall -void Server::client_process(ClientMetadata &curr_connection_data, - int child_write_pipe_fd, - const std::string python_interp, - const std::string script_path) { - int curr_child_exec_status { CHLD_TERM }; - build_chroot_jail(curr_connection_data, python_interp, script_path); - fprintf(stdout, "Changing the chroot jail owner to %d:%d\n", curr_connection_data.uid, - curr_connection_data.gid); - const std::string change_own { "chown -R " + - std::to_string(curr_connection_data.uid) + - ":" + std::to_string(curr_connection_data.gid) + - " " + curr_connection_data.chroot_path }; - system(change_own.data()); - // cd into root jail directory - if (chdir(curr_connection_data.chroot_path.data())) { - std::cerr << "Cannot cd to chroot jail directory, check paths\n"; - write(child_write_pipe_fd, &curr_child_exec_status, sizeof(int)); - exit(EXIT_FAILURE); - } - // chroot to jail - if (chroot(curr_connection_data.chroot_path.data())) { - std::cerr << "Can't chroot, check process privileges" << std::endl; - write(child_write_pipe_fd, &curr_child_exec_status, sizeof(int)); - exit(EXIT_FAILURE); - } - std::cout << "The client process is jailed" << std::endl; - // set PATH environment variable to /bin - // it allows the client to run executables without writing its' full path - char env[] = "PATH=/bin"; - putenv(env); - // drop privileges from root to regular user - fprintf(stdout, "Dropping privileges from %d:%d to %d:%d\n", getuid(), getgid(), - curr_connection_data.uid, - curr_connection_data.gid); - if (setgid(curr_connection_data.gid) || setuid(curr_connection_data.uid)) { - std::cerr << "Cannot drop root privileges, unknown fatal error, aborting the child process\n"; - exit(EXIT_FAILURE); - } - std::cout << "The privileges are successfully dropped\n"; - fprintf(stdout, "Opening a reverse shell for %s:%hu\n", curr_connection_data.ip_address.data(), - curr_connection_data.port); - // pop the chrooted non-privileged reverse shell for the client - struct sockaddr_in sockaddr {}; - sockaddr.sin_family = AF_INET; - sockaddr.sin_addr.s_addr = inet_addr(curr_connection_data.ip_address.data()); - sockaddr.sin_port = htons(curr_connection_data.port); - int sock_fd = socket(AF_INET, SOCK_STREAM, 0); - if (sock_fd < 0) { - std::cerr << "Cannot open socket file descriptor" << std::endl; - write(child_write_pipe_fd, &curr_child_exec_status, sizeof(int)); - exit(EXIT_FAILURE); - } - int conn_status = connect(sock_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); - if (conn_status < 0) { - fprintf(stderr, "[IP: %s | port %hu] - the port is closed\n", curr_connection_data.ip_address.data(), - curr_connection_data.port); - write(child_write_pipe_fd, &curr_child_exec_status, sizeof(int)); - exit(EXIT_FAILURE); - } - std::cout << "The client is successfully connected, redirecting the I/O streams to the socket...\n"; - dup2(sock_fd, fileno(stdin)); - dup2(sock_fd, fileno(stdout)); - dup2(sock_fd, fileno(stderr)); - curr_child_exec_status = CHLD_SUCCESS; - write(child_write_pipe_fd, &curr_child_exec_status, sizeof(int)); - // give /bin/bash shell for the client - // -i argument stands for "interactive" - execl("/bin/bash", "/bin/bash", "-i", nullptr); - exit(EXIT_FAILURE); -} - -// This code runs in parent process after each fork syscall -// Waits for notifications from the child via pipe and print an appropriate message -void Server::server_process(clients_map_t &clients_map, - clients_map_iterator curr_conn_iter, - int parent_read_pipe_fd, - pid_t child) { - ClientMetadata &curr_connection_data { curr_conn_iter->second }; - curr_connection_data.client_pid = child; - std::cout << "Connecting a client, please wait...\n"; - int child_ret_status {}; - read(parent_read_pipe_fd, &child_ret_status, sizeof(int)); - // let the child execl to succeed/fail - sleep(1); - // if the child process returned success code, but it turns out that the process is dead -> execl failure - if ((child_ret_status == CHLD_SUCCESS) && kill(child, 0)) { - fprintf(stderr, "Client (PID %d) execl failure\n", child); - system(("rm -rf " + curr_connection_data.chroot_path).data()); - clients_map.erase(curr_conn_iter); - } else if (child_ret_status == CHLD_TERM) { // if the child return 1 -> pre-execl failure - fprintf(stderr, "The client %s:%hu has not been connected and terminated prior to execl\n", - curr_connection_data.ip_address.data(), - curr_connection_data.port); - system(("rm -rf " + curr_connection_data.chroot_path).data()); - clients_map.erase(curr_conn_iter); - } else { - fprintf(stderr, "The client %s:%hu has been successfully connected to %s chroot jail\n", - curr_connection_data.ip_address.data(), - curr_connection_data.port, - curr_connection_data.chroot_path.data()); - } - sleep(2); -} - -void Server::run() { - clients_map_t clients_map {}; - prompt_response curr_response {}; - // ignore the child signals, it will prevent zombies - // in this implementation, the parent process doesn't really need to know - // about child exit status, it simply confirms execl success or failure - // so wait/waitpid syscalls are not needed in this implementation - signal(SIGCHLD, SIG_IGN); - ConnectionQueue conn_queue {}; - std::thread listening_thread(&Server::add_pending_connection, this, std::ref(conn_queue)); - // client acceptor thread will live by it's own - listening_thread.detach(); - while ((curr_response = prompt(clients_map, conn_queue)).second != RQ_EXIT) { - // if there is no new connection, go back to prompt - if (curr_response.second != RQ_CONN_CLIENT) continue; - ClientMetadata &curr_connection_data { curr_response.first->second }; - int execl_status_pipe[2]; - // open the new pipe for each fork cycle in order to avoid pipe blocks - // previous pipes are destroyed, so no leaks - if (pipe(execl_status_pipe)) { - std::cerr << "Pipe creation failure\n" << std::endl; - continue; - } - // instantiate the client (child) process - pid_t child = fork(); - switch (child) { - case -1: - std::cerr << "fork failure\n" << std::endl; - break; - case 0: // child (client) process - close(execl_status_pipe[0]); // child doesn't read from the pipe - client_process(curr_connection_data, execl_status_pipe[1], py_interp_path, chroot_jail_builder_path); - break; - default: // parent (server) process - close(execl_status_pipe[1]); // parent doesn't write to the pipe - curr_connection_data.chroot_path += ( - curr_connection_data.ip_address + ':' + - std::to_string(curr_connection_data.port) - ); - server_process(clients_map, curr_response.first, execl_status_pipe[0], child); - break; - } - } - // be sure that all child processes are dead - for (auto client_map_iter {clients_map.cbegin()}; client_map_iter != clients_map.cend(); ++client_map_iter) { - kill(client_map_iter->second.client_pid, SIGKILL); - system(("rm -rf " + client_map_iter->second.chroot_path).data()); - } -} diff --git a/server/src/remramd_server_main.cpp b/server/src/remramd_server_main.cpp deleted file mode 100644 index 675ce04..0000000 --- a/server/src/remramd_server_main.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "../include/remramd_server.hpp" -#include -#include - -int main(int argc, char **argv) { - if (getuid()) { - std::cerr << "Run it as root\n"; - exit(EXIT_FAILURE); - } - if (argc != 4) { - fprintf(stderr, "Usage: sudo %s \n", argv[0]); - exit(EXIT_FAILURE); - } - uint16_t port { static_cast(std::stoi(argv[1])) }; - std::unique_ptr server { std::make_unique(port, argv[2], argv[3]) }; - server->run(); - return 0; -} diff --git a/server/src/server_main.cpp b/server/src/server_main.cpp new file mode 100644 index 0000000..e25d725 --- /dev/null +++ b/server/src/server_main.cpp @@ -0,0 +1,17 @@ +#include "../inc/Server.hpp" +#include "../../shared/inc/remramd_exception.hpp" +#include + +int main(int argc, char **argv) { + if (argc != 4) { + std::cerr << "Usage: " << argv[0] << " \n"; + exit(1); + } + try { + remramd::Server server_obj(argv[1], std::stoi(argv[2]), std::stoi(argv[3])); + server_obj.run(); + } catch (const remramd::exception &e) { + std::cerr << e.what() << std::endl; + } + return 0; +} diff --git a/shared/inc/Connection.hpp b/shared/inc/Connection.hpp new file mode 100644 index 0000000..579446a --- /dev/null +++ b/shared/inc/Connection.hpp @@ -0,0 +1,76 @@ +#pragma once +#include "Protocol.hpp" +#include "../../server/inc/Server.hpp" +#include "../../client/inc/Client.hpp" +#include "../../server/inc/PipeWrapper.hpp" +#include "remramd_exception.hpp" +#include +#include +#include +#include + +namespace remramd { + class Connection { + private: + int sock_fd; + bool server_side; + void connect(const std::string &server_ip, const std::uint16_t server_port, internal::PipeWrapper &pipe); + public: + //Connection(const std::string &target_ip, const std::uint16_t target_port); + Connection(const Connection &) = delete; + + // server-side reverse shell connection (!!!) + Connection(const internal::Protocol::ClientData &new_client, internal::PipeWrapper &pipe, Server &server_instance); + // client-side regular classic connection + + template + static void send_data(int fd, const T &data); + + template + static T receive_data(int fd, int timeout = -1); + + int get_sock_fd() const noexcept; + //std::optional> get_connected_client() noexcept; + ~Connection() noexcept; + }; + + template + void Connection::send_data(int fd, const T &data) { + static_assert(std::is_enum::value, "Only enum and enum class data types are allowed for writing to a socket"); + + if (write(fd, &data, sizeof(data)) != sizeof(data)) { + throw exception("Cannot send data through the socket"); + } + } + + template + T Connection::receive_data(int fd, int timeout) { + static_assert(std::is_enum::value, "Only enum and enum class data types are allowed for reading from a socket"); + + internal::Protocol::validate_timeout(timeout); + + struct pollfd pfds[1] {}; + pfds[0].fd = fd; + pfds[0].events = POLLIN; + + int events_num { poll(pfds, 1, timeout) }; + + if (!events_num) { + throw exception("poll timeout on read from a socket"); + } + + int pollin_happened { pfds[0].revents & POLLIN }; + + if (pollin_happened) { + T data {}; + + if (read(fd, &data, sizeof(data)) != sizeof(data)) { + throw exception("Failed to read the requested amount of data from a socket"); + } + + return data; + } + + throw exception("Unknown read failure"); + } +} \ No newline at end of file diff --git a/shared/inc/Protocol.hpp b/shared/inc/Protocol.hpp new file mode 100644 index 0000000..f263928 --- /dev/null +++ b/shared/inc/Protocol.hpp @@ -0,0 +1,112 @@ +#pragma once +#include "../../shared/inc/remramd_exception.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace remramd { + namespace internal { + class Protocol { + private: + static void validate_timeout(int &timeout); + static const std::pair init_reverse_shell_server(int timeout); + public: + // client request code == reverse shell port + using ClientRequest = std::uint16_t; + + static constexpr int timeout_limit { 30000 }; + + struct ClientData { + std::string ip; + std::string fakeroot_path; + std::vector exposed_binaries; + pid_t pid; + uid_t uid; + gid_t gid; + std::uint16_t reverse_shell_port; + std::uint16_t pend_conn_sock_fd; + ClientData(); + }; + + enum ClientResponse : std::uint8_t { + ACK = 2, + C_TIMEOUT + }; + + enum ServerResponse : std::uint8_t { + NOPE, + YEP, + S_TIMEOUT + }; + + enum PipeResponse : std::uint8_t { + CHILD_FAILED, + CHILD_SUCCESS + }; + + template + static void send_data(const Protocol::ClientData &pend_conn, const T &data); + + template + static std::optional receive_data(const ClientData &pend_conn, int timeout = -1) noexcept; + + // server-side methods + static std::optional accept_new_client(int fd, int timeout); + static std::optional wait_new_connection_request(int server_sock_fd, int client_response_timeout); + // client-side methods + static const std::uint16_t request_connection(const std::string &server_ip, const std::uint16_t server_port); + }; + + template + void Protocol::send_data(const Protocol::ClientData &pend_conn, const T &data) { + static_assert(!std::is_pointer::value, "Cannot operate with pointers"); + + if (write(pend_conn.pend_conn_sock_fd, &data, sizeof(data)) != sizeof(data)) { + close(pend_conn.pend_conn_sock_fd); + throw exception("Cannot write data to the socket"); + } + } + + template + std::optional Protocol::receive_data(const Protocol::ClientData &pend_conn, int timeout) noexcept { + static_assert(!std::is_pointer::value && !std::is_reference::value, "Cannot read from pointer and has no functionality to work with references"); + + if (timeout != -1) { + validate_timeout(timeout); + } + + struct pollfd pfds[1] {}; + pfds[0].fd = pend_conn.pend_conn_sock_fd; + pfds[0].events = POLLIN; + + int events_num { poll(pfds, 1, timeout) }; + + if (!events_num) { + std::cerr << "Timeout trying to read data from a socket, pending connection IP: " + pend_conn.ip << std::endl; + return {}; + } + + int pollin_happened { pfds[0].revents & POLLIN }; + + if (pollin_happened) { + T data {}; + + if (read(pend_conn.pend_conn_sock_fd, &data, sizeof(data)) != sizeof(data)) { + std::cerr << "Cannot read the requested amount of data from a socket" << std::endl; + return {}; + } + + return data; + } + + std::cerr << "Unknown connection failure" << std::endl; + return {}; + } + } +} \ No newline at end of file diff --git a/shared/inc/Utils.hpp b/shared/inc/Utils.hpp new file mode 100644 index 0000000..4477d2e --- /dev/null +++ b/shared/inc/Utils.hpp @@ -0,0 +1,46 @@ +#pragma once +#include "Protocol.hpp" +#include +#include +#include +#include + +namespace remramd { + namespace internal { + class Utils { + private: + static const std::vector dump_ldd_output(const std::string &exposed_program); + + public: + static inline const std::string menu_msg { + R"(1) Show pending connection)" "\n" + R"(2) Show current connected clients)" "\n" + R"(3) Accept pending connection)" "\n" + R"(4) Decline pending connection)" "\n" + R"(5) Decline all pending connections)" "\n" + R"(6) Drop specific client)" "\n" + R"(7) Drop all clients)" "\n" + R"(8) Exit)" "\n" + "\n" + R"(> )" + }; + + // first - library, second - directory where the library resides + using ldd_output_data = std::pair; + + template + static void process_input(InputType &input_var, const std::string &msg = "") { + if (msg.size()) { + std::cout << msg; + } + std::string input_str_container {}; + std::getline(std::cin, input_str_container); + std::stringstream ss(input_str_container); + ss >> input_var; + } + + static std::vector parse_so_dependencies(const std::string &exposed_program); + static const std::string populate_client_jail(const Protocol::ClientData &new_client, const std::string &jail_path); + }; + } +} \ No newline at end of file diff --git a/shared/inc/remramd_exception.hpp b/shared/inc/remramd_exception.hpp new file mode 100644 index 0000000..68fa376 --- /dev/null +++ b/shared/inc/remramd_exception.hpp @@ -0,0 +1,14 @@ +#pragma once +#include +#include +#include + +namespace remramd { + class exception : public std::exception { + private: + std::string _err_msg; + public: + exception(const std::string &err_msg); + const char* what() const noexcept override; + }; +} \ No newline at end of file diff --git a/shared/src/Connection.cpp b/shared/src/Connection.cpp new file mode 100644 index 0000000..4960737 --- /dev/null +++ b/shared/src/Connection.cpp @@ -0,0 +1,82 @@ +#include "../inc/Connection.hpp" +#include +#include +#include +#include +#include +#include + +namespace remramd { + Connection::~Connection() { + close(sock_fd); + } + + void Connection::connect(const std::string &target_ip, const std::uint16_t target_port, internal::PipeWrapper &pipe) { + sock_fd = socket(AF_INET, SOCK_STREAM, 0); + + if (sock_fd == -1) { + throw exception("cannot open a socket for a requested connection"); + } + + struct sockaddr_in client_info {}; + client_info.sin_family = AF_INET; + client_info.sin_addr.s_addr = inet_addr(target_ip.c_str()); + client_info.sin_port = htons(target_port); + + if (::connect(sock_fd, (struct sockaddr *)&client_info, sizeof(client_info))) { + close(sock_fd); + throw exception("Failed to connect"); + } + + //if (this->server_side) { + dup2(sock_fd, fileno(stdin)); + dup2(sock_fd, fileno(stdout)); + dup2(sock_fd, fileno(stderr)); + pipe.write(internal::Protocol::PipeResponse::CHILD_SUCCESS); + execl("/bin/bash", "/bin/bash", "-i", nullptr); + // } + + } + // server-side constructor for reverse shell connection preparation + Connection::Connection(const internal::Protocol::ClientData &new_client, + internal::PipeWrapper &pipe, + Server &server_instance) : server_side(true) { + + const std::string chown_cmd { + "chown -R " + std::to_string(new_client.uid) + ':' + + std::to_string(new_client.gid) + ' ' + + new_client.fakeroot_path + }; + + std::system(chown_cmd.c_str()); + + std::cout << "Changing the current working directory to " << new_client.fakeroot_path << std::endl; + if (chdir(new_client.fakeroot_path.c_str())) { + throw exception("Cannot change the current directory to the client's fakeroot"); + } + + if (chroot(new_client.fakeroot_path.c_str())) { + throw exception("Failed to chroot into the client's jail"); + } + + if (setgid(new_client.gid) || setuid(new_client.uid)) { + throw exception("Cannot drop client's permissions for the given chroot jail"); + } + + char env[] = "PATH=/bin"; + putenv(env); + + this->connect(new_client.ip, new_client.reverse_shell_port, pipe); + } + + int Connection::get_sock_fd() const noexcept { + return sock_fd; + } + + /*std::optional> Connection::get_connected_client() noexcept { + if (server_side) { + return std::ref(connected_client); + } + return {}; + }*/ +} \ No newline at end of file diff --git a/shared/src/Protocol.cpp b/shared/src/Protocol.cpp new file mode 100644 index 0000000..9ebc3ef --- /dev/null +++ b/shared/src/Protocol.cpp @@ -0,0 +1,167 @@ +#include "../inc/Protocol.hpp" +#include "../inc/remramd_exception.hpp" +#include "../../shared/inc/Utils.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace remramd { + namespace internal { + Protocol::ClientData::ClientData() { + exposed_binaries.push_back("/bin/bash"); + exposed_binaries.push_back("/bin/cp"); + exposed_binaries.push_back("/bin/mv"); + exposed_binaries.push_back("/bin/ls"); + exposed_binaries.push_back("/bin/cat"); + } + + std::optional Protocol::accept_new_client(int fd, int timeout) { + validate_timeout(timeout); + + struct pollfd pfds[1] {}; + pfds[0].fd = fd; + pfds[0].events = POLLIN; + ServerResponse positive_response { ServerResponse::YEP }; + + write(fd, &positive_response, sizeof(positive_response)); + + int event_num { poll(pfds, 1, timeout) }; + + if (!event_num) { + // timeout + return ClientResponse::C_TIMEOUT; + } + + int pollin_happened { pfds[0].revents & POLLIN }; + + if (pollin_happened) { + ClientResponse response {}; + read(fd, &response, sizeof(response)); + return response; + } + + return {}; + } + + void Protocol::validate_timeout(int &timeout) { + if (!timeout || timeout == -1) return; + + if (timeout < 0) { + timeout = (~timeout) + 0x1u; + } + + // wrap around the timeout value if it passes the protocol's limit + if (timeout > timeout_limit) { + timeout %= timeout_limit; + timeout = timeout ? (timeout + 1) : timeout; + } + + timeout *= 1000; // from seconds to milliseconds + } + + std::optional Protocol::wait_new_connection_request(int server_sock_fd, int client_response_timeout) { + struct sockaddr_in client_addr {}; + unsigned client_addr_len { sizeof(client_addr) }; + struct pollfd pfds[1] {}; + pfds[0].events = POLLIN; + + ClientData curr_pend_conn {}; + + if ((curr_pend_conn.pend_conn_sock_fd = accept(server_sock_fd, (struct sockaddr *)&client_addr, &client_addr_len)) == -1) { + std::cerr << "Cannot accept a new connection\n"; + return {}; + } + + curr_pend_conn.ip = inet_ntoa(client_addr.sin_addr); + + std::cout << std::endl << "New connection request from IP: " << curr_pend_conn.ip << std::endl << Utils::menu_msg; + + validate_timeout(client_response_timeout); + + // poll 10 seconds for reading + pfds[0].fd = curr_pend_conn.pend_conn_sock_fd; + int events_num { poll(pfds, 1, client_response_timeout) }; + + if (!events_num) { + close(curr_pend_conn.pend_conn_sock_fd); + std::cerr << "Timeout\n"; + return {}; + } + + int pollin_happened { pfds[0].revents & POLLIN }; + + // check if I can read an actual data from client's socket + if (!pollin_happened) { + close(curr_pend_conn.pend_conn_sock_fd); + std::cerr << "Unexpected poll event\n"; + return {}; + } + + return curr_pend_conn; + } + + const Protocol::ClientRequest Protocol::request_connection(const std::string &server_ip, const std::uint16_t server_port) { + auto get_rand_port = []() -> ClientRequest { + static constexpr std::uint16_t port_rng_start { 1024 }, + port_rng_end { 49151 }; + static std::default_random_engine def_rnd_eng { std::random_device()() }; + static std::uniform_int_distribution ushort_distr(port_rng_start, port_rng_end); + return ushort_distr(def_rnd_eng); + }; + + int client_sock { socket(AF_INET, SOCK_STREAM, 0) }; + + if (client_sock == -1) { + throw exception("Cannot open a socket for a client"); + } + + struct sockaddr_in sa_in {}; + sa_in.sin_family = AF_INET; + sa_in.sin_port = htons(server_port); + + if (inet_pton(sa_in.sin_family, server_ip.c_str(), &sa_in.sin_addr) <= 0) { + close(client_sock); + throw exception("Invalid server IP address"); + } + + if (connect(client_sock, (struct sockaddr*)&sa_in, sizeof(sa_in))) { + close(client_sock); + throw exception("Cannot connect to the server"); + } + + ClientRequest rand_port { get_rand_port() }; + + rand_port = htons(rand_port); + + write(client_sock, &rand_port, sizeof(rand_port)); + + rand_port = ntohs(rand_port); + + ServerResponse server_response {}; + + std::cout << "Waiting for the server response...\n"; + read(client_sock, &server_response, sizeof(server_response)); + + ClientResponse client_response {}; + + close(client_sock); + + switch (server_response) { + case ServerResponse::YEP: + return rand_port; + case ServerResponse::NOPE: + throw exception("The server refused to establish a given connection"); + default: + throw exception("Unknown server response"); + } + + } + } +} \ No newline at end of file diff --git a/shared/src/Utils.cpp b/shared/src/Utils.cpp new file mode 100644 index 0000000..89bf46e --- /dev/null +++ b/shared/src/Utils.cpp @@ -0,0 +1,97 @@ +#include "../inc/Utils.hpp" +#include "../inc/remramd_exception.hpp" +#include +#include +#include + +namespace remramd { + namespace internal { + const std::vector Utils::dump_ldd_output(const std::string &exposed_program) { + FILE* fp { popen(("ldd " + exposed_program).c_str(), "r") }; + if (!fp) { + throw remramd::exception("Cannot invoke ldd for " + exposed_program); + } + + char popen_byte {}; + std::string full_ldd_output {}; + // read ldd output byte by byte + while ((popen_byte = fgetc(fp)) != EOF) { + full_ldd_output += popen_byte; + } + if (!full_ldd_output.size()) { + pclose(fp); + throw exception(exposed_program + " doesn't exist (or wrong path has been provided)"); + } + + std::stringstream ss(full_ldd_output); + std::string curr_line {}; + std::vector formatted_ldd_output {}; + + while (std::getline(ss, curr_line, '\n')) { + auto first_non_htab_char_idx { curr_line.find_first_not_of('\x09') }; + curr_line.erase(std::remove(curr_line.begin(), curr_line.begin() + first_non_htab_char_idx, '\x09'), + curr_line.begin() + first_non_htab_char_idx); + formatted_ldd_output.push_back(curr_line); + } + + pclose(fp); + return formatted_ldd_output; + } + + std::vector Utils::parse_so_dependencies(const std::string &exposed_program) { + const auto ldd_output { dump_ldd_output(exposed_program) }; + // parse the given full ldd output of the given exposed program + const std::regex lib_path_regex { ".*/.*" }; + std::smatch lib_path_smatch {}; + std::vector so_dep_paths {}; + + for (const auto &curr_line : ldd_output) { + std::stringstream ss(curr_line); + std::string curr_token {}; + + while (std::getline(ss, curr_token, ' ')) { + if (std::regex_match(curr_token, lib_path_smatch, lib_path_regex)) { + auto last_forward_slash_idx { curr_token.find_last_of('/') }; + so_dep_paths.push_back({ curr_token.substr(last_forward_slash_idx + 1), curr_token.substr(0, last_forward_slash_idx) }); + } + } + + } + + if (!so_dep_paths.size()) { + throw exception("Failed to parse ldd output"); + } + + return so_dep_paths; + } + + const std::string Utils::populate_client_jail(const Protocol::ClientData &new_client, const std::string &jail_path) { + const std::string curr_client_fakeroot_path { jail_path + '/' + new_client.ip }; + + if (!std::filesystem::exists(curr_client_fakeroot_path)) { + std::filesystem::create_directory(curr_client_fakeroot_path); + std::cout << curr_client_fakeroot_path << " has been created\n"; + } + + // populate the client's jail directory with all relevant dependencies + for (const auto &exposed_binary : new_client.exposed_binaries) { + auto so_deps { internal::Utils::parse_so_dependencies(exposed_binary) }; + for (const auto &so_dependency : so_deps) { + const std::string destination_path { curr_client_fakeroot_path + so_dependency.second }; + if (!std::filesystem::exists(destination_path)) { + std::filesystem::create_directories(destination_path); + std::cout << destination_path << " has been created\n"; + } + const std::string source_path { so_dependency.second + '/' + so_dependency.first }; + std::filesystem::copy(source_path, destination_path, std::filesystem::copy_options::skip_existing); + std::string exp_bin_dest_path { exposed_binary.substr(0, exposed_binary.find_last_of('/')) }; + exp_bin_dest_path = curr_client_fakeroot_path + exp_bin_dest_path; + std::filesystem::create_directories(exp_bin_dest_path); + std::filesystem::copy(exposed_binary, exp_bin_dest_path, std::filesystem::copy_options::skip_existing); + } + } + + return curr_client_fakeroot_path; + } + } +} \ No newline at end of file diff --git a/shared/src/remramd_exception.cpp b/shared/src/remramd_exception.cpp new file mode 100644 index 0000000..a4e1475 --- /dev/null +++ b/shared/src/remramd_exception.cpp @@ -0,0 +1,11 @@ +#include "../inc/remramd_exception.hpp" + +namespace remramd { + exception::exception(const std::string &err_msg) : _err_msg(err_msg) { + _err_msg = "remramd::exception: " + _err_msg; + } + + const char* exception::what() const noexcept { + return _err_msg.c_str(); + } +}