CIDY
[Linux Kernel] 2. Kernel Debugging 본문
gdg디버거로 일반 바이너리는 많이 디버깅 해보았을 것이다. 커널도 이와 유사하지만, 커널에 중단점을 걸고 디버깅하면 중단점에 도달한 순간 운영체제가 정지한다는 차이가 있다.
따라서 디버깅 대상 커널과 디버거를 분리해야 한다. (그래서 QEMU를 이용한다. QEMU에서 게스트 커널을 돌리고, 로컬 gdb에서 remote로 디버깅하는 것이다. QEMU설명 및 설치, 이미지 부팅 등은 아래 글 참고)
https://orcinus-orca.tistory.com/239
커널 디버거는 크게 세 가지가 있다.
커널 디버거 : 직렬(시리얼)포트가 네트워크로 커널에 직접 디버깅 명령을 내리고, 커널 내부에서는 그 명령을 처리한다. 커널과 디버거가 같은 메모리에 위치하기 때문에 커널이 불안정하거나 디버거 메모리가 변조될 수 있으면 사용이 힘들다.
하드웨어 디버거 : 하드웨어 점검에 사용되는 JTAG등을 통해 회로의 상태를 직접 조회, 변경한다. (별도의 디버깅 장비 필요)
가상 머신 디버거 : 커널을 하드웨어 바로 위에서 구동하지 않고 가상 머신을 활용하는 방식이다. 별도의 디버깅 장비를 요구하지 않는다. 이런 방식으로 가상 머신 상태를 외부에서 조사하는 것을 VMI라고 한다.
커널은 일단 컴파일되어 운영체제로 기능하기 시작하면 수정하기가 쉽지 않다. 하지만 커널 권한으로 실행되어야 하는 장치 드라이버들은 지속적으로 개발 및 패치되고 있으며, 커널에 보안상 문제가 발견될 경우 즉각적인 패치가 필요하기도 하다.
따라서 커널에 필요한 기능을 탈부착할 수 있도록 LKM(적재 가능한 커널 모듈)을 도입했는데, 이를 흔히 커널 모듈이라고 부른다. 커널 모듈은 커널과 같은 메모리 공간을 사용하기 때문에 커널 모듈의 취약점은 커널의 취약점으로 연계될 수 있다.
디버깅 심볼, 스크립트
실습 파일을 기준으로 진행해보자.
디버깅 심볼이 컴파일된 프로그램에 남아있다면 역분석이 편리하지만, 많은 경우 심볼은 프로그램 수행에 필요하지 않아 용량이나 역분석 방해를 위해 심볼을 제거한다.
*실습을 진행하다보니 가상머신 내부에서 gdb설치가 안 되는 경우가 발생했다. (update가 되지 않았다.)
sudo apt update
sudo apt-get update
sudo apt-get install git
위 명령어 실행 후 gdb및 peda플러그인을 설치해주었다.
그런데 생각해보면 가상 머신 내부에서 gdb설치를 할 필요가 없었다.. remote로 디버깅하기 때문에.. 초심자의 삽질 정도로 이해하고 넘어가자.
gdb vmlinux 명령어로 커널 심볼을 읽어올 수 있다. (vmlinux는 리눅스 커널 컴파일 시 생성되는 ELF파일로, 커널 심볼과 디버그 심볼이 포함되어 있다.)
또, 리눅스 커널 소스코드에는 디버깅에 유용한 파이썬 스크립트가 있다. vmlinux-gdb.py파일인데, 이를 사용하기 위해서는 gdb에서 그 스크립트 적재를 허용해야 한다.
홈 디렉토리에 존재하는 .gdbinit파일에 위의 네 번째 줄을 추가해준다. (vmlinux-gdb.py경로)
vmlinux-gdb.py를 이용하면 gdb에서 커널 디버깅에 유용한 명령어들을 사용할 수 있다.
lx-symbols [경로…] | 리눅스 커널(vmlinux)과 모듈(.ko)의 심볼을 (다시) 불러들임 |
lx-dmesg | 커널 로그(kmsg) 버퍼 내용 출력 |
lx-lsmod | 현재 적재된 커널 모듈의 목록 출력 |
lx-ps | 프로세스, 스레드 목록 출력 |
lx-cpus | 커널이 인식하는 CPU 코어들의 관리 상태 출력 |
lx-list-check <list_head> | 주어진 list_head 연결 리스트 구조체의 일관성 검사(Unlink 관련 취약점 디버깅 시 유용) |
디버거 부착
가상 머신 내의 커널을 디버깅하려면 QEMU의 원격 디버깅 기능(네트워크 통신을 통해 가상 머신이나 다른 컴퓨터를 디버깅하는 것)을 사용해야 한다. QEMU는 게스트를 원격 디버깅할 수 있는 기능을 제공하고 있다.
./run.sh -S -gdb tcp:localhost:1234
이렇게 실행 스크립트 파일을 실행시킬 때 TCP원격 디버깅 옵션을 활성화하여 실행해준다. -S옵션으로 인해 게스트가(커널이) 일시 중지된 상태로 부팅된다. (만약 1234포트가 사용중이라면 다른 아무 포트나 써주자.)
target remote 127.0.0.1:1234
gef-remote localhost 1234 //gef의 경우
TCP원격 디버깅 기능을 사용하면, QEMU가 커널 디버깅을 위한 TCP디버깅 포트를 열어준다. gdb vmlinux한 뒤, gdb에서 위와 같은 명령어를 수행해주면 그 포트로 접근해서 QEMU의 게스트 커널을 디버깅할 수 있다.
// Path: testmod/testmod.c
...
int init_module(void) {
int i;
int sum = 0;
printk("Hello, world!\n");
printk("my_count = %d\n", my_count);
for (i = 1; i <= my_count; i++) {
printk("%d\n", i);
sum += i;
}
printk("Sum is: %d\n", sum);
return 0;
}
...
init_module()함수는 모듈이 커널에 적재될 때 가장 먼저 실행되는 모듈 함수이다. lx-symbols명령어로 testmod/를 심볼 경로에 추가해서 testmod.ko의 심볼 정보를 읽고, init_module()에 중단점을 설정해보자.
tbreak start_kernel
c
위 명령어로 start_kernel까지 진행시킨다.
lx-symbols ../testmod/
break testmod.c:init_module
커널에 아직 이 모듈을 적재하지 않았기 때문에 No source file named testmod.c라는 알림이 뜬다.
원래 그 다음에 Make breakpoint pending on future shared library load? 해서 y입력하면 pending해준다는데 그런 경고가 안 떠서; 그냥 진행해보기로 했다.
sudo insmod shared/testmod.ko
sudo rmmod testmod
위 명령어로 게스트에서 testmod.ko커널 모듈을 적재하고 탈착 수 있다. (insmod는 insert module의 약자이다.)
음 breakpoint를 걸지 않고 진행하면 모듈이 끝까지 진행되어 버려서 모듈 내에서 디버깅을 할 수가 없었다.
하지만 상세 디버깅 방법은 일반 바이너리와 크게 다르지 않은 것 같아 패스하도록 하겠다.
sudo poweroff
위 명령어로 가상 머신을 종료할 수 있다.
Ref.
https://dreamhack.io/lecture/courses/51
'Hack > Kernel' 카테고리의 다른 글
[Linux Kernel] 4. prepare & commit (1) | 2023.07.25 |
---|---|
[Linux Kernel] 3. KASLR (0) | 2023.07.25 |
[Linux Kernel] 1. QEMU (0) | 2023.07.25 |
[Linux Kernel] 0. Linux Kernel Exploit🐧 (0) | 2023.07.25 |
[Linux Kernel] Kernel Debugging Environment setup (0) | 2023.07.24 |