3월부터 현재까지 Locky 라는 이름의 랜섬웨어의 국내감염 사례가 지속적으로 발생하고 있다. 국내뿐 아니라 해외 여러 보안업체에서도 이러한 Locky 랜섬웨어 대응을 위한 다양한 시도들이 진행되고 있으며, 제작자 또한 이를 우회하기 위해 기존의 동작방식에 변화를 주는 추세이다. 본 글에서는 최근 국내접수 Locky 를 통해 확인한 변화내용 3가지를 소개하고자 한다.

 

1. DGA (Domain Generation Algorithm) 호출방식

Locky 는 파일 암호화에 사용할 키 정보를 얻기위해 내부에 저장된 IP 로 접속을 시도한다. (접속 실패 시, 파일 암호화 과정 실패) 과거에는 "main.php" 가 최근에는 "submit.php"가 이용되고 있다.

최근에 발견되는 Locky 에는 총 3곳의 IP가 존재하며, 1차로 저장된 IP 로의 접속 실패 시, 2차로 고유한 DGA 함수를 통해 생성한 도메인으로 접속을 시도한다. Locky 에서 사용하는 DGA 함수는 "GetSystemTime" 함수를 통해 얻은 년/월/일 정보를 통해 생성되는 구조이며, 매일 생성하는 도메인이 변경되는 특징을 갖는다. (하루에 12개 씩)

Locky 에서 사용하는 DGA 알고리즘은 현재까지 총 2가지 방식이 알려져있다. 아래의 사이트에서 각 알고리즘 별로 dga.py, dgav2.py 이름의 파이썬 코드를 공개하였다. 이를 통해 Locky 가 접속시도하는 도메인 정보를 사전에 미리 인지하여 탐지에 활용할 수 있다.

- [참고] https://github.com/baderj/domain_generation_algorithms/tree/master/locky

하지만, DGA 알고리즘에서 가장 핵심이 되는 것이 seed 값(4바이트 상수)으로 Locky 마다 가변적인 형태로 발견되고 있다. 즉, 알려지지 않은 Locky 에 대해 도메인을 사전에 인지하는 것은 어려운 구조를 갖는다.

최근 접수 Locky 에서는 DGA 방식은 동일하나 12개의 도메인이 순차적으로 생성되는 것이 아닌, 이 중 일부가 선택되어 접속하는 방식으로 변경되었다.

아래의 [그림-1], [그림-2]는 각각 과거/현재 DGA 함수를 호출하는 부분이다. 최근 파일에서는 DGA 함수 호출 전, CryptGenRandom 함수를 통해 얻은 난수값을 12로 나눈 나머지 값(0~11)이 DGA 함수 호출 시, 인지값으로 사용되는 것을 알 수 있다. 인자값은 12개 생성 도메인의 인덱스와 같은 기능을 하며, 해당 코드의 추가로 기존과 달리 접속하는 도메인이 중복/누락될 수 있는 구조를 갖는다.

[그림-1] DGA 함수 호출방식 (과거)

 

[그림-2] DGA 함수 호출방식 (최근)

 

Locky 에서 고유한 seed 값이 저장된 위치는 GetSystemDefaultLangID 함수 호출 전, 얻어오는 구조를 갖는다.

00A045F0 A1 4C48A100   MOV    EAX, DWORD PTR DS:[A1484C]  ; seed
00A045F5 A3 84AAA100   MOV    DWORD PTR DS:[A1AA84], EAX  
00A045FA A0 5648A100   MOV    AL, BYTE PTR DS:[A14856]
00A045FF 84C0          TEST   AL, AL
00A04601 74 3B         JE     SHORT 00A0463E
00A04603 FF15 2021A100 CALL   DWORD PTR DS:[A12120]       ; GetSystemDefaultLangID 

