When exploiting a buffer overrun vulnerability, the goal of an attacker is usually to change the flow of execution from the normal execution flow to a flow dictated by the attacker. Sure, he may want to whack a DWORD in memory or change a variable, but usually he wants to get shellcode running.

 

The classic attack is to whack sensitive data on the stack, most notably the function return address; but, there is more than one construct the attacker is interested in; other examples include exception handlers and pointers, especially pointers to code.

 

Here’s a quick (and highly contrived) code example:

 

void SomeFunc() {

  // do something

}

 

typedef void (*FUNC_PTR )(void);

 

int DangerousFunc(char *szString) {

  char buf[32];

  strcpy(buf,szString);

 

  FUNC_PTR fp = (FUNC_PTR)(&SomeFunc);

  // Other code

  // Other code

  (*fp)();

  return 0;

}

 

If an attacker controls the szString argument to DangerousFunc, then of course, the stack-based buffer named buf is ready to get clobbered. You know the rest of the story. Let’s assume for a moment that the attacker can’t whack the function return address (perhaps szString isn’t long enough) that leaves the function pointer, fp, as a likely victim. If the attacker can overwrite the function pointer then he can make it point to another address, perhaps exploit code and wheh (*fp)() is called, the ‘sploit runs. So we need to protect the function pointer.

 

Oh, something I should add, we re-arranged the stack layout in VC++ 2003 and VC++ 2005, so buffers are higher in memory than non-buffers, so to whack fp, you’d need a buffer under-run. But humor me.

 

In Windows XP SP2 and Windows Server 2003 SP1 we added functions to the OS to help defend such pointers from attack. The functions are:

 

EncodePointer and DecodePointer

EncodeSystemPointer and DecodeSystemPointer

 

All these functions do is XOR a pointer with a random number; the first two functions use a random number held in every application’s  process information block, and the system versions use a value that is tied to each system reboot and is accessible to every application running on the box.

 

Now take a look at the following somewhat safer code:

 

int DangerousFunc(char *szString) {

  char buf[32];

  strcpy(buf,szString);

 

  FUNC_PTR fp = (FUNC_PTR)EncodePointer(&SomeFunc);

  // Other code

  // Other code

  FUNC_PTR fp2 = (FUNC_PTR)DecodePointer(fp);

  if (fp2) (*fp2)();

  return 0;

}

 

Rather than using the pointer directly, the pointer is encoded and stays encoded until you’re about to use it. This makes is more difficult for an attacker, because he must overwrite the function pointer with a value that would calculate correctly after the XOR operation. Not impossible, but highly unlikely.

 

Which leads me to entropy. The per-process random number is derived from:

 

  • The higher portion of the system tick count (100 nano-second tick count since Jan 01, 1601) XOR
  • The lower portion the system tick count XOR
  • The number of interrupts since system boot XOR
  • The number of system calls since system boot.

The source code example I showed is somewhat contrived, but these pointer encoding functions become really useful if you have global or very long-lived pointers, for example, in Windows we use these functions to encode unhandled exception filter addresses and more.

 

You shouldn’t use it for every pointer you use, because that would simply bog the system down, and require a ton of code churn – rather, you should pick your battles. If you have long-lived function pointers in critical Internet-facing code that are not often accessed, I would seriously consider using these APIs.

 

Finally, the code samples I showed are of course, lousy. If you compile this code with VC++ 2005 you’ll be told that strcpy is bad and you should use something else, like strcpy_s. But because the buffer size is a constant, VC++ 2005 can automatically change the call from strcpy to strcpy_s.

 

(Thanks to Neill Clift, Shawn Hernan and Nitin Kumar Goel for their draft reviews)