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.