957 views
# REP30: Handling of `Microsoft_x86_64`'s register parameter stack area ###### tags: `microsoft` `abi` `stack` `registers` `rep` `design` **Status**: `🟢 READY` ## What is this? `Microsoft_x86_64` reserves 4 stack slots to store the register arguments ([docs](https://learn.microsoft.com/en-us/cpp/build/stack-usage?view=msvc-170#stack-allocation)). We need to treat them correctly. ## Why is this important? Right now, we're emitting out-of-bound accesses on the stack, which is incorrect. ## Example [Godbolt](https://godbolt.org/z/55YWvoMPf). ```c long long use_reserved_space(long long arg) { int a = 1; return arg; } void asd() { use_reserved_space(1); } ``` Output: ```x86asm use_reserved_space PROC $LN3: mov QWORD PTR [rsp+8], rcx sub rsp, 24 mov DWORD PTR a$[rsp], 1 mov rax, QWORD PTR arg$[rsp] add rsp, 24 ret 0 use_reserved_space ENDP ``` Note how `rcx` (`arg`) is moved on the stack *below* the return address. Current output: ```c #include "types-and-globals.h" #include "helpers.h" _ABI(Microsoft_x86_64) uint64_t use_reserved_space(uint32_t argument_0) { struct _PACKED struct_1 { uint32_t offset_0; uint8_t padding_at_4[20]; } _stack; // NOTE: out-of-bound write *((generic64_t *) ((pointer_or_number64_t) &(&_stack)[1] + 8)) = argument_0; _stack.offset_0 = 1; // NOTE: out-of-bound read return *((generic64_t *) ((pointer_or_number64_t) &(&_stack)[1] + 8)); } ``` > **NOTE**: MIPS also, technically, requires the same thing, but I wasn't able to force a compiler (either gcc or clang) to use this space. They allocate it, but never store anything there. ## Design The current situation is: 1. `DetectABI` builds an initial `RawFunctionDefinition`, ignoring the stack. 2. `DetectStackSize` adds to such definition a `StackArgumentsType` `struct` with the size of stack arguments. Such `struct` includes the "register parameter stack area". This is not entirely incorrect, since one could consider that area as unused stack arguments. 3. `DataLayoutAnalysis` populates the `struct` above, creating fields for the "register parameter stack area" as well. 4. `ConvertFunctionsToCABI` upgrades the `RawFunctionDefinition` to a `CABIFunctionDefinition` with ABI `Microsoft_x86_64`. This is the problematic part. Our desired output would be: ```c #include "types-and-globals.h" #include "helpers.h" _ABI(Microsoft_x86_64) uint64_t use_reserved_space(uint32_t argument_0) { struct _PACKED struct_1 { uint32_t offset_0; uint8_t padding_at_4[20]; } _stack; struct _PACKED struct_2 { uint64_t offset_0; uint8_t padding_at_8[24]; } _stack2; _stack2.offset_0 = argument_0; _stack.offset_0 = 1; return _stack2.offset_0; } ``` We need to: * Extend [`model::Function`](https://docs.rev.ng/references/model/#Function) to have a `StackFrameType2` field (TODO: find a better name) that represents this area. * Extend `ConvertFunctionsToCABI` for `Microsoft_x86_64` so that does the following: * Move the last 4 stack slots of `RawFunctionDefintion::StackArgumentsType` to `model::Function::StackFrameType2` and consequently shrink `StackArgumentsType`. * Process the stack arguments ignoring the second part that has been moved. If any of these is not viable, we can simply bail out the conversion and stick with the `RawFunctionDefinition`. For instance, if, for some reason, we have field crossing he boundary where we want to split, we can just bail out. * Extend `SegregateStackAccesses` to emit a second variable for the `StackFrameType2`. Also, we need to extend the rest of the decompilation pipeline to emit it as well. This is pretty similar to what we are already doing with `_stack`, it shouldn't be problematic. In the future, optionally, we need to: * Extend `DataLayoutAnalysis` to report somehow how much of a `Function::StackFrameType` is read before being written to in at least one path. This will enable us to reduce false positives.