모두의 dream

HermeticWiper Malware (Detail) 본문

분야/malware Analysis

HermeticWiper Malware (Detail)

오리꽥이로 2022. 4. 22. 00:33
Contents 접기

공부를 하며 정리한 내용으로 잘못된 내용이 있을 수 있습니다.

x64dbg와 IDA를 이용하여 분석했습니다.

블로그에는 가독성을 위해 IDA 코드를 위주로 사진을 첨부했습니다.

Detail 포스팅에는 정말 자세하게 함수 하나하나 분석한 내용이 정리되어 있습니다.

보고서: HermeticWiper Malware analysis report (tistory.com)

 

1. 정적분석

바이너리 파일

Windows7 32bit 에서 분석했다. (Windows10 에서도 정상적으로 작동 됨.)

 

Detect It Easy

32bit exe 실행파일임을 Detect It Easy 를 이용하여 확인했다.

 

virus total

virus total 검색을 통해 이 악성코드의 이름을 찾을 수 있었다.

이름은 HermeticWiper, 우크라이나·러시아 전쟁에서 사이버공격에 사용된 악성코드였다.

 

2. 동적분석

process explorer

그냥 실행하면 실행되지 않고, 관리자 권한으로 실행해야 된다.

 

process monitor & Unable to run OS 

process monitor 로 확인해본 결과 폴더들에 접근하면서 어떤 행동을 하고 있음을 확인했다.

화면상으로는 아무 반응이 없었고, 정말 오랬동안 켜놓고 있으면 블루스크린이 뜨고 재부팅을 하면 OS를 실행하지 못한다. (모든 행동이 끝나도 실행한 악성코드 바이너리는 프로세스에 계속 남아있다.)

 

3. 코드분석 (동적분석)

EntryPoint가 프로그램의 main 함수인 것 같았고, 바로 바이너리의 권한을 상승시키는 코드가 나왔다.

1. GetcurrentProcess를 이용해서 이 프로세스의 핸들값을 가져온다.

2. OpenProcessToken을 이용해서 이 프로세스에 Access Token이 있는지 확인하고 가져온다.

3. LookupPrivilegeValue를 통해 Accesses Token 속에 SeShutdownPrivilege, SeBackupPrivilege 권한이 있는지 확인한다.

4. 두 권한이 모두 있다면 AdjustTokenPrivileges를 이용하여 두 권한을 활성화 시킨다.

 

* SeShutdownPrivilege: 컴퓨터 종료 권한, SeBackupPrivilege: 시스템의 모든 파일을 자유롭게 읽고 쓸 수 있음. (권한 무시)

 

참고: 윈도우 권한 (Windows privilege) (tistory.com)

 

여기서 SeBackupPrivilege 권한은 텍스트로 바로 보였지만 SeShutdownPrivilege 권한은 텍스트로 보이지 않는 상태였다.

이 부분에 대한 해답은 아래 코드에 있었다.

GetModulFileNameW를 이용해서 현재 실행중인 파일의 경로를 가져온다.

만약에 GetModulFileNameW 값 반환에 실패하면 wsprintf를 이용해서 실행중인 파일의 경로를 저장해야 할 위치에 c* 문자열을 저장한다. 

그리고 FindFirstFileW을 통해 아래와 같이 파일의 정보가 저장된다.

파일의 정보들은 아래와 같다.

여기있는 파일의 정보들 중 cFileName, 파일의 이름 정보 중 가장 첫번째 단어만 가져와서 CharLowerW 를 이용하여 소문자로 바꿔준다.

 

그리고 파일명이 소문자 c로 시작하면 그 값을 이용하여 SeShutdownPrivilege 문자열이 저장되어 있는 곳으로 이동을 하게 되고 아래 사진처럼 비워져 있는 값을 77 00 6E, 50 00 72 값으로 채워주게 된다.

그래서 SeShutdownPrivilege 권한이라는 것을 알 수 있다.

만약 실행파일명이 hello일 경우 아래 사진처럼 엉뚱한 곳에 77 00 6E, 50 00 72 값이 채워진다.

 

권한 상승이 종료되면 반복문이 나온다.

sub_4029D0 함수의 반환 값에 따라 계속 반복을 하거나, ExitProcess로 인해서 프로그램이 종료된다.

 

디버깅을 해본 결과 관리자권한이냐 아니냐에 따라서 반복을 하거나 프로그램이 종료되는데, 함수 내부로 들어가서 자세한 내용을 분석해봤다.

 

GetModuleHandleW를 이용해서 kernel32.dll의 핸들값을 가져온다. (시스템콜 함수들을 사용하기 위한 과정)

그리고 GetProcAddress를 이용하여 Wow64DisableWow64FsRedirection, IsWow64Process 함수의 주소를 가져온다.

 

만약 IsWow64Process 함수를 정상적으로 불러왔다면 if 문 안으로 들어가게 된다.

여기서 IsWow64Process 함수를 통해 현재 프로세스의 실행 환경을 확인한다.

만약 64bit OS에서 32bit 파일이 실행된다면 WOW64 환경에서 실행되므로 IsWow64Process 결과가 1이 반환된다.

32bit OS에서 32bit 파일을 실행한다면 WOW64 환경과는 무관하므로 0이 반환된다.
(32bit OS 시스템 폴더: System32, 64bit OS 시스템 폴더: System32(64bit 프로세스)/SysWOW64(32bit 프로세스))

 

위에서 설명한 방식을 이용하면 OS가 64bit인지 32bit인지 구분할 수 있고, 맞는 bit에 따라 리소스를 불러오게 된다.

(FindResourceW: 리소스를 찾음, LoadResource: 리소스를 로드함, LockResource: 리소스를 포인터에 반환함)

 

위 코드대로라면 RCDATA 형식의 DRV_X86이라는 리소스의 핸들값을 이용해서 리소스를 불러온다.

리소스 불러오기 성공

 

VerSetConditionMask, VerifyVersionInfoW는 운영체제의 버전명을 가져오기 위해 사용된다.
(자세하게 어떤 이유로, 어떤 방식으로 함수를 사용하는지는 파악하지 못했음.)

 

참고: 프로세스와 스레드, 핸들(Windows)과 커널 (tistory.com)

 

위 코드는 리소스를 불러온 후에 나오는 코드다.

만약에 IsWow64Process 결과가 TRUE이고(WOW64), Wow64DisableWow64FsRedirection 함수를 정상적으로 불러왔다면 if 문 안에 있는 코드가 실행된다.

 

Wow64DisableWow64FsRedirection의 기능은 64bit에서 32bit 프로세스가 실행되면 sysWOW64 폴더에 접근하게 되는데(리다이렉트 해줌), 64bit 프로세스 폴더인 system32로 접근할 수 있도록 해준다.

 

RegOpenKeyW를 이용해서 레지스트리를 연다. (성공하면 핸들 반환)
(HKEY_LOCAL_MACHINE(0x80000002)\SYSTEM\CurrentControlSet\Control\CrashControl)

반환된 핸들과 RegSetValueExW 함수를 이용해서 CrashDumpEnabled라는 이름의 값을 0으로 바꿔준다.

그리고 RegCloseKey로 가져온 레지스트리 핸들을 닫아준다.

 

CrashDumpEnabled: 블루스크린과 같이 컴퓨터가 예기치 않게 중지될 경우 디버깅 정보를 다른 파일 형식(메모리 덤프 파일)에 기록해주는 역할. 

 

악성코드가 실행된 후 발생하는 로그들을 남기지 않기 위해서 하는 작업으로 보인다.

(윈도우10 64bit 에서 windbg로 테스트해볼 예정)

 

wnsprintf를 이용하여 Destination 이라는 변수에 \\\\.\\EPMNTDRV\\%u 문자열이 복사된다.

그리고 이 변수가 sub_401870 함수에 인자로 전달된다.
(이 함수 부분이 도저히 이해가 안되서 분석 보고서를 보다보니 어느정도 접근이 가능했다.)

 

함수 내부에 들어가면 CreateFileW를 이용해서 EPMNTDRV 라는 파일을 열려고 한다.

그래서 만약 EPMNDRV가 있어서 설치할 필요가 없다면 else로 빠지고 sub_401870 를 불러온 함수인 sub_4029D0 에서도 탈출한다.

EPMNDRV를 설치해야 된다면 바로 v5(CreateFileW 반환값)를 반환한다.

 

EPMNTDRV: EaseUs Partition Master, 파티션과 디스크 관리를 위한 드라이버.

드라이버 자체가 악성행위를 하는게 아닌, 정상적인 드라이버를 악용하는 행위라고 볼 수 있다.

 

GetSystemDirectoryW : 시스템 디렉토리 경로 검색.
PathAppendW: 시스템 디렉토리 경로 뒤에 Drivers라는 경로 추가
PathAddBackslashW: Drivers 경로 뒤에 슬래시 추가

 

pszDest 변수는 초반에 \??\ 문자열이 들어가 있었고, 그 뒤에 시스템 디렉토리+Drivers\ 경로까지 추가된다.

 

그리고 wcslen 이라는 함수를 통해 문자열의 길이를 계산하여 0x40 이라는 값을 반환하게 된다.

wcslen 함수 어셈 코드

 

v32 변수에는 소문자 a~z가 저장된다.

 

StrCatBuffW 함수로 v12 변수 뒤에 drv라는 문자열 추가. (최대 길이 4)
v12 변수: 프로세스 id를 이용하여 소문자 a~z 중 2개의 알파벳을 랜덤으로 뽑아서 추가됨. 

최대 길이를 4로 지정했으므로 랜덤 알파벳 2개와 dr 이라는 문자가 합쳐져서 v12 변수로 들어가게 된다.

 

아래 사진은 위 과정을 어셈으로 분석해서 정리한 글이다.

 

PathFileExistsW를 통해서 \??\C:\windows\system32\Drivers 경로가 존재할 경우 do while문을 탈출한다.

 

 

CreateFileW를 이용해서 C:\\windows\\system32\\drivers\\lxdr 이라는 파일을 만들어준다.

여기서 디버거를 사용해보니 CreateFileW의 반환값이 FFFFFFF 이었고, 관리자가 아닐 때 실행이 안된 이유가 이 부분 때문이 아닐까 하는 생각이 들었다.

 

그래서 관리자 모드로 실행한 뒤 확인해 봤더니 -1이 아닌 핸들값이 반환되었고 아래와 같이 파일이 정상적으로 생성되었다.

그리고 WriteFile을 이용해서 위에서 만든 임의의 파일에 DRV_X86 리소스 데이터를 넣어준다.

넣어주는 과정이 성공하고 임의의 파일 크기와 위에서 SizeofResource를 이용해서 불러온 리소스의 크기가 같다면 FlushFileBuffers를 이용해서 버퍼에 있는 데이터를 디스크에 저장한다. 

 

참고: 파일을 쓰는데... 비정상 종료 등으로 꺼지게 되면 - GpgStudy 포럼

 

다음으로 나오는 코드인데 sub_4023C0 함수의 역할을 찾는것은 일단 보류했다..

(v16은 드라이버의 주소인데 디버거 상에서는 딱히 변경점이 없어서 내부 코드를 분석해야 자세한 기능 파악이 가능할텐데 생각보다 복잡하다.)

 

LZOpenFileW 로 생성한 드라이버가 저장된 주소를 읽고 쓴다.

PathAddExtensionW 를 이용해서 생성한 드라이버 뒤에 확장자 "sys"를 붙인다.

LZOpenFileW 를 이용해서 (0x1002) 새로운 파일을 생성하는데 확장자가 붙은 새로운 파일이 생성된다.

LZCopy를 이용해서 압축을 품과 동시에 데이터를 복사한다. (리소스의 시그니처가 SZDD)

압축 전, 후

그리고 LZCopy 에 성공했다면 sub_403930 함수를 호출한다. (StrStrIW가 사용되는 if 문은 위에서 사용된 VerifyVersionInfoW 값 반환에 실패했을 경우 (XP) 진입하게 된다.)

 

sub_403930 인자로 생성한 드라이버 파일의 경로와 Destination 이라는 값이 전달되는데 Destination은 아래의 값이다.

 

알파벳과 dr 이라는 문자가 합쳐진 파일의 길이인 4 를 계산하고, 이 값을 아래 wcsncpy에서 사용한다.

 

wcsncpy를 이용해서 Destination(\\.\EPMNTDRV\%u) 앞에 4글자를 랜덤의 알파벳과 dr 이라는 문자가 합쳐진 파일 문자열로 덮어준다. (Destomatopm[v15] = 0 부분은 아직 용도를 모르겠다.)

 

destination: dbdr PMNTDRV\0

 

sub_403930 내부를 보자.

 

GetCurrentProcess와 OpenProcessToken 으로 Access Token 존재 유무를 파악한다.

