I am writing some multi-thread C program. I tried to modify the few instructions at the beginning of a function's body to redirect the execution to somewhere else.
But I noticed that when debugging within Visual Studio 2015, some memory location seems to be unchangeable as displayed in the Memory
window.
For example:
In below picture, a function ApSignalMceToOs()
begins at 0x7FFBBEE51360
. I have unprotected the memory range 0x7FFBBEE51360
to 0x7FFBBEE5136E
to modify it.
Line 305 to 312 modify the address range 0x7FFBBEE51360
~ 0x7FFBBEE5136E
.
Everything is fine until 0x7FFBBEE51369
. At line 311, the (uint32_t(((uintptr_t)dst) >> 32
is 0x00007ffb
.
After line 311 is executed, I was expecting the memory range in 0x7FFBBEE51369
~ 0x7FFBBEE5136C
will be filled as fb 7f 00 00
. But as shown below, Visual Studio says it is 48 7f 00 00
, where the 48
is the old value.
Then I went to check the disassembly code of the function ApSignalMceToOs()
. And not surprisingly, the instruction at 00007FFBBF171365
is mov dword ptr [rsp+4], 7F48h
, which should be 7FFB
. As shown below in the red box below.
So until this point, Visual Studio 2015 is telling me that my modification would fail.
But as the yellow arrow in above picture shows, after the mov dword ptr [rsp+4], 7F48h
is executed, I checked the content in the stack area. Surprisingly it is indeed 7f fb
got moved onto the stack (shown in the green box in above picture).
And after the ret
instruction is executed, the RIP
register does change to 00007FFBBEEAD940
, which is no surprise. See below:
And in another function, the same location is being read. Shown as below:
The code[len]
or byte ptr [rax]
is the memory location holding 48
or fb
. But it reads 0xcc
, which is neither 0x48
nor 0xfb
.
Visual Studio disassembly code is decoded from the memory content. So the memory content or how VS2015 read/refresh it is the key point.
Based on above observation, I came to 2 conclusions with VS 2015 debug mode:
But the program runs smoothly when not debugging.
Does anyone know why this is happening?
Thanks to @MichaelBurr. I guess I can explain it now.
The root cause is I added a breakpoint at 0x00007FFB...369
at the disassembly code level, not the C source level.
When I did this, the VS Debugger did add a 0xCC
instruction at the location 0x00007FFB...369
. But it seems Visual Studio 2015 goes to great lengths to hide this fact. Below is the show of the memory content with the breakpoint at 0x00007FFB...369
, we can see 0x00007FFB...369
still holds the old value 0x48
.
But after I manually copied the memory from 0x00007FFB...360
to 0x00007FFB...36e
to somewhere else. The 0xCC
instruction at the offset 0x9
is unveiled. See below:
When I modify the content at 0x00007FFB...369
, Visual Studio seemed to be alerted and it just restored the content to the old preserved one, i.e. 0x48
. Not my newly written one.
But I think this very restoration doesn't make any sense. The restoration of the preserved byte content shouldn't be triggered at this moment in any way. A more reasonable action is to update the breakpoint's location a little bit and insert the 0xCC instruction to a new location. Because the newly modified code may change the "instruction boundary". This way, the debug experience of the self-modifying code can be best preserved. But this will require the Visual Studio to disassemble the new code in the nearby. And the new instruction content could be invalid if the programmer made a mistake.
I think you are essentially fighting with the debugger's breakpoint/single step handling. Breakpoints are often implemented with the int 3
instruction which has the encoding 0xCC
. When the debugger sets the 0xCC
for the breakpoint it has to save the original value, then replace it when the debugger has stopped program execution.
In a normal situation (code that isn't self-modified) this makes things appear as you expect when examining the code memory region. However if your program modifies the memory that is being managed by the debugger you can get confusing results since the debugger will restore the value it had saved when it set the breakpoint (overwriting your modification).