0

I've created a DLL and would like to execute one of the functions using the rundll32.exe command on windows.

Using rundll32.exe, it runs correctly from the command line; however, I'd like to call it (rundll32.exe) from a separate program. I cannot directly call the function from my code due to 32/64 bit compatibility issues in the underlying libraries I'm using (Easyhook).

Below is what I'm using in an attempt to run the dll function:

STARTUPINFO si;
PROCESS_INFORMATION pi;

ZeroMemory( &si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi));

LPCTSTR application = "C:\\Windows\\system32\\rundll32.exe";
LPTSTR cmd = "C:\\Projects\\Test\\mydll.dll,MyFunc";

BOOL cpRes = CreateProcess(application,
  cmd,
  NULL,
  NULL,
  FALSE,
  0,
  NULL,
  NULL,
  &si,
  &pi);

if(cpRes == 0) {
  cout << "ERROR\n";
  cout << GetLastError() << endl;
} else {
  cout << "DLL Launched!" << endl;
}

CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

The output to my console is always DLL Launched; however, I do not see the effects of my DLL actually being called (currently stubbed out in such a way that the command writes to a file).

If I swap out the application with something such as C:\\Windows\\system32\\notepad.exe, the program successfully runs.

For completion, here's the body of MyFunc:

ofstream file;
file.open("C:\\Projects\\Test\\test.txt");
file << "I wrote to a file!";
file.close();

Is there any reason CreateProcess cannot be used with rundll32? While reading over this I found several warnings about LoadLibrary() and DLLMain but it doesn't seem like they're relevant to this.


More Clarification:
This is currently a 32-bit application (allegedly) launching the 32-bit rundll32.exe (Logic will be added later to call the 32 or 64 bit version).

My dll is as follows:

extern "C" __declspec(dllexport) void CALLBACK MyFunc(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow);

void CALLBACK MyFunc(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow) { ... }

Which also has a .def file with:

EXPORTS
  MyFunc

Running

C:\Windows\system32\rundll32.exe C:\Projects\Test\mydll.dll,MyFunc 

produces the expected results.


Update
Setting application to NULL and including the rundll32.exe in cmd as mentioned in the comments seems to work.

Relevant Docs:
CreateProcess
RunDll32.exe

user5786682
  • 11
  • 2
  • 7
  • What is the output of `cout << GetLastError() << endl;` when DLL is not launched properly? – Naseef Chowdhury Sep 26 '17 at 19:47
  • @NaseefUrRahman The output is [0](https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx). – user5786682 Sep 26 '17 at 19:50
  • What is the exact signature of the function you're trying to call from the DLL? – Peter Ruderman Sep 26 '17 at 19:52
  • Does `rundll32` bitness match your dll bitness? – user7860670 Sep 26 '17 at 19:52
  • exec `rundll32.exe mydll.dll,MyFunc` direct under debugger and view what is going wrong – RbMm Sep 26 '17 at 19:59
  • @RbMm It exports correctly as MyFunc for 32 bit as seen when I use DependencyWalker and simply running it from the command line. – user5786682 Sep 26 '17 at 20:15
  • @user5786682 - did not notice def file. yes, in this case will de undecorated name (but for what you use also `__declspec(dllexport)` in this case ?). anyway you need in debugger run run32dll with you command line and look – RbMm Sep 26 '17 at 20:26
  • Are you sure the DLL is not actually being called? Maybe the DLL is having trouble accessing its output file instead. I would suggest changing the DLL function to do something a bit more noticeable, llke calling `OutputDebugString()` (which you can view using [SysInternals DebugView](https://learn.microsoft.com/en-us/sysinternals/downloads/debugview)) or `MessageBox()` – Remy Lebeau Sep 26 '17 at 20:27
  • 3
    use `LPTSTR cmd = "rundll32.exe C:\\Projects\\Test\\mydll.dll,MyFunc";` as command line – RbMm Sep 26 '17 at 20:27
  • @RbMM It's very possible I do not need __declspec(ddlexport) This is my first attempt at a DLL and I had read that I needed it. I'll see what I can find with a debugger. – user5786682 Sep 26 '17 at 20:27
  • change the command line - add exe name (without path) in the front – RbMm Sep 26 '17 at 20:28
  • @RbMm: if you do this, it is better to get rid of the `application` parameter altogether, just pass NULL to it, and then specify the full application path in the `cmd` parameter: `TCHAR cmd[] = "C:\\Windows\\system32\\rundll32.exe C:\\Projects\\Test\\mydll.dll,MyFunc"; CreateProcess(NULL, cmd, ...);` – Remy Lebeau Sep 26 '17 at 20:30
  • @RemyLebeau - use application parameter is good and not need set to 0. however some program, when parsing command line assume that first token is exe name. the presense of applicationname not change this – RbMm Sep 26 '17 at 20:32
  • @RbMm: I'm well aware of that. But if you set `application` to NULL, `CreateProcess()` handles passing the module name in the first command line argument for you. This is [documented behavior](https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425.aspx). – Remy Lebeau Sep 26 '17 at 20:34
  • 1
    @RemyLebeau - the command line always passed to child as is, how we set it in call. use or not use *lpApplicationName* not affect this. simply if we use *lpApplicationName* it is used. if 0 here - api get it from command line – RbMm Sep 26 '17 at 20:36
  • @RbMm Switching it to just use the cmd line arguments seems to work! – user5786682 Sep 26 '17 at 20:36
  • 2
    PS: [What’s the guidance on when to use rundll32? Easy: Don’t use it](https://blogs.msdn.microsoft.com/oldnewthing/20130104-00/?p=5643) – Harry Johnston Sep 26 '17 at 21:26

1 Answers1

3

Per the CreateProcess() documentation:

If both lpApplicationName and lpCommandLine are non-NULL, the null-terminated string pointed to by lpApplicationName specifies the module to execute, and the null-terminated string pointed to by lpCommandLine specifies the command line. The new process can use GetCommandLine to retrieve the entire command line. Console processes written in C can use the argc and argv arguments to parse the command line. Because argv[0] is the module name, C programmers generally repeat the module name as the first token in the command line.

You are not repeating rundll32.exe as the first command-line token.

So, if you continue using the lpApplicationName parameter, then change this:

LPCTSTR application = "C:\\Windows\\system32\\rundll32.exe";
LPTSTR cmd = "C:\\Projects\\Test\\mydll.dll,MyFunc";

To this instead:

LPCTSTR application = TEXT("C:\\Windows\\system32\\rundll32.exe");
LPTSTR cmd = TEXT("C:\\Windows\\system32\\rundll32.exe C:\\Projects\\Test\\mydll.dll,MyFunc");

Note that you are currently compiling for ANSI/MBCS (by virtue of the fact that you are passing narrow strings to CreateProcess()). If you ever update the project to compile for Unicode, use this instead:

TCHAR cmd[] = TEXT("C:\\Windows\\system32\\rundll32.exe C:\\Projects\\Test\\mydll.dll,MyFunc");

This is because the documentation states:

lpCommandLine [in, out, optional]
...
The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.

You might consider changing cmd into a TCHAR[] array anyway, even in ANSI/MBCS, so you can do something like this:

LPCTSTR application = TEXT("C:\\Windows\\system32\\rundll32.exe");

TCHAR cmd[(MAX_PATH*2)+10];
wsprintf(cmd, TEXT("%s %s,%s"), application, TEXT("C:\\Projects\\Test\\mydll.dll"), TEXT("MyFunc"));

Either way, by passing the module filename as the first token in the lpCommandLine parameter, you can then set the lpApplicationName parameter to NULL:

The lpApplicationName parameter can be NULL. In that case, the module name must be the first white space–delimited token in the lpCommandLine string.

Let CreateProcess() setup the correct command-line to pass to rundll32.exe for you:

TCHAR cmd[] = TEXT("C:\\Windows\\system32\\rundll32.exe C:\\Projects\\Test\\mydll.dll,MyFunc");

BOOL cpRes = CreateProcess(NULL, cmd, ...);
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    if we use *lpApplicationName* (!=0) we can add *any* token to *lpCommandLine*. not mandatory full path. even not name. any string(without space is ok). so say `@ C:\\Projects\\Test\\mydll.dll,MyFunc` will be work too. and if we *have* exactly *lpApplicationName* always better use pass it to `CreateProcess` – RbMm Sep 26 '17 at 20:52
  • 1
    Good answer overall, but if you're going to suggest using `TCHAR cmd[] = ...`, then you should also use the `TEXT` macro around the string literal, as in `TCHAR cmd[] = TEXT("C:\\Windows....\\mydll.dll,MyFunc");`. The alternative is to explicitly use `WCHAR` and the `L` prefix. – Adrian McCarthy Sep 26 '17 at 21:11
  • @RbMm by convention, `argv[0]` is expected to be a FULL path to the calling process. Sure, you can pass just the module name by itself, but you would break any console app that needs a full path. Maybe `rundll32` doesn't. Don't risk it. – Remy Lebeau Sep 26 '17 at 22:39
  • also if pass path or even name in command line, need enclose it in "", for case when path or name containing space. – RbMm Sep 26 '17 at 23:35