
취약한 서명된 드라이버를 무기화하여 EDR 커널 콜백, 오브젝트 콜백, ETW TI 공급자 및 사용자 공간 훅을 우회하고 LSASS 메모리 덤프 및 자격 증명 추출을 수행합니다.
EDRSandBlast는 서명된 취약한 드라이버를 무기화하여 EDR 탐지(Notify Routine 콜백, Object Callbacks 및 ETW TI 공급자) 및 LSASS 보호를 우회하기 위해 C로 작성된 도구입니다. 여러 사용자 모드 언후킹 기술도 구현되어 사용자 모드 모니터링을 회피합니다.
이번 릴리즈 기준으로, 사용자 모드(--usermode)와 커널 모드(--kernelmode) 기술의 조합을 사용하여 EDR 감시 하에 LSASS 메모리를 덤프했으며, 제품(클라우드) 콘솔에서 차단되거나 "OS 자격 증명 덤핑" 관련 이벤트가 생성되지 않았습니다. 테스트는 3개의 서로 다른 EDR 제품에서 수행되었으며 각 경우에 성공했습니다.
EDR 제품은 Windows에서 커널 "Notify Routines" 콜백을 사용하여 프로세스 및 스레드 생성, 이미지(exe/DLL) 로딩과 같은 시스템 활동을 커널로부터 통지받습니다.
이러한 커널 콜백은 일반적으로 콜백을 구현하는 드라이버가 커널 영역에서 문서화된 API(nt!PsSetCreateProcessNotifyRoutine, nt!PsSetCreateThreadNotifyRoutine 등)를 사용하여 정의됩니다. 이러한 API는 드라이버가 제공하는 콜백 루틴을 커널 공간의 문서화되지 않은 루틴 배열에 추가합니다:
PspCreateProcessNotifyRoutinePspCreateThreadNotifyRoutinePspLoadImageNotifyRoutineEDRSandBlast는 이러한 배열에 정의된 루틴을 열거하고 미리 정의된 EDR 드라이버 목록(1000개 이상의 보안 제품 드라이버 지원, EDR 드라이버 탐지 섹션 참조)에 연결된 모든 콜백 루틴을 제거합니다. 열거 및 제거는 취약한 드라이버의 악용( 취약한 드라이버 섹션 참조)을 통해 제공되는 임의의 커널 메모리 읽기/쓰기 프리미티브를 통해 가능해집니다.
앞서 언급한 배열의 오프셋은 여러 기술을 사용하여 복구됩니다. 자세한 내용은 오프셋 섹션을 참조하세요.
EDR(및 EPP) 제품은 종종 nt!ObRegisterCallbacks 커널 API를 사용하여 "Object callbacks"를 등록합니다. 이러한 콜백을 통해 보안 제품은 특정 객체 유형(프로세스, 스레드 및 데스크톱 관련 객체 콜백이 현재 Windows에서 지원됨)에 대한 핸들이 생성될 때마다 통지를 받을 수 있습니다. 핸들 생성은 객체 열기(OpenProcess, OpenThread 호출 등) 및 핸들 복제(DuplicateHandle 호출 등) 시 발생할 수 있습니다.
커널이 이러한 각 작업을 통지함으로써 보안 제품은 핸들 생성의 정당성을 분석하고(예: 알 수 없는 프로세스가 LSASS를 열려고 함) 위협이 감지되면 차단할 수도 있습니다.
ObRegisterCallbacks를 사용하여 콜백을 등록할 때마다 콜백의 영향을 받는 객체 유형(프로세스, 스레드 또는 데스크톱)을 설명하는 _OBJECT_TYPE 객체에 존재하는 이중 연결 리스트 CallbackList에 새 항목이 추가됩니다. 불행히도 이러한 항목은 Microsoft에서 문서화하거나 기호 파일에 게시하지 않은 구조체로 설명됩니다. 그러나 여러 ntoskrnl.exe 버전에서 연구한 결과, 이 구조체는 (최소) Windows 10 빌드 10240과 22000(2015년부터 2022년까지) 사이에서 변경되지 않은 것으로 보입니다.
언급된 객체 콜백 등록을 나타내는 구조체는 다음과 같습니다:```C typedef struct OB_CALLBACK_ENTRY_t { LIST_ENTRY CallbackList; // linked element tied to _OBJECT_TYPE.CallbackList OB_OPERATION Operations; // bitfield : 1 for Creations, 2 for Duplications BOOL Enabled; // self-explanatory OB_CALLBACK* Entry; // points to the structure in which it is included POBJECT_TYPE ObjectType; // points to the object type affected by the callback POB_PRE_OPERATION_CALLBACK PreOperation; // callback function called before each handle operation POB_POST_OPERATION_CALLBACK PostOperation; // callback function called after each handle operation KSPIN_LOCK Lock; // lock object used for synchronization } OB_CALLBACK_ENTRY;
위에서 언급한 `OB_CALLBACK` 구조체는 문서화되지 않았으며, 다음과 같이 정의됩니다:```C
typedef struct OB_CALLBACK_t {
USHORT Version; // usually 0x100
USHORT OperationRegistrationCount; // number of registered callbacks
PVOID RegistrationContext; // arbitrary data passed at registration time
UNICODE_STRING AltitudeString; // used to determine callbacks order
struct OB_CALLBACK_ENTRY_t EntryItems[1]; // array of OperationRegistrationCount items
WCHAR AltitudeBuffer[1]; // is AltitudeString.MaximumLength bytes long, and pointed by AltitudeString.Buffer
} OB_CALLBACK;
In order to disable EDR-registered object callbacks, three techniques are implemented in
EDRSandblast; however only one is enabled for the moment.
Enabled field of OB_CALLBACK_ENTRYThis is the default technique enabled in EDRSandblast. In order to detect and disable
EDR-related object callbacks, the CallbackList list located in the _OBJECT_TYPE
objects tied to the Process and Thread types is browsed. Both _OBJECT_TYPEs are
pointed by public global symbols in the kernel, PsProcessType and PsThreadType.
Each item of the list is assumed to fit the OB_CALLBACK_ENTRY structure described above
(assumption that seems to hold at least in all Windows 10 builds at the time of writing).
Functions defined in PreOperation and PostOperation fields are located to checks
if they belong to an EDR driver, and if so, callbacks are simply disabled toggling the Enabled
flag.
While being a pretty safe technique, it has the inconvenient of relying on an undocumented structure; to reduce the risk of unsafe manipulation of this structure, basic checks are performed to validate that some fields have the expected values :
Enabled is either TRUE or FALSE (don't laugh, a BOOL is an int, so it could be anything other than 1 or 0);Operations is OB_OPERATION_HANDLE_CREATE, OB_OPERATION_HANDLE_DUPLICATE or both;ObjectType points on PsProcessType or PsThreadType.CallbackList of threads and processAnother strategy that do not rely on an undocumented structure (and is thus theoretically
more robust against NT kernel changes) is the unlinking of the whole CallbackList
for both processes and threads. The _OBJECT_TYPE object is the following:```C
struct _OBJECT_TYPE {
LIST_ENTRY TypeList;
UNICODE_STRING Name;
[...]
_OBJECT_TYPE_INITIALIZER TypeInfo;
[...]
LIST_ENTRY CallbackList;
}
`Flink` 및 `Blink` 포인터가 `CallbackList`의 `LIST_ENTRY`가 `LIST_ENTRY` 자체를 가리키도록 만들면 리스트가 사실상 비어 있게 됩니다. `_OBJECT_TYPE` 구조체는 커널 심볼에 공개되어 있으므로 이 기법은 하드코딩된 오프셋/구조체에 의존하지 않습니다. 그러나 몇 가지 단점이 있습니다.
첫째, EDR의 콜백만 비활성화할 수 있는 것이 아니라, "정상적인" 소프트웨어에 의해 등록되었을 수 있는 모든 객체 콜백에 영향을 미친다는 점입니다. 그럼에도 불구하고 Windows 10에서는 (현재 작성 시점 기준) 객체 콜백이 사전 설치된 어떤 구성 요소에서도 사용되지 않으므로, 이를 비활성화해도 시스템 안정성에 영향을 미치지 않아야 합니다(일시적으로만 비활성화하는 경우 더욱 그렇습니다).
두 번째 단점은 프로세스 또는 스레드 핸들 작업이 OS의 정상적인 기능에서 매우 빈번하다는 점(거의 지속적)입니다. 따라서 사용된 커널 쓰기 기본 연산이 "원자적으로" `QWORD` 쓰기를 수행할 수 없는 경우, `_OBJECT_TYPE.CallbackList.Flink` 포인터가 덮어쓰기 도중에 커널에 의해 접근될 가능성이 높습니다. 예를 들어, MSI 취약 드라이버 `RTCore64.sys`는 한 번에 `DWORD` 쓰기만 수행할 수 있으므로 포인터를 덮어쓰기 위해 두 개의 개별 IOCTL이 필요하며, 그 사이에 커널이 이를 사용할 확률이 높습니다(결과적으로 충돌). 반면, 취약한 DELL 드라이버 `DBUtil_2_3.sys`는 하나의 IOCTL에서 임의 크기의 쓰기를 수행할 수 있으므로 이 방법을 사용해도 충돌 위험이 없습니다.
#### 객체 콜백 완전 비활성화
우리가 발견한 마지막 기법 중 하나는 스레드 및 프로세스에 대한 객체 콜백 지원을 완전히 비활성화하는 것입니다. 프로세스 및 스레드 유형에 해당하는 `_OBJECT_TYPE` 구조체 내부에는 문서화된 `_OBJECT_TYPE_INITIALIZER` 구조체를 따르는 `TypeInfo` 필드가 있습니다. 후자에는 `ObjectTypeFlags` 비트 필드가 포함되어 있으며, 그 중 `SupportsObjectCallbacks` 플래그는 설명된 객체 유형(Process, Thread, Desktop, Token, File 등)이 객체 콜백 등록을 지원하는지 여부를 결정합니다. 앞서 언급했듯이, 현재 작성 시점의 Windows 설치에서는 Process, Thread 및 Desktop 객체 유형만 이러한 콜백을 지원합니다.
`SupportsObjectCallbacks` 비트는 `ObpCreateHandle` 또는 `ObDuplicateObject`가 `CallbackList`를 읽기 전(물론 콜백을 실행하기 전)에 검사하므로, 커널 런타임에 해당 비트를 플립하면 모든 객체 콜백 실행이 사실상 비활성화됩니다.
이 방법의 주요 단점은 *KPP*("*PatchGuard*")가 일부(전부?) `_OBJECT_TYPE` 구조체의 무결성을 모니터링하고, 매개변수 4가 `0x8`인 [`0x109 Bug Check`](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/bug-check-0x109---critical-structure-corruption)를 트리거한다는 것입니다. 이는 객체 유형 구조체가 변경되었음을 의미합니다.
그러나 비활성화/재활성화(및 그 사이의 "악의적인" 작업)를 충분히 빠르게 수행하면 *PatchGuard*를 "추월"할 수 있어야 합니다(주기적 검사가 잘못된 시점에 수행되는 불운이 없는 경우).
### 미니필터 콜백 연결 해제를 통한 EDR 우회
Windows 필터 관리자 시스템을 사용하면 EDR이 "미니필터" 드라이버를 로드하고 콜백을 등록하여 파일 열기, 읽기, 쓰기 등 I/O 작업에 대한 알림을 받을 수 있습니다.