Very Basic question about PAGE_EXECUTE_READ

I was reading this article the TL;DR of which is they are patching the instructions of a loaded DLL inside of their process. It reminded me that I do it all the time in the debugger too when I want to quickly set a forced break using int 3h.

But then when I look at the VMMAP of the DLL I see that the .text section has PAGE_EXECUTE_READ , per https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constantsMSDN that means the page cannot be modified but executed. How then does this patching work?

BaseAddress: 00007ffba06b0000
RegionSize: 0000000000001000
State: 00001000 MEM_COMMIT
Protect: 00000002 PAGE_READONLY
Type: 01000000 MEM_IMAGE

BaseAddress: 00007ffba06b1000
RegionSize: 000000000000b000
State: 00001000 MEM_COMMIT
Protect: 00000020 PAGE_EXECUTE_READ
Type: 01000000 MEM_IMAGE

BaseAddress: 00007ffba06bc000
RegionSize: 0000000000006000
State: 00001000 MEM_COMMIT
Protect: 00000002 PAGE_READONLY
Type: 01000000 MEM_IMAGE

BaseAddress: 00007ffba06c2000
RegionSize: 0000000000002000
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 01000000 MEM_IMAGE

BaseAddress: 00007ffba06c4000
RegionSize: 0000000000005000
State: 00001000 MEM_COMMIT
Protect: 00000002 PAGE_READONLY
Type: 01000000 MEM_IMAGE

They’re probably changing the page protections for that page, then putting them back when they’re done.

VirtualProtect can be used to change the page protection - This is the code: https://github.com/rasta-mouse/AmsiScanBufferBypass/blob/e04cdf476afffda69aa11971aee83e0247413307/ASBBypass/Program.cs#L22

About the debugger - If you put a breakpoint, windbg debugger invokes WriteProcessMemory() to put the int3 breakpoint, and WriteProcessMemory() calls NtProtectVirtualMemory to change the page protection to PAGE_READWRITE before trying to write the int3 value - a callstack below:

 #   Call Site
00  ntdll!NtProtectVirtualMemory
01  KERNELBASE!WriteProcessMemory+0x431fc
02  dbgeng!LiveUserDebugServices::WriteVirtual+0x38
03  dbgeng!BaseX86MachineInfo::InsertBreakpointInstruction+0x14d
04  dbgeng!LiveUserTargetInfo::InsertCodeBreakpoint+0x88
05  dbgeng!CodeBreakpoint::Insert+0xa5
06  dbgeng!InsertBreakpoints+0x836
07  dbgeng!PrepareForExecution+0x58d
08  dbgeng!RawWaitForEvent+0x4f
09  dbgeng!DebugClient::WaitForEvent+0xb1
0a  windbg!EngineLoop+0x2a8
0b  KERNEL32!BaseThreadInitThunk+0x14
0c  ntdll!RtlUserThreadStart+0x21

@0xrepnz said:
VirtualProtect can be used to change the page protection - This is the code: https://github.com/rasta-mouse/AmsiScanBufferBypass/blob/e04cdf476afffda69aa11971aee83e0247413307/ASBBypass/Program.cs#L22

About the debugger - If you put a breakpoint, windbg debugger invokes WriteProcessMemory() to put the int3 breakpoint, and WriteProcessMemory() calls NtProtectVirtualMemory to change the page protection to PAGE_READWRITE before trying to write the int3 value - a callstack below:

 #   Call Site
00  ntdll!NtProtectVirtualMemory
01  KERNELBASE!WriteProcessMemory+0x431fc
02  dbgeng!LiveUserDebugServices::WriteVirtual+0x38
03  dbgeng!BaseX86MachineInfo::InsertBreakpointInstruction+0x14d
04  dbgeng!LiveUserTargetInfo::InsertCodeBreakpoint+0x88
05  dbgeng!CodeBreakpoint::Insert+0xa5
06  dbgeng!InsertBreakpoints+0x836
07  dbgeng!PrepareForExecution+0x58d
08  dbgeng!RawWaitForEvent+0x4f
09  dbgeng!DebugClient::WaitForEvent+0xb1
0a  windbg!EngineLoop+0x2a8
0b  KERNEL32!BaseThreadInitThunk+0x14
0c  ntdll!RtlUserThreadStart+0x21

Thanks for the tip, I woulsn’t expect WriteProcessmemory to do that, it is an API to write and not change bits(unlike VirtualProtect). If WriteProcessmemory is able to change whatever protection the memory owner set, then it is a bit counter intuitive isn’t it?

@Tim_Roberts said:
They’re probably changing the page protections for that page, then putting them back when they’re done.

Yes I thought so, but he article seems to not mention that at all, and the scripts used aren’t there.

If WriteProcessmemory is able to change whatever protection the memory owner set, then it is a bit counter intuitive isn’t it

Yes - I wasn’t expecting WriteProcessMemory to do this either. Remember that WriteProcessMemory is the win32 API but the native system call (NtWriteVirtualMemory) will not try to change the memory protection

Yes I thought so, but he article seems to not mention that at all, and the scripts used aren’t there.

The scripts used in the article simply load the .NET code I sent above: https://github.com/rasta-mouse/AmsiScanBufferBypass/blob/e04cdf476afffda69aa11971aee83e0247413307/ASBBypass/Program.cs#L22

As you can see they use VirtualProtect (0x40 - PAGE_EXECUTE_READWRITE)

    private static void PatchAmsi(byte[] patch)
    {
        try
        {
            var lib = Win32.LoadLibrary("amsi.dll");
            var addr = Win32.GetProcAddress(lib, "AmsiScanBuffer");

            uint oldProtect;
            Win32.VirtualProtect(addr, (UIntPtr)patch.Length, 0x40, out oldProtect);

            Marshal.Copy(patch, 0, addr, patch.Length);
        }
        catch (Exception e)
        {
            Console.WriteLine(" [x] {0}", e.Message);
            Console.WriteLine(" [x] {0}", e.InnerException);
        }
    }

I have not looked at the code myself, but that sounds like a compatibility shim - something that got added to WriteProcessMemory to help keep existing programs working as this verification became more rigorous. I can’t quite place it though, because read and execute were conflated by hardware for a long time, but I don’t remember anything about write and execute. And VirtualProtect is a fundamental API. Perhaps it had something to do with 16 bit support? But that’s a total guess

I don’t understand why you all are surprised by this. If you have permission to open another process, then there’s no particular reason for WriteProcessMemory to fail in this case. The writing process would have to that the page permissions anyway, and it already has permission to do so. It seems quite natural to me.