sha0coder/scemu

cdq in 64-bit mode broken for 32-bit registers

brandonros opened this issue · 28 comments

cdq in 64-bit mode broken for 32-bit registers
[
    {
      "message": "newValue mismatch",
      "i": 21,
      "x64dbgLine": {
        "rawLine": {
          "Index": "00015",
          "Address": "0000000144ED4A17",
          "Bytes": "8BEB",
          "Disassembly": "mov ebp,ebx",
          "Registers": "rbp: FFFFFFFFFFFFF468-> 7FFE0000",
          "Memory": "",
          "Comments": ""
        },
        "rip": "144ed4a17",
        "registerChanges": [
          {
            "registerName": "rbp",
            "previousValue": "fffffffffffff800",
            "newValue": "7ffe0000"
          }
        ],
        "memoryChanges": [
          ""
        ]
      },
      "scemuLine": {
        "rip": "144ed4a17",
        "registerChanges": [
          {
            "registerName": "rbp",
            "previousValue": "fffffffffffff800",
            "newValue": "ffffffff7ffe0000"
          }
        ],
        "memoryChanges": []
      }
    }
  ],

this breaks this though, any ideas? @sha0coder

@sha0coder please reopen ^

32bits mov clears the higher bits
"Disassembly": "mov ebp,ebx",
"Registers": "rbp: FFFFFFFFFFFFF468-> 7FFE0000",

all the instructions do this? I think there are exceptions I'm doing some tests.

with 16bits and 8 bits the behavior is different, don't clear the other bytes.

it seems all instructions of 32bits clears the higher bits.

there are things bad like Mnemonic::Not
was keeping higher bits:
val = value0 & 0xffffffff_00000000 | ival as u32 as u64;

I have to check this pattern.

~/s/scemu ❯❯❯ grep -n 'value0 &' src/emu.rs                                                                                                                                            
3976:                        //val = value0 & 0xffffffff_00000000 | ival as u32 as u64;
3982:                        val = value0 & 0xffffffff_ffff0000 | ival as u16 as u64;
3987:                        val = value0 & 0xffffffff_ffffff00 | ival as u8 as u64;
4024:                        result1 = (value0 & 0xff) & (value1 & 0xff);
4025:                        result2 = (value0 & 0xffffffffffffff00) + result1;
4028:                        result1 = (value0 & 0xffff) & (value1 & 0xffff);
4029:                        result2 = (value0 & 0xffffffffffff0000) + result1;
4032:                        result1 = (value0 & 0xffffffff) & (value1 & 0xffffffff);
4033:                        result2 = (value0 & 0xffffffff00000000) + result1;
4036:                        result1 = value0 & value1;
4079:                        result1 = (value0 & 0xff) | (value1 & 0xff);
4080:                        result2 = (value0 & 0xffffffffffffff00) + result1;
4083:                        result1 = (value0 & 0xffff) | (value1 & 0xffff);
4084:                        result2 = (value0 & 0xffffffffffff0000) + result1;
4087:                        result1 = (value0 & 0xffffffff) | (value1 & 0xffffffff);
4088:                        result2 = (value0 & 0xffffffff00000000) + result1;
4834:                    value1 = (value0 & 0x00000000_000000ff) << 24 | (value0 & 0x00000000_0000ff00) << 8 |
4835:                        (value0 & 0x00000000_00ff0000) >> 8 | (value0 & 0x00000000_ff000000) >> 24 |
4836:                        (value0 & 0xffffffff_00000000);
4839:                    value1 = (value0 & 0xff000000_00000000) >> 56 | (value0 & 0x00ff0000_00000000) >> 40 |
4840:                        (value0 & 0x0000ff00_00000000) >> 24 | (value0 & 0x000000ff_00000000) >> 8 |
4841:                        (value0 & 0x00000000_ff000000) << 8 | (value0 & 0x00000000_00ff0000) << 24 |
4842:                        (value0 & 0x00000000_0000ff00) << 40 | (value0 & 0x00000000_000000ff) << 56;
8003:                let a:u128 = (value0 & 0xffffffff) ^ (value1 & 0xffffffff);
8004:                let b:u128 = (value0 & 0xffffffff_00000000) ^ (value1 & 0xffffffff_00000000);
8005:                let c:u128 = (value0 & 0xffffffff_00000000_00000000) ^ (value1 & 0xffffffff_00000000_00000000);
8006:                let d:u128 = (value0 & 0xffffffff_00000000_00000000_00000000) ^ (value1 & 0xffffffff_00000000_00000000_00000000);
8019:                let a:u128 = (value0 & 0xffffffff_ffffffff) ^ (value1 & 0xffffffff_ffffffff);
8020:                let b:u128 = (value0 & 0xffffffff_ffffffff_00000000_00000000) ^ (value1 & 0xffffffff_ffffffff_00000000_00000000);
8117:                let result:u128 = value0 & value1;
8139:                let a:u128 = (value0 & 0xffffffff) + (value1 & 0xffffffff);
8140:                let b:u128 = (value0 & 0xffffffff_00000000) + (value1 & 0xffffffff_00000000);
8141:                let c:u128 = (value0 & 0xffffffff_00000000_00000000) + (value1 & 0xffffffff_00000000_00000000);
8142:                let d:u128 = (value0 & 0xffffffff_00000000_00000000_00000000) + (value1 & 0xffffffff_00000000_00000000_00000000);
8155:                let a:u128 = (value0 & 0xffffffff_ffffffff) + (value1 & 0xffffffff_ffffffff);
8156:                let b:u128 = (value0 & 0xffffffff_ffffffff_00000000_00000000) + (value1 & 0xffffffff_ffffffff_00000000_00000000);
8169:                let r128:u128 = (value0 & 0xffffffffffffffff0000000000000000) + result as u128;
8180:                let r128:u128 = (value0 & 0xffffffffffffffffffffffff00000000) + result as u128;
8190:                let a:u128 = (value0 & 0xffffffff) - (value1 & 0xffffffff);
8191:                let b:u128 = (value0 & 0xffffffff_00000000) - (value1 & 0xffffffff_00000000);
8192:                let c:u128 = (value0 & 0xffffffff_00000000_00000000) - (value1 & 0xffffffff_00000000_00000000);
8193:                let d:u128 = (value0 & 0xffffffff_00000000_00000000_00000000) - (value1 & 0xffffffff_00000000_00000000_00000000);
8206:                let a:u128 = (value0 & 0xffffffff_ffffffff) - (value1 & 0xffffffff_ffffffff);
8207:                let b:u128 = (value0 & 0xffffffff_ffffffff_00000000_00000000) - (value1 & 0xffffffff_ffffffff_00000000_00000000);
8220:                let r128:u128 = (value0 & 0xffffffffffffffff0000000000000000) + result as u128;
8231:                let r128:u128 = (value0 & 0xffffffffffffffffffffffff00000000) + result as u128;
8241:                let left:u128 = ((value0 & 0xffffffffffffffff0000000000000000)>>64) * ((value1 & 0xffffffffffffffff0000000000000000)>>64);
8242:                let right:u128 = (value0 & 0xffffffffffffffff) * (value1 & 0xffffffffffffffff);
8254:                let a:u128 = (value0 & 0xffffffff) * (value1 & 0xffffffff);
8255:                let b:u128 = (value0 & 0xffffffff00000000) * (value1 & 0xffffffff00000000);
8256:                let c:u128 = (value0 & 0xffffffff0000000000000000) * (value1 & 0xffffffff0000000000000000);
8257:                let d:u128 = (value0 & 0xffffffff000000000000000000000000) * (value1 & 0xffffffff000000000000000000000000);
8271:                let r128:u128 = (value0 & 0xffffffffffffffff0000000000000000) + result as u128;
8282:                let r128:u128 = (value0 & 0xffffffffffffffffffffffff00000000) + result as u128;

