헤더부분에는 파일이 실행 되기 위한 필요한 모든 정보들이 있고 구조체 형식으로 저장되어 있다.
DOS header부분부터 Section header까지를 PE헤더, Section들을 합쳐서 PE바디라고 한다.
VA는 프로세스 가상 메모리의 절대주소를 말하고
RVA는 어느 기준 위치에서부터의 상대주소를 가리킨다.
RVA + ImageBase = VA 수식으로 나타난다. 프로세스가 메모리에 로딩되는 순간 이미 다른 파일이 로딩되어 있는 경우 relocation을 통해 다른 위치에 로딩하기 위한 것이다.
1) DOS_HEADER
PE File Format을 만들 당시에 사용되던 DOS 파일에 대한 하위 호환성을 고려하여 만들어진 부분이다. IMAGE_DOS_HEADER 구조체로 구성되어 있다.
구조체의 크기는 40이고 중요 멤버는 e_magic, e_lfanew이다.
- e_magic
: DOS의 signature를 나타내는 부분으로, MZ값이 와야한다. MZ의 아스키 값인 4D5A가 e_magic 부분에 나타난다.
- e_lfanew
: NT Header 구조체의 offset을 나타낸다. 000000E0는 e_lfanew 값으로 나타나는 것을 확인할 수 있다.
2) NT_Header
signature
: 5045000h (PE00)값을 갖는다.
3) file header
: IMAGE_FILE_HEADER 구조체에서는 Machine,NumberOfSections, SizeOfOptionalHeader,Characteristics 멤버가 중요하다
- Machine(CPU) : CPU별로 고유한 값이 부여된다.
- NumberOfSections : PE 파일에 존재하는 Section의 개수를 나타낸다.
- SizeOfOptionalHeader : IMAGE_NT_HEADERS의 마지막 멤버인 IMAGE_OPTIONAL_HEADERS32 구조체의 크기를 나타낸다. C언어 구조체이기 때문에 크기가 이미 정해져 있지만, Windows PE로드는 이 값을 보고 구조체 크기를 인식한다.
- Characteristics : 파일의 속성을 나타내는 값으로, 실행이 가능한 형태인지, DLL 파일인지 등의 정보가 OR 형식으로 조합되어 표기된다.
4) NT_Header-Optional_Header
PE헤더 구조체 중에서 가장 크기가 큰 IMAGE_OPTIONAL_HEADER32이다.
- Magic : 32bit용 구조체인 경우 10B, 64bit용 구조체인 경우 20B를 갖는다.
- AddressOfEntryPoint : EP의 RVA 값을 나타낸다.
- ImageBase : PE 파일이 로딩되는 시작 주소를 나타낸다.
- SectionAlignment, FileAlignment : 각각 메모리에서의 최소 단위, 파일에서의 최소 단위를 나타낸다.
- SizeOfImage : 가상 메모리에서 PE Image가 차지하는 크기를 나타낸다.
- SizeOfHeader : PE 헤더의 전체 크기를 나타낸다.
- Subsystem : 시스템 드라이버(*.sys)인지, 일반 실행 파일인지(*.exe, *.dll)를 구분한다.
- NumberOfRvaAndSizes : IMAGE_OPTIONAL_HEADER32 구조체의 마지막 멤버인 DataDirectory 배열의 개수를 나타낸다.
- DataDirectory : IMAGE_DATA_DIRECTORY 구조체의 배열로, 배열의 각 항목마다 정의된 값을 지닌다. 각 항목은 다음과 같다.
5) Section Header
각 섹션의 속성(property)을 정의한 것이 섹션 헤더이다. 섹션 헤더는 각 섹션별로 IMAGE_SECTION_HEADER 구조체의 배열로 되어 있다.
- VirtualSize : 메모리에서 섹션이 차지하는 크기
- VirtualAddress : 메모리에서 섹션의 시작 주소(RVA) - SizeOfRawData : 파일에서 섹션이 차지하는 크기
upx 패킹
upx -o : 생성될 패킹 파일 경로
upx 언패킹
upx -d -o "생성될 언패킹 파일 경로" "언패킹에 사용할 파일 경로"
notpadxp.exe파일을 upx패킹을 해주었다.
파일사이즈가 65KB에서 47KB로 줄어든 것을 확인할 수있다.
패킹 후의 notepad_upx.exe파일을 PE view로 확인해보면
Size of Raw Data의 크기가 0임을 알 수있다.
압축해제 코드와 압축된 원본 코드가 두번째 섹션에 존재하여 파일이 시작되는 순간 원본 코드를 첫번째 섹션에 압축해제 시키기 때문이다. 압축 해제 코드와 압축된 원본 코드는 두번째 섹션에 존재한다는 뜻이다. 파일이 실행되면 압축 해제 코드가 실행되어 원본 코드를 첫 번째 섹션에 해제시킨다. 그리고 이 압축해제가 끝나면 원본 EP 코드를 실행한다.
UPX로 압축딘 notepad 디버깅
EntryPoint의 주소는 01014280이다.
mov esi,notepad_upx.1010000은 esi레지스터를 두번째 섹션 시작수소로 이동한다.
lea edi, dword ptr ds:[esi-F000]은 edi 레지스터를 첫번째 섹션 시작주소로 이동한다.
edx에서 한바이트씩 읽어서 edi에 쓰기
esi 값을 al로 옮기고 esi++,edi값을 al로 옮기고, edi++,edx++
0이 아니면 10142a1로 이동하는 모습을 통해 esi에서 값을 읽고 edi에 가져오는 것을 알 수 있다.
디코딩
이부분이 압축을 해제하는 코드이다.
두번째 섹션의 주소에서 차례대로 값을 읽어 연산을 거쳐 압축을 해제하고 edi가 가리키는 첫번째 섹션의 주소에 값을 써준다.
원본 코드의 CALL/JMP명령어의 목적지 주소 복원
원본 코드의 call/jmp명령어의 목적지 주소를 복원시켜주는 코드이다.
IAT테이블 재구성 루프
IAT테이블을 세팅하는 루프이다. 저장되어 있는 원본 notepadxp.exe에서 사용되는 API이름 문자열이 끝날때까지 반복하면 원본 notepad.exe의 IAT복원 과정이 마무리 된다.
OEP 코드로 이동
UPX패커의 특징인 OEP코드로 가는 JMP 평령어는 popad명령어 바로 뒤에 나타난다.