본문 바로가기
Orange The Client/기타

WIN32 프로세스 강제 종료 탐지 방법

by Orange labs 2025. 2. 21.

 

Orange labs, 김범진

 

혹시라도 이걸 만들기 위해 고민하시는 분들 계시면 참고하시라고 올리며 더 좋은 방법 찾으신 분은 알려주시면 감사드리겠습니다.

WIN32_프로세스_강제_종료_탐지.pdf
0.27MB

 

힌트를 얻은 글은

https://blog.naver.com/techshare/220974308231

 

C# - 특정 EXE 프로세스를 종료시킨 EXE를 찾아내는 방법

다음과 같은 tweet을 봤습니다. ^^ Matt Swann‏ @MSwannMSFT Mar 31 ; https://twitte...

blog.naver.com

 

Microsoft-Windows-Kernel-Audit-API-Calls 로 분류된 이벤트 사용.

https://github.com/repnz/etw-providers-docs/blob/master/Manifests-Win10-17134/Microsoft-Windows-Kernel-Audit-API-Calls.xml

 

etw-providers-docs/Manifests-Win10-17134/Microsoft-Windows-Kernel-Audit-API-Calls.xml at master · repnz/etw-providers-docs

Document ETW providers. Contribute to repnz/etw-providers-docs development by creating an account on GitHub.

github.com

 

C++ 대략 코드

struct __declspec(uuid("{e02a841c-75a3-4fa7-afc8-ae09cf9b7f23}")) MWKAAC_PROVIDER_GUID_HOLDER;
static const auto MWKAAC_PROVIDER_GUID = __uuidof(MWKAAC_PROVIDER_GUID_HOLDER);

	struct {
		ULONG					status;
		WCHAR					szLoggerName[1024];
		TRACEHANDLE				hSession;
		EVENT_TRACE_PROPERTIES	*pSessionProperties;
		ULONG					BufferSize = 0;
		EVENT_TRACE_LOGFILEW	logFile;
		TRACEHANDLE				hTrace;
		HANDLE					hThread;
		EVT_HANDLE				hSubscribe;
		EVT_HANDLE				hBookmark;

	} m_event;
	
