Introduction

Việc xóa thư viện CRT khiến cho IAT của file binary không còn chứa các unused function của Visual Studio cũng như là của Windows API. Điều này cũng làm cho file binary trở nên đáng ngờ, đặc biệt là khi kết hợp với kỹ thuật Compile Time API Hashing.

Ví dụ, xét đoạn code sau:

#include <Windows.h>
 
int main() {
 
  	// infinite wait
	WaitForSingleObject((HANDLE)-1, INFINITE);
	return 0;
}

Biên dịch nó thành IatCamouflage.exe mà không phụ thuộc vào thư viện CRT thì sẽ cho ra IAT như sau:

C:\Users\MaldevUser\Desktop\IATCamouflage\x64\Release>dumpbin.exe /IMPORTS IATCamouflage.exe
Microsoft (R) COFF/PE Dumper Version 14.42.34438.0
Copyright (C) Microsoft Corporation.  All rights reserved.
 
 
Dump of file IATCamouflage.exe
 
File Type: EXECUTABLE IMAGE
 
  Section contains the following imports:
 
    KERNEL32.dll
             140002000 Import Address Table
             1400021A0 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference
 
                         610 WaitForSingleObject
 
  Summary
 
        1000 .pdata
        1000 .rdata
        1000 .rsrc
        1000 .text

Khi thực thi, nó sẽ bị Process Hacker đánh dấu là bị packed:

Manipulating The IAT

Chúng ta có thể làm giả IAT bằng cách gọi các hàm mà trông vô hại của Windows API và truyền NULL vào các tham số.

Hơn thế nữa, ta có thể đặt chúng ở trong một câu điều kiện if-else mà không bao giờ được thõa mãn. Tuy nhiên, trình biên dịch có thể chỉnh sửa luồng thực thi bằng cách áp dụng kỹ thuật Dead-code elimination nhằm xóa các đoạn code mà không ảnh hưởng đến chương trình.

Ví dụ, ta đặt các lời gọi đến các Windows API trong một câu điều kiện không bao giờ được thỏa mãn như sau:

int z = 4;
 
// Impossible if-statement that will never run
if (z > 5) {
 
	// Random benign WinAPIs
	unsigned __int64 i = MessageBoxA(NULL, NULL, NULL, NULL);
	i = GetLastError();
	i = SetCriticalSectionSpinCount(NULL, NULL);
	i = GetWindowContextHelpId(NULL);
	i = GetWindowLongPtrW(NULL, NULL);
	i = RegisterClassW(NULL);
	i = IsWindowVisible(NULL);
	i = ConvertDefaultLocale(NULL);
	i = MultiByteToWideChar(NULL, NULL, NULL, NULL, NULL, NULL);
	i = IsDialogMessageW(NULL, NULL);
}

Trình biên dịch biết được câu điều kiện sẽ không bao giờ được thỏa mãn nên nó sẽ không biên dịch. Dẫn đến, các Windows API được gọi ở trên sẽ không có trong IAT của file binary.

Có 2 cách để giải quyết vấn đề này:

  1. Disable việc tối ưu code.
  2. Lừa compiler để nó nghĩ rằng đoạn code gọi đến các Windows API là được sử dụng.

Disabling Code Optimization

Để disable, ta chỉ cần chỉnh option Optimization của Visual Studio thành “Disabled (/Od)”

Tuy nhiên, việc disable tính năng này trong các project lớn có thể làm giảm hiệu năng do compiler không còn thực hiện tối ưu nữa.

Tricking The Compiler

Để đánh lừa compiler, ta sẽ sử dụng một câu điều kiện mà compiler không thể xác định xem nó có thể được thỏa mãn hay không.

Trước tiên, ta sẽ tạo ra một hàm dùng để sinh ra một seed có kiểu là int dựa trên macro __TIME__:

// Generate a random compile-time seed
int RandomCompileTimeSeed(void)
{
	return '0' * -40271 +
		__TIME__[7] * 1 +
		__TIME__[6] * 10 +
		__TIME__[4] * 60 +
		__TIME__[3] * 600 +
		__TIME__[1] * 3600 +
		__TIME__[0] * 36000;
}

Sau đó, xây dựng hàm Helper giúp cấp phát một buffer và gán 4 byte đầu (tương ứng với kích thước vùng nhớ của kiểu int) bằng với RandomCompileTimeSeed() % 0xFF nhằm giới hạn giá trị của seed xuống dưới 0xFF (255):

// A dummy function that makes the if-statement in 'IatCamouflage' interesting
PVOID Helper(PVOID *ppAddress) {
 
	PVOID pAddress = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0xFF);
	if (!pAddress)
		return NULL;
	
	// setting the first 4 bytes in pAddress to be equal to a random number (less than 255)
	*(int*)pAddress = RandomCompileTimeSeed() % 0xFF;
	
	// saving the base address by pointer, and returning it 
	*ppAddress = pAddress;
	return pAddress;
}

Cuối cùng, tạo ra hàm gọi các Windows API ở trong một câu điều kiện.

// Function that imports WinAPIs but never uses them
VOID IatCamouflage() {
 
	PVOID		pAddress	= NULL;
	int*		A		    = (int*)Helper(&pAddress);
	
	// Impossible if-statement that will never run
	if (*A > 350) {
 
		// some random whitelisted WinAPIs
		unsigned __int64 i = MessageBoxA(NULL, NULL, NULL, NULL);
		i = GetLastError();
		i = SetCriticalSectionSpinCount(NULL, NULL);
		i = GetWindowContextHelpId(NULL);
		i = GetWindowLongPtrW(NULL, NULL);
		i = RegisterClassW(NULL);
		i = IsWindowVisible(NULL);
		i = ConvertDefaultLocale(NULL);
		i = MultiByteToWideChar(NULL, NULL, NULL, NULL, NULL, NULL);
		i = IsDialogMessageW(NULL, NULL);
	}
 
	// Freeing the buffer allocated in 'Helper'
	HeapFree(GetProcessHeap(), 0, pAddress);
}

Có thể thấy, câu điều kiện so sánh A, là một con trỏ kiểu int* có giá trị là địa chỉ của buffer trả về từ hàm Helper, với 350. Do Helper luôn trả về một buffer có 4 bytes đầu bằng với một giá trị ngẫu nhiên bé hơn 255 nên câu điều kiện này sẽ không bao giờ được thỏa mãn. Tuy nhiên, compiler sẽ không biết điều này và nó sẽ biên dịch các lời gọi hàm của Windows API, dẫn đến việc chúng sẽ xuất hiện trong IAT:

C:\Users\MaldevUser\Desktop\IATCamouflage\x64\Release>dumpbin.exe /IMPORTS IATCamouflage.exe
Microsoft (R) COFF/PE Dumper Version 14.42.34438.0
Copyright (C) Microsoft Corporation.  All rights reserved.
 
 
Dump of file IATCamouflage.exe
 
File Type: EXECUTABLE IMAGE
 
  Section contains the following imports:
 
    KERNEL32.dll
             140002000 Import Address Table
             140002218 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference
 
                         36C HeapAlloc
                         370 HeapFree
                         2D4 GetProcessHeap
                         538 SetCriticalSectionSpinCount
                         412 MultiByteToWideChar
                          AF ConvertDefaultLocale
                         27D GetLastError
 
    USER32.dll
             140002040 Import Address Table
             140002258 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference
 
                         24E IsWindowVisible
                         1E1 GetWindowContextHelpId
                         284 MessageBoxA
                         1EA GetWindowLongPtrW
                         233 IsDialogMessageW
                         2E0 RegisterClassW

Resources