How to extract volatile method parameters of x64 programs
one: background
1. Storytelling
Recently, I often encounter feedback from friends on how to extract method parameters in the thread stack in the x64 environment. Friends who are familiar with the x64 calling protocol should know that under this protocol, the first four parameters of the method are passed in registers. For example, there are four registers rcx, rdx, r8d, r9d
. Due to the temporary nature of the register value, its value is easily used by the subsequent logic. Is there any way to do this in this case? What about extracting it? To be honest, it all depends on luck. Why do you say that? If this is temporarily saved in the thread stack during the stack initialization process of the method, congratulations, you can successfully fish it out.
Let’s talk in depth through a small case.
Two: Case Analysis
1. A case demonstration
For the convenience of description, here I use Marshal
to allocate heap blocks on ntheap, and then extract the user handle of the Marshal.FreeHGlobal
method. The reference code is as follows:
static void Main(string[] args)
{
//1. Allocate heap blocks
IntPtr ptr = Marshal.AllocHGlobal(sizeof(int));
Console.WriteLine("ptr= 0x{0:X2}", ptr);
//2. Write data
var num = int.MaxValue;
Marshal.WriteInt32(ptr, num);
Console.WriteLine("num has been written to ptr= 0x{0:X2} heap block", ptr);
Debugger.Break();
//3. Release the heap block
Marshal.FreeHGlobal(ptr);
Console.WriteLine("ptr= 0x{0:X2} heap block released successfully", ptr);
}
Friends who are familiar with ntheap know that if you use the FreeHGlobal
method in a debugging environment, it will hit the underlying ntdll!RtlpValidateHeap
method. Just set a breakpoint here. That’s it, the reference code is as follows:
0:000>bp ntdll!RtlpValidateHeap
0:000> g
Breakpoint 0 hit
ntdll!RtlpValidateHeap:
00007ffe`8e92a784 48895c2410 mov qword ptr [rsp+10h],rbx ss:00000021`2037e078=00007ffd00000000
0:000> k 10
# Child-SP RetAddr Call Site
00 00000021`2037e068 00007ffe`8e9295f5 ntdll!RtlpValidateHeap
01 00000021`2037e070 00007ffe`8e855cc1 ntdll!RtlDebugFreeHeap+0x99
02 00000021`2037e0d0 00007ffe`8e855b74 ntdll!RtlpFreeHeap+0xc1
03 00000021`2037e280 00007ffe`8e8547b1 ntdll!RtlpFreeHeapInternal+0x464
04 00000021`2037e340 00007ffe`8c33934f ntdll!RtlFreeHeap+0x51
05 00000021`2037e380 00007ffd`d4af5c7c KERNELBASE!LocalFree+0x2f
06 00000021`2037e3c0 00007ffd`7b132a10 System_Private_CoreLib!System.Runtime.InteropServices.Marshal.FreeHGlobal+0x4c [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.Windows.cs @ 144]
07 00000021`2037e490 00007ffd`dacaae93 Example_18_1_1!Example_18_1_1.Program.Main+0xd0 [D:\skyfly\18.20230322\src\Example\Example_18_1_1\Program.cs @ 21]
...
You can see from the code that this function is called when the heap block is released. Next, there is a requirement. I want to know what the first parameter of KERNELBASE!LocalFree
is. Some friends are sure I want to say that you can get the rcx
register, but don’t forget that at this time the code runs to the ntdll!RtlpValidateHeap
method, in rcx
The value has long been overwritten by other methods, so what should I do?
2. Is there still hope
Whether there is any hope really depends on luck. At this time, you should carefully observe the assembly code at the entrance of the KERNELBASE!LocalFree
method to see if it saves rcx in the stack. If so, If you save it to the stack, you’ll be lucky. Once you have an idea, just do it.
0:000> u KERNELBASE!LocalFree
KERNELBASE!LocalFree:
00007ffe`8c339320 48895c2410 mov qword ptr [rsp+10h],rbx
00007ffe`8c339325 4889742418 mov qword ptr [rsp+18h],rsi
00007ffe`8c33932a 48894c2408 mov qword ptr [rsp+8],rcx
00007ffe`8c33932f 57 push rdi
00007ffe`8c339330 4883ec30 sub rsp,30h
00007ffe`8c339334 488bd9 mov rbx,rcx
00007ffe`8c339337 f6c308 test bl,8
00007ffe`8c33933a 753f jne KERNELBASE!LocalFree+0x5b (00007ffe`8c33937b)
Looking at the assembly code, we are really lucky. The code saves rcx
to the stack location of rsp+8
. Next, we urgently need to know the rsp+ here. Which memory address does 8
refer to?
3. Where does rsp+8 point to?
Under the x64 platform, in order to maximize the use of registers, the method stack frame uses an rsp to mark the stack space, unlike the 32bit platform which uses ebp
and esp
Two registers to jointly carry, refer to the k
command output.
0:000> k 8
# Child-SP RetAddr Call Site
00 00000021`2037e068 00007ffe`8e9295f5 ntdll!RtlpValidateHeap
01 00000021`2037e070 00007ffe`8e855cc1 ntdll!RtlDebugFreeHeap+0x99
02 00000021`2037e0d0 00007ffe`8e855b74 ntdll!RtlpFreeHeap+0xc1
03 00000021`2037e280 00007ffe`8e8547b1 ntdll!RtlpFreeHeapInternal+0x464
04 00000021`2037e340 00007ffe`8c33934f ntdll!RtlFreeHeap+0x51
05 00000021`2037e380 00007ffd`d4af5c7c KERNELBASE!LocalFree+0x2f
06 00000021`2037e3c0 00007ffd`7b132a10 System_Private_CoreLib!System.Runtime.InteropServices.Marshal.FreeHGlobal+0x4c [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.Windows.cs @ 144]
07 00000021`2037e490 00007ffd`dacaae93 Example_18_1_1!Example_18_1_1.Program.Main+0xd0 [D:\skyfly\18.20230322\src\Example\Example_18_1_1\Program.cs @ 21]
The next question is: What is the relationship between Child-SP and rsp in the KERNELBASE!LocalFree method? To answer this question, you need to be very clear about how Child-SP
marks stack frames. Draw a picture as follows:
It can be clearly seen from the picture: Child-SP
marks the position of the first parameter in the sub-method, and the RSP at the method entrance points to the return address RIP of the method. The position is one pointer unit smaller than Child-SP
.
Some friends may ask why the RIP is located. This is because the assembly call instruction will be implicitly executed as follows.
PUSH RIP
SUB ESP,8
After having these foundations, the next step will be easier. The calculation formula is:
rsp = Child-SP - 0x8
Then rsp + 0x8 = Child-SP - 0x8 + 0x8 = Child-SP = 000000212037e3c0
, then use windbg to verify.
0:000>dp 000000212037e3c0 L1
00000021`2037e3c0 00000141`a81f1f20
0:000> !heap -x 00000141`a81f1f20
Entry User Heap Segment Size PrevSize Unused Flags
-------------------------------------------------- -------------------------------------------------- ----------
00000141a81f1f10 00000141a81f1f20 00000141a8100000 00000141a8100000 40 90 3c busy extra fill
Looking back at the output of the Console interface, it turned out to be the ptr= 0x141A81F1F20
address that I was looking for.
Three: Summary
This is a very useful experience sharing post. I believe you will definitely use it in dump analysis. Generally speaking, since the method parameters are passed through registers, whether you can successfully obtain them requires you to carefully observe the assembly code. Only then can we know.
The good things in the world can be obtained by those who believe.