실행 압축기 종류 중 하나로 PE 헤더를 독특하게 변형하는 기법으로 많은 PE 유틸리티들이 정상적으로 작동하지 못하게 한 이력이 있음.

e_magic : 4D5A("MZ")로 정상적e_lfanew : 보통 DOS Header 크기 + DOS Stub 크기 = E0가 되는 것에 비해 10이라는 매우 작은 값을 사용하였다. 물론 PE 스펙 자체에 어긋나지는 않는다. 10부터 쓰기 시작하면서 PE 헤더를 겹쳐쓴다.
1. SizeOfOptionalHeader : 원래 IMAGE_OPTINONAL_HEADER 구조체의 크기는 E0인데 UPack은 이 값을 148h로 변경한다. \
2. SizeOfOptionalHeader의 또 다른 의미는 섹션 헤더의 시작 옵셋을 결정하는 것이다. <-- IMAGE_OPTIONAL_HEADER가 끝나면 바로 섹션 헤더가 시작하므로
3. 그러면 이렇게 크기를 바꾼 의도가 무엇일까?

1. NumberOfRvaAndSizes : 정상적인 IMAGE_DATA_DIRECTORY 배열의 원소는 10h개이지만, UPack에서는 A개로 변경된다. 즉 인덱스 9 이후의 구조체 배열의 원소들은 무시한다. 그리고 그 영역을 자신의 코드로 덮어버린다.

섹션 헤더를 보면 첫번째 헤더와 세번째 헤더의 RawOffset과 RawSize의 값이 같은 것을 볼 수 있다. '이렇게 될 수 있나?' 싶지만 PE 스펙에는 문제가 없다.

위와 같이 같은 파일 이미지를 가지고 각각 다른 위치, 크기의 메모리 이미지를 만들 수 있다. 이 때 첫번째 섹션의 크기는 14000이다. 이는 SizeOfImage의 크기와 같다. 즉, 두 번째 섹션에 압축된 파일 이미지를 그대로 첫번째 섹션에 해제하는 것이다.

RAW를 계산하기 위해 UPack의 RVA, VirtualAddress, PointerToRawData를 구했다.
RAW = 1018(RVA) - 1000(VirtualAddress) + 10(PointerToRawData) = 28

근데 이곳을 보니 뭔가 이상하다. 코드가 아니라 LoadLibraryA 문자열 영역이다.
알아보니 PointerToRawData 영역이 FileAlignment의 배수가 되어야 한다. 따라서 200의 배수인 0,200,400,600 등의 값을 가져야 한다.
RAW = 1018(RVA) - 1000(VirtualAddress) + 0(PointerToRawData) = 18

이를 통해 EP를 나왔음을 알 수 있다.

Import Table의 RVA : 0271EE
RAW = 271EE(RVA) - 27000(VirtualAddress) + 0(RawOffset) = 1EE

1EE부터 201까지 첫번째 구조체이다. 그러나 그 뒤의 값은 두번째 구조체도 아니고, 그렇다고 Import Table의 끝을 나타내는 NULL 구조체도 아니다.

세번째 섹션의 크기 및 offset은 첫번째 섹션과 같았고 이 만큼의 공간을 메모리에 할당한다. 나머지는 NULL로 다 채운다. 이는 PE 스펙을 어긋나지 않은 활동이다.
이를 확인하기 위해 IAT의 Name 변수를 확인해보겠다.

Name의 RVA는 2이고 이는 계산해보면 RAW도 2이다.
2를 추적해서 확인해 보면 아까 보았던 DOS Header의 MZ 다음 kernel32.dll이라는 이름이 나온다. 잘 입력되어있는 것을 확인 할 수 있다.
--> UPack은 헤더 분석이 어렵지만 로딩하는 과정은 결국 다른 패커들과 다를 게 없음.
익숙하지 않고 이전에 배웠던 PE 스펙의 틀을 깨버리는 과정이라서 어려운 것 같다. 익숙해지면 PE File Format처럼 다룰 수 있을 듯 하다.
안녕하세요! 개발자 준비하시는 분이나 현업에 종사하고 계신 분들만 할 수 있는 시급 25달러~51달러 LLM 평가 부업 공유합니다~ 제 블로그에 자세하게 써놓았으니 관심있으시면 한 번 읽어봐주세요 :)