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:
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.