2 years ago

#49337

test-img

J.Todd

Why do these push instructions only shift the (64 bit) stack pointer by 2 bytes?

I'm not sure whether this is a bug in the emulator, but I'm going to assume it's more likely that I, the assembly newbie, am mistaken rather than the programming gurus who designed the Unicorn (based on Qemu) engine :P

This is Python code, but my question is really about x86-64 / assembly language, not Python:

# In this code, I simply assemble some assembly code, disassemble
# it for a sanity check, and then execute the assembled binary,
# observing the resulting stack.

# Keystone
# The Ultimate Assembler
from keystone import *

# Capstone
# The Ultimate Disassembler
from capstone import *

# Unicorn
# The ultimate CPU emulator
import unicorn
from unicorn import *
from unicorn.x86_const import *

# globals

KB = 1024
MB = KB * KB
MEM_SIZE = int(MB / 256)
STACK_SIZE = int(MEM_SIZE / 2)
REGS_CACHE = False

# code

ks = Ks(KS_ARCH_X86, KS_MODE_64)
ks.syntax = KS_OPT_SYNTAX_ATT
md = Cs(CS_ARCH_X86, CS_MODE_64)

ASM = b''
ASM += b'PUSH $0x21;'
ASM += b'PUSH $0x22;'
ASM += b'PUSH $0x23;'
ASM += b'PUSH $0xffffffff;'
ASM += b'PUSH $0x1d1d1d1d1d1d1d1d;'
ASM += b'PUSH $0x21;'
ASM += b'MOV $1, %rax;'
ASM += b'MOV $1, %rdi;'
ASM += b'MOV %rsp, %rsi;'
ASM += b'MOV $1, %rdx;'
ASM += b'syscall;'
ASM += b'ADD $8, %rsp;'

try:
   BIN, count = ks.asm(ASM)
except KsError as e:
   print("ERROR: %s" %e)

START_ADDR = 0x0
BIN = bytes(BIN)
BIN_LEN = len(BIN)
END_ADDR = START_ADDR + BIN_LEN

print('Sanity check disassembly:')
for i in md.disasm(BIN, START_ADDR):
    print(f'0x{i.address}\t {i.mnemonic}\t {i.op_str}')
    pass

mu = Uc(UC_ARCH_X86, UC_MODE_64)
mu.mem_map(START_ADDR, MEM_SIZE)
mu.mem_write(START_ADDR, BIN)
# initialize stack
mu.reg_write(UC_X86_REG_RSP, MEM_SIZE - STACK_SIZE)
mu.emu_start(START_ADDR, END_ADDR)
print("Emulation done. Below is the stack:")
memory = mu.mem_read(START_ADDR, MEM_SIZE)
rsp = mu.reg_read(UC_X86_REG_RSP)
stack = memory[rsp:MEM_SIZE]
print('rsp', rsp)
print('mem len:', len(memory))
print(bytes(stack).hex())

Output:

Sanity check dissassembly:
0x0      push    0x21
0x2      push    0x22
0x4      push    0x23
0x6      push    0xffff
0x10     push    0x1d1d
0x14     push    0x21
0x16     movabs  rax, 1
0x26     movabs  rdi, 1
0x36     mov     rsi, rsp
0x39     movabs  rdx, 1
0x49     syscall
0x51     add     rsp, 8
Emulation done. Below is the stack:
rsp 2020
mem len: 4096
1d1dffff230000000000000022000000000000002100000000000000000000000000000000000000000000000...snip...

Before I modified these instructions, it was a simple assembly code example pushing 0x21 (!) to the stack and making a system call to trigger output (logs an ! mark). But I threw some extra pushes in there to play with the stack, expecting each push to add 64 bits to the stack (decrementing RSP by 8 bytes). But I noticed instead that when I pushed the 4 and 8 byte values 0xffffffff and 0x1d1d1d1d1d1d1d1d, only 0xffff and 0x1d1d was appended to the stack, and RSP was decremented by 2 bytes, twice. Pushing 0x21 to the stack again pushed a full 8 bytes. The 0x21 being pushed isn't actually apparent in this log output, but I think that's because the following instructions, maybe the syscall, popped it from the stack. But if those instructions are removed, it is there.

I'm confused about what's going on.

assembly

x86-64

qemu

0 Answers

Your Answer

Accepted video resources