Выход за границы контейнера Modern-приложений Windows 8

от автора

Ещё в операционной системе Windows Vista компания Microsoft добавила средство создания «песочниц» — так называемые Integrity Levels: Untrusted < Low < Medium < High < System. Всё в операционной системе (файлы, ветки реестра, объекты синхронизации, пайпы, процессы, потоки) имеет свой Integrity Level. Процесс, имеющий, к примеру Low Integrity Level не может открыть файл с диска, имеющий Medium Integrity Level (уровень по-умолчанию).

Именно на этом механизме работают UAC и «Run as administrator», повышая Integrity Level запускаемого процесса. Именно на этой технологии работает песочница в Google Chrome: все процессы вкладок имеют самый низкий Integrity Level — Untrusted, что делает невозможным взаимодействие процесса вообще ни с какими файлами, процессами, ветками реестра и т.д.

Этот одна из сильных сторон безопасности Хрома — ведь даже найдя в нём какой-нибудь stack overflow вы упрётесь в систему безопасности ОС, которая не даст выйти за границы процесса. К стати, сама Microsoft такой механизм организации песочниц для браузера применила лишь 4 года спустя в Win8.1 + IE 11 (было в выключенном состоянии в Win8 + IE 10 — но кто же пойдёт это искать и включать, так что не считается).

С выходом Windows 8 компании Microsoft понадобилось сделать механизм изоляции Modern-приложений, аналогичный применяемым в других мобильных ОС. Нужно было дать понять как пользователю, так и разработчику, что программа из магазина никак не достанет приватные данные юзера без его согласия, никак не сломает его систему и не нарушит работу других приложений даже при собственном крахе. Для реализации этой идеи был снова использован механизм Integrity Levels. Microsoft придумала такую штуку как «AppContainer». Читая доки в Интернете и даже глядя на описание процессов в Process Explorer, можно подумать, что AppContainer — это ещё один Integrity Level. Правда, непонятно где он — между Low и Medium? Между Untrusted и Low? Что тут можно сказать: и доки в Интернете и утилита Process Explorer — врут. Я себе не представляю как это маркетологи должны были задурить голову программистами, чтобы поля данных из официальных структур отображались намеренно неверно, но так оно и есть.

Правильное положение дел показывает сторонняя утилита ProcessHacker. Как мы видим из неё, AppContainer — это не новый Integrity Level. Это всего-лишь специальная метка, которая добавляется к работающему в общем-то под Low Integrity Level процессу. При этом эта метка уникальна для каждого приложения и используется как дополнительный барьер, ограничивая доступ не только к приложениям с более высокими Integrity Levels, но даже между процессами с Low Integrity Levels, но разными AppContainer-метками.

До этого момента всё было ещё более или менее логично. А вот отсюда начинается мракобесие.

Мракобесие

Почему-то данное поведение по-умолчанию многими (в том числе работниками Microsoft) называется единственно возможным. Вот к примеру исследование Mozilla, которым Microsoft заявил, что они не могут использовать пайпы между modern-приложением и обычным десктопным. Вот вопрос на Stackoverflow с тем же ответом от работника Microsoft. Вот доклад на конференции BUILD, где опять-таки работник Microsoft дважды (на 47:20 и 55:00) заявляет, что невозможно создать комплекс из backend в десктопном приложении и frontend в Modern-части. И ещё раз о том же в блоге на MSDN — снова от работника Microsoft.

Всё это ерунда, дефолтное поведение можно изменить и из приложения в AppContainer мы можем общаться (через пайпы, memory-mapped файлы, мьютексы и т.д.) с приложениями под любыми более высокими Integrity Levels.

Спойлер

Сразу скажу, что тут не будет никаких «0-day exploit» и «вау, мы взломали Windows!». Всё, о чём я буду говорить, есть в MSDN, является официальным и работает под Win8 и Win 8.1. Просто как-то его трудно найти плюс из-за моря очевидной дезинформации по ссылкам выше это всё даже никто не ищет.

Суть

