SMSW (store machine status word) stores the 16 lowest bits of cr0 in the operand register. In the case of SMSW with a reg32, the highest word is not defined - it seems to be always 8001h, though.
It makes it a weird reg32 opcode (why accepting a 32b operand if you undefine the highest bits and if there is a 16b operand counterpart ?) but it definitely changes the highest word (some disassembler show invariably a word operand, which is wrong).
While 'mov eax, cr0' is a priviledged instruction, SMSW isn't.
In cr0, bits 1 and 3 are called MP (Monitor Coprocessor) and TS (Task switched). Their state depends on FPU operations. This leads to several anti-* similar to the GS one.
They are usually set. This is no anti-debugger, but it might just be used as an anti-emulator.
smsw eax
cmp ax, 03bh
jnz bad
Execute any FPU opcode (even FNOP), then both bits will be cleared. Similarly to the GS trick, if you step, both bits will be in their usual set. To bypass it easily, you have to start running BEFORE the fpu opcode. Starting execution from the fpu opcode will give MP and TS their wrong state.
fnop
smsw eax
cmp ax, 031h
jnz bad
But after the FPU opcode, the bits will eventually revert to their set state.
So a wrong emulation might be stuck in an infinite loop.
_1:
smsw eax
cmp ax, 031h
jz _1
Another version of that test is to wait for one bit to be set and then check the other is set too.
the Intel docs says:
The SMSW instruction is only useful in operating-system software; however, it is not a privileged instruction and can be used in application programs.
Now, you know it's also useful as an anti-*.
In some case (1 out of 20?), execution fails. Likely, the FPU opcode might trigger those bits too late. So double checking will fix this race condition.
Thanks to Peter Ferrie.
Source and binary
[...]
anti-émulateur/pas à pas basé sur SMSW
SMSW (store machine status word) met les 16 bits de poids faibles de cr0 dans le registre de l'opérande. Dans le cas de SMSW avec un reg32, le mot de poid fort n'est pas défini - il semble cependant être toujours 8001h.
Ca donne un opcode bizarre pour reg32 (pourquoi accepter une opérande de 32b si le mot de poids fort n'est pas défini et qu'il existe une version sur 16b ?) mais qui modifie pour sûr le mot de poid fort (certains disassembleurs montrent toujours une opérande sur 16b, ce qui est faux).
Alors que 'mov eax, cr0' est une instruction privilégiée, SMSW ne l'est pas.
Dans cr0, les bits 1 et 3 sont appelés MP (Monitor Coprocessor) et TS (Task switched). Leur état dépend des opérations du FPU. Cela donne quelques anti-* similaires à celui de GS.
Ces bits sont à 1 d'habitude. Ce n'est pas un anti-débogueur, mais ça peut être utilisé simplement comme anti-émulateur.
smsw eax
cmp ax, 03bh
jnz bad
Executez n'importe quel opcode FPU (même FNOP), et les 2 bits seront à zéro. De même que pour GS, si on avance en pas à pas, les deux bits seront remis à leur valeur initiale. Pour le passer facilement, il faut commencer l'exécution AVANT l'opcode FPU. Si on démarre à partir de l'opcode FPU, MP et TS auront leur mauvaise valeur.
fnop
smsw eax
cmp ax, 031h
jnz bad
Mais après l'opcode FPU, les bits vont finir par revenir à leur état initial. Donc une mauvaise émulation pourrait être bloquée dans une boucle infinie.
_1:
smsw eax
cmp ax, 031h
jz _1
Une autre version de ce test revient à attendre qu'un des 2 bits soit redéfini, et ensuite vérifier que le 2ème l'est aussi.
La documentation Intel dit :
L'instruction SMSW n'est utile que pour le système d'exploitation; quoi qu'il en soit, elle n'est pas privilégiée et peut être utilisé dans des applications.
Maintenant, vous savez que c'est aussi utile comme anti-*.
De temps en temps (1 sur 20?), l'exécution échoue: l'opcode FPU change probablement les bits trop tard, auquel cas faire un 2eme test peut s'avérer nécessaire.
Merci à Peter Ferrie.
Source et binaire
16-bit smsw returns the low 16-bits of cr0.
ReplyDelete32-bit smsw returns the full 32-bits of cr0.
This second behaviour is undocumented but absolutely defined. :-)
In Windows, paging and resume bits are always enabled, hence the 8001h in the top half.