Back to Home
Red Team

CustomC2 vs Cobalt Strike — CS와의 비교분석

CloakCat

Author

CloakCat

Time

8 min read

Read by

10

CloakCat vs Cobalt Strike

들어가며

이전 글에서 CloakCat의 설계 철학과 아키텍처를 다뤘다. Phase 0~8까지 전부 완료한 지금, 솔직한 질문을 던져볼 때다. 벤치마킹 대상인 Cobalt Strike와 비교하면 어디까지 왔고, 어디가 부족한가?

이 글은 감상이 아니라 코드 기반 분석이다. 전체 워크스페이스 ~14,700 LOC를 크레이트별로 뜯어보고, CS의 주요 기능과 1:1로 비교 분석한 결과를 공유한다.


1. 현재 규모

| 크레이트 | LOC | 파일 수 | 역할 |
|----------|-----|---------|------|
| cat-agent | 6,096 | 20 | 비콘 에이전트 |
| cat-server | 2,817 | 13 | 팀서버 |
| cat-cli | 2,079 | 15 | 운영자 CLI |
| cloakcat-protocol | ~1,000 | 7 | 공유 타입/암호화 |
| tools/shellcode-loader | ~2,700 | 20+ | Shellcode 실행기 |
| **합계** | **~14,700** | **75+** | |

에이전트가 전체의 41%를 차지한다. 포스트 익스플로잇(토큰, 횡이동), BOF 로더, sRDI 변환기가 다 에이전트에 들어있으니 당연한 비율이다. 서버는 리팩토링을 거쳐서 handler/service/db 레이어가 깔끔하게 분리된 상태고, 프로토콜 크레이트는 의도적으로 가볍게 유지했다.


2. Cobalt Strike와 1:1 비교

제작에 성공한 기능

Beacon 체크인 루프 — 완성도 90%

Sleep + Jitter + 지수 백오프가 모두 구현되어 있고, 서버 측 long-poll은 tokio::sync::Notify 기반이라 CS의 busy-wait보다 효율적이다. 다만 CS의 interactive 모드(sleep 0, 실시간 응답)는 아직 없어서 10%를 뺐다.

리스너 (HTTP/HTTPS) — 완성도 85%

다중 리스너 동시 운용, 런타임 추가/제거, rcgen 기반 자체서명 인증서 자동 생성까지 된다. 부족한 건 리디렉터와 도메인 프론팅 — 이건 인프라 구성의 영역이라 코드만으로 해결되지 않는 부분도 있다.

Malleable C2 프로파일 — 완성도 70%

TOML 기반 프로파일 엔진이 471 LOC로 구현되어 있고, body transform(XOR, base64, prepend/append)과 헤더 커스터마이징이 동작한다. CS 대비 부족한 건 data_jitter, http-stager, dns-beacon, process-inject 블록들이다. CS의 자체 DSL(.profile)보다 TOML이 파싱은 안정적이지만 표현력은 떨어진다.

Shell 명령 실행 — 완성도 95%

300초 타임아웃 + 1MB 출력 제한이 포함되어 있어서 CS와 거의 동등하다. 오히려 CS보다 방어적 코딩이 더 잘 되어 있는 영역이다. sleep 999999로 비콘이 죽는 일은 없다.

파일 전송 — 완성도 80%

512KB 청크 기반 업로드/다운로드가 동작하고, 50MB까지 전송 가능하다. CS의 다운로드 큐잉이나 프로그레스 바 수준의 UX는 없지만 기능적으로는 충분하다.

토큰 조작 — 완성도 75%

steal_token, make_token, rev2self 핵심 3종이 구현되어 있다. CS 대비 부족한 건 getuid, getsystem, 그리고 권한 상승 자동화 파이프라인이다. make_tokenLOGON32_LOGON_NEW_CREDENTIALS만 사용하기 때문에 네트워크 인증에만 적용되고 로컬 프로세스 생성에는 영향이 없다는 점도 CS와 다르다.

Lateral Movement — 완성도 70%

PsExec(SCM 기반)과 WMI(COM vtable 직접 호출) 두 가지가 613 LOC로 구현되어 있다. 특히 WMI는 windows-sys의 COM 바인딩을 쓰지 않고 vtable 오프셋을 수동으로 계산해서 호출하는데, 외부 의존성을 최소화한 대신 유지보수가 까다롭다. CS의 jump winrm, DCOM, pass-the-hash는 아직 없다.

BOF 실행 — 완성도 80%

COFF 파서(535 LOC) + 인메모리 로더(394 LOC) + BeaconAPI 호환 레이어(368 LOC, 15개 이상 함수)가 구현되어 있다. AMD64 리로케이션(REL32, ADDR64, ADDR32NB)을 지원하고, 실행은 별도 스레드에서 30초 타임아웃으로 격리된다. 다만 BeaconPrintf가 C varargs를 실제로 확장하지 않아서 %s, %d 포맷 문자열이 그대로 출력되는 문제가 있다. 실제 BOF를 돌리면 출력이 깨질 수 있는 부분이다.

SOCKS5 프록시 — 완성도 75%

리버스 SOCKS5가 RFC 1928 준수로 구현되어 있다(서버 373 LOC + 에이전트 174 LOC). 세션당 5분 타임아웃에 에이전트당 10세션 제한이 있어서 장시간 proxychains nmap 스캔 같은 건 중간에 끊길 수 있다.

sRDI — 완성도 85%

PE 파서(1,054 LOC) + DLL→Shellcode 변환기(1,693 LOC)가 구현되어 있다. PEB 워킹, Import 해석, 리로케이션 적용이 모두 동작하고, PE 헤더 제로화 옵션도 있다. r2로 디스어셈블해서 stub 코드를 검증했을 때 PEB 접근, VirtualAlloc, 섹션 매핑, IAT 해석, DllMain 호출 전 과정이 올바르게 생성되었다.

CS에 있고 CloakCat에 없는 것 (중요도 순)

여기가 솔직해져야 하는 부분이다.

Sleep Mask — 최상 우선순위. 비콘이 sleep 중일 때 메모리에 코드가 평문으로 노출된다. Windows Defender의 메모리 스캔 한 방에 잡힌다. malleable.rsStageConfig.sleep_mask 필드는 존재하지만 실제 구현은 없다. EDR 환경에서 생존하려면 반드시 필요한 기능이다.

Process Injection — 최상 우선순위. 현재 모든 포스트 익스플로잇이 에이전트 프로세스 안에서 실행된다. 에이전트 프로세스가 죽으면 모든 기능이 날아간다. CS의 inject, shinject, dllinject에 대응하는 게 없어서 프로세스 마이그레이션이 불가능하다.

execute-assembly — 최상 우선순위. CLR 호스팅으로 .NET 어셈블리를 인메모리 실행하는 기능이다. 이게 없으면 Rubeus, Seatbelt, SharpHound를 돌릴 수 없다. BOF로 일부 대체는 가능하지만, CRTO 시험의 90%가 이 도구들에 의존한다.

SMB Beacon (Named Pipe Pivoting) — 상 우선순위. 내부 네트워크에서 직접 외부 통신이 불가능한 호스트를 체이닝하는 기능이다. HTTP만으로는 내부 피벗 시나리오를 재현할 수 없다.

DNS Beacon — 상 우선순위. DNS TXT/A 레코드 기반의 느리지만 가장 은밀한 C2 채널이다. Transport trait이 이미 있으니 DnsTransport를 구현하면 되는 구조이긴 하다.


3. CS보다 잘한 부분

비교 분석을 하다 보면 부족한 점만 눈에 띄기 쉬운데, 코드 레벨에서 CS보다 명확히 나은 영역도 있다.

암호학적 설계. CS는 단일 shared key를 인증과 서명에 동시 사용한다. CloakCat은 HKDF-SHA256으로 master key에서 인증용/서명용 키를 각각 파생한다. HMAC 메시지에도 null 바이트 구분자를 삽입해서 필드 간 길이 혼동 공격을 방지한다. 이건 CS의 프로토콜보다 암호학적으로 더 정확한 설계다.

서버 아키텍처. CS 팀서버는 단일 모놀리스 Java 서버다. CloakCat의 4-crate 워크스페이스는 의존성이 단방향(protocol ← 나머지)으로 유지되고, 서버 내부도 handler → service → db 레이어가 명확히 분리되어 있다. 462 + 601 + 514 = 1,577 LOC가 깔끔하게 나뉘어 있다.

DB 기반 감사 로그. CS는 teamserver.log 텍스트 파일이다. CloakCat은 PostgreSQL 테이블에 actor, action, target_type, target_id, context(JSONB)를 구조화해서 저장한다. 쿼리와 필터링이 가능하다. (다만 audit 테이블 마이그레이션이 누락되어 런타임 에러가 나는 아이러니한 상태긴 하다.)

sqlx 컴파일타임 쿼리 검증. db.rs의 모든 SQL이 빌드 시 타입 체크된다. CS의 Java 코드에서 런타임에 발견되는 SQL 버그를 원천 차단하는 구조다.

sRDI 코드젠 안전성. 1,693 LOC의 sRDI 변환기에서 수동 x86_64 코드를 생성하는데, Rust의 Asm struct + emit()/patch_rel32() 패턴이 바이트 조작 실수를 줄여준다. C로 동일 작업을 하면 버퍼 오버플로우 가능성이 훨씬 높은 영역이다.

빌드 자동화. CS는 GUI 기반 Payload Generator다. CloakCat은 CLI에서 build-agent --format shellcode --encrypt 한 줄로 DLL 빌드 → sRDI 변환 → AES-256-GCM 암호화까지 파이프라인이 돌아간다. 스크립팅과 자동화에 훨씬 적합하다.


4. 솔직히 부족한 점

실전에서 쓰기 어려운 기능들

Lateral Movement가 파일을 디스크에 쓴다. lateral.rsdeploy_payload()가 에이전트 바이너리를 \\target\ADMIN$에 복사하고 서비스/WMI로 실행하는 구조다. 이 패턴은 모든 EDR이 탐지한다. CS는 spawn+inject로 shellcode만 메모리에 주입할 수 있는데, CloakCat은 프로세스 인젝션이 없으니 파일 드롭이 필수다.

BOF 출력이 깨진다. BeaconPrintf가 C varargs를 실제로 확장하지 않는다. 포맷 문자열 %s, %d가 그대로 출력된다. 실제 BOF 도구를 실행하면 결과가 읽을 수 없게 된다. beacon_api.rs 주석에도 "varargs not expanded"라고 명시되어 있다.

SOCKS5 타임아웃이 짧다. 세션당 5분, 에이전트당 10세션 제한이 있어서 장시간 스캔이나 대량 연결이 필요한 상황에서 끊긴다.

Shellcode 자체 복호화가 안 된다. 빌드 시 AES-256-GCM으로 암호화하지만, 에이전트 자체에 복호화 로직이 없다. 별도 로더(tools/shellcode-loader)가 반드시 필요하다. 즉 stager/dropper를 따로 준비해야 한다.

OPSEC 관점

Sleep Mask 없음. sleep 중 메모리에 평문 코드가 노출된다. 현대 EDR의 주기적 메모리 스캔에 즉시 탐지된다.

프로세스 격리 없음. 모든 작업이 에이전트 프로세스에서 실행된다. 하나가 크래시하면 전부 죽는다.

ETW/AMSI 패치 없음. .NET 실행 시 AMSI에 잡히고, 모든 Windows API 호출이 ETW 이벤트를 생성한다.

Direct Syscall 없음. 모든 Windows API가 windows-sys 또는 LoadLibraryA/GetProcAddress를 경유한다. EDR의 userland hooking에 고스란히 노출된다.

코드 품질 이슈

몇 가지 즉시 수정해야 할 버그도 발견되었다.

audit 테이블 마이그레이션이 누락되어 있다. insert_audit(), list_audit() 함수가 있지만 실제 테이블을 만드는 0006번 마이그레이션이 없어서 런타임에 sqlx 에러가 발생한다. 그리고 cat-clicloakcat-protocol의 Cargo.toml에 edition = "2024"로 되어 있는데, 이건 존재하지 않는 에디션이다. 2021이어야 한다. 서버의 error.rstracing 대신 eprintln!을 사용하고 있어서 로깅 일관성이 깨져 있다. 터널 매니저의 gc()가 자동 호출되지 않아서 닫힌 SOCKS5 세션이 메모리에 누적될 수 있다.

