Post

파일 서명 조작 (Security Directory)

Invalid Certificate

최근 들어, 유효하지 않은 형태로 서명된 해킹툴이 많이 보이고 있습니다. 아래 사진은 게임 메모리를 조작하는 DLL 파일인데 ‘Microsoft Corporation’으로 서명되어 있습니다.

이러한 가짜 서명은 안티치트를 우회하는 목적으로 사용되며, 주로 유저 레벨에서 동작하는 실행 파일(exe, dll)에서 발견되고 있습니다.

Code Signing

SignTool을 사용하여 서명이 됐을 때와 안됐을 때의 차이점을 직접 알아보겠습니다. 왼쪽은 서명 전, 오른쪽은 서명 후 바이너리 데이터입니다.

서명된 후에는 Security Directory가 변경되고, 파일 끝에 인증서와 관련된 바이너리 데이터가 추가됩니다.

또한 파일에 대한 CheckSum 값이 추가되었습니다.

정리하면 서명 시 변경되는 데이터들은 다음과 같습니다.

MemberBefore signingAfter signing
CheckSumNULLPE 체크섬 값
Security DirecotryNULL인증서 데이터의 오프셋.
Certificate BinaryNULL인증서 데이터

Security Directory

서명 후 변경되는 데이터들을 해킹툴 DLL과 비교하여 살펴보겠습니다. 해킹툴 파일은 서명이 되어있지만 CheckSum 값은 변경되지 않았습니다.

Security Directory가 변경되었으며, 파일 끝에 ‘Microsoft Corporation’ 서명 데이터가 추가되었습니다.

이를 통해, 해킹툴은 SignTool을 통해 정상적으로 서명한 것이 아닌, 정상 파일의 Security Directory 내용을 복사한 것을 유추할 수 있습니다.

POC Code

분석한 내용을 바탕으로 해킹툴과 동일하게 Security Directory 내용을 복사하는 도구를 개발하였습니다. 전체 소스코드는 GitHub에 업로드 하였습니다. x64를 기준으로 제작되었기 때문에 x86 파일에 대해서는 따로 포팅 작업이 필요합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// CopyCert.exe [source] [destination]
void _tmain(int argc, const TCHAR* argv[])
{
    ...

	// Source File
	pNtHeaders = PIMAGE_NT_HEADERS(SrcFile[0] + PIMAGE_DOS_HEADER(SrcFile[0])->e_lfanew);
	pSecurityDir = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];

	dwSecDirOffset = pSecurityDir->VirtualAddress;
	dwSecDirSize = pSecurityDir->Size;

	// Destination File
	pNtHeaders = PIMAGE_NT_HEADERS(DestFile[0] + PIMAGE_DOS_HEADER(DestFile[0])->e_lfanew);
	pSecurityDir = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
	pSecurityDir->VirtualAddress = DestFile.size;
	pSecurityDir->Size = dwSecDirSize;

	DestFile.Write(pNtHeaders, sizeof(IMAGE_NT_HEADERS64), PIMAGE_DOS_HEADER(DestFile[0])->e_lfanew, FILE_BEGIN);
	DestFile.Write(SrcFile[dwSecDirOffset], dwSecDirSize, 0, FILE_END);

    ...
}

해당 도구의 실행 방법은 다음과 같습니다.

1
CopyCert.exe [SrcFile] [DestFile]

해당 도구를 사용하여 kernel32.dll에 서명된 ‘Microsoft Corporation’ 인증서를 복사하는데 성공하였습니다.

1
CopyCert.exe "kernel32.dll" "TEST.dll"

Anti-Cheat Bypass

대부분 안티치트에서는 LoadLibrary 후킹을 통해 Global DLL Injection, Remote DLL Injection 등 서명되지 않은 DLL의 실행을 차단합니다. 그러나 일부 안티치트에서는 이 과정에서 실행되는 파일의 서명 유무만 확인할 뿐 유효성은 검증하지 않습니다. 이러한 경우 해당 방식을 통해 우회가 가능하게 됩니다. 잘 만들어진 안티치트는 유효성 또한 검증합니다.

WinVerifyTrust

이러한 우회 방식을 차단하는 것은 간단합니다. 바로 WinVerifyTrust API를 통해 서명된 인증서의 유효성을 체크하는 것입니다. Microsoft에서 다음과 같이 샘플 코드 또한 제공하고 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
BOOL VerifyEmbeddedSignature(LPCWSTR pwszSourceFile)
{
    LONG lStatus;
    DWORD dwLastError;

    WINTRUST_FILE_INFO FileData;
    memset(&FileData, 0, sizeof(FileData));
    FileData.cbStruct = sizeof(WINTRUST_FILE_INFO);
    FileData.pcwszFilePath = pwszSourceFile;
    FileData.hFile = NULL;
    FileData.pgKnownSubject = NULL;

    GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
    WINTRUST_DATA WinTrustData;

    memset(&WinTrustData, 0, sizeof(WinTrustData));

    WinTrustData.cbStruct = sizeof(WinTrustData);
    WinTrustData.pPolicyCallbackData = NULL;
    WinTrustData.pSIPClientData = NULL;
    WinTrustData.dwUIChoice = WTD_UI_NONE;
    WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE; 
    WinTrustData.dwUnionChoice = WTD_CHOICE_FILE;
    WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY;
    WinTrustData.hWVTStateData = NULL;
    WinTrustData.pwszURLReference = NULL;
    WinTrustData.dwUIContext = 0;
    WinTrustData.pFile = &FileData;

    lStatus = WinVerifyTrust(
        NULL,
        &WVTPolicyGUID,
        &WinTrustData);

    // Any hWVTStateData must be released by a call with close.
    WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
    WinVerifyTrust(NULL, &WVTPolicyGUID, &WinTrustData);

    return (lStatus == ERROR_SUCCESS);
}

안타깝게도, 이렇게 간단한 구현임에도 불구하고 일부 안티치트에서는 Security Directory가 NULL인지 아닌지 정도만 체크할 뿐 유효성을 추가로 검사하지 않습니다. 해당 글에서 언급되는 해킹툴 또한 이러한 취약점을 통해 우회하고 있습니다.

This post is licensed under CC BY 4.0 by the author.