i could be wrong but if mov is going to call get_operand_value which returns Option<u64>, 32-bit numbers are going to be getting cast from 32-bits to 64-bits

then, we call set_operand_value which takes u64, so we aren't capable of setting u32 value on mov

am i wrong? i might be. otherwise it looks like we need get/set operand value functions for u8/u16/u32/u64?

internally everything is u64 specially addresses and registers, even in 32bits mode, otherwise would be crazy, I think is the way.
get_operand_value() and set_operand_value() are key functions that saves a lot of work.

Ok seems logic to do get_ax() -> u16
but how get_operand_value() can return different types every time?
and how set_operand_value() can receive parameters of different types every time?

look what simple is in this way, it figures out the proper getter and setter and all of them compatible because are u64:

Mnemonic::Mov => {                                                 
                self.show_instruction(&self.colors.light_cyan, &ins);
                                            
                assert!(ins.op_count() == 2);   
                                              
                let value1 = match self.get_operand_value(&ins, 1, true) {
                    Some(v) => v,             
                    None => return,           
                };                            
                               
                if !self.set_operand_value(&ins, 0, value1) {                             
                    return;                         
                }                         
            }

if the problem is that this doesn't clear the value:

macro_rules! set_reg32 {
($reg:expr, $val:expr) => (
$reg &= 0xffffffff00000000;
$reg |= ($val & 0x00000000ffffffff);
)
}

should be how it was: (or I'm not understanding something?)

macro_rules! set_reg32 {
($reg:expr, $val:expr) => (
$reg = ($val & 0x00000000ffffffff);
)
}

You are most likely right, but I am curious what you think is resolution so we can fix this:

DTS9_PatcherV.exe RIP 144ed4a17 mov ebp,ebx:

x64dbg: rbp: FFFFFFFFFFFFF468-> 7FFE0000

scemu: rbp: fffffffffffff468 -> ffffffff7ffe0000

image

are we missing some kind of implied zero extending that is going on moving ebx into ebp in 64-bit mode?

In 64bit-mode the 32bits operations clear the higher bits, 16bits or 8bits operations don't clear, keep higher bits.
so set_reg32 macro has to clear higher bits for both 64bits-mode and 32bits-mode. This should fix this mov.

in local with new set_reg32:

0x144ed49fe: bswap r9w // this returns zero
...
0x144ed4a11: movzx bx,r9b // keeps higher value 7ffe0000 (but scemu started rbx with zero)
...
0x144ed4a17: mov ebp,ebx // movs zero to ebp

0x7ffe is just a residual value based on the initialization on the debugger is not modified before.

(but scemu started rbx with zero)

pub fn init_registers64(&mut self) {
        // set all to 0
        self.regs.rax = 0x0000000000000000;
        self.regs.rbx = 0x0000000000000000;
        self.regs.rcx = 0x0000000000000000;
        self.regs.rdx = 0x0000000000000000;
        self.regs.rsp = 0x0000000000000000;
        self.regs.rbp = 0x0000000000000000;
        self.regs.rsi = 0x0000000000000000;
        self.regs.rdi = 0x0000000000000000;
        self.regs.r8 = 0x0000000000000000;
        self.regs.r9 = 0x0000000000000000;
        self.regs.r10 = 0x0000000000000000;
        self.regs.r11 = 0x0000000000000000;
        self.regs.r12 = 0x0000000000000000;
        self.regs.r13 = 0x0000000000000000;
        self.regs.r14 = 0x0000000000000000;
        self.regs.r15 = 0x0000000000000000;
        // DTS9_PatcherV.exe
        self.regs.rax = 0x00000001448A76A4;
        self.regs.rbx = 0x000000007FFE0385;
        self.regs.rcx = 0x0000000140000000;
        self.regs.rdx = 0x0000000000000001;
        self.regs.rsi = 0x0000000000000001;
        self.regs.rdi = 0x000000007FFE0384;
        self.regs.r10 = 0x000000007FFE0384;
        self.regs.r11 = 0x0000000000000246;
        self.regs.r12 = 0x00000001448A76A4;
        self.regs.r14 = 0x0000000140000000;
    }

are you keeping these in mind?

ah ok, rethinking :)

