Sunday, February 25, 2007

Open Letter Regarding Display Eater

To Reza Hussain:

There is a lack of official information about versions of Display Eater that predate version 1.85, which is the version I decompiled. You were interviewed by a Swedish web site, and allegedly provided the information we are looking for, but the English translation of that interview is currently only available through a third party.

Reza, I respectfully request an answer to this question:

Did previous versions of Display Eater delete files and/or folders other than your own Application Support folder?

I recognize that version 1.85 only deletes files that your app created, and I only ask this question to provide facts to the Mac community, and to conclude this sad chapter of our shared history.

Be assured that I am not interested in harassing you, whatever your answer may be. We just want to know the truth. In the interest of preserving what is left of your good reputation, please enlighten us, in English.

Thank you.

To everyone else:

The comments to this post are reserved for Reza, and polite commenters. Inflammatory comments will be removed, as there are countless other outlets for those sentiments.

Thank you.

Saturday, February 24, 2007

Behind The Curtain With Display Eater

You may have seen the discussion recently about an app called Display Eater by Reza Hussain. While the author has not responded to the latest discussion, he allegedly admitted that the whole thing was a hoax to deter potential pirates. Reza has just replied with a public letter. While the discussion has been lively, the investigation has been nonexistant. So here it is.

First, the legal stuff:

According to the Display Eater 1.8.5 license agreement:

3. License Restrictions
b. You may not alter, merge, modify, adapt or translate the Software, or decompile, reverse engineer, disassemble, or otherwise reduce the Software to a human-perceivable form.


But I wanted to disassemble the app! Fortunately, the license is not really a legal agreement:

THIS SOFTWARE END USER LICENSE AGREEMENT(EULA) IS A LEGA AGREEMENT BETWEEN YOU(EITHER AN INDIVIDUAL OR, IF PURCHASED OR OTHERWISE AQUIRED BY OR FOR AN ENTITY, AN ENTITY) AND THE AUTHOR OF THIS SOFTWARE.


Since LEGA AGREEMENTs don't hold up in court, we're free to do what we want with the app*. I'd also like to suggest to John Stansbury that while his assertion that users don't have the right to reverse engineer an app is correct in most cases, it's not applicable here, for the above reason.

Reza, fix your license.

Second, the fun stuff:

This is one of those apps that writes copious amounts of crap to the console for no reason. For example, after simply launching the app, declining to register, and quitting, I got this:

display_eater[3338] help me Start Recording
display_eater[3338] no file
display_eater[3338] mmmkay
display_eater[3338] mmmmm
display_eater[3338] selfmutilation DVC PAL
display_eater[3338] lettesta /Applications/Display Eater 1.85/Display Eater.app/Contents/Resources/cursor2.gif
display_eater[3338] onenationunderfuck 640x480
display_eater[3338] cheated--20 fps
display_eater[3338] loadedrender

Yes, that says onenationunderfuck. In my console log. Is it still cursing if you don't use camelCase? Well, at least he's a South Park fan. Here are a few other nice ones I found in the disassembly:

This "emailDeveloper:" method doesn't actually send any mail, but at least it further pollutes my log.

-(void)[mainWindowController emailDeveloper:]
3c600003 lis r3,0x3
38639500 addi r3,r3,0x9500 emailz0r
4801e6d8 b 0x21bd0 _NSLog


And from other methods:

386395e0    addi     r3,r3,0x95e0     FATTTTTTTTTY>>>>>>>
4801e005 bl 0x21bd0 _NSLog

386395a0 addi r3,r3,0x95a0 not saved lol
4801e1a1 bl 0x21bd0 _NSLog

3863a5a0 addi r3,r3,0xa5a0 lawl
4800f438 b 0x21bd0 _NSLog

3863b2c0 addi r3,r3,0xb2c0 OMGFWTFBBQxinFINITY
48003ee8 b 0x21bd0 _NSLog

38639e60 addi r3,r3,0x9e60 SDKNLLLLLLLLLLLLL
480161d5 bl 0x21bd0 _NSLog


And on and on and on. People, mescaline can be loads of fun, but it's not conducive to a healthy programming environment.

Ok, so Reza likes to make jokes at the expense of my log file, but that's not a capital offense. Allegedly deleting user data should be. While we're talking about spurious log data, I should note that Display Eater actually logs the customer's name, email and serial number on every launch. I'll give you one guess why that's a Bad Idea™.

Finally, the good stuff:

Ok, so Reza beat me to the punch by admitting that Display Eater does not really delete your home directory. But it does delete something, and those users who watch their console logs already know what it is. I had planned to present the complete annotated assembly of the methods involved, but that would bloat this post incredibly and bore most of you. Here's a higher level explanation:

The C function that does the deleting is called "destroy". In otool or otx output, search for "_destroy:". Its C declaration would look like this:

void destroy(NSString* inString);


It is called only from itself, and from one Obj-C method in the "recordCreateController" class:

- (id)addRecordWithPath: (NSString*)inPath
andRect: (NSRect*)inRect;


(class-dump says inPath should be of type "id" but the code in Display Eater assumes an NSString*. We'll have to wait for the GPL'd code, but I suspect this is just more sloppy code- see below for the rest)

In the disassembled code, "addRecordWithPath:andRect:" immediately follows "_destroy:".

Note the interestingly named "emptyEntireClipOnAllOfThem" that follows "addRecordWithPath:andRect:". While it smacks of gangland violence, it is not involved with deleting any files. My good conscience prevents me from revealing its purpose :)

