1

I'm writing a small tool that should be able to inspect an arbitrary process of interest and check if any of its statically linked functions were trampolined. (An example of a trampoline could be what Microsoft Detours does to a process.)

For that I parse the PE header of the target process and retrieve all of its imported DLLs with all imported functions in them. Then I can compare the following between DLLs on disk and the DLLs loaded in the target process memory:

A. Entries in the Import Address Table for each imported function.

B. First N bytes of each function's machine code.

And if any of the above do not match, this will most certainly mean that a trampoline was applied to a particular function (or WinAPI.)

This works well, except of one situation when a target process can import a global variable instead of a function. For example _acmdln is such global variable. You can still find it in msvcrt.dll and use it as such:

//I'm not sure why you'd want to do it this way,
//but it will give you the current command line.
//So just to prove the concept ...
HMODULE hMod = ::GetModuleHandle(L"msvcrt.dll");
char* pVar = (char*)::GetProcAddress(hMod, "_acmdln");
char* pCmdLine = pVar ? *(char**)pVar : NULL;

So, what this means for my trampoline checking tool is that I need to differentiate between an imported function (WinAPI) and a global variable. Any idea how?

PS. If I don't do that, my algorithm that I described above will compare a global variable's "code bytes" as if it was a function, which is just a pointer to a command line that will most certainly be different, and then flag it as a trampolined function.

PS2. Not exactly my code, but a similar way to parse PE header can be found here. (Search for DumpImports function for extracting DLL imports.)

c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • 1
    Probably not possible 100%, but checking whether the memory is executable or not should be close. – David Heffernan Dec 20 '17 at 09:00
  • @DavidHeffernan: Thanks. That was my thought too. I was hoping though that there was something in the PE header itself that could tell them apart, that I overlooked. – c00000fd Dec 20 '17 at 18:40
  • It should be fairly reliable to map the memory sections of each target DLL and range check your pointers against these ranges. Global variables will likely reside in the ".data" section or equivalent. Also as @DavidHeffernan pointed out, you can test these section characteristics (flags such as read/write/execute) rather than hardcoding section names. I can't imagine compiler generated code placing exported global variables in any code section. Quickly looking at the MSVC 10's CRT DLL, I happened to have found that _acmdln is indeed in the ".data" section range. – byteptr Jan 04 '18 at 15:21

1 Answers1

0

Global variables will be in the .data section not the .text section, in addition the section will not have execute permissions if it's not a function. Therefore you can use both of these characteristics to filter.

GuidedHacking
  • 3,628
  • 1
  • 9
  • 59