0

Suppose I have a function, I know that the first 4 arguments come with fixed registers.

_BYTE *__fastcall foo(__int64 a1, _QWORD *a2, unsigned int a3, char a4, _QWORD *a5)

For the fifth one, if I move my mouse on that argument, it displays a hint

_QWORD *a5 // [rsp+120h] [rbp+28h] ISARG BYREF

BYREF: address of this variable is taken somewhere in the current function (e.g. for passing to a function call);
ISARG: shown for function arguments (in mouse hint popups);

According to documentation.The fist part of the comment is the variable location. However, rsp[0xffff940a1f436b48]+120h and rbp[0xffff940a1f436bf9]+28h are 2 different values. Does that mean the argument is stored in 2 locations? If it's stored separately, how do I know how many bytes are stored in those 2 locations?

I just tried several times. It works fine now.

1.
rsp = 0xffff86817543df49
rbp = 0xffff86817543de98

2.
rsp = 0xffff868175428b48
rbp = 0xffff868175428bf9

3.
rsp = 0xffff868175692e18
rbp = 0xffff868175692ee0
5
  • 1
    B49 is unaligned are you sure you dont have a typo. Rsp + x can be equal to rbp +y
    – blabb
    Commented Jul 19, 2023 at 11:57
  • read b49 as bf9 this rbp[0xffff940a1f436bf9] stack address will be aligned on qword boundaries
    – blabb
    Commented Jul 19, 2023 at 17:55
  • Your question is not clear. Where are the numbers 0xffff940a1f436b48 and 0xffff940a1f436bf9 coming from? A live debugging session? If so, those values are going to depend on where within the function the instruction pointer is at the time you computed those values (e.g. both of them will differ before and after the function prolog). Commented Jul 19, 2023 at 18:50
  • @blabb I just directly pasted from the dbg. If I want to view the fifth argument, which one should I use? Why IDA displays 2 hints here? Commented Jul 22, 2023 at 3:27
  • Your edit shows the values as inconsistent only one set seems to aligned and other two seems to be unaligned i wont try to interpret them further as rolf rolles has provided a comprehensive but complicated answer
    – blabb
    Commented Jul 23, 2023 at 2:49

1 Answer 1

1

You've stumbled into a complicated question. I'll try to answer it in detail, but for starters, the fact that there are two locations shown for a single stack variable does not mean that it is at two different locations. It means that Hex-Rays is using two different ways of referring to the same location on the the stack. Both shown offsets must be interpreted with care, can be highly misleading, and can also be outright wrong. I will explain all of those points below.

For the sake of discovering local variables on the stack and performing analyses that determine what memory may be overwritten by a given memory write, Hex-Rays needs a consistent way to refer to offsets on the stack. This is complicated because rsp can change throughout the function, and because not all functions use frame pointers like rbp. Thus, it has to do something else to keep track of stack offsets. So, Hex-Rays computes the lowest stack value at any point within the function, uses that as the "bottom of the stack", and performs all stack-based analysis relative to that point.

When Hex-Rays prints local variables and displays strings off to the right like [rsp+30h], that refers to Hex-Rays' internal computation of where the bottom of the stack is. Since rsp can change throughout a function, the location rsp+0x30 might mean different things at different points in the function. On x64/Windows binaries that conform to the x64 ABI, rsp will have a consistent value after the function prolog and before the epilog, so the numbers should be reasonably safe to use after the prolog and before the epilog (though I've noticed that they are sometimes off by 8). Since the prolog and the epilog change rsp, those numbers won't be meaningful during the prolog or epilog. In particular, on the first line of the function before the prolog has executed, the rsp number is especially meaningless. On x86 binaries, esp changes often throughout a function, and so those numbers will be generally unreliable as a way to locate stack variables on x86.

Meanwhile, when Hex-Rays prints a string like [rbp-38h], the 38h refers to offset within IDA's stack frame for the function. I.e., if you were to look at the top of the function, you might see something like:

.text:000000006202C6F0     var_40= qword ptr -40h
.text:000000006202C6F0     a1  = xmmword ptr -38h ; <--- HERE
.text:000000006202C6F0     var_28= qword ptr -28h
.text:000000006202C6F0     var_18= byte ptr -18h

Although the offsets line up with what's in the stack view, Hex-Rays always prints rbp, whereas x64 functions can use other non-volatile registers as frame pointers. Therefore, you won't always be able to find the value at rbp-Offset, either; you would need to substitute whatever register is being used for the frame pointer (such as r11) in place of rbp. And note that, like rsp, rbp will also change values within the function (e.g. being set in the prolog and restored in the epilog), so even if a function is using rbp as a frame pointer, the rbp-N offsets will only make sense after the prolog and before the epilog.

If you'd like more technical details about this, you should take a look at hexrays.hpp in the IDA SDK. Specifically, there is a diagram showing what Hex-Rays thinks about the stack, and how the rsp-relative numbers are related to the rbp-relative numbers. You want to search that .hpp file for tmpstk_size. It's also available online. As of the time of writing, it begins on line 4596. I've reproduced it here:

/*
                     +-----------+ <- inargtop
                     |   prmN    |
                     |   ...     | <- minargref
                     |   prm0    |
                     +-----------+ <- inargoff
                     |shadow_args|
                     +-----------+
                     |  retaddr  |
     frsize+frregs   +-----------+ <- initial esp  |
                     |  frregs   |                 |
           +frsize   +-----------+ <- typical ebp  |
                     |           |  |              |
                     |           |  | fpd          |
                     |           |  |              |
                     |  frsize   | <- current ebp  |
                     |           |                 |
                     |           |                 |
                     |           |                 | stacksize
                     |           |                 |
                     |           |                 |
                     |           | <- minstkref    |
 stkvar base off 0   +---..      |                 |    | current
                     |           |                 |    | stack
                     |           |                 |    | pointer
                     |           |                 |    | range
                     |tmpstk_size|                 |    | (what getspd() returns)
                     |           |                 |    |
                     |           |                 |    |
                     +-----------+ <- minimal sp   |    | offset 0 for the decompiler (vd)
 
  There is a detail that may add confusion when working with stack variables.
  The decompiler does not use the same stack offsets as IDA.
  The picture above should explain the difference:
  - IDA stkoffs are displayed on the left, decompiler stkoffs - on the right
  - Decompiler stkoffs are always >= 0
  - IDA stkoff==0 corresponds to stkoff==tmpstk_size in the decompiler
  - See stkoff_vd2ida and stkoff_ida2vd below to convert IDA stkoffs to vd stkoff
 
*/

TL;DR the reason Hex-Rays shows you two different representations for where a variable is on the stack is because Hex-Rays itself, and IDA, use two different ways of representing the stack frame. The rbp-N numbers refer to the offsets you see in IDA's stack view, and the rsp+N numbers refer to displacements from what Hex-Rays thinks is the bottom of the stack.

Especially considering that rsp and rbp can change throughout a function, the addresses it shows won't be meaningful at every point within the function. Particularly, none of the numbers make sense before the prolog has finished executing, or after the epilog has begun executing. The esp numbers are particularly unreliable on 32-bit x86, and Hex-Rays will show frame-relative offsets using the rbp register even if the function uses a different register like r11 for a frame pointer.

Simple, right? ;-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.