Meu código x86 pré-compilado pode estar sendo executado em 16 bits (modo real ou modo protegido de 16 bits) ou 32 bits (modo protegido i386). Como posso detectá-lo a partir do código em tempo de execução?
Consegui chegar a esta fonte NASM:
bits 16
cpu 386
pushf
test ax, strict word 0 ; In 32-bit mode this is `test eax, ...', +2 bytes.
jmp short found_16
; Fall through to found_32.
found_32:
bits 32
popf
int 32 ; Or whatever code.
found_16:
bits 16
popf
int 16 ; Or whatever code.
No entanto, não gosto, porque ele usa a pilha. Existe uma solução que não modifique nenhum registrador de uso geral, registrador de segmento ou flags, não use a pilha e funcione em um 8086 (somente modo de 16 bits) e em um 386 (ambos os modos)?
Tentei lea esi, [dword esi+0]
no modo de 32 bits, mas isso não é possível no modo de 16 bits.
Observe que estou ciente de que para a maioria dos programas o modo é decidido em tempo de compilação (como parte da arquitetura e plataforma), e eles não precisam ser capazes de detectar o modo em tempo de execução. Também para programas iniciados normalmente, o sistema operacional escolherá o modo correto com base no cabeçalho do arquivo, portanto, quase não há perigo de executar acidentalmente um arquivo de programa completo no modo errado. No entanto, alguns trechos de programa, como o shellcode de exploração, podem se beneficiar da detecção em tempo de execução de todos os tipos (incluindo a arquitetura e o sistema operacional). Também tenho alguns outros casos de uso obscuros em mente.
Se você estiver satisfeito com as mudanças temporárias que
esi
foram desfeitas, algo como isto pode funcionar:No modo de 16 bits que decodifica para:
E em 32 bits:
Percebi que posso melhorar minha solução anterior.
JMP NEAR
, o opcode 0xE9 pega um deslocamento imediato de 16 bits de dois bytes no modo de 16 bits e um deslocamento de 32 bits de quatro bytes no modo de 32 bits. Além disso, esse deslocamento é relativo ao início da próxima instrução. Então, se os 16 bits superiores do deslocamento de 32 bits forem zero, isso significa que o alvo do salto no modo de 16 bits está dois bytes abaixo do alvo do salto no modo de 32 bits. Isso é espaço suficiente para um salto curto para o destino real de 16 bits.Exemplo de NASM:
Saída de
ndisasm -b16 foo.bin
:Saída de
ndisasm -b32 foo.bin
:Minha solução anterior, incluída para referência, era usar
0x0001
como os 16 bits superiores do deslocamento, de modo que no modo de 32 bits, o alvo do salto seja 64K+2 bytes adiante. Isso requer ter pelo menos 64K+ de espaço de código disponível.Saída de
ndisasm -b16 foo.bin
:Saída de
ndisasm -b32 foo.bin
:É muito incomum querer isso e querer preservar o estado arquitetônico original, incluindo FLAGS. Seu
test
método de detecção de diferença de comprimento de instrução é o que eu usaria. Oumov reg, imm16/32
se você quiser preservar FLAGs, mas pode destruir sua escolha de registro.Se sua CPU suportar NOPs longos (
0F 1F modrm
), você pode usar isso em vez do opcodetest eax, imm32
/imm16
para evitar afetar até mesmo FLAGS. O suporte a NOPs longos está presente pelo menos desde o P6 (Pentium Pro / Pentium II), mas talvez não no P5 Pentium ou anterior; se você se importa com compatibilidade com hardware retro, você deve verificar novamente ou apenas evitar isso e usar a resposta do Jester.Decodificado no modo de 16 bits:
Decodificado no modo de 32 bits:
Mesmo código com o deslocamento do ramo calculado pelo montador
Então você pode colocar até 127 bytes entre
nop
/jmp short
e ofound16:
rótulo.Semi-relacionado: https://codegolf.stackexchange.com/questions/139243/determine-your-languages-version/139717#139717 -
11 bytes de código de máquina que define AL =
16
,32
, ou64
(e destrói FLAGS, CX e R8B).