Да, приложение в AppContainer (кстати, это не только Modern-приложения, но и тот же десктопный IE 11) не может само выйти за границу своего контейнера, ему не доступны глобальные именованные объекты операционной системы, оно не может открыть файл с диска (кроме своей папки). Но давайте вспомним нашу задачу — связать десктопное приложение, работающее под как минимум Medium Integrity Level с приложением в AppContainer. И вот здесь оказывается, что приложение может создать именованный объект (который по-умолчанию унаследует Integrity Level процесса) и дать доступ к нему приложениям с более низкими Integrity Levels. Более того, на этот объект мы можем даже навесить метку определенного AppContainer’а или даже всех AppContainer’ов в системе, что сделает возможным доступ к этому объекту из этих процессов.

Можно провести аналогию с заключённым в камере, который не может сам из неё выйти, но вот охранник, находящийся вне камеры, вполне может по своей воле с ним общаться или даже дать доступ к той части помещений тюрьмы, куда может попасть сам.

Отправной точкой для нас будет код из вот этой статьи в MSDN:

Код

#pragma comment(lib, "advapi32.lib") #include <windows.h> #include <stdio.h> #include <aclapi.h> #include <tchar.h>  int main(void) { BOOL GetLogonSid (HANDLE hToken, PSID *ppsid)  {     BOOL bSuccess = FALSE;     DWORD dwLength = 0;     PTOKEN_GROUPS ptg = NULL;      // Verify the parameter passed in is not NULL.     if (NULL == ppsid)         goto Cleanup;      // Get required buffer size and allocate the TOKEN_GROUPS buffer.      if (!GetTokenInformation(             hToken,         // handle to the access token             TokenLogonSid,    // get information about the token's groups              (LPVOID) ptg,   // pointer to TOKEN_GROUPS buffer             0,              // size of buffer             &dwLength       // receives required buffer size         ))      {         if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)              goto Cleanup;          ptg = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(),                                     HEAP_ZERO_MEMORY, dwLength);          if (ptg == NULL)             goto Cleanup;     }      // Get the token group information from the access token.      if (!GetTokenInformation(             hToken,         // handle to the access token             TokenLogonSid,    // get information about the token's groups              (LPVOID) ptg,   // pointer to TOKEN_GROUPS buffer             dwLength,       // size of buffer             &dwLength       // receives required buffer size             ) || ptg->GroupCount != 1)      {         goto Cleanup;     }      // Found the logon SID; make a copy of it.      dwLength = GetLengthSid(ptg->Groups[0].Sid);     *ppsid = (PSID) HeapAlloc(GetProcessHeap(),                 HEAP_ZERO_MEMORY, dwLength);     if (*ppsid == NULL)         goto Cleanup;     if (!CopySid(dwLength, *ppsid, ptg->Groups[0].Sid))      {         HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid);         goto Cleanup;     }     bSuccess = TRUE;  Cleanup:       // Free the buffer for the token groups.      if (ptg != NULL)         HeapFree(GetProcessHeap(), 0, (LPVOID)ptg);      return bSuccess; }  BOOL CreateObjectSecurityDescriptor(PSID pLogonSid, PSECURITY_DESCRIPTOR* ppSD) {     BOOL bSuccess = FALSE;     DWORD dwRes;     PSID pAllAppsSID = NULL;     PACL pACL = NULL;     PSECURITY_DESCRIPTOR pSD = NULL;     EXPLICIT_ACCESS ea[2];     SID_IDENTIFIER_AUTHORITY ApplicationAuthority = SECURITY_APP_PACKAGE_AUTHORITY;      // Create a well-known SID for the all appcontainers group.     if(!AllocateAndInitializeSid(&ApplicationAuthority,              SECURITY_BUILTIN_APP_PACKAGE_RID_COUNT,             SECURITY_APP_PACKAGE_BASE_RID,             SECURITY_BUILTIN_PACKAGE_ANY_PACKAGE,             0, 0, 0, 0, 0, 0,             &pAllAppsSID))     {         wprintf(L"AllocateAndInitializeSid Error %u\n", GetLastError());         goto Cleanup;     }      // Initialize an EXPLICIT_ACCESS structure for an ACE.     // The ACE will allow LogonSid generic all access     ZeroMemory(&ea, 2 * sizeof(EXPLICIT_ACCESS));     ea[0].grfAccessPermissions = STANDARD_RIGHTS_ALL | MUTEX_ALL_ACCESS;     ea[0].grfAccessMode = SET_ACCESS;     ea[0].grfInheritance= NO_INHERITANCE;     ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;     ea[0].Trustee.TrusteeType = TRUSTEE_IS_USER;     ea[0].Trustee.ptstrName  = (LPTSTR) pLogonSid;      // Initialize an EXPLICIT_ACCESS structure for an ACE.     // The ACE will allow the all appcontainers execute permission     ea[1].grfAccessPermissions = STANDARD_RIGHTS_READ | STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE | MUTEX_MODIFY_STATE;     ea[1].grfAccessMode = SET_ACCESS;     ea[1].grfInheritance= NO_INHERITANCE;     ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;     ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;     ea[1].Trustee.ptstrName  = (LPTSTR) pAllAppsSID;      // Create a new ACL that contains the new ACEs.     dwRes = SetEntriesInAcl(2, ea, NULL, &pACL);     if (ERROR_SUCCESS != dwRes)      {         wprintf(L"SetEntriesInAcl Error %u\n", GetLastError());         goto Cleanup;     }      // Initialize a security descriptor.       pSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR,                               SECURITY_DESCRIPTOR_MIN_LENGTH);      if (NULL == pSD)      {          wprintf(L"LocalAlloc Error %u\n", GetLastError());         goto Cleanup;      }        if (!InitializeSecurityDescriptor(pSD,             SECURITY_DESCRIPTOR_REVISION))      {           wprintf(L"InitializeSecurityDescriptor Error %u\n",                                 GetLastError());         goto Cleanup;      }        // Add the ACL to the security descriptor.      if (!SetSecurityDescriptorDacl(pSD,              TRUE,     // bDaclPresent flag                pACL,              FALSE))   // not a default DACL      {           wprintf(L"SetSecurityDescriptorDacl Error %u\n",                 GetLastError());         goto Cleanup;      }       *ppSD = pSD;     pSD = NULL;     bSuccess = TRUE; Cleanup:      if (pAllAppsSID)          FreeSid(pAllAppsSID);     if (pACL)          LocalFree(pACL);     if (pSD)          LocalFree(pSD);      return bSuccess; }  �     PSID pLogonSid = NULL;     PSECURITY_DESCRIPTOR pSd = NULL;     SECURITY_ATTRIBUTES  SecurityAttributes;     HANDLE hToken = NULL;     HANDLE hMutex = NULL;  �     //Allowing LogonSid and all appcontainers.      if (GetLogonSid(hToken, &pLogonSid) && CreateObjectSecurityDescriptor(pLogonSid, &pSd) )     {         SecurityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);         SecurityAttributes.bInheritHandle = TRUE;         SecurityAttributes.lpSecurityDescriptor = pSd;          hMutex = CreateMutex(                      &SecurityAttributes,         // default security descriptor                     FALSE,                       // mutex not owned                     TEXT("NameOfMutexObject"));  // object name     }      return 0; } 

Здесь создаётся мьютекс, разделяемый между данным процессом и всеми AppContainers текущего пользователя системы. Созданный таким образом мьютекс может быть спокойно открыт из процесса любого Modern-приложения или десктопного приложения, работающего в AppContainer.

Пару моментов

  • Код в MSDN слегка неполный (видите — там знаки вопроса стоят). Токен не должен быть NULL, перед первым использованием он должен быть валиден. Можно, например, взять токен текущего процесса:
    	OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken); 	

  • Для того, чтобы создать разделяемый пайп или memory-mapped файл нужно слегка изменить права доступа. Видите, там в коде есть пару макросов с текстом «MUTEX» — они не имеют смысла для других именованных объектов. Если не хотите заморачиваться, можно просто вместо них написать
    	STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL 	

    — это слегка избыточно, но зато точно будет работать.

  • Доступ ну вот вообще из всех AppContainer’ов — это хорошо для тестового кода, но если хотите использовать это в настоящем приложении — лучше ограничиться только своим контейнером.

Удачи в разработке и не поменьше верьте когда вам говорят «это невозможно сделать никак».

ссылка на оригинал статьи http://habrahabr.ru/post/186812/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *