※ 질문/내용오류/공유할 내용이 있다면 jinkilee73@gmail.com으로 메일 주세요 :-)
얼마 전에 포스팅한 buffer overflow shell code execution 관련해서 추가적으로 kernel 관련한 내용을 포스팅 할 일이 생겨서 이렇게 포스팅을 한다. shellcode execution 포스팅에서 수행시키는 그 shellcode를 실행하면 실제로 shell이 뜨게 되는데 왜 shell이 실행될까 궁금해서 공부하던 중 알아낸 내용인데 너무너무나도 중요하다고 판단해서 포스팅하려 한다.
우선 실행할 shellcode를 다시 한번 봐보자.
shellcode부터 shellcode+35까지 봐보자. 처음 세 줄은 %eax, %ebx, %ecx를 0x00000000으로 초기화 하는 과정이다. cltd는 %eax register에 대하여 Convert signed Long To signed Double Long 하라는 뜻이다. 즉, signed long 형의 % eax register를 %edx:%eax로 바꾸라는 것이다. 그리고 0xa4의 값을 %al register에 복사한다. %al register는 아래의 표를 참고하면 이해할 수 있을 것이다.
%al register는 %eax register의 low 8 bit을 의미한다. %cl, %dl, 등등 다른 register들도 위의 표를 보고 참고하면 이해하기 쉬울 것이다.
자 아무튼 %al에 164(0xa4)를 넣고 int 0x80을 한다. 이 부분이 중요하다. 이 부분은 system call을 하는 부분이다. 여기서 164의 의미를 알아보자.
(location : /arch/x86/kernel/syscall_table_32.S)
커널에 선언되어있는 system call table을 살펴보면 164가 sys_setresuid16이라는 함수임을 알 수 있다. 이 부분이 중요한 이유는 system call을 할 때의 parameter passing은 일반적인 procedure call의 parameter passing과는 다르기 때문이다. system call은 application stack에 직접 접근할 수 없으므로 register에 값을 저장하여 그 값을 parameter로 사용한다. 우선 system call을 호출할 때 가장 중요한 system call 번호는 무조건 %eax에 저장한다. 따라서 mov $0xa4, %al이라는 명령어를 수행한 것이다. 그리고 system call에 대한 parameter들은 차례대로 아래와 같이 전달된다.
(location : arch/x86/ia32/ia32entry.S)
위의 주석은 실제 리눅스 커널에 쓰여있는 주석이다. 이 주석을 보면 각 register에 있는 값을 system call에 대한 parameter로 사용하겠다는 것은 이해할 수 있다. 그런데 몇 개의 parameter를 사용하는지는 어떻게 알 수 있을까? /include/linux/syscalls.h에 보면 각 system call에 대한 선언이 되어있다. 여기서 각 system call들이 몇 개의 parameter를 가지고 있는지 확인할 수 있다.
(location : include/linux/syscalls.h)
우리가 사용할 sys_setresuid는 syscalls.h에 위와 같이 세 개의 parameter를 사용하기로 정의가 되어있다. 따라서 사용할 사람은 %ebx부터 차례대로 %ecx, %edx에 parameter로 사용할 값을 넣고 int 0x80으로 인터럽트를 수행시키면 된다. 지금까지 설명한 과정이 아래의 코드에서 모두 수행된다.
xor %eax, %eax
xor %ebx, %ebx
xor %ecx, %ecx
cltd
mov $0xa4, %al
int $0x80
그 다음 과정은 0xb를 push한 후, 0xb를 pop하면서 %eax에 저장하는 것이다. 여기에서 궁금한 점은 왜 아래와 같이 한 명령어로 끝내지 않고
mov $0xb, %al
두 번에 걸쳐 push & pop을 하는지 궁금하다. 그렇지만 우선 계속 넘어가보자. 다음 수행할 코드는 push와 mov이다.
push %ecx
push $0x68732f2f
push $0x6e69622f
mov $esp, $ebx
위 명령어의 결과를 실제 스택으로 보면 아래와 같은 모습의 스택이 될 것이다.
push를 이용해서 값을 스택에 쌓은 후 %esp의 값을 %ebx에 복사한다. 왜 %ebx에 복사할까? 후에 system call interrupt를 통해 system call을 수행하기 위함이다. 그 다음에 수행할 명령어는 아래와 같다.
mov %esp, %edx
push %ebx
mov %esp, %ecx
이 상태에서 int 0x80을 수행하여 system call을 호출한다. 아래의 두 명령어를 통해서 %eax는 11이 된다.
push $0xb
pop %eax
Kernel에 선언되어있는 데로 system call table을 찾아보면 system call number 11은 ptregs_execve라는 system call과 mapping되어 있다. ptregs_execve는 프로그램을 수행시키는 함수이다. 이 과정에서 0xbffff480에 있는 /bin//ssh 문자열이 사용되고 해당 프로그램이 실행되는 것으로 추정된다.
그렇다면 실제 커널에서 이 함수가 어떻게 선언되어있는지 확인해보자.
위와 같이 선언되어있다. 즉 이 함수는 X개의 파라미터를 사용하기 때문에 이 함수를 call하기 위해서는 %ebx, %ecx, %edx 세 개의 레지스터에 값을 넣으느 후 execve system call을 의미하는 숫자 11을 %eax에 넣은 후 int 0x80을 하면 된다.
'Linux Kernel' 카테고리의 다른 글
[Kernel] printf 함수 (3) | 2013.11.23 |
---|---|
시작하며 (0) | 2013.08.30 |