Covert Channels -- trenchcoats for the digital age
Due by midnight, Friday 01 Monday 04 December 2006
Don't ask for any further extensions unless you have some code actually implemented!
In this assignment, you will be implementing a pair of programs that communicate indirectly via a covert channel.
A covert channel is a communication channel between two objects that are not usually able to communicate. They can be constructed to communicate in a number of ways. The two most frequently discussed methods are timing channels and space based channels. For this project, we'll be communicating via a space based channel.
Imagine having two people at a school that aren't supposed to communicate with each other and perhaps don't even know each other's identity. However, one party can convey information to the other by putting or releasing holds on a book in the library, while the other party just checks every day if there is a hold on the book. There is no direct communication between them, yet information can be conveyed.
For this project, you will write a sender and receiver program to communicate the data contained in an ASCII text file between each other. The sender will transmit the contents of the text file one bit at a time, and the receiver will collect and resemble the data.
We are going to use shared memory objects to indicated whether the next transmitted bit is 0 or 1. If the sender needs to transmit a 0, it will create a shared memory object with an agreed upon name; if, in turn, it needs to send a 1, it will not hold the object will that name. The sender will send a new bit every N seconds.
The receiver's responsibility is then to check for the new bit every N seconds. It will attempt to create the same shared memory object. If the object already exits, then the receiver interprets it as 0, and if the creation process succeeds, the receiver treats it as 1. On a successful receival of 8 bits, the receiver can assemble them into a character and write it to an output file.
You are permitted to use any programming language that is supplied on the CS lab machines. The methods for accessing shared objects readily accessed via system calls in C. I've implemented a Java wrapper for these system calls which I can distribute as needed. For other languages, I'll help as best I can, but you might be on your own.
You should make sure that your programs compile and are prepared for execution via a Makefile. (I.e., just need to type make from the command line.)
Create a program called cc_writer that takes 3 arguments as follows:
cc_writer <obj_name> <interval> <infile>
This program will read in data from infile and communicate it by manipulating the obj_name memory object. If the bit being transmitted is 0, then the object must stay created for interval seconds. If the bit being transmitted is 1, then the program needs to make sure that it doesn't lock the object during interval seconds.
Create a program called cc_reader that takes 3 arguments as follows:
cc_reader <obj_name> <interval> <outfile>
This program will be reading the data by checking existence of the obj_name object in memory every interval seconds. Existence indicates 0, while non-existence means 1. If the reader program was able to successfully create the object, it should quickly delete it, so that it does not interfere with the writer program. You can do this by attempting to open the shared object. It should fail with either an error value indicating the shared memory segment does not exist (ENOENT) or that you don't have permission to open it (EACCES). Note that if you are testing against your own writer, you'll have permission to access the object, so success should be treated the same as EACCES. All received data should be stored in outfile.
Once you have the program working at slower speeds, you may optionally modify your programs to support a -m, -u, or -n flag that allows for you to interpret the interval as milli-, micro-, or nano-seconds respectively. My test program was able to create and delete 10,000,000 shared memory segments in a little over 41 seconds. Be aware that these shorter intervals might result in greater difficulties synchronizing.
Since the namespace for all shared objects is global, it is possible that two students will happen to choose the same obj_name and possibly cause unintentional interactions. If this becomes an issue, please let me know and we'll find a solution. Students found deliberately interfereing with other students by abusing this resource will be given a 0 on the project and referred to the instructor for further action. Also, make sure that upon termination your processes delete all shared memory objects that they created.
After the writer program reads a byte of data from the input file, it will transmit it one bit at a time. The most significant bit of a byte is always transmitted first. Similarly, the reader program after receiving 8 bits will interpret the first bit as the most significant bit of the byte during the character assembly.
After the writer program finishes tramission of the input file, it will send an additional byte with the code of 255 to indicate that the end of the file has been reached. (Note that in some encoding schemes this value could represent a valid character, but for the purpose of this assignment we assume that such characters will not be present in the input file.) Correspondingly, the reader program will terminate after it receives 8 consecutive 1's on the 8-bit boundary, i.e., a byte with the code of 255. This byte should not be written in the output file.
Notice that in our implementation the absence of the object in memory means 1. Thus, if for some reason the writer program fails or is not currently running, the reader program will terminate after at most 15 intervals.
Another scenario where things can go wrong is when the writer program hangs without releasing the object. In this case, the reader program will constantly receive a stream of 0's, while no information is being communicated through the channel. To fix the problem, we make the reader program terminate after receiving 3 consecutive bytes with the value of 0 (i.e., three chunks of 8 bits on the 8-bit boundary). In this case only the first two such bytes are written to the output file, and the reader terminates.
Another issue which is very important for this project is synchronization between the processes. The simplest approach to implement is for your program to simply go to sleep for the number of seconds specified for one bit transmission. This, however, might result in drifts when a rather large file is being trasmitted. For instance, if you started checking with the interval of 5 seconds, your actual execution might in this case be something like 5.05s, 10.1s, 15.15s, etc., which eventually might result in incorrect trasmission or receival. A better alternative is to fix the time when the first check was performed, determine the time when the next check should be performed (i.e., the start time plus the length of the interval multiplied by the number times the check has been performed), get the current time, and compute the duration of time for which the program should go to sleep. In other words, you calculation is based not on the last time when the check was performed but on the very first check.
Another thing which is unlikely to happen but is still possible is that the writer and reader might be waking up at the same time. Then depending on the way the processes are scheduled, the reader might or might not receive the correct bit. In order to overcome this problem, you might want to implement a smarter behavior for your reader. For example, you let it to wake up twice during the interval that corresponds to the transmission interval of one bit. It then will compare the bits and make a decision about the actual bit being trasmitted. An even better approach could be, for example, a two-out-of-three scheme. There are no constraints on the way you implement the reader, so go for the protocol you feel the most comfortable with.
It is possible that the writer will also fail to create a shared object, if the writer and reader happen to wake up at the same time. In your implementation, allow the writer to retry sending the bit instead of immediate termination.
Since we are transmitting ASCII data, it is necessary to ensure that the receiver knows where a new byte starts. In other words, if the reader starts listening to the stream at an arbitrary point, it is unlikely to resemble the message correctly. To overcome this problem, we are going to use the following solution. The writer, prior to sending any infile data, will transmit the following leader:
11110000 11001100 10101010The reader can use it or any suffix of it to determine where the actual message is going to start. For example, in your implementation you may choose to assume that successful receival of the last two bytes of the leader is sufficient for synchronization to be successful (in this case if the reader receives a part of the first byte, it still needs to verify its pattern). You are free to choose what suffix length is considered sufficient for your implementation. And if the suffix condition is not met for the reader, you can assume that it won't be able to interpret the message correctly, so that you can print an error message, and terminate.
You might find the following calls useful for the project:
I was going to put a bunch of things here, but I'll just point you to some sample bits of code that I wrote. All of these things are in one tarball: hw6-sample-code.tar.gz
For C-people, you'll want to take a look at:
For Java-people, you'll want to look at:
[ba]sh: export LD_LIBRARY_PATH=.:
[t]csh: setenv LD_LIBRARY_PATH .:
I've set up my copy of cc_writer to be sending a message on the machine 'dvorak' with a 2 second interval. A full transmission of the message takes just under 10 minutes. In order to keep you from having to wait too long, I've set it up so that it will transmit on a different memory segment depending on the minute.
Minute 0: cc_writer 20000 2 message
Minute 1: cc_writer 20001 2 message
Minute 2: cc_writer 20002 2 message
Minute 3: cc_writer 20003 2 message
Minute 4: cc_writer 20004 2 message
Minute 5: cc_writer 20005 2 message
Minute 6: cc_writer 20006 2 message
Minute 7: cc_writer 20007 2 message
Minute 8: cc_writer 20008 2 message
Minute 9: cc_writer 20009 2 message
Additionally, I have a faster writer that runs every minute but has an interval of only 10 milliseconds
cc_writer -m 19999 10 message2
Let me know if any problems arise with these samples.
As with the other assignments, I encourage you to discuss various approaches with your peers. Once you have a working solution, it may be especially useful to communicate between your two accounts.
Along with your program, include a README file which includes the following information: