Copy Link
Add to Bookmark
Report

Minotauro Magazine Issue 09 06 Instrucciones no Documentadas de la familia 80x86

eZine's profile picture
Published in 
Minotauro Magazine
 · 6 Feb 2021

  

MINOTAURO MAGAZINE #9:
Instrucciones no Documentadas
de la familia 80x86

Fuentes y agradecimientos:
- The Undocumented PC, de Frank Van Gilluwe
- Al flaco, por haber descubierto el UMOV hace mil a¤os, sin saber que era :)

Podriamos tocar varias instrucciones no documentadas, pero muchas
no vienen al caso en cuestion de virus. Asi que lo que aca tenemos es una
sintesis de lo mas relevante del tema. Sin embargo aqui tienen una breve
lista de estas instrucciones:

AAD Adjust AX Before BCD Divide All
AAM Adjust AX After BCD Multiply All
IBTS Insert Bit String 80386 A Step
ICEBP In Circuit Emulator Break Point Most 80386+
LOADALL Load All Processor Registers Some 286-486
POP CS Load CS from Stack 8088/8086 only
RDMSR Read Model Specific Register Some 386+
RDTSC Read Time Stamp Counter Pentium
RSM Resume from System Management Mode Some 386+
SETALC Set AL to Carry All
SHL Shift Left by Count 80188+
SHL Shift Left by 1 All
SMI System Management Interrupt Entry AMD 386DXLV/SXDLV
TEST Test Register with Immediate All
UMOV User Move Register Instructions Some 386/486
WRMSR Write Model Specific Register Some 386+
XBTS Extract Bit String 80386 A Step

Como ven, hay varias instrucciones ya documentadas. El tema es como se las
puede codificar. Por ejemplo el AAD (d50ah) convierte un valor de 0907h en AX
a 61h en AL. La logica seria ((AH * 10) + AL) --> AL y AH <-- 00h
el ejemplo anterior:

(09h * 10d) + 07h --> AL

Lo que no esta documentado es que asi como se multiplica por la base del
BCD default (10d) se puede modificar por cualquier otra base. Los ensambladores
prefijan este valor a 10d (0ah), aunque se puede manejar y verificar a mano.
El ejemplo anterior se veria:

db 0d5h, 0ah ; aad

Si quisieramos trabajar con otra base, digamos hexa seria:

db 0d5h, 10h ; aad

Y asi sucesivamente... En sintesis, con un opcode de D5h, imm
la logica seria:

AL = (AH * imm) + AL
AH = 00h

Como esto, lo mismo pasa con otras instrucciones como SHL, AAM, etc ...

Ahora veamos las mas interesantes:

===============================================================================
ICEBP In Circuit Emulator Break Point Most 80386+
===============================================================================
0f1h

Para la parte practica, esta instruccion se comporta como un INT 01h de un solo
byte. Como tiene un uso no documentado oficialmente los desensambladores y
antivirus que analizan el codigo no la reconocen.
El mnemonico viene de su funcion original, setear un breakpoint para el ICE
(contrariamente con el Intruder Countermeasure Electronics, esta sigla
viene de In Circuit Emulator). El tema del ICE es bastante extenso, pero
resumidamente es un hard especial que se conecta al sistema (el cual tiene que
venir preparado) con funciones de debugging externas. Este hard vendria
preparado con memoria Hidden (una memoria que co-existiria con la memoria
comun de usuario) la cual solo se podria utilizar desde un modo especial del
micro. Este modo se lo conoce como Management Mode, desde el cual se podria
acceder a la memoria hidden con instrucciones MOV comun y corrientes. Si desde
este modo se necesitaria transferir informacion de la memoria de usuario comun,
se haria por medio un UMOV (User MOV). Comunmente no va a haber problemas,
ya que como dijimos antes el micro lo interpreta como un INT 01h de un solo
byte (pasaria a modo Management, si el bit 12 del DR7 pasara a 1)
Aunque esto depende de cada micro y debe ser consultado especificamente si se
quiere investigar. En este estado cualquier int 1h, ya sea generado a mano, por
un ICEBP o por el trap flag (int 1h) realiza un cambio a Management Mode, por
lo que si el sistema no estuviese preparado para soportar hidden memory, o ICE
se colgaria.

===============================================================================
LOADALL Load All Processor Registers Some 286-486
===============================================================================
0fh, 05h ; Load Registers from 0:800h 286 only
0fh, 07h ; Load Registers from es:edi 386/486

Esta es una instruccion muy especial. Era usada originalmente por los ICE
para poder cargar de un saque y desde una tabla, CADA registro del CPU.
Esto nos permitiria cambiar los descriptores de segmento, pudiendo acceder
a memoria extendida sin necesidar de switchear a modo protegido. Se que esto
se hace, pero yo no lo pude hacer andar... Seguramente no tengo en cuenta
alguna otra cosa, no se. Si alguien pudiera :) me avisa :)

Este es un extracto de un articulo sobre LOADALL:

LOADALL loads the entire CPU state from a table pointed to by
ES:EDI. At the completion of LOADALL, the CPU state is defined
according to this table. No protection checks are performed
against values in the table, and LOADALL can generate no
exceptions in real mode, or in protected mode at IOPL 0.
Attempting to execute LOADALL at any other privilege level will
generate an exception 13.

There are three types of structures in the LOADALL image:
1) 32-bit CPU registers entries;
2) 16-bit segment registers (zero-extended to 32-bits);
3) 96-bit segment descriptor cache entries.

The segment register entries have the following format:
SREG STRUC
REG_VAL DW ? ; low 16-bits defined
DW 0 ; high 16-bits=0
ENDS

The segment descriptor cache entires have the following format:
DESC_CACHE STRUC
DB 0 ; b[00-07] not used
S_USE DB ? ; b[14] operand size
S_Access DB ? ; b[16-23] Access Rights
DB 0 ; b[24-31] not used
S_Addr DD ? ; Segment Address in memory
S_Limit DD ? ; Segment size limit
ENDS

The LOADALL tables is organized as follows:

;----------------------------------------------------------------
; LOADALL table pointed to by ES:EDI
;----------------------------------------------------------------
Offset Description Size Value
====== =========== ==== =====
[00] CR0 DD ?
[04] EFLAGS DD ?
[08] EIP DD ?
[0C] EDI DD ?
[10] ESI DD ?
[14] EBP DD ?
[18] ESP DD ?
[1C] EBX DD ?
[20] EDX DD ?
[24] ECX DD ?
[28] EAX DD ?
[2C] DR6 DD ?
[30] DR7 DD ?
[34] TR_REG SREG <?>
[38] LDT_REG SREG <?>
[3C] GS_REG SREG <?>
[40] FS_REG SREG <?>
[44] DS_REG SREG <?>
[48] SS_REG SREG <?>
[4C] CS_REG SREG <?>
[50] ES_REG SREG <?>
[54] TSS_DESC DESC_CACHE <?,?,?>
[60] IDT_DESC DESC_CACHE <0,?,?>
[6C] GDT_DESC DESC_CACHE <0,?,?>
[78] LDT_DESC DESC_CACHE <?,?,?>
[84] GS_DESC DESC_CACHE <?,?,?>
[90] FS_DESC DESC_CACHE <?,?,?>
[9C] DS_DESC DESC_CACHE <?,?,?>
[A8] SS_DESC DESC_CACHE <?,?,?>
[B4] CS_DESC DESC_CACHE <?,?,?>
[C0] ES_DESC DESC_CACHE <?,?,?>
[CC] LENGTH OF TABLE

The following two diagrams take a closer look at fields within
the LOADALL table:
1) the descriptor cache register;
2) the access rights within the descriptor cache register.

;---------------------------------------------------------------------
; Segment descriptor cache register
;
; 9 6 3 2 1 1 0 0
; 5 3 1 3 5 3 7 0
; +--------------+---------------------+---+---------------+---+---+
; | 32-bit limit | 32-bit base address | 0 | Access Rights | 0 | 0 |
; +--------------+---------------------+---+---------------+---+---+
;
;---------------------------------------------------------------------
; 386 Descriptor Cache Access Rights
;
; ++++++++----------------------------- 0=Undefined
; |||||||| +--------------------------- Present 0=No 1=Yes
; |||||||| |++------------------------- Descriptor privelege level
; |||||||| |||+------------------------ System Desc. 0=Sys 1=Code/Data
; |||||||| ||||+++--------------------- Type(*)
; |||||||| ||||||+-----------------------Read/Write 0=R/O 1=R/W
; |||||||| |||||+|-----------------------Expansion 0=Up 1=Dwn
; |||||||| ||||+||-----------------------Executable 0=No 1=Yes*
; |||||||| ||||||| 000=Read Only
; |||||||| ||||||| 001=Read/Write
; |||||||| ||||||| 010=Read Only, Expand down
; |||||||| ||||||| 011=Read/Write, Expand down
; |||||||| ||||||| 100=Execute only
; |||||||| ||||||| 101=Execute/Read
; |||||||| ||||||| 110=Execute only, conforming
; |||||||| ||||||| 111=Execute/Read, conforming
; |||||||| |||||||+-------------------- Accessed
; |||||||| |||||||| +------------------ 0=Undefined (was G bit)
; |||||||| |||||||| |+----------------- Default operand size(+)
; |||||||| |||||||| || 0=16-bit operands
; |||||||| |||||||| || 1=32-bit operands
; |||||||| |||||||| ||
; |||||||| |||||||| ||++++++-++++++++-- 0=Undefined
; |||||||| |||||||| |||||||| ||||||||
; |||||||| |||||||| |||||||| ||||||||
; 3||||||||2||||||||1||||||||0||||||||0 Bit
; 1||||||||3||||||||5||||||||7||||||||0 Offset
; +++++++++++++++++++++++++++++++++++++
; | Intel |22221111|11|Intel| Intel | (*) = CS can be marked as a R/W
; |Reserved|32109876|54|Rsvd.|Reserved| data segment if LOADALL
; +++++++++++++++++++++++++++++++++++++ is used to load register.
; (+) = Only applicable for CS
;
;---------------------------------------------------------------------
; A closer look at the access rights field definitions:
;
; 2 2 2 2 1 1 1 1 1 1 1 Bit 2 2 2 2 1 1 1 1 1 1
; 3 2 1 0 9 8 7 6 5 4 3 Offset 3 2 1 0 9 8 7 6 5 4
; +-+---+-+-----+-+-+-+-+ +-+---+-+-----+-+-+-+
; |P|DPL|S|Type |A|0|G|D| |P|DPL|S| Type |G|D|
; | | | |0| | | | | | | | | | | |1| | | | | | |
; +-+---+-+-----+-+-+-+-+ +-+---+-+-----+-+-+-+
; Bit:
; P Present bit. 1=Present, 0=Not present.
; This bit signals the CPU if the segment addressed by the
; segment base address is actually present in memory.
; DPL Descriptor Privilege Level: 0=highest, 3=lowest
; S System descriptor: 0=Code, Data; 1=System descriptor
; Type Segment Type: (S=0)
; +-+-+-+
; |X|Y|Z|
; +-+-+-+
; | | |
; | | +-- Read/Write 0=Read-only 1=Read/Write
; | +---- Expansion direction. 0=Expand up 1=Expand down
; +------ Executable 0=Data Seg 1=Code Seg
; Type Segment Type: (S=1)
; 0000 = Reserved
; 0001 = Available 286 TSS
; 0010 = LDT
; 0011 = Busy 286 TSS
; 0100 = 286 Call Gate
; 0101 = Task Gate
; 0110 = 286 Interrupt Gate
; 0111 = 286 Trap Gate
; 1000 = Reserved
; 1001 = Available 386, 486 TSS
; 1010 = Reserved
; 1011 = Busy 386, 486 TSS
; 1100 = 386, 486 Call Gate
; 1101 = Reserved
; 1110 = 386, 486 Interrupt Gate
; 1111 = 386, 486 Trap Gate
; A Accessed (S=0) 0=Not Accessed 1=Accessed
; The processor sets this bit when the descriptor is
; accessed.
; G Granularity 0=Byte 1=4k
; When set, upon loading the limit field of the descriptor
; cache register, the CPU shifts the limit by 12, and fills
; in the 1st 12 bits with 1's as follows:
; SHL LIMIT,12
; OR LIMIT,0FFFh
; D Default operand size 0=16-bit 1=32-bit
; When set, the CPU interprets all operands, and effective
; addresses as 32-bit values. When clear, all operands
; and effective addresses are 16-bit values. This bit
; is only applicable to the CS descriptor cache.
;---------------------------------------------------------------------
; The definition of these bits is exactly as that of the access
; rights in the descriptor table, with the following exceptions:
; 1) The "PRESENT" bit becomes a valid bit. Using LOADALL, you
; may load a descriptor cache register whose P bit is marked
; not present (P=0). During normal CPU operaion, simply
; loading the segment selector with a descriptor table entry
; whose P=0 will cause an exception-11. This is different
; that operating with LOADALL. LOADALL will let you load the
; descriptor cache register with P=0. But any memory
; reference using that segment selector will cause exception-
; 13.
; 2) The DPL field for SS & CS descriptors determine the CPL.
; 3) The DPL field for DS, ES, FS, & GS should be 3.
; 4) The Granularity (G) bit has no effect on the limit field
; in the descriptor cache register
; 5) A Code segment (CS) may be Read/Write/Executable by setting
; the access rights as a Read/Write/Data segment. This will
; even work in protected mode.

===============================================================================
UMOV User Move Register Instructions Some 386/486
===============================================================================
0fh, 10h, r/m mov regB1/mem8, regB2
0fh, 11h, r/m mov regW1/memW, regW2
0fh, 12h, r/m mov regB1, regB2/memB
0fh, 13h, r/m mov regW1, regW2/memW

Desde el System Managemente Mode (desde ahora SMM) se lo utiliza para traer
o llevar datos desde memoria Hidden a memoria de Usuario y viceversa. Pero lo
interesante es que si no estamos en el SMM, opera de la misma manera que un
MOV con los opcodes del 88h al 8bh. Osea que tenemos una forma de MOV no
documentada, por lo que desensambladores y antivirus no reconocen que esta
pasando. Para no andar haciendo todo a mano, fijense que el UMOV difiere de
un ADC comun por el 0fh de delante. Por lo que pueden dejar que el compilador
haga todo el trabajo:

UMOV ax, bx --> db 0fh
adc ax, bx

UMOV al, cl --> db 0fh
adc al, cl

Nota: No podemos garantizar hasta cuando se va a poder seguir haciendo esto,
quizas saquen el opcode, o metan un HALT en su lugar, quien sabe :-)
Tengo entendido que en Cyrix, se comporta como un NOP..... fijense y tengan
en cuenta que es posible que no funcione en todos lados donde lo prueben.

Saludos,
Drako, digital anarchy

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT