[2026 SWING magazine] Volatility 기반 메모리 포렌식과 YARA 탐지 자동화

메모리 포렌식 기초

메모리 포렌식 기초

메모리 포렌식 개념 및 필요성

메모리 포렌식은 휘발성 정보인 메모리를 수집한 뒤, 이를 분석하여 증거를 확보하는 과정을 의미한다.
휘발성 메모리에서 정보를 추출하고 분석함으로써 시스템의 상태, 실행 중인 프로세스, 네트워크 연결, 파일 핸들링 등 다양한 데이터를 확인할 수 있다. 이러한 과정을 통해 악성 행위를 탐지하거나 시스템에서 발생한 이벤트를 파악할 수 있다. 최근에는 디스크에 흔적을 남기지 않고 메모리에서만 동작하는 악성코드가 증가하고 있어, 메모리 포렌식의 중요성도 점차 커지고 있다.
휘발성 메모리에서 정보 수집이 가능한 이유는 RAM에 프로세스 정보, 악성코드 관련 데이터, 시스템 데이터 구조, 사용자 활동 정보 등이 일시적으로 저장되기 때문이다. CPU가 연산을 수행하기 위해서는 모든 소프트웨어의 코드와 데이터가 RAM에 적재되어야 하며, 이러한 특성으로 인해 메모리 포렌식을 통해 다양한 정보를 분석할 수 있다.

운영체제별 메모리 구조 이해

(1) Windows 메모리 구조
메모리 구조란, 프로그램 실행을 위해 사용되는 메모리를 물리적 RAM, 이를 관리하는 운영체제의 가상 메모리, 그리고 실제 실행 단위인 프로세스의 주소 공간(코드·데이터·힙·스택)으로 구분해 설명하는 개념이다.

그림 1. 윈도우 메모리의 전반적 구조

그림 1. 윈도우 메모리의 전반적 구조


물리 메모리는 컴퓨터 시스템에서 실제로 존재하는 메모리를 말한다. 이는 램(RAM)이라고도 불리며, CPU가 직접 접근할 수 있는 메모리 공간을 의미한다.

그림 2. RAM 이미지

그림 2. RAM 이미지


가상 메모리는 물리 메모리의 한계를 극복하기 위해 사용되는 개념이다. 이는 운영체제가 제공하는 기능으로, 프로그램이 필요로 하는 메모리 공간을 효율적으로 관리하여 제공한다. 가상 메모리는 가상 메모리를 통해 메모리 부족으로 인해 프로그램이 중단되는 상황을 줄이고, 시스템이 안정적으로 동작할 수 있도록 한다.
가상 메모리를 통해 CPU는 물리 메모리보다 더 큰 메모리를 가진 것처럼 메모리를 사용할 수 있다. 실제로 메모리의 크기가 증가한 것은 아니지만, 운영체제가 가상 주소 공간을 제공함으로써 이러한 동작이 가능해진다. 운영체제는 CPU에 가상 주소를 할당하고, CPU는 해당 가상 주소를 기반으로 명령어를 수행한다.

그림 3. 가상 주소 공간의 독립성

그림 3. 가상 주소 공간의 독립성


윈도우 운영체제에서 각 프로세스는 독립적인 가상 주소 공간(Virtual Address Space)을 할당 받아 사용한다. 이 가상 주소 공간은 물리적 메모리와 직접적으로 연결되지 않고, 메모리 관리 장치(MMU)에 의해 물리적 메모리로 매핑된다. 이러한 가상 메모리 시스템은 개별 프로세스의 메모리에 대한 독립성을 제공하고 한 프로세스가 다른 프로세스의 메모리에 직접 접근할 수 없게 한다.

Page Table은 일종의 주소 변환 사전이다. 프로세스가 사용하는 가상 주소를 실제 물리적 메모리 주소로 변환하는 역할을 한다. 각 항목은 한 페이지 단위로, 가상 페이지가 물리적 페이지 프레임 어디에 적재되어 있는지 가리킨다.

그림 4. Page Table 구조

그림 4. Page Table 구조


Page Table에서는 두 가지 개념이 사용된다. Virtual Page Number, 즉 VPN은 페이지 테이블에서 어떤 항목을 참조할지 결정하는 번호이다. Offset은 해당 페이지 내에서의 위치를 가리키며, 실제 매핑된 물리 프레임 번호를 가리키는 PEN(Physical Frame Number)이라는 개념도 있다.
예를 들어 가상 주소가 0xABCD1234라면, 이 주소는 상위 비트인 0Xabcd가 VPN을 나타내고, 하위 비트인 0x1234가 Offset을 의미한다. 먼저 CPU가 가상 주소를 생성하면, 이 주소는 VPN과 Offset으로 구분된다. 이후 VPN은 페이지 테이블의 인덱스로 사용되어 해당 페이지가 물리 메모리의 어느 위치에 존재하는지를 확인하는 데 활용된다. 마지막으로, 페이지 테이블에서 얻은 물리 페이지 번호에 Offset을 더해 실제 물리 주소가 만들어진다.

그림 5. VPN과 offset

그림 5. VPN과 offset


가상 메모리를 통해 CPU는 더 많은 프로그램을 동시에 실행할 수 있게 되었다. 하지만 실제 데이터에 접근하기 위해서는 결국 물리 메모리가 필요하며, 이때 가상 주소를 물리 주소로 변환해 주는 장치가 메모리 관리 장치(MMU)이다. CPU는 가상 주소 공간을 기준으로 동작하고, 메모리 접근이 필요한 경우에만 MMU를 통해 가상 주소를 물리 주소로 변환하여 접근한다.

그림 6. 물리 주소로의 접근

그림 6. 물리 주소로의 접근


가상 메모리는 페이징(Paging)과 스와핑(Swapping)이라는 두 가지 주요 기법을 사용한다.

  • Paging
    페이징(Paging)은 프로그램의 메모리 요구를 작은 고정 크기의 블록, 즉 페이지 단위로 나누어 관리하는 방식이다. 가상 메모리는 일정한 크기의 페이지(일반적으로 4KB)로 나뉘며, 물리 메모리 역시 동일한 크기의 프레임으로 나누어 매핑된다. 이 기법을 통해 연속된 가상 주소 공간이 실제 물리 메모리에서는 서로 떨어진 위치에 저장되어 있더라도 프로그램 실행이 가능하다. 예를 들어, 어떤 프로그램이 12KB의 메모리를 요구하지만 연속된 12KB 공간이 존재하지 않는 경우에도, 이를 4KB씩 세 개의 페이지로 나누어 물리 메모리의 서로 다른 빈 프레임에 적재할 수 있다.

  • Swapping
    스와핑(Swapping)은 물리 메모리가 부족할 경우, 프로세스 전체 또는 일부 메모리 영역을 디스크로 이동시키고 필요할 때 다시 물리 메모리로 불러오는 기법이다. 디스크 접근 속도는 RAM보다 훨씬 느리기 때문에 스와핑이 빈번하게 발생하면 시스템 성능이 크게 저하될 수 있다. 그럼에도 불구하고 스와핑을 통해 실제 물리 메모리보다 큰 프로그램을 실행할 수 있다는 장점이 있다.
    물리 메모리와 가상 메모리는 상호 보완적인 관계를 가진다. 물리 메모리는 빠른 속도와 높은 효율성을 제공하지만 용량이 제한적이다. 반면 가상 메모리는 이러한 한계를 보완하여 더 큰 메모리 공간을 제공한다. 운영체제는 이 두 메모리 유형을 효율적으로 관리함으로써 시스템의 전반적인 성능을 최적화한다.
    운영체제가 ‘물리 주소 → 가상 주소’ 방식이 아니라 ‘가상 주소 → 물리 주소’ 방식을 사용하는 이유는 크게 두 가지로 설명할 수 있다.
    첫째는 프로세스 보호이다. 만약 물리 주소를 직접 사용한다면, 하나의 프로세스가 다른 프로세스의 메모리 영역이나 운영체제 커널 영역에 접근할 가능성이 생기게 된다. 이는 악성 코드에 의해 시스템이 손상될 위험을 증가시킨다. 반면 운영체제가 각 프로세스에 독립적인 가상 주소 공간을 부여하면, 프로세스는 자신만의 메모리를 사용하는 것처럼 동작하게 되며 실제 물리 메모리와의 매핑은 운영체제가 관리하게 된다. 이를 통해 프로세스 간의 메모리 침범을 효과적으로 방지할 수 있다.
    둘째는 유연성이다. 물리 메모리는 크기가 제한되어 있고 주소 공간이 연속적이지 않을 수 있다. 그러나 운영체제가 가상 주소 공간을 먼저 제공하면, 프로세스는 항상 연속적인 메모리를 사용하는 것처럼 인식할 수 있다. 이로 인해 실제 물리 메모리가 여러 위치에 분산되어 있더라도, 프로세스는 이를 인식하지 않고 정상적으로 실행될 수 있다.

프로세스 메모리 구조
하나의 프로그램이 실행되면 프로세스가 만들어지고, 그 프로세스는 자기만의 메모리 공간을 가지게 된다. 즉, 프로세스 메모리는 실행 중인 프로그램의 내부 구조를 나타내는 메모리 구성이라고 보면 된다. 프로세스가 실행될 때, 메모리는 보통 이런식으로 나뉜다.

그림 7. 프로세스 메모리 구조

그림 7. 프로세스 메모리 구조


  • 커널 영역/유저 영역
    일반 프로그램을 실행시키기 위한 메모리 공간과 운영체제 실행을 위한 메모리 공간을 분리하기 위해 커널/유저 영역을 구분한다.
    유저 영역은 해당 프로세스의 가상 주소 공간에 위치한 유저 영역 메모리이다. 프로그램을 만들고 부여받은 메모리는 모두 이 유저 영역에 적재되어 사용된다. 프로세스가 사용하는 코드, 데이터, 힙, 스택 등의 메모리가 모두 유저 영역의 메모리이다.
    커널 영역은 운영체제가 사용하는 메모리 영역으로, 운영체제의 핵심 코드와 자료 구조가 위치하는 가상 주소 공간 부분이다. 운영체제가(하드웨어 제어를 포함한) 스레드 스케줄링, 메모리 관리, 파일 시스템 지원, 네트워크 자원들을 구현하는 모든 코드와 모든 디바이스 드라이버들이 커널 영역에 로드된다. 커널 모드(운영체제가 CPU 제어권을 가지고 운영체제 코드를 실행하는 모드)에서만 접근이 가능하며 일반 프로세스는 직접적인 형태로는 이 영역에 접근할 수 없다.

유저 영역에서는 공간이 프로세스마다 독립적이. 독립성이 보장되기 때문에 한 프로세스가 다른 프로세스의 유저 영역을 직접 침범할 수 없고, 이는 곧 보안과 안정성의 기초가 된다.
이와 다르게 커널 영역은 모든 프로세스가 동일하게 공유하는 단일한 공간이다. 하지만, 공유된다는 것이 곧 자유로운 접근을 뜻하는 것은 아니다. 일반적인 프로그램이 실행되는 유저 모드에서는 차단된다. 이는 사용자 프로그램이 운영체제 핵심을 함부로 건드려 시스템 전체가 무너지는 일을 방지하기 위한 설계이다.
유저 영역 속 프로세스 구조에는 코드 영역, 데이터 영역, 스택 영역, 힙 영역이 있다. 기본 개념은 Linux와 공통되지만, Linux에서는 이를 더 세분화하여 5개의 세그먼트로 구분한다.

(2) Linux 메모리 구조
리눅스에서는 프로세스의 메모리를 크게 5가지 세그먼트로 구분한다. 여기서 세그먼트란 적재되는 데이터의 용도별로 메모리의 구획을 나눈 것인데 크게 세그먼트, 데이터 세그먼트, BSS 세그먼트, 힙 세그먼트, 그리고 스택 세그먼트로 구분한다.

그림 8. 리눅스 프로세스 메모리 세그먼트

그림 8. 리눅스 프로세스 메모리 세그먼트


  • 코드 세그먼트(Code Segment)
    실행 가능한 기계어 코드가 저장되는 영역이다. 프로그램이 정상적으로 실행되기 위해 해당 영역에는 읽기 권한과 실행 권한이 부여된다. 그러나 쓰기 권한이 허용될 경우 취약점을 이용해 프로그램 코드가 변조될 수 있으므로, 보안상의 이유로 대부분 쓰기 권한은 부여되지 않는다.

그림 9. 코드 세그먼트

그림 9. 코드 세그먼트


  • 데이터 세그먼트(Data Segment)
    컴파일 시점에 값이 정해진 전역 변수 및 전역 상수가 저장되는 영역이다. CPU가 해당 데이터를 읽을 수 있어야 하므로 읽기 권한이 부여된다. 데이터 세그먼트는 쓰기 가능 여부에 따라 다시 두 영역으로 구분된다.
    쓰기가 가능한 영역인 data 세그먼트에는 프로그램 실행 중 값이 변경될 수 있는 전역 변수가 저장된다. 반면, 쓰기가 불가능한 영역인 rodata 세그먼트에는 프로그램 실행 중값이 변경되면 안 되는 전역 상수가 저장된다.

그림 10. 데이터 세그먼트

그림 10. 데이터 세그먼트


  • BSS 세그먼트
    컴파일 시점에 초기값이 정해지지 않은 전역 변수가 저장되는 영역이다. BSS 세그먼트에 포함된 변수들은 프로그램 시작 시 모두 0으로 초기화되며, 읽기 및 쓰기 권한이 부여된다.

그림 11. BSS 세그먼트

그림 11. BSS 세그먼트


  • 스택 세그먼트(Stack Segment)
    프로세스의 스택이 위치하는 영역으로, 함수의 매개변수, 지역 변수 등과 같은 임시 데이터가 저장된다. 프로그램 실행 중 데이터의 저장과 제거가 빈번하게 발생하므로 읽기 및 쓰기 권한이 부여된다.
    과거에는 스택 세그먼트에 실행 권한이 존재했으나, 공격자가 스택에 쉘코드를 삽입한 뒤 해당 영역을 실행하는 스택 기반 버퍼 오버플로우 공격이 등장하면서 보안 문제가 발생하였다. 이에 따라 현재는 컴파일 시 기본적으로 NX(Non-Executable) 보호 기법이 적용되어 스택 영역에 실행 권한을 부여하지 않는다.

그림 12. 스택 세그먼트

그림 12. 스택 세그먼트


  • 힙 세그먼트(Heap Segment)
    힙 세그먼트는 프로그램 실행 중 동적으로 할당되는 데이터가 저장되는 영역이다. 스택 세그먼트와 마찬가지로 실행 중 크기가 변할 수 있으며, 주로 malloc, new와 같은 동적 메모리 할당 함수에 의해 사용된다.
    힙 세그먼트와 스택 세그먼트가 동일한 방향으로 확장되고 연속된 주소 공간에 할당된다고 가정할 경우, 힙 영역이 확장되는 과정에서 스택 세그먼트와 충돌할 위험이 있다. 이러한 문제를 방지하기 위해 리눅스에서는 힙 세그먼트가 스택 세그먼트와 반대 방향으로 성장하도록 설계하여, 두 영역이 메모리를 보다 유연하게 사용할 수 있도록 하고 충돌 가능성을 최소화한다.

그림 13. 힙 세그먼트

그림 13. 힙 세그먼트


악성코드 분류 개요

악성코드(Malware)**란 사용자의 의사와 이익에 반하여 시스템을 파괴하거나 정보를 유출하는 등 악의적인 행위를 수행하도록 의도적으로 제작된, 컴퓨터 상에서 실행 가능한 모든 형태의 소프트웨어를 말한다.

그림 14. 악성코드: 동작에 의한 분류

그림 14. 악성코드: 동작에 의한 분류


  • 바이러스(Virus)
    실행 가능한 파일이나 프로그램에 자신을 복제하여 감염시키는 악성 프로그램이다. 감염된 파일이 실행되어야만 활성화되며, 숙주 프로그램에 종속되는 특징을 가진다.

  • 웜(Worm)
    운영체제나 응용 프로그램의 취약점을 이용하여 네트워크를 통해 스스로 전파되는 악성 프로그램이다. 바이러스와 달리 다른 파일에 기생하지 않으며, 지속적으로 자신을 복제하여 확산되므로 신속한 제거가 필요하다.

  • PUP (Potentially Unwanted Program)
    사용자의 동의를 포함하는 형태로 설치되는 ‘잠재적으로 원하지 않는 프로그램’이다. 설치 과정에서 사용자에게 동의를 요청하지만, 광고성 문구나 복잡한 선택지로 인해 사용자가 이를 인지하지 못한 채 설치되는 경우가 많다.

  • 트로이 목마(Trojan Horse)
    정상적인 프로그램으로 위장하여 사용자를 속이고 설치되도록 하는 악성 프로그램이다. 스스로 전파되지는 않지만, 공격자에게 시스템에 대한 접근 권한을 제공하여 정보 유출이나 원격 제어 등의 악의적인 행위를 가능하게 한다.

그림 15. 악성코드: 목적에 의한 분류

그림 15. 악성코드: 목적에 의한 분류


  • Downloader
    파일을 다운로드하는 기능을 수행하는 프로그램으로, 그 자체만으로는 반드시 악성이라고 할 수는 없다. 그러나 사용자가 실행할 경우 외부로부터 추가적인 악성 코드를 다운로드하여 이후 악성 행위를 수행하기 위한 수단으로 사용될 수 있다.

  • Launcher
    감염된 시스템에 이미 설치되어 있는 악성 코드를 실행하는 역할을 수행하는 프로그램이다. 시스템 재부팅 이후에도 악성 코드가 지속적으로 실행되도록 하거나, 특정 조건이 만족될 때만 악성 행위를 시작하도록 제어할 수 있다.

  • 애드웨어(Adware)
    사용자의 컴퓨터에 광고를 표시하는 프로그램이다. 무료 소프트웨어 설치 시 함께 설치되는 경우가 많으며, 일반적으로 사용자의 동의를 받고 설치된다. 다만 과도한 광고 노출로 인해 사용자에게 불편을 초래할 수 있다.

  • 스파이웨어(Spyware)
    사용자의 활동을 감시하고 정보를 수집하는 악성 프로그램이다. 무료 소프트웨어 설치 과정에서 함께 설치되는 경우가 있으며, 대부분 사용자의 동의 없이 설치되어 개인정보를 무단으로 수집한다.

  • 랜섬웨어(Ransomware)
    강력한 암호화 기법을 사용하여 사용자의 파일을 암호화한 뒤, 이를 인질로 삼아 금전을 요구하는 악성 프로그램이다. 암호화된 파일은 사용자가 실행하거나 읽을 수 없게 되며, 복호화가 매우 어려운 경우가 많다.

  • 백도어(Backdoor)
    사용자의 동의 없이 임의의 포트를 열어 공격자의 접속을 허용하는 악성 코드이다. 이를 통해 공격자는 시스템 내부로 은밀히 접근하여 정보 탈취나 추가적인 악성 행위를 수행할 수 있다.

  • 루트킷(Rootkit)
    시스템에 은밀히 침투하여 관리자(루트) 권한을 획득하거나 유지하기 위해 사용되는 악성 코드이다. 주로 파일이나 레지스트리에 자신을 숨기며, 획득한 권한을 이용해 다른 악성 코드를 설치하고 정상 파일처럼 위장할 수 있다. 일반적인 백신으로는 탐지가 어렵고, 엔드포인트 보안 솔루션 등에 의해 탐지될 수 있다.

  • 익스플로잇(Exploit)
    운영체제나 특정 프로그램의 취약점을 이용하도록 설계된 공격 코드이다. 그 자체로는 악성 코드라고 보기 어렵지만, 다른 악성 코드를 시스템에 침투시키기 위한 공격 도구로 사용된다. 취약점이 보안 업데이트로 제거되면 해당 익스플로잇은 효력을 잃으므로, 지속적인 보안 업데이트가 중요하다.

  • 봇넷(Botnet)
    공격자가 원격으로 제어할 수 있도록 감염된 다수의 컴퓨터로 구성된 네트워크이다. 봇넷에 포함된 컴퓨터들은 스팸 메일 발송, 분산 서비스 거부 공격(DDoS) 등 대규모 사이버 공격에 활용된다.

  • 스케어웨어(Scareware)
    사용자에게 경고 메시지나 위협적인 화면을 표시하여 공포심을 유발하는 프로그램이다. 실제로 악의적인 기능을 수행하지 않는 경우도 많지만, 사용자를 속여 금전 결제나 특정 프로그램 설치를 유도할 수 있다.

Volatility 기초

Volatility 설치 민 환경 설정

Volatility는 메모리 덤프 파일을 분석하여 악성코드 감염 여부, 해킹 흔적, 시스템 상태 등을 조사할 수 있는 대표적인 오픈 소스 메모리 포렌식 도구로, 디지털 포렌식 조사 및 사고 대응 분야에서 널리 사용된다.
메모리 덤프는 시스템의 RAM에 존재하는 데이터를 그대로 복사한 파일로, 프로세스 정보, 네트워크 연결, 열린 파일, 드라이버, 레지스트리 등 다양한 휘발성 정보를 포함한다. 이러한 정보는 시스템 종료 시 사라지기 때문에, Volatility를 이용한 분석이 중요하다.
Volatility는 다양한 분석 기능을 플러그인 형태로 제공하며, 사용자는 필요에 따라 플러그인을 선택하여 사용하거나 직접 개발할 수도 있다. 이를 통해 환경에 맞는 유연한 메모리 분석이 가능하다.

기본 플러그인 사용법

  • windows.info
    시스템의 기본적인 운영체제 정보를 출력하는 플러그인이다. 분석 대상 메모리 덤프가 어떤 환경에서 생성되었는지를 파악하는 데 사용된다.
    • Is64Bit: 시스템이 64비트 기반 운영체제인지 여부
    • NTBuild: Windows 빌드 버전 정보

  • windows.pslist
    현재 실행 중인 프로세스 목록을 출력하는 플러그인이다. 출력 결과가 길어질 수 있으므로 리다이렉션(>)을 이용해 파일로 저장한 후 분석하는 것을 권장한다. 실제 분석은 Notepad++와 같은 텍스트 뷰어 도구를 사용하여 진행한다.
    • PID / PPID: 프로세스 ID 및 부모 프로세스 ID
    • ImageFileName: 프로세스 이름
    • CreateTime: 프로세스가 생성된 시간

  • windows.psscan
    메모리 영역을 직접 스캔하여 프로세스 목록을 출력하는 플러그인이다. pslist와 psscan은 모두 프로세스 정보를 제공하지만 내부 동작 방식이 다르다.
    pslist는 비교적 빠르게 결과를 출력하지만, 악성코드에 의해 조작된 경우 탐지하지 못할 수 있다. 반면 psscan은 메모리 구조를 직접 탐색하므로 은닉된 프로세스를 발견하는 데 유리하다. 따라서 두 플러그인의 결과를 비교 분석하는 것이 바람직하다.

  • windows.pstree
    프로세스 목록을 트리(Tree) 구조로 출력하여 부모–자식 프로세스 관계를 직관적으로 확인할 수 있는 플러그인이다. 자식 프로세스는 들여쓰기 및 * 표시를 통해 표현된다.
    다른 프로세스 관련 플러그인과 달리, 프로세스의 실행 경로와 커맨드 라인 정보를 함께 확인할 수 있어 악성 행위 분석에 유용하다.

  • windows.handles
    –pid 옵션과 함께 사용되며, 해당 프로세스가 열고 있는 핸들 목록을 출력하는 플러그인이다. 이를 통해 프로세스가 접근 중인 파일, 레지스트리, 뮤텍스 등의 자원을 확인할 수 있다.

  • windows.dlllist
    –pid 옵션과 함께 사용되며, 해당 프로세스가 로드한 DLL 목록을 출력하는 플러그인이다. 악성코드는 정상 프로세스에 악성 DLL을 로드하여 동작하는 경우가 많으므로, DLL의 이름보다는 로드된 경로가 정상적인지 여부를 중심으로 분석해야 한다.

  • windows.dumpfiles
    메모리에 존재하는 파일을 덤프할 수 있는 플러그인이다.
    • –pid 옵션을 사용하면 해당 프로세스와 관련된 파일들을 덤프할 수 있다.
    • –virtaddr 옵션을 사용하면 지정한 가상 주소에 위치한 파일을 덤프할 수 있다.
    • –physaddr 옵션을 사용하면 지정한 물리 주소에 위치한 파일을 덤프할 수 있다.

  • windows.memmap
    –pid 옵션과 함께 사용되며, 해당 프로세스의 메모리 구조를 확인할 수 있는 플러그인이다. –dump 옵션과 함께 사용하면 프로세스의 전체 메모리 영역을 파일로 덤프할 수 있다. 덤프된 메모리는 크기가 크기 때문에, 키워드를 기반으로 검색하여 분석하는 것이 효율적이다.

  • windows.cmdline
    프로세스가 실행될 때 사용자가 입력한 커맨드 라인 명령어를 출력하는 플러그인이다. 실행 옵션이나 인자를 통해 악성 행위의 의도를 파악할 수 있다.

  • windows.filescan
    메모리에 존재하는 모든 파일 객체를 스캔하여 목록을 출력하는 플러그인이다. 출력 결과에는 파일의 주소(Offset)가 함께 제공되며, 해당 주소를 이용해 windows.dumpfiles 플러그인으로 파일을 덤프할 수 있다.

  • windows.envars
    분석 대상 시스템의 환경 변수를 출력하는 플러그인이다. 악성 코드는 환경 변수에 실행에 필요한 값을 저장하는 경우가 있으므로, 환경 변수에 포함된 경로를 중심으로 비정상적인 값이 있는지 분석해야 한다.

  • windows.hashdump
    Windows 시스템에 저장된 사용자 계정의 NTLM 해시 값을 추출하는 플러그인이다. 공격자는 해당 해시 값을 이용해 패스워드 크래킹이나 권한 상승 공격을 시도할 수 있다.

FormBook 악성코드 기초 실습

FormBook 개요

FormBook은 2016년 2월부터 ‘ng-Coder’라는 사용자명으로 해킹 포럼에서 판매되기 시작한 정보탈취형 악성코드이다. 특히 윈도우 사용자를 표적으로 삼으며, 사용자가 입력하거나 화면에 표시되는 정보들을 감시하고 유출하는 기능을 한다.

그림 16. FormBook 기사

그림 16. FormBook 기사


FormBook 악성코드는 공격자가 제품처럼 구매하여 사용하는 상업적 악성코드(Malware-as-a-Service)라는 점이 가장 큰 특징이다. 플러그인 형태로 다양한 기능을 제공하여 공격자는 필요한 기능만 선택적으로 활성화할 수 있으며, 지속적인 업데이트를 통해 안티바이러스 및 보안 솔루션의 탐지를 회피한다.

그림 17. FormBook 광고

그림 17. FormBook 광고


또한 FormBook은 웹 패널(Web Panel) 형태의 관리용 인터페이스를 제공하여, 공격자가 웹 브라우저를 통해 GUI 환경에서 악성코드를 효율적으로 관리할 수 있도록 지원한다. 이러한 특성으로 인해 FormBook은 상용 소프트웨어와 유사하게 체계적으로 개발된 악성코드로 분류된다.
FormBook의 주요 기능은 사용자의 민감한 정보를 수집하는 데 있다. 대표적으로 키로깅(Keylogging), 폼 그래빙(Form Grabbing), 스크린샷 캡처, 클립보드 모니터링 기능을 수행한다. 키로깅은 사용자가 입력하는 키 값을 실시간으로 가로채는 기능으로, 특히 금융 사이트나 이메일 로그인 화면에서 입력되는 계정 정보를 수집하는 데 활용된다. 폼 그래빙은 사용자가 웹사이트에 로그인할 때 아이디와 비밀번호 등의 정보가 서버로 전송되기 직전에 이를 가로채는 방식이다.
이와 함께 FormBook은 시스템 제어를 위해 C&C(Command and Control) 서버와 통신하며, 원격 명령 수행, 프로세스 관리, 파일 시스템 조작과 같은 기능을 수행한다. C&C 통신에는 HTTPS 프로토콜이 사용되어 정상적인 웹 트래픽으로 위장되며, 수집된 데이터는 암호화되어 전송된다. 또한 C&C 서버로부터 추가 악성코드 다운로드, 특정 파일 삭제, 시스템 종료 등의 명령을 수신할 수 있다.

그림 18. FormBook 판매 사이트

그림 18. FormBook 판매 사이트


유포 방식 및 감염 시나리오 이해

FormBook은 주로 피싱 이메일의 첨부파일(PDF, DOC, EXE), 악성 웹사이트를 통한 다운로드, 또는 정상 소프트웨어나 문서 파일로 위장한 형태로 유포된다.
사용자가 이를 실행하면 초기 악성코드가 시스템 환경을 분석한 뒤, explorer.exe, svchost.exe와 같은 정상 프로세스를 대상으로 삼는다. 이후 해당 정상 프로세스에 코드 인젝션(Code Injection) 기법을 사용하여 자신의 코드를 주입함으로써, 프로세스 이름은 정상으로 보이지만 실제로는 FormBook 악성코드가 실행되도록 한다.

그림 19. FormBook 첨부 파일

그림 19. FormBook 유포 방식


그림 20. FormBook 첨부 파일

그림 20. FormBook 유포 방식


Volatility를 활용한 FormBook 감염 프로세스 탐색

본 실습에서는 FormBook 악성코드 샘플을 가상 머신 환경에서 실행한 후, Volatility를 이용하여 감염 프로세스를 분석하였다.

그림 21. FormBook 악성코드 샘플 다운로드

그림 21. FormBook 악성코드 샘플 다운로드


Process Explorer 도구를 사용하여 프로세스 트리를 확인한 결과, explorer.exe 하위에 net1.exe 프로세스가 새롭게 생성된 것을 확인하였다. FormBook 실행을 확인한 후, 해당 프로세스의 메모리를 덤프하여 분석을 진행하였다.

그림 22. FormBook 실행 후 프로세스 트리

그림 22. FormBook 실행 후 프로세스 트리


pslist 명령어를 사용하여 프로세스 목록을 확인한 결과, 악성코드 실행 시점인 2025-08-20 06:12 이후로 생성된 프로세스는
svchost.exe, MSBuild.exe, net1.exe, SearchProtocolHost.exe, SearchFilterHost.exe
로 확인되었다.
이 중 DumpIt.exe는 메모리 덤프 수집을 위해 분석자가 직접 실행한 도구이며, 해당 실행 과정에서 생성된 conhost.exe는 콘솔 호스트 프로세스이므로 분석 대상에서 제외하였다.
일반적으로 svchost.exe, net1.exe, MSBuild.exe는 Windows에서 사용되는 정상 프로세스이지만, FormBook은 탐지를 회피하기 위해 이러한 정상 프로세스에 악성 코드를 주입하여 악용하는 특징을 가진다.

그림 23. pslist 명령어 실행 결과

그림 23. pslist 명령어 실행 결과


pstree 명령어를 사용하여 svchost.exe, net1.exe, MSBuild.exe의 부모–자식 관계를 트리 형태로 확인하였다.
• svchost.exe
경로: C:\Windows\System32\svchost.exe
실행 흐름: wininit.exe(700) → services.exe(812) → svchost.exe(다수)

• net1.exe
경로: C:\Windows\SysWOW64\net1.exe
실행 흐름: winlogon.exe(748) → userinit.exe(3256) → explorer.exe(3288) → msedge.exe(5928) → net1.exe(6440)

• MSBuild.exe
경로: \Device\HarddiskVolume3\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe
실행 흐름: MSBuild.exe(4680) → MSBuild.exe(7920)

그림 24. pstee 명령어 실행 결과

그림 24. pstee 명령어 실행 결과


psscan 명령어를 사용하여 해당 프로세스들이 이미 종료되었거나 은닉된 상태인지 확인하였다. 분석 결과, PID 608, 684, 4680에 해당하는 프로세스는 메모리 상에서 확인되지 않았다. 이는 해당 프로세스들이 이미 종료되어 메모리에서 완전히 제거되었기 때문으로 판단된다.

그림 25. psscan 명령어 실행 결과

그림 25. psscan 명령어 실행 결과


주요 기능 탐지 (기본 플러그인 실습)

dlllist 명령어를 사용하여 프로세스와 관련된 DLL 목록을 확인한 결과, net1.exe가 로드한 대부분의 DLL은 정상적인 Windows 시스템 DLL로 확인되었다.
그러나 FormBook은 PE 파일 전체를 메모리에 직접 로드하여 실행하는 파일리스(Fileless) 방식을 사용하므로, 디스크 상에 별도의 악성 DLL을 생성하지 않는다. 이로 인해 dlllist 결과상에서는 정상 프로세스와 동일한 DLL 구성으로 보이지만, 실제로는 프로세스 메모리 내부에서 악성 코드가 실행되고 있다.

그림 26. dlllist 명령어 실행 결과

그림 26. dlllist 명령어 실행 결과


실제로 FormBook 제작자가 공개한 설명에 따르면, FormBook은 별도의 DLL 파일을 생성하지 않고 메모리 인젝션 방식으로 동작하며, 의심스러운 Windows API 호출을 최소화하도록 설계되었다. 이러한 특성으로 인해 시그니처 기반 안티바이러스나 정적 분석 기법을 효과적으로 우회할 수 있다.

그림 27. FormBook 기능

그림 27. FormBook 기능


netscan 명령어를 사용하여 외부 연결 시도 흔적을 확인했으나, 특별한 흔적은 발견되지 않았다. 이는 HTTPS 트래픽이 정상 웹 트래픽으로 위장되어 탐지가 어려웠거나, 메모리 덤프 시점에 C&C 통신이 비활성 상태였기 때문일 수 있다. 또한 암호화된 통신을 통해 정상 트래픽으로 위장했거나, 로컬에서 수집된 데이터가 아직 외부로 전송되지 않은 상태였을 가능성도 있다.

그림 28. netscan 명령어 실행 결과

그림 28. netscan 명령어 실행 결과


cmdline 명령어를 사용하여 각 프로세스의 실행 시 명령줄 인수를 확인한 결과, MSBuild.exe 프로세스의 커맨드라인이 비어 있는 것으로 확인되었다. 정상적인 MSBuild.exe 실행 시에는 빌드 대상 프로젝트 파일(.xml 또는 .csproj) 경로가 명시되어야 하므로, 명령줄 인수가 없는 상태는 정상적인 빌드 수행이 아닐 가능성이 높다. MSBuild.exe는 Microsoft에서 제공하는 공식 빌드 도구이지만, 악성 XML 프로젝트 파일을 통해 임의 코드 실행이 가능한 LOLBin(Living Off The Land Binary)으로 널리 알려져 있다. FormBook과 같은 정보 탈취형 악성코드는 이러한 특성을 악용하여 정상 프로세스를 통해 악성 행위를 수행함으로써 탐지를 우회한다.

그림 29. cmdline 실행 결과

그림 29. cmdline 실행 결과


net1.exe 프로세스 역시 명령줄 인수가 없는 상태로 실행되었는데, 정상적인 Windows 환경에서 net1 명령어는 반드시 하위 명령어 및 인자를 요구한다.
인자 없이 실행된 net1.exe는 정상적인 시스템 관리 목적과는 부합하지 않으며, 이는 악성코드가 정상 시스템 프로세스를 위장 실행 대상으로 악용했을 가능성을 보여준다.

그림 30. net1의 명령어 인자 목록

그림 30. net1의 명령어 인자 목록


handles 명령어를 사용하여 net1.exe 프로세스가 사용하는 시스템 리소스를 확인한 결과, NGO-N-Q4X77EG17Z, 64274Q3C307-xWLG와 같은 의미 없는 랜덤 문자열 형태의 뮤텍스가 생성된 것이 확인되었다.
이러한 비정상적인 뮤텍스명은 악성코드가 중복 실행을 방지하거나 내부 상태를 관리하기 위해 사용하는 전형적인 기법이다.

그림 31. handles 명령어 실행 결과

그림 31. handles 명령어 실행 결과


또한 net1.exe 프로세스가 Microsoft Edge 브라우저 관련 파일에 접근한 흔적이 확인되었는데, 네트워크 명령어 도구인 net1.exe의 정상적인 기능 범위와는 무관한 동작이다. 이는 FormBook이 브라우저 저장 정보 및 세션 데이터를 수집하기 위해 해당 프로세스를 악용했을 가능성을 뒷받침한다.

그림 32. handles 명령어 실행 결과2

그림 32. handles 명령어 실행 결과2


malfind 명령어를 통해 메모리 내 악성코드 인젝션 여부를 확인한 결과, net1.exe 프로세스에서 의심스러운 메모리 영역이 탐지되었다.
해당 메모리 영역은 읽기(Read), 쓰기(Write), 실행(Execute) 권한이 동시에 부여된 RWX 속성을 가지고 있었으며, 이는 정상적인 Windows 프로세스에서는 거의 사용되지 않는 형태이다.
또한 무작위 hex 패턴과 함께 PE 파일의 시작을 의미하는 MZ 헤더(4D 5A)가 확인되었는데, 이는 악성코드가 자신의 전체 PE 이미지를 대상 프로세스의 메모리에 삽입하여 실행하는 PE 인젝션 기법을 사용했음을 의미한다.
이러한 방식은 디스크에 악성 파일을 남기지 않기 때문에 정적 분석 및 파일 기반 탐지를 효과적으로 우회할 수 있다.

그림 33. malfind 명령어 실행 결과

그림 33. malfind 명령어 실행 결과


filescan 명령어를 사용하여 메모리 상에 존재하는 파일 객체를 확인한 결과,
\Users\bino_\AppData\Roaming\jYzfFb.exe 경로에 위치한 의심스러운 실행 파일이 발견되었다.
해당 파일명은 의미 없는 랜덤 문자열로 구성되어 있다.

그림 34. filascan 명령어 실행 결과

그림 34. filascan 명령어 실행 결과


추가로 windows.filescan 결과를 작업 스케줄러 경로 기준으로 필터링한 결과,
jYzfFb.exe가 \Windows\System32\Tasks\Updates\jYzfFb 경로에 작업 스케줄러로 등록되어 있는 것이 확인되었다.
이는 jYzfFb.exe가 시스템 재부팅 이후에도 FormBook을 지속적으로 실행시키기 위한 드로퍼(Dropper) 역할을 수행하며, 영속성(persistence)을 확보하기 위해 작업 스케줄러를 악용한 것으로 판단된다.

그림 35. filescan | findstr 명령어 실행 결과

그림 35. filescan | findstr 명령어 실행 결과


메모리에서 FormBook 흔적 추출 및 분석 실습

vol.exe -f after.raw windows.memmap –pid 6440 –dump 명령어를 사용하여 net1.exe 프로세스(PID 6440)의 메모리 영역을 덤프한 후 분석을 진행했다.

그림 36. PID 6440 덤프

그림 36. PID 6440 덤프


FormBook의 C&C 서버 정보를 확인하기 위해 메모리 상에 남아 있는 도메인 문자열을 추출하였다.
FormBook은 추적을 회피하기 위해 다수의 C&C 서버를 운영하거나, 정상 도메인과 악성 도메인을 혼합하여 사용하는 것으로 알려져 있다.

그림 37. C2 리스트 출력

그림 37. C2 리스트 출력


실제로 메모리에서 추출된 도메인 목록에는 정상 웹사이트 주소와 악성 웹사이트 주소가 혼재되어 있었으며, 이는 정상 도메인을 미끼로 사용하여 실제 C&C 서버를 은폐하려는 의도로 판단된다.

그림 38. 악성 웹사이트 탐지

그림 38. 악성 웹사이트 탐지


그림 39. 악성 웹사이트 탐지2

그림 39. 악성 웹사이트 탐지2


메모리에서는 도메인뿐만 아니라 여러 IP 주소도 발견되었다.
이 중 203.251.233.119와 203.251.233.132는 한국 내 Akamai CDN 인프라에 속하는 공용 IP로 확인되었다. CDN 기반 인프라는 정상 트래픽과 구분이 어렵기 때문에, FormBook이 C&C 통신 또는 중간 경유지로 활용했을 가능성이 있다.

그림 40. IP 리스트 출력

그림 40. IP 리스트 출력


WriteProcessMemory는 다른 프로세스의 메모리 공간에 데이터를 기록할 때 사용되는 Windows API이며, CreateRemoteThread는 대상 프로세스 내에서 새로운 스레드를 생성하는 API 함수이다.
메모리에서 해당 API 문자열이 확인되었다는 점은 FormBook이 정상 프로세스의 메모리 공간에 악성 코드를 주입하고, 해당 프로세스 내부에서 코드를 실행하는 프로세스 인젝션 기법을 사용했음을 시사한다.

그림 41. WriteProcessMemory API 문자열 검색 결과

그림 41. WriteProcessMemory API 문자열 검색 결과


그림 42. CreateRemoteThread API 문자열 검색 결과

그림 42. CreateRemoteThread API 문자열 검색 결과


또한 LoadLibrary, GetProcAddress와 같은 동적 로딩 API가 다수 확인되었다. 이러한 방식은 실제 호출되는 API들이 PE 파일의 Import Table에 기록되지 않도록 하여, 정적 분석 및 시그니처 기반 탐지를 우회하는 데 사용된다.
이로 인해 FormBook의 행위는 파일 분석만으로는 파악하기 어려우며, 메모리 기반 분석이 필수적임을 확인할 수 있다.

그림 43. LoadLibrary API 문자열 검색 결과

그림 43. LoadLibrary API 문자열 검색 결과


그림 44. GetProcAddress API 문자열 검색 결과

그림 44. GetProcAddress API 문자열 검색 결과


MSBuild.exe(PID 7440)의 메모리를 덤프하여 HxD로 분석한 결과, net1.exe 실행과 관련된 문자열 및 실행 정보가 확인되었다.
이는 MSBuild.exe가 단순히 실행된 것이 아니라, 이후 단계에서 net1.exe를 실행하거나 악성 행위를 전달하는 중간 로더 역할을 수행했음을 의미한다.

그림 45. PID 7440의 net1.exe 실행 정보

그림 45. PID 7440의 net1.exe 실행 정보


그림 46. PID 7440의 net1.exe 실행 정보 (2)

그림 46. PID 7440의 net1.exe 실행 정보 (2)


종합적으로 분석한 결과, FormBook의 감염 시나리오는 다음과 같이 정리할 수 있다.
1. jYzfFb.exe(드로퍼 역할)가 실행되면, explorer.exe에 FormBook의 초기 페이로드를 인젝션한다.
2. 인젝션된 FormBook은 explorer.exe 내부에서 %WinDir%\System32\ 경로의 정상 Windows 시스템 파일을 탐색한 후, 임의의 정상 파일을 하위 프로세스로 실행한다. 본 실습 환경에서는 MSBuild.exe가 선택되었다.
3. MSBuild.exe가 악성 코드를 악용해 악성 페이로드를 컴파일/실행하고, 이후 최종 페이로드를 또 다른 정상 시스템 프로세스인 net1.exe에 인젝션한다.
4. net1.exe는 외형상 정상 프로세스로 보이지만, 실제로는 FormBook이 메모리에 상주하며 키로깅, 데이터 탈취, C&C 서버 통신 등의 악성 행위를 수행한다.

탐지 및 대응 방안

FormBook을 효과적으로 탐지하기 위해서는 메모리 기반 분석이 필수적이다.
FormBook은 파일 시스템에 남기는 흔적을 최소화하고, 정상 프로세스 내부에 악성 페이로드를 인젝션하는 방식으로 동작하기 때문에, 일반적인 파일 기반 탐지 방식만으로는 식별이 어렵다. 따라서 메모리 분석을 통해 비정상적인 프로세스 생성 관계, 읽기·쓰기·실행(RWX) 권한이 부여된 메모리 영역, 비정상적인 외부 통신 흔적, 키보드 후킹 관련 행위 등을 종합적으로 확인해야 한다.
실제 FormBook 감염이 의심되는 경우, 즉시 해당 시스템을 네트워크에서 격리하여 추가적인 정보 유출을 차단해야 한다. 이때 주의할 점은 시스템 전원을 즉시 종료하지 않고, 메모리 덤프를 우선적으로 수집하는 것이다. FormBook은 메모리 상에서 동작하는 특성을 가지므로, 전원을 차단할 경우 중요한 분석 증거가 소실될 수 있다.
메모리 분석을 통해 악성 프로세스가 식별되었다면, 해당 프로세스를 종료하고 관련 파일을 격리해야 한다. FormBook은 다중 인젝션 기법을 사용하여 여러 정상 프로세스에 악성 코드를 주입할 수 있으므로, 단일 프로세스 종료만으로는 충분하지 않을 수 있다. 따라서 연관된 프로세스 및 의심 파일을 함께 격리하여 추가 감염을 예방해야 한다.
또한 FormBook이 시스템 재부팅 이후에도 다시 실행되지 않도록 하기 위해서는 영속성(persistence) 메커니즘 제거가 필요하다. 이를 위해 작업 스케줄러에 등록된 의심 작업을 삭제하고, 레지스트리의 Run 키 및 시작 프로그램 폴더를 점검하여 자동 실행 항목을 제거해야 한다.
마지막으로 FormBook은 키로깅 및 정보 탈취 기능을 수행하는 악성코드이므로, 감염이 확인되었거나 의심되는 경우 수집되었을 가능성이 있는 계정 비밀번호 및 중요 정보는 즉시 변경하는 것이 바람직하다.

메모리 포렌식 심화 및 트로이 목마 분석

DLL 기초

DLL 개념과 역할

DLL(Dynamic Link Library)은 Windows 운영 체제에서 사용되는 실행 파일 형식 중 하나로, 실행 중인 프로그램에 동적으로 로드되어 기능을 제공하는 라이브러리 파일이다. DLL 파일은 코드, 데이터, 아이콘 및 이미지와 같은 자원을 포함할 수 있으며, 여러 응용 프로그램이 하나의 DLL을 공유하여 사용할 수 있다.
DLL의 주요 목적은 코드 재사용성 향상과 시스템 자원 절약이다. 동일한 기능을 여러 프로그램에서 각각 포함하는 대신, 공통 기능을 DLL로 분리하여 공유함으로써 중복 코드를 줄이고 메모리 사용량을 최소화할 수 있다.

정적 링크(static linking)는 컴파일 및 링크 과정에서 필요한 라이브러리 코드가 실행 파일 내부에 직접 포함되는 방식이다. 이 방식은 실행 파일 하나만으로 독립적인 실행이 가능하다는 장점이 있으나, 동일한 라이브러리를 사용하는 여러 프로그램이 각각 라이브러리를 포함하게 되어 실행 파일 크기가 증가하고 자원 사용이 비효율적이다.

그림 47. 정적 링크

그림 47. 정적 링크


반면, DLL은 동적 링크(dynamic linking) 방식을 사용한다. 동적 링크에서는 실행 파일이 실행될 때 필요한 DLL을 메모리에 로드하고, 해당 DLL의 함수 주소를 참조하여 기능을 호출한다. DLL은 실행 파일과 분리된 형태로 존재하므로, 필요할 때만 메모리에 로드되고 여러 프로그램에서 동시에 공유될 수 있다.

그림 48. 동적 링크

그림 48. 동적 링크


DLL 파일은 EXE 파일과 유사한 PE(Portable Executable) 구조를 가지지만, 단독으로 실행될 수 없고 다른 실행 파일이나 DLL에 의해 호출된다. 일반적으로 다음과 같은 구성 요소를 포함한다.
• Header: 파일 형식 및 구조 정보
• Data Directory: 코드 및 데이터 위치 정보
• Code Segment: 실행 가능한 코드
• Data Segment: 전역 변수 및 상수 데이터
• Mutable Data: 실행 중 변경 가능한 데이터
DLL이 로드되면 운영 체제는 해당 파일을 프로세스의 가상 메모리 공간에 매핑하고, 함수 호출이 가능하도록 주소를 재배치한다.

윈도우 DLL 로딩 과정

Windows는 DLL 파일을 로드할 때 정해진 검색 순서에 따라 파일을 탐색하며, 동일한 이름의 DLL이 여러 경로에 존재할 경우 가장 먼저 발견된 DLL이 로드된다.

Safe DLL 검색 모드 활성화 시 (기본값)
1. 애플리케이션이 로드된 디렉터리
2. 시스템 디렉터리 (C:\Windows\System32)
3. 16비트 시스템 디렉터리
4. Windows 디렉터리 (C:\Windows)
5. 현재 작업 디렉터리
6. PATH 환경 변수에 포함된 디렉터리

Safe DLL 검색 모드 비활성화 시
1. 애플리케이션이 로드된 디렉터리
2. 현재 작업 디렉터리
3. 시스템 디렉터리
4. 16비트 시스템 디렉터리
5. Windows 디렉터리
6. PATH 환경 변수에 포함된 디렉터리

Safe DLL 검색 모드는 DLL 하이재킹과 같은 보안 위협을 완화하기 위해 도입된 기능으로, 기본적으로 활성화되어 있다.
레지스트리 설정을 변경하거나 SetDllDirectory API를 사용하면 검색 순서에 영향을 줄 수 있다.

정상 DLL과 악성 DLL의 차이

정상 DLL과 악성 DLL의 가장 큰 차이는 사용 목적과 동작 방식에 있다.
정상 DLL은 운영체제나 소프트웨어의 기능을 지원하기 위해 사용되지만, 악성 DLL은 시스템을 손상시키거나 사용자 정보를 탈취하는 등 악의적인 행동을 수행하기 위해 제작된다.
정상 DLL은 윈도우 운영체제를 구성하는 핵심 요소 중 하나다. 윈도우 환경에서는 여러 프로그램이 공통으로 사용하는 기능이나 리소스를 DLL 파일로 분리해 관리한다. 이로 인해 각 프로그램이 동일한 코드를 중복해서 포함할 필요 없이, 필요한 DLL을 불러와 사용하는 방식으로 동작한다.

이러한 구조 덕분에 동일한 기능을 여러 프로그램에서 반복 구현하지 않아도 되며, 여러 프로세스가 하나의 DLL을 공유해 사용함으로써 메모리 사용량을 줄일 수 있다. 또한 기능 수정이나 보완이 필요할 경우 DLL 파일만 교체하면 되기 때문에 유지보수 측면에서도 효율적이다.

반면 악성 DLL은 시스템 내부에 침투해 사용자 몰래 악성 행위를 수행하는 DLL 파일을 의미한다. 공격자는 정상 DLL의 구조와 동작 방식을 악용해 악성 코드를 DLL 형태로 제작하고, 이를 정상 프로세스에 로드시키는 방식으로 공격을 시도한다.
악성 DLL은 원격에서 시스템을 제어할 수 있도록 백도어를 생성하거나, 키 입력을 기록하는 키로깅 기능, 화면을 캡처하는 기능 등을 통해 개인정보나 금융 정보를 탈취한다. 또한 다른 악성코드를 추가로 다운로드하거나 실행하는 역할을 수행하기도 한다.
특히 악성 DLL은 정상 DLL과 구분하기 어렵도록 유사한 파일 이름을 사용하거나, 정상 프로그램이 자주 접근하는 경로에 위치하는 경우가 많다. 대표적인 공격 기법으로는 DLL 하이재킹, DLL 인젝션, DLL 사이드로딩이 있다.

DLL 하이재킹은 정상 프로그램이 DLL을 로드하는 경로를 악용해 악성 DLL이 먼저 로드되도록 유도하는 방식이며, DLL 인젝션은 CreateRemoteThread, APC, SetWindowsHookEx 등의 기법을 이용해 다른 프로세스의 메모리에 악성 DLL을 주입하는 방식이다. DLL 사이드로딩은 취약한 프로그램이 정상 DLL 대신 악성 DLL을 로드하도록 유도하는 공격 기법이다.
또한 악성 DLL은 정상적인 디지털 서명이 없거나, 위조된 서명을 사용해 사용자나 보안 솔루션을 속이려는 특징을 가진다.

트로이목마 개념 및 특징

트로이목마 정의

트로이목마는 정상적인 소프트웨어로 위장하여 사용자가 의심 없이 설치하거나 실행하도록 유도한 후, 내부에 숨겨진 악성 행위를 수행하는 악성코드의 한 종류이다. 참고로 고대 그리스 신화의 트로이 목마 전설에서 유래했다고 한다.

그림 49. 트로이목마

그림 49. 트로이목마


일반적인 특징과 악성 행위 유형

바이러스나 웜과 달리 자기 복제 기능이 거의 없으며, 이메일 첨부파일, 소프트웨어 다운로드, 웹사이트 방문 등을 통해 유포된다. 최근에는 소셜 엔지니어링 기법과 결합하여 그 악성행위가 더욱 교묘해지고 있다.
주요 악성 행위로는 원격 제어 기능을 통한 C2 서버 운영으로 공격자가 감염된 시스템을 조작하고, 지속성 확보를 위해 시스템 재부팅 후에도 자동 실행되도록 레지스트리나 시작 프로그램에 등록한다. 또한 실행될 때마다 그 형태를 변화시키며 탐지를 회피하기도 한다.

트로이 목마 주요 유형은 다음과 같다.

  • 백도어(Backdoor) 트로이 목마: 백도어 트로이 목마는 공격자가 피해자의 컴퓨터에 원격으로 접근할 수 있도록 숨겨진 통로를 설치하는 유형이다.
  • 뱅킹 트로이(Banking Trojan) 목마: 뱅킹 트로이 목마는 금융 정보를 탈취하는 데 초점을 맞춘 악성코드이다.
  • 키로거(Keylogger) 트로이 목마: 키로거 트로이 목마는 사용자의 키보드 입력을 기록하여 로그인 정보, 신용카드 번호 등을 탈취한다.
  • 다운로더(Downloader) 트로이 목마: 다운로더 트로이 목마는 감염된 시스템에 추가 악성코드를 다운로드하여 설치하는 역할을 한다.
  • DDos 트로이 목마: DDos 공격을 수행하는 트로이 목마로, 감염된 시스템을 이용해 대규모 트래픽을 발생시켜 특정 서버를 마비시킨다.
  • 루트킷 (Rootkit) 트로이 목마: 루트킷 트로이 목마는 시스템 깊숙이 숨어 보안 소프트웨어의 탐지를 우회하는 악성코드이다.

Emotet 분석

Emotet 개요 및 발전사

Emotet은 처음부터 지금과 같은 위협적인 악성코드는 아니었다. 초기에는 은행 계좌 정보나 신용카드 정보 탈취에 초점을 맞춘 비교적 단순한 금융 악성코드였지만, 시간이 지나면서 공격 대상과 역할이 계속 확장됐다. 현재는 개인 사용자뿐만 아니라 기업, 정부 기관, 의료 기관까지 공격 범위를 넓히며 위협 수준이 크게 증가했다.

2014-2016
Emotet은 2014년 독일에서 처음 발견됐다. 당시에는 스팸 이메일에 포함된 첨부파일을 실행하도록 유도하는 방식이 주된 유포 수단이었다.

그림 50. Emotet 스팸 이메일

그림 50. Emotet 스팸 이메일


2017-2019
Emotet은 다층적 운영 메커니즘을 갖춘 정교한 구조로 발전하였으며, 스팸 미끼, 악성 첨부파일, 악성코드 실행파일 등을 다층적으로 운영되는 방식으로 진화했다.

2019-2021
이 시점부터 Emotet은 단순한 악성코드가 아니라, 국제적인 봇넷을 운영하는 형태로 성격이 달라졌다.

2021, 1월
2021년 1월 17일 기준으로 45,000대 이상의 컴퓨터를 감염시킨 걸로 보도되었다. FBI는 다른 7개국의 법 집행 기관과 협력하여 자국 관할권 내의 서버에 있는 Emotet 악성코드를 법 집행기관이 제작한 파일로 대체하였다. 이 파일은 Emotat과 연결된 C2서버와 감염된 컴퓨터 간의 통신을 차단하고, 감염된 컴퓨터에서 봇넷을 분리하여 추가 악성코드가 설치되는 것을 방지한다.

그림 51. Emotet 봇넷 차단 작전 (2021)

그림 51. Emotet 봇넷 차단 작전 (2021)


2021, 11월
1년간 점적한 Emotat은 TricBot 트라이 목마를 통해 재등장했다. TrickBok이나 Qbot과 같은 악성코드의 경우, 랜섬웨어 공격으로 이어지는 경우가 많다.

그림 52. 트로이목마2

그림 52. 트로이목마2


App Installer 프로토콜 악용을 시작했고, Cobalt Strike 페이로드 배포를 시작했다.
또한 첨부파일에서 악성 링크를 통한 Office 문서 다운로드 방식으로 변화했으며, .hta 파일과 PowerShell 스크립트를 이용하기 시작했다.
App Installer는 Windows 10/11에 내장된 기능으로, 사용자가 앱을 설치할 수 있게 해주는 프로토콜이다. Emotet 공격자는 이를 악용하여 가짜 Adobe PDF 소프트웨어로 위장한 악성 Windows App Installer 패키지를 배포했다.

Emotet 공격자는 침투 테스팅 도구로 개발된 Cobalt Strike를 악용하여 Cobalt Strike 페이로드인 Beacon이라는 에이전트를 배포했다.

즉, Emotet은 App Installer를 통해 시스템에 침투 후, Cobalt Strike 침투 도구를 추가로 설치하여 시스템을 완전히 장악하는 방식으로 진화했다.

그림 53. Emotet 악성코드의 감염 과정

그림 53. Emotet 악성코드의 감염 과정


2022-현재
PowerShell 명령어 업데이트와 암호화 알고리즘 개선하여 배포하고 있다.
최근 기사에 따르면, 북한 해킹그룹 김수키가 ‘사례비 지급’ 내용으로 위장한 원노트를 이용해 악성코드를 유포한 정황이 포착되었다. 이처럼 Emotet은 꽤 최근까지도 악성활동을 이어왔다.

그림 54. 2023년 Emotet 보안뉴스 기사

그림 54. 2023년 Emotet 보안뉴스 기사


감염 경로 및 지속성 메커니즘

그림 55. Emotet 감염사슬

그림 55. Emotet 감염사슬


  • 감염 경로 (Infection Chain)
    Emotet은 주로 피싱 이메일을 통해 유포된다. 이메일에 포함된 첨부파일이나 링크를 시작점으로 삼아, 여러 단계를 거쳐 시스템을 장악하는 방식이다. 단순히 파일 하나를 실행하는 수준이 아니라, 감염 과정이 단계적으로 이어진다는 점이 특징이다.

(1) 초기 유입 – 악성 스팸 메일(Malspam)
공격자는 사용자의 관심을 끌 수 있는 제목과 내용을 가진 이메일을 대량으로 발송한다. 이메일에는 악성 매크로가 포함된 Microsoft Office 문서(Word, Excel)나 이를 압축한 ZIP 파일이 첨부되어 있는 경우가 많다. 최근에는 매크로 차단을 우회하기 위해 바로가기 파일(.LNK)이나 엑셀 추가 기능 파일(.XLL)을 이용하는 변종도 발견되고 있다.

(2) 1차 로더 실행 – Dropper 단계
첨부된 문서를 열면 “콘텐츠 사용” 또는 “편집 사용”을 요구하는 안내 문구가 표시된다. 정상적인 보안 경고처럼 보이지만, 매크로 실행을 유도하기 위한 장치다.
사용자가 이를 허용하면 문서 내부에 숨겨진 스크립트가 실행되고, 이 과정에서 PowerShell이 호출되는 경우가 많다. PowerShell은 외부의 침해된 서버로부터 Emotet의 핵심 DLL 파일을 내려받는 역할을 한다.
이 단계에서는 탐지를 피하기 위한 기법도 함께 사용된다. 파일 크기를 비정상적으로 늘리거나(Binary Padding), 여러 단계로 난독화된 스크립트를 사용해 백신의 정적 분석을 회피하려는 시도가 대표적이다.

(3) Emotet 실행 및 내부 확산 – Payload 단계
다운로드된 Emotet DLL은 rundll32.exe와 같은 정상적인 윈도우 프로세스를 통해 메모리에 로드된다. 이를 통해 파일 기반 탐지를 피하고, 악성 행위의 실행 주체를 정상 프로세스로 위장한다.
또한 감염 시점마다 파일명, 경로, 프로세스명, 서비스명을 바꿔 사용하는 특징이 있어, 단순한 시그니처 기반 탐지를 어렵게 만든다.
한 대의 PC가 감염되면 내부망 확산도 시도된다. Emotet은 웜처럼 동작하며, 관리자 계정 비밀번호에 대한 무차별 대입 공격이나 운영체제 취약점을 이용해 동일 네트워크 내 다른 시스템으로 전파된다.

지속성 메커니즘 (Persistence Mechanisms)
Emotet은 시스템이 재부팅되거나 사용자가 로그아웃하더라도 계속 악성코드 실행 상태를 유지하기 위해 다음과 같은 여러가지 방법으로 지속성을 확보한다.
• 레지스트리 키 등록 (Registry Run Keys)
가장 흔하게 사용되는 방식이다. 윈도우 시작 시 자동 실행되는 레지스트리 경로
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
에 자신의 실행 경로를 등록해, 시스템이 재시작될 때마다 다시 실행되도록 한다
• 예약 작업 생성 (Scheduled Tasks)
윈도우 작업 스케줄러를 악용하는 방식이다. 특정 시간이나 이벤트 발생 시 Emotet이 자동 실행되도록 작업을 등록한다. 이는 레지스트리만 감시하는 보안 솔루션을 우회하기 위한 수단이기도 하다.
• 서비스 등록 (Windows Services)
Emotet은 자신을 정상적인 윈도우 서비스처럼 등록하기도 한다. 백그라운드에서 상시 실행되도록 만들고, 서비스 이름과 파일명을 임의의 문자열로 설정해 분석과 탐지를 어렵게 한다.
• 자동 시작 폴더 (Startup Folder)
실행 파일의 바로가기를 시작 프로그램 폴더에 생성해, 사용자가 로그온할 때마다 악성코드가 실행되도록 하는 방식도 사용된다. 단순하지만 여전히 효과적인 방법이다. 이처럼 여러 지속성 메커니즘이 동시에 사용되기 때문에, 단순히 악성 파일 하나를 삭제하는 것만으로는 Emotet을 완전히 제거하기 어렵다.

프로세스 인젝션 기법 분석

Emotet은 자신의 악성 행위를 숨기고 보안 제품의 탐지를 회피하기 위해 프로세스 인젝션(Process Injection) 기법을 적극적으로 사용한다. 프로세스 인젝션 기법이란 악성 코드를 정상적인 윈도우 프로세스의 메모리 공간에 주입하여 실행하는 기술이다.
Emotet은 주로 다음과 같은 고전적이고 안정적인 프로세스 인젝션 절차를 따른다.

  • (1) 타겟 프로세스 생성 및 선택 (Target Process)
    Emotet은 explorer.exe (윈도우 탐색기)와 같이 신뢰도가 높고 항상 실행 중인 시스템 프로세스나, 자신이 직접 생성한 자식 프로세스(예: svchost.exe의 복사본)를 타겟으로 삼는다. 정상 프로세스의 컨텍스트에서 실행되면 행위 기반 탐지를 우회하기 용이하기 때문이다.
  • (2) 메모리 공간 할당 (Memory Allocation)
    VirtualAllocEx Windows API 함수를 호출하여 타겟 프로세스의 가상 메모리 공간에 악성 DLL(페이로드)을 기록할 공간을 할당한다. 이 공간은 ‘읽기, 쓰기, 실행’(RWX) 권한을 갖도록 설정된다.
  • (3) 악성 코드 작성 (Writing Malicious Code)
    WriteProcessMemory API 함수를 사용하여 할당된 메모리 공간에 Emotet의 악성 코드를 바이트 단위로 복사하여 주입한다.
  • (4) 원격 스레드 실행 (Remote Thread Execution)
    CreateRemoteThread API 함수를 호출하여 타겟 프로세스 내에서 새로운 스레드를 시작시킨다. (이 스레드의 시작점은 바로 이전에 주입한 악성 코드의 주소이다.)
    이 순간부터 Emotet의 모든 악성 행위(정보 수집, C&C 통신 등)는 정상 프로세스(예: explorer.exe)의 이름으로 수행되므로, 작업 관리자나 기본적인 모니터링 도구에서는 악성 행위를 인지하기 매우 어려워진다.

이러한 일련의 과정은 악성코드 분석을 방해하고, 어떤 프로세스가 진짜 악성 행위의 주체인지 파악하기 어렵게 만드는 Emotet의 핵심적인 은폐 기술이다.

일반적으로 Emotet은 피싱 이메일을 통해 유포되지만, 본 실습에서는 감염 과정을 보다 정확하게 관찰하기 위해 악성코드 압축 파일을 직접 다운로드하여 실행하였다.

그림 56. Emotet 다운로드

그림 56. Emotet 다운로드


악성코드 실행 이전에 Process Explorer를 미리 실행해 두고, 새로운 프로세스 생성 여부와 함께 explorer.exe, svchost.exe와 같은 정상 시스템 프로세스에 대한 코드 인젝션 발생 여부를 중점적으로 관찰하였다.
해당 과정은 동적 분석에 해당하지만, 이후 인젝션으로 생성된 파일을 정확히 분석하기 위한 사전 단계로 수행하였다.

그림 57. Process Explorer 창

그림 57. Process Explorer 창


Emotet 실행 직후 ENRth.exe라는 파일이 생성되어 실행되었으나, 매우 짧은 시간 내에 종료되는 모습이 확인되었다. 정상적인 프로그램으로 보기에는 실행 시간이 지나치게 짧으며, 이는 악성코드 주입 과정에서 생성된 임시 실행 파일일 가능성이 높다.
이후 Volatility3를 이용하여 메모리 상의 프로세스 정보를 분석하였다. 먼저 pslist 플러그인을 통해 당시 실행 중이던 프로세스 목록을 확인하였다.

그림 58. pslist 출력 결과

그림 58. pslist 출력 결과


pslist 결과를 살펴보면 PID 8188, 6700의 6f6bac4133e06b 프로세스가 특히 의심스럽다. 일반적인 정상 프로그램은 사람이 인지할 수 있는 의미 있는 파일명을 가지는 경우가 많지만, 이와 같이 무작위 문자열 형태의 파일명은 탐지를 회피하기 위해 악성코드에서 자주 사용되는 특징이다.
또한 해당 프로세스는 각각 두 번씩 실행된 기록이 확인되었다.

PID 8188과 6700의 부모 프로세스가 모두 PID 532로 동일하다는 점에 주목하여 해당 PID를 추적한 결과, 다음과 같은 관계가 확인되었다.

PID 532는 csrss.exe와 연결되어 있었다. csrss.exe는 Windows 운영체제에서 기본적으로 동작하는 Client/Server Runtime Subsystem으로, 시스템 핵심 기능을 담당하는 프로세스이다.
해당 프로세스는 메모리에 상주하며 여러 개의 인스턴스로 실행되기 때문에, 다수의 악성코드가 코드 인젝션 대상으로 삼는 경우가 많다.

특히 의심스러운 점은, csrss.exe와 같은 중요 시스템 프로세스는 일반적으로 사용자 수준 애플리케이션이나 무작위 파일명의 부모 프로세스가 되지 않는다는 점이다. 이러한 비정상적인 부모-자식 관계는 프로세스 인젝션(Process Injection) 기법을 통해 악성코드가 실행되었을 가능성을 강하게 시사한다.

또한 PID 8188과 6700의 실행 시간은 모두 6초 이하로 매우 짧게 나타났다. 이와 같은 짧은 실행 시간은 드로퍼(Dropper) 악성코드의 전형적인 특징이다. 드로퍼는 실제 악성 페이로드를 시스템에 생성·실행한 후, 자신의 흔적을 최소화하기 위해 즉시 종료된다.
이러한 점을 종합할 때, Emotet 감염 과정에서 드로퍼가 생성·실행되었을 가능성이 매우 높다.

그림 59. pslist 출력 결과(2)

그림 59. pslist 출력 결과(2)


일반적인 드로퍼의 동작 방식은 다음과 같다. 유포 페이지로부터 다운로드된 ZIP 파일을 압축 해제하면 install_setup.exe와 같은 파일이 생성되며, 이를 실행할 경우 TEMP 경로에 setup_installer.exe 형태의 7z SFX(Self-Extracting Archive) 파일을 생성하고 실행한다. 이 내부에는 10~15개의 악성코드 파일과 이를 실행하기 위한 로더가 포함되어 있다.

그림 60. 드로퍼

그림 60. 드로퍼


pslist 분석 결과, PID 532 아래에서 7zM.exe 프로세스가 실행된 흔적이 확인되었다. 이러한 정황은 악성코드가 자체적으로 압축 해제 과정을 수행하여 추가 페이로드를 로딩하도록 설계되었음을 보여주며, 드로퍼 방식의 감염 시나리오와 일치한다.

그림 61. pslist 출력 결과 (3)

그림 61. pslist 출력 결과 (3)


6f6bac4133e06b 프로세스는 다음과 같은 추가적인 의심 프로세스들을 실행한 것으로 확인되었다.
• ENRth.exe (PID 8544)
• mmgaserver.exe (PID 9160)
• iesysprep.exe (PID 3584)
해당 프로세스들은 정상적인 Windows 프로세스 명칭과 일치하지 않으며, ENRth.exe가 cmd.exe를 실행한 흔적이 확인되었다. 이는 시스템 명령어를 이용한 악성 동작이 이루어졌을 가능성을 보여준다.

그림 62. pslist 출력 결과 (4)

그림 62. pslist 출력 결과 (4)


pslist 분석을 통해 단일 악성 프로세스뿐만 아니라, 여러 의심 프로세스와 이들 간의 실행 관계를 함께 확인할 수 있었다. 이를 토대로 악성코드의 전체 실행 흐름을 다음과 같이 정리하였다.

그림 63. pslist로 확인한 악성코드 흐름

그림 63. pslist로 확인한 악성코드 흐름


다음으로 서비스 및 레지스트리 변조 여부를 확인하기 위해 svcscan과 registry.printkey 플러그인을 실행하였다.

그림 64. svcscan 출력 결과

그림 64. svcscan 출력 결과


그림 65. registry.printkey 출력 결과

그림 65. registry.printkey 출력 결과


레지스트리 출력 결과를 분석한 결과, 앞서 의심 프로세스로 판단한 PID의 실행 시간과 정확히 일치하는 시점에 레지스트리 파일 변경 흔적이 확인되었다.
6f6bac4122e06b 프로세스의 생성 시간은 2025-09-17 08:43:46이었으며, registry.printkey 결과에서 settings.dat 파일이 2025-09-17 08:43:43, 즉 프로세스 생성 약 3초 전에 수정되었다.
이는 악성코드가 본격적인 실행 이전에 시스템 환경을 변경하거나 사전 설정 작업을 수행했음을 보여준다.
또한, 악성 프로세스 종료 약 5초 후 UsrClass.dat 파일이 수정된 것이 확인되었으며, 이는 드로퍼가 인젝션 수행 후 지속성을 설정한 것으로 판단된다.

지금까지의 분석 내용을 시간 순서대로 정리하면 다음과 같다.
• 08:43:43: 공격 시작, settings.dat 수정 → 사전 환경 설정 수행
• 08:43:46: 주입된 csrss.exe를 통해 메인 드로퍼 6f6bac4133e06b 실행
• 08:43:47 ~: 드로퍼가 ENRth.exe, mmgaserver.exe 등 실제 악성 페이로드 실행
• 08:43:52: 드로퍼가 역할을 수행한 후 자가 종료
• 08:43:57: 지속성 확보를 위해 UsrClass.dat에 관련 정보 등록

Malfind 명령어를 통해 SearchApp.exe(PID 6340)가 탐지되었다. SearchApp.exe는 Windows 검색 기능을 담당하는 정상적인 시스템 프로세스이지만, 출력 결과를 보면 PAGE_EXECUTE_READWRITE 권한을 가진 메모리 영역이 존재하는 것이 확인되었다.
일반적으로 프로그램은 코드 영역을 읽기·실행(RX), 데이터 영역을 읽기·쓰기(RW)로 분리하여 사용한다. 그러나 malfind에서 확인된 메모리 영역은 읽기, 쓰기, 실행이 모두 가능하도록 설정되어 있었으며, 이런 구조는 악성코드가 메모리에 코드를 기록한 뒤 즉시 실행할 때 흔히 사용된다.
데이터 실행 방지(DEP) 기능은 RWX 권한이 동시에 부여되는 것을 제한한다. 따라서 SearchApp.exe에서 확인된 메모리 권한 설정은 일반적인 동작과 차이가 있으며, 악성 행위가 발생했을 가능성을 보여준다.
Hexdump를 확인한 결과 48 ff e0 명령어가 존재하였으며, 이는 jmp rax에 해당한다. 이 명령어는 실행 흐름을 특정 주소로 전달하는 역할을 하며, 주입된 코드나 쉘코드 분석 과정에서 자주 관찰되는 패턴이다.

그림 66. malfind 출력 결과

그림 66. malfind 출력 결과


그 다음으로 의심 PID(8188, 6700, 8544, 9160, 3584)를 추려 덤프를 시도하였다. 이 중 PID 9160과 3584의 덤프가 출력되었으며, 나머지는 암호화되어 읽을 수 없었다.

덤프 파일 상단에서 “This program cannot be run in DOS mode” 메시지가 확인되었는데, 이는 DOS stub이라 불리는 모든 Windows 실행 파일(PE 파일)에 기본 포함되는 문구이다. 또한 PE 시그니처(50 45 00 00)를 통해 해당 덤프가 완전한 실행 파일임을 확인할 수 있었다.
결론적으로, 이 두 PID의 덤프 파일은 단순 코드 조각이 아니라 완전한 악성 실행 파일이며, 일반적인 프로그램처럼 DLL을 로드하는 방식이 아닌 독립적 실행 파일로 존재함이 확인되었다.

그림 67. PID 9160, 3584 덤프

그림 67. PID 9160, 3584 덤프


VirusTotal 검증 결과, 다수 백신 엔진이 해당 파일을 IcedID 또는 Marte 악성코드로 탐지하였다.
• IcedID: 원래 금융정보 탈취용 뱅킹 트로이목마였으나, 최근에는 다른 악성코드를 시스템에 내려받는 로더(Loader) 역할을 수행
• Marte: 쉘코드를 이용해 정상 프로세스에 침투하는 로더형 악성코드
이 과정에서 malfind로 확인한 프로세스 주입(Process Injection) 기법이 명확히 드러난다.

그림 68. VirusTotal 결과

그림 68. VirusTotal 결과


C&C 통신 패턴 분석

C&C 통신 패턴 분석의 목적은 악성코드가 외부와 어떤 방식으로 통신하는지 확인하는 것이다. ENRth.exe (PID 8544)와 같은 단기 실행 프로세스가 남긴 TIME_WAIT 상태 연결은, 악성코드가 C&C 서버에 자신의 존재를 알리는 신호인 Beaconing 흔적으로 판단된다.

그림 69. NETSCAN 출력 결과

그림 69. NETSCAN 출력 결과


IP 조회 결과, 79.137.83.50이 악성 IP로 의심되었다. 악성코드는 방화벽을 우회하고 탐지를 피하기 위해, HTTP/HTTPS 포트를 사용하여 정상 트래픽처럼 위장하는 경우가 많으며, 실제 내용은 암호화된 C&C 통신인 경우가 대부분이다. 본 실습에서도 SearchApp.exe에 주입되어 443 포트로 통신한 사례가 확인되었다.

그림 70. VirusTotal 결과 (2)

그림 70. VirusTotal 결과 (2)


네트워크 아티팩트 추출
netscan 출력 결과는 시스템의 네트워크 활동을 확인할 수 있는 정보로, 악성코드 분석에서 중요한 아티팩트에 해당한다. 특히 악성코드는 C&C 서버 정보를 실행 파일 내부에 포함시키는 경우가 많으며, 이 정보는 네트워크 행위를 파악하는 데 핵심적인 단서가 된다.
C&C 서버 주소는 평문으로 존재하기보다는 실행 파일 내부에 암호화된 형태로 저장되는 경우가 많다. 따라서 해당 정보 역시 중요한 네트워크 아티팩트로 간주된다.
다만, 현재 다른 출력 파일에서 확인된 악성코드 실행 파일은 전체적으로 암호화되어 있어 C&C 서버 정보를 바로 확인할 수 없는 상태이다. 이에 따라 C&C 서버를 식별하기 위해서는 실행 파일에 대한 복호화 과정이 필요하다.

그림 71. Strings 명령어

그림 71. Strings 명령어


그림 72. strings 명령어 결과

그림 72. strings 명령어 결과


Strings 명령어를 사용해 악성코드 내부의 문자열을 확인하려 했지만, 읽을 수 있는 정보는 거의 없었다. 이는 악성코드가 패킹되거나 암호화되어 있음을 나타낸다.

Formbook 실습 정리

분석 결과, 모든 악성 행위는 드로퍼(Dropper)에서 시작된 것으로 확인되었다. csrss.exe(PID 532)를 부모 프로세스로 하여 비정상적인 프로세스 6f6bac4133e06b(PID 8188)이 생성되었으며, 해당 프로세스가 드로퍼 역할을 수행한 것으로 판단된다. 드로퍼는 페이로드를 시스템에 생성하고 실행한 뒤, 약 6초 후 종료되었다.

드로퍼가 종료되기 전, 여러 악성 프로세스가 추가로 실행되었다.
ENRth.exe(PID 8544), mmgaserver.exe(PID 9160), iesysprep.exe(PID 3584)가 확인되었으며, 이들 페이로드는 시스템 정보 수집, 추가 악성코드 다운로드, C&C 서버와의 통신 등의 행위를 수행한 것으로 보인다. 특히 ENRth.exe가 cmd.exe를 실행한 흔적이 확인되어, 명령 기반 악성 활동이 이루어졌음을 알 수 있다.

이후 페이로드는 정상 시스템 프로세스인 SearchApp.exe(PID 6340)에 자신의 코드를 주입하였다. malfind 분석 결과, PAGE_EXECUTE_READWRITE 권한을 가진 메모리 영역이 확인되었으며, 해당 영역을 덤프한 결과 완전한 형태의 PE 파일임이 확인되었다.
이는 프로세스 주입 기법이 사용되었음을 보여준다.

덤프된 실행 파일을 VirusTotal로 분석한 결과, IcedID 계열 악성코드로 최종 식별되었다. 또한 Strings 및 YARA 분석을 통해 실행 파일에 패킹 또는 암호화가 적용되어 있음을 확인할 수 있었다.

다음 과정을 도식으로 정리하면 다음과 같다.

그림 73. Emotet 침투 시나리오 도식도

그림 73. Emotet 침투 시나리오 도식도


이 모든 분석 결과를 통해, 초기 드로퍼가 시스템에 침투해 다수의 페이로드를 실행한 후 종료되었으며, 실행된 페이로드들은 정상 프로세스인 SearchApp.exe에 주입되어 탐지를 회피하며 활동했음을 알 수 있었다.

탐지 및 대응 방안

(1) 탐지 방법
Emotet 악성코드는 고유한 코드 패턴을 가지고 있어, YARA 룰을 활용하면 효과적으로 탐지할 수 있다. 실제로 다양한 Emotet 탐지용 YARA 룰이 존재하며, 이를 통해 메모리나 파일 내 특정 패턴을 찾아낼 수 있다.

예를 들어, 한 YARA 룰에서는 $a = { 66 83 38 5C … }와 같이 16진수 바이트 패턴을 정의한다. 이 값들은 단순한 텍스트가 아니라, Emotet 변종이 실행될 때 메모리 상에서 나타나는 고유한 기계어 명령어 순서이다. 조건 부분에서 all of them을 사용하면, 정의된 모든 패턴이 발견될 때 해당 파일이나 메모리를 Emotet로 탐지하도록 설정된다.

그림 74. Emotet 탐지 YARA룰 스크립트

그림 74. Emotet 탐지 YARA룰 스크립트


이 YARA 룰을 적용한 결과, 이전에 의심되었던 PID 8188과 6700이 Emotet로 탐지되는 것이 확인되었다. 이는 pslist 분석 결과와 일치하며, 분석 과정에서 확인된 사실과 동일하다.

그림 75. YARA를 통한 Emotet 탐지 결과

그림 75. YARA를 통한 Emotet 탐지 결과


YARA 룰 외에도 악성 행위를 탐지할 수 있는 방법은 여러 가지가 있다. 예를 들어, 프로세스에서 이상한 네트워크 연결(C2 서버) 발생 여부를 확인하거나, malfind를 이용해 RWX 권한을 가진 메모리 영역이 생성되었는지 점검할 수 있다. 또한, 신규 또는 수정된 실행 파일을 모니터링하고 해시값을 비교하는 방법도 활용할 수 있다.

(2) 대응방안
악성코드의 확산과 피해를 방지하기 위해 다음과 같은 대응 방안을 권장한다.
먼저, 악성 소프트웨어(.dll, .exe)와 관련된 이메일 첨부 파일을 차단하고, 바이러스 백신으로 검사할 수 없는 압축 파일(.zip 등)도 차단해야 한다. 또한, 바이러스 백신을 운영하는 동시에 운영체제와 소프트웨어의 패치를 체계적으로 관리하고, 감염이 확인된 시스템은 네트워크에서 격리하며 감염된 프로세스를 즉시 종료해야 한다.
이메일 게이트웨이에 필터를 적용하고, 방화벽에서 의심스러운 IP 주소를 차단하는 것도 중요하다. 최소 권한 원칙이 준수되고 있는지 확인하고, 파일 및 프린터 공유 서비스를 비활성화하여 불필요한 접근을 차단하는 것 역시 필요하다.
마지막으로, 이동식 미디어를 사용할 때 주의를 기울이고, 운영체제와 소프트웨어는 항상 최신 버전으로 업데이트하는 것이 안전하다.

정보탈취형 악성코드 분석

정보탈취형 악성코드 개념

“인포스틸러(정보탈취형 악성코드)”는 웹 브라우저나 이메일 클라이언트 같은 프로그램에 저장되어 있는 사용자 계정 정보나 가상화폐 지갑 주소, 파일과 같은 사용자의 정보들을 탈취하는 것이 목적인 악성코드이다.
인포스틸러를 유포하는 공격자들은 감염 시스템들에서 수집한 계정 정보를 악용할 수 있다. 예를 들어 계정 정보를 포함한 다양한 사용자 정보들을 공격에 직접 이용하거나 딥웹에 판매하여 다른 공격자들이 이용할 수도 있다. 탈취 대상이 기업의 사용자일 경우에는 기업 네트워크 침투에 이용될 수도 있고 수집한 메일 목록들을 이용해 또 다른 스팸 메일 공격에 사용할 수도 있다.
또한, 인포스틸러는 최근들어 그 수가 급격히 증가하여 우려를 불러오고 있는 악성코드 중 하나이다. 나름 큰 규모의 공급망을 가진 대기업들도 줄줄이 피해를 보면서, 보안 패치에 아직까지 많은 보안기업이 힘쓰고 있다.

그림 76. 데이터넷 뉴스

그림 76. 데이터넷 뉴스


그림 77. byline 뉴스

그림 77. byline 뉴스


대표적인 정보탈취형 악성코드(Infostealer)에는 여러 종류가 있다. Formbook은 정상 문서로 위장하거나 피싱 이메일의 첨부 파일 형태로 유포되며, 키로깅과 웹 브라우저 폼 정보 탈취 기능을 주로 수행한다.
AgentTesla는 .NET 기반 악성코드로, 다양한 응용 프로그램에서 계정 및 인증 정보를 탈취할 수 있으며 파일리스 방식으로 실행되는 등 다양한 특징을 가진다. Lokibot은 이메일, 웹 브라우저, FTP 계정 정보 탈취를 주요 기능으로 하며, 피싱 메일을 통해 대량으로 유포되는 경우가 많다.
Redline은 암호화폐 지갑 정보와 시스템 정보 등 비교적 폭넓은 데이터를 수집하는 데 사용되며, SnakeKeyLogger는 키로깅 기능에 특화되어 있어 사용자의 입력 정보를 수집한 뒤 원격 서버로 전송하는 방식으로 동작한다.

스틸러 악성코드의 공격 방식은 크게 정보 수집과 유출 단계로 구분된다. 수집 단계에서는 브라우저 후킹으로 웹 활동을 모니터링하고, 키로깅으로 키보드 입력을 추적하며, API 후킹으로 인증 프로세스를 가로채고, GUI 캡처로 화면 정보를 수집한다. 유출 단계에서는 수집된 사용자 데이터, 계정 인증정보, 클립보드 및 스크린샷 파일을 공격자의 서버로 전송한다.

그림 78. A systematization of the information-stealing features of Snake(출처: Cybereason)

그림 78. A systematization of the information-stealing features of Snake(출처: Cybereason)


악성코드가 실행하는 주요 기능은 다음과 같다.
• 키로깅: 사용자의 키보드 입력을 실시간으로 기록하여 아이디, 비밀번호, 개인정보 등 민감한 정보를 수집한다.
• 클립보드 데이터 탈취: 복사(Ctrl+C)한 내용을 가로채어 비밀번호, 계좌번호, 암호화폐 지갑 주소 등 중요한 정보를 수집한다.
• 스크린샷 탈취: 화면 캡처를 통해 온라인 뱅킹, OTP, 기밀 문서 등 시각적 정보를 확보한다.
• 인증정보 탈취: 저장된 로그인 정보를 추출하여 다양한 플랫폼에서 계정 정보를 탈취한다.
• 데이터 유출: 수집한 정보를 외부 서버로 전송하며, FTP, SMTP, Telegram 등 다양한 통신 프로토콜을 활용한다.

악성코드가 주로 노리는 인증 정보는 Discord, Telegram, Slack과 같은 통신 플랫폼, FileZilla, WinSCP 등 FTP 클라이언트, Outlook, Thunderbird와 같은 메일 클라이언트, Chrome, Edge, Firefox 등 웹 브라우저, 그리고 저장된 무선 네트워크 비밀번호 등으로 정리할 수 있다. 데이터 전송은 FTP 서버 업로드, 이메일 전송, 최근에는 Telegram Bot API를 활용하는 방식이 주로 사용된다.

Agent Tesla 분석

Agent Tesla 개요

Agent Tesla는 정보 탈취를 목적으로 하는 인포스틸러 계열 악성코드로, 주로 스팸 이메일을 통해 유포된다. 송장, 선적 서류, 구매 주문서 등 정상 문서로 위장한 이메일 첨부 파일 형태로 전달되며, 파일 이름 또한 실제 업무 문서와 유사하게 구성되는 경우가 많다. 일부 샘플은 .pdf, .xlsx 등 문서 파일 확장자로 위장해 사용자의 실행을 유도한다.

Agent Tesla는 다양한 애플리케이션을 대상으로 정보를 수집하는 것이 특징이다.
웹 브라우저, 이메일 클라이언트, FTP 클라이언트, VNC 프로그램 등 여러 환경에서 저장된 계정 정보를 수집할 수 있으며, Chromium 기반 브라우저와 Firefox 등이 주요 대상에 포함된다.
수집된 정보는 내부에 저장된 설정 값을 기반으로 복호화 과정을 거쳐 추출되며, 이후 SMTP, FTP 또는 Telegram API와 같은 통신 방식을 이용해 공격자의 C&C 서버로 전송된다. 이러한 특성으로 인해 Agent Tesla는 비교적 단순한 구조를 가지면서도 지속적으로 변종이 등장하고 있다.

그림 79. Agent Tesla 감염과 정보 탈취 과정

그림 79. Agent Tesla 감염과 정보 탈취 과정


Agent Tesla 계열 악성코드는 7년 이상 지속적으로 활동해 온 원격 접속 트로이 목마(RAT)로, 현재까지도 Windows 환경에서 흔히 관찰되는 위협 중 하나이다. 다양한 공격자들이 이 악성코드를 활용해 스크린샷 촬영, 키로깅, 클립보드 캡처 등의 기능을 수행하며, 이를 통해 사용자 자격 증명과 각종 민감 정보를 탈취한다.
Agent Tesla는 멀웨어 컴파일 과정에서 공격자별 설정 값이 하드코딩되기 때문에, 변종에 따라 동작 방식이 다소 달라질 수 있다. 이러한 특성으로 인해 악성코드는 지속적으로 변화해 왔으며, 최근에는 웹 브라우저와 이메일 클라이언트뿐만 아니라 VPN 클라이언트, 사용자 이름과 비밀번호를 저장하는 다양한 소프트웨어까지 공격 대상으로 확장되고 있다.
또한 Agent Tesla의 진화는 배포 방식에도 반영되고 있다. 일부 최신 변종은 엔드포인트 보호 솔루션을 우회하기 위해 Microsoft의 멀웨어 방지 소프트웨어 인터페이스(AMSI)를 표적으로 삼는 기능을 포함하고 있다.
현재 유포되고 있는 Agent Tesla의 주요 버전들은 공통적으로 악성코드의 기능과 동작을 제어하는 전역 변수 집합을 사용한다. 공격자는 이러한 변수 값을 구성 파일 형태로 전달하며, 이를 통해 C&C 서버와의 통신 주기나 동작 지연 시간 등 세부적인 악성 행위를 조정한다.

그림 80. Agent Tesla의 내부코드 (1)

그림 80. Agent Tesla의 내부코드 (1)


Agent Tesla에서 사용되는 두 버전에 공통으로 적용되는 변수는 구성 파일에서 설정한 정수 값을 기반으로 C&C 통신에 사용할 네트워크 프로토콜을 결정한다.
또한, 다음 동작을 활성화 또는 비활성화할 수 있다.
• 지속성(운영 체제가 재부팅될 때 RAT가 다시 시작되도록 허용)
• 원격 제어 기능 활성화
• 감염된 호스트의 IP 주소 수집
• 설치 후 C&C에 성공 메시지 전송
• 스크릿샷을 통해 데이터 훔칠지 여부
• 키 입력을 기록할지 여부(그리고 Agent Tesla v3에서 Windows 시스템 클립보드의 내용을 훔칠지 여부)
• Agent Tesla v3에서 통신을 숨기기 위해 Tor 클라이언트를 배포할지 여부

Agent Tesla는 일반적으로 악성 스팸 이메일에 첨부 파일로 포함된다.
아래 예에서 Agent Tesla를 유포하는 악성코드는 .zip 압축 파일 첨부 파일로 위장되어 있으며, 공격자는 이 파일에 수신자가 검토할 수 있는 카탈로그가 포함되어 있다고 속인다.

그림 81. Agnet Tesla 피싱 이메일 예시

그림 81. Agnet Tesla 피싱 이메일 예시


최신 버전의 Agent Tesla는 샌드박스 및 정적 분석을 더욱 어렵게 만들고 엔드포인트 탐지를 회피하기 위해 여러 가지 방법을 사용한다.
이러한 다단계 악성코드 설치 프로그램은 코드를 난독화하기 위해 패커를 사용하는 것 외에도, 합법적인 웹사이트에 호스팅된 구성 요소를 그대로 노출한다. 또한 Agent Tesla 설치 프로그램은 Micrsoft AMSI의 코드를 덮어쓰려고 시도한다.

그림 82. Agent Tesla 감염 과정과 동작 흐름

그림 82. Agent Tesla 감염 과정과 동작 흐름


Agent Tesla의 두 버전이 활성화되면 가장 먼저 하는 일은 실행 중인 다른 Agnet Tesla 인스턴스를 확인하고 종료하는 것이다. 이는 봇이 지속성을 확립하도록 구성된 경우 원래 배포된 사본이 제거되도록 하는 단계이다. 그런 다음 동적으로 설정되는 추가 전역 변수와 설치에 사용할 폴더를 초기화한다.
악성코드는 또 다른 샌드박스 회피 기법을 실행하여 코드가 샌드박스에서 실행되고 있는지 확인하는 데 사용되는 타이머를 초기화한다.
이 타이머는 사용자 입력을 검색하고 비교하는 프로시저를 가지고 있으며, 사용자 입력이 감지되지 않으면 Agent Tesla가 종료된다.

본 실습은 Agent Tesla 샘플로 진행하였다.

그림 83. Process Explorer 창

그림 83. Process Explorer 창


우선 프로세스 트리를 확인한 결과, PID 204는 일반적인 시스템 프로세스가 생성하는 정상적인 이름 체계를 따르지 않고, 의미 없는 무작위 문자열 기반의 프로세스명으로 실행되었다.

메모리 덤프 분석 결과, PID 204 프로세스는 bdd442200b52~ 와 같이 의미 없는 문자열로 구성된 파일명을 가지고 있었으며, 이는 정상적인 Windows 시스템 환경에서는 거의 나타나지 않는 비정상적인 특징이다. 해당 프로세스는 Temp 디렉터리 내부에서 실행되었으며, 경로 역시 분석 대상 사용자에 의해 직접 실행되었다고 보기 어려운 위치해 존재했다.

또한, 동일한 이름의 프로세스가 반복적으로 등장하는 것도 확인되었는데, PID 204 외에도 6976, 7220, 9844 등 동일한 이름의 실행파일이 짧은 시간 간격으로 연속 생성/종료된 흔적이 존대한다. 이는 악성코드가 자기 재실행(Self-replication) 또는 프로세스 인젝션 후 재생성 방식으로 동작했을 가능성을 시사한다.

분석한 pstree 결과에서 PID 204 프로세스는 부모 프로세스 (PPID 6348, 7Zfm.exe) 아래에서 실행되었으며, 이는 공격자가 이메일 첨부 파일 압축 ㅍ k일 또는 스팸 캠페인을 통해 악성 파일을 전달했을 가능성을 보여준다. 7-zip 프로그램으로 압축을 해제한 뒤, 해당 악성 파일이 즉시 실행된 것으로 보이며, 이는 일반적인 Agent Tesla 유포 방식과 일치한다.

또한, 본 프로세스는 정상적인 Microsoft 서명 정보가 존재하지 않았고, Windows의 주요 시스템 경로(C:\Windows\System32, Program Files)가 아닌 임시 Temp 디렉터리에서 시작된 점 역시 명백히 악성 행위의 특징으로 확인된다.
PID 204 프로세스는 자체적으로 실행된 것이 아니라, cmd.exe 와 conhost.exe를 함꼐 호출하여 명령 실행을 수행한 정황이 확인된다. 이는 Agent Tesla가 자주 활용하는 명령줄 기반 실행 방식 또는 인젝션 수행 방식과 유사하며, 실행 시점 또한 사용자가 의도적으로 프로그램을 실행한 것과는 거리가 멀다.

특히 CID(데이터 시작 주소) 및 실행 시간 분석 결과, 해당 악성 프로세스는 부팅 직후 일정 시간이 지난 뒤 자동 실행된 패턴을 보였으며, 이는 일반적인 Agent Tesla가 스케줄러 또는 런 레지스트리를 악용하여 자동 실행되도록 구성하는 행위와 동일한 양상이다.

일반적인 Windows 시스템 프로세스들은 대부분 다음 경로에서 실행된다.
• C\Windows\System32
• C\Program Files

하지만 PID 204의 실행 파일은 다음과 같은 특징을 보인다.
• 정상적인 시스템 경로가 아님
• Microsoft Authenticode 서명이 없음
• 동일 이름을 가진 정상 서비스/모듈 존재하지 않음
• 난수 기반 이름으로 위장됨

이러한 요소는 악성코드가 분석을 회피하기 위한 위장 기법의 전형적인 형태이며, Agent Tesla 계열 샘플에서 흔히 관찰되는 특징이다.

• 난수 기반 파일명으로 위장
• Temp 디렉터리에서 실행됨
• 동일 파일명으로 여러 프로세스가 반복 생성
• 부모 프로세스가 압축 해제 프로그램(7zFM.exe) 실행
• cmd.exe → conhost.exe를 활용한 비정상 명령 실행 흐름
• Microsoft 서명 정보 없음
• 정상적인 Windows 모듈 및 경로와 불일치
• Agent Tesla의 전형적인 유포 및 실행 패턴과 일치

최종적으로 PID 204 프로세스는 Agent Tesla 악성코드가 압축 해재·자동 실행 → 메모리 상 반복 생성 → 명령 실행 기반 동작을 수행한 흔적이며, 사용자 시스템 상에서 실질적인 악성 행위를 수행한 핵심 프로세스로 판단된다.

키로깅 메커니즘 분석

입력 시 키스트로크를 추적하고 기록하는 멀웨어 또는 하드웨어의 한 형태로, 정보를 가져와서 명령 및 제어(C&C) 서버를 사용하여 공격자에게 보낸다. 그런 다음 공격자는 키 입력을 분석하여 사용자 이름과 암호를 찾고 이를 사용하여 보안 시스템을 해킹한다.
업로드한 파일은 실행 파일이 아닌 프로세스 메모리 덤프로, Windows 시스템 DLL 조각과 일반 문자열이 섞여 있어 시그니처 기반 엔진이 정상 PE 파일처럼 분석하기 어렵다.
또한, GetfouregroudWindow, SetWindowsHookEx 등은 정상 프로그램에서도 널리 사용되는 Windows API이기 때문에, 해당 문자열만으로는 악성 행위를 확정할 수 없다.

그림 84. PID 덤프

그림 84. PID 덤프


메모리 덤프에서 확인된 ctfmon.exe와 TextInputHost.exe는 모두 Windows에서 기본적으로 동작하는 키보드 입력 처리 관련 시스템 프로세스이다.
ctfmon.exe는 텍스트 서비스와 입력기(IME)를 관리하며, TextInputHost.exe는 Windows 10/11에서 터치 키보드 및 텍스트 입력 UI를 담당하는 정상 프로세스이다.
따라서 이 두 프로세스의 존재만으로는 악성 활동이나 키로깅을 직접적으로 의심하기 어렵다.

그림 85. 메모리 덤프에서 확인된 프로세스 목록

그림 85. 메모리 덤프에서 확인된 프로세스 목록


브라우저 정보 탈취

msedge.exe 및 msedgewebview2.exe 계열 프로세스들은 모두 Microsoft Edge 기반 브라우저 또는 WebView2 런타임과 관련된 정상적인 프로세스이다. 그러나 브라우저 프로세스는 사용자 로그인 정보, 자동 완성 데이터, 쿠키, 세션 토큰, 브라우저 기록 등 다양한 민감 정보가 포함되는 환경이기 때문에, 공격자들은 일반적으로 Agent Tesla와 같은 정보 탈취형 악성코드를 통해 이들 프로세스를 주요 공격 대상으로 삼는다.

특히 분석된 메모리 이미지에서는 여러 개의 msedge.exe 프로세스가 동일 시간대에 다수 동시 생성된 패턴이 관찰되었다. 이는 Edge가 멀티 프로세스 구조로 동작하기 때문이지만, 악성코드가 브라우저 내부 메모리에 접근하여 정보 탈취를 수행할 경우 흔히 나타나는 특징과도 일치한다. 또한 WebView2 기반 하위 프로세스들이 동일 부모 PID 하에서 생성된 점 역시 브라우저 동작을 악용한 정보수집 가능성을 시사한다.

PID 2932(msedgewebview2.exe)는 메인 WebView2 프로세스로서, 다수의 자식 프로세스 (PID 5776, 7224, 5940, 6960, 4964)를 동시에 생성했으며, 이는 브라우저 기반의 외부 렌더링·스크립트 처리·GPU 가속·유틸리티 처리가 같은 시점에 실행되었음을 의미한다.

이어서 같은 시간대 생성된 MicrosoftEdgeUpdate 프로세스들(PID 7684, 10688) 또한 Edge 업데이트 채널과 관련된 정상적 요소일 수 있으나, 악성코드가 업데이트 프로세스를 위장하거나 업데이트 동작을 가장하여 브라우저 세션 정보에 접근한 사례가 과거 Agent Tesla 캠페인에서도 보고된 바 있다.

시간 흐름을 기반으로 하 분석에서도, Agent Tesla로 추정되는 악성 프로세스는 브라우저 실행 직후 혹은 동일 세션 내에서 동적으로 동작한 것으로 판단된다. Msedge.exe의 실행 시점과 악성 프로세스의 동작 시점이 근접하게 발생하였으며, 이는 악성코드가 브라우저 프로세스를 대상으로 메모리 인젝션 또는 핸들 오픈을 통해 브라우저 내부 정보를 탈취 했을 가능성을 보여준다.

브라우저 기반 정보탈취 악성코드 행동 패턴은 보통 다음과 같은 순서를 따른다.
• 브라우저 프로세스 동작 감지
• WebView2 및 렌더러 프로세스에서 민감 데이터 저장 영역 식별
• 세션 쿠키·자동완성 정보·로그인 토큰을 메모리에서 직접 추출
• 특성 C&C 서버로 데이터 전송

해당 덤프에서는 msedge.exe 및 관련 WebView2 프로세스가 다수 실행 중이었으며, 악성 프로세스군(PID 204, 7220, 9844 등)이 이들과 유사한 시간대에 동작했다. 또한 Agent Tesla는 일반적으로 브라우저의 Credential Vault, 쿠키 파일, WebData DB, 로컬 세션 메모리에 접근해 크리덴셜을 탈취하는 기능을 탑재하고 있어, 이번 분석 환경 역시 동일한 공격 시나리오가 진행되었을 것으로 추정된다.

따라서 현재의 메모리 포렌식 분석 결과는 단순한 브라우저 실행이 아닌, 브라우저 기반 인증 정보·쿠키·세션 토큰을 목표로 한 정보 탈취 시도가 존재했을 가능성이 매우 높다는 것을 보여준다. 실행 시점 및 프로세스 간 연계성을 고려할 때, Agent Tesla로 판단되는 악성 프로세스가 브라우저 프로세스가 생성될 때를 트리거로 하여 동작했을 것으로 보이며, 이는 해당 악성코드의 대표적인 크리덴셜·브라우저 정보 수집 메커니즘과 정확히 일치한다.

그림 86. 브라우저 프로세스(msedge.exe) 관련 메모리 덤프 분석 결과

그림 86. 브라우저 프로세스(msedge.exe) 관련 메모리 덤프 분석 결과


안티 분석 기법 우회

Agent Tesla 악성코드는 분석 환경 탐지를 우회하고 보안 기능을 무력화하기 위해, 정상 프로세스에 악성 코드를 인젝션하는 방식으로 탐지를 어렵게 만든다.
(1) SkypeApp.exe 메모리 인젝션
SKypeApp.exe의 정상코드는 실행과 읽기 기능만을 허용하는 PAGE_EXECUTE_READ 권한으로 구성되는 것이 일반적이다. 그러나 이번 분석에서 SkypeApp.exe에는 실행·읽기·쓰기 권한이 모두 부여된 RWX메모리 영역이 존재했다.
또한, 해당 메모리 영역의 Hex 데이터를 확인한 결과, 함수 시작부에서 흔히 나타나는 프롤로그 패턴 등 명령어 시그니처가 발견되었다. 이는 SkypeApp.exe 내부에 실제로 실행 가능한 코드가 삽입되었음을 시사한다. Skype는 네트워크 통신이 빈번한 애플리케이션이기 때문에, 공격자가 이를 악용해 C&C 통신 등을 자연스럽게 위장하려는 목적이 있는 것으로 판단된다.

그림 87. SkypeApp.exe 메모리 영역에서 발견된 RWX 권한 및 의심 코드 패턴

그림 87. SkypeApp.exe 메모리 영역에서 발견된 RWX 권한 및 의심 코드 패턴


(2) MsMpEng.exe(Windows Defender) 메모리 변조
MsMpEng.exe는 Windows Defender의 핵심 프로세스로, 일반적으로 정해진 코드 영역 외에는 RWX 권한을 사용하는 경우가 거의 없다. 그러나 분석 결과 Defender 프로세스에서 대규모 RWX 메모리 영역이 여러 구역에서 발견되었으며, 동일한 패턴의 코드가 반복적으로 존재하는 것이 확인되었다. 이는 Defender 프로세스가 공격에 의해 변조되었을 가능성을 보여준다.
또한, 시간 정보를 비교해 보면 Agent Tesla가 실행된 직후 Defender 관련 프로세스 (MsMpEng.exe, MpDefenderCoreService, NisSrv.exe)가 연달아 재시작된 것이 관찰되었다. 이는 악성코드가 Defender를 강제로 종료시키고, 재시작 시점에 인젝션을 수행했을 가능성을 보여준다.

그림 88. MsMpEng.exe메모리 영역에서 발견된 RWX 권한 및 반복적 코드 패턴

그림 88. MsMpEng.exe메모리 영역에서 발견된 RWX 권한 및 반복적 코드 패턴


(3) RuntimeBroker.exe 메모리 이상 징후
RuntimeBroker.exe는 정상적으로 운영될 경우 일정한 코드 섹션을 유지해야 하지만, 분석된 메모리 구간에는 NULL 값만 존재하는 비정상적인 영역이 확인되었다. 이는 코드가 아직 로딩되기 전의 임시 할당 구간이거나, 악성코드가 안티 포렌식을 진행한 후의 상태일 가능성이 있다. 혹은 이후 악성 페이로드를 적재하기 위해 미리 확보해 둔 공간일 수도 있다.

그림 89. RuntimeBroker.exe 메모리에 존재하는 NULL값

그림 89. MRuntimeBroker.exe 메모리에 존재하는 NULL값


(4) smartscreen.exe 메모리 변조
Smartscreen.exe는 Windows SmartScreen 기능을 담당하며, 파일 평한 검사 및 악성 URL 차단 기능을 수행한다. 그러나 해당 프로세스에서도 Skype와 유사하게 RWX 권한 메모리와 인젝션 패턴이 확인되었다. 이는 SmartScreen 기능이 악성코드 탐지를 수행하지 못하도록 우회하기 위한 의도로 보인다.
프로세스 실행 시점을 보면 Agent Tesla가 실행된지 약 10분 후 SmartScreen이 새로 시작되었으며, 시작 직후 악성코드 인젝션이 발생한 것으로 분석된다. SmartScreen은 다운로드 파일을 검증하는 역할을 하기 때문에, 공격자는 이를 무력화하여 악성 파일이 차단되는 것을 방지하려 한 것으로 보인다.

