Demon 시즌2/linux kernel exploitation

[linux kernel] (1) - 커널(kernel)이란?

jir4vvit 2020. 12. 11. 14:07

1. 커널의 특징 및 개념, 이론, 부트로더 등

1-1. 커널이란?

운영체제를 사용자의 관점 혹은 시스템의 관점에서 살펴보자.

 

사용자의 관점에서 운영체제는, 컴퓨터를 사람이 사용하게 쉽게 여러가지 일들을 수행해주는 역할을 한다. 시스템의 관점에서 운영체제는, 하드웨어와 가장 밀접한 프로그램이라고 볼 수 있다. 컴퓨터 시스템은 특정 문제를 해결하기 위해 필요한 여러가지 자원들을 사용한다.(ex CPU시간, 메모리 공간, 파일 저장 공간, 입출력 장치... ) 운영체제는 이러한 자원의 관리자로써 동작한다.

 

운영체제는 커널과 시스템 프로그램으로 구분될 수 있다. 커널은 운영체제의 핵심으로 컴퓨터 자원들을 관리하는 역할을 한다. 하지만 커널은 사용자와의 상호작용은 전혀 지원하지 않는다. 그래서 사용자와 직접적인 상호작용을 위해서 시스템 프로그램이 필요하다. 시스템 프로그램의 예로 쉘(Shell)이라는 명령어 해석기가 있다. 쉘은 사용자가 컴퓨터에게 전달하는 명령을 해석하는 프로그램으로 사용자와의 상호작용을 가능하게 한다.

 

정리를 하면 '운영체제'는 커널과 함께 사용자 편의를 위한 시스템 프로그램을 포함하며, '커널'은 컴퓨터 자원을 관리하는 운영체제의 핵심 부분이다. 

 

커널은 컴퓨터의 물리적(하드웨어) 자원과 추상화 자원을 관리하기 위해 시스템의 다른 모든 부분을 위한 기본적인 서비스를 제공하고, 하드웨어를 관리하며, 시스템 자원을 나눠준다. 추상화는 물리적으로 하나 뿐인 하드웨어를 여러 사용자들이 번갈아 사용하게 중재함으로서, 한 개의 하드웨어가 여러개인 것처럼 보여지게 한다. 이를 위해 커널은 하나의 하드웨어 자원을 여러 사용자들을 위한 복수 개의 추상화된 객체로 관리한다.

 

커널이 하는 일

  • 태스크(task) 관리자 : CPU를 task라는 추상적인 자원으로써 제공
  • 메모리 관리자 : 메모리를 segment나 page로 제공
  • 파일시스템 : 디스크를 파일로 제공
  • 네트워크 관리자 : 네트워크 장치를 소켓으로 제공
  • 디바이스 드라이버 관리자 : 각종 장치를 디바이스 드라이버를 통해 일관되게 접근하도록 함 

 

커널의 구성요소, 즉 관리자들이 존재하는 공간이 Kernel Space이다. Kernel Space 위에 사용자로 여겨지는 태스크(process)들이 존재하는 User Space가 있다. (프로그램 파일이 결국 task가 된다.)

 

Kernel Space와 User Space 사이에 System Call Interface가 있다. User Space의 task들이 커널이 관리하는 자원에 접근해야할 필요가 있으면 System Call Interface를 통해 Kernel Space의 자원 관리자에게 요청이 전달된다. 그리고 이 커널의 각 자원 관리자는 사용자 요청에 맞게 알맞는 하드웨어에 사용자 명령을 전달하고 작업을 수행한다. 

 

정리하자면, '커널'은 사용자가 system call을 통해 컴퓨터 자원을 사용할 수 있게 해주는 자원관리자라고 볼 수 있다.

 

* 출처

더보기

5equal0.tistory.com/entry/Linux-Kernel-%EC%BB%A4%EB%84%90%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BC-%EC%BB%A4%EB%84%90%EC%9D%98-%EA%B5%AC%EC%A1%B0

1-2. 부트로더

부트로더(boot loader, 초기적재프로그램)란 운영 체제가 시동되기 이전에 미리 실행되면서 커널이 올바르게 시동되기 위해 필요한 모든 관련 작업을 마무리하고 최종적으로 운영 체제를 시동시키기 위한 목적을 가진 프로그램을 말한다.
-위키백과-

여기서 말하는 필요한 작업들은 하드웨어를 초기화하고 커널을 압축 해제 후 메모리에 적재, 제어를 OS에 넘기는 과정을 수행하는 것을 말한다. 

 

 

