Icon Ressource

[C/WinAPI] Rundll32.exe und Ausführung von (eigenen) Libraryfunktionen

Rundll32.exe ist ein Hostprogramm, mit dem es möglich ist Funktionen einer DLL aufzurufen. Das wird auch immer wieder mit allen möglichen Funktionen versucht. Meist nach dem "Try And Error" Prinzip, da kaum jemand verstanden hat, was das "Rundll32 can only call functions from a DLL explicitly written to be called by Rundll32" auf der Microsoft Manpage bedeutet. Ich schreibe hier keine neue Liste mit allen möglichen Rundll32 Aufrufen unterschiedlichster Funktionen. Davon gibt es wirklich schon ausreichend und viele von solchen Aufrufen beruhen auf undefiniertem Verhalten.

Hier geht es also eher um zwei Aspekte:
  1. Woher weiß ich, welche Libraryfunktionen mit Rundll32 aufrufbar sind?
  2. Wie kann ich Funktionen selbst schreiben die mit Rundll32 aufrufbar sind? (Beispielsweise um ein zusätzliches Kommandozeileninterface zur Verfügung zu stellen, das eigene API Funktionen wrappt.)


Fangen wir mal mit einer Kommandozeile an, wie sie beispielhaft für die Syntax von Rundll32 Aufrufen steht:
Rundll32 Libname.dll,Funktionsname Argument(optional)

Was macht Rundll32 nun daraus?
Ganz grob:
  • LoadLibrary wird aufgerufen um die DLL zu laden.
  • GetProcAddress wird aufgerufen um den Pointer auf die Funktion zu bekommen.
  • Die Funktion wird mit 4 Argumenten aufgerufen.
  • FreeLibrary wird aufgerufen.

Punkt 3 ist hier von Interesse. Rundll32 ist nicht in der Lage die geforderte Deklaration der Funktion zu ermitteln. Hier kommen wir zum "explicitly written to be called by Rundll32". Rundll32 geht grundsätzlich davon aus, einen Funktionspointer folgenden Typs zu bekommen:
void (__stdcall *)(HWND, HINSTANCE, LPWSTR, int)
Daraus folgt, alle Funktionen die mit Rundll32 aufgerufen werden und nicht dieser Spezifikation entsprechen, erzeugen undefiniertes Verhalten.

Schauen wir uns die 4 Parameter genauer an:
  • HWND Window Handle der Rundll32 Instanz (Wert wird von Rundll32 ermittelt, Rundll32 ist keine Consoleanwendung und hat kein sichtbares Fenster)
  • HINSTANCE Handle der Programminstanz von Rundll32 (Wert wird von Rundll32 ermittelt)
  • LPWSTR Argument als String
  • int Fensterstyle (Wert ist 10 aka SW_SHOWDEFAULT, siehe ShowWindow Funktionsreferenz)

Wie zu sehen ist, sind die Argumente für Parameter 1, 2 und 4 nicht zu beeinflussen. Parameter 3 bekommt 1:1 den String der oben in der Kommandozeile als "Argument(optional)" beschrieben ist. Der String beginnt beim ersten Zeichen nach "Funktionsname", das kein Whitespace ist. Wird kein Argument übergeben, so bekommt der 3. Parameter einen String der Länge Null zugewiesen.

Zur Veranschaulichung bauen wir uns eine eigene DLL, "rundll32_testlib.dll". Diese muss als C-Code kompiliert werden. Wegen des Name-Manglings und der Weise und Reihenfolge wie Rundll32 nach Funtionen sucht die nicht exakt dem angegebenen Name entsprechen, würden C++ Funktionen nicht gefunden werden.
rundll32_testlib.c:
#include <windows.h>
#include <shellapi.h>
#include <stdio.h>
#include <wchar.h>
#include <errno.h>
#include <math.h>
#include <io.h>

__declspec(dllexport)
void __stdcall rundll32_testfuncW(HWND hWnd, HINSTANCE hInstance, LPWSTR arg, int show)
{
  wchar_t buffer[BUFSIZ] = { 0 };
  swprintf_s(buffer, BUFSIZ, L"hWnd\t\t %p\nhInstance\t %p\narg\t\t %s\nshow\t\t %d",
             (void*)hWnd, (void*)hInstance, arg, show);

  MessageBoxW(NULL, buffer, L"* rundll32_testfuncW *", MB_ICONINFORMATION | MB_SYSTEMMODAL);
}

