통화 스택은 누가
Elastic의 주요 Windows 엔드포인트 원격 분석 차별화 요소 중 하나는 호출 스택입니다.
대부분의 탐지는 현재 일어나는 일에 의존하는데, 대부분의 행동이 이중 목적을 가지고 있기 때문에 이 방법으로는 불충분한 경우가 많습니다. 통화 스택을 사용하면 누가 활동을 수행하는지도 확인할 수 있는 세분화된 기능이 추가됩니다. 이 조합은 악성 활동을 발견할 수 있는 독보적인 능력을 제공합니다. 이 심층적인 원격 분석을 Elastic Defend의온-호스트 규칙 엔진에 제공함으로써 새로운 위협에 신속하게 대응할 수 있습니다.
통화 스택은 아름다운 거짓말
컴퓨터 과학에서 스택은 라스트 인, 퍼스트 아웃 데이터 구조입니다. 실제 아이템 스택과 마찬가지로 맨 위에 있는 요소만 추가하거나 제거할 수 있습니다. 호출 스택은 현재 활성화된 서브루틴 호출에 대한 정보를 담고 있는 스택입니다.
x64 호스트에서 이 호출 스택은 인텔 LBR, 인텔 BTS, 인텔 AET, 인텔 IPT 및 x64 아키텍처 LBR과 같은 CPU의 실행 추적 기능을 통해서만 정확하게 생성할 수 있습니다. 이러한 추적 기능은 성능 프로파일링 및 디버깅 목적으로 설계되었지만 일부 보안 시나리오에서도 사용할 수 있습니다. 그러나 더 일반적으로 사용할 수 있는 것은 스택 워킹이라는 메커니즘을 통해 스레드의 데이터 스택에서 복구되는 대략적인 호출 스택입니다.
x64 아키텍처에서'스택 포인터 레지스터'(rsp
)는 당연히 스택 데이터 구조를 가리키며, 이 스택의 데이터를 읽고 쓰는 효율적인 명령어가 있습니다. 또한 call
명령은 제어권을 새 서브루틴으로 전송하지만 스택 포인터가 참조하는 메모리 주소에 반환 주소도 저장합니다. ret
명령어는 나중에 이 저장된 주소를 검색하여 실행이 중단된 지점으로 돌아갈 수 있도록 합니다. 대부분의 프로그래밍 언어의 함수는 일반적으로 이 두 가지 명령어를 사용하여 구현되며, 함수 매개변수와 로컬 함수 변수는 성능을 위해 이 스택에 할당되는 것이 일반적입니다. 단일 함수와 관련된 스택 부분을 스택 프레임이라고 합니다.
스택 워킹은 스레드 스택에 저장된 이기종 데이터에서 반환 주소만 복구하는 작업입니다. 반환 주소는 제어 흐름을 위해 어딘가에 저장되어야 하므로 스택 워킹은 이 기존 데이터를 채택하여 호출 스택을 근사화합니다. 이는 대부분의 디버깅 및 성능 프로파일링 시나리오에는 전적으로 적합하지만 보안 감사에는 약간 덜 유용합니다. 가장 큰 문제는 거꾸로 분해할 수 없다는 것입니다. 지정된 통화 사이트의 리턴 주소는 언제든지 결정할 수 있지만 그 반대는 불가능합니다. 가장 좋은 방법은 15 가능한 각 선행 인스트럭션 길이를 확인하고 정확히 하나의 호출 인스트럭션으로 분해되는 것을 확인하는 것입니다. 이 경우에도 복구된 것은 이전 통화 사이트일 뿐, 반드시 정확한 이전 통화 사이트가 아니어야 합니다. 이는 대부분의 컴파일러가 테일 호출 최적화를 사용하여 불필요한 스택 프레임을 생략하기 때문입니다. 이로 인해 Win32StartAddress 함수가 호출되더라도 스택에 있다는 보장이 없는 등 보안상 성가신 시나리오가 발생 하게 됩니다.
따라서 우리가 일반적으로 호출 스택이라고 부르는 것은 실제로는 리턴 주소 스택입니다.
멀웨어 작성자는 이러한 모호함을 이용해 거짓말을 합니다. 이들은 합법적인 모듈을 통해 트램폴린 스택 프레임을 만들어 악성 코드에서 시작된 호출을 숨기거나, 스택 워킹이 CPU가 실행할 반환 주소와 다른 반환 주소를 예측하도록 강요합니다. 물론 멀웨어는 항상 거짓말을 하려는 시도일 뿐이며 안티멀웨어는 그 거짓말을 폭로하는 과정일 뿐입니다.
"... 하지만 결국 진실은 밝혀질 것입니다."
- 윌리엄 셰익스피어, 베니스의 상인, 2막 2장 2절
통화 스택을 아름답게 만들기
지금까지 스택 워크는 숫자 메모리 주소의 목록일 뿐입니다. 분석에 유용하게 사용하려면 컨텍스트를 보강해야 합니다. (참고: 현재 커널 스택 프레임은 포함되지 않습니다.)
최소한의 유용한 보강은 이러한 주소를 모듈 내에서 오프셋으로 변환하는 것입니다(예 ntdll.dll+0x15c9c4
). 하지만 이 방법은 가장 심각한 멀웨어만 잡아낼 수 있으며, 더 깊이 파고들 수 있습니다. Windows에서 가장 중요한 모듈은 네이티브 및 Win32 API를 구현하는 모듈입니다. 이러한 API의 애플리케이션 바이너리 인터페이스를 사용하려면 각 함수의 이름이 포함된 모듈의 내보내기 디렉터리에 포함되어야 합니다. 이것은 현재 Elastic이 엔드포인트 호출 스택을 보강하는 데 사용하는 정보입니다.
공급업체의 인프라(특히 Microsoft)에서 호스팅되는 공개 심볼(사용 가능한 경우)을 사용하면 더 정확한 보강을 달성할 수 있지만, 이 방법은 더 높은 충실도를 제공하지만 운영 비용이 많이 들며 에어 갭이 있는 고객에게는 적합하지 않습니다.
Microsoft 커널 및 기본 심볼에 대한 경험 법칙은 각 구성 요소의 내보낸 인터페이스에 Ldr, Tp 또는 Rtl과 같은 대문자로 된 접두사가 있다는 것입니다. 비공개 함수는 이 접두사를 p로 확장합니다. 기본적으로 외부 링크가 있는 비공개 함수는 공개 심볼 테이블에 포함됩니다. 오프셋이 매우 크면 매우 큰 함수를 나타낼 수도 있지만 기호가 없는 이름 없는 함수를 나타낼 수도 있습니다. 일반적인 지침은 내보낸 함수에서 세 자리 이상의 오프셋이 있으면 다른 함수에 속할 가능성이 있는 것으로 간주하는 것입니다.
통화 스택 | 스택 워크 | 스택 워크 모듈 | 스택 워크 내보내기(탄력적 접근 방식) | 스택 워크 공개 기호 |
---|---|---|---|---|
0x7FFB8EB9C9C2 0x12D383F0046 0x7FFB8EB1A9D8 0x7FFB8EB1AAF4 0x7FFB8EA535FF 0x7FFB8DA5E8CF 0x7FFB8EAF14EB | 0x7FFB8EB9C9C4 0x7FFB8C3C71D6 0x7FFB8EB1A9ED 0x7FFB8EB1AAF9 0x7FFB8EA53604 0x7FFB8DA5E8D4 0x7FFB8EAF14F1 | ntdll.dll+0x15c9c4 kernelbase.dll+0xc71d6 ntdll.dll+0xda9ed ntdll.dll+0xdaaf9 ntdll.dll+0x13604 kernel32.dll+0x2e8d4 ntdll.dll+0xb14f1 | ntdll.dll!NtProtectVirtualMemory+0x14 kernelbase.dll!VirtualProtect+0x36 ntdll.dll!RtlAddRefActivationContext+0x40d ntdll.dll!RtlAddRefActivationContext+0x519 ntdll.dll!RtlAcquireSRWLockExclusive+0x974 kernel32.dll!BaseThreadInitThunk+0x14 ntdll.dll!RtlUserThreadStart+0x21 | ntdll.dll!NtProtectVirtualMemory+0x14 kernelbase.dll!VirtualProtect+0x36 ntdll.dll!RtlTpTimerCallback+0x7d ntdll.dll!TppTimerpExecuteCallback+0xa9 ntdll.dll!TppWorkerThread+0x644 kernel32.dll!BaseThreadInitThunk+0x14 ntdll.dll!RtlUserThreadStart+0x21 |
통화 스택 강화 수준 비교
위 예시에서 0x12d383f0000의 셸코드는 의도적으로 꼬리 호출을 사용하여 스택 워크에 주소가 나타나지 않도록 했습니다. 이 거짓말은 스토킹 워크만 봐도 알 수 있습니다. 악성 코드가 타이머 콜백 함수를 등록하여 다른 스레드에서 VirtualProtect
로의 호출을 프록시하기 때문에 Elastic은 proxy_call
휴리스틱으로 이를 보고합니다.
강력한 통화 스택 만들기
Windows용 이벤트 추적 (ETW)으로 모니터링하는 시스템 호출의 호출 스택은 예상되는 구조를 가지고 있습니다. 스택의 맨 아래에는 StartAddress 스레드(일반적으로 ntdll.dll!RtlUserThreadStart)가 있습니다. 그 다음에는 Win32 API 스레드 항목인 kernel32.dll!BaseThreadInitThunk와 첫 번째 사용자 모듈이 이어집니다. 사용자 모듈은 Win32(또는 네이티브) API의 일부가 아닌 애플리케이션 코드입니다. 이 첫 번째 사용자 모듈은 스레드의 Win32StartAddress와 일치해야 합니다(해당 함수가 테일 호출을 사용하지 않는 한). 최종 사용자 모듈이 네이티브 API를 호출하는 Win32 API를 호출할 때까지 더 많은 사용자 모듈이 이어지며, 최종적으로 커널에 대한 시스템 호출이 이루어집니다.
탐지 관점에서 볼 때 이 호출 스택에서 가장 중요한 모듈은 최종 사용자 모듈입니다. Elastic은 이 모듈의 해시 및 모든 코드 서명을 포함하여 이 모듈을 표시합니다. 이러한 세부 정보는 알림 분류에 도움이 되지만, 더 중요한 것은 멀웨어처럼 동작하는 정상 소프트웨어의 동작을 기준으로 삼을 수 있는 세분성을 크게 향상시킨다는 점입니다. 정상에 대한 기준을 더 정확하게 설정할수록 멀웨어가 숨어들기가 더 어려워집니다.
{
"process.thread.Ext": {
"call_stack_summary": "ntdll.dll|kernelbase.dll|file.dll|rundll32.exe|kernel32.dll|ntdll.dll",
"call_stack": [
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtAllocateVirtualMemory+0x14" }, /* Native API */
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualAllocExNuma+0x62" }, /* Win32 API */
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualAllocEx+0x16" }, /* Win32 API */
{
"symbol_info": "c:\\users\\user\\desktop\\file.dll+0x160d8b", /* final user module */
"callsite_trailing_bytes": "488bf0488d4d88e8197ee2ff488bc64883c4685b5e5f415c415d415e415f5dc390909090905541574156415541545756534883ec58488dac2490000000488b71",
"callsite_leading_bytes": "088b4d38894c2420488bca48894db8498bd0488955b0458bc1448945c4448b4d3044894dc0488d4d88e8e77de2ff488b4db8488b55b0448b45c4448b4dc0ffd6"
},
{ "symbol_info": "c:\\users\\user\\desktop\\file.dll+0x7b429" },
{ "symbol_info": "c:\\users\\user\\desktop\\file.dll+0x44a9" },
{ "symbol_info": "c:\\users\\user\\desktop\\file.dll+0x5f58" },
{ "symbol_info": "c:\\windows\\system32\\rundll32.exe+0x3bcf" },
{ "symbol_info": "c:\\windows\\system32\\rundll32.exe+0x6309" }, /* first user module - typically the ETHREAD.Win32StartAddress module */
{ "symbol_info": "c:\\windows\\system32\\kernel32.dll!BaseThreadInitThunk+0x14" }, /* Win32 API */
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlUserThreadStart+0x21" /* Native API - the ETHREAD.StartAddress module */
}
],
"call_stack_final_user_module": {
"path": "c:\\users\\user\\desktop\\file.dll",
"code_signature": [ { "exists": false } ],
"name": "file.dll",
"hash": { "sha256": "0240cc89d4a76bafa9dcdccd831a263bf715af53e46cac0b0abca8116122d242" }
}
}
}
강화된 통화 스택 샘플
콜 스택 최종 사용자 모듈 강화:
name | 호출_스택_최종_사용자_모듈의 파일 이름입니다. 개인 실행 메모리를 나타내는 "지원되지 않음" 또는 의심스러운 호출 스택을 나타내는 "미확인" 일 수도 있습니다. |
---|---|
경로 | 호출_스택_최종_사용자_모듈의 파일 경로입니다. |
hash.sha256 | 호출_스택_최종_사용자_모듈의 sha256 또는 보호_프로바이넌스 모듈이 있는 경우 보호_프로바이넌스 모듈입니다. |
코드_서명 | 호출_스택_최종_사용자_모듈의 코드 서명 또는 보호_출처 모듈(있는 경우)의 코드 서명입니다. |
할당_개인_바이트 | 이 메모리 영역에서 +X와 공유할 수 없는 바이트 수입니다. 0이 아닌 값은 코드 후킹, 패치 또는 중공을 나타낼 수 있습니다. |
protection | 페이지의 작동 영역에 대한 메모리 보호는 RX가 아닌 경우 포함됩니다. MEMORY_BASIC_INFORMATION.Protect에 해당합니다. |
protection_provenance | 이 페이지의 보호를 마지막으로 수정한 메모리 영역의 이름입니다. "지원되지 않는" 셸코드를 나타낼 수 있습니다. |
보호_출처_경로 | 이 페이지의 보호 기능을 마지막으로 수정한 모듈의 경로입니다. |
이유 | "미확인" 보호_출처로 이어진 비정상적인 call_stack_summary. |
빠른 통화 스택 용어집
콜 스택을 검토할 때 알아두면 도움이 되는 몇 가지 네이티브 API 함수가 있습니다. 현재 Microsoft의 Ken Johnson이 NTDLL 커널 모드에서 사용자 모드 콜백으로 전환하는 카탈로그를 제공해 주었습니다. 여기서 잠시 멈추고 먼저 읽어보시기 바랍니다.
앞서 RtlUserThreadStart를 만났습니다. 이 메서드와 형제 메서드인 RtlUserFiberStart는 모두 호출 스택의 맨 아래에만 표시되어야 합니다. 이들은 각각 사용자 스레드와 파이버의 진입점입니다. 그러나 모든 스레드의 첫 번째 인스트럭션은 실제로 LdrInitializeThunk입니다. 스레드 초기화의 사용자 모드 컴포넌트(및 필요한 경우 프로세스)를 수행한 후, 이 함수는 명령 포인터를 직접 업데이트하는 NtContinue를 통해 진입점으로 제어권을 전송합니다. 이는 향후 스택 워크에 나타나지 않음을 의미합니다.
따라서 LdrInitializeThunk가 포함된 호출 스택이 표시되면 스레드 실행의 맨 처음에 있다는 뜻입니다. 심 엔진이 작동하는 애플리케이션 호환성, 후크 기반 보안 제품이 자체 설치를 선호하고 멀웨어가 다른 보안 제품보다 먼저 실행을 시도하는 곳이 바로 이 곳입니다. Marcus Hutchins와 Guido Miggelenbrink는 이 주제에 대한 훌륭한 블로그를 작성했습니다. 원격 측정을 위해 커널 ETW를 활용하는 보안 제품에는 이러한 스타트업 경쟁이 존재하지 않습니다.
{
"process.thread.Ext": {
"call_stack_summary": "ntdll.dll|file.exe|ntdll.dll",
"call_stack": [
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!ZwProtectVirtualMemory+0x14" },
{ "symbol_info": "c:\\users\\user\\desktop\\file.exe+0x1bac8" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlAnsiStringToUnicodeString+0x3cb" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitShimEngineDynamic+0x394d" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0x1db" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0x63" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0xe" }
],
"call_stack_final_user_module": {
"path": "c:\\users\\user\\desktop\\file.exe",
"code_signature": [ { "exists": false } ],
"name": "file.exe",
"hash": { "sha256": "a59a7b56f695845ce185ddc5210bcabce1fff909bac3842c2fb325c60db15df7" }
}
}
}
사전 진입점 실행 예시
다음 쌍은 KiUserExceptionDispatcher와 KiRaiseUserExceptionDispatcher입니다. 커널은 사용자 모드 예외 조건이 발생한 후 전자를 사용하여 등록된 사용자 모드 구조화된 예외 처리기로 실행을 전달합니다. 후자는 또한 예외를 발생시키지만 대신 커널을 대신하여 발생합니다. 이 두 번째 변형은 일반적으로 애플리케이션 검증기를 포함한 디버거에서만 발견되며, 사용자 모드 코드가 시스템 호출의 반환 코드를 충분히 확인하지 않는 경우를 식별하는 데 도움이 됩니다. 이러한 함수는 일반적으로 애플리케이션별 크래시 처리 또는 Windows 오류 보고와 관련된 호출 스택에서 볼 수 있습니다. 그러나 때때로 멀웨어는 시스템 호출 직후 셸코드를 다시 숨기기 위해 메모리 보호를 변동하려는 경우와 같이 의사 브레이크포인트로 이를 사용하기도 합니다.
{
"process.thread.Ext": {
"call_stack_summary": "ntdll.dll|file.exe|ntdll.dll|file.exe|kernel32.dll|ntdll.dll",
"call_stack": [
{
"symbol_info": "c:\\windows\\system32\\ntdll.dll!ZwProtectVirtualMemory+0x14",
"protection_provenance": "file.exe", /* another vendor's hooks were unhooked */
"allocation_private_bytes": 8192
},
{ "symbol_info": "c:\\users\\user\\desktop\\file.exe+0xd99c" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlInitializeCriticalSectionAndSpinCount+0x1c6" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlWalkFrameChain+0x1119" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!KiUserExceptionDispatcher+0x2e" },
{ "symbol_info": "c:\\users\\user\\desktop\\file.exe+0x12612" },
{ "symbol_info": "c:\\windows\\system32\\kernel32.dll!BaseThreadInitThunk+0x14" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlUserThreadStart+0x21" }
],
"call_stack_final_user_module": {
"name": "file.exe",
"path": "c:\\users\\user\\desktop\\file.exe",
"code_signature": [ { "exists": false }],
"hash": { "sha256": "0e5a62c0bd9f4596501032700bb528646d6810b16d785498f23ef81c18683c74" }
}
}
}
예외 처리기를 통한 보호 변동 예시
다음은 사용자 APC를 전달하는 데 사용되는 KiUserApcDispatcher입니다. Microsoft는 이러한 도구의 사용에 대한 가시성을 제한적으로만 제공하기 때문에 멀웨어 제작자들이 가장 선호하는 도구 중 하나입니다.
{
"process.thread.Ext": {
"call_stack_summary": "ntdll.dll|kernelbase.dll|ntdll.dll|kernelbase.dll|cronos.exe",
"call_stack": [
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtProtectVirtualMemory+0x14" },
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualProtect+0x36" }, /* tail call */
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!KiUserApcDispatcher+0x2e" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!ZwDelayExecution+0x14" },
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!SleepEx+0x9e" },
{
"symbol_info": "c:\\users\\user\\desktop\\file.exe+0x107d",
"allocation_private_bytes": 147456, /* stomped */
"protection": "RW-", /* fluctuation */
"protection_provenance": "Undetermined", /* proxied call */
"callsite_leading_bytes": "010000004152524c8d520141524883ec284150415141baffffffff41525141ba010000004152524c8d520141524883ec284150b9ffffffffba0100000041ffe1",
"callsite_trailing_bytes": "4883c428c3cccccccccccccccccccccccccccc894c240857b820190000e8a10c0000482be0488b052fd101004833c44889842410190000488d84243014000048"
}
],
"call_stack_final_user_module": {
"name": "Undetermined",
"reason": "ntdll.dll|kernelbase.dll|ntdll.dll|kernelbase.dll|file.exe"
}
}
}
APC 예시를 통한 보호 변동
Windows 창 관리자는 커널 모드 장치 드라이버(win32k.sys)로 구현됩니다. 대부분. 때때로 창 관리자는 사용자 모드에서 무언가를 수행해야 하는데, KiUserCallbackDispatcher는 이를 위한 메커니즘입니다. 기본적으로 user32.dll 함수를 대상으로 하는 리버스 시스콜입니다. 프로세스의 KernelCallbackTable 항목을 덮어쓰는 것은 GUI 스레드를 탈취하는 쉬운 방법이므로 이 호출을 따르는 다른 모듈은 의심스러운 것입니다.
이러한 커널 모드에서 사용자 모드에 이르는 각 진입점의 목적을 알면 주어진 호출 스택이 자연스러운 것인지 아니면 다른 목표를 달성하기 위해 오용된 것인지 판단하는 데 큰 도움이 됩니다.
통화 스택을 이해하기 쉽게 만들기
또한, 이해를 돕기 위해 이벤트에 식별되는 다양한 process.Ext.api.behaviors로 태그를 지정합니다. 이러한 행동이 반드시 악의적인 것은 아니지만, 경보 분류 또는 위협 헌팅과 관련된 측면을 강조합니다. 통화 스택의 경우 여기에는 다음이 포함됩니다:
native_api | Win32 API가 아닌 네이티브 API를 직접 호출했습니다. |
---|---|
직접_시스콜 | A syscall instruction originated outside of the Native API layer. |
프록시_통화 | 호출 스택은 실제 소스를 가리기 위해 프록시된 API 호출을 나타낼 수 있습니다. |
Shellcode | 민감한 API라고 하는 2세대 실행 가능한 비이미지 메모리. |
image_indirect_call | An entry in the call stack was preceded by a call to a dynamically resolved function. |
image_rop | 호출 스택의 항목 앞에 호출 명령이 없습니다. |
image_rwx | 호출 스택의 항목은 쓰기 가능합니다. 코드는 읽기 전용이어야 합니다. |
unbacked_rwx | 호출 스택의 항목은 이미지가 아니며 쓰기 가능합니다. JIT 코드도 읽기 전용이어야 합니다. |
truncated_stack | The call stack seems to be unexpectedly truncated. This may be due to malicious tampering. |
일부 상황에서는 이러한 동작만으로도 멀웨어를 탐지하는 데 충분할 수 있습니다.
스푸핑 - 우회인가, 책임인가?
리턴 주소 스푸핑은 수년 동안 게임 해킹 및 멀웨어의 주요 기법으로 사용되어 왔습니다. 이 간단한 트릭을 사용하면 삽입된 코드가 합법적인 모듈의 평판을 거의 영향 없이 차용할 수 있습니다. 심층 콜 스택 검사 및 행동 기준선의 목표는 멀웨어에 이러한 무임승차를 허용하지 않는 것입니다.
공격 연구자들은 전체 통화 스택 스푸핑을 위한 접근 방식을 연구하며 이러한 노력을 지원하고 있습니다. 가장 주목할 만한 점은
- 통화 스택을 스푸핑하여 EDR을 혼동하게 만드는 스푸핑 통화 스택 작성자: William Burgess
- SilentMoonwalk: 알레산드로 마그노시, 아라시 파사, 아타나시오스체르펠리스의 동적 콜 스택 스푸퍼 구현하기
실런트문워크는 뛰어난 공격적 연구일 뿐만 아니라, 거짓말을 하면 두 배의 곤경에 처할 수 있다는 것을 보여주는 훌륭한 사례입니다. 많은 방어 회피 기법은 보안을 통한 모호성에 의존하며, 일단 연구자에게 노출되면 문제가 될 수 있습니다. 이 경우 우회 시도로 인한 탐지 기회에 대한 조언이 연구에 포함되었습니다.
{
"process.thread.Ext": {
"call_stack_summary": "ntdll.dll|kernelbase.dll|kernel32.dll|ntdll.dll",
"call_stack": [
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtAllocateVirtualMemory+0x14" },
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualAlloc+0x48" },
{
"symbol_info": "c:\\windows\\system32\\kernelbase.dll!CreatePrivateObjectSecurity+0x31",
/* 4883c438 stack desync gadget - add rsp 0x38 */
"callsite_trailing_bytes": "4883c438c3cccccccccccccccccccc48895c241057498bd8448bd2488bf94885c90f84660609004885db0f845d060900418bd14585c97411418bc14803c383ea",
"callsite_leading_bytes": "cccccccccccccccccccccccccccccc4883ec38488b4424684889442428488b442460488944242048ff15d9b21b000f1f44000085c00f8830300900b801000000"
},
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!Internal_EnumSystemLocales+0x406" },
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!SystemTimeToTzSpecificLocalTimeEx+0x2d1" },
{ "symbol_info": "c:\\windows\\system32\\kernelbase.dll!WaitForMultipleObjectsEx+0x982" },
{ "symbol_info": "c:\\windows\\system32\\kernel32.dll!BaseThreadInitThunk+0x14" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlUserThreadStart+0x21" }
],
"call_stack_final_user_module": {
"name": "Undetermined", /* gadget module resulted in suspicious call stack */
"reason": "ntdll.dll|kernelbase.dll|kernel32.dll|ntdll.dll"
}
}
}
SilentMoonwalk 호출 스택 예시
숨겨진 아티팩트를 발굴하는 표준 기법은 여러 가지 기법을 사용하여 열거하고 그 결과를 비교하여 불일치하는 부분이 있는지 확인하는 것입니다. 루트킷리빌러는 이렇게 작동합니다. 이 접근 방식은 스레드 스택을 올라 갈 뿐만 아니라 내려가는 Get-InjectedThreadEx.exe에서도 사용되었습니다.
특정 상황에서는 두 가지 방법으로 통화 스택을 복구할 수 있습니다. 불일치가 있는 경우 신뢰도가 낮은 호출 스택이 call_stack_summary_original로 전송됩니다.
{
"process.thread.Ext": {
"call_stack_summary": "ntdll.dll",
"call_stack_summary_original": "ntdll.dll|kernelbase.dll|version.dll|kernel32.dll|ntdll.dll",
"call_stack": [
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtContinue+0x12" },
{ "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0x13" }
],
"call_stack_final_user_module": {
"name": "Undetermined",
"reason": "ntdll.dll"
}
}
}
호출 스택 요약 원본 예제
모두를 위한 통화 스택
기본적으로 알림에서만 통화 스택을 찾을 수 있지만 고급 정책을 통해 이를 구성할 수 있습니다.
events.callstacks.emit_in_events | 설정하면 호출 스택이 수집되는 정기 이벤트에 호출 스택이 포함됩니다. 그렇지 않으면 행동 보호 규칙을 트리거하는 이벤트에만 포함됩니다. 이 옵션을 설정하면 데이터 용량이 크게 증가할 수 있습니다. 기본값: false |
---|
Windows 호출 스택에 대한 자세한 인사이트는 다음 Elastic Security Labs 문서에서 확인할 수 있습니다: