| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- 함수 호출 규약
- Rich Header
- __stdcall
- 프로그래머스
- __fastcall
- 실행파일
- crackme
- Programmers
- rev
- Calling Convention
- image section header
- Image dos header
- stack frame
- __vectorcall
- RVA
- 리치헤더
- Reversing
- ABI
- 파이썬
- CodeEngn
- 리버싱
- 크랙미
- Python
- x64
- __cdecl
- 32bit
- x32
- pe format
- Dos Stub
- 코드엔진
- Today
- Total
kj0on
[Definition] RVA to RAW 본문
0. VA & RVA & RAW
VA, RVA, RAW에 대한 개념은 https://kj0on.tistory.com/29 참고
1. RVA to RAW 변환의 필요성
RVA to RAW 변환은 PE 파일을 메모리에 로드하지 않고 디스크 상의 파일 자체의 구조를 확인하거나 값을 수정할 때(RVA를 실제 디스크 파일 내 위치로 바꿔야 할 때) 사용된다. 특히 헤더나 디렉터리 정보들은 대부분 RVA로 표현된다. 파일 자체를 디스크 상에서 다루는 작업을 할때 해당 데이터가 실제로 파일 내 어느 위치에 있는지를 알아야 하므로 RVA를 RAW(파일 오프셋)로 변환해야 한다.
1-1. 윈도우 로더
윈도우 로더는 PE 파일을 실행할 때 RVA를 RAW로 변환하지 않는다. 각 섹션의 PointerToRawData와 VirtualAddress를 기반으로 디스크에서 데이터를 읽어 메모리의 해당 VA 위치에 직접 복사하는 방식으로 로딩을 수행한다. 또한 데이터 디렉터리는 실행 중 메모리에 로드된 섹션 내부에 포함되며 해당 위치는 RVA로 표현된다. 디렉터리의 RVA는 섹션의 RVA에 맞춰 작성되어 있기 때문에 VA를 계산할 수 있고 디렉터리 구조체에 메모리 상에서 직접 접근할 수 있게 된다. 따라서 RVA to RAW 변환은 실행이 아닌 정적 분석이나 디스크 상의 수동 수정 작업에서만 필요한 과정이다.
2. 비례식
RAW - PointerToRawData = RVA - VitualAddress
RAW = RVA - VirtualAddress + PointerToRawData
여기서 VirtualAddress는 IMAGE_SECTION_HEADER의 VirtualAddress(RVA)다. 프로세스 내부 기준 절대 주소를 의미하지 않는다.

식을 좀 더 정확하게 표현하면 위와 같다. 이 식에서 사용하는 VirtualAddress와 PointerToRawData는 IMAGE_SECTION_HEADER 구조체의 멤버이며 이는 섹션 헤더에 정의된 여러 섹션 중 알고자 하는 값이 속해있는 섹션의 헤더에서 가져온 값들에 해당한다.
3. 원리

원리를 이해하기 위해서는 먼저 위 식이 나타내는 의미를 제대로 이해할 필요가 있다.
3-1. RVA - VirtualAddress

특정 RVA값을 알고 있다고 가정한다. 이 값을 앞서 설명한 비례식에 대입해 RAW 오프셋을 구하려는 상황이다. 해당 RVA는 .text 섹션에 속해 있으므로 비례식에 사용되는 PointerToRawData와 VirtualAddress는 IMAGE_SECTION_HEADER 구조체 중 .text 섹션의 멤버 값을 사용해야 한다.

IMAGE_SECTION_HEADER.VirtualAddress는 PE 파일 실행 시 .text 섹션이 메모리에 적재될 때의 RVA를 의미한다. 즉, ImageBase를 기준으로 .text 섹션이 어느 위치에서 부터 메모리에 배치될지를 나타낸다.

RVA에서 VirtualAddress를 뺀다는 것은 기준점을 ImageBase가 아니라 해당 섹션의 시작 위치로 옮긴다는 의미다. 특정 섹션 내부에서 해당 RVA가 얼마나 떨어져 있는지를 나타낸 값이라 할 수 있다. 해당 RVA가 .text 섹션에 포함되어 있기 때문에 이 값은 .text 섹션의 시작 주소를 기준으로 얼마나 떨어져 있는지를 나타내며 섹션 내부에서의 상대적 위치(RVA)를 의미한다.
3-2. RAW - PointerToRawData

RAW 오프셋은 파일상에서의 실제 위치를 의미한다. RVA to RAW를 통해 구하고자 하는 값이다. 처음에 가정한 RVA값이 .text 섹션에 있기 때문에 RAW 또한 .text 섹션에 포함되어 있다고 생각한다.

IMAGE_SECTION_HEADER.PointerToRawData는 PE 파일 내에서 .text 섹션의 데이터가 파일상에서 시작하는 오프셋(RAW Offset)을 의미한다. 파일 상에서 PE 파일의 시작을 기준으로 .text 섹션의 데이터가 어느 위치부터 저장되어 있는지를 나타낸다.

RAW에서 PointerToRawData를 뺀다는 것은 기준점을 파일의 시작에서 해당 섹션이 시작하는 위치로 옮긴다는 의미다. 이는 RAW가 가리키는 특정 데이터가 해당 섹션의 시작 기준으로 얼마나 떨어져 있는지를 나타내는 값이다.
3-3. Equal

이 두 값이 같다는 의미는 처음 주어진 RVA 값을 기준으로, 메모리 상에서 .text 섹션 내부에서의 위치와 디스크 상에서 .text 섹션 내부에서의 위치가 동일한 간격을 가지고 있다는 뜻이다. 해당 데이터는 메모리에 로드되었을 때나 디스크 상에 존재할 때나 모두 .text 섹션의 시작 지점을 기준으로 같은 거리만큼 떨어져 있다는 것이다. 따라서 비례식을 쉽게 풀어보면 "RVA가 섹션의 시작 위치로부터 메모리에서 이 정도 떨어져 있으니 파일에서도 섹션 시작 지점으로부터 이만큼 떨어져 있겠지" 라는 의미가 된다.
3-4. 비례식이 성립하는 이유

RVA to RAW 비례식이 성립하는 이유는 PE 파일의 각 섹션이 디스크와 메모리에서 동일한 순서와 구조로 배치되기 때문이다. 이를 확인하기 위해 파일 상의 섹션과 메모리에 로드된 섹션을 비교하여 그 일치 여부를 분석한다.
3-4-1. 작업 환경

동일한 PE 파일을 대상으로 파일에 존재하는 섹션은 010 Editor를 사용해 추출하고 메모리에 로드된 섹션은 Process Hacker 2를 통해 추출한다. 각각의 섹션을 분리하여 파일로 저장한 뒤 디스크와 메모리 상의 구조가 동일한지 비교한다.

.reloc 섹션은 실행 중 사용되지 않거나 메모리에 로드되지 않는 경우가 많기 때문에 .reloc 섹션을 제외한 .text 섹션, .rdata 섹션, .data 섹션, .rsrc 섹션을 비교한다.

비교는 010Editor의 Compare Files 기능을 통해 진행한다.
3-4-2. .text 섹션

.text 섹션은 CPU가 실행할 기계어 코드가 담긴 영역이다. 비교 결과 파일상의 .text 섹션과 메모리에 로드된 .text 섹션의 크기와 데이터 내용 모두 일치했다.
3-4-3. .rdata 섹션

.rdata 섹션은 변경되지 않는 상수나 테이블처럼 읽기 전용 데이터가 모여 있는 곳이다. 비교 결과 파일상의 .rdata 섹션과 메모리에 로드된 .rdata 섹션의 전체적인 구조가 일치했다. 빨간색으로 표시된 영역은 값이 달라진 부분, 노란색 영역은 실행 시 패딩되어 채워진 부분을 나타낸다.

일부 값이 달라진 빨간색 영역은 실행 시 IAT(Import Address Table)에 의해 실제 함수 주소로 덮어써진 부분이다. 이처럼 데이터 값에는 차이가 있을 수 있지만 섹션의 구성과 배치 구조가 그대로 유지된다는 점이 중요하다.
3-4-4. .data 섹션

.data 섹션은 초기값이 있는 전역, 정적 변수가 저장되는 영역이다. 비교 결과 파일상의 .data 섹션과 메모리에 로드된 .data 섹션의 전체적인 구조가 일치했다.

일부 값은 프로그램의 동작에 따라 실행 중 변수 값이 변경되면서 차이를 보였지만 중요한 점은 이러한 값의 변화와 관계없이 .data 섹션의 전체 구조가 파일과 메모리에서 일관되게 유지된다는 것이다.
3-4-5. .rsrc 섹션

.rsrc 섹션은 아이콘, 비트맵, 대화상자처럼 프로그램 리소스가 트리 구조로 저장된 구역이다. 비교 결과 패딩을 제외하고, 파일상의 .rsrc 섹션과 메모리에 로드된 .rsrc 섹션의 데이터 내용이 일치했다.
3-4-6. 결론

RVA to RAW 변환식이 성립하는 이유는 PE 파일의 각 섹션이 디스크에 저장된 구조 그대로 메모리에 로드되기 때문이다. 윈도우로더는 IMAGE_SECTION_HEADER에 정의된 VirtualAddress와 PointerToRawData를 기준으로 파일 상의 섹션 데이터를 메모리의 정확한 위치에 복사한다. 이때 섹션 내부의 배치가 그대로 유지되기 때문에 특정 RVA가 섹션 시작 위치로부터 메모리에서 어느 정도 떨어져 있다면 디스크 상에서도 해당 섹션의 시작 지점으로부터 동일한 거리만큼 떨어져 있을 것이라는 가정이 성립한다. 즉, "RVA가 섹션의 시작 위치로부터 메모리에서 이 정도 떨어져 있으니 파일에서도 섹션 시작 지점으로부터 이만큼 떨어져 있겠지" 라는 비례 관계가 가능해지는 것이다.
4. RVA to RAW 비례식이 성립하지 않을 때

파일과 메모리에서 Raw Offset과 Vitual Address가 위와 같다. RVA가 0x2D00일 경우 RVA to RAW를 적용해 보자.

RVA가 .rdata 섹션에 속해 있기 때문에 해당 RVA는 .rdata 섹션의 시작 지점을 기준으로 0xD00만큼 떨어진 위치에 해당한다. 비례식의 원리에 따라 디스크 상의 RAW 오프셋 또한 .rdata 섹션의 시작 위치를 기준으로 동일하게 0xD00만큼 떨어져 있어야 한다.

디스크에서 .rdata 섹션의 시작 위치를 기준으로 0xD00을 더해 RAW 오프셋을 계산해보면 결과값은 0x2100이 된다. 그러나 이 오프셋은 .data 섹션의 범위에 속하므로 RVA가 속한 섹션과 RAW가 속한 섹션이 서로 일치하지 않게 된다. 해당 경우에는 RVA to RAW 비례식이 성립하지 않는다.

이와 같은 오류가 발생하는 이유는 .rdata 섹션의 파일 상 크기와 메모리 상 크기가 다르기 때문이다. PE 파일은 디스크에서는 FileAlignment 단위로 정렬되고 메모리에서는 SectionAlignment 단위로 정렬된다. 따라서 File에서 .rdata 섹션의 크기는 SizeOfRawData인 0xC00이고, 메모리에서 .rdata 섹션의 크기는 Misc.VirtualSize를 SectionAlignment로 라운드 업 한 크기인 0x1000이다. (SizeOfRawData의 경우 FileAlignment로 라운드 업 되어 있으며 Misc.VirtualSize는 그렇지 않다.)

PE 파일이 실행되면 .rdata 섹션은 디스크에 저장된 구조 그대로 메모리에 복사되며 이때 SizeOfRawData만큼의 실제 데이터가 로드된다. 만약 VirtualSize(SectionAlignment 단위 적용)가 SizeOfRawData보다 크다면 남는 영역은 윈도우로더에 의해 자동으로 0으로 채워진다.

RVA 0x2D00 값은 메모리에 로드된 .rdata 섹션에서 실제 데이터 영역이 아닌 0으로 채워진 패딩 영역에 해당한다. RVA가 .rdata 섹션의 실제 데이터가 존재하는 경계를 넘어서는 위치를 가리키고 있기 때문에 디스크에서 .rdata 섹션의 범위를 넘어가는 것이다. 따라서 RVA to RAW 변환이 성립하지 않게 된다.

RVA는 메모리에 로드된 각 섹션의 시작 주소를 기준으로 해당 섹션의 실제 크기인 VirtualSize까지의 범위에서만 유효하다. 이 범위를 초과하는 RVA는 해당 섹션에 속하지 않으며 더 이상 유효한 주소로 간주되지 않는다. 또한 VirtualSize 내에 있더라도 그 RVA가 SizeOfRawData를 초과하는 위치에 있다면 해당 영역은 디스크에 존재하지 않는다. 따라서 RVA가 0으로 패딩된 메모리에만 존재하는 공간을 가리킨다면 메모리 상에는 존재하지만 디스크 상에서는 해당 섹션의 범위를 초과한 위치이므로 RAW 오프셋을 정확히 대응시킬 수 없다. 따라서 이 상태에서 RVA to RAW 변환을 시도하면 섹션의 파일 내 범위를 넘어선 잘못된 오프셋이 계산된다.
4-1. 요약
RVA가 가리키는 곳이 섹션 내의 실제 데이터가 존재하는 영역이면 RVA to RAW 비례식이 성립하고, 0패딩의 영역이라면 성립하지 않는다는 것이다. 이때 말하는 0패딩은 메모리 상에서만 존재하는 0패딩을 의미한다. 좀 더 정확하게 표현하자면 SectionAlignment로 라운드 업 된 섹션의 VirtualSize가 SizeOfRawData보다 클 때 채워지는 0패딩이다. 파일상에서도 존재하는 0패딩을 의미하지는 않는다.
'Reversing > Definition' 카테고리의 다른 글
| [Definition] 32비트 스택 프레임 (32bit Stack Frame) (0) | 2025.07.13 |
|---|---|
| [Definition] 컴파일 (Compilation) (0) | 2025.07.10 |
| [Definition] VA & RVA & RAW (0) | 2025.07.06 |
| [Definition] Rich Header (3) | 2025.07.05 |
| [Definition] 64비트 윈도우 실행 파일 구조 (PE+ Format) (0) | 2025.07.04 |