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 3Palestra 2: âControle de ataques de hackersâ
Parte 1 /
Parte 2 /
Parte 3Aula 3: âEstouros de Buffer: ExploraçÔes e Proteçãoâ
Parte 1 /
Parte 2 /
Parte 3Palestra 4: âSeparação de PrivilĂ©giosâ
Parte 1 /
Parte 2 /
Parte 3Palestra 5: âDe onde vĂȘm os sistemas de segurança?â
Parte 1 /
Parte 2Palestra 6: âOportunidadesâ
Parte 1 /
Parte 2 /
Parte 3Palestra 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?