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:
- Disable việc tối ưu code.
- 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