2013년 11월 26일 화요일

2.4.22 이하 리눅스 커널의 do_brk() 취약점

자료출처: 해커스쿨

2003년 12월 초~중순에 발표된 리눅스 운영체제 관련 취약점입니다.
현재 대부분의 리눅스 시스템이 버전 2.4.22 이하의 커널을 사용하고 있기 때문에 이 취약점으로 인한 여파가 클 것으로 예상됩니다.
한 예로 최신 리눅스 배포본 중 하나인 레드햇 9.0 역시 버전 2.4.22 이하의 커널을 사용하고 있습니다. 따라서, 서버 관리자님들은 신속히 이 취약점에 대한 패치 작업을 진행하여 크래커들로부터의 부당한
피해를 미연에 방지해야 할 것입니다.

[공격의 대상]
이 취약점은 2.4.22 이하의 리눅스 커널을 사용하는 모든 운영체제에
해당합니다. 서버 관리자 분들은 다음과 같은 방법으로 현 시스템의
커널 버젼을 확인할 수 있습니다.

[root@work root]# uname -r
2.4.23
[root@work root]#

만약 위 숫자가 2.4.22보다 낮을 경우엔 이 취약점에 의하여 공격당할 수 있으므로 필히 패치 작업을 진행하셔야 합니다.

참고1) 이 취약점은 로컬 공격에 해당합니다. 즉, 타겟의 쉘 접속 권한을 가지고 있는 공격자만이 이 취약점을 악용할 수 있습니다.
      
참고2) 레드햇, 와우, 수세 등등 배포본의 종류와 관계없이 리눅스 기반의 커널을 사용하고 있는 모든 운영체제에 해당하는 취약점 입니다.


[공격의 피해]
공격자는 이 취약점을 이용하여 일반 쉘 권한을 최고 관리자
쉘 권한으로 상승시킬 수 있습니다.

====================================================
Trying 192.168.0.2 ...
Connected to 192.168.0.2.
Red Hat Linux release 9 (Shrike)
Kernel 2.4.22 on an i686
login: user
Password:
Last login: Tue Dec 16 09:21:01 from 192.168.0.1
[user@localhost user]$
[user@localhost user]$ id
uid=501(user) gid=501(user) groups=501(user)
[user@localhost user]$
====================================================

이처럼 일반 사용자 권한을 가지고 있는 사용자가 공격 코드를 만들어 실행합니다.

====================================================
[user@localhost user]$ cat > exp.c
.. 악용 가능성이 있음으로 생략 ..
[user@localhost user]$ gcc -o exp exp.c
[user@localhost user]$ ./exp
sh-2.05b# id
uid=0(root) gid=0(root)
sh-2.05b#
====================================================

이처럼 단시간에 최고 관리자 권한인 root를 획득하게 됩니다.
root 권한을 가진 사용자는 시스템의 파일들을 삭제/변경/열람할 수 있음으로 취약한 시스템을 운영하고 계신 서버 관리자님들은 필히 패치를 하셔야 합니다.


[취약점 분석]
작성일 : 2003년 12월 6일
작성자 : iSEC Security Research (http://isec.pl)
번역 : 해커스쿨 (http://hackerschool.org)

이 문서는 지난 12월 초에 발견된 새로운 리눅스 커널 결함에 대하여 외국의 iSEC Security Research사에서 분석한 문서를 번역한 것입니다.
번역과 배포의 목적은 이 취약점 분석을 통한 리눅스 커널에 대한 지식 확충을 위해서이며, 공격과 관련된 악성 코드는 악용 가능성이 있음으로 번역/첨부하지 않습니다.

[문서 목차]
1. 개요
2. 리눅스의 메모리 관리
3. 취약점 요약
4. 상세 분석
   1) 공격 구상
   2) 힙 영역의 확장
   3) 커널 메모리 보호의 우회
   4) 커널 구조체
   5) 권한 상승
   6) Cleanup 문제

1. 개요

버전 2.4.22 이하의 리눅스 커널에서 메모리 관리 체계에 관련된 심각한 보안 취약점이 발견되었다. 그리고 이 취약점은 오픈소스 커뮤니티조차도 알려지지 않은 채 조용히 2.4.23 버전과 2.6.0-test6 releaqse 버전에 패치되어 배포되었다.
커널 개발자들은 이 취약점이 악용 가능하다고 생각하지 않았던건지, 아니면 그들이 만든 소프트웨어에 대한 새로운 보안 권고문을 쓴다는 것이 두려웠던 건지는 모르는 일이다.

