Por que tentar invadir o kernel do NT 3.1 faz minha máquina 486DX4 reiniciar?
(retrocomputing.stackexchange.com)- No Windows NT 3.1, ao acionar um break-in com Ctrl-C no
i386kd, o sistema 486DX4 de destino reinicia em vez de mostrar o promptkd>; a causa era um problema de compatibilidade com 486 aprimorados KiSaveProcessorControlStateeKiRestoreProcessorControlStatedo NTOSKRNL.EXE usam uma comparação de word ao verificar o tipo de CPU, fazendo um 486 com suporte a CPUID ser confundido com um Pentium ou superiorCpuTypeeCpuIDdo KPRCB são lidos juntos, então o número do modelo de um 486 com suporte a CPUID é interpretado como 256 a mais e, como resultado, ocorre acesso ao registrador CR4, que não existe no 486- Com o 486DX-33 original instalado e com um Am486DX4-NV8T write-through sem SMM, a depuração do kernel funciona normalmente, restringindo o problema aos 486 aprimorados que fornecem a instrução CPUID
- É possível corrigir tanto o NT 3.1 Advanced Server original quanto o NT 3.1 SP3 alterando os dois pontos
cmp ds:word_FFDFF138, 5no NTOSKRNL.EXE para uma comparação de byte
Ambiente de reprodução e sintomas
- Após instalar o Windows NT 3.1 em um Compaq ProSignia 3080, foi feita uma tentativa de depuração do kernel
- Esse sistema realmente executava Windows NT 3.1 em produção e parece ser uma das máquinas explicitamente visadas pelo Windows NT
- A RAM foi expandida para 128MB, e o Intel 486DX-33 em soquete foi trocado por um AMD enhanced 486DX4-SV8B
- Essa CPU suporta cache write-back e SMM, e foi instalada em um soquete adaptador de tensão
- O tratamento do BIOS para suporte a 486DX4 foi deixado de lado, e a CPU foi configurada por jumper em multiplicador 2x
- Sem suporte do chipset a L1 write-back e com multiplicador 2x, esperava-se compatibilidade de software com o Intel 80486DX2-66
- O Intel 80486DX2-66 é uma opção suportada nesse sistema
- A instalação do Windows NT 3.1 em si foi concluída normalmente
- Como o CD do Windows NT 3.1 inclui símbolos completos de depuração, foi feita uma tentativa de depuração do kernel
- O objetivo era investigar por que o NetDDE registrava erros no log de eventos
- Também havia um problema em que o sistema travava com uma determinada placa Ethernet EISA, e ainda restava a possibilidade de defeito de hardware
- Em vez do
kdountkddo kit de desenvolvimento mais recente do Windows 10, é preciso usar oi386kdincluído no Windows NT 3.1 para que a configuração de depuração do kernel esteja correta - Ao tentar um break-in com Ctrl-C no
i386kd, a máquina de destino reinicia em vez de fornecer o promptkd>
O que foi descartado como causa
- A memória foi verificada e estava normal
- Não era corrupção de arquivos do sistema
- Não havia watchdog de hardware configurado para reiniciar o sistema quando o kernel parasse para depuração
- O adaptador USB-serial do host se comunica normalmente
- Embora o dispositivo pareça um PL2301 falsificado, ele não enviou por engano um comando de depurador que transmitisse “reboot system”
- O protocolo KD realmente tem um comando de reboot do sistema
- Também não havia relação com opções de gerenciamento remoto ou alertas da placa-mãe
Causa real: incompatibilidade entre 486 aprimorados e o kernel do NT 3.1
- O kernel do Windows NT 3.1 não é compatível com processadores 486 aprimorados
- Mais especificamente, o problema ocorre em processadores 486 que fornecem a instrução
CPUID - A depuração do kernel funciona normalmente nas seguintes CPUs
- O 486DX-33 originalmente instalado
- Um Am486DX4-NV8T write-through de núcleo mais antigo, não aprimorado e sem SMM
- Se a intenção é apenas testar a depuração do kernel no NT 3.1, é mais adequado usar uma CPU que já seja compatível por padrão com o Windows NT 3.1
- Para corrigir o próprio NT, é preciso aplicar um patch no bug de identificação de tipo de CPU no NTOSKRNL.EXE
O caminho de código do kernel onde o problema acontece
- A causa direta da incompatibilidade é um bug em
KiSaveProcessorControlState- A função correspondente,
KiRestoreProcessorControlState, também tem um bug semelhante
- A função correspondente,
KiSaveProcessorControlStateé chamada em três pontos dentro do NTOSKRNL.EXE- Quando uma exceção é refletida para o depurador de kernel via
KdpTrap- No break-in por Ctrl-C, ocorre uma exceção de breakpoint na função de polling de break-in dentro da interrupção de tick do temporizador
- Quando
KeBugCheckExé chamada, ou seja, em uma situação de “tela azul” - Quando
KiSaveProcessorStateé chamada- Se a análise de fluxo de controle do NTOSKRNL.EXE no IDA estiver completa, essa função não é exportada nem chamada internamente no NTOSKRNL, então aparentemente isso não ocorre na prática
- Quando uma exceção é refletida para o depurador de kernel via
KiSaveProcessorControlStatesalva o estado de controle do processador em uma estruturaCONTEXTestendida- Salva CR0, CR2 e CR3
- Em Pentium ou superior, também salva CR4
- Salva os registradores de depuração DR0~DR3, DR6 e DR7
- Também salva configurações globais do modo protegido, como endereço da GDT, endereço da IDT, seletor do TSS ativo e seletor da LDT
Erro de interpretação dos campos do KPRCB
- A identificação do tipo de processador usa valores do KPRCB
- O KPRCB faz parte do KPCR
- O KPRCB do processador de boot, ou de um sistema com um único processador, fica no endereço virtual
FFDFF120, que está hardcoded nesse método
- Segundo os campos de KPRCB do NT 3.1 documentados por Geoff Chappell, temos:
+018 CHAR CpuType+019 CHAR CpuID+01A UShort CpuStep
- Esses campos são inicializados em
KiSetProcessorType- Para processadores 486, o byte no offset 18 é definido como 4
- Para processadores Pentium, é definido como 5
- Para processadores Pentium Pro e Pentium II/III, é definido como 6
- O byte no offset 19 é um sinalizador booleano que indica se o processador suporta CPUID e se isso funciona de forma “razoável”
- A instrução de comparação problemática lê um word do endereço
FFDFF138, e não um byteFFDFF138fica a 18h bytes do início do KPRCB- Portanto, em vez de interpretar apenas
CpuType, ela também incorpora como parte do número do modelo o byte seguinte,CpuID
- Processadores com suporte a CPUID têm o número do modelo tratado como 256 acima do valor real
- Um 80-4-86 com suporte a CPUID é tratado no Windows NT 3.1 como se fosse um 80-260-86
- Como 260 é muito maior que 5, que é o limiar do Pentium, o kernel conclui que esse processador deveria ter CR4
- Como 486 aprimorados não têm CR4, o problema aparece no caminho do break-in
Como aplicar o patch
- Depois que o bug é confirmado, a correção é simples
- A instrução
cmp ds:word_FFDFF138, 5aparece apenas duas vezes no NTOSKRNL.EXEKiSaveProcessorControlStateKiRestoreProcessorControlState
- Nos dois pontos, a comparação de word deve ser trocada por uma comparação de byte
- Em um editor hexadecimal, faça o patch da seguinte sequência de bytes duas vezes
- Original:
66 83 3D 38 F1 DF FF 05 - Alterado:
90 80 3D 38 F1 DF FF 05
- Original:
- Essa correção se aplica tanto ao NTOSKRNL.EXE da distribuição original do NT 3.1 Advanced Server quanto ao NT 3.1 SP3
1 comentários
Comentários do Hacker News
Antigamente, criei uma verificação de portas para checar se um serviço estava no ar, e só de abrir algumas portas TCP nas máquinas acabei derrubando metade da empresa
Eram tempos absurdos
Avançamos muito
https://en.wikipedia.org/wiki/Ping_of_death
Algumas máquinas também tinham uma vulnerabilidade no SMB que ficou anos sem patch. Já era a época em que o Metasploit existia, então com alguns comandos dava para injetar VNC na maioria dos hosts Windows da rede local. Hoje em dia, pelo menos, a velocidade dos patches é enorme
Ah, a toca do coelho da computação retrô. Inofensivamente separada das consequências do mundo real, mas ainda assim muito satisfatória
É uma verdadeira armadilha de mel para nerds. Acabei clicando no botão de expandir “ver mais 6 comentários”
Talvez hoje eu ainda consiga trabalhar um pouco
Mas um 486? Isso me traz muitas lembranças de telas azuis e de esperar sem fim enquanto ele fazia swap para o disco porque não dava conta da memória. Acho que ainda é cedo demais para mim
“A solução é óbvia” é realmente a frase certa
Imagino que tenha sido lendo continuamente o disassembly do código de trap até achar o problema. Não parece provável que desse para usar QEMU ou algum outro meio de depuração
Vale muito a pena ler também o método enterrado no comentário oculto lá no final
Não é como se ele escondesse os comentários com menos votos, nem os mais recentes ou os mais antigos. Parece aleatório e desnecessário
Se o problema é espaço, acho que seria melhor colocar paginação do que esconder
Coisas como os adaptadores slotket usados por compatibilidade em placas antigas da Abit, Asus e talvez MSI tinham uma capacidade real de overclock
A questão era qual era a velocidade da RAM e até que clock a placa-mãe aguentava nas configurações de multiplicador. Era uma fase relativamente inicial do overclock, na época dos Celeron e Pentium II
Em certo momento, por compatibilidade, coloquei um Celeron 600 em um adaptador e depois em um adaptador Slot II para Socket 370, numa configuração como a [1], e ele rodava bem a 1,2 GHz. Acho que cheguei até a 1,4 GHz no Windows ME, e no fim acabei queimando aquele CPU
[1]http://krick.3feetunder.com/370mod/
A resposta à pergunta foi realmente excelente
O assunto era bom e a resposta foi muito boa
É legal que a pessoa que fez a pergunta tenha postado a resposta também
Gosto disso. Fico muito feliz que as pessoas se interessem por computação retrô
Documentar problemas e soluções como esse para todo mundo é uma coisa boa
Eu sabia que esse tal de CPUID era uma má ideia
Por que a pergunta e a resposta tinham o mesmo timestamp?
Provavelmente ele julgou que colocar essa informação no formato de perguntas e respostas do SO sobreviveria por mais tempo do que um post de blog que ninguém encontraria
É ruim eu ter sabido o que era sem nem abrir?
Enfim, preciso ir gritar com essa molecada para sair do meu gramado
O que tínhamos para nos apoiar era a esperteza e, se necessário, alguns gurus que dava para chamar pelo telefone fixo. Não existia essa internet maravilhosa cheia de respostas com utilidade variada