[교육이수] 리눅스 국비교육 최종 발표회 및 회고

OhJiwoo·2023년 10월 7일

경험 정리

목록 보기
4/4
post-thumbnail

2023.04~2023.08 기간동안 달려왔던 K-Digital Training 리눅스 시스템 및 커널 전문가 과정 교육을 드디어 수료했다. 이는 최종 발표회에 대한 기록이다.

최종 발표회는 서초구에 위치한 프로그래머스 본사에서 진행되었다. 각자 수업 내용 중에 흥미롭게 들었던 부분을 선택하여 본인만의 스타일로 재구성하여 발표하는 방식이다. 나는 프로세스 스택을 주제로 선택하여 발표하였다. 다음은 발표했던 내용을 정리한 것이다.

프로세스 스택

프로세스 스택의 구조

프로세스에 대한 정보는 task_struct 구조체에 저장된다. 이는 프로세스의 속성 정보를 표현하는 중요한 자료구조로, 프로세스가 생성되면 커널에 의해 프로세스에게 할당된다. 즉 프로세스마다 1개의 task_struct 구조체를 가지게 되는 것이다.

출처: https://www.researchgate.net/figure/Linux-task-struct-structure-with-PCX-fields_fig21_308737624

task_struct 구조체의 구조는 다음과 같다. 프로세스 상태(state), 프로세스 아이디(pid), 부모 프로세스(parent) 등을 저장한다. 이때 주의깊게 보아야할 속성은 thread_info이다. thread_info 구조체는 프로세스 스택의 최하단에 위치해있다.


출처: Linux Kernel Development (3rd Edition)

프로세스 스택은 높은 주소 공간에서 낮은 주소 공간으로 자란다. thread_info 구조체는 스택의 최하단에 위치하므로, 가장 낮은 주소 공간에 배치될 것이다. thread_info의 속성으로 task가 있는데, 이는 task_struct의 시작 주소를 가리킨다.

프로세스 내에서 함수가 실행되면 해당 함수로 jump 하기 전에 레지스터 값, 복귀할 주소 등을 저장해두어야 한다. 이러한 정보가 저장되는 공간이 바로 프로세스 스택이다. 즉, 함수가 실행이 되면 현재 정보를 스택에 push 한다. 스택에는 함수가 종료된 후 복귀할 주소가 들어있으므로, 함수 실행이 끝나면 스택에서 값을 pop 한 뒤, program counter가 복귀할 주소를 가리키게 함으로써 원래 상태로 되돌아간다.


이는 TRACE32라는 디버깅 툴로 확인한 스택 공간이다.
이때 실행한 함수의 목록은 다음과 같은데, invoke_syscall(), do_el0_syc() 함수를 스택 공간에서 확인할 수 있다.

스택을 더 자세히 살펴보면 다음과 같다.
가장 낮은 주소부터 스택이 시작된다. 스택에 최하단에 thread_info 구조체가 저장되어있는 것을 확인할 수 있다. 즉, thread_info의 주소는 스택의 시작 주소와 같다.

프로세스에서 호출된 함수의 흐름을 확인할 수 있다.

이 부분은 레지스터 값들이 저장된 것이다. 함수가 종료되고 나면 해당 값들을 pop하여 함수를 실행하기 전 상태로 돌아간다.

프로세스 스택 할당 과정

프로세스가 생성되면 리눅스 커널은 copy_process()라는 함수를 호출한다.

copy_process 함수 내부에서 dup_task_struct라는 함수를 호출한다.

dup_task_struct 함수에서 alloc_thread_stack_node()함수를 통해 스택을 위한 공간을 할당해준다. 그 뒤, task_struct의 stack 속성에 할당받은 공간을 연결해줌으로써 프로세스에 스택이 할당된다.

스택 카나리

스택 카나리는 프로세스 스택의 오버플로우를 검출할 수 있도록 저장되는 값이다.

출처: https://www.researchgate.net/figure/Linux-task-struct-structure-with-PCX-fields_fig21_308737624