2003년 9월말, 리눅스 커널에 대한 일반적인 분석을 하던 도중, 우리는 똑같은 버그를 발견했고, 그것의 심각한 영향을 즉각 이해했다. 또한, 멀지않아 우리는 간단한 익스플로잇 코드도 구현할 수 있었다.

이 문서에서는 do_brk() 함수에서 발견된 취약점의 기술적인 상세 내용과 익스플로잇을 구현하는 동안의 우리의 연구 결과를 기술하고 있다.
이 문서는 또한, 우리가 효과적인 익스플로잇을 만들기 위해 사용했던 수 없이 많은 기술적 노하우(각종 보안 패치로 방어된 커널에서의 권한 상승 등)를 담고 있다.

2. 리눅스의 메모리 관리

x86 계열의 CPU에서 구동되는 최근의 리눅스 커널은 간소화된 가상 메모리 모델에 의하여 관리된다. 32비트 아키텍쳐에서는 각 사용자 프로세스가 0에서 4기가바이트까지의 가상 메모리를 사용할 수 있다. 이것은 실제 물리 메모리보다 훨씬 많은 양이다. 가상 메모리는 선형의 주소 공간이 4kb 사이즈의 페이지 단위로 나뉘어진 것과 같다. 이 페이지들은 물리 메모리 페이지에 적당하게 매핑된다. 프로세스의 페이지 테이블은 매핑된 각각의 페이지를 위한 보호 특성을 포함한 추가적인 특성을 가지고 있다.

프로세스의 가상 메모리는 두부분으로 나뉜다. TASK_SIZE 는 커널 상수로서, 가장 낮은 권한 레벨에서 돌아가는 코드에 대한 접근 가능한 메모리의 최대 제한 값을 정의한다.

1기가바이트 이하의 물리 메모리를 사용하는 시스템에서는 일반적으로 이 값이 0xc0000000으로 정의되어 있다. (이 문서의 모든 예제는 이 값을 참조한다.)
이 제한선 위에 있는 메모리에는 데이터 구조를 포함한 커널 코드가 있지만, 페이지 프로텍션 메카니즘에의해 유져에게는 접근 권한을 허락하지 않는다.
이 부분은 단지 커널로의 접근 권한이 있는 코드에 의해서만 제어가 가능하다.

TASK_SIZE 제한선 아래쪽의 일반 유져가 접근할 수 있는 메모리 영역은 더욱 많은 논리 구역으로 나뉘어져 있다. 각 영역은 가상 주소 범위와 프로텍션 특성에 의해 달라진다. 또한, 각 영역은 서로 다른 기능을 수행한다. 예를들어, ".text"라는 영역은 적재된 바이너리의 실행 가능한 코드를 담고 있고, ".data" 영역은 읽기/쓰기가 가능한 데이터를 담고 있으며, ".rodata" 영역은 읽기만 가능한 데이터를 담고 있다.

일반적인 사용자 프로세스의 메모리 구조는 다음과 같다:

bash$ cat /proc/self/maps
08048000-0804c000 r-xp 00000000 03:02 207935 /bin/cat
0804c000-0804d000 rw-p 00003000 03:02 207935 /bin/cat
0804d000-0804e000 rwxp 00000000 00:00 0
40000000-40015000 r-xp 00000000 03:02 213752 /lib/ld-2.3.2.so
40015000-40016000 rw-p 00014000 03:02 213752 /lib/ld-2.3.2.so
40016000-40017000 rw-p 00000000 00:00 0 40020000-40021000 rw-p 00000000 00:00 0
42000000-4212f000 r-xp 00000000 03:02 319985 /lib/tls/libc-2.3.2.so
4212f000-42132000 rw-p 0012f000 03:02 319985 /lib/tls/libc-2.3.2.so
42132000-42134000 rw-p 00000000 00:00 0
bfffc000-c0000000 rwxp ffffd000 00:00 0

이 리눅스 커널에서의 메모리 섹션들은 가상 메모리 영역이라고도 불리어진다.

효율적인 메모리 사용을 위하여 커널은 각 프로세스의 모든 가상 메모리 영역을 직접 관리한다. (스와핑, 디맨드 로딩, 프로젝션 폴트 핸들링 등..)
각각의 가상 메모리 영역은 에 정의된 vm_area_struct 구조체에 설명되어 있으며, 이 구조체에서 가장 중요한 부분은 다음과 같다.

struct vm_area_struct {
unsigned long vm_start;
unsigned long vm_end;
pgprot_t vm_page_prot;
/* ... */
}

프로세스의 가상 메모리 영역은 메모리 디스크립터 구조체(mm_struct)와 연결되어 있다. 이것은 mm 멤버 변수와 다음의 구조체를 이용해서 프로세스의 디스크립터(task_struct)를 참조한다.

struct mm_struct {
struct vm_area_struct * mmap; /* list of VMAs */
/* ... */
int map_count; /* number of VMAs */
/* ... */
unsigned long start_brk, brk, start_stack;
/* ... */        
}

리눅스 메모리 관리에 관련된 더 자세한 설명은 다음 섹션에서 계속된다.

3. 취약점 요약

do_brk()는 프로세스의 메모리 힙(brk)을 관리하여 그것을 알맞게 늘이고 줄이기 위해 간접적으로 호출되는 커널 내부 함수이다. 사용자는 내부적으로 do_brk()를 호출하는 brk(2) 시스템 콜을 이용하여 힙을 조작할 수 있다. do_brk()는 nmap(2) 시스템 콜의 간소화된 버전으로, 초기화되지 않은 데이터를 위한 익명의 매핑(anonymous mappings)만을 다룬다.

do_brk() 함수는 인자 값에 대한 바운드 체킹을 하지 않기 때문에 이를 이용하여 임의의 큰 가상 메모리 영역을 생성하도록 함으로써 유져가 접근 가능한 메모리영역을 초과할 수 있게 된다.

일반적인 환경에서 힙은 프로세스의 가상 메모리에 포함된 한 구역이며, TASK_SIZE 경계 아래로 수 킬로바이트에서 수 메가바이트의 범위에 분포된다. 일반적으로 힙은 주로 malloc() 라이브러리 함수에 의해 동적 할당된 데이터를 취급하는데 이용된다. do_brk() 커널 함수가 바운드 체킹을 하지 않음으로 TASK_SIZE 경계위쪽까지 힙 영역이 확장되는 것이 가능하다. 따라서, 커널 메모리 관리 시스템은 사용자가 접근할 수 없도록 보호된 커널 영역의 메모리가 유져 프로세스의 힙에 속하는 것으로 착각하게 된다. 그러나, 이 속임수는 커널 메모리의 직접적인 접근을 가능하게 하지는 않는다. 커널 페이지는 CPU의 MMU 장치에 의해서 보호되기 때문이다. 하지만, 커널 페이지의 보호를 교란시키기 위해 아주 큰 VMA를
조작하는 다른 시스템 콜을 사용함으로써 공격이 가능해진다.

I4. 상세 분석

1) 공격 구상

불완전한 do_brk() 함수는 usblib() 바이너리 포맷 핸들러로부터 뿐만아니라 ELF와 a.out 바이너리 포맷 로더에 의해서도 내부적으로 호출된다.
do_brk() 취약점을 익스플로잇 하기 위해 sys_brk() 콜과 더불어 3가지 다른 방법이 사용될 수 있다. 이 문서에서는 공격을 위하여 sys_brk() 시스템 콜만을 사용할 것이다.

2) 힙 영역의 확장

힙은 요청된 주소 범위가 이미 할당되지 않았을 경우에만 확장이 허가 된다.
일반적인 프로세스의 스택 영역은 보통 TASK_SIZE 주소 바로 아래인 프로세스 메모리의 가장 상위 부분에 위치한다. 따라서, 스택은 익스플로잇하기 전에 반드시 메모리의 어디론가로 옮겨져야 한다. 다음 단계는 힙이 프로세스의 메모리 배치에서 가장 마지막 부분이 되도록 하는 것이다.

이제 우리는 힙을 커널 메모리 영역에 걸치도록 하기 위해서 brk(2) 시스템 콜을 이용할 것이다. 이것은 반드시 매번 상대적으로 적은 바이트의 힙을 늘려가며 여러번에 걸쳐 brk를 호출하여 행해져야 한다. 이것은 가상 메모리에 매핑되어 있을 커널 경계를 do_brk() 함수를 이용하여 한번에 우회할 필요가 있기 때문이다.

이 세가지 단계 이후에 힙의 모습은 아마도 다음과 같을 것이다.

080a5000-fffff000 rwxp 00000000 00:00 0

만약 가상 메모리의 모든 영역이 종료된 프로세스 또는 매핑되지 않은 메모리 페이지에 속하거나 커널 메모리 관리자에게로 넘어가 버린다면, 우리의 프로세스는 종료될 것이다. 따라서, 시스템을 불안정하게 하거나 리부팅을 야기하는 커널 메모리의 일부분은 접근을 하지 않는다.

3) 커널 메모리 보호의 우회

힙 영역을 0xc0000000 경계 이상으로 확장한 후에도 아직 유져 프로세스에 의한 직접적인 접근은 불가능하다. 모든 커널 메모리 페이지는 슈퍼바이저 비트란 것이 붙어 있기 때문이다. 이러한 승인되지 않은 접근은 하드웨어 MMU 장치에 의해서도 방지된다. 하지만 ptrace(2) 시스템 콜을 이용한 간접적인 접근은 가능할 수도 있다. 그러나, 이 방법은 보통 대부분의 리눅스 시스템에서 방지되어 있음으로 사용하지 않을 것이다.

따라서 우리는 커널 페이지에 접근하기 전에 프로텍트를 해제할 것이다. 즉, 커널 페이지를 읽고, 쓰기 가능한 상태로 만든다. 다행이도 간단한 연구 끝에 올바른 가상 메모리 영역이 프로세스의 메모리 기술자에 존재한다면, mprotect(2) 시스템 콜이 커널 페이지에도 완벽히 먹혀든다는 것을 발견했다. 따라서, 우리는 커널 내부의 거의 어떠한 페이지 프로텍트도 원하는대로 변경할 수 있다.

하지만, 페이지 사이즈 확장(PSE) 기능을 가지고 있는 어떤 x86 프로세서는 성능 상의 이유로 커널 코드 페이지 크기를 4MB로 되게 한다. 그리고 mprotect(2) 시스템 콜은 즉각적인 충돌을 일으킬 그런 큰 페이지는 다루지 않는다. 그것은 오직 4kb 크기의 페이지에만 사용된다. 그러한 큰 크기의 페이지들은 커널 메모리 kmalloc()와 vmalloc() 할당자에 의해 사용된다. vmalloc() 함수는 한 예로 커널 모듈을 위한 메모리 할당에 사용된다.

지금까지의 정보를 이용하여 우리는 kmalloc, vmalloc화된 커널 메모리에 어떠한 것도 쓸 수 있음을 알았다. 두가지 중요한 문제는 무엇을 쓰고 또, 그것을 어디에다가 쓸 것인가하는 것이다.

4) 커널 구조체

우리는 커널 메모리 allocator를 이용하여 약간의 데이터를 커널 메모리 상에 잠시동안 상주하게 만들 수 있다. 그리고 우리가 그 내용물을 수정한 후에 쉽게 권한을 상승시킬 수 있는 구조체를 찾아야한다.

프로세스의 LDT(local descriptor table)은 segment descriptor라고 하는 각각의 세그먼트 경계와 접근 권한을 기술하는 배열을 가지고 있다. 이 배열은 modify_ldt(2) 시스템 콜을 이용해서 LDT 엔트리에 기록되는 프로세스를 vmalloc()를 사용하여 할당한다. LDT는 프로세스가 끝나지 않고 있을 때까지 메모리에 존재한다. 커널은 LDT 배열의 엔트리를 쓰기 위한 권한을 제한한다. 이것은 ring0 권한이라고 불리는 프로세스로부터 사용자 프로세스의 LDT 오용을 막기 위해서이다. 그러므로, 만약에 우리가 LDT 배열의 어떤 엔트리에 값을 쓸 수만 있다면 우리는 쉽게 권한을 상승시킬 수 있을 것이다.

커널 메모리의 layout은 시스템마다 다르다. 그것은 커널 설정과 컴파일러와 컴파일 옵션에 의존적이다. 메모리 할당에 의해서 리턴되는 주소 값은 매우 예측하기 어렵다. 이 부분이 익스플로잇의 가장 어려운 부분이다.

우리의 목표는 추측하여 공격하지 않는 것이다. 우리는 커널 메모리의 LDT 배열의 정확한 주소를 찾는 방법을 원한다. 그리고 이 부분은 익스플로잇을 만드는데 가장 많은 시간이 소모되었다. 그것은 바로 리눅스의 signal handling를 사용하는 것이다.

만약 시그널이 임의로 설치된 시그널 핸들러와 함께 프로세스로 보내진다면, 그 시그널 핸들러 루틴은 시그널의 정보(시그널 전송자와 그 시그널이 보내진 이유 등..)를 받게될 것이다. SIGSEGV 시그널은 유져 프로세스가 접근 불가능한 메모리영역에 읽거나 쓰기를 시도할 때마다 보내진다.

그리고 각각의 페이지 폴트는 do_page_fault() 커널 함수에 의해서 다루어진다.
이 함수의 인자들 중 하나는 CPU에서 제공되는 에러코드이다. 이 인자는 page fault가 필요한 정확한 이유와 faulty page를 적재하는 등, page fault에 대한 처리가 필요한지를 기술하며, copy-on-write 혹은 잘못된 메모리의 요청을 한 경우에 SIGSEGV 시그널을 이용해서 죽이기 위해서 사용된다.

SIGSEGV 시그널이 발생하는 경우, 커널의 do_page_fault() 루틴은 에러 코드값을 시그널 핸들러에게 고의적으로 누락한다. 우리가 관심을 가지는 것은 다음 두 경우에 대한 에러코드 값이다.

- 메모리에 페이지가 맵핑 되지 않아서 page fault 가 일어나는 경우
- 페이지 프로텍트가 접근을 허락하지 않아서 page fault 가 일어나는 경우

그러므로 에러코드의 값은 페이지가 사용자에 의해서 직접적으로 접근이 불가능함에도 불구하고 TASK_SIZE 제한 위의 주소가 커널 주소 공간에 페이지 맵 되었는지 아닌지를 판단하기에 적당하다. 이 조건은 예를들어 커널 메모리의 정확한 맵을 생성하는 verr 어셈블러 명령어를 사용하는 TASK_SIZE 경계 위쪽의 각 페이지를 체크한다.

만약에 우리가 커널에 할당하기 전과 LDT 메모리를 할당한 뒤의 배열의 2개의 맵을 만들수 있다면, 우리는 쉽게 이 맵을 비교할수 있고, 커널 구조에 할당된 정확한 주소 값을 알 수 있을것이다.

5) 권한 상승

커널 메모리 안의 LDT 배열을 찾은 후에 우리는 그곳에 gate 디스크립터 함수를 생성할 수 있다. 이 gate 디스크립터 함수는 유져 레벨에서 커널 레벨로 권한을 상승하는 역할을 한다.

i386에서의 gate 함수는 세그먼트 셀렉터 코드와 gate 코드로 향하는 엔트리 포인트를 포함하고 있다. 세그먼트 셀렉터 코드는 gate 함수에 의하여 실행되는 코드의 권한을 결정한다. 반면에, 디스크립터 권한은 호출된 코드의 필요 권한을 결정한다.

gate 함수는 일반 프로세스를 커널 모드로 변경하는 int $0x80 시스템콜 메카니즘과 비슷한 방식으로 작동한다. 시스템 콜 신호와의 가장 큰 차이점은 쓰기 가능한 LDT에 단지 CPLO 권한에서 호출된 실행 가능한 루틴의 주소만을 저장할 수 있다는 것이다.

우리는 디스크립터 권한 레벨 3과 KERNEL_CS(CPLO를 위한 커널 코드 디스크립터)와 같은 값을 가진 세그먼트 코드를 가진 LDT 안의 gate 함수를 TASK_SIZE 보다 낮은 값의 프로세스 주소 공간으로 가리키기로 결정했다. 그러므로 유져 모드의 태스크가 CPLO에서 자신의 코드를 직접 실행 할 수 있게 된다.

이 태스크를 실행하기 위해서는 현재 프로세스로의 포인터와 실제 exploit 코드를 담은 C 함수를 가르키도록 계산된 어셈블러 코드가 CPLO의 레벨이 어떤 커널 구조체로도 변경할 수 있는 상태로 실행되는 동안에 가능하다.

프로세스의 권한을 변경하는 것은 아주 쉬운 작업이다.
우리가 해야할 오직 한가지 작업은 커널 메모리 안 어딘가에 존재하는 task_struct를 찾은 후 그것의 UID, GID와 capability set을 변경하는 것이다.
task_struct에 대해 자세히 알고 싶다면, 커널 소스의 [linux/sched.h]를 보면된다.