So, when does "addRecordWithPath:andRect:" call "destroy"? The Obj-C code looks something like this:

    id  delegate = [[NSApplication sharedApplication] delegate];

// Reza, you may want to call [delegate respondsToSelector:] first...
NSString* support = [delegate applicationSupportFolder];

// "ninjakiller" is the reg file.
// Reza, use stringByAppendingPathComponent, mmmkay?
NSString* regFile = [support stringByAppendingString: @"/ninjakiller"]
NSArray* regArray = [NSUnarchiver unarchiveObjectWithFile: regFile];

if (![[regArray objectAtIndex: 1] compare: @"Special [K]"] ||
![[regArray objectAtIndex: 1] compare: @"KCN"])
{
NSLog(@"BUT I FALTER");
destroy(@"~/Library/Application Support/display_eater/");

// and then show the pirate dialog...
}


Ok, nothing surprising. Whoever Special [K](nice name, btw) and KCN are, they're screwed. So what happens inside "destroy"? It seems clear already that Display Eater's app support folder will get nuked, but is that all that happens?

void destroy(NSString* inString)
{
NSFileManager* manager = [NSFileManager defaultManager];

NSString* path = [inString stringByExpandingTildeInPath];
// Believe it or not, 'path' is only used for the following call.

NSArray* files = [manager directoryContentsAtPath: path];
unsigned int curFile; // I assume this is unsigned.

for (curFile = 0; curFile < [files count]; curFile++)
{
// Reza, you've already expanded this- you don't need to do it twice.
NSString* basePath = [inString stringByExpandingTildeInPath];
NSString* curFileName = [files objectAtIndex: curFile];

// Reza, please use stringByAppendingPathComponent.
NSString* curPath = [NSString stringWithFormat: @"%@/%@"
basePath, curFileName];

NSLog(@"%@", curPath);

// Reza, you did this already...
NSFileManager* manager2 = [NSFileManager defaultManager];

// ...and this, too.
// I'm not sure why passing just the name and not the path works,
// but it does.
if ([manager2 isDeletableFileAtPath: [files objectAtIndex: curFile]])
{
// Calling default manager yet again. At least we're using
// the full path this time.
[[NSFileManager defaultManager]
removeFileAtPath: curPath handler: nil];
NSLog(@"DELETABLE");
}
else
{
destroy(curPath);
}
}
}


So there it is. Display Eater recursively deletes the contents of its own Application Support folder(but not the folder itself), and nothing else. If the user was silly enough to put anything in that folder, it would have been nuked. But in that case, one might argue that they deserved it.

Note that the "piratekiller" file, whose contents seem to indicate a perverted TCL function call(kilo transform ar reza) is never used for anything.

Ultimately, the worst thing about this app is the sloppy Obj-C code. Reza, I would be happy to optimize your code a bit, if you're so inclined.

* I'm no lawyer- if anyone can prove me wrong about this point, please do so.

Thursday, February 22, 2007

Another Link Post

Lately, I've had better luck with links than real info, and I've recently come across a webcomic that most of you will enjoy. Here's a taste. OK, one more. And be careful hopping off the bus, Gus.

Recommended usage: Go to the main page and continue hitting the 'prev' button until your boss/wife/parole officer starts asking questions.

Many thanks to whichever Mac dev shared that site with me via their del.icio.us bookmarks. Sorry I don't remember who it was.

Tuesday, February 20, 2007

Some BOOLs Have All The Luck

ALERT: This post has been proven wrong. It remains here as a badge of shame. Continue reading for entertainment purposes only!

A quick note, since something just bit me in the ass, and some of us dislike unsolicited ass-biting. Disclaimer: I'm using XCode 2.2 and whatever gdb was current then- this may have been fixed since then.

I had a BOOL member/instance variable, we'll call it myBool. I set it like so-
myBool = someInt == someOtherInt;

And all was well in both debug and release targets. Then I changed the scope of the member/instance variable to global, outside of any class, as in-

static BOOL myBool;

After doing so, the boolean expression above evaluated to false/NO, regardless of the values of the 2 ints. I watched it happen in XCode's debugger window, it was awesome.

After several forceful introductions of my keyboard and my forehead, I tried a silly test that shouldn't matter at all, and it worked.

myBool = (someInt == someOtherInt);

I compiled and disassembled both versions of my release and debug targets, hoping for a most excellent blog post, and was greeted with identical code all round. So the problem appears to be that my version of gdb has different rules for evaluating boolean expressions, depending on the scope of the receiver of the expression. Global variables require a parenthesized expression while member/instance variables do not.

One guess who will be parenthesizing more than usual in the future.

And sorry, Wolf. The Rod Stewart reference was too good to pass up :)

Update: Victoria Wang, who I met at PSIG 101, posted an amusing quote about parentheses from one of her 'CIS' professors(Computing and Information Systems?)

"Some teachers go crazy when you use parentheses on an expression that's going to be evaluated first anyway. I don't care. It's like wearing a belt, and suspenders too!"

So, did gdb swipe my belt or my suspenders? And can I set a watchpoint on my fly?

Cheers, Victoria, and thanks to Wolf for the link.

Update 2:
After a restart, Xcode is no longer exhibiting the strange behavior. Maybe something was corrupt, I don't know. Sorry for the false alarm. It is now safe to reenter the building.

Wednesday, February 14, 2007

Show A Brotha Some Love

My boy Karsten just pimped GDB and made copies of his pimp cane available for all of us.

Holla.

Wednesday, February 7, 2007

God Went And Did It Again

Finally, the truth about global warming. Those damned scientists were really getting on my nerves.

I don't want this to be a link blog, but this gem was too good to pass up.