Development/Windows

#2 AMSI - AMSI 스캔 기능 사용하기

geunyeong 2021. 11. 6. 01:59

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가 구성된다.

     

    amsiclient.exe의 AMSI IAT
    amsiclient.exe의 모듈 목록

     

    AmsiInitialize API를 사용하면 AMSI Client는 Windows 레지스트리에서 AMSI 구성 정보를 읽어온다. 아래 그림에선 V3의 AMSI Provider가 보인다.

     

    V3 Lite의&amp;nbsp; AMSI Provider 프로필 레지스트리

     

    AMSI\Providers 키 하위에 GUID 포맷의 서브키가 존재한다. 해당 GUID를 Classes\CLSID에서 서브키 이름으로 하는 레지스트리를 찾으면 해당 AMSI Provider의 AMSI DLL 경로가 나타난다.

     

    V3 Lite의 WOW64 AMSI 구성 정보(AMSI DLL 경로)

     

    AMSI Provider의 AMSI DLL을 로드한다. amsiclient.exe의 모듈 목록에 v3amsi64.dll이 존재하는 걸 볼 수 있다.

     

    amsiclient.exe가 V3의 AMSI DLL인 v3amsi64.dll을 로드한 모습

     

    AMSI DLL이 Export하는 Scan 함수를 호출한 후 그 결과를 AMSI_RESULT로 받아 처리하는 구조라고 볼 수 있다.

    1. amsi.dll 로드
    2. 레지스트리에서 AMSI Provider 탐색
    3. AMSI Provider의 AMSI DLL을 로드
    4. AMSI Provider의 AMSI DLL의 Scan 함수 호출
    5. 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