본 게시글은 책 <악성코드 분석 시작하기> 의 내용을 정리한 글 입니다.

 

악성코드 분석 시작하기 : 네이버 도서

네이버 도서 상세정보를 제공합니다.

search.shopping.naver.com

1.6 PE 헤더 정보 조사

- 윈도우 실행 파일은 PE/COFF (Portable Executable/Common Object File Format)를 반드시 준수해야 함

 

- PE 파일 포맷

> 윈도우 실행 파일(.exe, .dll, .sys, .ocx, drv)이 사용

> 운영 시스템이 메모리로 로딩할 때 필요한 정보를 가진 일련의 구조체와 하위 컴포넌트

 

- 실행 파일을 컴파일하면 해당 구조체를 설명하는 헤더 (PE 헤더)를 포함

> 실행 파일이 메모리에서 실행될 위치

> 실행 파일의 시작 위치

> 애플리케이션이 의존하는 라이브러리/함수 목록

> 바이너리가 사용하는 리소스와 같은 정보가 포함

 

- PE 파일 구조체를 이해하기 위한 유용한 자료 링크 
> Win32 PE 파일 포맷에 대한 심층적인 조사 - 파트 1 : https://www.delphibasics.info/home/delphibasicsarticles/anin-depthlookintothewin32portableexecutablefileformat-part1
> Win32 PE 파일 포맷에 대한 심층적인 조사 - 파트 2 : https://www.delphibasics.info/home/delphibasicsarticles/anin-depthlookintothewin32portableexecutablefileformat-part2
> PE 헤더와 구조 : https://www.openrce.org/reference_library/files/reference/PE%20Format.pdf
> PE101 - 윈도우 실행 파일 공략 : https://github.com/corkami/pics/blob/master/binary/pe101/pe101ko.pdf

 

- PE 구조와 그 하위 컴포넌트를 검사하고 수정할 수 있는 몇 가지 도구 
> CFF Explorer : https://ntcore.com/explorer-suite/

> PE Internals : https://www.andreybazhan.com/pe-internals.html

> PPEE(puppy) : https://www.mzrst.com/
> PEBrowse Professional : https://download.cnet.com/developer/smidgeonsoft/i-6276008

파일 의존성과 임포트 조사

- 악성코드는 파일, 레지스트리, 네트워크 등과 상호작용하며, 이를 위해 운영 시스템에서 제공하는 함수에 많이 의존

- 윈도우는 API로 불리는 함수를 임포트하며, 상호작용을 위해 동적 링크 라이브러리 (DLL, Dynamic Link Library) 파일이 필요

> DLL에서 여러 함수를 임포트 (Import) 하거나 호출

> 악성코드가 의존하고 있는 DLL과 그런 DLL이 임포트하고 있는 API 함수를 조사하면 악성코드의 기능과 성능, 실행 중 예상할 수 있는 기능을 알 수 있음

 

- 윈도우 실행 파일의 파일 의존성은 PE 파일 구조의 임포트 테이블에 저장

> pestudiolibraries 버튼을 클릭하면 의존성을 가진 모든 DLL 파일과 각 DLL에서 임포트한 함수의 수를 보여줌

> imports 버튼을 클릭하면 DLL에서 임포트한 API를 표시

- 때때로 악성코드는 LoadLibrary() 또는 LdrLoadDLL()과 같은 API를 호출해서 실행 중 명시적으로 DLL 로드 가능

- 또는, GetProcAddress() API를 사용해 함수 주소를 확인할 수 있음

> 실행 중 로드한 DLL의 정보는 PE 파일의 임포트 테이블에 나타나지 않음

> 윈도우 API 함수와 그 함수의 기능 정보 참고 : https://learn.microsoft.com/ko-kr/search/

 

- Python을 이용해 DLL 파일과 임포트한 함수를 나열할 수 있으며, pefile 모듈을 사용

import pefile
import sys

# 명령행 인자 체크
if len(sys.argv) != 2:
    print("Usage: python script.py <filename>")
    sys.exit(1)

mal_file = sys.argv[1]


def enum_import(mal_file):
    try:
        # PE 파일 로드
        pe = pefile.PE(mal_file)
        
        # Import Table이 존재하는지 확인
        if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
            print(f"[+] {mal_file} Import Table Found")
            for entry in pe.DIRECTORY_ENTRY_IMPORT:
                print(f"DLL : {entry.dll.decode('utf-8')} ({len(entry.imports)})")
                for i, imp in enumerate(entry.imports):
                    if imp.name is not None:
                        print(f"  {i + 1}. {imp.name.decode('utf-8')}")
                    else:
                        print(f"  {i + 1}. Ordinal: {imp.ordinal}")
                print()
        else:
            print("[-] No Import Table Found")

    except FileNotFoundError:
        print("[-] File not found.")
    except pefile.PEFormatError:
        print("[-] Invalid PE file.")
    except Exception as e:
        print(f"[-] Error: {e}")


if __name__ == '__main__':
    enum_import(mal_file)

익스포트 (Export) 조사

- 실행 파일과 DLL은 다른 프로그램에서 사용할 수 있는 함수를 익스포트 (Export)할 수 있음

> 일반적으로 DLL은 실행 파일이 임포트할 수 있는 함수 (익스포트)를 노출

> DLL은 단독으로 실행할 수 없기 때문에 호스트 프로세스를 통해 실행

> 공격자는 악의적인 함수를 익스포트하는 DLL을 자주 생성

※ 익스포트 함수명이 매번 악성코드의 기능에 대한 힌트를 주는 것은 아님

 

- pestudio의 exports 메뉴에서 확인 가능

> Python 예시 참조

import pefile
import sys

# 명령행 인자 체크
if len(sys.argv) != 2:
    print("Usage: python script.py <filename>")
    sys.exit(1)

mal_file = sys.argv[1]


def enum_import(mal_file):
    try:
        # PE 파일 로드
        pe = pefile.PE(mal_file)
        
        # Import Table이 존재하는지 확인
        if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
            print(f"[+] {mal_file} Import Table Found")
            for entry in pe.DIRECTORY_ENTRY_IMPORT:
                print(f"DLL : {entry.dll.decode('utf-8')} ({len(entry.imports)})")
                for i, imp in enumerate(entry.imports):
                    if imp.name is not None:
                        print(f"  {i + 1}. {imp.name.decode('utf-8')}")
                    else:
                        print(f"  {i + 1}. Ordinal: {imp.ordinal}")
                print()
        else:
            print("[-] No Import Table Found")
        
        # Export Table이 존재하는지 확인
        if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'):
            print(f"[+] {mal_file} Export Table Found")
            for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols:
                if entry.name is not None:
                    print(f"DLL : {entry.name.decode('utf-8')}")
                else:
                    print(f"Ordinal : {entry.ordinal}")
        else:
            print("[-] No Export Table Found")

    except FileNotFoundError:
        print("[-] File not found.")
    except pefile.PEFormatError:
        print("[-] Invalid PE file.")
    except Exception as e:
        print(f"[-] Error: {e}")


if __name__ == '__main__':
    enum_import(mal_file)

PE 섹션 테이블과 섹션 조사

- PE 파일의 실제 내용은 섹션 (Section)으로 구분

> 섹션은 코드 (Code)데이터 (Data)를 나타내고, 읽기/쓰기와 같은 메모리 내부 속성을 가짐

구분 설명
코드 - 프로세스가 실행할 명령어를 포함
데이터 - 읽기/쓰기 프로그램 데이터 (전역변수)
- 임포트/익스포트 테이블
- 리소스 등

 

- 각 섹션은 해당 섹션의 목적을 나타내는 고유한 이름을 갖고 있음

> 실행 파일을 컴파일하는 동안 컴파일러는 일관된 섹션 이름을 추가

> PE 파일의 공통 섹션

섹션명 설명
.text 또는 CODE - 실행 코드를 포함
.data 또는 DATA - 읽기/쓰기 데이터와 전역 변수 포함
.rdata - 읽기 전용 데이터를 포함. 때에 따라 임포틑 또는 익스포트 정보 포함
.idata - 존재한다면 임포트 테이블 포함. 존재하지 않으면, .rdata 섹션에서 탐색 가능
.edata - 존재한다면 익스포트 정보 포함. 존재하지 않으면, .rdata 섹션에서 탐색 가능
.rsrc - 실행 파일에서 사용하는 아이콘, 대화창, 메뉴, 문자열 등의 리소스

> 섹션명은 사람을 위한 것으로 운영 시스템에서는 사용하지 않으며, 공격자 또는 난독화 소프트웨어가 섹션명을 변경할 수 있음

 

- 섹션에 대한 정보 (섹션명, 섹션의 위치, 특징)는 PE 헤더에 있는 섹션 테이블 (Section Table)에 존재

> pestudio의 sections 메뉴에서 섹션 테이블에서 추출한 섹션 정보와 그 속성 (읽기/쓰기)을 표시

> 섹션 테이블 필드

필드 설명
name - 섹션 이름
virtual-size - 메모리에 로딩할 때 섹션의 크기 
virtual-address - 섹션을 메모리 어디에서 찾을 수 있는지 나타내는 상대적 가상 주소(실행 파일 베이스 주소에서 얼마나 떨어져 있는지를 나타내는 오프셋)
raw-size - 해당 섹션이 디스크에 존재할 때의 크기
raw-data - 파일에서 해당 섹션을 찾을 수 있는 오프셋 
entry-point - 코드가 실행을 시작하는 RVA(Relatve Virtual Address, 상대적 가상 주소). 일반적으로 .text 섹션에 존재

> 만약 컴파일러에서 생성한 일반적인 섹션명 (.text, .data 등)이 아니라면 의심

> 일반적으로 raw-size와 virtual-size의 크기는 거의 같아야 함

> 만약 raw-size ≒ 0인데, virtual-size가 더 큰 공간을 갖고 있다면, 이는 패킹한 바이너리일 수 있음 (패킹한 바이너리를 실행할 때 패커의 압축 해제 루틴이 런타임 중 압축 해제한 데이터 또는 명령어를 메모리로 복사하기 때문)

컴파일 타임스탬프 조사

- PE 헤더는 바이너리가 컴파일될 때 생성되는 정보를 포함

- 타임스탬프 (Timestamp) 필드를 조사하면 악성코드가 언제 처음 생성됐는지 알 수 있음

> 공격 활동의 타임라인을 작성할 때 도움을 줌

> 공격자가 실제 타임스탬프를 알 수 없도록 수정해 분석 방해 가능

> pestudio의 file-header 메뉴에 “compiler-stamp” 항목에서 확인 가능

PE 리소스 조사

- 아이콘, 메뉴, 대화장사, 문자열과 같이 실행 파일에 필요한 리소스는 실행 파일의 리소스 섹션 (.rsrc)에 저장

> 공격자는 때때로 추가 바이너리, 미끼 문서, 설정 데이터 같은 정보를 리소스 섹션에 저장

> 리소스 해커 (Resource Hacker)는 의심 바이너리에서 리소스를 조사하고 확인 후 추출할 수 있는 도구

본 게시글은 책 <악성코드 분석 시작하기> 의 내용을 정리한 글 입니다.

 

악성코드 분석 시작하기 : 네이버 도서

네이버 도서 상세정보를 제공합니다.

search.shopping.naver.com

1.4 문자열 추출

- 문자열 (String) : 파일에 포함된 출력 가능한 ASCII와 유니코드의 순차적인 문자 집합

 

- 문자열 추출은 프로그램 기능과 의심 바이너리 관련 지표에 대한 단서를 제공

> 파일명, URL, 도메인명, IP 주소, 공격 명령어, 제시트리 키 등을 포함

> 악성코드가 파일을 생성한다면 해당 파일명이 바이너리 안에 문자열로 저장

> 공격자가 통제하는 도메인명을 악성코드가 호출한다면 해당 도메인명이 문자열로 저장

> 파일의 목적과 기능에 대한 명확한 단서는 아니지만, 악성코드가 할 수 있는 일에 대한 힌트를 제공

 

- 리눅스에서는 strings 유틸리티를 사용해 문자열을 추출할 수 있음 (기본적으로 최소 4문자 이상인 ASCII 문자열 추출)

> -a 옵션 : 전체파일에서 문자열 추출

> -el 옵션 : 유니코드 추출

> 바이너리에서 유용한 정보를 얻으려면 때때로 ASCII와 유니코드 문자열 모두를 추출해야 함

- 윈도우에서는 ASCII와 유니코드 문자열 모두를 표현해 주는 pestudio를 사용

> pestudio는 훌륭한 PE 분석 도구로 의심 바이너리의 악성코드 초기 점검이 가능

> PE 실행 파일에서 유용한 정보의 여러 힌트를 얻을 수 있음

- FLOSS (FireEye Labs Obfuscated String Solver)는 악성코드에서 난독화된 문자열을 자동을 추출하고 식별하고자 디자인된 도구

> 악성코드는 탐지를 회피하고자 문자열 난독화(String Obfuscation) 기법을 사용

> 난독화가 적용된 경우 strings 유틸리티를 이용해 문자열 추출 불가

1.5 파일 난독화 파악

- 악성코드 제작자는 악성코드 바이너리를 일반적으로 난독화하거나 보호

> 난독화 기술은 바이너리를 탐지/분석하기 어렵게 하기 때문에 추출할 수 있는 문자열이 거의 없음

> 악성코드 제작자는 보안 제품의 탐지 회피 및 분석을 방해하고자 패커 (Packer)크립터 (Cryptor) 같은 프로그램을 자주 사용

 

- 패커 (Packer)

> 실행 파일을 입력받아 실행 파일의 내용을 압축해 난독화하는 프로그램

> 난독화한 파일은 새로운 실팽 파일의 구조체에 저장

> 패킹 (Packing)한 프로그램을 실행하면 압축해제 루틴이 실행되고 메모리에 원본 바이너리를 추출한 후 실행

 

- 크립터 (Cryptor)

> 패커와 유사하나, 실행 파일의 내용을 난독화하고자 압축 대신 암호화를 사용

> 암호화한 내용은 새로운 실행 파일에 저장

> 암호화한 프로그램을 실행하면 실행할 때 복호화 루틴을 실행해 원본 바이너리를 메모리에 추출한 후 실행

 

- UPX 패커 적용

> UPX는 압축 기법을 사용하기 때문에 패킹한 바이너리가 원본 바이너리보다 크기가 작음

> 패킹한 바이너리에 strings 명령을 실행하면 난독화한 문자열을 보여 주고 가치 있는 정보를 노출하지 않음 -> 난독화하는 이유

- 윈도우에서는 Exeinfo PE를 이용해 파일 난독화 탐지

> Exeinfo PE : 윈도우에서 패커를 탐지하는 도구

> 패킹된 파일을 Exeinfo PE에 로딩하면 패킹 정보와 패킹 해제에 사용할 명령어에 대한 힌트도 제공

> 정상적인 실행 파일 대부분은 파일 내용을 난독화하지 않기 때문에 패킹을 했다면 악성코드일 확률이 매우 높음

- 패커, 크립터 탐지를 도울 수 있는 다른 CLI와 GUI 도구 
> TrID
> TRIDNet
> Detect it Easy
> RDG Packer Detector

> packerid.py

> PEiD

+ Recent posts