바이너리 분석

jammy0903·2024년 8월 18일

study

목록 보기
2/7

바이너리 파일이란?🗒

바이너리 파일은 0과 1로 이루어진 데이터의 집합입니다.
컴퓨터의 언어로 이루어진 파일이요.
모든 디지털 파일은 근본적으로 바이너리 형태이지만, 일반적으로 '바이너리'라고 할 때는 실행 가능한 프로그램 파일을 지칭합니다.

소스 코드에서 실행 파일까지 🚣‍

  1. 소스 코드 작성: ex)C 언어로 main.c 파일을 작성합니다.
  2. 컴파일: gcc -c main.c와 같은 명령어로 객체 파일(main.o)로 변환합니다. 이 과정에서 고수준 언어(c언어)가 기계어로 변환되지만, 아직 실행 가능한 형태는 아닙니다.
  3. 링킹: gcc main.o -o a.out와 같은 명령어로 객체 파일을 실행 가능한 바이너리(a.out)로 변환합니다. 이 과정에서 여러 객체 파일과 라이브러리가 하나의 실행 파일로 결합됩니다.

바이너리 분석의 필요성

  1. 소스 코드 부재: 대부분의 상용 소프트웨어는 소스 코드를 공개하지 않습니다.
  2. 악성코드 분석: 바이러스나 멀웨어의 동작을 이해하고 대응책을 마련하기 위해
  3. 취약점 연구: 소프트웨어의 보안 취약점을 찾아 패치하거나 익스플로잇을 개발하기 위해
  4. 성능 최적화: 컴파일된 코드의 성능을 분석하고 개선하는 데 활용됩니다.

초도 분석📈

: 바이너리에 대한 기본적인 정보를 수집하는 과정

  1. file 명령어

    • 사용법: file [파일명]
    • 기능: 파일의 종류, 아키텍처, 운영체제 등 기본 정보를 제공합니다.
    • 예시: file a.out → "a.out: ELF 64-bit LSB executable, x86-64,
      version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=1234..."
  2. strings 명령어

    • 사용법: strings [파일명]
    • 기능: 바이너리 내의 인쇄 가능한 문자열을 추출합니다.
    • 용도: 하드코딩된 문자열, 메시지, URL 등을 식별하는 데 유용합니다.
    • 예시: strings a.out → "Hello, World!", "/usr/lib/", "malloc error" 등의 문자열 출력
  3. ldd (List Dynamic Dependencies)

    • 사용법: ldd [실행 파일명]
    • 기능: 프로그램이 의존하는 공유 라이브러리를 나열합니다.
    • 중요성: 프로그램의 외부 의존성을 파악하고, 필요한 라이브러리가 시스템에 존재하는지 확인할 수 있습니다.
    • 예시: ldd a.out → "linux-vdso.so.1 => (0x00007fff5ebac000), libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f363ff3e000), ..."
  4. xxd 명령어

    • 사용법: xxd [파일명]
    • 기능: 파일의 16진수 덤프와 ASCII 표현을 동시에 보여줍니다.
    • 용도: 바이너리 데이터의 구조를 바이트 단위로 검사할 수 있습니다.
    • 예시: xxd a.out | head → "0000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............"
  5. readelf 명령어

    • 사용법: readelf [옵션] [ELF 파일명]
    • 기능: ELF (Executable and Linkable Format) 파일의 내부 구조를 분석합니다.
    • 주요 옵션:
      • -h: ELF 헤더 정보
      • -l: 프로그램 헤더 정보
      • -S: 섹션 헤더 정보
      • -s: 심볼 테이블
    • 예시: readelf -h a.out → ELF 헤더 정보 출력
  6. nm 명령어

    • 사용법: nm [옵션] [객체 파일 또는 실행 파일]
    • 기능: 바이너리 파일의 심볼 정보를 나열합니다.
    • 주요 옵션:
      • -D: 동적 심볼만 표시
      • --demangle: C++ 심볼 이름을 원래 형태로 복원
    • 예시: nm -D --demangle a.out → "0000000000201010 B __bss_start, U free@@GLIBC_2.2.5, ..."
  7. strace와 ltrace

    • strace: 시스템 콜 추적
      • 사용법: strace [옵션] [명령어]
      • 예시: strace ./a.out → "execve("./a.out", ["./a.out"], [/ 22 vars /]) = 0, ..."
    • ltrace: 라이브러리 함수 호출 추적
      • 사용법: ltrace [옵션] [명령어]
      • 예시: ltrace ./a.out → "printf("Hello, World!") = 13, ..."
  8. objdump 명령어

    • 사용법: objdump [옵션] [파일명]
    • 기능: 객체 파일이나 실행 파일의 다양한 정보를 표시합니다.
    • 주요 옵션:
      • -d: 어셈블리 코드 역어셈블
      • -h: 섹션 헤더 정보
      • -t: 심볼 테이블
    • 예시: objdump -d a.out → 어셈블리 코드 출력

정적 분석 vs 동적 분석

정적 분석

  • 정의: 프로그램을 실행하지 않고 코드를 분석하는 기법
  • 장점:
    1. 전체 코드 커버리지 가능
    2. 악성코드 분석 시 안전
    3. 리소스 효율적
  • 단점:
    1. 난독화된 코드 분석 어려움
    2. 런타임 동작 예측의 한계

동적 분석

  • 정의: 프로그램을 실행하면서 동작을 관찰하고 분석하는 기법
  • 장점:
    1. 실제 동작 확인 가능
    2. 난독화, 암호화된 코드 분석에 유리
    3. 런타임 환경의 영향 고려 가능
  • 단점:
    1. 모든 실행 경로 테스트 어려움
    2. 악성코드 분석 시 위험 가능성
    3. 리소스 집약적

고오급 분석 기법

1. Taint Analysis (오염 분석)

: 프로그램 실행 중 데이터의 흐름을 추적하여 입력이 프로그램의 다른 부분에 어떻게 영향을 미치는지 분석하는 기법 ( 발자국 찾아가는 느?낌)

  • 주요 개념:
    1. Taint Source: 오염의 시작점 (예: 사용자 입력)
    2. Taint Sink: 오염이 도달하는 종점 (예: 시스템 함수 호출)
    3. Taint Propagation: 오염의 전파 규칙
  • 도구 예시: libdft (Intel Pin 기반의 동적 오염 분석 프레임워크)
  • 응용: 버퍼 오버플로우, SQL 인젝션 등의 취약점 탐지

2. Symbolic Execution (심볼릭 실행)

: 프로그램의 입력을 구체적인 값 대신 심볼로 표현하여 실행 경로를 탐색하는 기법

  • 목적: 특정 프로그램 상태나 영역에 도달할 수 있는 입력 조건 탐색
  • 핵심 요소: 제약 조건 해결기 (Constraint Solver)
  • 도구 예시: KLEE, angr
  • 응용: 자동화된 테스트 케이스 생성, 버그 탐지

3. 메모리 오류 탐지 도구

  • Address Sanitizer: 메모리 접근 오류를 탐지하는 컴파일러 기반 도구
    • 사용법: gcc나 clang에서 -fsanitize=address 옵션 사용
  • Valgrind: 동적 바이너리 계측(Instrumentation) 프레임워크
    • 기능: 메모리leak, 경쟁 조건,caeche 사용 등 다양한 문제 탐지
    • 사용법: valgrind [옵션] [실행 파일]

분석 방해 기법🏴‍☠️

리버스 엔지니어링을 어렵게 만들기 위해 사용하는 기법들

1. 난독화 (Obfuscation): 코드나 데이터를 읽기 어렵게 변형하는 기법

  • 방법:
    • 제어 흐름 난독화: 불필요한 분기문 추가, 코드 재배치 ...etc
    • 데이터 난독화: 문자열 암호화, 상수 변환 등
    • 디컴파일 방지: 유효하지 않은 바이트코드 삽입
  • 도구 예시: ProGuard (Java), Dotfuscator (C#)

2. 패킹 (Packing): 실행 파일을 압축하거나 암호화하는 기법

  • 목적: 파일 크기 감소, 리버스 엔지니어링 방해
  • 동작 원리:
    1. 원본 코드 압축/암호화 - 헤더를 망가트리기도 함
    2. 언패커(Unpacker) 코드 추가
    3. 실행 시 메모리에서 원본 코드 복원 후 실행
  • 도구 예시: UPX, ASPack, PECompact

3. 안티 디버깅 (Anti-Debugging): 디버거의 사용을 감지하고 방해하는 기법

  1. API 사용: IsDebuggerPresent(), CheckRemoteDebuggerPresent() 등
  2. PEB (Process Environment Block) 검사: BeingDebugged 플래그 확인
  3. 타이밍 체크: 디버깅 시 실행 시간 변화 감지
  4. 예외 처리: 의도적인 예외 발생 후 처리 관찰
  • 대응 방법: 디버거 감지 루틴 우회, 가상화 환경 사용

4. 코드 가상화 (Code Virtualization)

  • 정의: 원본 코드를 가상 머신 위에서 실행되는 바이트코드로 변환하는 기법
  • 동작 원리:
    1. 원본 코드를 커스텀 가상 머신의 명령어로 변환
    2. 실행 파일에 가상 머신 인터프리터 포함
    3. 런타임에 가상 머신이 변환된 코드 실행
  • 장점: 매우 강력한 난독화 효과
  • 단점: 성능 저하, 파일 크기 증가
  • 도구 예시:
    • VMProtect: 파일 섹션 랜덤화, vmp0, vmp1 등의 특이한 섹션 명 사용
    • Themida: 고급 패킹 및 가상화 기술을 결합한 강력한 보호 도구. 다중 레이어 암호화와 안티-디버깅 기능


바이너리 분석을 하려면 알아야 하는 지식

( 내가 공부하려고 적어놓는 것)

1. 아키텍처 및 각 특징

  • PPC (PowerPC): IBM과 Motorola가 개발한 RISC 기반 아키텍처
  • ARM: 모바일 기기와 임베디드 시스템에서 널리 사용되는 RISC 아키텍처
  • RISC-V: 오픈 소스 ISA(Instruction Set Architecture)로, 최근 주목받고 있는 아키텍처
  • MIPS: 마이크로프로세서 아키텍처의 한 종류로, 임베디드 시스템에서 많이 사용됨

각 아키텍처별 특징:

  • 레지스터 구조와 개수
  • 명령어 세트와 인코딩 방식
  • 메모리 주소 지정 모드
  • 인터럽트 및 예외 처리 메커니즘

2. 디버깅 도구 사용법

  • GDB (GNU Debugger):
    • 기본 사용법: 브레이크포인트 설정, 메모리/레지스터 조회, 스택 추적 등
    • 고급 기능: 스크립팅, 원격 디버깅, 역방향 디버깅 등
  • WinDbg (Windows Debugger):
    • 커널 모드 및 유저 모드 디버깅
    • 덤프 파일 분석
    • 확장 모듈 사용법
  • IDA freeware(Interactive Disassembler):
    • 정적 분석 기능
    • 스크립팅
    • 사용자 정의 프로세서 모듈 개발
  • Ghidra:
    • NSA에서 개발한 오픈소스 리버스 엔지니어링 도구
    • 디컴파일러, 어셈블러, 분석 도구 등 제공

3. 분석 방해 기법 파악 및 우회법 학습

  • 난독화 우회
  • 패킹 탐지 및 언패킹
  • 안티 디버깅 우회

4. 고급 분석 기법 심화 학습

  • Symbolic Execution
  • Taint Analysis
  • Fuzzing

5. 여러 분석 보고서 학습

  • CVE 분석 보고서
  • 보안 업체의 APT 분석 보고서

0개의 댓글