Pwnstar
CVE-2015-0235(pwnable.kr nuclear) 본문
소위 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 |