Skip to content

x64: PUSH size #1450

Description

@Garfield1002

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions