Table of Contents
Abstract
AMSI Provider가 제공하는 AMSI Scan 기능을 사용하기 위해서는 Windows SDK에서 제공하는 amsi.h를 인클루드하고 AmsiScanString 혹은 AmsiScanBuffer API를 호출하면 된다. AMSI API가 담긴 amsi.lib는 자동으로 링크되지 않으므로 #pargma 같은 전처리문이나 프로젝트 속성에서 amsi.lib를 포함시켜야 한다.
How to use AMSI Scan
앞서 AMSI는 AMSI 스캔 기능을 제공하는 AMSI Provider와 이를 사용하는 일반 응용 프로그램들로 나뉜다고 설명했다. 본 글에서는 AMSI Provider가 제공하는 AMSI 스캔 기능을 사용하는 방법에 대해 적어보려 한다. AMSI 스캔 기능을 사용하는 응용 프로그램이나 서비스를 따로 부르는 용어는 없는 것 같아 편의상 AMSI Client라고 적겠다.
Sample Code
AMSI를 사용하는 방법은 간단하다. amsi.h를 인클루드하고 AmsiScanBuffer 혹은 AmsiScanString 함수를 호출하기만 하면 된다. 아래는 간단한 예제 코드다.
#include <Windows.h>
#include <amsi.h>
#pragma comment(lib, "amsi.lib") // link with amsi.lib
int main() {
HAMSICONTEXT amsi_context;
// AMSI 초기화
HRESULT result = AmsiInitialize(L"AppName", &amsi_context);
if (result != S_OK) {
fprintf(stderr, "AmsiInitialize failed (HRESULT: %u)\n", result);
return 1;
}
// 문자열 AMSI 스캔
AMSI_RESULT amsi_result = AMSI_RESULT_NOT_DETECTED;
result = AmsiScanString(
amsi_context, // amsi context
L"SCANNED STRING", // string
L"ContentName", // content name(filename, URL, unique script ID or etc)
nullptr, // amsi session
&amsi_result // result
);
// AMSI 스캔 결과에 따른 처리
if(AmsiResultIsMalware(amsi_result)) {
printf("MALICIOUS\n");
}
else if (AmsiResultIsBlockedByAdmin(amsi_result)) {
printf("BLOCKED BY ADMIN\n");
}
else {
printf("NORMAL\n");
}
// AMSI clean up
// When the app is finished with the AMSI API
// it must call AmsiUninitialize
AmsiUninitialize(amsi_context);
}
Description
Linking
AMSI API에 대한 함수 원형이나 #define 매크로, enum 값들은 amsi.h에 작성되어 있지만, 실제 API 코드는 amsi.lib에 존재한다. amsi.lib를 추가하지 않고 빌드하면 AMSI API 코드를 찾을 수 없다는 에러가 발생하며 빌드에 실패하므로 #pragma같은 전처리문이나 프로젝트 속성에서 amsi.lib를 추가해야 한다.
Initializing
AmsiScanString이나 AmsiScanBuffer API를 호출하기 전에 AmsiInitialize API를 호출해야 한다. AmsiInitialize API는 Windows 레지스트리에서 AMSI 구성 정보를 읽어와 AMSI Provider들이 제공하는 AMSI DLL을 자신의 가상 메모리 영역으로 로드한다.
HRESULT AmsiInitialize(
_In_ LPCWSTR appName,
_Out_ HAMSICONTEXT *amsiContext
)
Parameter | Description |
appName | AMSI API를 호출하는 AMSI Client의 이름이나 버전, GUID 문자열 등이다. |
amsiContext | AMSI API를 호출하는 데 사용되는 AMSI Context 핸들을 반환받을 포인터다. |
appName은 추후 AMSI Provider 제작 글에서 다시 얘기하겠지만 AMSI Provider의 Scan 함수의 파라메터로 들어오며 해당 AMSI 스캔 요청을 어떤 응용 프로그램이 요청했는지 알 수 있도록 하는 Wide Character 문자열 값이다.
- PowerShell의 AMSI AppName
PowerShell_C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe_10.0.17134.1
- wscript의 AMSI AppName
wscript.exe
- cscript의 AMSI AppName
cscript.exe
Scanning
AmsiInitialize를 통해 반환받은 AMSI Context 핸들을 통해 AMSI Provider가 제공하는 스캔 기능을 이용할 수 있다. 스캔 기능은 AmsiScanString나 AmsiScanBuffer를 호출함으로써 이용할 수 있다. AmsiScanString은 말 그대로 문자열을 스캔하는 함수고, AmsiScanBuffer는 바이너리를 스캔하는 함수다.
HRESULT AmsiScanBuffer(
_In_ HAMSICONTEXT amsiContext,
_In_ PVOID buffer,
_In_ ULONG length,
_In_ LPCWSTR contentName,
_In_opt_ HAMSISESSION amsiSession,
_Out_ AMSI_RESULT *result
);
HRESULT AmsiScanString(
_In_ HAMSICONTEXT amsiContext,
_In_ LPCWSTR string,
_In_ LPCWSTR contentName,
_In_opt_ HAMSISESSION amsiSession,
_Out_ AMSI_RESULT *result
);
Parameter | Description |
amsiContext | AmsiInitialize를 통해 반환받은 AMSI Context 핸들이다. |
buffer/string | AMSI Provider가 스캔할 바이너리 혹은 문자열이다. 문자열은 Wide Char 문자열로 전달되어야 한다. |
length (only AmsiScanBuffer) | AmsiScanBuffer를 사용할 때만 추가되는 파라메터다. 바이너리 데이터의 크기를 전달한다. |
contentName | 스캔될 데이터의 이름, URL, 고유 ID 등 데이터를 구별할 수 있도록 하는 문자열이다. 빈 문자열(L"")도 가능하다. 파워쉘의 -c 옵션처럼 명령행 상에서 스크립트 데이터를 직접 실행하는 경우 contentName이 빈 문자열로 나온다. |
amsiSession | 여러 스레드에서 AMSI Scan 기능을 이용할 때 이들을 구별할 수 있는 Session 값이다. 옵션 사항이므로 필요하지 않다면 null을 넣으면 된다. |
result | 스캔 결과를 반환 받을 AMSI_RESULT 타입의 포인터다. |
AmsiScanBuffer나 AmsiScanString API를 통해 스캔한 데이터의 결과는 result 파라메터로 받을 수 있다. result 파라메터는 AMSI_RESULT 타입의 포인터이며, amsi.h가 제공하는 AmsiResultIs* 매크로로 악성 데이터인지 아닌지 쉽게 확인할 수 있다. AMSI_RESULT는 amsi.h에 아래와 같이 정의되어 있다. AMSI Provider가 필요하다고 판단하면 겹치지 않는 범위에서 임의로 추가하는 것도 가능하다. MSDN에 따르면 숫자가 클수록 더 위험하다는 걸 나타내도록 의도한 것 같다.
typedef /* [v1_enum] */
enum AMSI_RESULT
{
AMSI_RESULT_CLEAN = 0,
AMSI_RESULT_NOT_DETECTED = 1,
AMSI_RESULT_BLOCKED_BY_ADMIN_START = 0x4000,
AMSI_RESULT_BLOCKED_BY_ADMIN_END = 0x4fff,
AMSI_RESULT_DETECTED = 32768
} AMSI_RESULT;
#define AmsiResultIsMalware(r) ((r) >= AMSI_RESULT_DETECTED)
#define AmsiResultIsBlockedByAdmin(r) ((r) >= AMSI_RESULT_BLOCKED_BY_ADMIN_START) && (r) <= AMSI_RESULT_BLOCKED_BY_ADMIN_END
Finalize
AmsiUninitialize 함수는 초기화 했던 AMSI 컨텍스트 데이터를 해제하는 역할을 한다.
When use AMSI
AMSI를 포함해 빌드하면 Amsi.dll에 대한 IAT가 구성된다.
AmsiInitialize API를 사용하면 AMSI Client는 Windows 레지스트리에서 AMSI 구성 정보를 읽어온다. 아래 그림에선 V3의 AMSI Provider가 보인다.
AMSI\Providers 키 하위에 GUID 포맷의 서브키가 존재한다. 해당 GUID를 Classes\CLSID에서 서브키 이름으로 하는 레지스트리를 찾으면 해당 AMSI Provider의 AMSI DLL 경로가 나타난다.
AMSI Provider의 AMSI DLL을 로드한다. amsiclient.exe의 모듈 목록에 v3amsi64.dll이 존재하는 걸 볼 수 있다.
AMSI DLL이 Export하는 Scan 함수를 호출한 후 그 결과를 AMSI_RESULT로 받아 처리하는 구조라고 볼 수 있다.
- amsi.dll 로드
- 레지스트리에서 AMSI Provider 탐색
- AMSI Provider의 AMSI DLL을 로드
- AMSI Provider의 AMSI DLL의 Scan 함수 호출
- AMSI_RESULT를 받아 처리
Github
AMSI Example 코드 깃허브(main 브랜치에 안보인다면 dev 브랜치에서 찾으면 된다)
https://github.com/geun-yeong/amsi-example/tree/main
'Development > Windows' 카테고리의 다른 글
Windows I/O Manager (0) | 2021.11.19 |
---|---|
#5 AMSI - Regsvr32로 AMSI Provider 등록하기 (0) | 2021.11.11 |
#4 AMSI - AMSI Provider 등록하기 (0) | 2021.11.09 |
#3 AMSI - AMSI Provider 작성하기 (0) | 2021.11.08 |
#1 AMSI - 개요 (0) | 2021.11.05 |