보안을 공부하면서 "랜섬웨어가 어떻게 동작하는가"를 코드 레벨에서 이해하고 싶었다.
방어를 잘 하려면 공격을 알아야 한다.
GitHub: ye11oc4t/sdev_ransomware

왜 만들었나

보안 공부를 하다 보면 랜섬웨어 사고 분석 자료를 자주 만나게 된다. "암호화로 파일을 암호화하고, 암호화 키를 인질로 돈을 갈취한다"는 설명은 개념적으로 알지만, 실제로 어떤 코드가 어떤 순서로 실행되는지 직접 구현해보기 전까지는 피상적인 이해에 머무는 느낌이 있었다.

그래서 직접 만들어보기로 했다. "문서 파일을 찾아 .sdev 확장자로 암호화하는 랜섬웨어"이다.

교육 목적의 개인 실습 프로젝트로, 가상머신 격리 환경에서만 테스트했다.


구조 설계

랜섬웨어는 크게 세 단계로 동작한다고 봤다. 설정 → 실행 → 저장. 이 흐름대로 각 모듈을 설계했다.

설정부
├── Windows-setup.h  : OS 버전 확인, 사용자명, 타겟 디렉토리 설정
├── aes_main.h       : AES 암호화 키 생성
└── Socket-setup-client.cpp : 공격자 서버와의 소켓 통신 (키 수신)

실행부
├── Filetype-check.h : 파일 헤더 hex로 파일 유형 판별
├── aes.h            : tiny-AES 라이브러리 기반 파일 암호화
└── 반복문           : 전체 디렉토리 순회하며 실행부 반복

저장부
├── .sdev 파일 생성  : 암호화된 파일을 .sdev 확장자로 저장
└── 원본 파일 삭제   : 암호화 완료 후 원본 제거

설정부: Windows 환경 감지

랜섬웨어가 처음 실행되면 가장 먼저 피해자 시스템의 환경을 파악해야 한다. Windows 11에서는 OneDrive가 기본 경로에 삽입되는 등 버전별로 파일 경로가 달라지기 때문이다.

Windows-setup.h에서 NetWkstaGetInfo로 Windows 버전의 major/minor 번호를 가져오고, 추가로 cmd /c ver를 파이프로 실행해서 빌드 넘버까지 추출한다. 빌드 넘버 22000을 기준으로 Windows 10과 11을 구분했다.

FILE* fp = _popen("cmd /c ver", "r");
fgetc(fp);
fgets(Window_version, 1024, fp);
fclose(fp);

// 빌드 넘버로 Win10/11 구분
if ((dwMajor == 10) && (dwMinor == 0)) {
    if (buildNumber < 22000) return "WIN_10";
    else if (buildNumber > 22000) return "WIN_11";
}

버전 확인 후 사용자명을 기반으로 타겟 디렉토리를 설정한다. Documents, Desktop, Downloads, AppData\Temp — 실제 랜섬웨어가 주로 노리는 경로들이다.


소켓부: C2 서버와의 키 교환

랜섬웨어의 핵심 설계 원칙 중 하나는 암호화 키를 피해자 시스템에 남기지 않는 것이다. 키가 로컬에 있으면 복호화할 수 있기 때문이다.

소켓부는 이 문제를 다룬다. 피해자(클라이언트)가 실행되면 자신의 IP와 포트를 공격자 서버로 전송한다. 서버는 이 정보를 받아 해당 클라이언트용 AES 키를 생성해서 blocking 방식으로 전송한다. 전송된 키는 HKEY_CURRENT_USER\Environment\KEY 레지스트리에 삽입된다.

이 구조에서 키는 서버만 가지고 있다. 피해자가 돈을 내야 키를 받을 수 있는 구조가 여기서 성립한다.


파일 유형 판별: 확장자가 아닌 헤더 hex로

Filetype-check.h에서 조금 신경 쓴 부분이다. 파일 유형을 확장자로 판별하지 않고 파일 헤더의 magic bytes(hex signature) 로 판별했다.

