지난해 말부터 시작하여 꾸준히 유포되고 있는 헤르메스 랜섬웨어 2.1은 기능적으로 큰 변화가 없으며, 이에 따라 구현된 내부 PE(Portable Executable) 파일도 유사한 형태를 가진다. 하지만 외형의 경우 아이콘이나 무의미한 문자열 같은 리소스, 데이터가 랜덤하게 다양한 값을 가지기 때문에 형태 뿐만 아니라 크기까지도 매우 상이한 형태로 유포되었다.


최근 유포되고 있는 헤르메스 랜섬웨어 2.1의 경우 실제 랜섬웨어의 기능을 담당하고 있는 내부 PE 파일은 과거와 유사하지만 외형적으로는 큰 차이를 보인다. C / C++로 개발된 과거의 형태와는 달리 개발 언어 자체가 변경되었는데, Visual Basic 6.0 ( N-Code : Native ) 으로 만들어진 외형을 가진 채로 유포 중이다.


개발 언어는 변경되었지만 그 기능은 동일한데, 쓰레기 코드를 거친 후 결국 암호화되어 저장된 실제 헤르메스 PE를 복구하고 메모리에 새로 쓰는 RunPE 방식으로 구현되어 있다.


 

v2.1 초기 형태

v2.1 이후

 v2.1 최신 형태

기간 

2017년 10월 경

2018년 상반기

2018년 7월 경

외형

C /C++

C /C++

  Visual Basic 6.0 

 내부 PE

 Hermes Ransomware v2.1

(확장자를 .HRM으로 변경)

Hermes Ransomware v2.1
(확장자 변경 X)

Hermes Ransomware v2.1
(확장자 변경 X)

특징

 RunPE 방식으로 실제 PE 로드 

 RunPE

 RunPE



v2.1 초기 형태


초기 버전의 경우 다음과 같은 쓰레기 코드로 시작한다.


[그림1]  쓰레기 코드(Garbage Code)


이후 복호화 루틴을 거쳐 실제 PE를 복구한 후 메모리에 새로 쓰고 다시 실행하는 RunPE 방식이 사용된다.

[그림2]  복호화 루틴 및 복호화 된 실제 헤르메스 PE




VB6 외형을 가진 최신 헤르메스 v2.1


일반적으로 VB6 외형을 가진 악성코드의 경우 Form 초기화 함수 ( Form_Initialize ) 등에 실제 코드를 숨겨놓는 경우가 많지만 헤르메스는 Form이 삽입만 되어있을 뿐 폼과 관련된 이벤트 루틴은 존재하지 않는다.


[그림3]  랜덤 문자열 이름을 갖는 Form



즉 GUI 형태로 보이지만 Console 모드로 컴파일되었으며, 이에 따라 아래와 같이 실제 EP(Entry-Point)를 확인할 수 있다. 아래의 그림은 VB파일에서 사용되는 구조체 정보를 나타내며 구조체 시작+0x2C에 위치한 멤버가 Console Mode에서 EP 주소(0x004283A4)를 나타낸다.

[그림4]  ThunRTMain() 호출 전 push하는 구조체



진행하다 보면 여러 쓰레기 코드들을 확인할 수 있다.

[그림5]  시작부터 마지막 부분까지 모두 쓰레기 코드


쓰레기 코드를 지나 실제 복호화 루틴 주소를 획득한 후 CallWindowProcW() 함수를 호출하는데 해당 함수의 인자 중 lpPrevWndFunc에 복호화 루틴의 주소를 넣는다. 이를 통해 CallWindowProcW() API 함수를 호출함으로써 실제 복호화 루틴으로 분기할 수 있다. 이러한 형태는 VB6로 만들어진 RunPE 형태에서 자주 사용되는 방식이다.


[그림6]  CallWindowProcW() 함수를 호출하기 위한 DllFunctionCall()

 


최신 버전의 경우 또한 두 단계의 복호화 루틴이 존재한다. 아래 코드 섹션 상에 존재하는 복호화 루틴은 이전 버전과 같이 복호화 대상이 또 다른 형태의 복호화 루틴 및 데이터 그리고 실제 헤르메스의 PE이다. 


[그림7]  첫 번째 복호화 루틴


실제 헤르메스의 PE에 대한 복호화를 담당하는 두 번째 복호화 루틴은 다음과 같으며 안티 디버깅 및 안티 VM 기법이 존재한다.


[그림8]  Anti-Debugging 및 Anti-VM 기법


IsDebuggerPresent() 함수를 직접 구현하였으며, NtGlobalFlags 값을 획득하여 현재 디버깅 중인지 판단한다. 마지막으로 CPUID를 이용해 현재 CPU가 MMX를 지원하는지 검사한다. 이것은 보통 가상 환경의 CPU가 MMX를 지원하지 않는다는 점을 이용한 Anti VM으로 판단되며 MMX 관련 명령어는 이후에도 사용된다. 


다음으로 문자열 kID1(0x6B494431)을 찾는데 현재 샘플은 오프셋 그리고 메모리 기준에서도 0x89CC에 해당 문자열이 위치한다. (참고로 이 파일은 .text 섹션이 0x1000에서 시작하기 때문에 메모리에 로드된 것과 동일한 형태이다. 즉 File Alignment가 Section Alignment와 마찬가지로 0x1000을 갖는다)


[그림9]  "kID1" 시그니처 문자열

 


복호화 시작위치를 찾는데 기준이 되는 문자열 "kID1" 이후부터 주어진 크기 만큼 할당한 메모리에 복사한다. 메모리로 복사된 데이터에는 인코딩되어 있는 PE 외에도 XOR에 사용되는 키 값이 존재한다. 


[그림10]  아래의 XOR 루틴에서 사용되는 키

 

해당 키 값으로 다음과 같이 MMX 명령어를 이용한 복호화가 진행된다.

[그림11]  MOVD, PXOR 등 다수의 MMX 명령어

 

헤르메스의 PE는 두 번 인코딩되어 있기 때문에 위의 복호화 루틴을 지나도 PE를 확인할 수 없다. 마지막으로 0x100 크기의 키를 생성한 후 이 키와 XOR 연산을 거침으로써 PE가 복구된다.


[그림12] 생성된 0x100 크기의 키와 이를 이용한 xor 연산

 

복구된 PE를 확인해 보면 시그니처 "MZ"가 빠진 형태이며 이후에 직접 "MZ"를 추가한다.

[그림13] 'MZ' 시그니처를 추가하는 과정

 

과거 형태가 자가 프로세스에서 메모리를 언매핑한 후 새로 쓰는 방식이었다면, 최신 버전은 자가 프로그램을 자식 프로세스로 실행하여 복호화한 헤르메스 PE를 인젝션하는 방식을 사용한다. CREATE_SUSPENDED 모드로 CreateProcessW()를 실행한 후 자식 프로세스의 바이너리 모듈을 NtUnmapViewOfSection()으로 언매핑한 후 새로 메모리를 할당하여 써 넣는 방식이다.


[그림14] 자가 프로그램을 자식 프로세스로 생성하는 과정

 

이후 부모 프로세스는 종료되며, 자식 프로세스에 인젝션되어 실행되는 헤르메스는 실제 랜섬웨어의 기능을 수행한다. 외형은 바뀌었지만 내부의 실제 헤르메스 PE는 다음 링크에서 분석한 바와 거의 동일하다. [ http://asec.ahnlab.com/1088 ]


하지만 "AhnLab" 폴더명을 제외하고 암호화를 진행하는 것 외에도 AhnLab 관련 스트링이 더 확인 되었다. 다음과 같이 "ah1nl1ab.dll"이라는 라이브러리에 대한 로드를 시도하고 실패 시 종료하는 부분이 추가 된 점이다.


[그림15]  "ah1nl1ab.dll" 라이브러리를 로드하는 부분

 

안랩 제품에서는 헤르메스 랜섬웨어 v2.1의 최신 버전을 다음과 같이 진단하고 있다.


- 파일 진단 : Trojan/Win32.Hermesran  (2018.07.03.06)

- 행위 진단 : Malware/MDP.Ransom.M1171

Posted by 분석팀