존재한다면 LookupPrivielgeValueW를 이용해서 Access Token 속 SeLoadDriverPrivilege 권한이 있는지 확인한다.

AdjustTokenPrivileges를 이용해서 SeLoadDriverPrivilege 권환을 활성화 한다.

 

SeLoadDriverPrivilege: 장치 드라이버 로드 및 언로드

 

지금까지는 드라이버를 시스템에 설치하는 과정이었다면, 이제부터는 로드를 한다.

 

권환이 성공적으로 활성화 되면 드라이버가 설치된 경로를 제대로 가져왔는지 if문으로 확인하고 안으로 진입한다.

OpenSCManagerW를 이용해서 윈도우 서비스(백그라운드에서 실행되는 프로그램)를 제어한다.

OpenServiceW를 이용해서 "드라이버.sys " 서비스 열기를 시도한다. (기존 서비스 열기. 없는 서비스면 열기 실패)

열기에 성공하면 위에 있는 if(v10) 안에 있는 코드들이 작동된다.

(권한들이 숫자로 들어가 있는데 아마 여러개의 권한을 주기 위해서 더한 값?? 이지 않을까 하는 예측..)

 

OpenSCManagerW: 0x3 -> SC_MANAGER_CREATE_SERVICE (0x2) + SC_MANAGER_CONNECT (0x1)

OpenServiceW: 0x16 -> SERVICE_START (0x0010) + SERVICE_QUERY_STATUS (0x0004) + SERVICE_CHANGE_CONFIG (0x0002)

 

각각의 권한에서 사용할 수 있는 함수들이 있는데, 코드에서 사용된 함수들과 비교해보면 모두 일치한다.

 

OpenServiceW 열기에 실패하면 아래 코드가 실행된다.

GetLastError의 결과값이 1060 일때 아래 if문은 pass 한다. (1060: 지정된 서비스가 설치된 서비스로 존재하지 않습니다. 아직 드라이버가 서비스로 돌아가지 않기 때문에 1060이 반환되는 것으로 추정.)

 

현재 "드라이버.sys " 가 등록되지 않아서 OpenServiceW에 실패했고, 반환값으로 1060이 나왔으므로 CreateServiceW로 드라이버 서비스를 만들어준다.

 

StartServiceW로 서비스를 start 해준다.
(sleep은 start 를 기다려주는거 같고, 최대 5번까지 StartService를 시도하는 것 같다.)

그리고 CloseServiceHandle이 호출되고 함수를 탈출한다.

 

 

성공적으로 서비스를 실행시키면 if(v33) 내부로 진입한다.

wsprintfW를 이용해서 SYSTEM\\CurrentControlSet\\services와 Destination에 드라이버 랜덤 이름 문자열을 이어주고 RegDeleteKeyW로 HKEY_LOCAL_MACHINE 레지스트리에 있는 설치한 드라이버 관련 키 정보를 삭제한다.

그리고 DeleteFile로 system32에 있는 driver 폴더 속 설치한 드라이버 파일도 삭제한다.

PathFindExtensionW과 DeleteFileW를 이용해서 확장이 붙어있지 않은 드라이버 파일까지도 삭제해준다.

그리고 함수를 탈출한다.

 

정상적으로 서비스를 불러왔다면 아래 코드를 실행하게 된다.

OpenSCManagerW를 이용해서 윈도우 서비스(백그라운드에서 실행되는 프로그램)를 제어한다.

(SC_MANAGER_ALL_ACCESS (0xF003F))

 

OpenServiceW를 이용해서 "vss" 서비스 열기를 시도한다. (기존 서비스 열기. 없는 서비스면 열기 실패)

(SERVICE_STOP (0x0020), SERVICE_CHANGE_CONFIG (0x0002))

 

ChangeServiceConfigW를 이용해서 vss 서비스를 실행하지 못하도록 바꿔준다.

(vss 서비스는 백업을 할때 사용하는 서비스로, 실행하지 못하도록 함으로써 복구를 막는다.)

 

이 부분이 OS를 불러올 수 없게 하는 부분이다.

 

GetModuleFileNameW에 성공하면 이 부분은 패스한다.

 

 

sub_401D60에 들어가면 아래 코드가 나온다.

wnsprintf를 이용해서 PhysicalDrive 문자열 뒤에 0~100 까지 붙여준다.

(PhysicalDrive0~100: MBR 영역을 포함한 물리 디스크의 영역)

 

그리고 이 값이 sub_401870 함수 (인자: pszDest, &v26, v25)로 전달된다.

 

CreateFileW를 이용해서 PhysicalDrive0 ~ 100까지의 드라이버 파일 열기를 시도한다. (핸들 반환)

 

열었다면 DeviceIoControl을 사용한다. 

DeviceIoControl: 지정된 장치 드라이버로 제어 코드를 직접 전송하여 해당 장치가 해당 작업을 수행하도록 하는 함수

0x2D1080: IOCTL_STORAGE_GET_DEVICE_NUMBER, 장치 유형, 장치 번호 및 분할 가능한 장치의 경우 장치의 파티션 번호를 검색합니다.

 

파티션 번호 검색?? -> 어쨋든 작동시켜보면 7이 반환된다.

 

참고: Windows IOCTL reference (ioctls.net)

 

위에서 반환된 OutBuffer의 값이 7이라면 다음 단계로 진행된다. (DeviceIoControl이 성공했을 경우 반환되는 값인데 7이 무슨 값인지 모르겠음)

 

그리고 쭉 내려가면 if (a2) 내부로 들어가게 된다.

v6= DeviceIOControl

 

0x700A0: IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, 실제 Disk의 지오메트리에 대한 확장 정보(타입, 실린더 수, 실린더당 트랙 수, 트랙당 섹터 수, 섹터당 바이트 수)를 검색합니다.

 

디스크 지오메트리 확장 정보 검색: 1E98 반환.

어떤 의미일까... 모르겠다..

 

결국 최종적으론  createfile로 반환된 핸들값이 result로 들어가고 return 된다.

 

 

다시 sub_401D60 함수.

v6에는 PhysicalDrive0 ~ 100 까지의 핸들 값.

 

DeviceIOControl을 이용한다.

IOCTL_DISK_GET_DRIVE_LAYOUT_EX(0x70050), 디스크의 파티션 테이블에 있는 각 항목에 대한 확장 정보를 검색합니다.

 

만약 DeviceIOControl 이 실패했다면 위 코드가 동작되고, 성공했다면 아래 코드가 동작된다.

아래 GetProcessHeap(), HeapAlloc, SetFilePointerEx, ReadFile 을 통해서 MBR영역의 데이터를 가져오는 것 같다.

MBR?

 

위 코드에서 아래 코드가 중요해 봐서 확인해 봤다.

a3(v6, v17, v29, v25[1], v34[1].LowPart, v34[1].HighPart);

 

a3은 sub_401D10 함수이다.

 

인자값 정리.

v6: PhysicalDrive0~100 까지 핸들 값.

v17: &v40인데, 아까 위해서 읽어온 MBR 영역 데이터들이 들어가 있음.

v25: v25[0]은 PhysicalDrive0~100 OutBuffer 값. (아까 7이었음.) v25[1]은 모르겠다..

 

sub_401D10 함수 내부.

 

sub_401D10 함수 내부.

암호화를 하는 함수들인데.. 아마 위에서 가져온 내용들이 PhysicalDrive0~100 이니까 얘들을 암호화 시키는게 아닐까 싶다... 

 

근데 아래에 FAT, NTFS 종류에 따라 디스크를 손상시키는 방식이 달라지는데 이건 어떤 과정인지 아직 파악하지 못했다.

 

이 부분도 아직 이해하지 못한 부분이다.

 

위에서 말했던 FAT, NTFS 종류에 따라 디스크를 손상시키는 방식이 달라지는 곳이다.

sub_401D60은 위에서 본 함수와 동일하고, sub_401B80이 아래와 같다.

lstrcmpA로 NTFS 문자열인지 비교하는 구간이 있는데, 여기서 String1은 MBR에서 확인을 한다.
(MBR 불러오는 코드는 위에 있었음.)

MBR

만약 NTFS 라면 0이 반환 되면서 아래 코드가 진행된다.

sub_401590이 위에서 봤던 CryptGenRandom 등의 암호화 함수가 있던 그 함수이다.

 

 

 

'분야 > malware Analysis' 카테고리의 다른 글

Onyx Ransomware Analysis Report  (0) 2022.05.09
HermeticWiper Malware Analysis Report  (2) 2022.04.28
Comments