Writing

Software, technology, sysadmin war stories, and more. Feed
Friday, July 26, 2013

Binary editing thwarted by a simple optimization

Some years back, I was trying to get rid of the thing in my IM client which would send the "so and so has closed the conversation window" message. This was an annoying misfeature which could not be disabled through the UI. I needed to get rid of it since it was sending the wrong impression to my chat friends.

When I'm at work, sometimes I like to "garbage collect" my chat windows by closing the ones which haven't been active for a while. It's nothing personal, and it doesn't mean I "hung up" on someone. It just means I don't want their text in my face any more since it's not currently serving a purpose. So, I'd close the window.

Unfortunately, some version of Gaim (or was it Pidgin by then, hmm...) changed things so that it would emit a message to the server, and that would then bounce out to the other person who would find out. I decided to turn it off. The trouble was that I was running a binary version at work and didn't want to go through the mess of rebuilding it from source. I figured I could just find the string and change it to something else so it would send a harmless no-op message to the server. It was easier than trying to find the actual code which sent the message and NOPping it out.

I set to digging around in the binary, and hit a snag: the string in question didn't occur anywhere by itself. This was bizarre because the string itself was right there:

case JM_STATE_GONE:
	child = xmlnode_new_child(message, "gone");
	break;

Still, looking for "gone" didn't turn up anything useful. The closest thing I found was "host-gone" which was used for something else entirely. I stared at this one for a minute or two and then it occurred to me: the compiler was probably doing something clever.

Think about it in terms of pointers. That literal "gone" and the other literal "host-gone" can both be expressed with a single entry in a table somewhere and two slightly different pointers. References to "host-gone" would amount to "ptr", whereas "gone" would be "ptr + 5". Since it would never change, it was safe to point both of them at the same memory.

I verified this by taking the source and changing "host-gone" to something else. Once that happened, "gone" popped up as its own entry in the symbol table.

This pretty much established that it would be impossible to merely patch a string in the binary to turn off this unwanted feature. I wound up having to patch the annoyance out of the source and run a custom binary instead. This was suboptimal but it was better than having people feeling offended because I decided to clean up my desktop.

...

Want to see this in action? Try this:

$ cat str.cc
#include <stdio.h>
 
int main() {
  printf("test 0x12345678\n");
  printf("0x12345678\n");
 
  return 0;
}

Take that and compile it with optimizations, then go looking for the magic string. It'll probably only hit once.

$ g++ -O -Wall -o str str.cc
$ strings str | grep 12345678
test 0x12345678

Do it without optimizations and they get separate entries.

$ g++ -Wall -o str-no-opt str.cc
$ strings str-no-opt | grep 12345678
test 0x12345678
0x12345678

Want hard proof that it's the same location? Okay, change the first string with your favorite binary editing tool, and then run it again.

$ g++ -O -Wall -o str str.cc
$ cat str | sed "s/test 0x12345678/elite fun hax0r/g" > str2
$ chmod a+rx str2 && ./str2
elite fun hax0r
 fun hax0r

Easy enough.