-
18. UPack PE 헤더 상세 분석리버싱 엔지니어링/리버싱 핵심 원리 2022. 3. 30. 01:40
UPack
- PE 패커로, PE 헤더를 독특하게 변형함
- 특이한 PE 헤더로 각종 PE 유틸리티(PEViewer 등)들이 정상적으로 동작하지 않음.
- 따라서, 악성 코드를 UPak으로 실행 압축하여 배포함
- 현재는 UPack으로 실행 압축된 파일을 악성파일로 진단/삭제함
UPack으로 notepad.exe 실행 압축하기
UPack 다운로드 : https://blog.naver.com/wjdeh313/222290932166
리버싱 핵심원리 : UPack
UPack dwing이라는 사람이 만든 패커 매우 독특한 기법으로 PE 헤더를 변형한다고 한다. 검색해서 자료...
blog.naver.com
UPack - notepad.exe를 불러와 [Pack!] 버튼을 통해 실행 압축 : notepad_upack.exe
notepad_upack.exe PE View로 실행
- PE View가 PE 헤더를 제대로 읽어들이지 못하고 있음 (더 예전 버전은 비정상 종료되기도 함)
Stud_PE 이용
: UPack파일의 PE 헤더를 분석 가능
PE 헤더 비교
: Hex Editor로 notepad.exe 와 notepad_upack의 헤더 부분을 비교
notepad.exe(원본)의 PE의 헤더
- 전형적인 PE 헤더
notepad_upack.exe(실행 압축)의 PE의 헤더
- MZ, PE 시그니처가 매우 가깝게 붙어있음
- DOS Stub가 없어짐
- 원본에서의 DOS Stub
notepad.exe DOS stub - 코드도 보이는 듯함 (GetProcAddress)
UPack의 PE 헤더 분석
헤더 겹쳐쓰기
: MZ헤더와 PE 헤더를 교묘하게 겹쳐쓰는 것
- 헤더를 겹쳐씀으로 해서 헤더 공간을 절약할 수 있음
- 복잡성을 증가시켜 분석을 어렵게 함(ex. PE 관련툴)
- Stub_PE를 통한 MZ 헤더 분석(Headers > Basic HEADERS tree view in hexeditor)
- MZ 헤더(IMAGE_DOS_HEADER)에서 중요한 2개의 멤버
(offset 0) e_magic : Magic number = 45DA('MZ') (offset 3C) e_lfanew : File address of new exe header
- e_Ifanew의 값에 따라 IMAGE_NT_HEADERS의 시작위치가 결정됨
- 정상적인 프로그램에서의 e_Ifanew 값 = MZ 헤더크기(40) + DOS Stub 크기(가변 : VC++의 경우 보통 A0) = E0
- UPack에서는 e_Ifanew 값이 10, MZ 헤더와 PE 헤더의 겹쳐쓰기가 가능
IMAGE_FILE_HEADER.SizeOfOptionalHeader의 값 변경
: 이 값을 조작해 헤더 안에 디코딩 코드 삽입
- SizeOfOptionalHeader 값 의미 : IMAGE_OPTIONAL_HEADER 구조체의 크기(E0)
→ UPack은 아래 사진과 같이 148로 변경함
SizeOfOptionalHeader - IMAGE_OPTIONAL_HEADER 구조체의 크기는 '구조체'이기 때문에 E0로 크기가 결정되었음. 하지만 왜 크기를 따로 입력하게 했을까?
- 의도 : PE 파일 형태에 따라 각각 다른 IMAGE_OPTIONAL_HEADER 형태의 구조체로 바꿀 수 있도록 설계
- ex) 64비트용 PE32+의 IMAGE_OPTIONAL_HEADER 구조체 크기 : F0
- SizeOfOptionalHeader은 섹션 헤더(IMAGE_SECTION_HEADER)의 시작 옵셋도 결정함
- IMAGE_OPTIONAL_HEADER 시작 옵셋에 SizeOfOptionalHeader 값을 더한 위치(옵셋)부터 IMAGE_SECTION_HEADER가 나타남
- UPack은 SizeOfOptionalHeader 값을 148로 설정하여 정상적인 값(E0, F0) 보다 크게 설정
- UPack이 SizeOfOptionalHeader 값을 바꾼 이유?
- 값을 늘려 IMAGE_OPTIONAL_HEADER와 IMAGE_SECTION_HEADER 사이에 공간 확보 가능
- 따라서, 해당 영역에 디코딩 코드를 추가
디코딩 영역 IMAGE_0PTIONAL_HEADER.NumberOfRvaAndSizes 값 변경
: 헤더에 자신의 코드를 삽입하기 위한 목적
- NumberOfRvaAndSizes 값의 의미 : IMAGE_DATA_DIRECTORY 구조체 배열의 원소 개수
- 정상적인 파일에서의 값 : 10
- UPack에서의 값 : A
NumberOfRvaAndSizes - UPack의 경우 IMAGE_DATA_DIRECTORY 구조체 배열의 뒤쪽 6개 원소들은 무시된다 (10 - A = 6)
- 무시된 영역에 자신의 코드를 덮어씀
IMAGE_SECTION_HEADER
: 사용되지 않는 항목에 자신의 데이터를 기록
- SizeOfOptionalHeader 값 : 148
- IMAGE_SECTION_HEADER = IMAGE_OPTIONAL_HEADER 시작 옵셋(28) + SizeOfOptionalHeader(148) = 170
- 파일 옵셋 1B0 위치에 있는 offser to relocations의 값 0100739D는 원본 notepad.exe의 EP 값
섹션 겹쳐쓰기
- Stud_PE > Sections
- 첫번째 섹션과 세번째 파일 시작 옵셋이(RawOffset) 10으로 되어있음
- 옵셋 10은 헤더 영역인데 UPack에서는 이곳부터 섹션이 시작됨
- 첫번째 섹션과 세번째 섹션의 파일 시작 옵셋(RawOffset)과 파일 크기(RawSize)가 완전히 동일. 하지만, 섹션의 메모리 시작 RVA(VirtualOffset) 항목과 메모리 크기값(VirtualSize)이 다름
→ UPack은 PE 헤더, 첫번째 섹션, 세번째 섹션이 겹쳐 있다.
겹쳐쓰기 특징 - 섹션헤더(IMAGE_SECTION_HEADER)에 정의된 값에 의해 PE로더는 파일 옵셋 0~01FF 영역을 다른 메모리 위치(헤더, 첫번째 섹션, 세번째 섹션)에 각각 매핑함
- 두번째 섹션(2nd section)영역의 크기(AE28)은 원본 파일(notepad.exe)가 압축되어 있음.
- 메모리에서의 첫번째 섹션
- 섹션의 메모리 크기 : 14000(= 원본 파일의 Size Of Image)
- 즉, 두 번째 섹션에 압축된 파일 이미지를 첫 번째 섹션에 그대로 압축해제
- 원본에서는 3개의 섹션으로 나누어져있음
압축 해제된 첫 번째 섹션 → 두 번째 섹션 영역에 압축된 notepad가 들어 있고, 압축이 풀리며 첫 번째 섹션 영역에 기록
RVA to RAW
일반적인 RVA → RAW 변환 방법
RAW = RVA - VirtualAddress + PointerToRawData
위 공식을 이용한 EP의 파일 옵셋(RAW) 계산
AddressOfEnrtyPoint (섹션 겹쳐쓰기 이미지 참고)
- UPack EP : RVA 1018
- RAW = 1018 - 1000 +10 =28 (t)
RAW hex editor를 통해 살펴보기
- RAW 28 영역은 코드가 아니라 "LoadLibraryA" 문자열 영역
- UPack의 트릭 : PointerToRawData
- PointerToRawData 값은 FileAlignment의 배수가 되어야함.
- UPack FileAlignment는 200, 따라서 PointerToRawDat 값은 0, 200, 400, 600 등의 값을 가져야함.
- 정상적인 RVA → RAW 변환 : RAW = 1018 - 1000 + 0 = 18
- PointerToRawData = 0
- 확인해보기
hex editor OllyDbg Import Table
Directory Table에서 Import Directory Table 주소 얻기
- 앞 4바이트가 Import Table의 주소(RVA), 뒤 4바이트가 Import Table의 크기(size)
- Import Table의 RVA : 271EE
RVA → RAW 변환 : 271EE
- 메모리 주소 271EE는 메모리에서 세 번째 섹션 영역
- 271EE - 27000 + 0 = 1EE
- RawOffset 0으로 강제 변환
Hex Editor로 살펴보기
- IMAGE_IMPORT_DESCRIPTION 구조체
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; // INT }; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; // IAT } IMAGE_IMPORT_DESCRIPTOR;
- Import Table은 IMAGE_IMPORT_DESCRIPTION 구조체 배열로 이루어지고 마지막은 NULL 구조체로 끝나야함
- 선택된 영역 : IMAGE_IMPORT_DESCRIPTION
- 201 뒤는 두번째 구조체도, NULL 구조체도 아님
IAT
- IMAGE_IMPORT_DESCRIPTOR 구조체 중요 멤버
offset member RVA 1EE OriginalFirstThunk(INT) 0 1FA Name 2 1FE FirstThunk(IAT) 11E8 - Name의 RVA 값 = 2, 헤더 영역(헤더 영역은 RVA와 RAW 값이 같음)
- "KERNEL32.DLL" 문자열
- DOS 헤더의 사용되지 않는 영역이라서 이곳에 Import DLL 이름을 씀
- INT를 쫓아가면 API 문자열이 나오지만, 0일 때는 IAT값을 따라가도 됨
- 11E8 : 첫 번째 섹션 영역
- 11E8 - 1000 + 0 = 1E8
IAT
- 2개의 API : RVA 28, BE
- RVA 위치에 Import 함수 [ordinal + 이름 문자열]
- 'LoadLibraryA','GerProcAddress' API를 임포트
'리버싱 엔지니어링 > 리버싱 핵심 원리' 카테고리의 다른 글
20 인라인 패치 실습 (0) 2022.03.30 19 UPack 디버깅 - OEP 찾기 (0) 2022.03.30 17 실행 파일에서 .reloc 섹션 제거하기 (0) 2022.03.27 16 Base Relocation Table (0) 2022.03.27 15 UPX 실행 압축된 notepad 디버깅 (0) 2022.03.27