VOID CETW::Start() {
		m_event.status				= -1;
		m_event.BufferSize			= sizeof(EVENT_TRACE_PROPERTIES) + sizeof(WCHAR) * MAX_LOGFILE_NAME + 
										sizeof(WCHAR) * MAX_LOGGER_NAME;
		m_event.pSessionProperties	= (EVENT_TRACE_PROPERTIES*)__alloc("pSessionProperties", m_event.BufferSize);
		if (NULL == m_event.pSessionProperties)
		{
			AGENT2_FUNCTION_ERROR_LOG(this, __FUNCTION__, "m_event.pSessionProperties", m_event.BufferSize, "can not allocate");
			break;
		}

		// Set the session properties. You only append the log file name
		// to the properties structure; the StartTrace function appends
		// the session name for you.

		ZeroMemory(m_event.pSessionProperties, m_event.BufferSize);
		m_event.pSessionProperties->Wnode.BufferSize	= m_event.BufferSize;
		m_event.pSessionProperties->Wnode.Flags			= WNODE_FLAG_TRACED_GUID;
		m_event.pSessionProperties->Wnode.ClientContext = 1; //QPC clock resolution
		
		m_event.pSessionProperties->Wnode.Guid			= OrangeLabsTheClientAgent;
		m_event.pSessionProperties->LogFileMode			= EVENT_TRACE_REAL_TIME_MODE; //EVENT_TRACE_SYSTEM_LOGGER_MODE;
		m_event.pSessionProperties->LoggerNameOffset	= sizeof(EVENT_TRACE_PROPERTIES);
		m_event.pSessionProperties->BufferSize			= 0;
		m_event.pSessionProperties->MaximumBuffers		= 200;

		StringCbCopy(m_event.szLoggerName, sizeof(m_event.szLoggerName), LOGGER_NAME);

		// Create the trace session.
		m_event.status	= StartTrace((PTRACEHANDLE)&m_event.hSession, m_event.szLoggerName, m_event.pSessionProperties);

		if (ERROR_SUCCESS == m_event.status) {
			m_log.Log2("%20ws StartTrace:%d", LOGGER_NAME, m_event.status);
		}
		else
		{
			if (ERROR_ALREADY_EXISTS == m_event.status)
			{
				m_log.Log2("ERROR:%ws is already in use. hSession:%p", m_event.szLoggerName, m_event.hSession);
				if (ERROR_SUCCESS == ControlTrace(NULL, m_event.szLoggerName, m_event.pSessionProperties, EVENT_TRACE_CONTROL_QUERY)) {
					m_log.Log2("EVENT_TRACE_CONTROL_QUERY");
					if (ERROR_SUCCESS == ControlTrace(NULL, m_event.szLoggerName, m_event.pSessionProperties, EVENT_TRACE_CONTROL_STOP)) {
						m_log.Log2("EVENT_TRACE_CONTROL_STOP");
					}
					else {
						m_log.Log2("ERROR:EVENT_TRACE_CONTROL_STOP");
					}
				}
				else {
					m_log.Log2("ERROR:EVENT_TRACE_CONTROL_QUERY");
				}

			}
			else
			{
				m_log.Log2("StartTrace() failed with %lu", m_event.status);
			}
			break;
		}		
		
		
		m_event.logFile.LoggerName				= m_event.szLoggerName;
		//m_event.logFile.ProcessTraceMode		= PROCESS_TRACE_MODE_EVENT_RECORD;//PROCESS_TRACE_MODE_REAL_TIME;

		m_event.logFile.ProcessTraceMode		= PROCESS_TRACE_MODE_REAL_TIME|PROCESS_TRACE_MODE_EVENT_RECORD|PROCESS_TRACE_MODE_RAW_TIMESTAMP;
		m_event.logFile.EventRecordCallback		= EventRecordCallback;
		m_event.logFile.BufferCallback			= BufferRecordCallback;
		m_event.logFile.Context					= this;


		ULONG	status	= EnableTraceEx2(m_event.hSession, &MWKAAC_PROVIDER_GUID, EVENT_CONTROL_CODE_ENABLE_PROVIDER, 
					TRACE_LEVEL_INFORMATION, 0, 0, 0, NULL);
					
		m_event.hTrace	= ::OpenTraceW(&m_event.logFile);
		if ((TRACEHANDLE)INVALID_HANDLE_VALUE == m_event.hTrace) {
			CErrorMessage	err(GetLastError());
			m_log.Log(WIN32_ERROR_LOGFORMAT(__FUNCTION__, "OpenTraceW()", err));
			break;
		}	
}
VOID CETW::Shutdown() {
	if (m_event.pSessionProperties) {
		if (m_event.hSession) {
			if ((TRACEHANDLE)INVALID_HANDLE_VALUE != m_event.hTrace){
				CloseTrace(m_event.hTrace);
				m_event.hTrace	= NULL;
			}

			m_event.status = ControlTrace(m_event.hSession, m_event.szLoggerName, m_event.pSessionProperties, EVENT_TRACE_CONTROL_STOP);
			if (ERROR_SUCCESS != m_event.status)
			{

			}
			if (m_event.hThread) {
				WaitForSingleObject(m_event.hThread, 30 * 1000);
				CloseHandle(m_event.hThread);
				m_event.hThread	= NULL;
			}
			m_event.hSession = NULL;
		}
		__free("pSessionProperties", m_event.pSessionProperties);
		m_event.pSessionProperties = NULL;
	}
}
VOID	CETW::EventRecordCallback(PEVENT_RECORD EventRecord) {
	CETW	*pClass	= (CETW *)EventRecord->UserContext;

	DWORD	PID		= EventRecord->EventHeader.ProcessId;
	auto	opCode	= EventRecord->EventHeader.EventDescriptor.Opcode;
	auto	task	= EventRecord->EventHeader.EventDescriptor.Task;
	auto	eventId = EventRecord->EventHeader.EventDescriptor.Id;

	if( 2 == eventId ) {
		//	프로세스 강제 종료됨.		

		ProcessPtr	cpptr,	//	원인
								tpptr;	//	대상

		DWORD		TargetPID	= GetData<uint32_t>(EventRecord, L"TargetProcessId");
		int			nExclude	= 0;

		pClass->GetProcessCacheByPID(PID, [&](ProcessPtr pptr) {
			// 이벤트를 받았을 때 이미 강제 종료된 프로세스가 없어진 상태라 
			// 종료된 프로세스 정보도 일정 시간 동안 캐시에 보관해야함.
			if( pptr ) {
				cpptr	= pptr;
				pptr->doc.Get([&](Json::Value& doc) {
					if( pClass->ExcludeFileCount(doc) ) {

						nExclude++;
					}
					if( nExclude )	return;

					if (WINDOWS_OPERATING_SYSTEM_A_UID ==doc.get("ProductNameUID", 0).asUInt() .. 
								// 로그오프시 아래 녀석들이 프로그램들을 무자비하게 죽임. 적절히 필터링
								// csrss.exe, smss.exe, svchost.exe, LogonUI.exe
								// WerFault.exe 
								// 프로세스 비정상 종료시 WerFault.exe가 그 프로세스에 붙어 정보를 읽고 죽임
					
						)) {
						nExclude	+= 1;
						return;
					}
				});
			}
			else {

			}
		});
	
		pClass->GetProcessCacheByPID(TargetPID, [&](ProcessPtr pptr) {
			if( pptr ) {
				tpptr	= pptr;
				pptr->doc.Get([&](Json::Value& doc) {

				});
			}
		});

		if (cpptr && tpptr) {
			// cpptr 살인자
			// tpptr 희생자


		}
		else {

			if (!cpptr) {
				pClass->GetLog()->info("cpptr is null.");
			}
			if (!tpptr) {
				pClass->GetLog()->info("tpptr is null.");
			}	

			return;
		}		
	}
}
ULONG	CETW::BufferRecordCallback(_In_ PEVENT_TRACE_LOGFILE Buffer) {

	return TRUE;
}

 

남은 숙제가 있다면 Microsoft-Windows-Kernel-Audit-APICalls 엔 TerminateProcess 뿐 아니라 OpenProcess 같은 것 도있으니 실시간 이벤트를 모두 받지 않고 필터링 하고 싶다 . 그리고 이렇게 이벤트 받는 비용이 결코 가벼워 보이진 않는다는 것 .