리버싱 = 역공학
Good
- 개발 중단 프로그램 패치 시.
- 보안성 평가, 악성코드 분석
Bad
- 크랙, 키젠 프로그램, 시리얼 넘버 생성기 -> 유료 프로그램이 어떤 방식으로 정품 인증을 하는지 알아냄.
- 게임핵
기계어 : 0과 1로 이루어짐. -> 인간이 해석하기 어려움.
어셈블리어(Assembly Language), 어셈블러(Assembler) : 사람들이 이해하기 쉬운 언어로 어셈블리어 고안, 어셈블러는 어셈블리어를 기게어로 번역함.
-> 단점 : 규모가 큰 프로그램 개발 어려움.
컴파일러와 고급언어 : 어셈블리어보다 더 프로그램 개발 효율을 높임
저급 언어 : 기게어, 어셈블리어
고급 언어 : C, C++, Go, Rust 등
프로그램 : 연산 장치가 수행해야 하는 동작을 정의한 일종의 문서.
프로그램이 연산장치에 전달되면 그 내부의 CPU가 프로그램이 명령한 동작을 수행함.
Programmable 연산 장치 = 컴퓨터
non-programmable 연산 장치 = 일반 계산기
과거 : 천공 카드(Punched Card)에 프로그램을 기록하여 재사용 또는 사람이 전선을 연결.
ex) 에니악
Stored-Program Computer : 1950년 경. 프로그램을 메모리에 전자적, 광학적 저장. -> 현재 대부분 컴퓨터의 형태
바이너리(binary) : 엔지니어들이 프로그램을 바이너리라고 부름. SPC에서 프로그램이 저장 장치에 이진형태로 저장되기 때문임. 텍스트가 아닌 다른 데이터들도 바이너리라고 불리긴 하지만, 많은 경우에 바이너리라고 하면 프로그램을 의미함.
소스 코드(source code) : CPU가 수행해야 할 명령들을 프로그래밍 언어로 작성한 것.
컴파일(compile) : 소스코드를 기게어로 번역.
컴파일러(compiler) : 컴파일을 해주는 소프트웨어 -> GCC, Clang, MSVC 등
-> 아무 배경지식이 없는 사람이 책을 읽을 수 있도록 배경지식을 엮고, 번역하여 하나의 번역본을 만드는 과정.
인터프리터(interpreter) : 자바 스크립트와 파이썬은 컴파일러 사용 x. 동시통역사 같은 개념
*드림핵 에듀케이션 플랜에서 리버싱 커리큘럼을 듣고 있음.
컴파일 과정 : 전처리 - 컴파일 - 어셈블 - 링크 (C언어로 작성된 코드가 바이너리로 번역되는 과정)
리눅스 환경 GCC 사용
전처리(Preprocessing) : 컴파일 전에 필요한 형식으로 가공하는 과정
1. 주석 제거
프로그램의 동작과 상관이 없으므로 전처리 단게에서 모두 제거됨.
2. 매크로 치환
#define으로 정의한 매크로는 자주 사용하는 코드나 상숫값을 단어로 정의한 것. 전처리 과정에서 매크로의 이름은 값으로 치환됨.
3. 파일 병합
일반적인 프로그램은 여러 개의 소스와 헤더 파일로 이루어짐. 따로 컴파일해서 합치기도 하고, 전처리 단계에서 파일을 합치고 컴파일하기도 함.
-E : 전처리 결과를 화면에 출력.
컴파일 : c로 작성된 소스 코드를 어셈블리어로 번역하는 과정.
소스코드의 문법을 검사하고 문법적 오류가 있다면 컴파일을 멈추고 에러를 출력함.
또, 코드 번역 시 몇몇 조건을 만족하면 최적화 기술을 적용하여 효율적인 어셈블리 코드를 생성해줌.
-O, -O0, -O1, -O2, -O3, -Os -Ofast, -Og 등의 최적화 옵션
위의 opt.c를 최적화하여 컴파일하면, 컴파일러는 반복문을 어셈블리어로 옮기는 것이 아니라, 반복문의 결과로 x가 가질 값을 직접 계산하여 이를 대입하는 코드를 생성한다.
-> 사용자가 작성한 소스 코드와 연산 결과는 같으면서도, 최적화를 적용하면 더 짧고, 실행 시간도 단축되는 어셈블리 코드가 만들어지게 된다.
-S 옵션을 활용하면 소스코드를 어셈블리 코드로 컴파일 할 수 있다.
gcc -S add.i -o add.S
어셈블(Assemble) : 컴파일로 생성된 어셈블리어 코드를 ELF 형식의 목적 파일(Object File)로 변환하는 과정.
어셈블리 코드 -> 기계어, 실행 가능한 형식 변형.
*ELF = 리눅스의 실행파일 형식. (윈도우에서 어셈블 -> PE 형식)
목적 파일로 변환되고 나면 어셈블리 코드가 기계어로 번역되므로 더이상 사람이 해석하기 어려워진다.
-c 옵션으로 add.S를 목적 파일로 변환하고, 결과로 나온 파일을 16진수로 출력한다.
링크(Link) : 여러 오브젝트 파일들을 연결하여 실행 가능한 바이너리(프로그램)로 만드는 과정
위 코드에서 printf 함수를 호출함. 이 함수의 정의는 hello-world.c 에 없고, libc라는 공유 라이브러리에 존재함.
libc는 gcc의 기본 라이브러리 경로에 있는데, 링커는 바이너리가 printf를 호출하면 libc의 함수가 실행될 수 있도록 연결해줌. 링크를 거치고 나면 실행할 수 있는 프로그램이 완성됨.
링크 과정에서 링커는 main 함수를 찾는데, add의 소스 코드에는 main 함수의 정의가 없으므로 에러가 발생할 수 있음.
이를 방지하기 위해 --unresolved-symbols를 컴파일 옵션에 추가함.
디스어셈블(Disassemble) : 어셈블의 역과정
바이너리를 분석하려면 바이너리를 읽을 수 있어야 하는데 컴파일된 프로그램의 코드는 기계어로 작성되어 있음 -> 이해 어려움.
-> 기계어를 어셈블리어로 재번역.
디컴파일(Decompile) : 어셈블리어보다 고급 언어로 바이너리를 번역.
어셈블리어와 기계어는 거의 일대일 대응 -> 디스어셈블러 오차 X.
고급 언어와 어셈블리어 대응 관계 X.
- 코드 작성 시 사용한 함수, 변수의 이름 등 컴파일 과정에서 사라짐.
- 최적화로 코드 일부분 완전 변형.
-> 디컴파일러는 일반적으로 바이너리의 소스 코드와 동일한 코드 생성 못함.
디컴파일러 종류 : IDA Freeware, Hex Rays, Ghidra
'Dreamhack' 카테고리의 다른 글
리버싱 06 - rev-basic-0 (0) | 2022.07.12 |
---|---|
리버싱 05 - x86 Assembly (0) | 2022.07.12 |
리버싱 04 - Windows Memory Layout (0) | 2022.07.10 |
리버싱 03 - 컴퓨터 구조, 명령어 집합 구조 (0) | 2022.07.10 |
리버싱 02 - 동적 분석과 정적 분석 (0) | 2022.07.10 |