그림 90. RuntimeBroker.exe 메모리 영역에서 확인된 비정상 코드 패턴

그림 90. RuntimeBroker.exe 메모리 영역에서 확인된 비정상 코드 패턴


(5) taskhosw.exe 인젝션 및 지속성 확보
Taskhostw.exe는 Windows 예약 작업을 담당하는 프로세스 중 하나로, 분석 결과 여기에도 RWX 권한의 메모리 영역이 확인되었다. 특히 해당 프로세스는 악성코드 실행 후 약 9분 뒤 비정상적인 명령을 실행하며 동작을 시작했다. 로그에서는 다음과 같은 명령이 확인되었다.
Taskhostw.exe -RegisterDevice -Periodic
이렇게 설정된 예약 작업은 시스템 재부팅 후에도 자동 실행되며, 공격자가 장기간 시스템을 통제할 수 있는 기반이 된다.

그림 91. smartscreen.exe 메모리 영역에서 발견된 RWX 권한 및 인젝션 패턴

그림 91. smartscreen.exe 메모리 영역에서 발견된 RWX 권한 및 인젝션 패턴


메모리에서 설정 및 데이터 추출

다수의 시스템 기본 포트(135, 139, 445)가 활성화되어 있었으며, 이는 RPC 호출, NetBIOS 파일 공유, SMB 서비스 등 Windows 시스템의 기본적인 기능 제공을 위한 정상 동작으로 판단된다.
그러나 악성코드 Agent Tesla가 실행된 직후 특정 프로세스(SkypeApp.exe, msedgewebview2.exe)에서 비정상적인 UDP 통신이 집중적으로 발생한 것이 확인되었다.

그림 92. SkypeApp.exe(PID 5708)의 UDP 통신 내역

그림 92. SkypeApp.exe(PID 5708)의 UDP 통신 내역


(1) SkypeApp.exe의 UDP 통신
메모리 분석 결과, SkypeApp.exe(PID 5708)에서 UDP 포트 50029로의 통신이 확인되었다.
해당 포트는 일반적으로 Skype 통신에 사용되지만, Agent Tesla의 실행 시점과 UDP 연결 생성 시점이 거의 동일하게 나타났다.
이는 Agent Tesla가 SkypeApp 프로세스에 인젝션 된 후, 네트워크 동작을 위한 초기 통신을 수행한 것으로 볼 수 있다.
• 실행 2분 내 네트워크 연결 생성
• 단일 비정상 UDP 포트 사용
• Skype 동작과 무관한 짧은 시간 내 연결 시도

그림 93. msedgewebview2.exe(PID 5940)의 비정상 UDP 포트 생성

그림 93. msedgewebview2.exe(PID 5940)의 비정상 UDP 포트 생성


(2) msedgewebview2.exe(PID 5940)의 비정상 UDP 활동
추가로 msedgewebview2.exe 프로세스에서 총 4개의 UDP 포트(50431, 58408, 64207, 65108)가 거의 동시에 생성되었다.
위 포트들은 모두 49152~65535 범위의 동적 할당 포트이며, 일반적인 웹 렌더링 작업보다 짧은 시간 안에 다량의 포트가 생성된 것은 비정상적인 행동이다.
이는 Agent Tesla가 msedgewebview2 프로세스에 침투하여 자신의 PID로 직접 네트워크를 연결하는 대신, 정상 프로세스를 우회적으로 활용해 데이터를 전송하려 한 것이다.

(3) Agent Tesla 본인의 PID에서는 네트워크 연결 없음
다만, 메모리 내 netstat 출력 결과, Agent Tesla의 실제 PID 204에서는 직접적인 네트워크 연결 흔적이 전혀 발견되지 않았다.
이는 인젝션을 통해 정상 프로세스로 트래픽을 우회했거나, 일정 시간 대기 후 명령 수신을 위한 연결을 준비하고 있었을 가능성을 보여준다.

그림 94. msedgewebview2.exe(PID 5940)에서 확인된 연속 UDP 연결 생성 기록

그림 94. msedgewebview2.exe(PID 5940)에서 확인된 연속 UDP 연결 생성 기록


탐지 및 대응 방안

(1) 탐지 방법
Agent Tesla는 엔드포인트 보안 소프트웨어(EPP/EDR, V3, 알약 등)에서 ‘Spyware.AgentTesla’ 또는 ‘Trojan.Agent.Woreflint’ 등으로 탐지된다. EDR 솔루션은 계정 정보 파일 접근, 자동 실행 등록, C2 서버 연결 등 의심 행위를 기반으로 탐지할 수 있으며, 악성 파일이 패킹·난독화된 경우에는 샌드박스 환경에서 동적 분석이 필요하다. 또한 Sigma 규칙, 사용자 정의 행위 정책, 네트워크 모니터링 등을 통해 C2 통신과 정보 탈취 패턴을 탐지할 수 있다. YARA 룰을 작성하여 탐지하는 방법도 활용 가능하다.

(2) 대응 방안
Agent Tesla 감염 시, 우선 모든 계정 비밀번호와 인증 정보를 즉시 변경하고 복구해야 한다. 감염 시스템에서는 자동 실행 항목을 해제하고 악성 파일을 삭제하며, 행위 기반 규칙을 적용해 추가 확산을 방지해야 한다. EDR을 사용하는 경우에는 사용자 정의 규칙을 통해 감염 PC의 C2 연결을 자동 차단하도록 설정할 수 있다.
예방 조치로는 운영체제와 애플리케이션 보안 패치를 최신 상태로 유지하고, 이메일 첨부 파일과 다운로드 링크를 주의하며, 보안 제품을 항상 업데이트하는 것이 중요하다. 또한 제로 트러스트 접근 관리, 비정상적 사용자 행동 모니터링, 기업 내 보안 교육을 통해 추가 피해를 예방할 수 있다. 감염이 의심될 경우 네트워크 격리, 악성 파일 및 자동 실행 항목 삭제, 계정 정보 점검과 변경이 필요하다.
Agent Tesla는 지속적으로 변화하고 우회 방식을 발전시키므로, 행위 기반 모니터링과 보안 정책의 지속적 강화가 필수적이다.

YARA 룰 작성

Agent Tesla는 지속적으로 변화하며 우회 기법을 진화시키고 있기 때문에, 이에 대응하기 위한 행위 기반 모니터링과 보안 정책의 지속적인 강화가 필수적이다.

YARA 룰 기초 – 툴 이해 및 기초 문법

  • YARA 룰 기본 구조
    YARA는 악성코드를 찾아내기 위해 사용되는 패턴 매칭 기반 도구로, 파일 내에서 특정 문자열이나 바이너리 패턴을 찾아내는 데 사용된다.

  • Meta 섹션 (선택)
    YARA 룰은 크게 세 가지 섹션으로 구성된다. 첫 번째는 메타 섹션으로 룰의 작성자, 작성일, 설명 등의 정보를 담고, 두 번째는 문자열 섹션으로 탐지하고자 하는 패턴을 정의한다. 그리고 마지막으로 세 번째는 조건 섹션으로 어떤 조건을 만족했을 때 탐지할지를 설정한다.

    1
    2
    3
    4
    5
    rule 룰이름 {
    meta: // 메타데이터 (설명)
    strings: // 찾을 문자열/패턴
    condition: // 탐지 조건
    }
  • 기본 문법
    YARA 룰 작성 시 몇 가지 기본 문법을 알아야 한다. 먼저, 변수 이름 앞에는 반드시 $ 기호를 붙여야 하며, 대소문자는 구분하지 않는다.

    1
    2
    strings:
    $변수명 = "찾을문자열" 옵션

Unit16(위치) 함수는 파일의 특정 위치에서 2바이트를 읽어 오는 함수이다. 예를 들어, 다음과 같은 예시처럼 unit(0) == 0x5A4D는 파일의 시작 부분이 ‘MZ’로 시작하는지 확인하여 Windows 실행 파일인지 판별한다.

1
uint16(0) == 0x5A4D

Filesize는 파일의 크기를 나타내며, 너무 크거나 작은 파일을 필터링 하기 위해 사용한다.

1
filesize < 2MB

any of 키워드는 특정 패턴으로 시작하는 변수 중 하나라도 발견될 경우, 조건을 만족하는 것으로 간주한다.
논리 연산자에는 AND, OR, NOT이 있다. AND는 두 조건이 모두 참일 때, OR은 하나라도 참일 때, NOT은 조건이 거짓일 때 사용된다.

1
any of ($n*)

이러한 기본문법을 바탕으로 실제 악성 코드를 탐지하는 룰을 작성하면 된다.

분석 자동화 및 YARA 연동

Agent Tesla 탐지 룰 작성

먼저 첫 번째 룰인 rule agent_tesla_1은 가장 기본적인 Agent Tesla의 특성을 탐지하기 위해 작성된 룰이다.
이 룰은 GetAsyncKeyState, Clipboard, SmtpClient, mscoree.dll 네 가지 문자열을 탐지 대상으로 한다.
GetAsyncKeyState는 키보드 입력을 가로채는 API 함수이며, Clipboard는 복사한 내용을 훔치는 기능이고, SmtpClient는 정보를 이메일로 전송하는 .NET 클래스, mscoree.dll은 .NET 런타임 라이브러리를 의미한다. 탐지 조건은 대상 파일이 PE 실행 파일이면서 크기가 5MB 이하이고, 위 네 가지 문자열 중 세 개 이상이 포함되어 있다면 악성코드로 판단하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
rule agent_tesla_1 {
meta:
author = "Yebbi"
date = "2025-11-15"
description = "Agent Tesla 기본 탐지"

strings:
$a = "GetAsyncKeyState"
$b = "Clipboard"
$c = "SmtpClient"
$d = "mscoree.dll"

condition:
uint16(0) == 0x5A4D and
filesize < 5MB and
3 of them
}

두 번째 룰인 rule agent_tesla_2는 키로깅 기능에 집중한 탐지 룰이다.
키로깅 관련 API 함수로 GetAsyncKeyState, GetKeyboardState, GetKeyState 세 가지를 정의하고,
이메일 전송 관련 함수로 SmtpClient, MailMessage를 정의한다.
두 번째 룰의 탐지 조건은 키로깅 함수와 이메일 전송 함수가 둘 다 하나라도 존재할 때 악성코드로 판단하게 된다. 키보드 입력을 가로채고, 이를 외부에 전송하는 기능이 함께 기능하고 있다는 것은 전형적인 정보를 탈취하기 위한 악성 행위 패턴이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
rule agent_tesla_2 {
meta:
author = "Yebbi"
date = "2025-11-15"
description = "키로거 탐지"

strings:
$k1 = "GetAsyncKeyState"
$k2 = "GetKeyboardState"
$k3 = "GetKeyState"
$s1 = "SmtpClient"
$s2 = "MailMessage"

condition:
uint16(0) == 0x5A4D and
filesize < 5MB and
any of ($k*) and
any of ($s*)
}

세 번째 룰인 rule agent_tesla_3은 정보 탈취 기능을 탐지하는 룰이다.
키로깅에 사용되는 GetAsyncKeyState, 클립보드 탈취를 위한 get_Clipboard, 이메일 전송을 위한 SmptClient,
화면 캡처를 위한 CopyFromScreen, 저장된 비밀번호 수집을 위한 get_Password, FTP 파일 전송을 위한 FtpWebRequest, 컴퓨터 이름 수집을 위한 get_MachineName, 사용자 이름 수집을 위한 get_UserName 이렇게 총 여덟 가지 기능을 탐지 대상으로 정한다.
이 룰의 탐지 조건은 대상 파일이 PE 실행 파일이면서, 동시에 이 중 네 개 이상의 기능이 파일에 포함되어 있으면 Agent Tesla로 탐지하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
rule agent_tesla_3 {
meta:
author = "Yebbi"
date = "2025-11-15"
description = "정보 탈취 탐지"

strings:
$a = "GetAsyncKeyState"
$b = "get_Clipboard"
$c = "SmtpClient"
$d = "CopyFromScreen"
$e = "get_Password"
$f = "FtpWebRequest"
$g = "get_MachineName"
$h = "get_UserName"

condition:
uint16(0) == 0x5A4D and
4 of them
}

이러한 세 가지 룰은 각각 독립적으로 작동하며, 하나의 룰만 조건을 만족해도 해당 파일은 Agent Tesla 악성코드로 탐지한다. 첫 번째 룰은 기본적인 기능을 탐지하고, 두 번째 룰은 Agent Tesla의 대표적인 특성 중 하나인 키로깅을 집중적으로 탐지하고, 세 번째 룰은 인포스틸러 악성코드의 주 목적인 정보 탈취 행위를 탐지하는 역할을 수행한다.

분석 자동화 및 YARA 연동

자동화 스크립트 작성

이 코드는 Agent Tesla 인포스틸러 탐지에 특화된 Python 기반 YARA 자동화 스캐너로, 앞서 작성한 YARA 룰을 기반으로 동작한다.
본 스캐너는 다음과 같은 기능을 제공한다.
• 지정된 경로의 파일들을 YARA 룰로 검사한다.
• RAW, DMP 등 메모리 이미지 분석이 가능하다.
• 위협 탐지 시 이메일로 자동 발송된다.
• SHA 256 해시 기반으로 중복 탐지를 방지한다.

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#!/usr/bin/env python3
import yara
import os
import sys
import smtplib
import hashlib
import json
from datetime import datetime
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

SCAN_PATHS = [r"C:\Users\<사용자명>\Desktop\Agent Tesla.raw"]
YARA_FILE = r"C:\Users\<사용자명>\Desktop\AgentTesla.yar"
LOG_FILE = r"C:\yara_logs\scanner.log"
DB_FILE = r"C:\yara_logs\detections.json"
EXTENSIONS = [".exe", ".dll", ".raw", ".bin", ".dmp", ".img"]

SMTP_HOST = "smtp.naver.com"
SMTP_PORT = 465
EMAIL_USER = "***@naver.com"
EMAIL_PASS = "password"
EMAIL_TO = ["***@naver.com"]

YARA_RULES = """
rule agent_tesla_1
{
strings:
$a = "GetAsyncKeyState"
$b = "Clipboard"
$c = "SmtpClient"
$d = "mscoree.dll"
condition:
uint16(0) == 0x5A4D and
filesize < 5MB and
3 of them
}

rule agent_tesla_2
{
strings:
$k1 = "GetAsyncKeyState"
$k2 = "GetKeyboardState"
$k3 = "GetKeyState"
$s1 = "SmtpClient"
$s2 = "MailMessage"
condition:
uint16(0) == 0x5A4D and
filesize < 5MB and
any of ($k*) and any of ($s*)
}

rule agent_tesla_3
{
strings:
$a = "GetAsyncKeyState"
$b = "get_Clipboard"
$c = "SmtpClient"
$d = "CopyFromScreen"
$e = "get_Password"
$f = "FtpWebRequest"
$g = "get_MachineName"
$h = "get_UserName"
condition:
uint16(0) == 0x5A4D and
4 of them
}
"""

def log(msg, level="INFO"):
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
line = f"[{ts}] [{level}] {msg}"
print(line)

try:
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write(line + "\n")
except:
pass

def load_db():
if os.path.exists(DB_FILE):
try:
with open(DB_FILE, "r") as f:
return json.load(f)
except:
return {}
return {}

def save_db(db):
try:
os.makedirs(os.path.dirname(DB_FILE), exist_ok=True)
with open(DB_FILE, "w") as f:
json.dump(db, f, indent=2)
except Exception as e:
log(f"DB 저장 실패: {e}", "WARN")

def get_hash(path):
try:
h = hashlib.sha256()
with open(path, "rb") as f:
while True:
data = f.read(8192)
if not data:
break
h.update(data)
return h.hexdigest()
except:
return None

def should_scan(path):
if not os.path.isfile(path):
return False
ext = os.path.splitext(path)[1].lower()
return ext in EXTENSIONS

def scan_file(path, rules, db):
try:
matches = rules.match(path)

if not matches:
return None

file_hash = get_hash(path)

if file_hash and file_hash in db:
return None

result = {
"path": path,
"rules": [m.rule for m in matches],
"hash": file_hash,
"size": os.path.getsize(path),
"time": datetime.now().isoformat()
}

if file_hash:
db[file_hash] = result
save_db(db)

log(f"탐지: {os.path.basename(path)} -> {', '.join(result['rules'])}", "ALERT")
return result

except Exception as e:
log(f"스캔 오류 ({path}): {e}", "ERROR")
return None

def send_email(threats):
if not threats:
return

try:
msg = MIMEText("")
msg["Subject"] = f"YARA 위협 탐지 ({len(threats)}건)"
msg["From"] = EMAIL_USER
msg["To"] = ", ".join(EMAIL_TO)

# 텍스트 본문
body = f"YARA 위협 탐지 알림\n"
body += f"탐지 시각: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
body += "="*60 + "\n\n"

for i, t in enumerate(threats, 1):
body += f"[위협 {i}]\n"
body += f"파일: {t['path']}\n"
body += f"룰: {', '.join(t['rules'])}\n"
body += f"해시: {t['hash']}\n"
body += f"크기: {t['size']} bytes\n"
body += "\n"

msg.set_payload(body.encode('utf-8'))
msg.set_charset('utf-8')

with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) as s:
s.login(EMAIL_USER, EMAIL_PASS)
s.send_message(msg)

log("이메일 전송 완료")
except Exception as e:
log(f"이메일 전송 실패: {e}", "ERROR")

def main():
log("YARA 스캐너 시작")

try:
rules = yara.compile(source=YARA_RULES)
log("YARA 룰 로드 완료")
except Exception as e:
log(f"룰 로드 실패: {e}", "ERROR")
return 1

db = load_db()
threats = []
scan_cnt = 0

for scan_path in SCAN_PATHS:
log(f"스캔 경로: {scan_path}")

if os.path.isfile(scan_path):
if should_scan(scan_path):
scan_cnt += 1
result = scan_file(scan_path, rules, db)
if result:
threats.append(result)
elif os.path.isdir(scan_path):
for fname in os.listdir(scan_path):
fpath = os.path.join(scan_path, fname)
if should_scan(fpath):
scan_cnt += 1
result = scan_file(fpath, rules, db)
if result:
threats.append(result)
else:
log(f"경로 없음: {scan_path}", "WARN")

log(f"스캔 완료: {scan_cnt}개 파일, {len(threats)}개 위협")

if threats:
send_email(threats)

return 0

if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
log("사용자 중단", "WARN")
sys.exit(130)
except Exception as e:
log(f"예기치 않은 오류: {e}", "ERROR")
sys.exit(1)

전체 코드에서 사용하는 경로, 확장자, 이메일 정보 등을 한 곳에 모아서 관리하도록 했다. 경로는 단일 파일 또는 디렉터리 단위로 유연하게 지정할 수 있으며, 확장자를 기반으로 PE 실행 파일과 메모리 덤프만 선별적으로 스캔한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#설정
SCAN_PATHS = [r"C:\Users\been_\Desktop\Agent Tesla.raw"]
YARA_FILE = r"C:\Users\been_\Desktop\AgentTesla.yar"
LOG_FILE = r"C:\yara_logs\scanner.log"
DB_FILE = r"C:\yara_logs\detections.json"
EXTENSIONS = [".exe", ".dll", ".raw", ".bin", ".dmp", ".img"]

#이메일 설정
SMTP_HOST = "smtp.naver.com"
SMTP_PORT = 465
EMAIL_USER = "***@naver.com"
EMAIL_PASS = "password"
EMAIL_TO = ["***@naver.com"]

Agent Tesla는 키 입력 탈취, 클립보드 모니터링, 스크린샷 캡처 등 다양한 정보 탈취 기능을 수행하는 대표적인 인포스틸러 악성코드다. 본 스캐너에서는 이러한 주요 악성 행위 패턴을 기반으로 작성한 YARA 룰을 활용해 Agent Tesla의 흔적을 탐지한다.
룰을 외부 파일로 관리하는 방식 대신 소스코드를 내부에 직접 포함해, 별도 파일 없이도 사용이 가능하며 룰 수정이 필요할 경우 코드 내에서 바로 변경할 수 있도록 했다.

1
2
3
4
5
YARA_RULES = """
rule agent_tesla_1 { ... }
rule agent_tesla_2 { ... }
rule agent_tesla_3 { ... }
"""

탐지 시각과 매칭된 룰, 파일 메타데이터를 함께 기록하여 JSON 파일에 저장하고, 파일 해시를 키로 관리함으로써 중복 탐지를 방지한다.

1
2
3
4
5
6
7
8
def load_db():
if os.path.exists(DB_FILE):
try:
with open(DB_FILE, "r") as f:
return json.load(f)
except:
return {}
return {}

청크 단위로 파일을 읽어 대용량 파일도 안정적으로 처리하며, 생성한 SHA-256 해시를 사용해 동일 파일의 중복 스캔을 방지한다.

1
2
3
4
5
6
7
8
9
10
11
12
def get_hash(path):
try:
h = hashlib.sha256()
with open(path, "rb") as f:
while True:
data = f.read(8192)
if not data:
break
h.update(data)
return h.hexdigest()
except:
return None