uint8_t png_header[8]  = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
uint8_t pdf_header[4]  = { 0x25, 0x50, 0x44, 0x46 };
uint8_t mp4_header[8]  = { 0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70 };
// docx, pptx, xlsx는 모두 ZIP 기반이라 동일한 헤더
uint8_t docx_pptx_xlsx_header[8] = { 0x50, 0x4B, 0x03, 0x04, ... };

파일을 binary 모드로 열어서 앞 8바이트를 읽고 memcmp로 비교한다. 확장자를 바꿔서 탐지를 우회하려는 시도를 막을 수 있다.

docx, pptx, xlsx가 모두 같은 헤더를 가지는 이유도 이 과정에서 직접 확인했다. 세 형식 모두 내부적으로 ZIP 아카이브 구조를 쓰기 때문에 magic bytes가 동일하다(PK\x03\x04). 확장자 말고 파일 내부 구조로 구분해야 한다는 걸 직접 코드로 확인한 순간이었다.


암호화부: tiny-AES 라이브러리

암호화는 GitHub에 공개된 tiny-AES 라이브러리를 사용했다. 외부 의존성 없이 AES-128/192/256을 C로 구현한 라이브러리다. aes.h를 include해서 파일 단위로 AES 암호화를 적용했다.

암호화 흐름은 이렇다.

1. 파일 헤더 체크 → 타겟 파일 유형 확인
2. 파일 읽기 → AES 암호화 적용
3. 암호화된 내용을 같은 경로에 .sdev 확장자로 저장
4. 원본 파일 삭제

저장부를 반복문 내부에 삽입해서 전체 디렉토리를 순회하면서 파일 하나가 처리될 때마다 바로 .sdev로 저장하고 원본을 삭제하는 방식을 택했다. 반복문 횟수를 최소화하기 위한 설계다.


만들면서 배운 것

파일이 생각보다 단순하지 않다. 처음엔 파일을 읽고 암호화해서 저장하는 게 쉬울 거라 생각했다. 실제로는 Windows API의 파일 핸들링, 바이너리 모드 읽기/쓰기, 경로 처리의 edge case들이 생각보다 많았다.

소켓 통신과 레지스트리 조작은 별개의 공부였다. WinSock2로 소켓을 설정하고 레지스트리에 키를 쓰는 건 순수 C++ 코딩과는 다른 Windows API 지식이 필요했다. RegSetValueEx, NetWkstaGetInfo 같은 함수들을 처음 써봤다.

magic bytes 식별이 생각보다 재미있었다. PDF가 %PDF로 시작하고, PNG가 고정된 8바이트 시그니처를 가지고, docx가 사실 ZIP이라는 걸 코드로 직접 확인하는 과정이 포렌식 관점에서도 의미 있었다.

랜섬웨어가 왜 탐지하기 어려운지 이해됐다. 동작 자체는 파일 읽기 → 암호화 → 쓰기의 반복이다. AV 입장에서 이 행동 패턴을 악성으로 판단하려면 행위 기반 탐지가 필요한데, 정상적인 암호화 소프트웨어와 구분하는 게 쉽지 않다는 걸 만들면서 실감했다.


이 프로젝트는 랜섬웨어 공격이 실제로 어떻게 구성되는지를 코드 레벨에서 분해해보기 위해 진행했다.

직접 구현을 통해 흐름을 따라가보니(비록 편의상 단순한 암호 라이브러리를 사용했지만), 침투와 파일 암호화 과정의 기본적인 메커니즘 자체는 실제 공격과 동일한 구조를 가진다는 점을 확인할 수 있었다.

이러한 경험을 바탕으로, 향후 랜섬웨어 사고 대응이나 포렌식 과정에서
“이 악성코드는 어떤 순서로 파일을 암호화했는가?”와 같은 질문에 대해 보다 구조적으로 접근하고 추론할 수 있을 것이라 생각한다.


GitHub: ye11oc4t/sdev_ransomware
사용 라이브러리: kokke/tiny-AES-c
⚠️ 교육 목적으로만 제작됨. 가상머신 격리 환경에서만 테스트.