테스트 커버리지도 솔직히 부족하다. 서버 전체에 인라인 테스트가 tls.rs에 2개뿐이고, 서비스/핸들러/DB 레이어 테스트는 0개다. 통합 테스트가 수동 체크리스트에 의존하고 있다.


5. 향후 로드맵

즉시 수정

| 항목 | 작업 | 효과 |
|------|------|------|
| audit 마이그레이션 | 0006_init.sql 추가 | 런타임 에러 해결 |
| BeaconPrintf varargs | %s/%d/%x 최소 포맷 지원 | BOF 호환성 대폭 향상 |
| 터널 GC 자동화 | 주기적 gc() 호출 | 메모리 누수 방지 |
| Cargo.toml edition | 2024 → 2021 | 빌드 경고 제거 |
| eprintln → tracing | error.rs 수정 | 로깅 일관성 |
| token_b64 레거시 제거 | DB 컬럼 + 코드 정리 | 코드 단순화 |

중기

| 항목 | CRTO 관련도 | 설명 |
|------|:-----------:|------|
| Process Injection | 필수 | VirtualAllocEx + WriteProcessMemory + CreateRemoteThread. 프로세스 마이그레이션의 기본 |
| execute-assembly | 필수 | CLR 호스팅으로 .NET 인메모리 실행. Rubeus/Seatbelt 사용의 전제조건 |
| Sleep Mask | 핵심 | sleep 전 코드 섹션 XOR 암호화, wake 시 복호화. EDR 메모리 스캔 회피 |
| SMB Beacon | 필수 | Named Pipe 기반 내부 피벗 C2 채널 |
| Staged 페이로드 | 유용 | 스테이저가 최소 shellcode만 전송, 서버에서 full beacon 다운로드 |

장기

| 항목 | 설명 |
|------|------|
| Direct Syscalls | ntdll 직접 호출로 EDR userland hook 우회 |
| ETW/AMSI 패치 | EtwEventWrite/AmsiScanBuffer 런타임 패치 |
| DNS Beacon | DNS TXT/A 레코드 기반 은닉 C2 채널 |
| 도메인 프론팅 | CloudFront/Azure CDN 경유 네트워크 탐지 회피 |
| PPID Spoofing | 프로세스 생성 시 부모 프로세스 변조 |
| Module Stomping | 합법적 DLL 메모리 공간에 shellcode 로딩 |

6. 마무리

숫자로만 보면 CloakCat은 Cobalt Strike의 핵심 기능 중 약 70~80%를 재현했다. 비콘 체크인, 리스너, Malleable C2, 파일 전송, BOF, SOCKS5, 토큰 조작, 횡이동, sRDI까지. 1인 개발에 Rust라는 점을 고려하면 나쁘지 않은 수치다.

하지만 나머지 20~30%가 문제다. Process Injection, execute-assembly, Sleep Mask — 이 세 개가 없으면 EDR이 켜져 있는 환경에서는 1분도 못 버틴다. CS가 상용 도구로서 수년간 다듬어온 영역이고, 이 격차를 좁히려면 아직 갈 길이 멀다.

그래도 이 프로젝트의 가치는 완성도가 아니라 과정에 있다. sRDI 변환기의 x86_64 stub을 r2로 디스어셈블하면서 PEB 워킹이 어떻게 동작하는지 한 줄씩 확인했던 경험, WMI COM vtable 오프셋을 수동으로 계산하면서 Windows의 COM 인프라가 어떻게 구성되어 있는지 이해한 경험 — 이건 어떤 도구를 아무리 오래 써도 얻을 수 없는 종류의 학습이다.

다음 글에서는 Process Injection 구현기를 다룰 예정이다. VirtualAllocExWriteProcessMemoryCreateRemoteThread 체인을 Rust로 짜면서 "왜 이 패턴이 탐지되는지", "어떻게 하면 덜 탐지되는지"를 구체적으로 파고들 계획이다.


이 글은 CloakCat 개발 시리즈의 두 번째 글입니다. Phase별 업데이트를 계속 공유할 예정입니다.

이전 글: Rust로 C2 프레임워크를 설계하며 — CloakCat 아키텍처 리뷰

Comments

Loading comments…

Leave a Comment

Related Posts