same for this

 pub fn init_stack64(&mut self) {
        let stack = self.maps.get_mem("stack");

        //self.regs.rsp = 0x22e000;
        //self.regs.rbp = 0x22f000;
        //stack.set_base(0x22a000);
        //stack.set_size(0x6000);

        self.regs.rsp = 0x000000000014F4B0;
        self.regs.rbp = 0x0000000000000000;
        stack.set_base(0x0000000000149000);
        stack.set_size(0x0000000000007000);

        //assert!(self.regs.rsp < self.regs.rbp);
        //assert!(self.regs.rsp > stack.get_base());
        //assert!(self.regs.rsp < stack.get_bottom());
        //assert!(self.regs.rbp > stack.get_base());
        //assert!(self.regs.rbp < stack.get_bottom());
        //assert!(stack.inside(self.regs.rsp));
        //assert!(stack.inside(self.regs.rbp));
    }

23 0x144ed4a17: mov ebp,ebx
=>r ebp
ebp: 0x7ffe0000

do r rbp

GOOD
x64dbg: rbp: FFFFFFFFFFFFF468-> 7FFE0000

BAD
scemu: rbp: fffffffffffff468 -> ffffffff7ffe0000

nope, is because the setter

this is the correct setter, I don't remember why we changed it, but 32bits has to clear high bits always.

macro_rules! set_reg32 {                                                                                                                                            
    ($reg:expr, $val:expr) => (                                                                                                                                     
        $reg = ($val & 0x00000000ffffffff);                                                                                                                         
    )                                                                                                                                                               
} 

ok, we'll go with that

goes back to original issue. mov is now ok again, but cdq broken

[
    {
      "message": "registerName mismatch",
      "i": 27,
      "x64dbgLine": {
        "rawLine": {
          "Index": "0001B",
          "Address": "0000000144FF9544",
          "Bytes": "99",
          "Disassembly": "cdq ",
          "Registers": "rdx: 1-> 0",
          "Memory": "",
          "Comments": ""
        },
        "rip": "144ff9544",
        "registerChanges": [
          {
            "registerName": "rdx",
            "previousValue": "1",
            "newValue": "0"
          }
        ],
        "memoryChanges": [
          ""
        ]
      },
      "scemuLine": {
        "rip": "144ff9544",
        "registerChanges": [
          {
            "registerName": "rax",
            "previousValue": "1448a76a4",
            "newValue": "448a76a4"
          }
        ],
        "memoryChanges": []
      }
    },
    {
      "message": "newValue mismatch",
      "i": 27,
      "x64dbgLine": {
        "rawLine": {
          "Index": "0001B",
          "Address": "0000000144FF9544",
          "Bytes": "99",
          "Disassembly": "cdq ",
          "Registers": "rdx: 1-> 0",
          "Memory": "",
          "Comments": ""
        },
        "rip": "144ff9544",
        "registerChanges": [
          {
            "registerName": "rdx",
            "previousValue": "1",
            "newValue": "0"
          }
        ],
        "memoryChanges": [
          ""
        ]
      },
      "scemuLine": {
        "rip": "144ff9544",
        "registerChanges": [
          {
            "registerName": "rax",
            "previousValue": "1448a76a4",
            "newValue": "448a76a4"
          }
        ],
        "memoryChanges": []
      }
    }
  ],

#22 is done, scemu now uses libscemu.

