diff --git a/include/Zydis/Encoder.h b/include/Zydis/Encoder.h index b945fb42..a890c8cc 100644 --- a/include/Zydis/Encoder.h +++ b/include/Zydis/Encoder.h @@ -221,7 +221,11 @@ typedef struct ZydisEncoderOperand_ */ ZyanU8 scale; /** - * The displacement value. + * The displacement value. This value is always treated as 64-bit signed integer, so it's + * important to take this into account when specifying absolute addresses. For example + * to specify a 16-bit address 0x8000 in 16-bit mode it should be sign extended to + * `0xFFFFFFFFFFFF8000`. See `address_size_hint` for more information about absolute + * addresses. */ ZyanI64 displacement; /** @@ -302,6 +306,13 @@ typedef struct ZydisEncoderRequest_ * encoder deduces address size from `ZydisEncoderOperand` structures that represent * explicit and implicit operands. This hint resolves conflicts when instruction's hidden * operands scale with address size attribute. + * + * This hint is also used for instructions with absolute memory addresses (memory operands with + * displacement and no registers). Since displacement field is a 64-bit signed integer it's not + * possible to determine actual size of the address value in all situations. This hint + * specifies size of the address value provided inside encoder request rather than desired + * address size attribute of encoded instruction. Use `ZYDIS_ADDRESS_SIZE_HINT_NONE` to assume + * address size default for specified machine mode. */ ZydisAddressSizeHint address_size_hint; /** diff --git a/src/Encoder.c b/src/Encoder.c index 12147ad0..c64ea1bc 100644 --- a/src/Encoder.c +++ b/src/Encoder.c @@ -286,20 +286,17 @@ static ZydisEncodableEncoding ZydisGetEncodableEncoding(ZydisInstructionEncoding */ static ZyanU8 ZydisGetMachineModeWidth(ZydisMachineMode machine_mode) { - switch (machine_mode) - { - case ZYDIS_MACHINE_MODE_REAL_16: - case ZYDIS_MACHINE_MODE_LEGACY_16: - case ZYDIS_MACHINE_MODE_LONG_COMPAT_16: - return 16; - case ZYDIS_MACHINE_MODE_LEGACY_32: - case ZYDIS_MACHINE_MODE_LONG_COMPAT_32: - return 32; - case ZYDIS_MACHINE_MODE_LONG_64: - return 64; - default: - ZYAN_UNREACHABLE; - } + ZYAN_ASSERT((ZyanUSize)machine_mode <= ZYDIS_MACHINE_MODE_MAX_VALUE); + static const ZyanU8 lookup[6] = + { + /* ZYDIS_MACHINE_MODE_LONG_64 */ 64, + /* ZYDIS_MACHINE_MODE_LONG_COMPAT_32 */ 32, + /* ZYDIS_MACHINE_MODE_LONG_COMPAT_16 */ 16, + /* ZYDIS_MACHINE_MODE_LEGACY_32 */ 32, + /* ZYDIS_MACHINE_MODE_LEGACY_16 */ 16, + /* ZYDIS_MACHINE_MODE_REAL_16 */ 16, + }; + return lookup[machine_mode]; } /** @@ -330,6 +327,23 @@ static ZyanU8 ZydisGetOszFromHint(ZydisOperandSizeHint hint) return lookup[hint]; } +/** + * Calculates maximum size of absolute address value based on address size hint. + * + * @param request A pointer to `ZydisEncoderRequest` struct. + * + * @return Maximum address size in bits. + */ +static ZyanU8 ZydisGetMaxAddressSize(const ZydisEncoderRequest *request) +{ + ZyanU8 addr_size = ZydisGetAszFromHint(request->address_size_hint); + if (addr_size == 0) + { + addr_size = ZydisGetMachineModeWidth(request->machine_mode); + } + return addr_size; +} + /** * Calculates effective operand size. * @@ -491,7 +505,6 @@ static ZyanBool ZydisIsImmSigned(ZydisOperandEncoding encoding) case ZYDIS_OPERAND_ENCODING_JIMM16_32_64: case ZYDIS_OPERAND_ENCODING_JIMM32_32_64: case ZYDIS_OPERAND_ENCODING_JIMM16_32_32: - return ZYAN_TRUE; case ZYDIS_OPERAND_ENCODING_DISP8: case ZYDIS_OPERAND_ENCODING_DISP16: case ZYDIS_OPERAND_ENCODING_DISP32: @@ -499,6 +512,7 @@ static ZyanBool ZydisIsImmSigned(ZydisOperandEncoding encoding) case ZYDIS_OPERAND_ENCODING_DISP16_32_64: case ZYDIS_OPERAND_ENCODING_DISP32_32_64: case ZYDIS_OPERAND_ENCODING_DISP16_32_32: + return ZYAN_TRUE; case ZYDIS_OPERAND_ENCODING_UIMM8: case ZYDIS_OPERAND_ENCODING_UIMM16: case ZYDIS_OPERAND_ENCODING_UIMM32: @@ -571,17 +585,21 @@ static ZyanU8 ZydisGetEffectiveImmSize(ZydisEncoderInstructionMatch *match, Zyan return ZydisGetScaledImmSize(match, simm16_32_32_sizes, min_size); } case ZYDIS_OPERAND_ENCODING_DISP16_32_64: + { ZYAN_ASSERT(match->easz == 0); + const ZyanU8 addr_size = ZydisGetMaxAddressSize(match->request); + const ZyanU64 uimm = imm & (~(0xFFFFFFFFFFFFFFFFULL << (addr_size - 1) << 1)); + if (min_size < addr_size && ZydisGetUnsignedImmSize(uimm) > min_size) + { + min_size = addr_size; + } if (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) { if (min_size < 32) { min_size = 32; } - if (min_size == 32 || min_size == 64) - { - match->easz = eisz = min_size; - } + match->easz = eisz = min_size; } else { @@ -595,6 +613,7 @@ static ZyanU8 ZydisGetEffectiveImmSize(ZydisEncoderInstructionMatch *match, Zyan } } break; + } case ZYDIS_OPERAND_ENCODING_JIMM8: case ZYDIS_OPERAND_ENCODING_JIMM16: case ZYDIS_OPERAND_ENCODING_JIMM32: @@ -1569,7 +1588,7 @@ static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *mat { return ZYAN_FALSE; } - ZyanI64 displacement = user_op->mem.displacement; + const ZyanI64 displacement = user_op->mem.displacement; ZyanU8 disp_size = 0; if (displacement) { @@ -1578,17 +1597,6 @@ static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *mat { return ZYAN_FALSE; } - if (ZydisGetMachineModeWidth(match->request->machine_mode) == 16) - { - if ((ZyanI16)displacement == 0) - { - disp_size = 0; - } - else - { - disp_size = ZydisGetSignedImmSize((ZyanI16)displacement); - } - } match->cd8_scale = ZydisGetCompDispScale(match); if (match->cd8_scale) @@ -1596,7 +1604,10 @@ static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *mat const ZyanI64 mask = (1 << match->cd8_scale) - 1; if (!(displacement & mask)) { - disp_size = ZydisGetSignedImmSize(displacement >> match->cd8_scale); + if (ZydisGetSignedImmSize(displacement >> match->cd8_scale) == 8) + { + disp_size = 8; + } } else if (disp_size == 8) { @@ -1941,28 +1952,40 @@ static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *mat reg_index_class); } } - else + else if (disp_size != 8 || !match->cd8_scale) { + const ZyanU8 addr_size = ZydisGetMaxAddressSize(match->request); + if (disp_size > addr_size) + { + return ZYAN_FALSE; + } ZyanU8 min_disp_size = match->easz ? match->easz : 16; if (((min_disp_size == 16) && !(match->definition->address_sizes & ZYDIS_WIDTH_16)) || - (min_disp_size == 64)) + (min_disp_size == 64) || + (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64)) { min_disp_size = 32; } - if (ZydisGetUnsignedImmSize(displacement) == 16) - { - disp_size = 16; - } if (disp_size < min_disp_size) { disp_size = min_disp_size; } + const ZyanI64 disp = user_op->mem.displacement; if (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) { - candidate_easz = match->easz == 32 ? 32 : 64; + candidate_easz = addr_size; + if (addr_size == 32 && disp >= 0 && match->easz != 32) + { + candidate_easz = 64; + } } else { + const ZyanU64 uimm = disp & (~(0xFFFFFFFFFFFFFFFFULL << (addr_size - 1) << 1)); + if (disp_size < addr_size && ZydisGetUnsignedImmSize(uimm) > disp_size) + { + disp_size = addr_size; + } candidate_easz = disp_size; } disp_only = ZYAN_TRUE; @@ -2014,12 +2037,18 @@ static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *mat break; } case ZYDIS_SEMANTIC_OPTYPE_MOFFS: + { if (user_op->mem.base != ZYDIS_REGISTER_NONE || user_op->mem.index != ZYDIS_REGISTER_NONE || user_op->mem.scale != 0) { return ZYAN_FALSE; } + const ZyanU8 min_disp_size = ZydisGetSignedImmSize(user_op->mem.displacement); + if (min_disp_size > ZydisGetMaxAddressSize(match->request)) + { + return ZYAN_FALSE; + } if (match->eosz != 0) { const ZyanU8 eosz_index = match->eosz >> 5; @@ -2042,23 +2071,15 @@ static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *mat { return ZYAN_FALSE; } - // This is not a standard rejection. It's a special case for `mov` instructions (only ones - // to use `moffs` operands). Size of `moffs` is tied to address size attribute, so its - // signedness doesn't matter. However if displacement can be represented as a signed - // integer of smaller size we reject `moffs` variant because it's guaranteed that better - // alternative exists (in terms of size). - ZyanU8 alternative_size = ZydisGetSignedImmSize(user_op->mem.displacement); - const ZyanU8 min_disp_size = - (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) ? 32 : 16; - if (alternative_size < min_disp_size) - { - alternative_size = min_disp_size; - } - if (alternative_size < match->disp_size) + // This is not a standard rejection. It's a special case for `mov` instructions (`moffs` + // variants only). In 64-bit mode it's possible to get a shorter encoding for addresses + // that can fit into 32-bit displacements. + if (match->disp_size == 64 && min_disp_size < match->disp_size) { return ZYAN_FALSE; } break; + } default: ZYAN_UNREACHABLE; } @@ -4668,15 +4689,9 @@ ZYDIS_EXPORT ZyanStatus ZydisEncoderDecodedInstructionToEncoderRequest( enc_op->mem.base = dec_op->mem.base; enc_op->mem.index = dec_op->mem.index; enc_op->mem.scale = dec_op->mem.type != ZYDIS_MEMOP_TYPE_MIB ? dec_op->mem.scale : 0; - if (dec_op->encoding == ZYDIS_OPERAND_ENCODING_DISP16_32_64) - { - ZydisCalcAbsoluteAddress(instruction, dec_op, 0, - (ZyanU64 *)&enc_op->mem.displacement); - } - else + if (dec_op->mem.disp.has_displacement) { - enc_op->mem.displacement = dec_op->mem.disp.has_displacement ? - dec_op->mem.disp.value : 0; + enc_op->mem.displacement = dec_op->mem.disp.value; } enc_op->mem.size = dec_op->size / 8; break; diff --git a/tests/re_enc_test_cases.json b/tests/re_enc_test_cases.json index e40efc2f..afd5b295 100644 --- a/tests/re_enc_test_cases.json +++ b/tests/re_enc_test_cases.json @@ -706,5 +706,29 @@ "stack_width": "ZYDIS_STACK_WIDTH_64", "payload": "CACACA", "description": "ret far 0xCACA" + }, + { + "machine_mode": "ZYDIS_MACHINE_MODE_LONG_COMPAT_32", + "stack_width": "ZYDIS_STACK_WIDTH_32", + "payload": "003D00FFFFFF", + "description": "add byte ptr ds:[0xFFFFFF00], bh" + }, + { + "machine_mode": "ZYDIS_MACHINE_MODE_LONG_COMPAT_32", + "stack_width": "ZYDIS_STACK_WIDTH_32", + "payload": "67003E00FF", + "description": "add byte ptr ds:[0xFF00], bh" + }, + { + "machine_mode": "ZYDIS_MACHINE_MODE_LONG_64", + "stack_width": "ZYDIS_STACK_WIDTH_64", + "payload": "678C0CE5E5E5E5E5E5E5E5E5E5E5E5", + "description": "mov word ptr ds:[0xE5E5E5E5], cs" + }, + { + "machine_mode": "ZYDIS_MACHINE_MODE_LONG_COMPAT_32", + "stack_width": "ZYDIS_STACK_WIDTH_16", + "payload": "62E27D4F002D0000020000006291FF", + "description": "vpshufb zmm5 {k7}, zmm0, zmmword ptr ds:[0x00020000]" } ] \ No newline at end of file diff --git a/tools/ZydisFuzzShared.c b/tools/ZydisFuzzShared.c index ab4935a1..3e352d5d 100644 --- a/tools/ZydisFuzzShared.c +++ b/tools/ZydisFuzzShared.c @@ -285,46 +285,40 @@ void ZydisValidateInstructionIdentity(const ZydisDecodedInstruction* insn1, } break; case ZYDIS_OPERAND_TYPE_MEMORY: + { + // Usually this check is done after verifying instruction identity but in this case + // we have to fail early + if (insn1->length < insn2->length) + { + fputs("Suboptimal output size detected\n", ZYAN_STDERR); + abort(); + } + ZyanU64 addr1, addr2; + ZyanStatus status1 = ZydisCalcAbsoluteAddress(insn1, op1, 0, &addr1); + ZyanStatus status2 = ZydisCalcAbsoluteAddress(insn2, op2, + insn1->length - insn2->length, &addr2); + ZyanBool addresses_match = ZYAN_FALSE; + if (ZYAN_SUCCESS(status1) && ZYAN_SUCCESS(status2)) + { + if (addr1 != addr2) + { + fprintf(ZYAN_STDERR, "Mismatch for memory operand %u (absolute address)\n", i); + abort(); + } + addresses_match = ZYAN_TRUE; + } if ((op1->mem.type != op2->mem.type) || (op1->mem.segment != op2->mem.segment) || (op1->mem.base != op2->mem.base) || (op1->mem.index != op2->mem.index) || - (op1->mem.scale != op2->mem.scale && op1->mem.type != ZYDIS_MEMOP_TYPE_MIB) || - (op1->mem.disp.value != op2->mem.disp.value)) + ((op1->mem.scale != op2->mem.scale) && (op1->mem.type != ZYDIS_MEMOP_TYPE_MIB)) || + ((op1->mem.disp.value != op2->mem.disp.value) && !addresses_match)) { - ZyanBool acceptable_mismatch = ZYAN_FALSE; - if (op1->mem.disp.value != op2->mem.disp.value) - { - if ((op1->mem.disp.has_displacement) && - (op2->mem.disp.has_displacement) && - (op1->mem.index == ZYDIS_REGISTER_NONE) && - ((op1->mem.base == ZYDIS_REGISTER_NONE) || - (op1->mem.base == ZYDIS_REGISTER_EIP) || - (op1->mem.base == ZYDIS_REGISTER_RIP))) - { - ZyanU64 addr1, addr2; - ZydisCalcAbsoluteAddress(insn1, op1, 0, &addr1); - ZydisCalcAbsoluteAddress(insn2, op2, 0, &addr2); - acceptable_mismatch = (addr1 == addr2); - } - if ((insn1->machine_mode == ZYDIS_MACHINE_MODE_REAL_16) || - (insn1->machine_mode == ZYDIS_MACHINE_MODE_LEGACY_16) || - (insn1->machine_mode == ZYDIS_MACHINE_MODE_LONG_COMPAT_16) || - (insn1->stack_width == 16) || - (insn1->address_width == 16) || - (insn2->address_width == 16)) - { - acceptable_mismatch = ((op1->mem.disp.value & 0xFFFF) == - (op2->mem.disp.value & 0xFFFF)); - } - } - if (!acceptable_mismatch) - { - fprintf(ZYAN_STDERR, "Mismatch for memory operand %u\n", i); - abort(); - } + fprintf(ZYAN_STDERR, "Mismatch for memory operand %u\n", i); + abort(); } break; + } case ZYDIS_OPERAND_TYPE_POINTER: if ((op1->ptr.segment != op2->ptr.segment) || (op1->ptr.offset != op2->ptr.offset))