!!! mit Delphi ab Version 4 !!!
May the source be with you, stranger
... |
GoPyright © 2001 by -=Assarbad
[GoP]=-
Wie der vorgebildete Leser sicherlich schon weiß, ist Windows NT ein Multiuser Betriebssystem, was bedeutet, daß allgemein ein Programm immer im Sicherheitskontext des jeweiligen Benutzers läuft.
Eine Außnahme ermöglichen diverse Programme nach dem Vorbild von SU (Switch User, ein UNIX Programm).
Nun ist es aber auch so, daß Windows NT die Möglichkeit bietet Services zu programmieren und zu installieren. Windows NT Services sind die Entsprechung zu den Daemons unter *NIXen ;) ... dementsprechend läuft z.B. Microsoft's Internet Information Server als Service. Der Beispiele existieren viele.
Das ist nicht so schwer wie man glauben mag. Man binde die WinSVC Unit in sein Projekt ein (das im Besten Falle ein Konsolenprogramm sein sollte, da nur interaktive Services mit dem Benutzer interagieren können.).
Wir gehen also erstmal von einem Konsolenprogramm aus.
program svc; uses windows, winsvc; const servicename = 'TESTSERVICE'; displayname = 'Assarbad''s testservice'; procedure SERVICE_MAIN; forward; {$INCLUDE consolehlp.pas} {$INCLUDE service.pas} procedure SERVICE_MAIN; begin repeat if not paused then begin end; until stopped; end; begin PROG_MAIN; end. |
Die Prozedur PROG_MAIN ist in SERVICE.PAS definiert und CONSOLEHLP.PAS enthält einige Hilfroutinen für Konsolenprogramme die zum Teil der CRT Unit von Turbo Pascal nachempfunden sind.
Da eine jede eingebundene Unit extra Overhead mit sich bringt (das entsteht durch die Compilertechnologie, die verschiedene Informationen bei Units zwischenspeichert, die dann nicht mehr entfernt werden) nutzen wir INCLUDE Dateien.
PROG_MAIN sieht wie folgt aus:
procedure PROG_MAIN; begin case paramcount of 0: startasservice; else begin GetModuleFileName(hInstance, @modname[0], MAX_PATH); Getlasterror; param := paramstr(1); case param[1] = '/' of true: begin currtextattr := textattribute; settextattribute(FOREGROUND_GREEN or FOREGROUND_INTENSITY); writeln(cmd_header); settextattribute(currtextattr); case param[2] of 'I', 'i': begin currtextattr := textattribute; settextattribute(FOREGROUND_BLUE or FOREGROUND_INTENSITY); StartupMode := SERVICE_DEMAND_START; if length(param) > 2 then case param[3] of 'A', 'a': startupMode := SERVICE_AUTO_START; end else StartupMode := SERVICE_DEMAND_START; case startupMode of SERVICE_AUTO_START: writeln(cmd_install + 'n autostart service'); SERVICE_DEMAND_START: writeln(cmd_install + ' manual start service'); end; settextattribute(currtextattr); hSCM := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS); case hSCM of 0: FatalError; else begin hService := CreateService(hSCM, PChar(ServiceName), PChar(DisplayName), SERVICE_START or SERVICE_QUERY_STATUS or _DELETE, SERVICE_WIN32_OWN_PROCESS, // or SERVICE_INTERACTIVE_PROCESS, StartupMode, SERVICE_ERROR_NORMAL, @modname[0], nil, nil, nil, nil, nil); case hService of 0: begin CloseServiceHandle(hSCM); FatalError; end; else begin CloseServiceHandle(hSCM); CloseServiceHandle(hService); writeln(frmt(cmd_installed, [pchar(servicename), pchar('')])); end; end; end; end; end; 'U', 'u': begin currtextattr := textattribute; settextattribute(FOREGROUND_BLUE or FOREGROUND_INTENSITY); writeln('Attempting to uninstall "' + servicename + '"'); settextattribute(currtextattr); hSCM := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS); case hSCM of 0: FatalError; else begin hService := OpenService(hSCM, PChar(Servicename), SERVICE_ALL_ACCESS); case hService of 0: begin CloseServiceHandle(hSCM); FatalError; end; else begin startupMode := integer(DeleteService(hService)); CloseServiceHandle(hService); CloseServiceHandle(hSCM); case startupMode of 0: FatalError; else writeln(frmt(cmd_installed, [pchar(servicename), pchar('un')])); end; //case end; end; //case end; end; //case end; else showsyntax; end; //case end; false: startasservice; end; end; end; end; |
Es überprüft die übergebenen Parameter und installiert/deinstalliert gegebenenfalls den Service, oder eben startet diesen als Service:
procedure startasservice; begin dispatchtable[0].lpservicename := pchar(servicename); dispatchtable[0].lpserviceproc := @serviceproc; dispatchtable[1].lpservicename := nil; dispatchtable[1].lpserviceproc := nil; startservicectrldispatcher(dispatchtable[0]); end; |
startservicectrldispatcher() übergibt den Hauptthread des Prozesses an den Service Control Manager (SCM).
Auffällig im Hauptprogramm ist noch die Prozedur SERVICE_MAIN. Sie enthält die eigentliche Hauptschleife des Services, wobei die Variablen STOPPED und PAUSED innerhalb des Programms vordefiniert sind.
Alles innerhalb dieser Prozedur wird also ausgeführt, während der Service als Service läuft.
Download des Beispielservices. (Service-Skelett)
Das eben erworbene Wissen läßt sich auf einfache Weise in die Ausnutzung einer Sicherheitslücke umwandeln. Unter der Annahme, jeder beliebige Benutzer könnte z.B. einen Service installieren, kann man das kommende Beispiel sicher als hohes Sicherheitsrisiko verbuchen!
Man nehme das Gerüst von oben und gestalte es ein wenig um. Schon hat man ein Programm, welches in der Lage ist z.B. eine Instanz des Kommandozeilenprozessors von NT - CMD - unter dem Sicherheitskontext des LocalSystem Kontos zu starten.
Die einzige Sache, die wir hier erstmal ändern wollen, ist die SERVICE_MAIN Prozedur und die Startparameter des Services. Da wie oben erwähnt nur interaktive Services mit dem Benutzerdesktop interagieren können, muß man den Service als interaktiven Service starten.
Wir ändern also:
hService := CreateService(hSCM, PChar(ServiceName), PChar(DisplayName), SERVICE_START or SERVICE_QUERY_STATUS or _DELETE, SERVICE_WIN32_OWN_PROCESS, // or SERVICE_INTERACTIVE_PROCESS, StartupMode, SERVICE_ERROR_NORMAL, @modname[0], nil, nil, nil, nil, nil); |
zu:
hService := CreateService(hSCM, PChar(ServiceName), PChar(DisplayName), SERVICE_START or SERVICE_QUERY_STATUS or _DELETE, SERVICE_WIN32_OWN_PROCESS or SERVICE_INTERACTIVE_PROCESS, StartupMode, SERVICE_ERROR_NORMAL, @modname[0], nil, nil, nil, nil, nil); |
Dadurch wird der Service als interaktiver Service festgelegt. Außerdem fällt das parsen der Parameter ob manuell oder automatisch weg.
Folgende Routine dient als ShellExecute()-Ersatz und startet den NT-Kommandoprozessor (CMD):
var si: TStartupInfo; pi: TProcessInformation; szCmd: array[0..MAX_PATH * 2] of char; begin ZeroMemory(@si, SizeOf(si)); si.cb := SizeOf(si); lstrcpy(szCmd, 'cmd.exe /X /K cd\ & mode con cols=80 lines=43 & cls & title CMD.EXE running on "LocalSystem" account'); if CreateProcess(nil, szCmd, nil, nil, False, CREATE_NEW_CONSOLE, nil, nil, si, pi) then begin CloseHandle(pi.hThread); CloseHandle(pi.hProcess); end; end; |
Genau analysiert (obwohl das eher nicht so relevant ist, startet die Routine die CMD.EXE, wechselt ins Hauptverzeichnis des Laufwerks, schaltet die Konsole auf 80x43 Zeichen um (statt normal 80x25) und setzt den Titel des Konsolenfensters auf CMD.EXE running on "LocalSystem" account.
Mit folgendem kleinen CMD-Skript läßt sich das Ganze dann so ausführen, daß man in den Genuß einer Konsole kommt, die im Sicherheitskontext des LocalSystem Kontos läuft und somit (nahezu) unbeschränketn Zugriff auf das System genießt.
ACHTUNG: das LocalSystem Konto hat kein eigenes Profil!
@echo off REM Install service CMD_svc /i REM Start service NET START CMD_SERVICE REM Uninstall service CMD_svc /u pause
Download des interaktiven Beispielservices.
PS: Zum ausprobieren empfehle ich aus der (LocalSystem-)Konsole heraus REGEDT32 und/oder REGEDIT zu starten. Man kann sich so HKEY_LOCAL_MACHINE\SAM\SAM anschauen, welcher sonst gesperrt ist ;)
PS: Für weitere Beispiele zu NT Programmierung / nonVCL:
-=Assarbad
[GoP]=- Mail an den Autor Die Weiterverbreitung in unveränderter Form ist ausdrücklich erlaubt. Eine modifizierte Version bedarf meiner Zustimmung. Bitte dazu Kontakt über obige Mail-Adresse aufnehmen. |