Curso MIT "Segurança de sistemas de computadores". Palestra 7: Sandbox do Cliente Nativo Parte 2

Instituto de Tecnologia de Massachusetts. Curso de Aula nÂș 6.858. "Segurança de sistemas de computador". Nikolai Zeldovich, James Mickens. 2014 ano


Computer Systems Security é um curso sobre o desenvolvimento e implementação de sistemas de computador seguros. As palestras abrangem modelos de ameaças, ataques que comprometem a segurança e técnicas de segurança baseadas em trabalhos científicos recentes. Os tópicos incluem segurança do sistema operacional (SO), recursos, gerenciamento de fluxo de informaçÔes, segurança de idiomas, protocolos de rede, segurança de hardware e segurança de aplicativos da web.

Palestra 1: “Introdução: modelos de ameaças” Parte 1 / Parte 2 / Parte 3
Palestra 2: “Controle de ataques de hackers” Parte 1 / Parte 2 / Parte 3
Aula 3: “Estouros de Buffer: ExploraçÔes e Proteção” Parte 1 / Parte 2 / Parte 3
Palestra 4: “Separação de PrivilĂ©gios” Parte 1 / Parte 2 / Parte 3
Palestra 5: “De onde vĂȘm os sistemas de segurança?” Parte 1 / Parte 2
Palestra 6: “Oportunidades” Parte 1 / Parte 2 / Parte 3
Palestra 7: “Sandbox do Cliente Nativo” Parte 1 / Parte 2 / Parte 3

PĂșblico: por que o intervalo de capacidade da memĂłria do intervalo de endereços deve começar do zero?

Professor: porque em termos de desempenho, Ă© mais eficiente usar o salto de destino se vocĂȘ souber que um endereço vĂĄlido Ă© um conjunto contĂ­nuo de endereços começando do zero. Porque entĂŁo vocĂȘ pode fazer isso com uma Ășnica mĂĄscara AND , onde todos os bits altos sĂŁo um e apenas um par de bits baixos Ă© zero.

PĂșblico: Eu pensei que a mĂĄscara AND deveria fornecer alinhamento.

Professor: certo, a mĂĄscara fornece alinhamento, mas por que começa do zero? Eu acho que eles confiam no hardware segmentado por hardware de segmentação. EntĂŁo, basicamente, eles poderiam usĂĄ-lo para mover a ĂĄrea para cima, em termos de espaço linear. Ou talvez esteja relacionado apenas ao modo como o aplicativo "vĂȘ" esse intervalo. De fato, vocĂȘ pode colocĂĄ-lo com diferentes compensaçÔes no seu espaço de endereço virtual. Isso permitirĂĄ que vocĂȘ execute certos truques com hardware segmentado para executar vĂĄrios mĂłdulos no mesmo espaço de endereço.



PĂșblico: Talvez seja porque eles querem "capturar" o ponto de recebimento do ponteiro nulo?

Professor: sim, porque eles querem pegar todos os pontos de recepção. Mas vocĂȘ tem uma maneira de fazer isso. Porque o ponteiro nulo se refere ao segmento que estĂĄ sendo acessado. E se vocĂȘ mover o segmento, poderĂĄ exibir uma pĂĄgina zero nĂŁo utilizada no inĂ­cio de cada segmento. Portanto, isso ajudarĂĄ a criar alguns mĂłdulos.

Eu acho que uma das razĂ”es para essa decisĂŁo - iniciar o intervalo de 0 - se deve ao desejo de portar seu programa para a plataforma x64 , que tem um design um pouco diferente. Mas o artigo deles nĂŁo diz isso. No design de 64 bits, o prĂłprio equipamento se livrou de algum hardware de segmentação, no qual eles contavam por razĂ”es de eficiĂȘncia, entĂŁo eles tiveram que fornecer uma abordagem orientada a software. No entanto, para x32, esse ainda nĂŁo Ă© um bom motivo para o espaço começar do zero.

Portanto, continuamos a questĂŁo principal - o que queremos garantir do ponto de vista da segurança. Vamos abordar esse assunto de forma um tanto "ingĂȘnua" e ver como podemos arruinar tudo e depois tentar consertĂĄ-lo.

Acredito que um plano ingĂȘnuo Ă© procurar instruçÔes proibidas simplesmente digitalizando o executĂĄvel do começo ao fim. EntĂŁo, como vocĂȘ pode identificar essas instruçÔes? VocĂȘ pode simplesmente pegar o cĂłdigo do programa e colocĂĄ-lo em uma linha gigante que varia de zero a 256 megabytes, dependendo do tamanho do seu cĂłdigo, e entĂŁo iniciar a busca.



Essa linha pode primeiro conter o mĂłdulo de instruçÔes NOP , depois o mĂłdulo de instruçÔes ADD , NOT , JUMP e assim por diante. VocĂȘ apenas procura e, se encontrar uma instrução incorreta, diga que Ă© um mĂłdulo invĂĄlido e descarte-o. E se vocĂȘ nĂŁo vir nenhuma chamada do sistema para esta instrução, poderĂĄ ativar o lançamento deste mĂłdulo e fazer tudo dentro do intervalo de 0 a 256. VocĂȘ acha que isso vai funcionar ou nĂŁo? Com o que eles estĂŁo preocupados? Por que isso Ă© tĂŁo difĂ­cil?

PĂșblico: Eles estĂŁo preocupados com o tamanho das instruçÔes?

Professor: sim, o fato Ă© que a plataforma x86 possui instruçÔes de tamanho variĂĄvel. Isso significa que o tamanho exato da instrução depende dos primeiros bytes desta instrução. De fato, vocĂȘ pode observar o primeiro byte para dizer que a instrução serĂĄ muito maior e, em seguida, talvez seja necessĂĄrio observar mais alguns bytes e decidir qual tamanho serĂĄ necessĂĄrio. Algumas arquiteturas como Spark , ARM , MIPS tĂȘm instruçÔes de comprimento mais fixo. O ARM possui dois comprimentos de instrução - 2 ou 4 bytes. Mas na plataforma x86, o comprimento das instruçÔes pode ser de 1, 5 e 10 bytes e, se vocĂȘ tentar, poderĂĄ obter uma instrução bastante longa de 15 bytes. No entanto, estas sĂŁo instruçÔes complexas.

Como resultado, um problema pode aparecer. Se vocĂȘ digitalizar esta linha de cĂłdigo linearmente, tudo ficarĂĄ bem. Mas talvez em tempo de execução vocĂȘ vĂĄ para o meio de algum tipo de instrução, por exemplo, NÃO .



É possĂ­vel que seja uma instrução multibyte e, se vocĂȘ a interpretar a partir do segundo byte, ela parecerĂĄ completamente diferente.

Outro exemplo no qual vamos "brincar" com o assembler. Suponha que tenhamos a instrução 25 CD 80 00 00 . Tendo examinado o segundo byte, vocĂȘ o interpretarĂĄ como uma instrução de cinco bytes, ou seja, terĂĄ que olhar 5 bytes para frente e ver que Ă© seguido pela instrução AND% EAX, CD 0x00 00 80 , começando com o operador AND para o registro EAX com alguns constantes definidas, por exemplo, 00 00 80 CD . Esta Ă© uma das instruçÔes seguras que o Native Client deve simplesmente permitir pela primeira regra de verificação de instruçÔes binĂĄrias. Mas se, durante a execução do programa, a CPU decidir que deve começar a executar o cĂłdigo do CD , marcarei este local da instrução com uma seta, entĂŁo a instrução % EAX, 0x00 00 80 CD , que na verdade Ă© uma instrução de 4 bytes, significarĂĄ a execução do INT $ 0x80 , que Ă© uma maneira de fazer uma chamada de sistema no Linux .



Portanto, se vocĂȘ perder esse fato, deixe o mĂłdulo nĂŁo confiĂĄvel "pular" no kernel e fazer chamadas ao sistema, ou seja, faça o que vocĂȘ deseja impedir. Como podemos evitar isso?

Talvez devĂȘssemos tentar observar o deslocamento de cada byte. Como o x86 pode começar a interpretar apenas uma instrução nos limites de bytes, nĂŁo de bits. Portanto, vocĂȘ deve observar o deslocamento de cada byte para ver onde a instrução começa. VocĂȘ acha que esse Ă© um plano viĂĄvel?

PĂșblico: Eu acho que se alguĂ©m realmente usa AND , o processador nĂŁo salta para este local, mas simplesmente permite que o programa seja executado.

Professor: sim, porque basicamente ele nĂŁo Ă© propenso a falsos positivos. Agora, se vocĂȘ realmente quiser, pode alterar um pouco o cĂłdigo para evitĂĄ-lo. Se vocĂȘ souber exatamente o que o dispositivo de teste estĂĄ procurando, poderĂĄ alterar essas instruçÔes. Talvez configurando AND primeiro para uma instrução e depois use a mĂĄscara em outra. Mas Ă© muito mais fĂĄcil evitar esses arranjos de bytes suspeitos, embora isso pareça bastante inconveniente.

É possĂ­vel que a arquitetura inclua uma alteração no compilador. Basicamente, eles tĂȘm algum tipo de componente que realmente precisa compilar o cĂłdigo corretamente. VocĂȘ nĂŁo pode simplesmente "decolar" o GCC e compilar o cĂłdigo para o Native Client . EntĂŁo, basicamente, isso Ă© factĂ­vel. Mas, provavelmente, eles acham que isso causa muitos problemas, nĂŁo serĂĄ uma solução confiĂĄvel ou de alto desempenho e assim por diante. AlĂ©m disso, existem vĂĄrias instruçÔes x86 que sĂŁo proibidas ou devem ser consideradas inseguras e, portanto, devem ser proibidas. Mas, na maioria das vezes, eles tĂȘm um byte de tamanho, por isso Ă© muito difĂ­cil encontrĂĄ-los ou filtrĂĄ-los.

Portanto, se eles não podem simplesmente coletar e classificar instruçÔes inseguras e esperar o melhor, precisam usar um plano diferente para desmontå-lo de maneira confiåvel. Então, o que o Native Client faz para garantir que não "tropeça" nessa codificação de tamanho variåvel?

De certa forma, se realmente varrermos o arquivo executĂĄvel da esquerda para a direita e procurarmos todos os cĂłdigos incorretos possĂ­veis, e se for assim que o cĂłdigo for executado, estaremos em boa forma. Mesmo que haja algumas instruçÔes estranhas e algum viĂ©s, o processador ainda nĂŁo vai “pular” para lĂĄ, ele executarĂĄ o programa na mesma ordem em que as instruçÔes sĂŁo digitalizadas, ou seja, da esquerda para a direita.



Assim, o problema com a desmontagem confiĂĄvel surge devido ao fato de que em algum lugar da aplicação pode haver "saltos". O processador pode falhar se der um “salto” para alguma instrução de cĂłdigo que nĂŁo percebeu ao digitalizar da esquerda para a direita. Portanto, este Ă© um problema de desmontagem confiĂĄvel atĂ© agora em desenvolvimento. E o plano principal Ă© verificar para onde todos os "saltos" levam. De fato, Ă© bastante simples em algum nĂ­vel. Existem vĂĄrias regras que consideraremos em um segundo, mas o plano aproximado Ă© que, se vocĂȘ vir uma instrução de "salto", precisarĂĄ garantir que o objetivo do "salto" tenha sido observado anteriormente. Para fazer isso, de fato, basta escanear da esquerda para a direita, ou seja, o procedimento que descrevemos em nossa abordagem ingĂȘnua do problema.

Nesse caso, se vocĂȘ vir alguma instrução de “salto” e o endereço para o qual esta instrução aponta, verifique se esse Ă© o mesmo endereço que vocĂȘ jĂĄ viu durante a desmontagem da esquerda para a direita.

Se uma instrução de salto para este byte de CD for encontrada, devemos marcĂĄ-lo como invĂĄlido, porque nunca vimos a instrução iniciando no byte de CD, mas vimos outra instrução começando com o nĂșmero 25. Mas se todas as instruçÔes de salto ordenado a ir para o inĂ­cio da instrução, neste caso para 25, entĂŁo estamos bem. Isso estĂĄ claro?

O Ășnico problema Ă© que vocĂȘ nĂŁo pode verificar os objetivos de cada salto no programa, pois pode haver saltos indiretos. Por exemplo, em x86, vocĂȘ pode ter algo como um salto no valor desse registro EAX . Isso Ă© Ăłtimo para implementar ponteiros de função.



Ou seja, o ponteiro de função estĂĄ em algum lugar da memĂłria, vocĂȘ o mantĂ©m em algum registro e depois vai para qualquer endereço no registro de movimento.

EntĂŁo, como esses caras lidam com saltos indiretos? Porque, de fato, nĂŁo tenho idĂ©ia se isso serĂĄ um “salto” para o byte CD ou o byte 25. O que eles fazem nesse caso?

PĂșblico: usando ferramentas?

Professor: sim, a instrumentação Ă© o principal truque. Portanto, sempre que virem que o compilador estĂĄ pronto para executar a geração, isso prova que esse salto nĂŁo causarĂĄ problemas. Para fazer isso, eles precisam garantir que todos os saltos sejam executados com uma multiplicidade de 32 bytes. Como eles fazem isso? Eles mudam todas as instruçÔes de salto para o que chamam de "pseudo instruçÔes". Estas sĂŁo as mesmas instruçÔes, mas prefixadas, que limpam os 5 bits baixos no registro EAX . O fato de a instrução limpar 5 bits baixos significa que faz com que o valor fornecido seja um mĂșltiplo de 32, de dois a cinco, e entĂŁo um salto para esse valor jĂĄ Ă© realizado.



Se vocĂȘ observar isso durante a verificação, certifique-se de que esse "par" instrucional "salte" apenas com uma multiplicidade de 32 bytes. E entĂŁo, para garantir que nĂŁo haja possibilidade de "pular" em algumas instruçÔes estranhas, vocĂȘ aplica uma regra adicional. Consiste no fato de que, durante a desmontagem, quando vocĂȘ olha suas instruçÔes da esquerda para a direita, assegura que o inĂ­cio de cada instrução vĂĄlida tambĂ©m seja um mĂșltiplo de 32 bytes.

Assim, alĂ©m deste kit de ferramentas, vocĂȘ verifica se cada cĂłdigo mĂșltiplo de 32 Ă© a instrução correta. Por uma instrução vĂĄlida e vĂĄlida, quero dizer uma instrução desmontada da esquerda para a direita.

PĂșblico: Por que o nĂșmero 32 foi escolhido?

Professor: sim, por que eles escolheram 32 em vez de 1000 ou 5? Por que 5 Ă© ruim?

PĂșblico: porque o nĂșmero deve ser uma potĂȘncia de 2.

Professor: sim, bem, Ă© por isso. Porque, caso contrĂĄrio, garantir o uso de algo mĂșltiplo de 5 exigirĂĄ instruçÔes adicionais que levem Ă  sobrecarga. E oito? O nĂșmero Ă© oito o suficiente?

PĂșblico-alvo: vocĂȘ pode ter instruçÔes com mais de oito bits.

Professor: sim, isso pode ser para a instrução mais longa permitida na plataforma x86. Se temos uma instrução de 10 bytes e tudo deve ter um mĂșltiplo de 8, nĂŁo podemos inseri-la em nenhum lugar. Portanto, o comprimento deve ser suficiente para todos os casos, porque a maior instrução que vi foi de 15 bytes. EntĂŁo 32 bytes Ă© suficiente.

Se vocĂȘ deseja adaptar as instruçÔes para entrar ou sair do ambiente de serviço de processo, pode precisar de uma quantidade nĂŁo trivial de cĂłdigo em um slot de 32 bytes. Por exemplo, 31 bytes, porque 1 byte contĂ©m uma instrução. Deveria ser muito maior? Devemos fazer isso igual a, digamos, 1024 bytes? Se vocĂȘ tiver muitos ponteiros de função ou muitos saltos indiretos, toda vez que quiser criar um local para pular, deverĂĄ continuar na prĂłxima borda, independentemente do seu valor. Portanto, com 32 bits, Ă© um tamanho bastante normal. Na pior das hipĂłteses, vocĂȘ perderĂĄ apenas 31 bytes se precisar chegar rapidamente Ă  prĂłxima borda. Mas se vocĂȘ tem um tamanho mĂșltiplo de 1024 bytes, existe a possibilidade de desperdiçar um kilobyte inteiro de memĂłria em vĂŁo para um salto indireto. Se vocĂȘ tiver funçÔes curtas ou muitos ponteiros de função, um tamanho tĂŁo grande da multiplicidade do comprimento do "salto" causarĂĄ um desperdĂ­cio significativo de memĂłria.

Eu nĂŁo acho que o nĂșmero 32 seja uma pedra de tropeço para o Native Client . Alguns blocos podem funcionar com uma multiplicidade de 16 bits, alguns de 64 ou 128 bits, isso nĂŁo importa. Apenas 32 bits pareciam o valor ideal mais aceitĂĄvel.

EntĂŁo, vamos fazer um plano para uma desmontagem confiĂĄvel. Como resultado, o compilador deve ter um pouco de cuidado ao compilar o cĂłdigo C ou C ++ em um binĂĄrio do Native Client e observar as seguintes regras.



Portanto, sempre que ele pular, como mostrado na linha superior, ele deve adicionar essas instruçÔes adicionais fornecidas nas 2 linhas inferiores. E, independentemente do fato de ele criar uma função para a qual ele "saltarĂĄ", nossa instrução saltarĂĄ como a adição AND $ 0xffffffe0,% eax indica. E nĂŁo pode apenas complementĂĄ-lo com zeros, porque tudo isso deve ter os cĂłdigos corretos. Portanto, a adição Ă© necessĂĄria para garantir que todas as instruçÔes possĂ­veis sejam vĂĄlidas. E, felizmente, na plataforma x86 , nem uma Ășnica função noop Ă© descrita por um Ășnico byte, ou pelo menos nĂŁo hĂĄ um Ășnico noop com 1 byte de tamanho. Assim, vocĂȘ sempre pode adicionar coisas ao valor de uma constante.

EntĂŁo, o que isso nos garante? Vamos garantir que sempre vejamos o que acontece na terminologia das instruçÔes que serĂŁo seguidas. Aqui estĂĄ o que essa regra nos fornece - a garantia de que uma chamada do sistema nĂŁo serĂĄ feita por acidente. Isso se aplica aos saltos, mas e os retornos? Como eles lidam com retornos? Podemos retornar a uma função no Native Client ? O que acontece se vocĂȘ executar o cĂłdigo em brasa?

PĂșblico: Pode estourar a pilha.

Professor: Ă© verdade que aparece inesperadamente na pilha. Mas o fato Ă© que a pilha usada pelos mĂłdulos do Native Client realmente contĂ©m alguns dados. Portanto, ao lidar com o Native Client, vocĂȘ nĂŁo deve se preocupar com o estouro de pilha.

PĂșblico: espere, mas vocĂȘ pode colocar qualquer coisa na pilha. E quando vocĂȘ dĂĄ um salto indireto.

Professor: Ă© verdade. O retorno parece quase um salto indireto de algum lugar da memĂłria, localizado no topo da pilha. Portanto, acho que uma coisa que eles poderiam fazer para a função de retorno Ă© definir o prefixo da mesma maneira que na verificação anterior. E esse prefixo verifica o que aparece no topo da pilha. VocĂȘ verifica se isso Ă© vĂĄlido e, ao escrever ou usar o operador AND , verifica o que estĂĄ no topo da pilha. Isso parece um pouco confiĂĄvel devido Ă  constante mudança de dados. Como, por exemplo, se vocĂȘ olhar para o topo da pilha e verificar se estĂĄ tudo bem, e depois escrever alguma coisa, o fluxo de dados no mesmo mĂłdulo poderĂĄ modificar algo no topo da pilha, apĂłs o qual vocĂȘ se referirĂĄ ao errado endereço

PĂșblico: Isso nĂŁo se aplica ao salto na mesma extensĂŁo?

Professor: sim, então o que acontece lå com um salto? Nossas condiçÔes de corrida podem de alguma forma invalidar este teste?

PĂșblico: Mas o cĂłdigo nĂŁo Ă© gravĂĄvel?

Professor: sim, o cĂłdigo nĂŁo pode ser escrito, isso Ă© verdade. Portanto, vocĂȘ nĂŁo pode modificar AND. Mas nĂŁo poderia outro fluxo alterar o objetivo do salto entre essas duas instruçÔes?

PĂșblico: isso estĂĄ no registro, entĂŁo ...

Professor: Sim, isso Ă© uma coisa legal. Como se um fluxo modificar algo na memĂłria ou no conteĂșdo carregado do EAX (por si sĂł, vocĂȘ o faz antes do download), nesse caso, o EAX estarĂĄ em um estado ruim, mas eliminarĂĄ os bits ruins. Ou ele pode alterar a memĂłria depois, quando o ponteiro jĂĄ estiver no EAX , portanto, nĂŁo importa que ele altere o local da memĂłria da qual o registro EAX foi carregado.

De fato, os threads nĂŁo compartilham conjuntos de registros. Portanto, se outro segmento alterar o registro EAX , isso nĂŁo afetarĂĄ o registro EAX desse segmento. Portanto, outros segmentos nĂŁo podem invalidar esta sequĂȘncia de instruçÔes.

Hå outra questão interessante. Podemos contornar isso E ? Eu posso pular para onde quiser em qualquer lugar deste espaço de endereço. , AND .



, , , , , AND . . jmp , .



, , - , 1237. , 32. Native Client , , , . , , 1237 ?



- EAX , , , , . , ? ?

: NaCl , .

: , . x86 , , NaCl , 2 . , , : «, , !», 25 CD 80 00 00 . . , x86 .

, Native Client . , , , , NaCl . , .

: , , . , . , , , , .



: , . , . , , , EAX . , - . EAX , EBX . , . EAX EBX AND . , , EAX , . , - 64 . Jmp *% eax AND .



, , , , . Intel , , , , . , , . AND , EAX , «» .

, , . , . , , , . , , , .
, , C1 C7 .

C1 , , . , «» . , , . , , - . , .
2 , 0 64 . , , . , , .

3 , , , . , , .

4 , hlt . halt ? , C4 . , , - , .

, , ? , , - .
, , , , . , , , , . .



55:20

:

Curso MIT "Segurança de sistemas de computadores". 7: « Native Client», 3


.

, . VocĂȘ gosta dos nossos artigos? Deseja ver materiais mais interessantes? Ajude-nos fazendo um pedido ou recomendando a seus amigos, um desconto de 30% para os usuĂĄrios da Habr em um anĂĄlogo exclusivo de servidores bĂĄsicos que inventamos para vocĂȘ: Toda a verdade sobre o VPS (KVM) E5-2650 v4 (6 nĂșcleos) 10GB DDR4 240GB SSD 1Gbps de US $ 20 ou como dividir o servidor? (as opçÔes estĂŁo disponĂ­veis com RAID1 e RAID10, atĂ© 24 nĂșcleos e atĂ© 40GB DDR4).

3 Dell R630 — 2 Intel Deca-Core Xeon E5-2630 v4 / 128GB DDR4 / 41TB HDD 2240GB SSD / 1Gbps 10 TB — $99,33 , , .

Dell R730xd 2 vezes mais barato? Somente nós temos 2 TVs Intel Dodeca-Core Xeon E5-2650v4 128GB DDR4 6x480GB SSD 1Gbps 100 a partir de US $ 249 na Holanda e nos EUA! Leia sobre Como criar um prédio de infraestrutura. classe usando servidores Dell R730xd E5-2650 v4 custando 9.000 euros por um centavo?

Source: https://habr.com/ru/post/pt418225/


All Articles