2 pontos por GN⁺ 2023-09-29 | 1 comentários | Compartilhar no WhatsApp
  • 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 prompt kd>; a causa era um problema de compatibilidade com 486 aprimorados
  • KiSaveProcessorControlState e KiRestoreProcessorControlState do 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 superior
  • CpuType e CpuID do 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, 5 no 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 kd ou ntkd do kit de desenvolvimento mais recente do Windows 10, é preciso usar o i386kd incluí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 prompt kd>

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
  • 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
  • KiSaveProcessorControlState salva o estado de controle do processador em uma estrutura CONTEXT estendida
    • 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 byte
    • FFDFF138 fica 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, 5 aparece apenas duas vezes no NTOSKRNL.EXE
    • KiSaveProcessorControlState
    • KiRestoreProcessorControlState
  • 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
  • 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

 
GN⁺ 2023-09-29
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

    • Houve uma época em que o Win95 podia cair, ou até ser tomado, só com um ping enviado de um jeito específico
      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
    • Acho que era bem mais do que “algumas”
  • 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”

    • Vi o cursor indo em direção a esse botão de expandir e consegui puxá-lo de volta por pouco
      Talvez hoje eu ainda consiga trabalhar um pouco
    • Quando vejo computadores de antes da minha época, como o PET, penso que seria divertido brincar com eles. Uma máquina IRIX antiga também seria legal
      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
    • O conteúdo do artigo parece ter alguma relevância até hoje
  • A solução é óbvia” é realmente a frase certa

    • Meu professor de matemática dizia que existem dois tipos de problema: os fáceis e os que você ainda não entendeu
    • Depois que o bug é identificado, sim. Mas fico curioso sobre como alguém encontra um bug desses
      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

    • Nunca entendo o recolhimento automático de comentários do Stack Overflow quando o vejo
      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/

    • Overclock era divertido, mas também é bom que CPUs modernos consigam controlar clock e tensão com precisão para aproveitar a margem de segurança de antigamente
  • A resposta à pergunta foi realmente excelente
    O assunto era bom e a resposta foi muito boa

    • Na verdade, ele respondeu à própria pergunta. Mesmo assim, excelente
  • É 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

    • É engraçado que os desenvolvedores do Windows tenham sentido necessidade de colocar uma seção de “trustworthiness” na estrutura
  • Por que a pergunta e a resposta tinham o mesmo timestamp?

    • Foi para compartilhar a informação. Dá para ver que o autor da pergunta e o da resposta são a mesma pessoa
      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

    • A resolução de problemas na nossa época de crescimento era diferente
      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