Triton does not handle the push instruction properly when used with a size prefix (66).
For the instruction pushw encoded as 66 68 00 ff, rsp should be decremented by 2, but Triton decrements it by 8.
from triton import *
context = TritonContext(ARCH.X86_64)
# pushw 0xff00
instruction = Instruction(0x1000, b"\x66\x68\x00\xff")
context.setConcreteRegisterValue(context.registers.rsp, 0x1000)
context.processing(instruction)
rsp = context.getConcreteRegisterValue(context.registers.rsp)
print(f"expected rsp: 0xffe")
print(f"actual rsp: 0x{rsp:x}")
# rsp: 0xff8
The push instruction's handle's size in a convoluted fashion:
The operand size (16, 32, or 64 bits) determines the amount by which the stack pointer is decremented (2, 4 or 8).
If the source operand is an immediate of size less than the operand size, a sign-extended value is pushed on the stack. If the source operand is a segment register (16 bits) and the operand size is 64-bits, a zero-extended value is pushed on the stack; if the operand size is 32-bits, either a zero-extended value is pushed on the stack or the segment selector is written on the stack using a 16-bit move. For the last case, all recent Intel Core and Intel Atom processors perform a 16-bit move, leaving the upper portion of the stack location unmodified.
Stack-address size. Outside of 64-bit mode, the B flag in the current stack-segment descriptor determines the size of the stack pointer (16 or 32 bits); in 64-bit mode, the size of the stack pointer is always 64 bits.
The stack-address size determines the width of the stack pointer when writing to the stack in memory and when decrementing the stack pointer. (As stated above, the amount by which the stack pointer is decremented is determined by the operand size.)
In triton, it seems like the operand size is ignored if operand is an immediate, this bypasses any size prefix use:
void x86Semantics::push_s(triton::arch::Instruction& inst) {
auto& src = inst.operands[0];
auto stack = this->architecture->getStackPointer();
triton::uint32 size = stack.getSize();
/* If it's an immediate source, the memory access is always based on the arch size */
if (src.getType() != triton::arch::OP_IMM)
size = src.getSize();
...
|
/* If it's an immediate source, the memory access is always based on the arch size */ |
|
if (src.getType() != triton::arch::OP_IMM) |
|
size = src.getSize(); |
Here's a C program to test the behavior on your CPU:
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
int main(void) {
uintptr_t old_rsp;
uintptr_t new_rsp;
uintptr_t real_rsp;
asm volatile("mov %%rsp, %[real_rsp]\n\t"
"mov %%rsp, %[old_rsp]\n\t"
// pushw 0xff00
".byte 0x66, 0x68, 0x00, 0xff\n\t"
"mov %%rsp, %[new_rsp]\n\t"
"mov %[real_rsp], %%rsp\n\t"
: [old_rsp] "=&r"(old_rsp), [new_rsp] "=&r"(new_rsp),
[real_rsp] "=&r"(real_rsp)
:
: "memory");
printf("old_rsp = 0x%" PRIxPTR "\n", old_rsp);
// old_rsp = 0x7ffee752f360
printf("new_rsp = 0x%" PRIxPTR "\n", new_rsp);
// new_rsp = 0x7ffee752f35e
printf("offset = 0x%" PRIxPTR "\n", old_rsp - new_rsp);
// offset = 0x2
return 0;
}
This was tested on the python bindings for triton: triton-library=1.0.0rc4
Triton does not handle the push instruction properly when used with a size prefix (66).
For the instruction
pushwencoded as66 68 00 ff, rsp should be decremented by2, but Triton decrements it by8.The
pushinstruction's handle's size in a convoluted fashion:In triton, it seems like the operand size is ignored if operand is an immediate, this bypasses any size prefix use:
Triton/src/libtriton/arch/x86/x86Semantics.cpp
Lines 13963 to 13965 in bc84cf7
Here's a C program to test the behavior on your CPU:
This was tested on the python bindings for triton:
triton-library=1.0.0rc4