7

My question is dedicated mostly to profs and is about using C++ in "strange" way. In C++ there isn't really big difference between pointers to variables and pointers to functions. We can do something useless like this:

char* buff     = new char[32];
void (*func)() = (void (*)())buff;

But we allmost created a function that never existed, right? What if we go further and fill buff with x86 commands stord in a file? OS will never know that a function was created.

#include <iostream>
using namespace std;

// no stack push'ing or pop'ing, nothing to return
void func(void){cout << "Hello?";}

int main()
{
  char* x86_code = new char[6];

  x86_code[0]                 = 0x9A;          // call (far)
  *((__int32*)(x86_code + 1)) = (__int32)func; // load 32-bit address
  x86_code[5]                 = 0xC3;          // ret

  void (*x86_func)(void) = (void (*)(void))x86_code;
  x86_func();

  return 0;
}

Calling x86_func() makes a runtime error (violation reading location 0xFFFFFFFF). How does OS loads it's binaries or modules in RAM if not in this manner? Many thanks.

5
  • 1
    Look for "shellcode" in the WEB. Commented Nov 17, 2013 at 8:52
  • 2
    Don't call far. That is nearly guaranteed to be incorrect in 32bit code. Use an absolute near call (opcode 0xFF with the reg field in the ModRM byte set to 2) (or a normal near call, but be aware that it's relative). Also, you might have to make some system call to make that memory executable. Commented Nov 17, 2013 at 8:56
  • 1
    Related: Data Execution Prevention on Wikipedia Commented Nov 17, 2013 at 10:19
  • Please also see Why are data pointers and function pointers incompatible in C/C++ Commented Nov 17, 2013 at 10:27
  • For specifically how to call functions in assembler, look at something called the calling convention (en.wikipedia.org/wiki/X86_calling_conventions). It details how exactly the stack and registers must be set up. Commented Nov 23, 2013 at 13:27

1 Answer 1

4

Indeed you can fill an array with x86 machine code and attempt to execute it. It's called shellcode and managing to make an application or library execute such code when it wasn't intended to is called an "exploit".

Unfortunately it's not that easy, since modern hardware and OSes normally prevent you from executing code from areas that are writable, such as non-const char arrays. This is called W^X (write or execute permissions, but not both) But one can request POSIX-compliant OSes to disable such protection with the mprotect() function. Here's an example that works, because it enables read, write and execute permissions on the array of machine code bytes concerned:

#include <stdio.h>
#include <stdint.h>
#include <sys/mman.h>


typedef int(*FUNKY_POINTER)(void);


char shellcode[] = {
    0xb8, 0x2a, 0x00, 0x00, 0x00, //mov    $0x2a,%eax
    0xc3                          //retq
};



int main(void){
    uintptr_t pageSize        = 4096;
    uintptr_t shellcodeAddr   = (uintptr_t)shellcode;
    uintptr_t pageAlignedAddr = shellcodeAddr & ~(pageSize-1);
    FUNKY_POINTER shellcodeFn = (FUNKY_POINTER)shellcode;

    /* Magic */
    mprotect((void*)pageAlignedAddr,
             (shellcodeAddr - pageAlignedAddr) + sizeof(shellcode),
             PROT_EXEC|PROT_WRITE|PROT_READ);

    printf("The answer to the ultimate question of life, "
           "the universe and everything is %d\n",
           shellcodeFn());

    return 0;
}
Sign up to request clarification or add additional context in comments.

6 Comments

thank you! but seems like mprotect() doesn't exists on windows platforms. i used VirtualProtect link from WinAPI and it allowed me to execute shellcode. this one relly helped me to understand low level things.
but still i don't understand how to call functions or edit variables from shellcode.
Ah now for that you should look at something called the calling convention. en.wikipedia.org/wiki/X86_calling_conventions
@NickSweeting That your code still works is luck. The reason for page-alignment is that the OS and hardware controls to prevent execution of shellcode operate at the granularity of a 4KB page; So mprotect() can only alter protection settings for blocks that start on addresses divisible by 4KB. Hence why I calculate the shellcode's offset from the block start address and add that to the shellcode's size – Only that guarantees that the whole shellcode is marked executable, while your omission of that offset means that it is possible that only a portion of the shellcode will be executable.
@NickSweeting On examining your code, I see that you placed the shellcode in the first block of heap memory allocated by malloc(). There's an extremely high chance that will be aligned to a very large boundary, which is why it works right now, but malloc() isn't guaranteed to align the memory block on 4KB; For that you'd need posix_memalign() or suchlike. I still advise you to add the offset and give yourself peace of mind.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.