이번엔 system call interrupt가 어떻게 처리되는지 알아보려고 한다.
예를 들어 printf같은 함수를 사용하면 시스템 함수인 write를 호출하게 된다. 그리고 이 시스템 함수가 시스템 콜 인터럽을 발생시킨다.
mov eax, syscall#
INT 0x80
그러면 어셈블리 명령어로 mov eax, syscall#
가 실행되는데 이는 eax에 이 시스템 콜(write)의 번호가 저장된다는 뜻이다. 그리고 INT 0x80
이 실행된다. 16진수 80은 10진수로 8*16이므로 128이며 시스템 콜 인터럽의 인터럽 넘버를 의미한다.
이 과정까지는 사용자의 코드 내부인 user space
안에서 실행된다.
write같은 시스템 함수는 구현 부분이 커널 코드 내부에 있으므로 INT 0x80을 만나면 kernel space
로 진입한다.
시스템 콜 인터럽이기 때문에 IDT[0x80]으로 점프한 후 isr1인 entry_32.S(system_call)
로 들어간다. 그리고 그 안에서 isr2인 sys_write
로 이동해 실질적인 처리를 하게 된다.
ex1.c라는 간단하게 printf만 있는 사용자 프로그램을 만들었다고 해보자. 사실 printf는 라이브러리를 가져다 쓴 것이므로 ex1을 컴파일하면 만들어지는 파일에 이 ex1의 main뿐만 아니라 printf가 write함수를 사용해 구현한 부분까지도 포함되어있게된다.
따라서 cpu는 main을 읽다가 printf를 만나면 라이브러리 코드로 이동한다. 그러다 write를 만나면 위에서 언급했듯, mov, INT같은 어셈블리 코드가 실행되므로 인터럽이 발생한다.
이제 이 인터럽을 처리가히 위해 cpu는 운영체제 안의 ISR1으로 이동하게 된다. 그 안에 있는 system_call안에서 sys_call_table(eax에 저장했던 시스템 콜 번호)를 실행해 ISR2로 이동 후 본격적인 처리가 시작된다.
위 과정을 이해했다면 하이재킹도 쉽게 알 수 있다.
시스템 콜 인터럽을 처리하는 과정은 INT를 만나고 cpu는 system_call(ISR1)으로 이동하게 된다. 그리고 ISR2로 이동해 인터럽을 처리한다.
그러면 sys_call_table의 4번(write)을 내가 만든 ISR2로 바꿔놓으면 원래의 sys_write를 실행하지 않고 내가 만든 함수를 실행하게 되기 때문에 조작이 가능하다.
따라서 해킹 기법이다.
또는 새로운 시스템 콜을 직접 만들 수도 있다. sys_call_table의 모든 항목이 다 사용되는 것은 아니고 이름만 있고 비어있는 콜도 있기 때문에 이 것들을 원하는 이름으로 바꾸고 ISR2를 구현해 사용자 정의 시스템 콜을 만들 수 있다.
그렇게 만든 시스템 콜의 호출 방법은 아주 심플하게 syscall(x)
로 호출하면 된다(x에 넘버를 넣는다)