하지만, 만약 EUID=0을 얻은 후에 다른 바이너리를 직접 실행하려는 단순한 원리라면, 프로세스의 EUID와 EGID가 모두 바뀌어야만 한다. 왜냐하면, EUID=0에 의해 호출된 execve() 시스템 콜은 전체 프록세스의 능력이 reenable하게 되기 때문이다.

6) Cleanup 문제

권한을 상승시킨 후에는 시스템이 다운 되는 것과 해당 프로세스가 깨끗하게 종료되지 않는 것을 방지하기 위하여 CPLO 안에 clean up 코드 실행을 위한 공간을 만들어야 한다. 우리의 아이디어는 TASK_SIZE의 제한을 확장한 vm_area_struct 구조체를 커널 메모리에서 찾는 것이다. 이 구조체들이 TASK_SIZE 값까지 유지되도록 변경하면 안정적인 상태로 프로그램을 종료하기에 충분하다.

[취약점 패치]
이 취약점을 패치하는 방법은 크게 두 가지로 볼 수 있습니다.

1. 취약점이 패치된 2.4.23 버전으로 커널을 업그레이드.
2. 취약점에 대한 공격을 방어하는 커널 모듈 적재.

1번의 경우 커널을 새로 컴파일하여 설치해야하기 때문에 다소 많은 시간이 소요되지만, 이 취약점의 오류를 근본적으로 해결할 수 있습니다.

2번의 경우 방어 모듈을 적재함으로써 이 취약점을 무력화시킵니다.
아주 간단한 절차로써 방어를 할 수 있지만, 커널의 근본적인 결함을 없애는 것은 아님으로 이 취약점이 응용된 또 다른 기법에 의하여 공격당할 수 있는 가능성이 남습니다.

다음의 두 패치 방법 중 원하는 것을 선택하십시오.

[커널 업그레이드를 통한 패치]
시작하기 전에..

참고1) 만약 예상치 못한 이유로 커널 업그레이드에 실패할 경우에도 기존의 커널을 그대로 사용할 수 있습니다.

참고2) 커널 업그레이드 작업은 네트워크가 아닌, 직접 콘솔 상에서 하시기 바랍니다. 부팅 시 원하는 커널 이미지를 선택하고, 만약 새로운 커널이 시스템의 요구에 맞지 않아 정상적으로 부팅되지 않을 경우 복구하기 위해서 입니다.

먼저, 최신 버젼의 리눅스 커널을 www.kernel.org에서 다운로드 받습니다.
위 사이트에 접속한 후, The latest stable version of the Linux kernel is:  2.4.23 부분의 오른쪽에 있는 F(full)라는 링크를 클릭하시면 됩니다.

[직접 다운]
http://www.kernel.org/pub/linux/kernel/v2.4/linux-2.4.23.tar.bz2

받으신 후에는 리눅스 서버로 파일을 업로드하신 후, /usr/src/ 디렉토리에서 압축을 해제시킵니다.

=======================================================================
[root@work src]# bzip2 -d linux-2.4.23.tar.bz2  <- bz2를 tar로..
[root@work src]# tar xvf linux-2.4.23.tar <- tar 압축 해제
[root@work src]# tar xvf linux-2.4.23.tar
linux-2.4.23/
linux-2.4.23/Makefile
linux-2.4.23/MAINTAINERS
linux-2.4.23/CREDITS
linux-2.4.23/crypto/
... 생략 ...
[root@work src]#
=======================================================================

이제 linux-2.4.23 디렉토리로 이동한 후, 커널의 옵션을 설정하는 make menuconfig 명령을 입력합니다.

=======================================================================
[root@work src]# cd linux-2.4.23
[root@work src]# make menuconfig
=======================================================================

그럼 메뉴 환경으로 구성된 옵션 설정 화면이 나타나며, 많은 옵션들 중 다음과 같은 것들을 설정해 줍니다.

1. network device support 메뉴에서 엔터를 입력한 후, socket filtering 부분에 스페이스 키로 *표 체크합니다. (*은 커널에 포함하여 컴파일, M은 모듈 형태로 빼내어 컴파일합니다.)

2. network device support -> ethernet(10 or 100M)에서 자신의 랜카드 부분에 *표 체크를 합니다.

3. 다시 처음으로 가서, File systems -> EXT3에 *표 체크를 합니다.


이제 exit를 눌러서 종료하고, save할 것이냐고 물어보면 yes를 선택합니다.
다음엔 다음과 같은 명령을 차례로 입력하여 커널 컴파일을 시작합니다.

