Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can calling a function pointer saved from a previous execution fail?

I was curious if function pointers could be stored in a file and used at some future point in time when the program had exited and started again. For example, my first test program looked something like this pseudocode:

void f(){}

typedef void(*Fptr)();

int main() {
    int i;
    cin >> i;
    if (i == 1) {
        std::ofstream out(/**/);
        out << &f;
    }
    else {
        std::ifstream in(/**/);
        Fptr fp;
        in >> fp;
        fp();
    }
}

That is just the logic of what I wanted to do. I would launch it with input 1, let it exit, and run it again with input 2. Don't consider that to be my real code, as I erased the original test because...

That only worked if I don't change the directory that the executable is in!

Adding a new file to the directory (presumably removing a file too) and moving the executable somewhere new would all cause fp(); to crash. The new function address would be a different value.

So I made a new test which calculates the difference between an old function pointer and a current function address. Applying that offset to an old function pointer and calling it yields the correct function call, regardless of what I do to the directory.

I am confident this is UB. However, like de-referencing a null pointer will cause a segfault, the UB is pretty consistent.

Aside from rewriting the data with garbage, and assuming that the functions aren't loaded in a DLL, how likely is this method to succeed? In what ways will it still fail to work?

like image 581
Weak to Enuma Elish Avatar asked Nov 06 '15 07:11

Weak to Enuma Elish


1 Answers

As others mentioned, this problem is caused by the "Address Space Layout Randomization" (ASLR). This randomization is done for each module (i.e, each executable image). This means that if all your functions are contained in your .exe, they are guaranteed to to always have the same offset from the base of the module. If some function are in a DLL, the same apply, but from the base of the DLL module. It is important that the relative module addresses stay the same, because otherwise it wouldn't be possible to locate entry points and DLL functions.

On a Windows environment :

In Visual Studio (and MSVC), ASLR is ON by default, but you can disable it in the "Linker > Advanced > Randomized Base Address" option (/DYNAMICBASE:NO in command line). By disabling this option, the functions will always be at the same address.

You can also determine the offset at runtime. The module base address can be obtained with GetModuleHandle() (the module handle is in fact the base address). With this, you can work with relative addresses for your pointers.

uintptr_t base_address = (uintptr_t)GetModuleHandle(NULL);

uintptr_t offset = (uintptr_t)&f - base_address;
out << offset;

in >> offset;
fp = (Fptr)(offset + base_address);
fp();
like image 191
ElderBug Avatar answered Oct 06 '22 00:10

ElderBug