__declspec(dllexport)
void __stdcall rundll32_parseargsW(HWND hWnd, HINSTANCE hInstance, LPWSTR arg, int show)
{
  int argc = 0;
  wchar_t **argv = CommandLineToArgvW(arg, &argc);
  for (int i = 0; i < argc; ++i)
    MessageBoxW(NULL, argv[i], L"# rundll32_parseargsW #", MB_ICONINFORMATION | MB_SYSTEMMODAL);

  LocalFree(argv);
}

__declspec(dllexport)
int __cdecl rundll32_faketestW(int a, int b)
{
  wchar_t buffer[BUFSIZ] = { 0 };
  swprintf_s(buffer, BUFSIZ, L"a\t\t %08X\nb\t\t %08X\n", a, b);
  MessageBoxW(NULL, buffer, L"~ rundll32_faketestW ~", MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
  return 0;
}

__declspec(dllexport)
void __stdcall rundll32_sinW(HWND hWnd, HINSTANCE hInstance, LPWSTR arg, int show)
{
  // Tokenizing. Eigentlich für nur ein Argument nicht zwingend notwendig,
  // dient aber der Fehlerfindung und entfernt umschließende Anführungszeichen.
  int argc = 0;
  wchar_t **argv = CommandLineToArgvW(arg, &argc);
  if (argc != 1)
  {
    LocalFree(argv);
    return;
  }

  // Konvertierung zum double.
  wchar_t *endptr = NULL;
  errno = 0;
  double num = wcstod(argv[0], &endptr);
  LocalFree(argv);
  if (errno || *endptr)
    return;

  // Da Rundll32 keine Konsoleanwendung ist, muss das Konsolefenster des aufrufenden Prozesses angehängt werden.
  if (!AttachConsole(ATTACH_PARENT_PROCESS))
    return;

  // Nach dem AttachConsole Aufruf bleiben Umleitungen von stdout an eine Datei oder eine Pipe valide.
  // Das stdout ist aber nicht automatisch mit dem Console Host verknüpft, falls keine Umleitung aktiv ist.
  // _fileno(stdout) gibt in diesem Fall -2 statt 1 zurück und muss erst mit dem Console Host verknüpft werden.
  if (_fileno(stdout) < 0)
    freopen("CONOUT$", "w", stdout);

  // Eigentlicher Aufruf der sin() Funktion und Ausgabe des Ergebnisses zum stdout.
  wprintf(L"%f\n", sin(num));
}


Die rundll32_testfuncW Funktion gibt einfach nur die Werte aus, die übergeben wurden.
Die rundll32_parseargsW Funktion zeigt, wie mit dem 3. Parameter umzugehen ist, wenn man ein Tokenizing vornehmen will.
Die rundll32_faketestW Funktion erzeugt undefiniertes Verhalten bei einem Aufruf mit Rundll32.
Die rundll32_sinW Funktion gibt ein Beispiel wie eine Libraryfunktion gewrappt werden kann (sin() in diesem Fall) und was nötig ist um das Ergebnis in ein Konsolefenster auszugeben.


Nun legen wir folgendes Batchscript zu unserer "rundll32_testlib.dll":

test.bat:
@echo off &setlocal
rundll32 rundll32_testlib.dll,rundll32_testfunc foo (bar, "foobar")
rundll32 rundll32_testlib.dll,rundll32_parseargs 4429763.1234567890 "a b c d e f g h i j k l m n o p"
rundll32 rundll32_testlib.dll,rundll32_faketest 4711 -815
rundll32 rundll32_testlib.dll,rundll32_sin 1.5707963267948966
pause

Was vermutlich sofort auffällt ist, dass die Funktionsnamen in der Lib den Suffix W haben, während sie in der Kommandozeile ohne Suffix benannt sind. Nehmen wir an unsere Lib enthält drei Funktionen, "func", "funcA" und "funcW". Wenn wir im Aufruf von Rundll32 "func" definieren, dann wird in der Lib in folgender Reihenfolge gesucht:
  1. Existiert eine Funktion mit Name "funcW", so wird diese herangezogen. Der 3. Parameter wird als Typ LPWSTR (aka wchar_t*) angenommen.
  2. Existiert eine Funktion mit Name "funcA", so wird diese herangezogen. Der 3. Parameter wird als Typ LPSTR (aka char*) angenommen.
  3. Es wird die Funktion mit Name "func" herangezogen. Der 3. Parameter wird als Typ LPSTR (aka char*) angenommen.
Wer unter Windows außerhalb von Konsoleanwendungen mit Strings hantiert, wird feststellen, dass Wide Strings zu bevorzugen sind. Windows arbeitet intern mit UTF-16. So ziemlich alle ...A Versionen der API Funktionen wrappen lediglich die ...W Versionen, indem vorab eine Stringkonvertierung vorgenommen wird. Einige Funktionen haben nicht einmal eine ...A Alternative, wie bspw. CommandLineToArgvW aus dem obigen Code.



Was bekommen wir, wenn das Batchscript gestartet wird.

rundll32_testfunc zeigt die Werte der 4 Parameter.
1593267394016.png

Interssant ist hier der 3. Parameter. Alles was als optionales Argument an Rundll32 übergeben wurde, kommt als einzelner String an. Mit allen Leerzeichen, Klammern, Anführungszeichen usw.



rundll32_parseargs nutzt CommandLineToArgvW um den 3. Parameter in Tokens zu zerlegen. Die einzelnen Tokens sind aber immer noch Strings. Wer aus dem 4429763.1234567890 tatsächlich eine Fließkommazahl machen will, benötig eine entsprechende Konvertierung. Z.B. mittels wcstod.
1593267483492.png
1593267532030.png




rundll32_faketest erzeugt undefiniertes Verhalten. Nichts entspricht der von Rundll32 erwarteten Spezifikation. Nicht der Rückgabewert, nicht die Aufrufkonvention und nicht die Parameter.
1593267613040.png

Wer also erwartet, dass 4711 und -815 als numerische Werte an die Parameter a und b übergeben werden, wird enttäuscht. Ich habe hier auch eine Ausgabe in HEX gewählt, um den Vergleich mit dem ersten Beispiel ziehen zu können. Offenbar bekommt also Parameter a den Wert des HWND und Parameter b den Wert des HINSTANCE, narrowed zur Breite eines int. Aber wie gesagt, das ist undefiniertes Verhalten und nur eine Momentaufnahme auf meinem Rechner. Ebenso gut könnte der Code auf der Kiste vom Kim eine nordkoreanische Rakete Richtung Trumps Schlafzimmer abschießen (was mich auf merkwürdige Ideen bringt :ROFLMAO:) ...



rundll32_sin konvertiert das übergebene Argument (π/2) zu einem double und gibt dessen Sinus in das Konsolefenster aus.
1593447801147.png






Last but not least ...
Nachdem ich nun geschrieben habe, wie man mit Rundll32 umgehen kann, würde ich vermutlich einige Leser enttäuschen, die gehofft haben eine Lösung für Funktionen zu finden, bei denen sie mit Rundll32 nicht erfolgreich gewesen sind. Auch wenn das etwas off-topic in dieser Ressource ist, so gibt es natürlich auch andere Möglichkeiten als Rundll32 zu nutzen. Platform Invokation in einem PowerShell Script wäre das was unter Windows ohne die Nutzung von 3rd Party Tools möglich ist. Und natürlich lässt sich ein PowerShell Aufruf auch in eine Kommandozeile packen. Für unsere rundll32_faketest Funktion könnte das so aussehen:
Batch:
powershell -nop -ep Bypass -c "$c=Add-Type -Name pInv -PassThru -MemberDefinition '[DllImport(\"rundll32_testlib.dll\", CharSet=CharSet.Unicode, CallingConvention=CallingConvention.Cdecl)] public static extern int rundll32_faketest(int a, int b);'; $null=$c::rundll32_faketest(3, 6);"

et voilà
1593448622313.png
  • Like
Reaktionen: Rushh0ur und Mat
Autor
german
Aufrufe
2.529
Erstellt am
Letzte Bearbeitung
Bewertung
0,00 Stern(e) 0 Bewertung(en)

Weitere Ressourcen von german

Letzte Aktualisierungen

  1. Erweiterung der Beispiele

    DLL Funktion hinzu die zeigt wie API Funktionen zu wrappen sind. Beispielhafte PowerShell...
Zurück
Oben Unten