바이너리 파일이란?🗒
바이너리 파일은 0과 1로 이루어진 데이터의 집합입니다.
컴퓨터의 언어로 이루어진 파일이요.
모든 디지털 파일은 근본적으로 바이너리 형태이지만, 일반적으로 '바이너리'라고 할 때는 실행 가능한 프로그램 파일을 지칭합니다.
소스 코드에서 실행 파일까지 🚣
- 소스 코드 작성: ex)C 언어로
main.c 파일을 작성합니다.
- 컴파일:
gcc -c main.c와 같은 명령어로 객체 파일(main.o)로 변환합니다. 이 과정에서 고수준 언어(c언어)가 기계어로 변환되지만, 아직 실행 가능한 형태는 아닙니다.
- 링킹:
gcc main.o -o a.out와 같은 명령어로 객체 파일을 실행 가능한 바이너리(a.out)로 변환합니다. 이 과정에서 여러 객체 파일과 라이브러리가 하나의 실행 파일로 결합됩니다.
바이너리 분석의 필요성
- 소스 코드 부재: 대부분의 상용 소프트웨어는 소스 코드를 공개하지 않습니다.
- 악성코드 분석: 바이러스나 멀웨어의 동작을 이해하고 대응책을 마련하기 위해
- 취약점 연구: 소프트웨어의 보안 취약점을 찾아 패치하거나 익스플로잇을 개발하기 위해
- 성능 최적화: 컴파일된 코드의 성능을 분석하고 개선하는 데 활용됩니다.
초도 분석📈
: 바이너리에 대한 기본적인 정보를 수집하는 과정
-
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..."
-
strings 명령어
- 사용법:
strings [파일명]
- 기능: 바이너리 내의 인쇄 가능한 문자열을 추출합니다.
- 용도: 하드코딩된 문자열, 메시지, URL 등을 식별하는 데 유용합니다.
- 예시:
strings a.out → "Hello, World!", "/usr/lib/", "malloc error" 등의 문자열 출력
-
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), ..."
-
xxd 명령어
- 사용법:
xxd [파일명]
- 기능: 파일의 16진수 덤프와 ASCII 표현을 동시에 보여줍니다.
- 용도: 바이너리 데이터의 구조를 바이트 단위로 검사할 수 있습니다.
- 예시:
xxd a.out | head → "0000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............"
-
readelf 명령어
- 사용법:
readelf [옵션] [ELF 파일명]
- 기능: ELF (Executable and Linkable Format) 파일의 내부 구조를 분석합니다.
- 주요 옵션:
-h: ELF 헤더 정보
-l: 프로그램 헤더 정보
-S: 섹션 헤더 정보
-s: 심볼 테이블
- 예시:
readelf -h a.out → ELF 헤더 정보 출력
-
nm 명령어
- 사용법:
nm [옵션] [객체 파일 또는 실행 파일]
- 기능: 바이너리 파일의 심볼 정보를 나열합니다.
- 주요 옵션:
-D: 동적 심볼만 표시
--demangle: C++ 심볼 이름을 원래 형태로 복원
- 예시:
nm -D --demangle a.out → "0000000000201010 B __bss_start, U free@@GLIBC_2.2.5, ..."
-
strace와 ltrace
- strace: 시스템 콜 추적
- 사용법:
strace [옵션] [명령어]
- 예시:
strace ./a.out → "execve("./a.out", ["./a.out"], [/ 22 vars /]) = 0, ..."
- ltrace: 라이브러리 함수 호출 추적
- 사용법:
ltrace [옵션] [명령어]
- 예시:
ltrace ./a.out → "printf("Hello, World!") = 13, ..."
-
objdump 명령어
- 사용법:
objdump [옵션] [파일명]
- 기능: 객체 파일이나 실행 파일의 다양한 정보를 표시합니다.
- 주요 옵션:
-d: 어셈블리 코드 역어셈블
-h: 섹션 헤더 정보
-t: 심볼 테이블
- 예시:
objdump -d a.out → 어셈블리 코드 출력
정적 분석 vs 동적 분석
정적 분석
- 정의: 프로그램을 실행하지 않고 코드를 분석하는 기법
- 장점:
- 전체 코드 커버리지 가능
- 악성코드 분석 시 안전
- 리소스 효율적
- 단점:
- 난독화된 코드 분석 어려움
- 런타임 동작 예측의 한계
동적 분석
- 정의: 프로그램을 실행하면서 동작을 관찰하고 분석하는 기법
- 장점:
- 실제 동작 확인 가능
- 난독화, 암호화된 코드 분석에 유리
- 런타임 환경의 영향 고려 가능
- 단점:
- 모든 실행 경로 테스트 어려움
- 악성코드 분석 시 위험 가능성
- 리소스 집약적
고오급 분석 기법
1. Taint Analysis (오염 분석)
: 프로그램 실행 중 데이터의 흐름을 추적하여 입력이 프로그램의 다른 부분에 어떻게 영향을 미치는지 분석하는 기법 ( 발자국 찾아가는 느?낌)
- 주요 개념:
- Taint Source: 오염의 시작점 (예: 사용자 입력)
- Taint Sink: 오염이 도달하는 종점 (예: 시스템 함수 호출)
- 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): 실행 파일을 압축하거나 암호화하는 기법
- 목적: 파일 크기 감소, 리버스 엔지니어링 방해
- 동작 원리:
- 원본 코드 압축/암호화 - 헤더를 망가트리기도 함
- 언패커(Unpacker) 코드 추가
- 실행 시 메모리에서 원본 코드 복원 후 실행
- 도구 예시: UPX, ASPack, PECompact
3. 안티 디버깅 (Anti-Debugging): 디버거의 사용을 감지하고 방해하는 기법
- API 사용: IsDebuggerPresent(), CheckRemoteDebuggerPresent() 등
- PEB (Process Environment Block) 검사: BeingDebugged 플래그 확인
- 타이밍 체크: 디버깅 시 실행 시간 변화 감지
- 예외 처리: 의도적인 예외 발생 후 처리 관찰
- 대응 방법: 디버거 감지 루틴 우회, 가상화 환경 사용
4. 코드 가상화 (Code Virtualization)
- 정의: 원본 코드를 가상 머신 위에서 실행되는 바이트코드로 변환하는 기법
- 동작 원리:
- 원본 코드를 커스텀 가상 머신의 명령어로 변환
- 실행 파일에 가상 머신 인터프리터 포함
- 런타임에 가상 머신이 변환된 코드 실행
- 장점: 매우 강력한 난독화 효과
- 단점: 성능 저하, 파일 크기 증가
- 도구 예시:
- 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 분석 보고서