Pwnstar

CVE-2015-0235(pwnable.kr nuclear) 본문

문제 풀기 위한 배경지식

CVE-2015-0235(pwnable.kr nuclear)

포너블처돌이 2021. 6. 10. 14:42

소위 ghost(고스트) 취약점이라고 불리는 cve에 대해서 설명해보려고 한다. pwnable.kr의 nuclear문제에서 쓰인 cve인데, 문제를 풀 때 필요한 지식이어서 간단하게라도 짚고 넘어가려고 한다.

ubuntu 12.04에서 glibc-2.17 이전 버전에서 발생한다고 하는데, 어떤 취약점인지 파헤쳐보자.

취약한 함수의 이름은 __nss_hostname_digits_dots()이고, glibc의 nss/getXXXbyYY.c에 의해서 호출된다. #ifdef HANDLE_DIGITS_DOTS에 의해 묶여서 다음 소스코드에서만 정의되어있다.

  • inet/gethstbynm.c
  • inet/gethstbynm2.c
  • inet/gethstbynm_r.c
  • inet/gethstbynm2_r.c
  • nscd/gethstbynm3_r.c

아래의 코드는 glibc-2.17에서 가져온 것이다.

 35 int
 36 __nss_hostname_digits_dots (const char *name, struct hostent *resbuf,
 37                             char **buffer, size_t *buffer_size,
 38                             size_t buflen, struct hostent **result,
 39                             enum nss_status *status, int af, int *h_errnop)
 40 {
 ..
 57   if (isdigit (name[0]) || isxdigit (name[0]) || name[0] == ':')
 58     {
 59       const char *cp;
 60       char *hostname;
 61       typedef unsigned char host_addr_t[16];
 62       host_addr_t *host_addr;
 63       typedef char *host_addr_list_t[2];
 64       host_addr_list_t *h_addr_ptrs;
 65       char **h_alias_ptr;
 66       size_t size_needed;
 ..
 85       size_needed = (sizeof (*host_addr)
 86                      + sizeof (*h_addr_ptrs) + strlen (name) + 1);
 87
 88       if (buffer_size == NULL)
 89         {
 90           if (buflen < size_needed)
 91             {
 ..
 95               goto done;
 96             }
 97         }
 98       else if (buffer_size != NULL && *buffer_size < size_needed)
 99         {
100           char *new_buf;
101           *buffer_size = size_needed;
102           new_buf = (char *) realloc (*buffer, *buffer_size);
103
104           if (new_buf == NULL)
105             {
...
114               goto done;
115             }
116           *buffer = new_buf;
117         }
...
121       host_addr = (host_addr_t *) *buffer;
122       h_addr_ptrs = (host_addr_list_t *)
123         ((char *) host_addr + sizeof (*host_addr));
124       h_alias_ptr = (char **) ((char *) h_addr_ptrs + sizeof (*h_addr_ptrs));
125       hostname = (char *) h_alias_ptr + sizeof (*h_alias_ptr);
126
127       if (isdigit (name[0]))
128         {
129           for (cp = name;; ++cp)
130             {
131               if (*cp == '\0')
132                 {
133                   int ok;
134
135                   if (*--cp == '.')
136                     break;
...
142                   if (af == AF_INET)
143                     ok = __inet_aton (name, (struct in_addr *) host_addr);
144                   else
145                     {
146                       assert (af == AF_INET6);
147                       ok = inet_pton (af, name, host_addr) > 0;
148                     }
149                   if (! ok)
150                     {
...
154                       goto done;
155                     }
156
157                   resbuf->h_name = strcpy (hostname, name);
...
194                   goto done;
195                 }
196
197               if (!isdigit (*cp) && *cp != '.')
198                 break;
199             }
200         }

85~86번째 줄은 size_needed 변수를 계산하기 위한 세 개의 요소를 버퍼에 저장한다. host_addr, h_addr_ptrs, 그리고 name(hostname). 88~117번째 줄은 버퍼가 충분히 큰지 검사한다.

121~125번째 줄은 버퍼에 4가지 요소를 저장하기 위한 포인터를 준비한다. : host_addr, h_addr_ptrs, h_alias_ptr, 그리고 hostname. 그런데 h_alias_ptr의 사이즈는 size_needed 계산에서 빠져있다. 그러므로 157번째 줄에 있는 strcpy함수는 버퍼를 오버플로우 하게 해준다. 32비트에서는 4바이트, 64비트에서는 8바이트만큼 overflow가 가능하다.

157번째 줄의 오버플로우에 도달하기 위해서는 hostname의 요소가 다음을 충족시켜야 한다.

  • 첫번째 문자는 숫자여야 한다.
  • 마지막 문자는 '.'(dot)이 아니어야 한다.
  • 숫자와 점으로만 이루어져야 한다.
  • 버퍼를 오버플로우시킬만큼 길어야한다. (gethostbyname함수에서는 버퍼를 1024만큼 할당해줌.)
  • IPv4 주소로 inet_aton함수로 파싱될 수 있거나 IPv6로 inet_pton함수로 파싱될 수 있어야한다.

nuclear문제를 풀기 위한 지식은 이정도까지면 충분할 것 같다. 이 4바이트 오버플로우가 어떻게 쓰이는 지 nuclear 문제의 라업을 통해서 설명하도록 하겠다.

'문제 풀기 위한 배경지식' 카테고리의 다른 글

linux에 afl설치하기  (0) 2021.10.02
IO_FILE struct를 이용해서 libc leak하기  (0) 2021.01.26
Comments