2. 유저 영역(user land)가 아닌 커널 영역(kernel land)의 차이점

운영체제는 컴퓨터의 메모리를 관리한다. 컴퓨터를 안전하게 관리하기 위해서 유저 영역과 커널 영역으로 나누어 관리를 한다. 

 

  • 유저 영역(user land) : 프로그램이 동작하기 위해 사용되는 메모리 공간(stack, heap, bss, data, text 영역)
  • 커널 영역(kernel land) : 운영체제를 실행시키기 위해 필요한 메모리 공간

 

명령어 수행 과정에서 CPU는 항상 메모리에 이번에 수행해야 할 instruction의 주소를 건네준 후 instruction과 관련된 데이터나 코드를 받아서 실행한다. 이 과정에서 지금 현재 상태가 유저 모드인지 커널 모드인지가 중요하다.

 

커널 모드의 경우, CPU는 어떠한 영역의 메모리라도 접근하고 모든 instruction을 실행시킬 수 있다. 한 마디로 모든 영역의 접근이 허용된다는 의미다. 하지만 유저 모드의 경우는 오직 자신의 메모리 영역에만 접근할 수 있다.

 

이러한 모드를 왜 나눈 것일까? 

 

커널에서 중요한 자원, 즉 운영체제를 실행시키기 위한 자원을 관리하기 때문에 일반 사용자가 그 중요한 자원에 접근하지 못하도록 하기 위함이다.

 

한가지 예를 들어보자. 리눅스(Linux)는 윈도우(Windows)와 다르게 하나의 시스템을 다양한 사용자가 이용할 수 있다. 따라서 A라는 사용자가 실수로 B사용자의 영역에 무언갈 write하는 행위가 발생할 수 있다. 하지만 실세계에서는 그렇지 않다. 왜 그럴까? 바로 유저 모드와 커널 모드를 나눴기 때문이다.

 

운영체제는 메모리와 프로세스를 관리한다. 그리고 입출력을 관리해주는 역할도 한다. 따라서 A라는 사용자가 I/O instruction을 수행하기 위해서는 유저 모드인 상태를 커널 모드로 변경을 해야 한다. 

 

 

참고로 리눅스(Linux)의 주제에서 살짝 멀지만 윈도우(Windows)에서 재미있는 것(?)을 발견해서 가져와봤다.

더보기

프로그램이 멈출 때 작업 관리자에서 강제 종료 버튼만 눌러봤는데.. 이런 것도 확인할 수 있다. 작업 관리자의 성능탭에서 커널시간을 체크해보자.

[작업관리자]-[성능탭]

진한 색이 CPU가 커널영역에서 일을 했다는 뜻이고, 연한 색은 CPU가 유저영역에서 일을 했다는 뜻이다.

 

 

3. linux syscall을 kernel에서 처리하는 방식

(1) 유저 프로그램이 시스템 콜(System Call)을 호출한다. my code에서 printf()는 I/O instruction과 관련된 함수이다. 저 함수는 library에 있는 함수이고 실제 동작할 때 library는 I/O를 하기 위해 시스템 콜을 호출할 것이다. 이는 커널에게 I/O instruction의 수행을 부탁한다는 의미이다.

 

(2) 유저는 CPU를 뺏기고 더 이상 유저 모드에서 실행(run) 할 수 없게 되는 Trap에 걸린다. Wrapper Routine은 Trap으로 넘어갈 내용들을 준비하고 실질적으로 Trap을 일으키는 공간이다. Trap을 일으키기 전 Prepare parameter들을 준비하는데 이 중에서 가장 중요한 것이 아래 나올 System call number이다.

※ 커널은 System call function의 시작 주소를 담고 있는 배열을 가지고 있다. 이 배열이 System call table이고 배열의 index 번호가 System call number이다. 예를 들어 System call number가 4이면 커널에서 write() 함수를 불러 작업을 수행한다는 뜻이다.


(3) 하드웨어가 유저 모드에서 커널 모드로 모드 비트(mode bit)를 바꾼다. chmodk(change CPU protection mode to Kernel) 명령을 통해 CPU의 모드 비트가 유저 모드에서 커널 모드로 바뀌게 된다. chmodk가 실행되면서 프로그램은 런타임 중 Trap에 걸려 커널 영역으로 가게 된다.


(4) 하드웨어가 sys_call()이라는 커널 안의 Trap Handler로 가게 되고, Trap Handler는 커널 안의 assembly function을 수행한다.


※ 커널 안에 있는 모든 System call function의 이름은 sys_로 시작한다.


