Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reworked absolute address handling (Fixes #471) #473

Merged
merged 2 commits into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion include/Zydis/Encoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
/**
Expand Down Expand Up @@ -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;
/**
Expand Down
135 changes: 75 additions & 60 deletions src/Encoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}

/**
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -491,14 +505,14 @@ 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:
case ZYDIS_OPERAND_ENCODING_DISP64:
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:
Expand Down Expand Up @@ -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
{
Expand All @@ -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:
Expand Down Expand Up @@ -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)
{
Expand All @@ -1578,25 +1597,17 @@ 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)
{
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;
mappzor marked this conversation as resolved.
Show resolved Hide resolved
}
}
else if (disp_size == 8)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
24 changes: 24 additions & 0 deletions tests/re_enc_test_cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -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]"
}
]
60 changes: 27 additions & 33 deletions tools/ZydisFuzzShared.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Loading