지금까지 실행 흐름을 정리해보면 다음과 같다.
1. 파일에 대해 YARA 룰을 적용한다.
2. 매칭 결과가 존재할 경우 해당 파일을 위협으로 분류한다.
3. 파일의 고유 식별을 위해 SHA-256 해시 값을 계산한다.
4. 생성된 해시가 기존 탐지 DB에 존재하는지 확인하여 중복 여부를 판단한다.
5. 탐지된 파일의 경로, 룰, 해시, 크기 등의 메타데이터를 JSON 파일에 저장한다.

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
def scan_file(path, rules, db):
try:
matches = rules.match(path)

if not matches:
return None

file_hash = get_hash(path)

if file_hash and file_hash in db:
return None

result = {
"path": path,
"rules": [m.rule for m in matches],
"hash": file_hash,
"size": os.path.getsize(path),
"time": datetime.now().isoformat()
}

if file_hash:
db[file_hash] = result
save_db(db)

log(f"탐지: {os.path.basename(path)} -> {', '.join(result['rules'])}", "ALERT")
return result

except Exception as e:
log(f"스캔 오류 ({path}): {e}", "ERROR")
return None

단일 파일과 디렉터리 모두 지원하며, 확장자 필터링을 통해 대상이 아닌 파일은 스캔에서 제외시킬 수 있다.
main 함수의 실행 흐름은 다음과 같다.
1. YARA 룰을 컴파일한다.
2. 기존 탐지 DB(JSON)를 불러온다.
3. 설정된 모든 경로를 순회하며 스캔 대상 파일을 선별한다.
4. 각 파일에 대해 YARA 매칭 여부를 확인하고 탐지 결과를 수집한다.
5. 전체 스캔이 끝나면 통계를 로그로 기록한다.
6. 탐지된 위협이 있을 경우 이메일로 알림을 전송한다.

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
def main():
log("YARA 스캐너 시작")

try:
rules = yara.compile(source=YARA_RULES)
log("YARA 룰 로드 완료")
except Exception as e:
log(f"룰 로드 실패: {e}", "ERROR")
return 1

db = load_db()
threats = []
scan_cnt = 0

for scan_path in SCAN_PATHS:
log(f"스캔 경로: {scan_path}")

if os.path.isfile(scan_path):
if should_scan(scan_path):
scan_cnt += 1
result = scan_file(scan_path, rules, db)
if result:
threats.append(result)
elif os.path.isdir(scan_path):
for fname in os.listdir(scan_path):
fpath = os.path.join(scan_path, fname)
if should_scan(fpath):
scan_cnt += 1
result = scan_file(fpath, rules, db)
if result:
threats.append(result)
else:
log(f"경로 없음: {scan_path}", "WARN")

log(f"스캔 완료: {scan_cnt}개 파일, {len(threats)}개 위협")

if threats:
send_email(threats)

return 0

탐지 결과를 텍스트 형식의 이메일로 전송한다. 매칭된 YARA 룰과 파일 해시를 함께 제공해 즉각적인 대응이 가능하다.

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
def send_email(threats):
if not threats:
return

try:
msg = MIMEText("")
msg["Subject"] = f"YARA 위협 탐지 ({len(threats)}건)"
msg["From"] = EMAIL_USER
msg["To"] = ", ".join(EMAIL_TO)

body = f"YARA 위협 탐지 알림\n"
body += f"탐지 시각: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
body += "="*60 + "\n\n"

for i, t in enumerate(threats, 1):
body += f"[위협 {i}]\n"
body += f"파일: {t['path']}\n"
body += f"룰: {', '.join(t['rules'])}\n"
body += f"해시: {t['hash']}\n"
body += f"크기: {t['size']} bytes\n"
body += "\n"

msg.set_payload(body.encode('utf-8'))
msg.set_charset('utf-8')

with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) as s:
s.login(EMAIL_USER, EMAIL_PASS)
s.send_message(msg)

log("이메일 전송 완료")
except Exception as e:
log(f"이메일 전송 실패: {e}", "ERROR")

실제로 이메일을 수신하면 다음과 같이 탐지된 파일 경로, 매칭된 YARA 룰, SHA-256 해시 등이 구조화된 형태로 표시된다.

그림 95. 이메일로 전달된 YARA 탐지 알림 화면

그림 95. 이메일로 전달된 YARA 탐지 알림 화면


콘솔과 로그 파일에 동시에 기록하며, 타임스탬프와 로그 레벨을 포함해 구조화된 형태로 저장한다. 로그 파일 쓰기에 실패하더라도 프로그램 실행에는 영향을 주지 않도록 예외 처리를 했다.

1
2
3
4
5
6
7
8
9
10
11
def log(msg, level="INFO"):
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
line = f"[{ts}] [{level}] {msg}"
print(line)

try:
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write(line + "\n")
except:
pass

프로그램 종료 시에는 정상 종료뿐 아니라 Ctrl+C 중단, 예기치 않은 오류가 발생했을 때를 모두 처리해 프로그램이 비정상적으로 종료되는 것을 방지한다.

1
2
3
4
5
6
7
8
9
if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
log("사용자 중단", "WARN")
sys.exit(130)
except Exception as e:
log(f"예기치 않은 오류: {e}", "ERROR")
sys.exit(1)

본 YARA 스캐너는 Agent Tesla 탐지를 위해 제작된 자동화 도구로, 메모리 덤프 분석까지 지원해 포렌식 작업이나 보안 모니터링 환경에서도 유용하게 활용할 수 있다.

Volatility 플러그인 개발 및 자동화

Volatility 플러그인 구조 이해

Volatility는 메모리 이미지를 분석하기 위한 Python 기반 프레임워크이며, 플러그인 구조가 매우 모듈화되어 있어 새로운 기능을 쉽게 확장할 수 있다. Volatility 플러그인은 Python 클래스 형태로 작성되며, Volatility 프레임워크가 제공하는 다양한 인터페이스를 활용하여 메모리 분석 작업을 수행한다.
플러그인에 대해 알아보기 전에 Volatility 3의 기본 구조를 먼저 알고 넘어가는 게 이해에 도움이 된다.

Volatility 3의 구성 요소는 크게 아래 세 가지로 나뉜다.

(1) Framework (volatility3/framework/)
플러그인이 공통적으로 사용하는 기반 기능들은 아래와 같다.
• 메모리 접근 인터페이스 (Layer)
• 심볼 테이블 (Symbols)
• 파일 시스템 abstraction (Resources)
• 예외 처리/로깅
플러그인은 이 framework를 기반으로 한다.

(2) Plugins (volatility3/plugins/)
플러그인의 실제 분석 기능이 구현되는 부분이다.
각 플러그인은 클래스로 정의되고, interfaces.plugins.PluginInterface를 상속한다.
또한 아래와 같이 OS별로 플러그인이 나뉘어 있다.
• windows/
• linux/
• mac/

(3) CLI (vol.py)
사용자가 플러그인을 호출하는 인터페이스 부분이다.
플러그인 개발자는 CLI를 건드릴 필요가 없다. vol.py는 자동으로 플러그인을 로드하고 인자를 파싱해 실행한다.

  • 플러그인의 위치
    개발한 플러그인 파일은 Volatility가 인식할 수 있는 경로에 두어야 한다. 보통 Volatility 3의 기본 플러그인 경로(volatility3/framework/plugins/) 하위 디렉터리(예: windows, linux)에 파일을 넣거나, 실행 시 -plugins 옵션으로 경로를 지정해 준다.

  • 실행 조건
    Volatility는 파일 경로를 기반으로 플러그인 이름을 인식한다. 예를 들어, windows 디렉터리에 있는 yara_detector.py 파일 내의 DetectorClass는 windows.yara_detector와 같이 호출된다.
    다음으로 플러그인의 핵심 구성 요소에 대해 살펴볼 필요가 있다.
    모든 Volatility 3 플러그인은 다음의 주요 항목들을 필수로 포함하고 정의해야 한다.

(1) 클래스 상속 및 정의
플러그인을 만들기 위한 가장 첫 단계이다. 개발하려는 플러그인 클래스는 반드시 Volatility의 interfaces.plugins.PluginInterface를 상속받는다. 이는 “나는 Volatility 플러그인입니다”라고 프레임워크에 선언하는 것과 같다. 그리고 추가적으로_version 변수를 정의하여 플러그인의 버전을 명시해줘야 한다.

1
2
3
4
class DetectAnomalousProcessTree(interfaces.plugins.PluginInterface):
"""Detect anomalous parent-child process relationships and dump suspicious processes"""
_version = (1, 1, 0) # (Major, Minor, Patch)

(2) 요구사항 정의 (get_requirements)
플러그인이 작동하기 위해 필요한 모든 것을 프레임워크에 요청하는 부분이다. Volatility는 이 정보를 바탕으로 덤프 파일을 해석하고 사용자에게 옵션을 받는다.
• ModuleRequirement: 메모리 덤프를 분석하기 위한 커널 심볼이나 프로파일 정보를 요청한다. 예를 들어, Windows 덤프를 분석하려면 kernel 모듈을 요청해야 한다. 이 요청이 없으면 메모리 구조체에 접근할 수 없다.
• String/Int/Bool Requirement: 사용자로부터 입력값을 받기 위한 옵션을 정의한다. 예를 들어 YARA 룰 경로를 받아야 한다면 StringRequirement를 사용한다.

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
@classmethod
def get_requirements(cls):
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"]
),
requirements.BooleanRequirement(
name="show_all",
description="Show all processes (not just anomalous ones)",
optional=True,
default=False
),
requirements.BooleanRequirement(
name="dump",
description="Dump memory of anomalous processes",
optional=True,
default=False
),
requirements.StringRequirement(
name="dump_dir",
description="Directory to dump process memory",
optional=True,
default="./dumps"
)
]

(3) 실행 로직 (run 메서드)
플러그인이 실제로 분석 작업을 수행하는 곳이다.
self.context를 통해 메모리 계층(레이어)에 접근하고, self.config를 통해 사용자가 입력한 옵션에 접근한다. run 메서드 내에서 다른 Volatility 플러그인을 호출하거나, 메모리 구조체를 순회하며 원하는 분석을 수행한다. run 메서드는 최종적으로 분석 결과를 담은 객체를 반환해야 한다. Volatility의 표준 출력 형식은 renderers.TreeGrid 이다. 분석 결과를 이 객체에 맞게 정리해야 한다.

1
2
3
4
5
6
7
def run(self):
"""Run the plugin"""
return renderers.TreeGrid(
[컬럼 정의],
self._generator() # 분석 결과 생성
)

최종적으로 올바른 플러그인 템플릿 구조를 전체적으로 보면 다음과 같다.

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
# Volatility 3 Custom Plugin Template
from volatility3.framework import interfaces, renderers, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.plugins.windows import pslist

class MyCustomPlugin(interfaces.plugins.PluginInterface):
"""Plugin description in English"""

_version = (1, 0, 0)

@classmethod
def get_requirements(cls):
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"]
),
requirements.BooleanRequirement(
name="my_option",
description="Optional flag",
optional=True,
default=False
)
]

def _generator(self):
"""Generate results"""
# Get processes - correct way
for proc in pslist.PsList.list_processes(
self.context,
self.config["kernel"]
):
try:
# Extract data
pid = proc.UniqueProcessId
ppid = proc.InheritedFromUniqueProcessId
name = utility.array_to_string(proc.ImageFileName)

# Yield results (level, data_tuple)
yield (0, (pid, ppid, name))
except exceptions.InvalidAddressException:
continue

def run(self):
# Column names must be in English
return renderers.TreeGrid([
("PID", int),
("PPID", int),
("Process Name", str),
], self._generator())

플러그인 개발

악성코드 탐지용 플러그인 제작

우리가 앞서 분석한 모든 악성코드(Formbook, Emotet, AgentTesla)에서 감지된 핵심 포인트들을 집어내는 플러그인을 제작해보려고 한다. 각 악성코드 분석 실습 과정에서 비정상적인 프로세스 트리 관계가 반복적으로 관찰되었으며, 이러한 이상 패턴을 통해 악성코드의 실행 흔적을 효과적으로 식별할 수 있었다. 이 점을 플러그인으로 개발해 자동 탐지할 수 있다면 유용할 것 같다는 결론을 내렸다.

본 실습에서는 악성코드 탐지를 목적으로 프로세스 트리 이상 탐지 플러그인을 제작하였다.
이 플러그인은 메모리 덤프 내 프로세스들의 부모–자식 관계를 분석하여, 정상적인 실행 흐름에서 벗어난 비정상 패턴을 탐지하는 데 중점을 둔다. 프로세스 트리는 악성코드의 초기 침투 경로와 실행 흐름을 파악하는 데 매우 중요한 아티팩트이다. 특히 악성코드는 정상 프로세스를 위장하거나, 비정상적인 부모–자식 관계를 형성하는 경우가 많기 때문에 이러한 패턴을 자동으로 필터링하는 기능은 효과적인 분석 자동화 수단이 될 수 있다.

본 플러그인은 다음과 같은 비정상적인 프로세스 트리 관계를 탐지하는 것을 목표로 한다.
• 부모가 존재하지 않거나 이미 종료된 고아 프로세스
• 시스템 프로세스의 비정상적인 부모 관계
• Office 또는 PDF 뷰어 프로세스에서 cmd.exe, powershell.exe 실행
• 자식 프로세스가 부모보다 먼저 생성된 시간 역전 현상
• csrss.exe 등 핵심 시스템 프로세스의 중복 실행

플러그인은 여러 단계의 분석 로직으로 구성되어 있으나, 핵심 흐름은 다음과 같다.

1단계: 전체 프로세스 수집
Volatility의 pslist 플러그인을 활용하여 메모리 덤프에서 모든 프로세스를 수집한다. 각 프로세스의 PID, PPID, 이름, 생성 시간, 종료 시간 등을 딕셔너리 형태로 저장하여 이후 부모–자식 관계를 빠르게 조회할 수 있도록 구성한다.
이 단계는 전체 분석의 기반이 되는 데이터 수집 단계이다.

1
2
3
4
5
6
# 1단계: 모든 프로세스 수집
all_processes = {}
for proc in pslist.PsList.list_processes(self.context, self.config["kernel"]):
proc_info = self._get_process_info(proc)
if proc_info:
all_processes[proc_info['pid']] = proc_info

2단계: 부모 프로세스 찾기
각 프로세스의 PPID(Parent PID)를 이용해 부모 프로세스를 찾는다. 만약 부모 프로세스가 존재하지 않는다면, 이는 이미 종료되었거나 프로세스 할로잉과 같은 공격 기법의 흔적일 가능성이 있다.

1
2
3
4
5
# 2단계: 부모-자식 관계 분석
for pid, proc_info in all_processes.items():
ppid = proc_info['ppid'] # 부모 프로세스 ID
parent_info = all_processes.get(ppid, None) # 부모 프로세스 정보 조회

3단계: 이상 징후 탐지 - 부모 부재
정상적인 프로세스는 반드시 부모 프로세스를 가지므로, 부모가 확인되지 않는 경우 이를 이상 징후로 분류한다.이러한 패턴은 악성코드 주입 또는 비정상적인 실행 흐름을 의심할 수 있다.

1
2
3
4
# Rule 1: 부모 프로세스가 존재하지 않음
if parent_info is None:
anomalies.append(f"Parent PID {child_info['ppid']} not found (orphaned or hollowed)")

4단계: 이상 징후 탐지 - 시스템 프로세스 검증
Windows의 주요 시스템 프로세스는 고정된 부모–자식 관계를 가진다.
예를 들어 services.exe는 wininit.exe의 자식이어야 하며, svchost.exe는 services.exe에 의해 생성된다.
이러한 관계가 깨진 경우, 정상 프로세스로 위장한 악성코드일 가능성이 있으므로 탐지 대상으로 분류한다.

1
2
3
4
5
6
7
# Rule 2: 시스템 프로세스의 올바른 부모 검증
if child_name == 'services.exe' and parent_name != 'wininit.exe':
anomalies.append(f"services.exe should be child of wininit.exe, not {parent_name}")

if child_name == 'svchost.exe' and parent_name != 'services.exe':
anomalies.append(f"svchost.exe should be child of services.exe, not {parent_name}")

5단계: 이상 징후 탐지 - 매크로 공격 패턴
Microsoft Office 애플리케이션이 명령 프롬프트나 PowerShell을 실행하는 것은 매크로 기반 악성 코드의 전형적인 패턴이다.
이런 관계가 발견되면 악성 문서를 통한 공격일 가능성이 높으므로 탐지해낸다.

1
2
3
4
# Rule 3: Office 앱에서 cmd/powershell 실행 탐지
suspicious_parents = ['winword.exe', 'excel.exe', 'powerpnt.exe', 'outlook.exe']
if child_name in ['cmd.exe', 'powershell.exe'] and parent_name in suspicious_parents:
anomalies.append(f"{child_name} spawned by Office (possible macro/exploit)")

6단계: 이상 징후 탐지 - 시간 역행
자식 프로세스의 생성 시간이 부모 프로세스보다 빠른 경우를 탐지한다.
이는 프로세스 할로잉 공격에서 흔히 발생하는 현상으로, 공격자가 정상 프로세스를 생성한 후 악성 코드로 내용을 교체하면서 시간 정보가 조작될 수 있다.

1
2
3
4
# Rule 4: 자식이 부모보다 먼저 생성됨
if parent_info and child_info['create_time'] < parent_info['create_time']:
anomalies.append("Child created BEFORE parent (time anomaly/process hollowing)")

7단계: 메모리 덤프 - 프로세스 레이어 생성
Volatility는 프로세스별 가상 메모리 공간을 레이어 단위로 관리한다.
프로세스별 가상 메모리 공간에 접근하려면 해당 프로세스의 레이어를 생성해야 한다.
이를 통해 프로세스가 실제로 사용하는 메모리 영역에 접근할 수 있게 된다.

1
2
3
# 프로세스의 가상 메모리 공간에 접근
proc_layer_name = proc.add_process_layer()
proc_layer = self.context.layers[proc_layer_name]

8단계: 메모리 덤프 - VAD 기반 추출
VAD(Virtual Address Descriptor)를 순회하며 각 메모리 영역을 읽고, 실제 메모리 내용을 파일로 덤프한다. 읽기 오류가 발생하는 영역은 건너뛰도록 처리하여, 분석이 중단되지 않도록 구성하였다.

1
2
3
4
5
6
7
8
9
10
11
12
# VAD(Virtual Address Descriptor)를 순회하며 메모리 읽기
for vad in proc.get_vad_root().traverse():
start = vad.get_start()
end = vad.get_end()
size = end - start + 1

try:
data = proc_layer.read(start, size, pad=True)
f.write(data)
bytes_written += len(data)
except Exception:
continue # 읽기 실패 시 건너뜀

최종적인 프로세스 트리 이상 탐지 플러그인는 아래와 같다.

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
from typing import List, Dict, Set
from volatility3.framework import interfaces, renderers, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.plugins.windows import pslist
import os

class DetectAnomalousProcessTree(interfaces.plugins.PluginInterface):
"""Detect anomalous parent-child process relationships and dump suspicious processes"""

_version = (1, 1, 0)

@classmethod
def get_requirements(cls):
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"]
),
requirements.BooleanRequirement(
name="show_all",
description="Show all processes (not just anomalous ones)",
optional=True,
default=False
),
requirements.BooleanRequirement(
name="dump",
description="Dump memory of anomalous processes",
optional=True,
default=False
),
requirements.StringRequirement(
name="dump_dir",
description="Directory to dump process memory",
optional=True,
default="./dumps"
)
]

def _get_process_info(self, proc):
"""Extract process information"""
try:
pid = proc.UniqueProcessId
ppid = proc.InheritedFromUniqueProcessId
name = utility.array_to_string(proc.ImageFileName)

# Get creation time
create_time = "N/A"
try:
create_time = proc.get_create_time()
except Exception:
pass

# Get exit time
exit_time = "Running"
try:
exit_time = proc.get_exit_time()
except Exception:
pass

return {
'pid': int(pid),
'ppid': int(ppid),
'name': str(name),
'create_time': create_time,
'exit_time': exit_time,
'proc_obj': proc
}
except Exception:
return None

def _is_system_process(self, name):
"""Check if process is a known Windows system process"""
system_processes = {
'system', 'smss.exe', 'csrss.exe', 'wininit.exe',
'services.exe', 'lsass.exe', 'svchost.exe', 'winlogon.exe',
'explorer.exe', 'dwm.exe', 'taskhostw.exe', 'sihost.exe',
'fontdrvhost.exe', 'conhost.exe', 'runtimebroker.exe'
}
return name.lower() in system_processes

def _check_parent_child_anomaly(self, child_info, parent_info, all_processes):
"""
Detect anomalous parent-child relationships.
Returns (is_anomalous, reason)
"""
anomalies = []

child_name = child_info['name'].lower()
parent_name = parent_info['name'].lower() if parent_info else "Unknown"

# Rule 1: Parent process doesn't exist (orphan or parent terminated)
if parent_info is None:
anomalies.append(f"Parent PID {child_info['ppid']} not found (orphaned or hollowed)")

# Rule 2: System processes with wrong parents
if child_name == 'services.exe' and parent_name != 'wininit.exe':
anomalies.append(f"services.exe should be child of wininit.exe, not {parent_name}")

if child_name == 'lsass.exe' and parent_name != 'wininit.exe':
anomalies.append(f"lsass.exe should be child of wininit.exe, not {parent_name}")

if child_name == 'csrss.exe' and parent_name not in ['smss.exe', 'unknown']:
anomalies.append(f"csrss.exe should be child of smss.exe, not {parent_name}")

