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

  • Tutorial
Ещё в операционной системе 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'ов — это хорошо для тестового кода, но если хотите использовать это в настоящем приложении — лучше ограничиться только своим контейнером.


Удачи в разработке и поменьше верьте когда вам говорят «это невозможно сделать никак».
Инфопульс Украина 84,45
Creating Value, Delivering Excellence
Поделиться публикацией
Комментарии 23
  • 0
    Я правильно понимаю, что из маркета могут быть установлены только Modern-приложения? И что в установщик Desktop-приложения нельзя запихать установку Modern-приложения в обход маркета? Т.е. фактически невозможно гарантировать наличие обоих компонентов на компьютере, о чем и говорит разработчик MS на StackOverflow: “Metro apps can't count on desktop apps or services being installed on the machine. And desktop apps can't count on Metro apps running since they can be suspended any time”
    • +1
      Десктопное приложение и Modern-приложение не могут установить друг-друга, но десктопное приложение после установки может отправить пользователя на страницу приложения в маркете, откуда он установит (ну или не установит) Modern-часть. Так же как Modern-приложение может маякнуть пользователю о необходимости установить десктоп-часть. В конце-концов, никто же не удивляется когда страничка в интернете просит обновить флеш или десктопное приложение требует более новый .NET.
      • 0
        Modern-приложению доступны стронние COM-компоненты, которые были установлены на Desktop?
        • 0
          Modern-приложению доступен канал доступа к своему десктоп-приложению — статья только об этом. А уж из десктоп-приложения вызывайте что хотите и отдавайте результат в Modern-приложение.
          • 0
            Насколько я понял из статьи, там доступен канал не только к своему десктоп-приложению, но и всем другим десктоп-приложениям, которые этого пожелали.

            Спасибо за статью :-)
        • 0
          Если "Десктопное приложение и Modern-приложение не могут установить друг-друга, но десктопное приложение после установки может отправить пользователя на страницу приложения в маркете, откуда он установит (ну или не установит) Modern-часть.", то каким образом тогда работает «Relaunch Chrome in Windows 8 mode» и обратно? В маркет меня никто не направлял и я ничего не качал.
          • 0
            Честно — без понятия. Самому интересно.
            • +2
              Браузеры — это вообще отдельная категория приложений. Там один бинарь, который может работать и в десктопном режиме, и в полноэкранном (система ему говорит, в каком режиме запускаться), и даже в последнем у него больше привилегий, чем у обычного приложения — что, в частности, позволяет иметь общие с десктопом закладки, историю etc, а также делать вещи, недоступные обычным Store-приложениям, такие, как JIT-компиляция JS. Вот здесь (.docx) есть детальный разбор этой модели.

              Из Store при этом такому браузеру ставиться нельзя.
        • НЛО прилетело и опубликовало эту надпись здесь
          • 0
            Кстати, я сначала прочитал как
            дефолтное поведение можно изменить и из приложения в AppContainer, мы можем общаться…

          • +1
            К стати, сама Microsoft такой механизм организации песочниц для браузера применила лишь 4 года спустя в Win8.1 + IE 11


            Чушь.

            msdn.microsoft.com/en-us/library/bb625963.aspx
            msdn.microsoft.com/en-us/library/bb250462(v=vs.85).aspx
            • 0
              Не чушь. Сам по себе Protected Mode в IE < 11 ни от чего не защищал. Даже если его включить — процесс имел доступ к определенным папкам на диске, мог взаимодействовать с другими Low Integrity и Untrusted-процессами, в него можно было инжектить код, подгружать внешние библиотеки — т.е. по сути делать всё, что угодно. Только Enhanced Protected Mode реализовал полноценную песочницу на базе AppContainer, а Enhanced Protected Mode включен по-умолчанию только в Win8.1 + IE 11
          • +1
            Сам по себе Protected Mode в IE < 11 ни от чего не защищал.

            Защищал от доступа к процессам и ресурсам с Integrity Level > Low.

            мог взаимодействовать с другими Low Integrity и Untrusted-процессами, в него можно было инжектить код, подгружать внешние библиотеки

            В контексте целей и задач реальных атак на пользователей браузеров все перечисленное — мало существенно. Цель любых exploitaiton mitigation механизмов (песочницы к ним относятся) — увеличить стоимость разработки эксплойтов пригодных для использования в реальных атаках в реальном мире. Появление Protected Mode в IE < 11 этой цели достигло за счет того, что в дополнение к уязвимости в браузере атакующему стала требоваться и уязвимость локального повышения привилегий для выхода из-под Low Integrity до Medium или High. В Windows 8.1 c IE >= 11 и Enhanced Protected Mode в плане экономической составляющей атаки не поменялось совершенно ничего: требуется все та же связка из уязвимости в браузере и уязвимости локального повышения привилегий.
            • +2
              Неа, зона «Low Integrity» шире зоны «Low Integrity + AppContainer». В Low Integrity есть доступные на запись и чтение для всех Low Integrity — процессов папки и ветки реестра. Для AppContainer этого нет. Low Integrity процессы видят друг друга, AppContainer — нет. Low Integrity процессы видят глобальные именованные объекты, AppContainer видит лишь объекты в своём неймспейсе.

              В общем, с чего бы это Microsoft вбухивал силы в Enhanced Protected Mode, если и прошлой технологии хватало?
              • +2
                Неа, зона «Low Integrity» шире зоны «Low Integrity + AppContainer»


                Спасибо, кэп.

                В Low Integrity есть доступные на запись и чтение для всех Low Integrity — процессов папки и ветки реестра.


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

                В общем, с чего бы это Microsoft вбухивал силы в Enhanced Protected Mode, если и прошлой технологии хватало?


                Мало безопасности никогда не бывает :)

                PS: из песпективных фич, которые могли бы действительно существенно повлиять на стоимость атак, я могу отметить win32k system call disable policy, но AFAIK, в настоящий момент она не используется в каких-либо програмных продуктах.
                • +1
                  Вообще нормально песочница сделана только в Хроме, где прав нету просто никаких. Вот это действительно безопасно. А когда я вижу, что, ну процессу всё-таки можно кое-куда кое-что сохранить, что-то там открыть и т.д. — это сразу минус 100 очков к доверию. Как минимум возможна социальная инженерия вида «сохранить вирус в темп и объяснить пользователю на страничке в браузере, почему ему надо на этом файлике кликнуть „Run as administrator“.
                  • +5
                    Вообще нормально песочница сделана только в Хроме


                    Не могу не согласиться.

                    Как минимум возможна социальная инженерия вида «сохранить вирус в темп и объяснить пользователю на страничке в браузере, почему ему надо на этом файлике кликнуть „Run as administrator“.


                    С таким же успехом его можно уговорить скачать этот файл из сети безо всяких эксплойтов. Где нет мозгов — технологии бессильны.
            • +1
              С технической точки зрения можно много чего. Например, можно дать Store-приложению полный доступ к какой-либо части файловой системы, открыв таковой доступ роли «ALL APPLICATION PACKAGES». А те же пайпы используются VS для работы дизайнера XAML.

              Проблема в том, что кроме чисто технических ограничений есть и ограничения Store. В которых четко прописано, что Store-приложение не может каким-либо образом завязываться на другое приложение, в т.ч. и на десктопное. Т.е. если вы честно опишете, как работает ваша система, то вы просто не пройдете сертификацию. А если умолчите и пройдете, то это рано или поздно вскроется потом…

              Кстати, абсолютно такая же ситуация с большим количеством Win32 API вызовов, которые, если верить MSDN, «are not available in Windows Store apps» — вы зачастую можете их позвать, и они даже будут работать, но WACK будет на них ругаться.
              • 0
                При этом зачем-то открыт доступ к локальной машине по сети через сокеты. Для этого даже специальное разрешением есть отдельное. Если я не могу завязываться на другое приложение — зачем такое разрешение?

                Кроме того, можно предоставлять какую-нибудь функциональность просто так, даже без этого канала связи — такое приложение пройдёт проверку и будет работать. А для тех, кто поставит ещё и десктопную версию — будут дополнительные плюшки. Причём их в модерн-приложение может даже инжектить код десктопного, чтобы не палиться на проверках магазина.
                • 0
                  Внезапно, серверные сокеты там для того, чтобы писать серверы :)

                  Ну и вы же понимаете, что проверки — проверками, а правила — правилами. О нарушении последних может написать и ваш конкурент, например. И будет очень грустно раскрутить приложение до популярности, а потом внезапно быть вынужденным его убрать, потому что вся его соль может быть реализована только с нарушением правил сертификации.
              • 0
                Попробовал использовать этот код для организации пайпа между обычным приложением и IE под EPM. Оказалось, что он прописывает не все необходимые права. После поисков по инету и экспериментов нашел такое решение:

                function CreateAppContainerSecurityDescriptor(var SD: PSECURITY_DESCRIPTOR): boolean;
                const
                  SDDL_REVISION_1 = 1;
                var
                  pSD: PSECURITY_DESCRIPTOR;
                begin
                  result := false;
                  if ConvertStringSecurityDescriptorToSecurityDescriptor('S:(ML;;NW;;;LW)D:(A;;0x120083;;;WD)(A;;0x120083;;;AC)',
                    SDDL_REVISION_1, pSD, nil) then begin
                    SD := pSD;
                    result := true;
                  end;
                end;
                
                function Start: boolean;
                var
                  SD: PSECURITY_DESCRIPTOR;
                  SecurityAttributes: SECURITY_ATTRIBUTES;
                begin
                  result := false;
                  if Win32MajorVersion >= 6 then begin
                    if CreateAppContainerSecurityDescriptor(SD) then begin
                      SecurityAttributes.nLength := sizeof(SECURITY_ATTRIBUTES);
                      SecurityAttributes.bInheritHandle := true;
                      SecurityAttributes.lpSecurityDescriptor := SD;
                
                      PipeHandle := CreateNamedPipe('\\.\pipe\pipe1', PIPE_ACCESS_DUPLEX,
                        PIPE_TYPE_BYTE or PIPE_READMODE_BYTE, 1, 0, 0, 1000, @SecurityAttributes);
                      result := PipeHandle <> INVALID_HANDLE_VALUE;
                    end;
                  end;
                end;
                
                

                Здесь три правила.
                1) S:(ML;;NW;;;LW) — доступ к объекту из low integrity
                2) D:(A;;0x120083;;;WD) — разрешение всем читать\писать в пайп
                3) (A;;0x120083;;;AC) — разрешение всем AppContainer читать\писать в пайп
                • 0
                  Встала задача вызова RPC сервера по lcalpc из Enhanced Protected Mode IE 11 Windows 8.1 — за сутки разобрался. Задача разрешима, но решение получилось не очень элегантное и не в две строчки — если кому интересно — прошу в ПМ.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое