Keep up with Ransomware

복호화 가능성이 존재하는 Nova 랜섬웨어

■ 개요

2026년 6월 랜섬웨어 피해 사례 수는 지난 5월(734건) 대비 상승한 811건으로 집계됐다.

2026년 6월 1일, 랜섬웨어 그룹 ShadowByt3$가 자신의 유출 사이트에 “BreachForums is Back(breachforu.ms)”을 게시한 것으로 확인됐다. ShadowByt3$ 측은 해당 게시물을 통해 다수의 BreachForums 클론 포럼 중 하나인 breachforu.ms 도메인을 홍보하며, 기존 BreachForums가 다시 운영되고 있다는 취지의 메시지를 남겼다. 또한 BreachForums의 재등장을 지지한다는 입장을 밝히고, 이용자들에게 해당 도메인에 접속해 가입할 것을 권유한 것으로 알려졌다. 해당 게시글은 ShadowByt3$가 BreachForums 재등장에 편승해 자신들의 영향력을 과시하고, 이용자 유입을 유도하기 위해 작성한 홍보성 게시물로 보인다.

ShinyHunters 그룹은 Oracle PeopleSoft PeopleTools에서 발견된 원격 코드 실행 취약점(CVE-2026-35273)을 악용했다. 해당 취약점은 Updates Environment Management 컴포넌트의 Environment Management Hub(PSEMHUB)에서 인증 검증이 제대로 수행되지 않아 발생한 것으로, 별도 인증 절차 없이도 HTTP 요청만으로 PeopleSoft 서버에서 임의 코드를 실행할 수 있다. 공격자는 이를 악용해 PeopleSoft 서버에 침투한 뒤, MeshCentral 기반 원격 제어 도구를 배포하고 내부 시스템을 탐색 가능하다. 이후 설정 파일에서 자격 증명을 수집 및 SSH 기반 측면 이동을 통해 최종적으로 데이터를 탈취한 것으로 확인된다.

한편, DragonForce 그룹이 Microsoft Teams의 TURN [1] 중계 서버 인프라를 악용한 사례도 확인됐다. 해당 공격은Go 기반 백도어를 통해 Microsoft의 Skype 기반 ID 서비스에서 익명 Teams 방문자 토큰을 획득한 뒤, 정상 Microsoft TURN 중계 서버로 연결을 설정하고, 이어 공격자의 실제 C2 [2] 서버로 세션을 생성하는 방식으로 수행되었다. 이로 인해 보안 장비에서는 공격자의 C2 통신이 정상 Microsoft Teams의 트래픽처럼 보였으며, 공격자는 이를 통해 피해 기업 내부에서 장기간 은닉 활동을 수행할 수 있었다.

이후 공격자는 BYOVD [3] 기법을 사용해 Huawei, Topaz Antifraud, Tower of Fantasy, K7 Security Anti-Malware 관련 취약 드라이버를 악용해 보안 프로세스를 종료한 뒤, 랜섬웨어를 실행하여 시스템을 암호화했다.

2026년 6월 한 달간 국내를 겨냥한 공격은 제조 분야 4건, IT 기업 2건, 로펌 1건 등 총 7건으로 집계됐다. 이는 지난 5월 3건 대비 4건 증가한 수치로, 국내 기업을 대상으로 한 랜섬웨어 공격이 지속적으로 증가하고 있음을 보여준다.

6월 국내를 대상으로 공격을 수행한 랜섬웨어 그룹은 Qilin, DragonForce, Settra, Anubis, BlackX로 확인되며, Qilin 랜섬웨어 그룹은 제조 분야 1건, IT 업체 1건, 로펌 분야 1건 등 총 3건의 공격 정황이 포착됐다. Qilin은 피해 기업의 샘플 자료를 업로드하였으나, 총 3건 중 2건은 현재 해당 그룹의 다크웹 유출 사이트에서 삭제된 것으로 파악됐다. DragonForce 그룹은 국내 IT 업체 1곳을 공격해 내부 자료를 탈취했다고 주장하였으나, 직접적인 자료가 확인되지 않아 신빙성은 낮은 것으로 판단된다. 또한 Settra, Anubis, BlackX 그룹 역시 각각 국내 제조업체를 공격해 내부 자료를 탈취했다고 주장했다.

■ 랜섬웨어 뉴스

그림 1. 랜섬웨어 동향

■ 랜섬웨어 위협

그림 2. 2026년 6월 랜섬웨어 위협 현황

• 새로운 위협

2026년 6월에는 신규 랜섬웨어 그룹 6개가 등장했다. 확인된 랜섬웨어 그룹은 모두 다크웹 유출 사이트를 운영하며 활동을 이어가고 있는 것으로 확인된다.

그림 3. Sevyware의 다크웹 유출 사이트

2026년 6월에 등장한 SevyWare 그룹은 현재까지 다크웹 유출 사이트에 피해 사례가 확인되지 않았지만, 이들은 RaaS[4] 계열사 프로그램을 내세워 초기 침투 브로커를 모집하고 있다. 또한, 자신들이 과거 SLSH 및 Conti 그룹의 구성원 출신이라고 주장했다. 특히 협상 지원, 관리 패널, 24시간 지원 등을 제공한다고 홍보하며, 기존 랜섬웨어 그룹의 운영 방식을 차용한 계열사 기반의 운영 체계를 구축하려는 움직임을 보이고 있다.

그림 4. Settra의 다크웹 유출 사이트

2026년 6월 등장한 Settra 그룹은 다크웹 유출 사이트에 총 22건의 피해 사례를 게시했다. 해당 그룹은 다크웹 유출 사이트에 피해 기업의 탈취 데이터를 단순히 나열하는 데 그치지 않고, 내부 데이터를 분석한 내용을 함께 작성해 피해 기업에 대한 추가적인 압박 수단으로 활용하고 있다.

• Top5 랜섬웨어

그림 5. 산업/국가별 주요 랜섬웨어 공격 현황

2026년 6월 TheGentlemen 그룹은 총 119건의 피해 사례를 게시하며 전체 위협 그룹 중 가장 많은 피해 건수를 기록했다. 이는 77건을 기록한 5월 대비 대폭 증가한 수치다. 해당 그룹은 자체 개발한 GentleKiller라는 EDR 킬러 프레임워크를 운용하고 있으며, 이를 계열사에 제공해 랜섬웨어 실행 전 보안 제품을 무력화하는 데 이용하고 있다. GentleKiller는 BYOVD 기법으로 취약한 드라이버를 로드한 뒤 커널 권한에서 보안 제품 관련 프로세스를 종료하는 방식으로 동작한다. 또한 다수의 변종을 통해 서로 다른 취약 드라이버를 악용할 수 있도록 설계되어, 특정 드라이버가 차단되더라도 다른 변종을 활용해 탐지를 우회할 수 있는 구조를 갖추고 있다.

Qilin 그룹은 2026년 6월 총 80건의 피해 사례를 게시하며, 5월 109건 대비 감소세를 보였다. 이는 2025년 10월 이후 매월 세 자릿수 이상의 피해 사례를 지속적으로 게시하며 높은 활동량을 유지해 온 Qilin 그룹이 처음으로 두 자릿수의 피해 사례를 기록한 것이다. 다만 피해 사례가 감소했음에도 불구하고, Qilin은 여전히 랜섬웨어 생태계에서 주요 위협 그룹 중 하나로 꼽힌다.

LockBit 그룹은 6월 45건의 피해 사례를 기록하며, 지난 5월 26건 대비 상승세를 보였다. 이들은 6월 9일 미국의 병원인 Sierra Vista Hospital을 공격해 내부 자료를 탈취했다고 주장했으며, 같은 시기 미국 컬럼비아에 위치한 정형외과 그룹인 Columbia Orthopaedic Group을 공격해 자료를 유출하겠다고 협박했다.

INC 그룹은 6월 31건의 피해 사례를 기록하며, 월 기준 4번째로 많은 피해를 일으킨 것으로 나타났다. 이들은 Fortinet FortiGate 장비를 겨냥한 대규모 자격 증명 탈취 캠페인인 ‘FortiBleed’와 연계된 정황이 포착됐다. 해당 캠페인은 인터넷에 노출된 FortiGate 방화벽 및 VPN 게이트웨이에서 관리자 자격 증명과 인증 정보를 수집한 뒤, 이를 내부망 침투 및 권한 상승에 활용한 것으로 알려졌다. FortiBleed 인프라 분석 과정에서는 INC 그룹의 협상 패널에 접근한 운영자의 흔적이 발견됐으며, 일부 FortiBleed 피해 조직이 INC 그룹의 피해 사례와도 중복되는 것으로 확인된다. 이는 FortiBleed를 통해 확보된 초기 접근 권한이 INC 랜섬웨어 배포로 이어졌을 가능성을 보여준다.

6월 기준 5번째로 많은 피해를 일으킨 그룹은 31건의 피해 사례를 기록한 Akira 랜섬웨어다. 대표적으로 이들은 6월 3일 미국 IT 기업인 Apptricity Corporation을 공격 후 내부 소스코드 및 비밀유지계약서가 포함된 12GB 분량의 데이터를 유출했다고 주장했다.

■ 랜섬웨어 집중 포커스

그림 6. Nova 운영자의 소셜미디어 발언

Nova 랜섬웨어는 2025년 3월 22일 ‘RALord’라는 이름으로 활동을 시작한 랜섬웨어 그룹이다. 이들은 2025년 4월 초 Nova로 리브랜딩한 바 있다. 이는 중국 기반의 RA World 그룹과 명칭이 유사해 발생한 혼선을 해소하고, RA World 측의 간섭에서 벗어나 독립적인 브랜드로 활동하기 위한 것으로 풀이된다.

그림 7. Nova의 다크웹 유출 사이트

Nova 랜섬웨어 그룹은 등장 이후 총 128건의 피해 사례를 다크웹 유출 사이트에 게시했다. 이들의 유출 사이트에는 피해 기업의 탈취 데이터 목록뿐만 아니라, RaaS[5] 프로그램 정책을 위반해 계열사에서 블랙리스트로 지정된 인물들의 정보도 기록된 것으로 확인됐다.

Nova 랜섬웨어 그룹은 2025년부터 꾸준히 활동하며, 국내를 대상으로 총 4건의 피해를 입힌 바 있다. 이에 본 보고서는 향후 발생할 수 있는 위협에 대비하기 위해 Nova 랜섬웨어 샘플 분석 내용을 공유하고자 한다.

그림 8. Nova랜섬웨어 개요

• 랜섬웨어 전략

그림 9. 랜섬웨어 공격 전략

Nova 랜섬웨어는 실행 인자를 통해 암호화 대상을 제어할 수 있도록 설계되어 있다. 공격자는 인자를 사용해 특정 경로를 암호화 대상으로 지정할 수 있으며, 백업 삭제 명령의 실행여부와 백업 관련 프로세스 및 서비스 종료 기능의 실행 여부도 각각 설정 가능하다. 이때 사용되는 주요 실행 인자와 기능은 아래 표와 같다.

인자 설명
encrypt-all 전체 암호화 모드
encrypt-path 지정된 경로 파일 암호화
win-def-kill Windows Defender 비활성화
del-shdw-cp 백업 복사본 삭제
readme-add 현재 디렉토리에 랜섬노트 생성
readme-add-all 수집된 전체 경로에 랜섬노트 생성
workers worker 복사본 생성 및 병렬 암호화 수행
self-delete 자가 삭제
delete-logs 이벤트 로그 삭제
kill-edr EDR 관련 프로그램 종료
kill-process 지정된 프로세스 목록 종료

표 1. 랜섬웨어 실행인자

Nova 랜섬웨어는 별도 인자 없이 실행될 경우, 실행 파일이 위치한 디렉터리를 기준으로 해당 디렉터리 내 파일을 암호화한다. 반면 `encrypt-all` 인자를 사용할 경우, 랜섬웨어 내부에 사전 정의된 대상 경로를 순차적으로 탐색 후 암호화 대상을 확인한다. 이 과정에서 특정 확장자와 파일명 등 일부 항목은 암호화 대상에서 제외된다. 확인된 탐색 대상 경로와 암호화 제외 대상은 아래 표와 같다.

암호화 대상 탐색 경로

C:\Windows\Temp, Documents, Desktop, Downloads, Music, Videos, %PROGRAMDATA%
암호화 예외 확장자 및 파일명
.exe, .me, xgWLckNV, worker

또한 원활한 암호화 수행을 위해 특정 프로세스를 우선 종료한다. 확인된 종료 대상 프로세스 목록은 아래와 같다.

종료 프로세스

MsMpEng.exe, NisSrv.exe, SecurityHealthService.exe, Sense.exe, CSFalconService.exe, CSFalconContainer.exe, SentinelAgent.exe, SentinelStaticEngine.exe, SentinelDynamicEngine.exe, carbonblack.exe, cb.exe, McAfeeEndpointSecurity.exe, Mcshield.exe, mfemms.exe, SophosUI.exe, SophosFS.exe, SophosHealth.exe, CylanceSvc.exe, CylanceUI.exe, avp.exe, ksde.exe, kavfs.exe, symantec.exe, sep.exe, SavService.exe, bdagent.exe, vsserv.exe

랜섬웨어는 분석 방해 및 탐지 회피를 목적으로 Windows Defender의 실시간 감시, 행위 기반 탐지, 스크립트 및 압축 파일 검사, 네트워크 기반 침입 방지 등의 탐지 기능을 비활성화한다. 또한 네트워크 파일에 대한 검사 기능을 중지하고, 복구를 방해하기 위해 VSS(Volume Shadow Copy Service)를 통해 생성된 백업 데이터를 삭제한다. 아래는 해당 목적으로 사용된 명령어 및 설정 목록이다.

VSS 삭제

vssadmin delete shadows /for <drive> /all /quiet
Windows Defender 탐지 및 검사 기능 비활성화

DisableRealtimeMonitoring
DisableBehaviorMonitoring
DisableScriptScanning
DisableIOAVProtection
DisableArchiveScanning
DisableIntrusionPreventionSystem
DisableRemovableDriveScanning
DisableBlockAtFirstSeen
DisableScanningMappedNetworkDrivesForFullScan
DisableScanningNetworkFiles

그림 10. 파일 암호화 방식

Nova 랜섬웨어는 암호화 과정에서 파일마다 랜덤하게 생성한 24바이트 값과 랜섬웨어 내부에 하드코딩된 값을 Argon2i 알고리즘에 사용해 32바이트 암호화 키를 생성한다. 이때 암호화 키 생성에 사용된 24바이트 값은 XChaCha20-Poly1305 암호화에 필요한 nonce로도 활용되며, 암호화된 파일의 앞부분에 저장된다. Nova는 파일 전체를 암호화하며, XChaCha20-Poly1305 특성에 따라 암호화된 파일 끝에 무결성 검증을 위한 16바이트 Poly1305 MAC을 기록한다.

이때 생성된 암호화 키를 별도로 암호화하지 않고, 키 생성에 사용된 랜덤 24바이트 값과 랜섬웨어 내부에 하드코딩된 값으로 키를 생성한다. 따라서 하드코딩된 값을 확보할 경우 파일 복호화가 가능하며, 상세 내용은 뒤에서 다룰 ‘Nova 랜섬웨어 복호화’에서 살펴볼 수 있다. 다만, 본 보고서에서 분석한 샘플은 Katana 1 버전에 해당하며, 이후 공개된 Katana 2.0 버전에서는 별도의 암호화 키 보호 방식이 추가된 것으로 보인다. 이번에 소개하는 분석 내용은 Katana 1 버전에 한해서만 유효한 내용이다.

• 랜섬웨어 대응방안

그림 11. 랜섬웨어 대응방안

Nova 랜섬웨어는 드라이브 및 디렉터리 탐색 등을 통해 암호화에 앞서 대상과 시스템 환경을 확인한다. 이에 대응하기 위해서는 ASR [6] 규칙을 활성화해 비정상적인 프로세스 동작과 의심스러운 파일 접근을 차단하고, 행위 기반 탐지 솔루션을 통해 비정상적인 파일 탐색, 프로세스 조회, 파일 접근 행위 등을 조기에 탐지해야 한다.

또한 EDR 솔루션 도입과 최신 보안 패치 적용을 통해 취약점 악용에 따른 침투나 비정상 행위를 신속히 식별하고 차단할 수 있도록 해야 한다. 백업 복사본은 별도의 네트워크 구간, 외부 저장소, 오프라인 매체 등에 주기적으로 분산 보관해 시스템이 암호화되더라도 데이터가 복구되도록 해야 한다. 이때 백업 장치에 대한 접근 권한을 최소화하고, 정기적인 복구 테스트를 실시해 백업 데이터의 무결성을 보장해야 한다.

또한 Nova 랜섬웨어는 이벤트 로그 삭제 기능도 포함하고 있으므로, 주요 시스템 로그는 로컬에만 저장하지 않고 별도의 로그 수집 서버나 SIEM으로 실시간 전송되도록 구성해야 한다. 감염 시스템에서 로그가 삭제되더라도 침해 행위 분석에 필요한 흔적을 확보하기 위해서다.

Nova 복호화

앞서 Nova 랜섬웨어를 분석한 결과, 해당 랜섬웨어는 파일마다 랜덤하게 생성한 24바이트 값을 암호화된 파일의 선두에 저장한다. 이 값을 랜섬웨어 내부에 하드코딩된 값과 함께 Argon2i 알고리즘에 입력하여 32바이트 암호화 키를 생성한다.

이후 생성된 암호화 키를 사용해 XChaCha20-Poly1305 알고리즘으로 파일 전체를 암호화하며, 암호화된 파일 끝에는 무결성 검증을 위한 16바이트 Poly1305 MAC을 기록한다. 이때 암호화 키 생성에 사용되는 24바이트 값은 암호화된 파일에 평문으로 저장된다.

이와 같은 구조적 특성으로 인해 랜섬웨어 내부에 하드코딩된 값을 확보할 경우, 암호화된 파일 선두에 저장된 24바이트 값을 이용해 암호화 키를 재생성할 수 있어 파일 복호화가 가능하다. 단, 이는 분석한 샘플과 동일한 구조 및 동일한 하드코딩 값을 사용하는 변종에 한해만 적용된다. 복호화 시도 전에는 원본 파일을 백업하고 테스트를 통해 복호화 가능 여부를 먼저 확인할 필요가 있다.

Nova 복호화 스크립트

#!/usr/bin/env python3

#python -m pip install argon2-cffi pynacl

import argparse

import hashlib

from pathlib import Path


from argon2.low_level import Type, hash_secret_raw

from nacl.bindings import crypto_aead_xchacha20poly1305_ietf_decrypt



NONCE_SIZE = 24

TAG_SIZE = 16

EXTENSION = ".xgWLckNV"


# Recovered from the Nova/Katana sample

NOVA_KATANA_SECRET = bytes.fromhex(

"8c87b3a06f7ba46c9b5236ad4e34e1fb981e283c9aa2550f1f8dc9df42c00727"

)



def derive_key(nonce: bytes) -> bytes:

return hash_secret_raw(

secret=NOVA_KATANA_SECRET,

salt=nonce,

time_cost=15,

memory_cost=1024,

parallelism=1,

hash_len=32,

type=Type.I,

)



def default_output_path(path: Path, overwrite: bool) -> Path:

if path.name.endswith(EXTENSION):

out = path.with_name(path.name[: -len(EXTENSION)])

else:

out = path.with_suffix(path.suffix + ".decrypted")


if overwrite or not out.exists():

return out


candidate = Path(str(out) + ".decrypted")

index = 1

while candidate.exists():

candidate = Path(f"{out}.decrypted.{index}")

index += 1

return candidate

def decrypt_file(path: Path, output: Path | None = None, overwrite: bool = False) -> Path:

data = path.read_bytes()

if len(data) < NONCE_SIZE + TAG_SIZE:

raise ValueError("file is too small to be a Nova/Katana encrypted file")


nonce = data[:NONCE_SIZE]

ciphertext = data[NONCE_SIZE:-TAG_SIZE]

tag = data[-TAG_SIZE:]

key = derive_key(nonce)


try:

plaintext = crypto_aead_xchacha20poly1305_ietf_decrypt(

ciphertext + tag,

b"",

nonce,

key,

)

except Exception as exc:

raise ValueError("authentication failed") from exc



def iter_targets(root: Path, recursive: bool):

if root.is_file():

yield root

return


walker = root.rglob("*") if recursive else root.glob("*")

for path in walker:

if path.is_file() and path.name.endswith(EXTENSION):

yield path



def sha256_file(path: Path) -> str:

h = hashlib.sha256()

with path.open("rb") as f:

for block in iter(lambda: f.read(1024 * 1024), b""):

h.update(block)

return h.hexdigest()

"8c87b3a06f7ba46c9b5236ad4e34e1fb981e283c9aa2550f1f8dc9df42c00727"

)



def derive_key(nonce: bytes) -> bytes:

return hash_secret_raw(

secret=NOVA_KATANA_SECRET,

salt=nonce,

time_cost=15,

memory_cost=1024,

parallelism=1,

hash_len=32,

type=Type.I,

)



def default_output_path(path: Path, overwrite: bool) -> Path:

if path.name.endswith(EXTENSION):

out = path.with_name(path.name[: -len(EXTENSION)])

else:

out = path.with_suffix(path.suffix + ".decrypted")


if overwrite or not out.exists():

return out


candidate = Path(str(out) + ".decrypted")

index = 1

while candidate.exists():

out = output if output is not None else default_output_path(path, overwrite)

if out.exists() and not overwrite:

raise FileExistsError(f"output already exists: {out}")


out.parent.mkdir(parents=True, exist_ok=True)

out.write_bytes(plaintext)

return out



def iter_targets(root: Path, recursive: bool):

if root.is_file():

yield root

return


walker = root.rglob("*") if recursive else root.glob("*")

for path in walker:

if path.is_file() and path.name.endswith(EXTENSION):

yield path



def sha256_file(path: Path) -> str:

h = hashlib.sha256()

with path.open("rb") as f:

for block in iter(lambda: f.read(1024 * 1024), b""):

h.update(block)

return h.hexdigest()



def clean_input_path(value: str) -> str:

return value.strip().strip('"').strip("'")



def main() -> int:

parser = argparse.ArgumentParser(

description=(

"Decrypt Nova/Katana .xgWLckNV files encrypted as "

"[24B nonce][ciphertext][16B tag]."

)

)

parser.add_argument("target", nargs="?", help="Encrypted .xgWLckNV file or directory")

parser.add_argument("-r", "--recursive", action="store_true", help="Recurse into subdirectories")

parser.add_argument("-o", "--output", help="Output path, only valid when target is a single file")

parser.add_argument("--overwrite", action="store_true", help="Overwrite existing output files")

