AllowedReplicationLatency

레퍼럴 티켓 암호화에 사용되는 신뢰 패스워드는 포레스트 트러스트 관계에선 모두 자동으로 생성되며, 교차 포레스트 관계에서 This domain only 를 통해 신뢰를 구성했을 때만 유일하게 사용자가 직접 설정할 수 있습니다.

이렇게 구성된 평문 신뢰 패스워드는 Inter realm Kerberoasting을 통해 크랙할 수 있지만, 신뢰 관계가 구축된지 30일 이후부터는 자동으로 관리되기 때문에 크랙이 불가능해집니다.

일반 TGT를 암호화하는 krbtgt의 비밀키와 마찬가지로, 레퍼럴 티켓 또한 신뢰 패스워드가 변경되었을 때 KDC간의 동기화 지연 및 백업 용도로 직전에 사용한 패스워드를 oldKey 형태로 저장합니다.

oldKey는 신뢰 패스워드가 변경된 직후부터 60분동안만 유효하며 이후부터 KDC는 새롭게 변경된 신뢰 패스워드로 레퍼럴 티켓을 복호화하기 때문에 지속성이 짧다는 단점이 있습니다.

하지만 DC의 레지스트리 값을 변경할 수 잇다면 oldKey의 지속시간을 최대 30일 연장할 수 있습니다.

Abuse

# 레지스트리 값 삽입
Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Services\Kdc" -Name "AllowedReplicationLatency" -Value 43200 -Type DWORD

# 설정 복원
Remove-ItemProperty -Path "HKLM:\System\CurrentControlSet\Services\Kdc" -Name "AllowedReplicationLatency"

Root Cause

상세한 내용은 kdcsvc.dll 파일을 리버스 엔지니어링함에 따라 확인할 수 있습니다.

레퍼럴 티켓을 복호화하는 과정에서 조건 분기는 현재 시간과 신뢰 패스워드가 변경된 시간이 60 * KdcGlobalDomainPasswordReplSkew 값보다 작은지 검사합니다.

KdcGlobalDomainPasswordReplSkew 값은 v57 배열에서 멤버 변수를 4개씩 갖는 구조체의 멤버입니다.

RegOpenKeyW는 Windows API로 가장 마지막 매개변수는 열린 키에 대한 핸들을 받는 포인터입니다.

즉 v1이 가리키는 hKey는 레지스트리 키에 대한 핸들 값인 것이 확인되고, 이를 참조하는 xrefs 결과 offset 타입은 한 곳에서 호출하고 있는 것이 확인됩니다.

그리고 이 포인터에 핸들을 반환하는 곳에서 v57 배열에 사용되는 레지스트리 경로를 얻을 수 있습니다.

획득한 레지스트리 경로와 값을 조합하여 KdcGlobalDomainPasswordReplSkew 변수와 같은 구조체에 멤버로 속한 레지스트리 값의 절대 경로를 알 수 있습니다.

HKLM\System\CurrentControlSet\Services\Kdc\AllowedReplicationLatency

v57 배열은 총 204개의 값을 저장하고 있으며, 구조체는 4개의 멤버변수를 갖고 있으므로 51개의 구조체가 배열에 담겨있습니다.

GetRegistryDwords 함수 내부로 들어오면 조건 분기가 확인됩니다.

  • v6 = (_DWORD *)((char *)a4 + 1660) : v57 배열의 전체 크기는 8 * 4 * 51 = 1632바이트로 28바이트 더 큰 값이 v6에 할당됩니다.

  • v7 = 51 : 모든 구조체를 순회하기 위한 반복문의 인덱스입니다.

  • --v7 : 1회 반복마다 맨 뒤 구조체부터 맨 앞 구조체까지 1개씩 전진합니다.

  • v8 = *(v6 - 9) == 0 : v6은 현재 1660인데 (DWORD)9 만큼 감소한 값(1624)이 v8에 할당됩니다.

  • v6 -= 8 : 매 반복마다 v6은 32바이트 감소하여 다음 구조체로 넘어갑니다.

  • if ( !v8 || !KdcGlobalRegistryValuesInitialized ) : 현재 찾는 레지스트리의 구조체 멤버 변수에서 3번째 값이 0이 아닐 경우 분기합니다.

  • v10 = RegQueryValueExW(v9, *(LPCWSTR *)(v6 - 7), 0LL, Type, Data, &cbData) : RegQueryValueExW 함수의 반환 결과를 v10에 저장합니다. 이 함수는 레지스트리를 찾는다면 0을, 못 찾는다면 2를 반환합니다.

  • if ( !*(v6 - 2) ) : v6은 첫번째 반복문에서 v57 + 1628(4번째 멤버변수의 상위 4바이트 값인데 거기서 8바이트를 뺀 값은 3번째 멤버변수의 상위 4바이트

  • v11 = *(_DWORD *)Data : 레지스트리 값이 있으면서 DWORD이고 구조체의 멤버변수 3번째 값의 상위 4바이트가 0일 경우 v11은 반환 데이터

  • goto LABEL_7 : LABEL 7로 이동

  • **(_DWORD **)(v6 - 5) = v11 : v6 - 5은 두번째 멤버변수를 가리키며, 이곳에 v11을 대입

최종적으로 HKLM\System\CurrentControlSet\Services\Kdc\AllowedReplicationLatency 레지스트리 값이 있는지 확인하고 있다면 그 값과, 없다면 60과 60을 곱한 시간 만큼 oldKey가 사용됩니다.

이 값을 최대 값인 43200으로 등록하면 다음 신뢰 패스워드가 변경되어 oldKey가 사라지기 전까지는 최대 한달 간 oldKey를 통해 암호화한 레퍼럴 티켓을 사용할 수 있게 됩니다.

References

Last updated