(5) 지금까지 유저 프로그램에서 진행했던 단계를 저장을 한다. 커널 쪽 일이 다 끝나면 시스템 콜을 호출했던 곳으로 돌아가서 다시 진행을 해야 하기 때문에 저장을 한다.


(6) System call number가 커널 안에 System call table에 있는 번호에 맞는 번호인 지 확인한다. 예를들어 Wrapper Routine에서 설정한 write() 함수의 시작 주소를 뜻하는 index 번호, 즉 System call number가 4번인 것과 실제 커널 안의 System call table에서 write() 함수의 시작 주소가 있는 index 번호가 일치하는지 비교한다.


(7) 맞는 번호라면 System call function의 주소를 가져온 후 작업을 수행한다.


(8) 커널 쪽 일이 다 끝나면 다시 시스템 콜을 호출했던 유저의 영역으로 돌아가고 모드 비트(mode bit)를 커널 모드에서 유저 모드로 다시 전환한다.

 

 

 

4. 커널 드라이버, 커널 모듈이란?

커널은 한 번 컴파일 되고, 운영체제로 기능하기 시작하면 이를 수정하기 쉽지 않다. 그런데 커널의 권한으로 실행되어야 하는 많은 디바이스 드라이버는 빈번하게 개발되고 패치되고 있으며, 커널에 심각한 보안상의 문제가 발견되면 이를 즉각적으로 패치해야 한다.

 

  • 디바이스 드라이버
    • 시스템이 지원하는 하드웨어를 응용 프로그램에서 사용할 수 있도록 커널에서 제공하는 라이브러리
    • 응용 프로그램이 하드웨어를 제어하려면 커널에 자원을 요청하고, 커널은 요청에 따라 시스템 관리
  • 커널 모듈 : 리눅스 커널이 부팅되어 동작중인 상태에서 디바이스 드라이버를 동적으로 추가하거나 제거할 수 있게 하는 기술 

 

따라서 부족한 확정성을 개선하기 위해 커널에 필요한 기능을 탈부착할 수 있도록 커널 모듈(적재 가능한 커널 모듈(Loadable Kernel Module, LKM))이란 기술을 도입했다. 또한 리눅스는 다른 운영체제와 달리 대부분의 기능을 모듈로 구현할 수 있다.

 

커널 모듈은 커널과 같은 메모리 공간(커널 모듈은 커널의 한 부분으로 동작함)을 사용하기 때문에, 커널 모듈에서 취약점이 발생하면 커널 공격으로 이어질 수 있다.

 

 

5. kernel에서 사용하는 함수

  • module_init

    -> 모듈을 적재할 때 실행하는 함수
    -> 성공하면 0을 반환

  • module_exit

    -> 모듈을 삭제할 때 실행되는 함수

  • printk

    -> printf와 비슷한 함수
    -> printf는 stdout에 출력하지만, printk는 kernel ring buffer에 값을 출력
    -> dmesg 명령이나 /var/log/messages 또는 /var/log/kern.log 파일에서 확인 가능

  • kmalloc

    -> malloc과 비슷한 함수
    -> kmalloc는 ptmalloc2가 아닌 slab allocator를 통해 메모리를 할당

  • kfree

    -> free와 비슷한 함수
    -> kfree는 ptmalloc2가 아닌 slab allocator를 통해 메모리를 해제

  • copy_from_user

    -> memcpy와 비슷한 함수
    -> 유저 영역에서 커널 영역으로 특정 바이트 만큼 복사
    -> 정상적으로 수행했으면 0을 반환한다. (아니라면 복사되지 않은 바이트 수)

  • copy_to_user

    -> memcpy와 비슷한 함수
    -> 커널 영역에서 유저 영역으로 특정 바이트 만큼 복사
    -> 정상적으로 수행했으면 0을 반환한다. (아니라면 복사되지 않은 바이트 수)

  • prepare_kernel_cred

    -> 새로운 cred 구조체를 생성하는 함수
    -> 인자를 0으로 주면 내부 동작에 의해 uid, gid가 0으로 설정된 cred가 생성 된다. (root 권한)

    cred 구조체

    커널은 현재 프로세스의 권한 정보를 cred라는 구조체에 저장하는데, uid, gid 등 여러가지 정보를 담고 있음

  • commit_creds

    -> 인자로 cred 구조체를 넘겨주면 새로운 권한을 적용한다.

 

*출처

더보기

defenit.kr/2019/10/21/Pwn/%E3%84%B4%20Research/%EC%BB%A4%EB%84%90_%EA%B8%B0%EC%B4%88/