위에서 본 그림을 다시 확인해보면, thread_info 구조체 아래에 stack_canary 가 저장되어 있다. 해당 stack_canary 필드는 검사에 이용되는 원본 카나리 값이다.

스택에 저장되는 스택 프레임은 다음과 같은 형태로 되어 있다. 스택은 그림을 기준으로 위에서 아래 방향으로 자라나기 때문에 만약 오버플로우가 발생한다면 카나리 값이 변조될 수밖에 없을 것이다. 함수 실행 전에 카나리 값을 프레임에 함께 넣어 push 하고, 함수 실행이 끝난 뒤 해당 프레임을 pop 하여 카나리 값을 검사했을 때 변조되었다면 오버플로우가 발생했음을 짐작할 수 있다. 이는 중요한 값인 복귀할 주소가 오염되지 않도록 방지하는 역할과 동시에 공격자에 의한 버퍼 오버플로우 공격을 감지하여 막는 역할도 수행할 수 있다.

카나리 값이 저장되는 과정

이는 어셈블리 명령어의 일부분이다. 하나씩 살펴보자.

mrs		x2,#0x3,#0x0,c4,c1,#0x0		;	x2, SP_EL0

이 명령어는 x2의 값을 SP_EL0 레지스터에 저장하는 명령어이다. x2에는 보통 현재 실행 중인 프로세스의 task_struct의 시작 주소를 저장하고 있다.

ldr		x3,[x2,#0x540]		;	x3,[x2,#1344]

이는 x2로부터 0x540만큼 떨어진 곳의 값을 x3에 저장하는 명령어이다. 앞서 x2에는 task_struct의 시작주소를 저장되어 있다고 하였다.
task_struct의 시작 주소로부터 0x540만큼 떨어진 곳에는 stack_canary 값이 저장되어 있다. (0x540을 10진수로 변환한 것이 1344이다) 그러면 스택 카나리 값을 x3에 저장한 것이다.

str		x3,[sp,#0x1A8]		;	x3,[sp,#424]

이는 sp(스택 포인터)가 가리키는 곳에서 0x1A8만큼 더한 곳에 x3 값을 저장하는 명령어이다. 즉 스택 공간에 스택 카나리 값을 저장했음을 알 수 있다.

이러한 과정을 거쳐 스택에 카나리 값이 저장된다. 이제 모든 함수 실행이 끝나고 나면 저장되었던 값과 task_struct에 저장되어 있는 값을 비교하여 만약 다르다면 _stack_chk_fail()함수가 컴파일러에 의해 삽입되면서 더이상의 코드 실행이 이루어지지 않고 panic 모드로 전환된다. 공격이 일어났을 수도 있으니 이를 방지하기 위해 아예 프로세스를 종료시켜버리는 것이다.

😊 회고

발표회를 위해 주어진 시간은 일주일 정도밖에 되지 않아서, 자료 조사, ppt 제작 및 발표 영상 촬영까지 하는 것이 빠듯했다. 단순히 영상을 듣는 것에서 그치지 않고 다른 사람에게 설명하기 위해서는 나부터가 해당 개념을 완벽히 숙지하고 있어야 했기 때문이다. 이게 맞는 개념인지를 끊임없이 고민하고 의심하면서 정확한 자료를 찾기 위해 노력했고, 결과적으로 발표를 잘 마칠 수 있었다. 멘토님께 어려운 개념인데 이해하기 쉽게 잘 정리했다는 칭찬도 받았다 :) 교육을 들으면서 기억에 남는 내용이 별로 없는데, 프로세스 스택만큼은 확실히 잊어버리지 않을 것 같다. 교육을 들으며 중간중간 포기하고 싶었던 적도 많았는데, 그래도 포기하지 않고 하루하루 해나가다보니 여기까지 올 수 있었던 것 같다.

교육은 수료하였지만, 학업과 병행하다보니 놓친 부분도 많이 있어서 아쉽기는 하다. 그래도 생소한 리눅스 커널에 대해 동작 원리를 조금이나마 이해할 수 있었던 교육이었던 것 같다.

profile
아키텍트를 목표로 공부하는 2년차 시스템 관리자

0개의 댓글