if child_name == 'explorer.exe' and parent_name not in ['userinit.exe', 'explorer.exe', 'unknown']:
anomalies.append(f"explorer.exe has suspicious parent: {parent_name}")

# Rule 3: Suspicious parents for common applications
suspicious_parents_for_exe = {
'cmd.exe': ['winword.exe', 'excel.exe', 'powerpnt.exe', 'outlook.exe', 'acrord32.exe', 'acrord64.exe'],
'powershell.exe': ['winword.exe', 'excel.exe', 'powerpnt.exe', 'outlook.exe', 'acrord32.exe', 'acrord64.exe'],
'wscript.exe': ['winword.exe', 'excel.exe', 'powerpnt.exe', 'outlook.exe'],
'cscript.exe': ['winword.exe', 'excel.exe', 'powerpnt.exe', 'outlook.exe'],
}

if parent_info:
for suspicious_child, suspicious_parents in suspicious_parents_for_exe.items():
if child_name == suspicious_child and parent_name in suspicious_parents:
anomalies.append(f"{child_name} spawned by Office/PDF: {parent_name} (possible macro/exploit)")

# Rule 4: svchost.exe with wrong parent
if child_name == 'svchost.exe' and parent_name != 'services.exe':
anomalies.append(f"svchost.exe should be child of services.exe, not {parent_name}")

# Rule 5: Multiple explorer.exe instances (unusual)
explorer_count = sum(1 for p in all_processes.values() if p['name'].lower() == 'explorer.exe')
if child_name == 'explorer.exe' and explorer_count > 2:
anomalies.append(f"Multiple explorer.exe instances detected ({explorer_count})")

# Rule 6: Processes with PID 0 or invalid PPID
if child_info['pid'] == 0:
anomalies.append("Invalid PID (0)")

# Rule 7: Child created before parent (time anomaly)
if parent_info and child_info['create_time'] != "N/A" and parent_info['create_time'] != "N/A":
try:
if child_info['create_time'] < parent_info['create_time']:
anomalies.append("Child created BEFORE parent (time anomaly/process hollowing)")
except Exception:
pass

# Rule 8: Office apps spawning executables from temp directories
if parent_name in ['winword.exe', 'excel.exe', 'powerpnt.exe', 'outlook.exe']:
if any(keyword in child_name for keyword in ['.tmp', 'temp', 'appdata']):
anomalies.append(f"Office app spawned suspicious temp executable: {child_name}")

# Rule 9: System processes running from non-system directories
system_process_names = ['csrss.exe', 'lsass.exe', 'services.exe', 'smss.exe', 'winlogon.exe']
if child_name in system_process_names:
# In a full implementation, we'd check the image path
# For now, we flag multiple instances as suspicious
same_name_count = sum(1 for p in all_processes.values() if p['name'].lower() == child_name)
if same_name_count > 1:
anomalies.append(f"Multiple instances of system process {child_name} ({same_name_count})")

return (len(anomalies) > 0, anomalies)

def _dump_process_memory(self, proc, pid, process_name, dump_dir):
"""
Dump process memory to file using VAD-based approach
Returns tuple (success, output_file, error_msg)
"""
try:
# Create dump directory if it doesn't exist
os.makedirs(dump_dir, exist_ok=True)

# Create output filename
output_file = os.path.join(dump_dir, f"pid_{pid}_{process_name}.dmp")

# Get process layer
try:
proc_layer_name = proc.add_process_layer()
if not proc_layer_name:
return (False, None, "Failed to create process layer")
proc_layer = self.context.layers[proc_layer_name]
except Exception as e:
return (False, None, f"Process layer error: {str(e)}")

# Write memory to file using VAD regions
with open(output_file, 'wb') as f:
bytes_written = 0
skipped_regions = 0

# Try to iterate through VAD (Virtual Address Descriptor) regions
try:
for vad in proc.get_vad_root().traverse():
try:
start = vad.get_start()
end = vad.get_end()
size = end - start + 1

# Skip very large regions (> 100MB) to avoid issues
if size > 100 * 1024 * 1024:
skipped_regions += 1
continue

# Try to read this VAD region
try:
data = proc_layer.read(start, size, pad=True)
f.write(data)
bytes_written += len(data)
except Exception:
# Skip unreadable regions
skipped_regions += 1
continue

except Exception:
continue

except AttributeError:
# VAD not available, try direct memory mapping
try:
for map_start, map_size, _, _ in proc_layer.mapping(0x0, proc_layer.maximum_address, ignore_errors=True):
try:
# Read in chunks to avoid large memory issues
chunk_size = min(map_size, 10 * 1024 * 1024) # 10MB chunks
for offset in range(0, map_size, chunk_size):
read_size = min(chunk_size, map_size - offset)
try:
data = proc_layer.read(map_start + offset, read_size, pad=True)
f.write(data)
bytes_written += len(data)
except Exception:
skipped_regions += 1
continue
except Exception:
skipped_regions += 1
continue
except Exception as e:
if bytes_written == 0:
return (False, None, f"Memory mapping failed: {str(e)}")

if bytes_written == 0:
return (False, None, "No readable memory regions found")

status_msg = f"{output_file} ({bytes_written} bytes"
if skipped_regions > 0:
status_msg += f", {skipped_regions} regions skipped"
status_msg += ")"

return (True, status_msg, None)

except Exception as e:
return (False, None, f"Dump failed: {str(e)}")

def _generator(self):
"""Generate results for anomalous processes"""

show_all = self.config.get("show_all", False)
dump_enabled = self.config.get("dump", False)
dump_dir = self.config.get("dump_dir", "./dumps")

# First pass: collect all processes
all_processes = {}
for proc in pslist.PsList.list_processes(self.context, self.config["kernel"]):
proc_info = self._get_process_info(proc)
if proc_info:
all_processes[proc_info['pid']] = proc_info

# Second pass: analyze parent-child relationships
dumped_count = 0
for pid, proc_info in all_processes.items():
ppid = proc_info['ppid']
parent_info = all_processes.get(ppid, None)

# Check for anomalies
is_anomalous, anomalies = self._check_parent_child_anomaly(
proc_info,
parent_info,
all_processes
)

# Dump if anomalous and dump is enabled
dump_status = "N/A"
if dump_enabled and is_anomalous:
success, output_file, error_msg = self._dump_process_memory(
proc_info['proc_obj'],
proc_info['pid'],
proc_info['name'],
dump_dir
)

if success:
dump_status = f"Dumped: {output_file}"
dumped_count += 1
else:
dump_status = f"Failed: {error_msg}"

# Determine if we should show this process
should_show = show_all or is_anomalous

if should_show:
parent_name = parent_info['name'] if parent_info else "<not found>"
anomaly_desc = " | ".join(anomalies) if anomalies else "Normal"
severity = "HIGH" if is_anomalous else "Normal"

yield (
0,
(
int(proc_info['pid']),
str(proc_info['name']),
int(proc_info['ppid']),
str(parent_name),
str(severity),
str(anomaly_desc),
str(proc_info['create_time']),
str(proc_info['exit_time']),
str(dump_status)
)
)

# Print summary if dump was enabled
if dump_enabled:
print(f"\n[*] Dumped {dumped_count} anomalous processes to {dump_dir}")

def run(self):
"""Run the plugin"""
return renderers.TreeGrid(
[
("PID", int),
("Process Name", str),
("Parent PID", int),
("Parent Name", str),
("Severity", str),
("Anomaly Description", str),
("Create Time", str),
("Exit Time", str),
("Dump Status", str),
],
self._generator()
)

플러그인 테스트 및 디버깅

완성된 플러그인 파일은 volatility3/plugins/windows 경로에 배치하였다.

그림 96. Volatility 플러그인 파일 배치 화면

그림 96. Volatility 플러그인 파일 배치 화면


이후 python vol.py –help 명령어를 통해 플러그인이 정상적으로 로드되는지 확인하였다.

그림 97. Volatility3에서 플러그인 로딩 여부 확인 결과

그림 97. Volatility3에서 플러그인 로딩 여부 확인 결과


완성된 플러그인의 실행 명령어는 옵션에 따라 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
# 이상 프로세스만 표시 (덤프 없음)
python3 vol.py -f memory.dmp windows.detect_anomalous_process_tree

# 이상 프로세스 탐지 + 메모리 덤프
python3 vol.py -f memory.dmp windows.detect_anomalous_process_tree --dump

# 덤프 디렉토리 지정
python3 vol.py -f memory.dmp windows.detect_anomalous_process_tree --dump --dump-dir /path/to/dumps

# 모든 프로세스 표시 + 이상 프로세스만 덤프
python3 vol.py -f memory.dmp windows.detect_anomalous_process_tree --dump --show-all

본 플러그인 동작 검증은 Agent Tesla 분석 시 사용했던 메모리 덤프 파일(memdump.mem)을 활용하였다. 이전 분석에서 식별했던 의심 프로세스들이 본 플러그인에서도 정상적으로 탐지되는 것을 확인할 수 있었으며, 탐지된 프로세스의 메모리 덤프 또한 문제없이 수행되었다.

그림 98. 탐지된 의심 프로세스들의 메모리 덤프 결과

그림 98. DetectAnomalousProcessTree 플러그인을 통한 의심 프로세스 탐지 결과


그림 99. 탐지된 의심 프로세스들의 메모리 덤프 결과

그림 99. DetectAnomalousProcessTree 플러그인을 통한 의심 프로세스 탐지 결과


칼럼을 마무리하며

본 칼럼에서는 Formbook, Emotet, Agent Tesla 악성코드 분석 과정에서 확인된 공통적인 특징을 정리하였다. 세 악성코드 모두 실행 이후 비정상적인 프로세스 트리 관계를 형성하였으며, 해당 관계는 메모리 덤프 분석 과정에서 악성 행위를 추적하는 주요 단서로 활용될 수 있었다.
이러한 관찰 결과를 바탕으로, 프로세스 트리 이상 징후를 기준으로한 자동 탐지 플러그인 구현 가능성을 검토하였다. 해당 방식은 메모리 포렌식 분석 시 초기 분석 대상을 선별하는 용도로 활용할 수 있을 것으로 판단된다.
본 칼럼에서 제시한 프로세스 트리 기반 접근 방식은 메모리 포렌식 분석 시 우선적으로 분석할 프로세스를 선별하는 데 활용할 수 있다.

참고 문헌

  • 이경호. (2015). 윈도우 환경에서 프로세서 제어 영역을 통한 안티 메모리 포렌식 대응기법 (석사학위논문, 전남대학교, 광주).
  • Moz1e. (n.d.). [Operating System] 가상메모리와 물리메모리. Tistory. https://moz1e.tistory.com/528
  • Gwonsoo Lee. (2024, July 3). 물리 메모리와 가상 메모리. Zero to Expert. https://www.zerotoexpert.blog/p/f04
  • 민도리. (2023, January 3). 커널영역, 유저영역 차이. Tistory. https://minyeong3.tistory.com/28#google_vignette
  • 최자은. (2023, March 9). [자료구조] Heap & Stack. Velog.https://velog.io/@lattechoi/Heap-Stack
  • Nicolao, G. (2019, January). VB2018 paper: Inside Formbook infostealer. Virus Bulletin.
    https://www.virusbulletin.com/virusbulletin/2019/01/vb2018-paper-inside-formbookinfostealer/
  • NETSCOUT. (2017, September 20). The Formidable FormBook form grabber. NETSCOUT.
    https://www.netscout.com/blog/asert/formidable-formbook-form-grabber
  • Villeneuve, N., Eitzman, R., Nemes, S., & Dean, T. (2017, October 5). Significant FormBook
    distribution campaigns impacting the U.S. and South Korea. Google Cloud Blog.
    https://cloud.google.com/blog/topics/threat-intelligence/formbook-malware-distributioncampaigns/?hl=en
  • Cimpanu, C. (2017, October 5). FormBook infostealer sold on hacking forums is
    becoming quite a threat. Bleeping Computer.
    https://www.bleepingcomputer.com/news/security/formbook-infostealer-sold-on-hackingforums-is-becoming-quite-a-threat/
  • FortiGuard Labs. (n.d.). Threat Intelligence Platform. Fortinet. https://www.fortiguard.com/threatintel-search
  • AhnLab Security Emergency Response Center (ASEC). (2022, October 19). FormBook malware being distributed as .NET. AhnLab. https://asec.ahnlab.com/en/40663/
  • Kail-KM. (2016, March 3). 악성코드 분류. Tistory. https://kali-km.tistory.com/entry/악성코드-분류
  • Dreamhack. (n.d.). System hacking fundamental [Lecture]. Dreamhack. https://dreamhack.io/lecture/paths/system-hacking-fundamental
  • Dreamhack. (n.d.). Memory forensics [Lecture]. Dreamhack.https://dreamhack.io/lecture/units/memory-forensics
  • Hackyboiz. (2022, January 14). Linux memory layout. Hackyboiz. https://hackyboiz.github.io/2022/01/14/poosic/linux-memory-layout/
  • Nicolao, G. (2019, January). VB2018 paper: Inside Formbook infostealer. Virus Bulletin. https://www.virusbulletin.com/virusbulletin/2019/01/vb2018-paper-inside-formbookinfostealer/
  • NETSCOUT. (2017, September 20). The Formidable FormBook form grabber. NETSCOUT. https://www.netscout.com/blog/asert/formidable-formbook-form-grabber
  • Villeneuve, N., Eitzman, R., Nemes, S., & Dean, T. (2017, October 5). Significant FormBook distribution campaigns impacting the U.S. and South Korea. Google Cloud Blog. https://cloud.google.com/blog/topics/threat-intelligence/formbook-malware-distributioncampaigns/?hl=en
  • Cimpanu, C. (2017, October 5). FormBook infostealer sold on hacking forums is becoming quite a threat. Bleeping Computer.
    https://www.bleepingcomputer.com/news/security/formbook-infostealer-sold-on-hackingforums-is-becoming-quite-a-threat/
  • FortiGuard Labs. (n.d.). Threat Intelligence Platform. Fortinet. https://www.fortiguard.com/threatintel-search
  • AhnLab Security Emergency Response Center (ASEC). (2022, October 19). FormBook malware being distributed as .NET. AhnLab. https://asec.ahnlab.com/en/40663/
  • AhnLab. (2023, March 3). ASEC 분석팀 – Emotet 관련 게시글. AhnLab ASEC. https://asec.ahnlab.com/ko/41490/
  • BleepingComputer. (2023, October 10). Emotet now spreads via fake Adobe Windows app installer packages. BleepingComputer.
    https://www.bleepingcomputer.com/news/security/emotet-now-spreads-via-fake-adobewindows-app-installer-packages/
  • BoanNews. (2024). Emotet 관련 기사. BoanNews.https://www.boannews.com/media/view.asp?idx=116841&page=19&kind=1
  • CISA. (2020, October 6). AA20-280A: Emotet malware. Cybersecurity and Infrastructure Security Agency. https://www.cisa.gov/news-events/cybersecurity-advisories/aa20-280a
  • Cofense. (2020). The evolution of Emotet malware. Cofense. https://cofense.com/blog/theevolution-of-emotet-malware/
  • Darktrace. (2022). Glimpsing inside the Trojan Horse: An insider analysis of Emotet. Darktrace Blog. https://www.darktrace.com/blog/glimpsing-inside-the-trojan-horse-an-insider-analysis-ofemotet
  • Europol. (2021, January 27). World’s most dangerous malware Emotet disrupted through global action. Europol Newsroom. https://www.europol.europa.eu/mediapress/newsroom/news/world%E2%80%99s-most-dangerous-malware-emotet-disruptedthrough-global-action
  • FBI. (2021, February 1). Emotet malware disrupted. Federal Bureau of Investigation.
    https://www.fbi.gov/news/stories/emotet-malware-disrupted-020121
  • Fortinet. (n.d.). Trojan horse virus. Fortinet Cyber Glossary.
    https://www.fortinet.com/kr/resources/cyberglossary/trojan-horse-virus
  • Microsoft. (n.d.). Data execution prevention. Microsoft Learn. https://learn.microsoft.com/ko-kr/windows/win32/memory/data-execution-prevention
  • Microsoft. (n.d.). Dynamic-link library. Microsoft Learn. https://learn.microsoft.com/enus/troubleshoot/windows-client/setup-upgrade-and-drivers/dynamic-link-library
  • Microsoft. (n.d.). Dynamic-link library search order. Microsoft Learn.
    https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order
  • Palo Alto Networks – Unit 42. (2022). Emotet malware summary Epoch 4 & 5. Unit 42 Blog.
    https://unit42.paloaltonetworks.com/emotet-malware-summary-epoch-4-5/
  • Trend Micro. (2019). Going in-depth with Emotet multilayer operating mechanisms. Trend Micro Research. https://www.trendmicro.com/en_us/research/19/a/going-in-depth-with-emotetmultilayer-operating-mechanisms.html
  • Elastic. (2023). Windows Trojan Emotet YARA rule. GitHub. https://github.com/elastic/protectionsartifacts/blob/main/yara/rules/Windows_Trojan_Emotet.yar
  • File.net. (n.d.). SearchApp.exe process information. File.net.
    https://www.file.net/process/searchapp.exe.html#:~:text=SearchApp.exe%20is%20a%20software,
    :%5CWindows%5CSystemApps%5CMicrosoft.
  • HelpDeskGeek. (2022). What is SearchApp.exe and is it safe?. HelpDeskGeek.
    https://helpdeskgeek.com/what-is-searchapp-exe-and-is-itsafe/#:~:text=You%20can%20permanently%20uninstall%20the,methods%20have%20not%20re
    moved%20it
  • Kali-KM. (2020). DLL이란. Tistory. https://kali-km.tistory.com/entry/DLL%EC%9D%B4%EB%9E%80
  • Play-With. (2021). DLL 관련 게시글. Tistory. https://play-with.tistory.com/329
  • Rninche01. (2023). 악성코드 지속 메커니즘. Tistory.
    https://rninche01.tistory.com/entry/%EC%95%85%EC%84%B1%EC%BD%94%EB%93%9C-%EC%
    A7%80%EC%86%8D%EB%A9%94%EC%BB%A4%EB%8B%88%EC%A6%98
  • GBWorld. (2022). Emotet 관련 게시글. Tistory. https://gbworld.tistory.com/1420
  • AhnLab ASEC. (n.d.). Stealer malware 공격 동향 분석.https://asec.ahnlab.com/ko/53449/
  • Alyac. (n.d.). 정보 탈취형 악성코드(Stealer Malware)란?https://blog.alyac.co.kr/3557/
  • Boan News. (n.d.). 정보 탈취 악성코드 위협 분석.https://www.boannews.com/media/view.asp?idx=112296
  • Boan News. (n.d.). 스틸러 악성코드 확산 동향.https://m.boannews.com/html/detail.html?tab_type=1&idx=125094
  • Elastic Security. (n.d.). Windows_Trojan_AgentTesla.yar. GitHub repository.
    https://github.com/elastic/protectionsartifacts/blob/main/yara/rules/Windows_Trojan_AgentTesla.yar
  • Igloo Security. (n.d.). 스틸러 멀웨어(Stealer Malware) 공격 동향 및 대응.https://www.igloo.co.kr/securityinformation/%EC%8A%A4%ED%8B%B8%EB%9F%AC-%EB%A9%80%EC%9B%A8%EC%96%B4-stealermalware-%ED%83%88%EC%B7%A8%ED%98%95-%EC%95%85%EC%84%B1%EC%BD%94%EB%93%9C-%EA%B3%B5%EA%B2%A9%EB%8F%99%ED%96%A5-%EB%B0%8F-%EB%8C%80/
  • KSHOSTING. (n.d.). 악성코드의 종류와 특징. https://blog.naver.com/kshosting/222278553544
  • Piolink. (n.d.). 정보 탈취 악성코드 및 보안 대응 전략 (White paper). https://www.piolink.com/_dev/data/file/security/l6igow52fgnl.pdf
  • SentinelOne. (n.d.). Types of malware.
    https://www.sentinelone.com/ko/cybersecurity-101/cybersecurity/types-of-malware/
  • tatamo2966. (n.d.). YARA의 개념과 YARA Rule 설명. Velog.
    https://velog.io/@tatamo2966/Yara%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BCYara-Rule-%EC%84%A4%EB%AA%85
  • VirusTotal. (n.d.). YARA documentation. https://virustotal.github.io/yara/
  • Datanet. (n.d.). 정보 탈취형 악성코드 위협 증가.
    https://www.datanet.co.kr/news/articleView.html?idxno=193243
  • 정현덕, & 김도현. (2024). Windows OS 기반 악성코드 탐지를 위한 Volatility 3 Plug-in 개발 연구. 디지털포렌식연구, 18(2), 106–122.
  • Volatility Foundation. (n.d.). Volatility 3 basics. Volatility 3 documentation.
    https://volatility3.readthedocs.io/en/v2.4.1/basics.html
  • Volatility Foundation. (n.d.). Writing complex plugins. Volatility 3 documentation.
    https://volatility3.readthedocs.io/en/latest/complex-plugin.html
  • 정근영. (n.d.). Volatility 3 사용법 정리 (1). Tistory.
    https://geun-yeong.tistory.com/58
  • 정근영. (n.d.). Volatility 3 사용법 정리 (2). Tistory.
    https://geun-yeong.tistory.com/59