Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
lacuna-rs — Windows x64용 ghost-frame call-stack spoofing, runtime indirect syscalls 및 APC injection을 위한 Rust crate입니다. SSN resolution, JIT stub emission 및 VEH-based parameter encryption을 제공하여 EDR evasion을 지원합니다. | Kitploit
도구/GitHubGitHub/karkas66/lacuna-rs
ShellcodePost-ExploitationRed TeamingPayload DevelopmentBinary Exploitation
GitHubkarkas66/lacuna-rs

lacuna-rs

Windows x64용 ghost-frame call-stack spoofing, runtime indirect syscalls 및 APC injection을 위한 Rust crate입니다. SSN resolution, JIT stub emission 및 VEH-based parameter encryption을 제공하여 EDR evasion을 지원합니다.

저장소 보기
1811개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

lacuna-rs

고스트 프레임 호출 스택 스푸핑 + Windows x64용 런타임 간접 시스템 콜 — Rust로 포팅됨.

Mohamed Alzhrani (0xmaz)의 LACUNA Chain (lacuna_chain.c)에서 포팅되었습니다.

lacuna-rs는 원본 C TTP와 동일한 프리미티브를 제공하는 재사용 가능한 Rust 크레이트로, wsyscall-rs 또는 syscalls-rs처럼 모든 Rust 프로젝트에 바로 포함할 수 있도록 설계되었습니다. 하지만 런타임 SSN 해석, 함수별 syscall;ret 타겟팅, 고스트 프레임 스택 스푸핑을 지원합니다.


중요: 프레임 포인터 요구 사항

stack-spoof 기능은 소비 크레이트가 프레임 포인터로 컴파일되지 않은 경우 자동으로 실패합니다.

chain.rs의 스택 스톰핑 프리미티브는 mov rbp, {x} 인라인 어셈블리를 통해 호출자의 반환 주소 슬롯을 찾습니다. 이를 위해서는 RBP 체인이 온전해야 합니다. Rust(및 대부분의 릴리스 모드 컴파일러)는 기본적으로 프레임 포인터를 생략합니다.

build.rs는 이 크레이트 자체의 코드 생성에 대해 force-frame-pointers=yes를 설정하지만, Cargo는 컴파일러 플래그를 다운스트림 크레이트로 전파할 수 없습니다. 자신의 프로젝트에 다음을 추가해야 합니다:

root@kitploit:~
# .cargo/config.toml  (lacuna-rs가 아닌 자신의 크레이트에)
[build]
rustflags = ["-C", "force-frame-pointers=yes"]

이 설정이 없으면 stomp_plant()는 RBP에서 쓰레기 값을 읽어 아무 작업도 수행하지 않거나(최상의 경우) 스택을 손상시킵니다(최악의 경우). 크레이트는 런타임에 프레임 포인터가 활성화되었는지 감지할 방법이 없습니다. 단순히 작동하지 않습니다.

스캔, SSN 해석, 인젝션 프리미티브(스택 스푸핑 제외)만 필요한 경우 stack-spoof 기능을 생략할 수 있으며, 이 요구 사항은 적용되지 않습니다.


기능


기능 플래그

root@kitploit:~
[dependencies]
lacuna-rs = { version = "0.1", features = ["inject", "stack-spoof", "veh"] }

기능이 활성화되지 않으면 스캔/PE/NT 레이어만 사용할 수 있습니다.


빠른 시작

고스트 영역 스캔

root@kitploit:~
cargo run --example scan

고스트 프레임 체인 구축 + 검증

root@kitploit:~
cargo run --example verify --features stack-spoof

섹션 + APC를 통한 셸코드 인젝션

인젝션 증명 실제 임플란트를 사용한 인젝션 예제 - C2는 오프라인이었지만 셸코드가 실행됨

root@kitploit:~
cargo run --example inject --features inject,stack-spoof,veh -- <pid> <sc.bin>

VEH 진단 출력(스택 덤프, 레지스터 출력)을 활성화하려면 --verbose를 추가하세요:

root@kitploit:~
cargo run --example inject --features inject,stack-spoof,veh -- <pid> <sc.bin> --verbose

스레드 점수 알고리즘

대상 프로세스의 모든 스레드에 APC를 대기시키는 대신(너무 많은 스레드가 동시에 알림을 받으면 프로세스가 충돌할 수 있음), inject_sapc는 점수 알고리즘을 사용하여 최상의 MAX_APC_THREADS(5) 후보를 선택합니다:

  • 사이클 시간 — NtQueryInformationThread(ThreadCycleTime) — 주요 활동 지표
  • CPU 시간 — NtQueryInformationThread(ThreadTimes) — 커널 + 사용자 시간
  • 일시 중단 횟수 — NtQueryInformationThread(ThreadSuspendCount) — 일시 중단되지 않은 스레드는 +300 보너스; 일시 중단된 스레드는 일시 중단 횟수당 -150
  • 우선순위 — NtQueryInformationThread(ThreadBasicInformation) — 우선순위 8-10 스레드는 +150 보너스; 범위를 벗어난 우선순위는 -100 페널티

완전히 유휴 상태인 스레드(사이클 및 CPU 시간이 0)는 완전히 건너뜁니다. 나머지 후보는 점수(내림차순, 동점 시 사이클 기준)로 정렬되어 상위 5개로 제한됩니다.


라이브러리로 사용하기

기본: 스캔 + SSN 해석

root@kitploit:~
use lacuna::{scan, nt, win::get_module};

let ntdll = get_module(b"ntdll.dll\0");

// 고스트 영역 스캔
let mut ghosts = [scan::Ghost::default(); 512];
let n = scan::scan_ghosts(ntdll, &[b"NtAllocateVirtualMemory\0"], &mut ghosts);
println!("{} 고스트 영역이 ntdll에 있습니다.", n);

// 특정 함수에 대한 SSN + syscall;ret 해석
let (ssn, syscall_ret) = nt::resolve(ntdll, b"NtOpenProcess\0");
println!("NtOpenProcess: ssn={:#x}, syscall;ret={:#x}", ssn, syscall_ret);

모든 NT API에 대한 간접 시스템 콜 스텁 생성

root@kitploit:~
use lacuna::{nt, stub, win::{get_module, HMODULE}};

let ntdll: HMODULE = get_module(b"ntdll.dll\0");

// SSN + 함수 자체의 syscall;ret 주소 해석
let (ssn, syscall_ret) = nt::resolve(ntdll, b"NtAllocateVirtualMemory\0");
assert!(ssn != nt::SSN_INVALID && syscall_ret != 0);

// JIT 스텁 생성: mov r10,rcx; mov eax,SSN; jmp [syscall;ret]
// (또는 build_chain이 호출된 경우 jmp [ghost_gadget] -> JMP [RBX] -> syscall;ret)
let stub = stub::make_stub(ssn, syscall_ret).expect("스텁 할당 실패");

// 해당 함수 포인터 타입으로 캐스팅하여 호출
let alloc_vm: unsafe extern "system" fn(
    win::HANDLE, *mut win::PVOID, usize, *mut usize, win::ULONG, win::ULONG,
) -> win::NTSTATUS = unsafe { core::mem::transmute(stub.as_fn()) };

고스트 프레임 체인 구축 + VEH 등록

root@kitploit:~
// ntdll/kernelbase/wow64/win32u를 스캔하여 6계층 가짜 호출 스택을 구축합니다.
// G_GHOST_GADGET을 설정하여 스텁이 서명된 DLL의 JMP [RBX]를 통해 라우팅되도록 합니다.
lacuna::chain::build_chain();

// VEH 핸들러 등록 (파라미터 암호화 + 체인 가드)
let _veh = lacuna::veh::VehGuard::register().expect("VEH 등록 실패");

// 선택 사항: 런타임에 상세 진단 활성화
lacuna::veh::set_verbose(true);

스택 스푸핑 + 파라미터 암호화로 민감한 시스템 콜 래핑

root@kitploit:~
use lacuna::win::HANDLE;
use core::ptr;

let mut h_proc: HANDLE = ptr::null_mut();
let key: u64 = 0xCAFE_1337;

// 고스트 프레임 배치 (반환 주소를 서명된 DLL 고스트로 교체)
lacuna::chain::stomp_plant();

// syscall;ret에 DR0 설정 — VEH가 경계에서 XOR 암호화된 파라미터를 해독합니다.
lacuna::veh::pcrypt_arm(key, syscall_ret, true);

// 간접 스텁을 통해 호출 — RIP가 ntdll 내부 커널 진입점에 위치합니다.
let _status = unsafe { open_proc(/* XOR 암호화된 파라미터 */) };

// 보호되지 않은 호출 전에는 항상 비활성화
lacuna::veh::pcrypt_disarm();
lacuna::chain::stomp_restore();

OPSEC: 고스트 프레임 뒤에 숨겨야 할 것

모든 API 호출에 전체 LACUNA 처리가 필요한 것은 아닙니다. 핵심 원칙은 EDR이 인젝션 또는 사후 익스플로잇 활동으로 상관 관계를 맺을 수 있는 호출을 숨기는 것입니다.

간접 시스템 콜 + 고스트 프레임 + 파라미터 암호화가 반드시 필요한 호출

다음은 EDR이 후킹하고 상관 관계를 맺는 "크라운 주얼" NT 시스템 콜입니다:

이 각각에 대해:

  1. nt::resolve(ntdll, b"NtXxx\0")로 해석
  2. stub::make_stub(ssn, syscall_ret)로 스텁 생성
  3. veh::pcrypt_arm(key, syscall_ret, true)로 민감한 파라미터 XOR 암호화
  4. chain::stomp_plant()와 chain::stomp_restore() 사이에 호출 래핑

직접 호출 가능 (고스트 프레임 불필요)

주요 원칙

  1. 모든 NT 시스템 콜을 nt::resolve(ntdll, b"NtXxx\0")로 해석 — (SSN, syscall_ret_VA) 반환.
  2. stub::make_stub(ssn, syscall_ret)로 JIT 스텁 생성 — 호출 가능한 함수 포인터 반환.
  3. 스텁을 core::mem::transmute를 통해 적절한 extern "system" fn(...) 타입으로 캐스팅.
  4. (민감한 호출만) chain::build_chain()으로 고스트 프레임 체인 구축하고 호출 주위에 chain::stomp_plant() / chain::stomp_restore() 사용.
  5. (민감한 호출만) 호출 전에 veh::pcrypt_arm(key, syscall_ret, true)로 파라미터 암호화 설정, 호출 후 veh::pcrypt_disarm().
  6. 보호되지 않은 호출을 하기 전에 항상 VEH 비활성화 — 무장하지 않은 시스템 콜에 잘못된 DR0가 적중하면 충돌합니다.
  7. 모든 출력 문자열은 lc!()로 래핑해야 합니다 — 크레이트가 이를 강제하며, VEH 진단은 기본적으로 꺼져 있는 set_verbose(true) 뒤에 게이팅됩니다.

스텁은 mov r10, rcx / mov eax, SSN / jmp [syscall;ret] 시퀀스를 자동으로 처리합니다. 고스트 가젯이 등록된 경우 스텁은 JMP [RBX]를 통해 라우팅되어 이중 사용 실행 리디렉션 + 제로 아티팩트 브리지 프레임을 제공합니다.


라이트업 커버리지 검증

LACUNA Chain 라이트업의 9가지 주요 기여가 모두 코드에 포함되어 있습니다:

탐지 표면 커버리지


왜 syscalls-rs를 그냥 사용하지 않나요?

syscalls-rs와 wsyscall-rs는 특정 Windows 빌드의 ntdll에서 빌드 타임 SSN 테이블을 생성합니다. 대상 머신이 다른 빌드를 실행하면 SSN이 잘못되어 시스템 콜이 실패하거나 EDR 휴리스틱을 트리거합니다.

lacuna-rs는 ntdll의 스텁을 직접 읽어 런타임에 SSN을 해석하고, 함수 자체의 syscall;ret 명령어를 타겟팅하여 RIP가 커널 진입점의 ntdll 내부에 있도록 합니다. 이는 EDR의 "SSN 불일치" 및 "백업되지 않은 메모리의 간접 시스템 콜" 휴리스틱을 무력화합니다.

또한 lacuna-rs는 syscalls-rs가 제공하지 않는 고스트 프레임 스택 스푸핑 체인을 제공합니다.


빌드 요구 사항

  • 타겟: x86_64-pc-windows-msvc (또는 x86_64-pc-windows-gnu)
  • Rust 에디션: 2021
  • 의존성: litcrypt2 (컴파일 타임 문자열 난독화)

프레임 포인터 설정 (stack-spoof 전용)

stack-spoof 기능은 프레임 포인터가 필요합니다. build.rs는 이 크레이트에 대해 force-frame-pointers=yes를 자동으로 설정하지만, 소비 크레이트도 .cargo/config.toml에서 설정해야 합니다:

root@kitploit:~
# .cargo/config.toml (소비 크레이트에)
[build]
rustflags = ["-C", "force-frame-pointers=yes"]

litcrypt2 문자열 난독화

크레이트와 예제의 모든 문자열 리터럴은 lc!() 매크로로 래핑되어 컴파일 타임에 암호화되고 런타임에 해독됩니다. 암호화 키는 LITCRYPT_ENCRYPT_KEY 환경 변수에서 읽습니다. 설정되지 않은 경우 litcrypt2가 자동으로 무작위 키를 생성합니다.

재현 가능한 빌드를 위해 키를 고정하려면:

root@kitploit:~
set LITCRYPT_ENCRYPT_KEY=your-secret-key
cargo build

아키텍처

root@kitploit:~
lacuna-rs/
├── Cargo.toml
├── build.rs              # stack-spoof를 위한 force-frame-pointers 설정
├── src/
│   ├── lib.rs            # 크레이트 루트 + 재익스포트 + scan_all() + litcrypt2 설정
│   ├── win.rs            # Win32/NT FFI 바인딩 (no_std 호환)
│   ├── pe.rs             # PE 섹션 + 익스포트 파싱
│   ├── scan.rs           # .pdata 고스트 영역 + 가젯 스캔
│   ├── nt.rs             # SSN 해석 + syscall;ret 타겟팅
│   ├── stub.rs           # JIT 간접 시스템 콜 스텁 생성
│   ├── veh.rs            # VEH + 하드웨어 브레이크포인트 파라미터 암호화
│   ├── chain.rs          # LACUNA 고스트 프레임 체인 + 스톰프
│   └── inject.rs         # 섹션 기반 APC 인젝션
├── examples/
│   ├── scan.rs           # lacuna.exe scan
│   ├── verify.rs         # lacuna.exe verify
│   └── inject.rs         # lacuna.exe inject <pid> <sc.bin>
└── .cargo/
    └── config.toml       # force-frame-pointers + windows-msvc 타겟

크레딧

  • 원본 연구 및 C 구현: Mohamed Alzhrani (0xmaz) — LACUNA Chain
  • 기법: 고스트 프레임: .pdata Lacunae에서 그럴듯한 호출 스택 위조
  • 최적 APC 스레드 로직: FrankensteinAPCInjection에서 수정 — S12cybersecurity 제공
  • 문자열 난독화: litcrypt2 v0.1.3

완전 공개

이 프로젝트는 AI 챗봇의 적극적인 도움을 받았습니다. 저는 더 새롭고 매우 유능한 오픈 소스 모델(GLM-5.2 by Z.ai (Zhipu AI))을 테스트할 기회가 있었고, 어려운 작업을 주고 싶었습니다. 배운 점: 결과가 어떻게 나올지 사람이 알고 있다면 잘 작동했습니다. 그렇지 않다면... 그냥 쓰레기였습니다.

라이선스

MIT

