[2025.05.10] 리버싱난다_4주차 활동
참고 강의 - 유튜브 Fin 리버싱 강의 (15 ~ 17강). RVA To RAW, IAT(1,2)
RVA To RAW
- 3회차 Section Header 설명
- 각 섹션의 속성을 정의 한 것이 섹션 헤더로, 섹션의 속성이나 권한을 관리
- 섹션 ? 부분마다 파트가 있다 정도로 생각
- code, data, rsrc(리소스) 3가지로 나누어서 섹션을 저장하는 이유가 뭘까?
- 프로그램 복잡함 감소
- 프로그램 안전성
섹션헤더에 대해 잘 이해했다면 PE 파일이 메모리에 로딩되었을 때
각 섹션에서 메모리의 주소(RVA)와 Offset을 잘 매핑할 수 있어야 한다.
이런 매핑을 일반적으로는 RVA TO RAW라고 부른다
→ RVA TO RAW : PE파일이 메모리에 로딩되었을 때, 각 섹션에서 메모리 주소(RVA)와 File Offset(RAW)을 매핑하는 것
[ 단어 정리 ]
Offset - 기준점에서 떨어진 정도(오차), 오버헤드랑 같은 의미
매핑 - 하나의 값을 다른 값으로 대응시키는 것
로딩 - 대응시키는 걸 실제로 하는 것
PointerToRawData - 파일에서 섹션 시작 위치
VA(Virtual Address) - 메모리에서 섹션이 차지하는 크기
RVA - 상대주소
[ 문제풀기 ]
- 사진참고File PE header, Memory PE header임
- 화살표는 주소가 이동한다는 걸로 보면됨
- RVA = 3000 일 때 RAW는 얼마인가?
- RVA가 속한 섹션 찾기(파일이든 메모리든 상관 x)
- 16진수 계산기 켜기
- 16진수는 큰 수 - 작은 수 = 오프셋 값이 나옴
- 작은 수 - 큰 수 = 오버플로우 같이 FFFF처럼 나옴
- 오프셋이나 VA를 주어진 주소에서 뺐을 때(여기서는 3000-주소) 오프셋에서 FFFF로 넘어가는 그 사이가 RVA가 속한 섹션이다
- 공식을 이용해서 RAW(File Offset) 계산하기
- PointerToRawData = 00000400
- VA(Virtual Address) = 01001000ImageBase = 01000000RVA + ImageBase = VA(절대주소)와는 다르다는 점
- → VA - ImageBase = 1000
- 대신 이미지 베이스를 고려해줘야 함
- RAW = 3000-1000+400 = 2400
- RVA가 속한 섹션 찾기(파일이든 메모리든 상관 x)
- RVA = BC123 일 때 RAW는 얼마인가?
- .reloc에 존재
- VA - ImageBase = BC000BC123 - BC000 + B9C00 = B9D23
- PointerToRawDate = B9C00
- RVA = ABA8 일 때 RAW는 얼마인가?
- section(.text)
- VA - ImageBase = 1000ABA8 - 1000 + 400 = 9FA8
- → 메모리에서 포함되었던 섹션이 아니거나 벗어나는 건 말이 되지 않기 때문에, RVA에 대한 RAW를 정의할 수 없다고 한다.
- PointerToRawDate = 400
IAT(1)
정의 : 어떤 프로그램에 어떤 Library에서 어떤 함수를 사용하고 있는지 기술한 Table(Array : 배열)
- 실행 파일(ex: DLL)이 다른 모듈에서 사용하는 함수를 가리키는 포인터 배열
- 간단히 IAT는 모든 함수 주소들의 집합
[ DLL ]
Dynamic Linked Library
: 동적 연결 라이브러리, 동적으로 라이브러리 함수를 가져와서 연결(파일 형태)
MS-DOS : 16비트, DLL 존재 x → Library만 존재
ex) printf() → <stdio.h> 바이너리 코드(2진수)에서 printf와 관련된 모든 코드를 긁어옴 → 이 코드를 printf가 필요한 프로그램에 삽입
- 단점 : 메모리 낭비, 효율 안 좋음, 함수 호출할 때마다 이 작업을 해야함
→ [ DLL 초기 구상 ]
- 프로그램에 라이브러리를 포함시키지 않고 별도의 파일(DLL)로 구성하여 필요할 때마다 호출
- 한 번 로딩된 DLL의 코드, 리소스는 메모리 매핑 기술로 다같이 나누어 쓰자
- 라이브러리가 업데이트되었을 때 해당 DLL 파일만 교체하면 되니까 간편하다
[ DLL이 로딩되는 방식 ]
- explicit Linking : 프로그램에서 사용되는 순간 딱 연결(로딩)하고 사용 끝나면 메모리에서 해제되는 방법
- implicit Linking : 프로그램이 실행될 때 같이 연결(로딩)되어 프로그램 종료 시 메모리에서 해제되는 방법
[ kernel ]
OS Admin, 모든 것을 관리한다 정도로 이해
[ DLL 재배치 ]
어떤 파일의 ImageBase 주소를 다른 파일이 차지하고 있을 때, 로딩을 시도했다가 다른 빈 공간을 찾아서 로딩하는 거
IAT(2)
PE 파일은 자신이 어떤 라이브러리를 import(호출)하고 있는지 IMAGE_IMPORT_DESCRIPTOR 구조체를 통해서 명시
[ IMAGE_IMPORT_DESCRIPTOR ]
- PE 바디 부분에 위치
- 시작 주소 저장된 곳 : IMAGE_OPTIONAL_HEADER32[1].VirtualAddress
- 마지막 부분이 NULL 구조체로 끝남
- 여기서 말하는 모든 주소는 RVA
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; //INT (Import Name Table) address (RVA)
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name; //Library name string address (RVA)
DWORD FirstThunk; //IAT(Import Address Table) address (RVA)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
- OriginalFirstThunk : INT(Import Name Table) 주소(RVA)
- Name : Library 이름 문자열 주소(RVA)
- Import 함수가 소속된 라이브러리 파일의 이름 문자열 포인터
- FirstThunk : IAT 주소를 가리킴(RVA)
- Characteristics
참고로 INT, IAT는 Long Type() 배열이고 NULL로 끝남
그리고 일반적인 파일에서는 IAT와 INT 값이 서로 같아야 한다.(예외도 존재)
[ IMAGE_IMPORT_BY_NAME ]
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //함수의 고유번호
BYTE Name[1]; //함수의 이름
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
- INT 내부 구조체라고 생각? ← DLL에서 함수를 호출할 때 필요한 정보(이름, 고유번호)를 가짐
[ PE로더가 Import 함수 주소를 IAT에 입력하는 기본적인 순서 설명 ]
IAT 입력 순서
- IMAGE_IMPORT_DESCRIPTOR의 Name 멤버를 읽음 (Library 이름, 문자열)
- 해당 라이브러리를 로딩함 → LoadLibrary(”라이브러리이름”)
- IMAGE_IMPORT_DESCRIPTOR의 OriginalFirstThunk 멤버를 읽어서 INT 주소를 얻음
- INT - 함수 이름을 저장하는 배열 → 함수 이름을 얻을 수 있다
- Import 하는 함수의 정보(Ordinal = Hint)가 담긴 구조체 포인터 배열
- 이 정보를 통해 필요로 하는 프로세스 메모리에 로딩된 라이브러리에서 해당 함수의 시작주소를 정확히 구할 수 있다.
- INT - 함수 이름을 저장하는 배열 → 함수 이름을 얻을 수 있다
- INT에서 배열의 값을 하나씩 읽어서 IMAGE_IMPORT_BY_NAME의 주소를 얻음
- INT에 있으니까
- IMAGE_IMPORT_BY_NAME의 Hint 또는 Name 항목으로 해당 함수의 시작주소를 얻음(GetProcAddress() 함수로)
- IMAGE_IMPORT_BY_NAME ← 힌트 제공 역할
- IMAGE_IMPORT_DESCRIPTOR의 First Thunk(AT) 멤버를 읽어 IAT 주소 획득
- 해당 IAT 배열 값에 위에서 구한 함수 주소(5번) 입력
- INT NULL 일 때까지 4~7 반복
- Ordinal은 고유 번호인데 함수가 변수를 구별하기 위한 번호
[ 중간정리 ]
- OriginalFirstThunk - INT를 가리킴
- FirstThunk는 IAT를 가리키는 포인터임
- INT는 함수 이름과 고유번호를 가지고,
IMAGE_IMPORT_BY_NAME는 해당 함수에 대한 자세한 정보를 가짐 - NAME은 (필요한)가져와야 하는 DLL 파일의 이름을 가리키고,
IMAGE_IMPORT_BY_NAME은 DLL에서 내보내야 하는 함수의 이름과 고유번호 값을 가진 구조체임