=======================================================================
[root@work linux-2.4.23]# make dep  <- 의존성 체크
... 생략 ...
[root@work linux-2.4.23]# make clean  <- 관련 파일 초기화
... 생략 ...
[root@work linux-2.4.23]# make bzImage  <- 커널 컴파일
... 생략, 오랜 시간 소요 ...
[root@work linux-2.4.23]# make modules  <- 모듈 컴파일
... 생략 ...
[root@work linux-2.4.23]# make modules_install  <- 모듈 설치
... 생략 ...
[root@work linux-2.4.23]#
=======================================================================

모두 완료하였다면, 컴파일 완료된 커널 파일을 /boot/ 디렉토리로 옮깁니다.

============================================================================
[root@work linux-2.4.23]# cp arch/i386/boot/bzImage /boot/new_kernel
[root@work linux-2.4.23]#
============================================================================

이 때, /boot/로 복사하는 파일명은 마음대로하되, bzImage라는 이름으로 지정할 경우엔 기존의 커널을 덮어씌워 버림으로 주의하도록 합니다.

이제 마지막으로, 부트 로더에 새로운 커널 정보를 등록합니다.
여기에선 lilo를 기준으로 설명하며, 혹시 grub이나 기타 다른 부트 로더에 대한 수정 방법을 아시는 분은 댓글로 알려주시면 추가하도록 하겠습니다.

먼저, vi /etc/lilo.conf를 입력합니다.
그 다음엔 다음과 같이 새로운 커널의 정보를 추가합니다.

==================================================================
image=/boot/new_kernel (위에서 지정한 파일 이름)
      label=new
      root=/dev/hda2 (최상위 디렉토리가 설치된 파티션이 hda2가 아닐 경우 알맞게 수정)
==================================================================

vi를 종료하고, 변경된 lilo 설정을 적용시키기 위하여 /sbin/lilo 명령을 한번 실행해 줍니다.

이제 리부팅을 한 후, 부트로드에서 커널을 선택하는 화면이 나타나면 새로 컴파일 한 new라는 이름의 커널을 선택해 줍니다.
이제 부팅이 완료된 후, uname -r 명령으로 변경된 커널의 버젼을 확인하고 TCP/IP 네트워크 등의 기능이 정상적으로 작동하는지 확인합니다.
모든 것이 정상적으로 작동하는 것이 확인된 후에는 다시 /etc/lilo.conf 파일을 열어 상단의 default 부분의 값을 새로운 커널의 이름인 new로 바꾸어 줍니다. 이렇게 하면, 부팅 시 자동으로 new라는 이름의 커널이 선택됩니다. 수정 후에는 다시 한번 /sbin/lilo를 실행합니다.

만약, 커널 컴파일 후에 부팅이 되지 않는다거나, 네트워크가 작동하지 않는다거나, 혹은 특정 하드웨어를 인식하지 못한다면, 각 오류에 대한 수많은 원인이 있을 수 있음으로 kldp.org 등의 리눅스 전문 사이트에서 해결책에 대한 정보를 검색하시기 바랍니다.

만약 커널 업그레이드를 포기하게 되었을 경우엔, 원래의 커널 이미지로 부팅을 하신 후, /etc/lilo.conf에 등록한 새로운 커널 정보를 삭제하시고, 두 번째 방법인 방어 모듈을 이용한 패치를 참고하시기 바랍니다.


[방어 모듈 적재를 통한 패치]
리눅스 시스템은 모듈이라는 것을 제공합니다.
모듈이란, 커널 레벨에서 작동하는 프로그램이 메모리에 동적으로 적재되어 커널의 기능을 보완해주는 역할을 합니다.

다음의 모듈을 컴파일하여 메모리에 적재하면 이 취약점에 대한 공격을 방어할 수 있습니다.

[방어 모듈 소스 코드]

위 코드를 모두 복사하여 다음과 같이 brk_fix.c로 생성합니다.

================================================
[root@work root]# cat > brk_fix.c
[붙여넣기]
[CTRL+D]
[root@work root]#
================================================

혹은 ftp나 rz 등으로 위 소스 파일을 업로드하여도 됩니다.
파일 생성 후에는 다음과 같이 컴파일 합니다.

================================================
[root@work root]# gcc -c brk_fix.c
[root@work root]# ls brk_fix.o
brk_fix.o
[root@work root]#
================================================

위처럼 brk_fix.o라는 파일이 생성되었다면 컴파일이 성공한 것입니다. 반면에, 다음과 같은 에러가 출력되면서 컴파일에 실패하는 경우도 있습니다.