00A04B5D A1 00B0A100   MOV    EAX, DWORD PTR DS:[A1B000]
00A04B62 8078 0E 00    CMP    BYTE PTR DS:[EAX+E], 0
00A04B66 8B48 04       MOV    ECX, DWORD PTR DS:[EAX+4]    ; seed
00A04B69 890D 04B0A100 MOV    DWORD PTR DS:[A1B004], ECX
00A04B6F 74 40         JE     SHORT 00A04BB1
00A04B71 FF15 1C21A100 CALL   DWORD PTR DS:[A1211C]        ; GetSystemDefaultLangID

 

2. IP 주소 암호화

최근에는 접속에 사용되는 IP 정보가 암호화되어 저장된 형태이며, 실행 시점에 동적으로 복호화하여 생성하는 구조를 갖는다. 아래의 [그림-3], [그림-4]는 과거/현재 IP 주소가 존재하는 부분을 나타내며, 최근에는 IP주소가 동적으로 할당받은 메모리 공간에 쓰여진 것을 확인할 수 있다.

[그림-3] 파일내부에 저장된 IP정보 (과거) 

 

[그림-4] 실행시점에 동적으로 생성된 IP정보 (현재)

 

 

3. ZwQueryVirtualMemory API 후킹

최근 Locky 의 경우, "ZwQueryVirtualMemory" API를 후킹하는 기능을 갖는다. 아래의 [그림-5]는 과거/현재 Locky 내부의 문자열 데이터 중 일부이며, 최근에 "ntdll.dll", "NtQueryVirtualMemory" 문자열이 추가된 것을 알 수 있다.

[그림-5] 새롭게 추가된 API 후킹기능

 

아래의 [그림-6]은 후킹 전/후의 "ZwQueryVirtualMemory" API 시작부분을 나타낸다. 후킹 후, 함수 시작이 JMP 코드로 변경되었으며, 분기하는 위치는 Locky 랜섬웨어 코드영역이다.

[그림-6] ZwQueryVirtualMemory 함수(변경 전/후)

 

아래의 [그림-7]은 후킹을 통해 분기하는 곳 (0x008D0000)의 코드를 나타내며, "PAGE_EXECUTE" 속성이 부여된 메모리인 경우, 강제로 "MEM_IMAGE" 로 해당 메모리에 대한 상태(Type) 값이 설정되는 기능을 수행한다.

[그림-7]의 '0x008D0033' 주소의 [ECX]는 "MEMORY_BASIC_INFORMATION" 구조체를 나타내며, [ECX+14]와 [ECX+18]은 각각 해당 구조체의 'Protect'와 'Type' 값을 나타낸다.

typedef struct _MEMORY_BASIC_INFORMATION {
  PVOID  BaseAddress;
  PVOID  AllocationBase;
  DWORD  AllocationProtect;
  SIZE_T RegionSize;
  DWORD  State;
  DWORD  Protect;
  DWORD  Type;
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;

 

[그림-7] 후킹함수 부분

TEST [ECX+14], F0 명령어의 조건을 만족하는 경우는 메모리의 Protect 값이 아래의 4가지인 경우이며, 이 때 Type 값을 강제로 MEM_IMAGE(0x1000000)로 설정하는 기능을 한다.

  • PAGE_EXECUTE (0x10)
  • PAGE_EXECUTE_READ (0x20)
  • PAGE_EXECUTE_READWRITE (0x40)
  • PAGE_EXECUTE_WRITECOPY (0x80)

아래의 [그림-8]은 "ZwQueryVirtualMemory" 함수 시작 6바이트를 변경하는 코드를 나타낸다.

[그림-8] 메모리 패치하는 코드

 

이러한 기능의 후킹코드의 추가는 일반적인 악성코드에서는 잘 사용하지 않는 것으로 Locky 랜섬웨어가 동적으로 할당받은 메모리 공간(Private 영역)에 새롭게 생성한 자신의 PE 이미지(Execute 속성)가 다양한 분석툴 및 보안모듈(인젝션 코드탐지)에 의해 탐지/추출되는 것을 우회하기 위한 것으로 추정된다.

 

신고
Creative Commons License
Creative Commons License
Posted by yhayoung