parser.add_argument("--hash", action="store_true", help="Print SHA-256 of decrypted outputs")

args = parser.parse_args()


target_text = args.target


walker = root.rglob("*") if recursive else root.glob("*")

for path in walker:

if path.is_file() and path.name.endswith(EXTENSION):

yield path



def sha256_file(path: Path) -> str:

h = hashlib.sha256()

with path.open("rb") as f:

for block in iter(lambda: f.read(1024 * 1024), b""):

h.update(block)

return h.hexdigest()



def clean_input_path(value: str) -> str:

return value.strip().strip('"').strip("'")candidate = Path(f"{out}.decrypted.{index}")

index += 1

return candidate



def decrypt_file(path: Path, output: Path | None = None, overwrite: bool = False) -> Path:

data = path.read_bytes()

if len(data) < NONCE_SIZE + TAG_SIZE:

raise ValueError("file is too small to be a Nova/Katana encrypted file")


nonce = data[:NONCE_SIZE]

ciphertext = data[NONCE_SIZE:-TAG_SIZE]

tag = data[-TAG_SIZE:]

key = derive_key(nonce)


try:

plaintext = crypto_aead_xchacha20poly1305_ietf_decrypt(

ciphertext + tag,

b"",

nonce,

key,

)

except Exception as exc:

raise ValueError("authentication failed") from exc



def iter_targets(root: Path, recursive: bool):

if root.is_file():

yield root

return


walker = root.rglob("*") if recursive else root.glob("*")

for path in walker:

if path.is_file() and path.name.endswith(EXTENSION):

yield path



def sha256_file(path: Path) -> str:

h = hashlib.sha256()

with path.open("rb") as f:

for block in iter(lambda: f.read(1024 * 1024), b""):

h.update(block)

return h.hexdigest()

"8c87b3a06f7ba46c9b5236ad4e34e1fb981e283c9aa2550f1f8dc9df42c00727"

)



def derive_key(nonce: bytes) -> bytes:

return hash_secret_raw(

secret=NOVA_KATANA_SECRET,

salt=nonce,

time_cost=15,

memory_cost=1024,

parallelism=1,

hash_len=32,

type=Type.I,

)



def default_output_path(path: Path, overwrite: bool) -> Path:

if path.name.endswith(EXTENSION):

out = path.with_name(path.name[: -len(EXTENSION)])

else:

out = path.with_suffix(path.suffix + ".decrypted")


if overwrite or not out.exists():

return out


candidate = Path(str(out) + ".decrypted")

index = 1

while candidate.exists():

target_text = clean_input_path(input("Encrypted .xgWLckNV file or directory path: "))

if not target_text:

print("[!] No path entered")

return 1


recursive_answer = input("Search subdirectories too? (y/N): ").strip().lower()

if recursive_answer in ("y", "yes"):

args.recursive = True


target = Path(clean_input_path(target_text))

if args.output and not target.is_file():

parser.error("--output can only be used with a single file target")


targets = list(iter_targets(target, args.recursive))

if not targets:

print(f"[!] No {EXTENSION} files found")

return 1


ok = 0

failed = 0

for path in targets:

try:

output = Path(args.output) if args.output else None

out = decrypt_file(path, output=output, overwrite=args.overwrite)

ok += 1

if args.hash:

print(f"[+] {path} -> {out} sha256={sha256_file(out)}")

else:

print(f"[+] {path} -> {out}")

except Exception as exc:

failed += 1

print(f"[-] {path}: {exc}")


print(f"Done. success={ok} failed={failed}")

return 0 if failed == 0 else 2



if __name__ == "__main__":

raise SystemExit(main())for path in targets:

try:

output = Path(args.output) if args.output else None

out = decrypt_file(path, output=output, overwrite=args.overwrite)

ok += 1

if args.hash:



nonce,

key,

)

except Exception as exc:

raise ValueError("authentication failed") from exc



def iter_targets(root: Path, recursive: bool):

if root.is_file():

yield root

return


walker = root.rglob("*") if recursive else root.glob("*")

for path in walker:

if path.is_file() and path.name.endswith(EXTENSION):

yield path



def sha256_file(path: Path) -> str:

h = hashlib.sha256()

with path.open("rb") as f:

for block in iter(lambda: f.read(1024 * 1024), b""):

h.update(block)

return h.hexdigest()

"8c87b3a06f7ba46c9b5236ad4e34e1fb981e283c9aa2550f1f8dc9df42c00727"

)



def derive_key(nonce: bytes) -> bytes:

return hash_secret_raw(

secret=NOVA_KATANA_SECRET,

salt=nonce,

time_cost=15,

memory_cost=1024,

parallelism=1,

hash_len=32,

type=Type.I,

)



def default_output_path(path: Path, overwrite: bool) -> Path:

if path.name.endswith(EXTENSION):

out = path.with_name(path.name[: -len(EXTENSION)])

else:

out = path.with_suffix(path.suffix + ".decrypted")

if overwrite or not out.exists():

return out

candidate = Path(str(out) + ".decrypted")

index = 1

while candidate.exists():

if args.hash:

print(f"[+] {path} -> {out} sha256={sha256_file(out)}")

else:

print(f"[+] {path} -> {out}")

except Exception as exc:

failed += 1

print(f"[-] {path}: {exc}")


print(f"Done. success={ok} failed={failed}")

return 0 if failed == 0 else 2



if __name__ == "__main__":

raise SystemExit(main())

• IoCs

Hash(SHA-256)

1231d0b6cbba77151037fc117bcfbf9406e127e86407c1728a478a7a628bc4f3

■ 참고 사이트

Hookphish: (https://www.hookphish.com/blog/ransomware-group-shadowbyt3-hits-breachforums-is-back-breachforu-ms) The Hacker News: (https://thehackernews.com/2026/06/shinyhunters-exploits-oracle-peoplesoft.html) Security.com: (https://www.security.com/threat-intelligence/dragonforce-msteams-backdoor) Bleepingcomputer: (https://www.bleepingcomputer.com/news/security/gentlemen-ransomware-uses-multiple-edr-killers-to-disable-defenses/) Welivesecurity: (https://www.welivesecurity.com/en/eset-research/killing-me-gently-inside-gentlemens-edr-killer-framework/) Securityweek: (https://www.securityweek.com/fortibleed-campaign-linked-to-inc-lynx-ransomware-attacks/) Hackread.com: (https://hackread.com/fortibleed-credential-theft-in-lynx-ransomware)

  1. TURN(Traversal Using Relays around NAT): NAT 또는 방화벽으로 인해 단말 간 직접 통신이 어려울 때, 중계 서버를 통해 네트워크 트래픽을 전달하는 통신 프로토콜

  2. C2(Command and Control): 공격자가 악성코드 또는 침해된 시스템을 원격으로 제어하고 명령을 전달하기 위해 사용하는 통신 체계 또는 서버를 의미

  3. BYOVD(Bring Your Own Vulnerable Driver): 공격자가 정상적으로 서명된 취약점이 존재하는 드라이버를 시스템에 로드 후 이를 악용해 보안 제품을 우회하거나 악성 행위를 수행하는 기법

  4. RaaS (Ransomware-as-a-Service): 랜섬웨어를 서비스 형태로 제공해서 누구나 쉽게 랜섬웨어를 만들고 공격할 수 있도록 하는 비즈니스 모델

  5. RaaS (Ransomware-as-a-Service): 랜섬웨어를 서비스 형태로 제공해서 누구나 쉽게 랜섬웨어를 만들고 공격할 수 있도록 하는 비즈니스 모델

  6. ASR (Attack Surface Reduction): 공격자가 사용하는 특정 프로세스와 실행할 수 있는 프로세스를 차단하는 보호기능