« "Collapse" Idea #3: Software Quality as Environmentalism | Main | Microsoft Dining Services Upgrades »

May 25, 2006

Fixing An Obfuscated Code Example

In one of the classes we teach in Engineering Excellence, we talk about maintainable code, and as an example of the other way, we have a sample of obfuscated C code. I went looking for the origin of that code and it turns out it's actually the "applin" example from the 1985 Obfuscated Code Contest (just the second year of the contest). The code looks like this:

main(v,c)char**c;{for(v[c++]="Hello, world!\n)";(!!c)[*c]&&(v--||--c&&execlp(*c,*c,c[!!c]+!!c,!c));**c=!c)write(!!*c,*c,!!**c);}

Since I've been know to produce some obfuscated code of my own, I got a bit curious about this code and whether it would actually work in Visual Studio 2005. I discovered the following things:

  1. VS2005 does not allow the old-style declaration of function parameters, where int is assumed and the declaration of the type can follow the function argument list. There is no C compiler per se, you just write C++ code that doesn't use any C++, and I couldn't find a switch to put it back in "pure C" mode (plus for all I know ANSI C doesn't allow that style either).
  2. On a similar note, you have to declare that main() returns an int, it won't assume that.
  3. The code tries to modify a string literal, which is apparently undefined behavior in C++ (and possibly in C), and in any case actually fails with an access violation on the code that Visual Studio outputs. So you have to copy the literal if you want to modify it.
  4. The code calls execlp(), which is a Unix function to replace the current process with a new process; although this method does exists in the Windows C runtime, it seemed to bollix up the program. I replaced it with a call to spawnlp(), which creates a separate process (I recall vaguely that old Unixes didn't have a spawn() and you had to do a (wasteful) fork() followed by an immediate exec())?
  5. Because of how the Windows command processor parses command lines, putting the space between "Hello," and "world" confuses the program, so I took it out. You also have to make sure the name/path of the executable doesn't have any spaces in it (VS2005 by defaults puts the program under "My Documents").
  6. Although the C runtime functions exist with plain names, the guidance from the documentation is to use the versions that have an underscore in front of their names (like _write() instead of write()), so I did that.
  7. The program appears to have an honest-to-goodness bug in it, where it prints out every character after the first one twice. Even someone who compiled it unchanged under Cygwin saw this. And walking through the code I think I see why. So I had to put in a bug fix (obfuscated, of course) for this.

Along the way I had to actually understand how the thing works, which was fun. Put it all together, and you get the following, which does indeed behave as expected:

int main(int v,char **c){for(v[c++]=_strdup("Hello,world!\n)"),v=!!v;(!!c)[*c]&&(v--||--c&&_spawnlp(P_WAIT,*c,*c,c[!!c]+!!c,!c));**c=!c)_write(!!*c,*c,!!**c);}

Just include process.h and io.h and you are good to go.

Posted by AdamBa at May 25, 2006 02:14 PM

Trackback Pings

TrackBack URL for this entry:
http://proudlyserving.com/cgi-bin/mt-tb.cgi/441

Comments

I had to include string.h.

Posted by: Adam Vandenberg at May 25, 2006 05:16 PM

Hmm, I guess I also had stdafx.h included and that must include string.h or stdio.h.

- adam

Posted by: Adam Barr at May 25, 2006 08:00 PM

I know that in Visual Studio 2003, you could use the /Tc flag to force it to compile as C code, but it won't let you overwrite a literal string declared as a pointer. It will let you do it if you declare it with array notation: char test[] = "hello"; will work but: char **test="hello"; won't. gcc has a nifty switch to force allowing overwriting literal strings, but VS does not.

Posted by: David Chilton at June 4, 2006 12:47 PM