
Windows 파이버를 사용하여 메모리 내 코드를 은밀하게 실행하는 Rust 기반 PoC로, 커널 콜백 없이 제어 파이버와 페이로드 파이버 간 전환을 통해 EDR로부터 페이로드 스택을 숨깁니다.
파이버(Fiber)는 Windows에 내장된 우선순위 기반 스케줄링 메커니즘에 의존하지 않고 애플리케이션에서 수동으로 스케줄링해야 하는 실행 단위입니다. 파이버는 종종 경량 스레드라고 불립니다. 파이버가 무엇인지, 어떻게 작동하는지에 대한 자세한 내용은 공식 문서를 참조하세요. 파이버를 사용하면 단일 스레드 내에서 여러 실행 흐름을 가질 수 있으며, 각 실행 흐름은 자체 레지스터 상태와 스택을 갖습니다. 반면에 파이버는 커널에 보이지 않으므로, 새 스레드를 생성하는 것보다 메모리 내 코드를 실행하는 더 은밀하고(그리고 저렴한) 방법이 됩니다.
하나의 스레드는 여러 파이버를 생성할 수 있으며, SwitchToFiber 함수를 호출하여 원하는 대로 전환할 수 있습니다. 그 전에, 현재 스레드 자체가 먼저 ConvertThreadToFiber를 호출하여 파이버가 되어야 합니다. 오직 파이버만이 다른 파이버를 생성할 수 있기 때문입니다. 마지막으로, 스케줄링될 때 메모리 내 코드(예: PE를 리플렉티브 로드한 후 또는 일부 셸코드)를 실행하는 파이버를 생성하려면 CreateFiber를 호출하기만 하면 됩니다.
SwitchToFiber 함수는 이 과정에서 가장 중요한 부분이며 모든 마법이 일어나는 곳입니다. 이 함수는 하나의 파이버 또는 다른 파이버를 스케줄링할 수 있게 하며, 모든 작업이 사용자 공간에서 발생합니다. 공식 문서에 따르면, "SwitchToFiber 함수는 현재 파이버의 상태 정보를 저장하고 지정된 파이버의 상태를 복원합니다." 즉, 이 함수가 호출되면 레지스터 값과 스택이 현재 파이버 상태에서 대상 파이버 상태로 전환되어, 프로세스가 완료되면 현재 파이버의 스택을 "숨길" 수 있습니다. 또한 이를 통해 실행이 중단된 지점부터 대상 파이버의 실행을 계속할 수 있습니다(스케줄러가 자체 우선순위 로직에 따라 스레드 간 전환할 때 발생하는 것과 같은 방식입니다).
그리고 이것이 바로 이 간단한 PoC가 수행하는 작업입니다:
run() 함수를 실행하기 위해 새 파이버를 생성합니다. 이 파이버는 이하 **페이로드 파이버(payload fiber)**라고 합니다.이 프로세스는 무한히 반복됩니다.
파이버 사용은 다음과 같은 이유로 일부 유형의 페이로드(예: C2 비콘)에 유리할 수 있습니다:
JMP 또는 CALL 명령어를 사용하지 않고도 메모리 내 코드를 실행할 수 있습니다.LITCRYPT 플러그인을 사용하여 문자열 리터럴을 난독화하고 있으므로, 코드를 컴파일하기 전에 환경 변수 LITCRYPT_ENCRYPT_KEY를 설정해야 합니다:
C:\Users\User\Desktop\Fiber> set LITCRYPT_ENCRYPT_KEY="yoursupersecretkey"
그런 다음 페이로드와 로더를 모두 컴파일하고 로더를 실행하기만 하면 됩니다:
C:\Users\User\Desktop\Fiber\payload> cargo build --release
C:\Users\User\Desktop\Fiber\loader> cargo build --release
C:\Users\User\Desktop\Fiber\loader\target\release> loader.exe
이 PoC 실행에는 많은 비밀이 없습니다. 로더를 실행하고 ProcessHacker와 같은 도구를 사용하여 스레드 스택을 검사하기만 하면 됩니다. 페이로드가 Sleep 전에 다시 제어 파이버로 전환되므로 페이로드 파이버의 스택은 대부분의 시간 동안 숨겨진 상태로 유지됩니다. 출력에서 두 파이버가 이미 설명한 로직에 따라 연속적으로 스케줄링되는 것을 볼 수 있습니다.
코드는 파이버를 사용하고, 생성하고, 스케줄링하는 방법을 보여주기 위해 주석 처리되어 있습니다. 예제로 제공된 로더와 페이로드 모두 무한 루프에 "갇혀" 있어 파이버 간에 무기한 전환하고 실행을 계속할 수 있습니다.
다른 페이로드를 테스트하려면 로더의 src::main.rs 파일 32행에 있는 경로를 수정하기만 하면 됩니다. 이 경우 새 dll은 제어 파이버의 주소를 입력 매개변수로 받는 run(PVOID) 함수를 내보내야 합니다. 이 함수는 Sleep 함수를 호출하기 위해 다시 제어 파이버로 전환해야 하지만, 필요에 따라 이 동작을 자유롭게 수정할 수 있습니다.
임의의 페이로드로 이 도구를 테스트하는 또 다른 방법은 IAT 후킹을 수행하여 페이로드가 Sleep 함수(또는 다른 가져온 함수)에 대한 모든 호출을 로더에 있는 함수로 리디렉션하여 이 호출이 발생할 때 다시 제어 파이버로 전환할 수 있도록 하는 것입니다. 선택은 여러분의 몫입니다.
다음 스크린샷에서 파이버를 전환할 때 현재 스레드의 스택이 하나의 개인 메모리 영역에서 다른 영역으로 이동하는 것을 볼 수 있습니다:
