※ 질문/내용오류/공유할 내용이 있다면 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을 수행하기 위함이다. 그 다음에 수행할 명령어는 아래와 같다.

 

push     %ecx

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 11ptregs_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
Posted by 빛나유
,