Minotauro9:(TEXT_006.009):16/04/1996 << Back To Minotauro9


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