2. 목차
실습
수작업 PE 파일 만들기
DOS header
NT header
File header
Optional header
Section header
Header NULL padding
Section 내용
Section NULL padding
미뤄둔 내용 수정
코드로 PE 파일 만들기
부록
참고 사이트
3. 수작업 PE 파일 만들기
• DOS header
• NT header
• Section header
• Header NULL padding
• Section 내용
• Section NULL padding
• 미뤄둔 내용 수정
4. 수작업 PE 파일 만들기
아직 PE 헤더에서 배우지 않은 부분이 있어 완전한 PE 파일을 만들 수는 없다.
이번 실습에서는 제대로 된 PE 파일을 만드는 것이 목적은 아니다.
아무 변수도 없고 코드만 달랑 들어있는 가장 기초적인 PE를 만들 것이다.
코드로는 MOV EAX, 1 과 ADD EAX, 2 그리고 Ret만 넣어보자.
목표
올리 디버거에서 실행했을 때, 코드가 보이게 만드는 것
하는 이유
실제로 만들어 보면 PE헤더에 대한 이해도가 높아진다.
리틀 엔디안으로 숫자를 읽고 쓰는 데 익숙해진다.
기계어 코드를 만들고 이용하는 데 익숙해진다.
Stack overflow같은 공격을 할 때, 기계어 코드를 만들어 넣어야 한다.
헥사 에디터로 프로그램을 수정하는 것에 익숙해 진다.
5. 수작업 PE 파일 만들기
인라인 어셈블리 코드를 이용하여 기계어를 쉽게 얻었다.
B80100000083C002C3
총 9바이트이다.
7. DOS HEADER
• e_magic과 e_lfanew를 제외한
필드는 실행에 영향을 끼치지
않는다.
• 그냥 0으로 채워도 된다.
• e_magic
• MZ이라는 표식이
들어간다.
• e_lfanew
• NT Header의 위치를 저장
• DOS stub은 쓰지 않을
계획이므로 e_lfanew는 바로
다음 위치를 가리키도록
했다.
13. NT HEADER_FILE HEADER
NumberOfSections
• 섹션의 개수를 나타냄
• 반드시 0보다 커야 한다
• 정의된 섹션 수와 실제 수가 다르면 실행 에러 발생
• Text섹션만 만들 것이므로 1을 넣자.
TimeDateStamp와 PointerToSymolTable,
NumberOfSystembols는 그냥 0으로 채우자
18. NT HEADER_OPTIONAL HEADER
Magic
• IMAGE_OPTIONAL_HEADER32일 경우 10B
Major, Minor Linker Version
• 0으로 채움
SizeOfCode
• 기계어 코드 길이는 9 바이트이다.
• 그런데 sizeofcode는 filealignment를 적용한 값이 들어갈 것이다.
• Filealignment는 256byte(0x100)로 할 것이므로, 100을 쓴다.
19. NT HEADER_OPTIONAL HEADER
• 사용할 데이터가 따로 없으니
SizeOfInitializedData와 SizeOfUninitializedData는
0으로 채운다.
• AddressOfEntryPoint와 BaseOfCode는 아직 코드가
어느 위치에 들어갈 지 모르기 때문에 나중에 채운다
• 찾기 쉽도록 F로 채워 둔다
• 파일 오프셋을 기억해 두자!
• BaseOfData는 데이터를 사용하지 않으므로 0으로
채운다.
20. NT HEADER_OPTIONAL HEADER
• ImageBase는 흔히 사용하는 00040000으로 해 보자.
• Section Alignment는 0x100으로 해 보자(256byte)
• 원래 Section Alignment는 한 페이지 크기보다 작을 수 없다.
• 페이지란, 가상 메모리를 관리하는 최소 단위이다.
• 하지만 그보다 작게 해도 프로그램에 이상은 없다.
• Windows 운영체제가 실제로 프로그램을 로딩 할
때 페이지 크기만큼 메모리를 할당할 뿐이다.
• File Alignment도 0x100으로 해 보자.
• 사실 512~65,536사이여야 하지만, 작을 경우 알아서 처리 된다…
• File alignment와 section alignment가 같을 경우, 거의 같게 올라온다
(bss 등의 예외를 제외하면)
• Null padding이 같기 때문
21. NT HEADER_OPTIONAL HEADER
• 여기까지 쓴 모습이다.
• 나중에 쓰기로 한 AddressOfEntryPoint와
BaseOfCode의 위치를 잘 기억하자 (0x68, 0x6C)
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
22. NT HEADER_OPTIONAL HEADER
• Major, Minor OperatingSystemVersion은 최소 실행 가능 OS 버전이다.
• XP로 해 보자. XP에 해당하는 숫자는 5.1이다. Major에 5, Minor에 1을 쓴다.
• Major, Minor ImageVersion은 프로그램의 버전이다.
• 프로그래머가 프로그램 버전을 관리하고 싶을 때 이용하는 필드이다.
• 그냥 0으로 채워도 상관 없다.
• Major, Minor SubsystemVersion도 OperatingSystemVersion과 똑같이 맞춰준다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
23. NT HEADER_OPTIONAL HEADER
• Win32VersionValue는 0으로 채운다.
• 더 이상 쓰이지 않는 필드
• SizeOfImage는 PE 이미지의 총 크기이다.
• section alignment의 배수여야 한다.
• 아직 text 섹션을 쓰지 않았으므로 일단 F로 채우고 나중에 다시 쓴다.
• SizeOfHeaders는 헤더 영역의 총 크기이다.
• Section alignment의 배수여야 한다.
• 헤더를 전부 쓰고 채우자. 일단 F로 채운다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
24. NT HEADER_OPTIONAL HEADER
• 중간 모습이다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
25. NT HEADER_OPTIONAL HEADER
CheckSum
• 파일이 변했는지 판단할 때 사용하는 필드이다. 지금은 신경 쓸 것 없다.
• 0으로 채운다.
Subsystem
• 콘솔 프로그램으로 만들기 위해서는 3을 써야 한다. (CUI)
DllCharacteristics
• PE파일이 DLL파일일 때 의미가 있다.
• 0으로 채운다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
26. NT HEADER_OPTIONAL HEADER
SizeOfStack, Heap Reserve
• 디폴트 힙과 스택의 최대 크기
• 프로그램을 만들 때, 링커가 일반적으로 1M로 설정한다 (0x100000)
SizeOfStack, Heap Commit
• 한 번에 할당할 메모리의 양
• 이 크기로 가상 메모리의 스택, 힙 영역을 물리 메모리에 매핑
• 링커가 일반적으로 1페이지(0x1000) 크기로 써 준다. 이대로 써 주자.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
27. NT HEADER_OPTIONAL HEADER
LoaderFlags
• 지금은 사용하지 않는 필드
• 0으로 채운다
NumberOfRvaAndSizes
• 바로 아래 있는 배열 요소의 개수
• 기본적으로 16개를 사용하므로 16(0x10)을 써 준다.
DataDirectory
• 함수 import 등에 이용되지만 아직 배우지 않았으므로 0으로 채운다.
• 배열 하나당 8바이트이므로, 8*16, 총128개의 0을 쓴다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
28. NT HEADER_OPTIONAL HEADER
• Optional Header를 끝까지 쓴 모습이다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
30. SECTION HEADER
winnt.h에 정의돼 있는 Section header struct
빨간 네모 안 5개의 값이 중요하다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
31. SECTION HEADER
Name
• 이름은 .text라고 하자
• 이름 배열의 남은 부분은 0으로 채운다.
VirtualSize
• 해당 section의 얼마 만큼이 실제로 의미 있는
데이터인지 쓴다.
• Section Alignment가 적용되지 않은 값
• 실제 코드 크기인 9를 쓴다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
32. SECTION HEADER
VirtualAddress
• PE 이미지(메모리에 올려진 PE 파일)에서 섹션의
시작 주소
• RVA
• file alignment와 section alignment를 같게
했으므로 파일에서의 코드영역 시작 위치를 쓰면
된다.
• 코드 섹션은 헤더를 다 쓴 뒤 헤더의 파일
alignment를 맞춰주고 쓸 것이다. 일단 F로
채웠다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
• VirtualAddrss(0x144)
33. SECTION HEADER
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
• VirtualAddrss(0x144)
• PointerToRawData(0x148)
SizeOfRawData
• 파일에서의 해당 section의 크기
• File alignment가 적용된 크기
• NULL padding까지 포함한 크기
• 실제 code 내용 사이즈는 9이지만 file
alignment가 0x100이므로 0x100을 써 준다.
PointerToRawData
• 해당 section의 파일에서의 위치
• 헤더를 다 쓰고 채우자. 일단 F로 채웠다.
34. SECTION HEADER
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
• VirtualAddrss(0x144)
• PointerToRawData(0x148)
PointerToRelocations, NumberOfRelocations
• Obj 파일에서만 의미 있다. 0으로 채운다.
PointerToLine, NumberOfLinenumbers
• 디버깅에 이용되는 필드이다. 0으로 채운다.
35. SECTION HEADER
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
• VirtualAddrss(0x144)
• PointerToRawData(0x148)
Characteristics
• 해당 section의 권한, 특징
• 실행가능 파일을 만들 것이므로 0x60000020
36. SECTION HEADER
• Section header를 끝까지 쓴 모습이다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
• VirtualAddrss(0x144)
• PointerToRawData(0x148)
37. SECTION HEADER
• Section헤더는 Section 개수만큼 있다.
• 개수가 일정치 않다.
• Section 헤더 배열의 마지막을 알리는
NULL로 이루어진 Section 헤더를
추가한다.
• Section 헤더의 크기만큼 0을
추가한다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
• VirtualAddrss(0x144)
• PointerToRawData(0x148)
39. HEADER FILE ALIGNMENT
• 헤더 영역의 file alignment를 맞추기
위해 0을 추가했다.
• File alignment 는 0x100이었다.
• 0x200이 다음 내용(실제
section의 내용) 시작 주소가 될
수 있도록 1FF까지 0을 채운다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
• VirtualAddrss(0x144)
• PointerToRawData(0x148)
41. SECTION 내용
맨 처음 구한 기계어를 쓴다.
B80100000083C002C3
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
• VirtualAddrss(0x144)
• PointerToRawData(0x148)
43. SECTION 내용 사이 NULL PADDING
• Section의 file alignment를 맞추기
위해 0을 추가했다.
• 2FF까지 0을 채운다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
• VirtualAddrss(0x144)
• PointerToRawData(0x148)
44. 미뤄둔 내용 수정
• AddressOfEntryPoint (0x68)
• Section alignment와 file alignment가
같으므로 파일에서의 주소가 RVA가
된다.
• 코드영역의 시작 부분에 있는 코드에서
시작할 것이므로, 코드가 있는 section의
위치를 쓰면 된다.
• 0x200을 쓴다.
• BaseOfCode (0x6C)
• 코드 영역의 시작 위치, 0x200를 쓴다.
• 나중에 쓸 것
• AddressOfEntryPoint(0x68)
• BaseOfCode(0x6C)
• SizeOfImage(0x90)
• SizeofHeader(0x94)
• VirtualAddrss(0x144)
• PointerToRawData(0x148)
45. 미뤄둔 내용 수정
• SizeOfImage (0x90)
• Section alignment와 file alignment가
같으므로 PE 파일 크기를 쓰면 된다.
• 0x300을 쓴다.
• SizeOfHeader (0x94)
• 헤더의 크기(file alignment적용)는
0x200이다.
• 나중에 쓸 것
• SizeOfImage(0x90)
• SizeofHeader(0x94)
• VirtualAddrss(0x144)
• PointerToRawData(0x148)
46. 미뤄둔 내용 수정
• VirtualAddress (0x144)
• Section alignment와 file alignment가
같으므로 PE 파일에서의 section 시작
위치를 쓰면 된다.
• 0x200을 쓴다.
• PointerToRawData
• 파일에서의 section 시작 위치, 0x200을
쓴다.
• 나중에 쓸 것
• VirtualAddrss(0x144)
• PointerToRawData(0x148)
47. 올리 디버거에서 실행
• 에러가 뜨긴 하지만 실행이 된다.
• File, section alignment때문인 것 같다.
49. 코드로 PE 파일 만들기
앞에서 만든 PE 파일을 코드로 쓴다.
조금 더 편하게 만들 수 있다.
목표
실제로 PE 파일을 만들고 쓰는 코드를 만들어 본다. 후에 간간이 응용할 일이 있을 코드다.
하는 이유
헤더파일에 정의된, 운영체제가 제공하는 Struct를 이용하는 것에 익숙해진다.
바이너리 파일을 쓰는 것에 익숙해진다.
50. 코드로 PE 파일 만들기
첨부된 코드를 따라해 보자.
앞에서 손으로 만든 것을 그대로 코드로 옮겼다.
52. 참고 사이트
PE 파일을 직접 만들어보고 글을 쓴 블로그
조금 더 지켜야 하는 규칙을 잘 지켜서 썼음
http://zesrever.tistory.com/58
PE를 전문으로 다룬 리버싱 책 미리보기
책 내용 전부를 볼 수는 없지만, 책 내용에서 검색을 이용하면 인터넷에서 잘 찾아지지 않는 내용을 찾을 수 있다.
https://books.google.co.kr/books?id=ItWDDQAAQBAJ&printsec=frontcover&dq=%EB%A6%AC%EB%B2%84%EC
%8A%A4+%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81&hl=ko&sa=X&ved=0ahUKEwiF-6-
x6t3UAhUBzpQKHUXYBZkQ6AEIKzAB#v=onepage&q&f=false