도구 다운로드
프리미티브C 함수Rust 모듈
PE 섹션 + 익스포트 파싱pe_section(), pe_export()pe
.pdata 고스트 영역 스캔scan_ghosts(), best_ghost()scan
고스트 가젯 발견 (jmp [rbx])scan_ghost_gadgets()scan
win32u NOP 갭 찾기win32u_nop_gap()scan
BYOUD-MF 앵커 찾기find_mf_target()scan
SSN 해석 (Hell's Gate / Halo's Gate)resolve_ssn()nt
함수별 syscall;ret 로케이터find_func_syscall()nt
JIT 간접 시스템 콜 스텁 생성alloc_stub()stub
고스트 가젯 스텁 리디렉션(alloc_stub() 내)stub
VEH + 하드웨어 브레이크포인트 파라미터 암호화param_encrypt_veh(), pcrypt_arm()veh
체인 가드 VEHchain_veh()veh
LACUNA 체인 구축build_chain()chain
스택 스톰프 (BYOUD-RT)stomp_plant(), stomp_restore()chain
체인 워커 (검증)lacuna_walk_chain()chain
섹션 기반 APC 인젝션do_inject_sapc()inject
기능설명프레임 포인터 필요?
syscalls (기본값)SSN 해석 + JIT 스텁 생성아니오
inject (기본값)섹션 기반 APC 인젝션 (inject::inject_sapc)아니오
vehVEH + 하드웨어 브레이크포인트 파라미터 암호화아니오
stack-spoofLACUNA 고스트 프레임 체인 + 스택 스톰프예
no-stdno_std 모드 (실험적)아니오
호출민감한 이유
NtOpenProcess다른 프로세스에 대한 핸들 열기 — 인젝션의 첫 단계
NtCreateSection + NtMapViewOfSection (원격)섹션 기반 인젝션 시그니처
NtWriteVirtualMemory프로세스 간 쓰기 — 전형적인 인젝션 지표
NtCreateThreadEx원격 스레드 생성 — 가장 높은 신호의 인젝션 프리미티브
NtQueueApcThreadAPC 인젝션 — 높은 신호
NtProtectVirtualMemoryRWX 권한 변경 — 셸코드 스테이징 지표
NtAllocateVirtualMemory (원격)원격 할당 — 인젝션 전주곡
NtSetInformationThread스레드 숨기기 (HideFromDebugger) — 회피 지표
호출안전한 이유
NtQueryInformationThread읽기 전용, 거의 후킹되지 않음, 프로세스 간 쓰기 없음
NtDelayExecutionSleep — 무해, 모든 애플리케이션이 사용
NtClose핸들 닫기 — 무해, 매우 일반적
GetModuleHandleA / GetProcAddress모듈 해석 — 시스템 콜이 아님, EDR 사용자 모드 후킹 불가
CreateToolhelp32Snapshot / Thread32First / Thread32Next스레드 열거 — kernel32, ntdll 시스템 콜 아님
OpenThread / CloseHandle표준 핸들 작업 — kernel32
GetThreadContext / SetThreadContextDR0에 필요 — kernel32, 자체 프로세스 전용
#라이트업 개념코드 위치설명
1BYOUD-Gap (.pdata 수정 제로)chain.rs — 고스트 프레임 체인 구축RUNTIME_FUNCTION 항목 간의 간격을 활용; 언와인더가 리프 프레임으로 처리 (RSP += 8)
2ETW-Ti APC 윈도우 공격inject.rs — NtDelayExecution 알림 가능 드레인스레드 알림 가능 상태를 조작하여 ETW-Ti APC 스택 스냅샷이 발사되는 시점을 제어
3BYOUD 컨텍스트에서 파라미터 암호화veh.rs — pcrypt_arm(), param_encrypt_veh()스테이징 시 시스템 콜 파라미터를 XOR 암호화; syscall 명령어에서 하드웨어 브레이크포인트 VEH 내에서 해독
4Win32u NOP 갭 체인 + 고스트 가젯scan.rs — win32u_nop_gap(), scan_ghost_gadgets()win32u.dll에 1,242개의 NOP 갭이 화이트리스트 리프 프레임 제공; ntdll+0xFC47B에 JMP [RBX] 고스트 가젯
5kernelbase 의미론적 고스트 근접성chain.rs — VirtualProtect 근처 L2_kbase 레이어VirtualProtect 진입점에서 끝나는 238바이트 고스트 — 실제 VP 반환 사이트와 구별 불가능
6BYOUD-MF (머신 프레임 RSP 텔레포트)chain.rs — MachFrame 구조체, scan.rs — find_mf_target()KiUser* 디스패처에서 UWOP_PUSH_MACHFRAME (opcode 10)을 활용하여 단일 프레임에서 임의의 RSP 텔레포트
7BYOUD-RT (런타임 RSP 계산)chain.rs — teb_stack_base(), teb_stack_limit()호출 시간에 TEB.StackBase (GS:[0x08])를 읽어 정확한 프레임 거리 계산 — 사전 보정 필요 없음
8wow64.dll 고스트 근접성chain.rs — L1_wow64 레이어, scan.rs — Wow64PrepareForException 타겟팅Wow64PrepareForException 진입점에서 끝나는 91바이트 고스트 — 체인의 네 번째 의미론적 레이어
96계층 LACUNA 체인chain.rs — LacunaStack 구조체 (L1-L5 + MachFrame)전체 체인: KiUserExceptionDispatcher -> wow64 -> kernelbase -> ntdll -> win32u -> RtlUserThreadStart
탐지 계층상태구현
모듈 출처 확인회피모든 프레임이 ntdll / kernelbase / wow64 / win32u에 있음
언와인드 워크 정확성회피모든 라쿠나 프레임은 리프 -> 유효한 RSP+8
.pdata 포렌식 스캔회피수정 제로; 간격은 이미 존재
CET 섀도 스택회피순수 리프 체인; 섀도 스택 참조 안 함
의미론적 프레임 분석회피WoW64 예외 + VirtualProtect 인접성
Win32u 규칙 면제회피모든 규칙에서 레이어 4를 명시적으로 제외
ETW-Ti STACKWALK회피APC 윈도우 공격이 스냅샷 타이밍 제어
파라미터 검사회피HW 브레이크포인트 VEH 해독
커널 콜백부분적핸들 작업은 여전히 ObRegisterCallbacks 발생