==============================================================
[root@work root]# gcc -c brk_fix.c
brk_fix.c: In function `init_module':
brk_fix.c:74: `PAGE_OFFSET' undeclared (first use in this function)
brk_fix.c:74: (Each undeclared identifier is reported only once
brk_fix.c:74: for each function it appears in.)
brk_fix.c:83: `do_brk' undeclared (first use in this function)
brk_fix.c:114: union has no member named `usecount'
brk_fix.c: In function `my_brk':
brk_fix.c:136: `PAGE_OFFSET' undeclared (first use in this function)
brk_fix.c:141: `do_brk' used prior to declaration
[root@work root]#
==============================================================

이 경우는 컴파일에 필요한 헤더가 설치되지 않았기 때문이며, 다음 압축 파일을 다운받아 현재 폴더에 복사해 넣습니다.

[헤더 파일 모음]

받았다면, 다음과 같이 압축을 해제합니다.

============================================
[root@work root]# tar xvfz include.gzip
include/acpi/
include/acpi/actypes.h
include/acpi/actbl2.h
include/acpi/actbl1.h
... 생략 ...
[root@work root]#
============================================

압축해제 후에 다시 다음과 같이 -I 옵션을 추가하여 컴파일합니다.
-I는 대문자 '아이'이며, 헤더 파일의 위치를 지정합니다.

========================================================
[root@work root]# gcc -c brk_fix.c -I./include
[root@work root]# ls brk_fix.o
brk_fix.o
[root@work root]#
========================================================

이제 다음과 같이 컴파일된 모듈을 적재합니다.

==========================================
[root@work root]#  insmod brk_fix.o
Module brk_fix loaded.
[root@work root]#
==========================================

만약 다음과 같이 커널 버젼이 맞지 않는다는 에러 메시지가 출력된다면, --force 옵션을 추가로 붙여 에러를 무시하도록 합니다.

brk_fix.o: kernel-module version mismatch
           brk_fix.o was compiled for kernel version 2.4.23
           while this kernel is version 2.4.20-8.

==========================================================
[root@work root]# insmod brk_fix.o --force
Warning: kernel-module version mismatch
        brk_fix.o was compiled for kernel version 2.4.23
        while this kernel is version 2.4.20-8
Warning: loading brk_fix.o will taint the kernel: forced load
  See http://www.tux.org/lkml/#export-tainted for information about tainted modu
les
Module brk_fix loaded, with warnings
[root@work root]#
==========================================================

혹은, 모듈을 적재할 때 만약 다음과 같은 에러 메시지가 출력된다면 커널 자체에 시스템 콜 후킹 기능이 제공되지 않는 것이니 "커널 업그레이드"를 통한 패치를 진행할 것을 권장합니다.

==================================================
[root@work root]# insmod brk_fix.o
brk_fix.o: unresolved symbol __start___kallsyms
[root@work root]#
==================================================

방어 모듈을 이용한 패치 작업을 완료하였다면, 다음과 같이 모듈 목록에 포함된 brk_fix를 확인할 수 있을 것입니다.

====================================================
[root@work root]# lsmod | grep brk_fix
brk_fix                 1204   1
[root@work root]#
====================================================

이제 만약 공격자가 exploit을 이용하여 root 권한을 획득하려 한다면 다음과 같은 현상이 발생하면서 공격에 실패하게 됩니다.

=============================================================
[user@work user]$ ./kernelbug
[-] Unable to change page protection: Cannot allocate memory
[-] Unable to exit, entering neverending loop.

[1]+  Stopped                 ./kernelbug
[user@work user]$  
=============================================================

또한, 서버 관리자는 다음과 같이 콘솔 화면을 확인하는 방법으로 공격 시도가 있었는지의 여부를 확인할 수 있습니다.

==============================================
[root@work root]# cat /dev/vcs1 (혹은 vcs2)
caught do_brk exploit!!!
caught do_brk exploit!!!
caught do_brk exploit!!!
caught do_brk exploit!!!
caught do_brk exploit!!!
[root@work root]#
==============================================

마지막으로, 서버가 재부팅될 때마다 자동으로 모듈이 적재되도록 /etc/rc.d/rc.local 파일의 끝 부분에 다음의 명령을 추가해 넣습니다.

insmod /root/brk_fix.o --force

댓글 없음:

댓글 쓰기