Skip to content

Arbitrary Code Guard (ACG)

Code page'lerini immutable yapan bir process mitigation'ı: mevcut executable memory writable yapılamaz ve yeni unsigned executable memory oluşturulamaz.

Mechanism

Note

Arbitrary Code Guard (ACG, dahili olarak ProcessDynamicCodePolicy), korunan bir process için tek bir invariant'ı zorlar: code page'leri immutable'dır ve yeni hiçbir dynamic code ortaya çıkamaz. Somut olarak kernel şunu garanti eder:

  • mevcut code page'leri asla writable bir protection'a (bir image section üzerinde PAGE_EXECUTE_READWRITE / PAGE_READWRITE) geçiş yapamaz ve
  • signed bir image ile desteklenmeyen yeni executable page'ler allocate edilemez ya da executable olarak işaretlenemez.

Güvenlik hedefi, bir memory-corruption exploit'inin son aşamasını bozmaktır. Bir attacker control flow'u hijack etse bile, klasik shellcode writable+executable bir region'a (RWX allocate et, shellcode kopyala, jump) ya da mevcut bir code page'i overwrite etmeye ihtiyaç duyar. ACG her iki seçeneği de W^X katmanında ortadan kaldırır: attacker data'sı için W (write) ve X (execute) permission'ları asla bir arada bulunamaz, dolayısıyla bir exploit, zaten signed olan code'u (ROP/JOP) yeniden kullanmaya indirgenir; ACG bunu tek başına durdurmaz ama Control Flow Guard ve Code Integrity Guard gibi diğer mitigation'lar bunu hedef alır.

Policy, PROCESS_MITIGATION_DYNAMIC_CODE_POLICY (winnt.h) ile tanımlanır:

typedef struct _PROCESS_MITIGATION_DYNAMIC_CODE_POLICY {
  union {
    DWORD Flags;
    struct {
      DWORD ProhibitDynamicCode      : 1;  // 0x1: code pages immutable, no new dynamic code
      DWORD AllowThreadOptOut        : 1;  // 0x1: threads may opt out via ThreadDynamicCodePolicy
      DWORD AllowRemoteDowngrade     : 1;  // 0x1: non-AppContainer process may relax the policy
      DWORD AuditProhibitDynamicCode : 1;  // log-only mode (audit, do not block)
      DWORD ReservedFlags            : 28;
    } DUMMYSTRUCTNAME;
  } DUMMYUNIONNAME;
} PROCESS_MITIGATION_DYNAMIC_CODE_POLICY;

ProhibitDynamicCode enforce eden bit'tir. AllowThreadOptOut, belirli thread'lerin policy'yi SetThreadInformation(..., ThreadDynamicCodePolicy, ...) üzerinden gevşetmesine olanak tanır — Microsoft açıkça bu kombinasyonun güçlü güvenlikli bir konfigürasyon olmadığı konusunda uyarır; yalnızca aşamalı benimsemeyi (örn. bir JIT thread'i) kolaylaştırmak için vardır.

Walkthrough

ACG'yi mevcut process üzerinde SetProcessMitigationPolicy ve ProcessDynamicCodePolicy policy ID'si ile enable et:

#include <windows.h>
#include <stdio.h>

int main(void) {
    PROCESS_MITIGATION_DYNAMIC_CODE_POLICY dcp = {0};
    dcp.ProhibitDynamicCode = 1;

    if (!SetProcessMitigationPolicy(ProcessDynamicCodePolicy, &dcp, sizeof(dcp))) {
        printf("SetProcessMitigationPolicy failed: %lu\n", GetLastError());
        return 1;
    }
    printf("ACG enabled. Attempting to allocate RWX...\n");

    // Classic shellcode staging: allocate RWX memory.
    void *p = VirtualAlloc(NULL, 0x1000,
                           MEM_COMMIT | MEM_RESERVE,
                           PAGE_EXECUTE_READWRITE);
    if (!p) {
        printf("VirtualAlloc(PAGE_EXECUTE_READWRITE) blocked: %lu\n",
               GetLastError());
    } else {
        printf("RWX allocation succeeded at %p (ACG not enforcing!)\n", p);
    }
    return 0;
}

ACG aktif olunca beklenen çıktı — dynamic-code allocation'ı reddedilir:

ACG enabled. Attempting to allocate RWX...
VirtualAlloc(PAGE_EXECUTE_READWRITE) blocked: 1655

GetLastError(), ERROR_DYNAMIC_CODE_BLOCKED (1655) döndürür. Aynı block, mevcut bir image code page'ini PAGE_EXECUTE_READWRITE'a çevirmeye çalışan VirtualProtect'e ve ACG-protected bir target'a karşı bir remote process tarafından issue edilen VirtualAllocEx/VirtualProtectEx'e de uygulanır.

ACG, binary'yi değiştirmeden de uygulanabilir; PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON ile birlikte process-creation attribute'u PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY üzerinden ya da opt-in bir image-load flag'i olarak. Microsoft Edge content (renderer) process'leri, gerçek dünyadaki kanonik deployment'tır.

JIT uyumsuzluğu ve out-of-process JIT

ACG, in-process JIT compilation ile temelden uyumsuzdur: process içinde native kod emit eden bir JIT engine, kendi code page'lerini writable+executable yapamayacağı için çalışmayı durdurur. Microsoft Edge bunu Chakra JIT'i ayrı, sandbox'lı bir process'e taşıyarak çözdü — JIT process bytecode'u compile eder ve ortaya çıkan page'leri content process'e map eder, böylece content process kendi JIT code page'lerini asla doğrudan map etmez ya da değiştirmez. Out-of-process JIT helper'ı compromise edilebiliyorsa W^X invariant'ı dolaşılabilir; bu, ACG'nin bilinen residual risk'lerinden biridir.

Warning

ACG yalnızca korunan process'in içinde çalışan code'u kısıtlar. WriteProcessMemory hakkına sahip bir remote process, AllowRemoteDowngrade set'liyse eğer daha sonra executable olarak işaretlenen bir region'a code stage'leyebilir ya da non-ACG bir process'e inject edebilir. ACG bir katmandır, standalone bir boundary değil — CFG, CIG ve sandboxing'in yerine geçmek için değil, onlarla birlikte çalışmak için tasarlanmıştır.

Detection

ACG enforcement ve audit event'leri, Windows Event Log'da Microsoft-Windows-Security-Mitigations (Kernel Mode / User Mode channel'ları) altında görünür. ACG'yi audit modunda (AuditProhibitDynamicCode = 1) çalıştırmak, olası ihlalleri block etmeden log'lar; bu da savunmacıların enforcement'a geçmeden önce uyumluluğu ölçmesini sağlar. Get-ProcessMitigation -Name <exe>, bir process ya da image için DynamicCode / ProhibitDynamicCode state'ini raporlar.

Mitigation

Bir attacker'ın bakış açısından ACG, immutability invariant'ını yenerek değil, dynamic code'dan tamamen kaçınarak bypass edilir: signed code'u yeniden kullanan saf ROP/JOP chain'leri ile ya da opt-out yapmış bir thread'i (AllowThreadOptOut) hedef alarak. ACG'nin Control Flow Guard ve Code Integrity Guard ile eşleştirilmesinin nedeni budur; böylece reused-code path'leri de kısıtlanır.

References