1. 개요 (Overview)
1.1 분석 배경
DevMan 랜섬웨어는 2025년 초부터 활발히 유포되고 있는 악성코드로, ChaCha8 스트림 암호화를 기반으로 한 경량화된 암호화 방식을 채택하고 있습니다. DragonForce 랜섬웨어의 변종으로 추정되며, 유사한 암호화 메커니즘을 사용하지만 더 단순화된 구조를 가지고 있습니다. 본 보고서는 DevMan의 암호학적 구조를 정밀 분석하고, API 후킹을 통한 실시간 키 추출 기법과 복호화 전략을 제시합니다.
1.2 핵심 요약
- 경량화된 암호화 구조 : ChaCha8 스트림 암호화를 사용하여 빠른 암호화 속도를 달성합니다. 4번의 더블 라운드를 수행하여 성능과 보안성의 균형을 추구합니다.
- 파일별 독립 키 생성 : 각 파일마다 고유한 32바이트 키와 8바이트 nonce를 CryptGenRandom API를 통해 생성합니다.
- 단순화된 암호화 전략 : 복잡한 파일 크기별 전략 없이 일관된 암호화 방식을 적용합니다.
- 완벽한 복호화 가능성 : CryptGenRandom API 호출 시점에 생성되는 평문 키를 API 후킹으로 실시간 캡처할 경우, 완벽한 복호화가 가능합니다. 본 분석에서 개발한 복호화 스크립트를 통해 실증적으로 검증되었습니다.
2. 식별 정보 (Identification)
- Malware Family: DevMan
- Filetype: PE32 (Windows Executable)
- Hash (SHA256): df5ab9015833023a03f92a797e20196672c1d6525501a9f9a94a45b0904c7403
- Extension: .devman
- Target: Windows 기반 시스템
3. 분석 환경 및 도구 (Tools)
| 구분 | 도구명 (Tool) | 용도 (Purpose) |
| 정적 분석 | IDA Pro | ChaCha8 구현 루틴 및 암호화 로직 분석 |
| 동적 분석 | CryptGenRandom API Hooking (x86) | 실시간 키 및 nonce 추출 |
| 행위 분석 | Process Monitor | 파일 I/O 및 암호화 행위 추적 |
| 검증 도구 | Python | ChaCha8 복호화 스크립트 개발 및 PoC |
4. 암호화 기술 분석 (Technical Analysis)
4.1 전체 암호화 워크플로우
DevMan은 단순화된 암호화 모델을 기반으로 다음과 같은 4단계 프로세스를 거칩니다.
- 키 및 Nonce 생성 : CryptGenRandom API를 통해 파일별로 고유한 32바이트 ChaCha 키와 8바이트 nonce를 생성
- ChaCha State 초기화 : 생성된 키와 nonce로 ChaCha state를 초기화하고 카운터를 0으로 설정
- 파일 암호화 : ChaCha8 알고리즘을 사용하여 파일 전체를 암호화
- 파일명 변경 : 암호화된 파일의 확장자를 .devman으로 변경
4.2 키 생성 및 관리 (Key Generation)
4.2.1 CryptGenRandom을 통한 난수 생성
DevMan은 Windows CryptoAPI의 CryptGenRandom 함수를 사용하여 암호학적으로 안전한 난수를 생성합니다.
// 각 파일마다 고유한 키와 nonce 생성
CryptGenRandom(hProv, 32, chacha_key); // 32바이트 ChaCha Key
CryptGenRandom(hProv, 8, chacha_nonce); // 8바이트 ChaCha Nonce
4.2.2 ChaCha State 초기화
생성된 키와 nonce를 사용하여 ChaCha state를 초기화합니다.
- Constant : "expand 32-byte k" (ChaCha 표준 상수)
- Key : CryptGenRandom으로 생성한 32바이트 난수
- Counter : 0으로 초기화 (블록마다 1씩 증가)
- Nonce : CryptGenRandom으로 생성한 8바이트 난수
ChaCha State 구조 (64바이트):
| Constant (16B) | Key (32B) | Counter (8B) | Nonce (8B) |
4.3 ChaCha8 암호화 알고리즘
4.3.1 ChaCha8 키스트림 생성
ChaCha8은 일반적인 ChaCha20의 10번 더블 라운드 대신, 4번의 더블 라운드를 수행하여 성능을 최적화한 변형입니다.
ChaCha8 라운드 구조:
┌─────────────────────────────────┐
│ Initial State (64 bytes) │
│ [Constant | Key | Ctr | Nonce] │
└─────────────────────────────────┘
↓
┌───────────────┐
│ Double Round │ ← 4번 반복 (ChaCha8)
│ (QR 함수 8회) │
└───────────────┘
↓
┌─────────────────────────────────┐
│ Keystream (64 bytes) │
└─────────────────────────────────┘
4.3.2 Quarter Round 연산
Quarter Round (a, b, c, d):
a += b; d ^= a; d <<<= 16;
c += d; b ^= c; b <<<= 12;
a += b; d ^= a; d <<<= 8;
c += d; b ^= c; b <<<= 7;
4.3.3 ChaCha8 블록 생성
def chacha_block(key32, counter, nonce64, rounds=8):
constants = b"expand 32-byte k"
key_words = list(struct.unpack("<8I", key32))
nonce_words = list(struct.unpack("<2I", nonce64))
counter_low = counter & 0xffffffff
counter_high = (counter >> 32) & 0xffffffff
# Initial state 구성
state = [
struct.unpack("<I", constants[0:4])[0],
struct.unpack("<I", constants[4:8])[0],
struct.unpack("<I", constants[8:12])[0],
struct.unpack("<I", constants[12:16])[0],
] + key_words + [counter_low, counter_high] + nonce_words
working = state.copy()
# 4번의 더블 라운드
for _ in range(rounds // 2):
# Column round
quarter_round(working, 0, 4, 8, 12)
quarter_round(working, 1, 5, 9, 13)
quarter_round(working, 2, 6, 10, 14)
quarter_round(working, 3, 7, 11, 15)
# Diagonal round
quarter_round(working, 0, 5, 10, 15)
quarter_round(working, 1, 6, 11, 12)
quarter_round(working, 2, 7, 8, 13)
quarter_round(working, 3, 4, 9, 14)
# Add initial state
out = [(working[i] + state[i]) & 0xffffffff for i in range(16)]
return struct.pack("<16I", *out)
4.3.4 XOR 암호화 및 카운터 증가
# 생성된 키스트림과 평문을 XOR
for i in range(block_size):
ciphertext[i] = plaintext[i] ^ keystream[i]
# 블록마다 카운터 증가
counter++
4.4 파일 처리 아키텍처
DevMan의 파일 암호화 프로세스는 다음과 같습니다:
[파일 처리 과정]
1. 파일 열기 및 읽기
↓
2. CryptGenRandom 호출
- 32바이트 ChaCha 키 생성
- 8바이트 nonce 생성
↓
3. ChaCha State 초기화
- Constant: "expand 32-byte k"
- Key: 생성된 32바이트
- Counter: 0
- Nonce: 생성된 8바이트
↓
4. ChaCha8 블록 암호화
- 4번의 더블 라운드
- 키스트림 생성
- XOR 연산
↓
5. 암호화된 데이터 쓰기
↓
6. 파일명 변경
원본.확장자 → 원본.확장자.devman
4.5 정적 분석
CSP 초기화
- RSA-4096 공개키 임포트

- pbData (RSA1 시그니처)

- 5MB 작업 버퍼 할당

- 크리티컬 섹션 동기화 (5초 대기)

CSP(암호화 컨텍스트) 획득 시도




디렉터리 스캔
- 파일 검색

- 디렉터리 건너뛰기 (”.”, “..”, 리파스 포인트)

- 디렉터리 화이트/블랙리스트 체크 (목록 난독화 → 런타임 시, 복호화)

- 파일 확장자 화이트/블랙리스트 체크 (목록 난독화 → 런타임 시, 복호화)

암호화 전략 선택
1. ChaCha8 키 생성 + RSA 메타데이터 생성
- ChaCha 키 생성 (32바이트)

- Nonce 생성 (8바이트)

- ChaCha 상수 설정

- 카운터 초기화

- 파일명 메타데이터 저장

- RSA 공개키로 메타데이터 암호화

2. 파일 열기

3. 시스템 파일 제외 (목록은 난독화 → 런타임 복호화)

4. 파일 크기 읽기 및 암호화 전략 선택 시작




- 전체 파일 암호화 (작은 파일, 특수 파일(.dll/.exe))

- 부분 암호화 (중간 파일)

- Stride 암호화 (큰 파일)


- 암호화된 데이터 쓰기

5. 파일명 변경 (.infected 확장자 추가)


6. 키/IV 초기화

파일 암호화 (ChaCha8 + RSA 메타데이터)
- ChaCha8 상태 초기화

- ChaCha8 암호화 (4회 반복)

- 카운터 증가 (64바이트 블록 단위)

- 암호화된 데이터 쓰기

5. 복호화 기술 분석 (Decryption Strategy)
5.1 복호화 가능성 분석
DevMan 랜섬웨어는 다음과 같은 특성으로 인해 완벽한 복호화가 가능합니다:
- 키 생성 시점 취약점 : CryptGenRandom API 호출 시 생성되는 평문 키를 API 후킹으로 실시간 캡처 가능
- 메모리 덤프 가능성 : 암호화 진행 중 프로세스 메모리에 평문 키가 존재
- 파일별 독립 암호화 : 각 파일마다 별도의 키를 사용하므로, 해당 키만 있으면 개별 파일 복호화 가능
- 단순한 암호화 구조 : 추가적인 메타데이터나 RSA 암호화 없이 ChaCha8만 사용
5.2 ChaCha8 키 복구 메커니즘
DevMan의 암호학적 약점은 CryptGenRandom API 호출 시점에 생성되는 32바이트 키와 8바이트 nonce가 평문으로 메모리에 존재한다는 점입니다.
[취약점 구조]
CryptGenRandom API 호출
↓
평문 키 생성 (32B)
↓
★ API 후킹 지점 ★ ← 키 캡처 가능!
↓
ChaCha State 초기화
↓
파일 암호화 수행
5.3 ChaCha8 복호화 스크립트 개발
캡처한 키 정보를 활용하여 Python 기반 ChaCha8 복호화 스크립트를 개발했습니다.
스크립트 주요 기능:
- API 후킹 로그 파일 파싱 (키/nonce 추출)
- ChaCha8 state 초기화 및 키스트림 생성
- XOR 연산을 통한 복호화 수행
- 파일 시그니처 검증 (매직 넘버)
- 원본 파일 복원 및 저장
핵심 복호화 로직:
# 파일 시그니처 정의
COMMON_SIGNATURES = {
b"%PDF-": "pdf",
b"\x89PNG\r\n\x1a\n": "png",
b"PK\x03\x04": "zip",
b"MZ": "exe",
b"\xff\xd8\xff": "jpg",
}
def rotl32(v, n):
"""32비트 좌측 순환 시프트"""
return ((v << n) & 0xffffffff) | (v >> (32 - n))
def quarter_round(s, a, b, c, d):
"""ChaCha Quarter Round 연산"""
s[a] = (s[a] + s[b]) & 0xffffffff
s[d] ^= s[a]; s[d] = rotl32(s[d], 16)
s[c] = (s[c] + s[d]) & 0xffffffff
s[b] ^= s[c]; s[b] = rotl32(s[b], 12)
s[a] = (s[a] + s[b]) & 0xffffffff
s[d] ^= s[a]; s[d] = rotl32(s[d], 8)
s[c] = (s[c] + s[d]) & 0xffffffff
s[b] ^= s[c]; s[b] = rotl32(s[b], 7)
def chacha_block(key32, counter, nonce64, rounds=8):
"""ChaCha8 블록 생성"""
constants = b"expand 32-byte k"
key_words = list(struct.unpack("<8I", key32))
nonce_words = list(struct.unpack("<2I", nonce64))
# State 초기화
state = [
struct.unpack("<I", constants[i:i+4])[0]
for i in range(0, 16, 4)
] + key_words + [counter & 0xffffffff,
(counter >> 32) & 0xffffffff] + nonce_words
working = state.copy()
# 4번의 더블 라운드
for _ in range(rounds // 2):
# Column rounds
quarter_round(working, 0, 4, 8, 12)
quarter_round(working, 1, 5, 9, 13)
quarter_round(working, 2, 6, 10, 14)
quarter_round(working, 3, 7, 11, 15)
# Diagonal rounds
quarter_round(working, 0, 5, 10, 15)
quarter_round(working, 1, 6, 11, 12)
quarter_round(working, 2, 7, 8, 13)
quarter_round(working, 3, 4, 9, 14)
out = [(working[i] + state[i]) & 0xffffffff for i in range(16)]
return struct.pack("<16I", *out)
def chacha_keystream(key32, nonce64, rounds, length):
"""키스트림 생성"""
ks = bytearray()
ctr = 0
while len(ks) < length:
ks.extend(chacha_block(key32, ctr, nonce64, rounds))
ctr += 1
return bytes(ks[:length])
def parse_hook_log(path):
"""로그 파일에서 키-nonce 쌍 추출"""
text = Path(path).read_text(errors="ignore")
pairs = []
current_key = None
for line in text.split('\n'):
if 'Data: ' in line:
match = re.search(r'Data:\s+([0-9a-fA-F]+)', line)
if match:
data_hex = match.group(1).lower()
if len(data_hex) == 64: # 32바이트 키
current_key = data_hex
elif len(data_hex) == 16 and current_key: # 8바이트 nonce
pairs.append((current_key, data_hex))
current_key = None
return pairs
def decrypt_file(encrypted_file, key_hex, nonce_hex, output_file):
"""파일 복호화"""
# 키와 nonce를 바이너리로 변환
key = unhexlify(key_hex)
nonce = unhexlify(nonce_hex)
# 암호화된 데이터 읽기
ciphertext = Path(encrypted_file).read_bytes()
# 키스트림 생성 (ChaCha8, 8라운드)
keystream = chacha_keystream(key, nonce, rounds=8, length=len(ciphertext))
# XOR 복호화
plaintext = bytes(a ^ b for a, b in zip(ciphertext, keystream))
5.4 무결성 검증
복구된 파일의 유효성을 판단하기 위해 파일 헤더 시그니처(매직 넘버)를 검증합니다.
| 파일 유형 | 매직 넘버 (Hex) | 설명 |
| JPG | FF D8 FF | JPEG 이미지 파일 |
| PNG | 89 50 4E 47 0D 0A 1A 0A | PNG 이미지 파일 |
| 25 50 44 46 | PDF 문서 파일 | |
| EXE/DLL | 4D 5A (MZ) | Windows 실행 파일 |
| ZIP/DOCX | 50 4B 03 04 | 압축 파일 및 MS Office |
| ELF | 7F 45 4C 46 | Linux 실행 파일 |
6. 요약 및 결론 (Conclusion)
6.1 최종 평가
DevMan 랜섬웨어는 ChaCha8 스트림 암호화를 사용하는 경량화된 악성코드입니다. DragonForce의 변종으로 추정되지만, RSA 하이브리드 방식이나 복잡한 메타데이터 저장 없이 단순화된 암호화 구조를 가지고 있습니다. 그러나 키 생성 과정에서 CryptGenRandom API를 직접 호출하는 구조적 취약점으로 인해, API 후킹을 통한 실시간 키 캡처가 가능하며, 이를 활용한 완벽한 복호화가 실증되었습니다.
[취약점 요약]
CryptGenRandom API 호출
↓
평문 키 생성 (32B + 8B)
↓
★ API 후킹 지점 ★ ← 키 캡처 가능!
↓
ChaCha State 초기화
↓
파일 암호화 수행
↓
.devman 확장자 추가
6.2 대응 가이드
6.2.1 예방적 조치
- API 모니터링 강화
- CryptGenRandom, BCryptGenRandom 등 암호화 API 호출을 실시간 모니터링하는 EDR 솔루션 배치
- 의심스러운 대량의 난수 생성 패턴 탐지
- 행위 기반 탐지
- 대량 파일 접근 패턴 탐지
- .devman 확장자 추가 행위 차단
- 파일 시스템 보호
- 실시간 백업 시스템 구축 (VSS, 증분 백업)
- 중요 디렉터리에 대한 접근 제어 강화
6.2.2 사후 대응
- 초동 조치
- 감염 발견 즉시 시스템 격리 (네트워크 차단)
- 시스템을 종료하지 말고 메모리 덤프(Full Memory Dump) 수행
- 프로세스 메모리에서 CryptGenRandom 생성 키 추출 시도
- 키 복구 시도
- API 후킹 도구(Frida, Detours 등)를 사용하여 CryptGenRandom 호출 로그 확보
- 메모리 포렌식을 통한 평문 키 탐색
- 로그 파일 또는 메모리 덤프에서 32바이트 키 패턴 추출
- 복호화 수행
- 본 보고서에서 제공하는 ChaCha8 복호화 스크립트 활용
- 추출한 키/nonce로 암호화된 파일 복원
- 매직 넘버 검증을 통한 복호화 성공 여부 확인
6.3 기술적 특징 요약
| 항목 | 세부 내용 |
| 암호화 알고리즘 | ChaCha8 (4번의 더블 라운드) |
| 키 관리 | 파일별 독립 32바이트 키 + 8바이트 nonce |
| 암호화 전략 | 단순 전체 파일 암호화 |
| 메타데이터 | 없음 (키 정보 별도 저장 안함) |
| 확장자 | .devman |
| 복호화 가능성 | API 후킹 또는 메모리 덤프로 키 확보 시 100% 복구 가능 |
6.4 결론
DevMan 랜섬웨어는 ChaCha8을 사용하는 단순화된 암호화 구조를 가진 악성코드입니다. RSA 하이브리드 방식이나 복잡한 메타데이터 없이 순수 ChaCha8만 사용하여 빠른 암호화 속도를 달성하지만, 이는 동시에 복호화를 더 쉽게 만드는 요인이 됩니다. API 후킹을 통해 평문 키를 확보할 경우 즉시 복호화가 가능하며, 본 분석에서 개발된 복호화 도구는 DevMan 감염 피해 복구에 직접 활용될 수 있습니다. 향후 랜섬웨어 대응을 위해서는 암호화 API 모니터링 강화와 실시간 메모리 포렌식 체계 구축이 필수적입니다.
'랜섬웨어 분석 보고서' 카테고리의 다른 글
| Cloak 랜섬웨어 분석 보고서 (0) | 2026.02.06 |
|---|---|
| Akira 랜섬웨어 분석 보고서 (0) | 2026.02.06 |
| DragonForce 랜섬웨어 분석 보고서 (0) | 2026.02.03 |
| Lynx 랜섬웨어 분석 보고서 (0) | 2026.01.01 |
| Mamona 랜섬웨어 분석 보고서 (0) | 2026.01.01 |