28 0x144ff9544: cdq
--- console ---
=>r rdx
rdx: 0x1 1
=>
29 0x144ff9544: cdq
=>r rdx
rdx: 0x0 0

btw the stepping from console has to be fixed

27 0x144ff9543: push rdx ;0x1
diff_reg: rip = 144ff9543 rsp 22df98 -> 22df90;
rax: 0x1448a76a4 rbx: 0x7ffe0000 rcx: 0x140000000 rdx: 0x1 rsi: 0xa4 rdi: 0x7ffe0384 rbp: 0x7ffe0000
28 0x144ff9544: cdq
diff_reg: rip = 144ff9544 rax 1448a76a4 -> 448a76a4; rdx 1 -> 0;
rax: 0x448a76a4 rbx: 0x7ffe0000 rcx: 0x140000000 rdx: 0x0 rsi: 0xa4 rdi: 0x7ffe0384 rbp: 0x7ffe0000

In November I'm gonna do a presentation of scemu and libscemu on NcN security congress,
https://www.noconname.org/

27 0x144ff9543: push rdx ;0x1 diff_reg: rip = 144ff9543 rsp 22df98 -> 22df90; rax: 0x1448a76a4 rbx: 0x7ffe0000 rcx: 0x140000000 rdx: 0x1 rsi: 0xa4 rdi: 0x7ffe0384 rbp: 0x7ffe0000 28 0x144ff9544: cdq diff_reg: rip = 144ff9544 rax 1448a76a4 -> 448a76a4; rdx 1 -> 0; rax: 0x448a76a4 rbx: 0x7ffe0000 rcx: 0x140000000 rdx: 0x0 rsi: 0xa4 rdi: 0x7ffe0384 rbp: 0x7ffe0000

28 0x144ff9544: cdq
diff_reg: rip = 144ff9544 rax 1448a76a4 -> 448a76a4; rdx 1 -> 0;
rax: 0x448a76a4 rbx: 0x7ffe0000 rcx: 0x140000000 rdx: 0x0 rsi: 0xa4 rdi: 0x7ffe0384 rbp: 0x7ffe0000

you see how here we are getting rid of the 1 in the front? that is from 64-bit register i believe

i think of it like this

0x00000001_448a76a4

we need to not drop the upper 0x00000001

what do you think?

Ok what is happening is that CDQ is really "CDQ ax" a 16bits operation (sign-extending to EDX:EAX)
edx has to be cleared but eax nope, I cannot use the 32bits setter in this case.

or we can prepare two different setters clearing and without clearing.

ok fixed on lib, published new version, and updated lib version on scemu.

rdx looks good on cdq but rax is still an issue with the upper 00000001 getting dropped / turned into 00000000

{
    "i": 27,
    "x64dbgLine": {
      "rawLine": {
        "Index": "0001B",
        "Address": "0000000144FF9544",
        "Bytes": "99",
        "Disassembly": "cdq ",
        "Registers": "rdx: 1-> 0",
        "Memory": "",
        "Comments": ""
      },
      "rip": "144ff9544",
      "registerChanges": [
        {
          "registerName": "rdx",
          "previousValue": "1",
          "newValue": "0"
        }
      ],
      "memoryChanges": []
    },
    "scemuLine": {
      "rawLine": "diff_reg: rip = 144ff9544 rax 1448a76a4 -> 448a76a4; rdx 1 -> 0;",
      "rip": "144ff9544",
      "registerChanges": [
        {
          "registerName": "rax",
          "previousValue": "1448a76a4",
          "newValue": "448a76a4"
        },
        {
          "registerName": "rdx",
          "previousValue": "1",
          "newValue": "0"
        }
      ],
      "memoryChanges": []
    },
    "instructionErrors": [
      {
        "index": 0,
        "message": "unmatchedRegisterChange mismatch (scemu but not x64dbg)",
        "scemu": "rax"
      }
    ]
  },