Contents

x64dbg tips

Conditional breakpoint

Let’s say for example we want to break every time VirtualProtect is called but only when the value of the argument related to the protection parameter is one that will add execution permission.

If we check the VirtualProtect documentation, the argument we are interested in is the third one, which will be translated to [esp+c] in the x64dbg window related to stack memory.

We can find several memory protection constants that will add execution permission. Moreover, there are modifiers that can also be used, wich increases the number of possibility.

One first approach would be to break when one of those value is encountered as a third argument to VirtualProtect calls. This would give us, including modifiers, the following list:

1
2
3
4
5
6
7
[esp+C]==0x10 || [esp+C]==0x20 || [esp+C]==0x40 || [esp+C]==0x80
// Values with PAGE_GUARD(0x100) modifier
|| [esp+C]==0x110 || [esp+C]==0x120 || [esp+C]==0x140 || [esp+C]==0x180
// Values with PAGE_NOCACHE(0x200) modifier
|| [esp+C]==0x210 || [esp+C]==0x220 || [esp+C]==0x240 || [esp+C]==0x280
// Values with PAGE_WRITECOMBINE(0x400) modifier
|| [esp+C]==0x410 || [esp+C]==0x420 || [esp+C]==0x440 || [esp+C]==0x480

To add this condition to our breakpoint on VirtualProtect:

  • Go to the Breakpoints tab
  • Edit the wanted breakpoint (Shift+F2)
  • Add the condition in the Break condition field

In our case this will give us the following:

/images/tips/x64dbg/2024-03-23-190833.png
Add a conditional breakpoint
Warning
The field’s size limit is reached, so the first approach to list all possible values is not viable.

As we are also considering modifiers, we could instead check only the first byte of the argument as this is the one that will contain the execution permission regardless of modifiers. As per the official x64dbg documentation, it is possible to read n bytes from an address with the n:[addr] syntax.

If we take the example from before and we only check the first byte of the 3rd argument, we would then have:

1
1:[esp+C]==0x10 || 1:[esp+C]==0x20 || 1:[esp+C]==0x40 || 1:[esp+C]==0x80

Once added, the condition can be seen in the Summary column:

/images/tips/x64dbg/2024-03-23-193130.png
Conditional breakpoint checking first byte of 3rd argument to VirtualProtect

To test our condition, we try it with the following C code that will do several calls to VirtualProtect with different permission values:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
#include <windows.h>

void ChangeProtection(PVOID pVirtualAddress, SIZE_T size, DWORD flNewProtect, PDWORD pflOldProtect);

int main()
{
    PVOID pVirtualAddress;
    SIZE_T size = 1024;
    DWORD pflOldProtect = 0;
    CHAR* cString = "Test";

    pVirtualAddress = VirtualAlloc(NULL,
       size,
        MEM_COMMIT,
        PAGE_READWRITE);

    if (pVirtualAddress == NULL)
    {
        printf("Failed to allocate memory with VirtualAlloc. Error: %d\n", GetLastError());
    }
    else {
        printf("The allocated memory is at address: 0x%p\n", pVirtualAddress);
    }

    memcpy(pVirtualAddress, cString, strlen(cString));

    ChangeProtection(pVirtualAddress, size, 0x04, &pflOldProtect); //PAGE_READWRITE
    ChangeProtection(pVirtualAddress, size, 0x10, &pflOldProtect); //PAGE_EXECUTE
    ChangeProtection(pVirtualAddress, size, 0x02, &pflOldProtect); //PAGE_READONLY
    ChangeProtection(pVirtualAddress, size, 0x240, &pflOldProtect); //PAGE_NOCACHE + PAGE_EXECUTE_READWRITE

    VirtualFree(pVirtualAddress, 
        size,
        MEM_DECOMMIT);

    return 0;
}

void ChangeProtection(PVOID pVirtualAddress, SIZE_T size, DWORD flNewProtect, PDWORD pflOldProtect) {
    BOOL changedProtection = VirtualProtect(pVirtualAddress, size, flNewProtect, pflOldProtect);
    if (changedProtection == TRUE) {
        printf("Successfully changed protection. New value: %#04x\n", flNewProtect);
    }
    else {
        printf("Failed to change protection for value: %#04x\n", flNewProtect);
    }
}

We only get two breaks, on value 0x10 and 0x240, which is what we wanted:

/images/tips/x64dbg/2024-03-23-193645.png
First break on PAGE_EXECUTE(0x10)
/images/tips/x64dbg/2024-03-23-193734.png
Second break on PAGE_NOCACHE (0x200)+ PAGE_EXECUTE_READWRITE (0x40)