본문 바로가기

Programming/Linux_Kernel

ptrace 번역

윈도우에 hooking 이 있다면 Linux 에는 ptrace 가 있다.

(이게 무슨말인지 아는사람? 둘다 무척 잼있는(?) 기술이다.)

이것을 번역해 놓으신 분이 있으셔서 고맙게도 자료를 떠 왔다.

원문 : http://blog.naver.com/int_com?Redirect=Log&logNo=80055576334




정말 날림으로 번역했다 ㅠㅠ ;;;

그냥 원문 읽자니 잘 집중도 안되서 번역하면서 읽으면 잘 될거 같아서 ㅎㅎ

 

원문 주소 -> http://linuxgazette.net/issue81/sandeep.html

 

------------------------------------------------------------

 

1. introduction

  ptrace()는 한 프로세스가 다른 프로세스의 실행을 컨터롤할 수 있는 시스템 콜이다. 이것은

또한 프로세스가 다른 프로세스의 코어 이미지를 바꿀수 있게도 해 준다. 추적받는 프로세스는

일반적으로 시그널을 받을 때 까지 행동한다. 프로세스가 정지상태에 들어가게 되면 wait()

호출에 의해서 추적 하는 프로세스에게 알린다.

추적받는 프로세스는 또한 실행중에 몇가지 정의된 이벤트에 반응 하기 위해서 정지상태에

들어가기도 한다. 이 경우는 오직 만약 추적하는 프로세스가 이벤트 플래그를 추적받는

프로세스의 context 에 설정했을 경우에만 일어난다. 추적 후에 추적하는 프로세스는 추적받는

프로세스를 죽이거나 실행 을 계속할 것이다.

NOTE. ptrace()는 매우 하드웨어 아키텍쳐에 의존적이다. ptrace를 사용하는 어플리케이션은  이기종의 아키텍쳐로 포팅하거나 구현하기가 쉽지 않다.

2. More Details

  ptrace()의 프로토타입은 다음과 같다.

--------------------------------------------------------
#include <sys/ptrace.h>
long int ptrace(enum __ptrace_request request, pid_t pid,
void *addr, void *data)
--------------------------------------------------------

4개의 아규먼트가 있는데 request는 무엇을 할 것인지 결정한다. pid는 추적하고자 하는

프로세스의 ID이다. Addr은 레지스터와 프로세스에 관한 다른 정보를 가지고 있는 자식프로세스의

USER 공간에 있는 변위이다.

부모프로세스는 자식 프로세스를 fork하고 PTRACE_TRACEME의 request 와 함께 ptrace를

호출해서 추적할 수 있다. 부모프로세스는 또한 존재하는 프로세스를 PTRACE_ATTACH를

사용해서 추적할 수 있다. 다른 request의 값은 아래에 열거하였다.

2.1 How does ptrace() work.

  어느때든지 ptrace가 호출되면 가장 먼저 커널을 잠근다(lock the kernel). 리턴하기 전에 다시

커널을 풀어준다(unlocks the kernel). request의 다른값들이 어떤일들을 하는지 알아보자

PTRACE_TRACEME

이 프로세스는 이 프로세스의 부모에 의해 추적되어 진다는것을 가리킨다. 이 프로세스에 전달된

신호(SIGKILL을 제외)는 프로세스 추적을 멈추고 부모 프로세스가 wait을 인식하도록 야기한다.

또한, 이 프로세스에 의해 exec되는 모든 연속적인 콜들은 새로운 프로그램이 실행하기전에

제어를 얻을 기회를 부모 프로세스에게 주기위해 SIGTRAP을 야기한다. 프로세스는 부모

프로세스가 추적을 기대하지 않는다면 이런 요구(request)를 만들어서는 안된다.


위의 request는 자식 프로세스에 의해서만 사용된다. 나머지는 부모 에 의해서만 사용된다. 다음

request들 중에 pid는 작동하는 자식 프로세스를 가리킨다. PTRACE_KILL 외 다른 request들을

위해서 자식 프로세스는 멈추어져야만 한다.

PTRACE_PEEKTEXT, PTRACE_PEEKDATA

자식 프로세스 메모리의 addr 위치의 워드(word)를 읽고 ptrace콜의 결과로써 워드를 반환한다.

리눅스는 text와 data 주소 공간을 분리 하지 않는다. 그래서 두개의 request는 현재 같다.

(data 는 무시 된다.)

PTRACE_PEEKUSER

레지스터와 프로세스에 관한 다른 정보를 가지고 있는 자식 프로세스의 USER공간에 있는 변위

addr의 워드를 읽는다. word는 ptrace 시스템 콜의 결과로써 반환된다. 전형적으로 변위는

아키텍쳐에 의해 바뀐다 할 지라도 워드-정렬이어야 한다.

PTRACE_POKETEXT, PTRACE_POKEDATA

부모 프로세스 메모리에 있는 위치 data에서 자식 프로세스 메모리에 있는 위치 addr으로 word를

복사한다. 위처럼 두 request는 현재 같다

PTRACE_POKEUSER

부모 프로세스 메모리에 있는 위치 data에서 자식 프로세스의 addr USER영역으로 word를

복사한다. 위처럼 변위는 전형적으로 워드-정렬 이어야 한다. 커널의 무결성을 유지하기 위해,

USER 영역에 대한 수정은 허용되지 않는다,.

PTRACE_GETREGS, PTRACE_GETFPREGS

자식 프로세스의 범용 또는 부동 소수점 레지스터들을 각각 부모 프로세스의 data 위치로 복사한다.

이 데이터의 포맷에 관한 정보를 위해서는 <linux/user.h>를참조해라(data는 무시된다)

PTRACE_SETREGS, PTRACE_SETFPREGS

부모 프로세스의 data위치에서 자식 프로세스의 범용 또는 부동 소수점 레지스터들을 각각 복사한다.

PTRACE_POKEUSER처럼, 몇몇 범용 레지스터 수정은 허용되지 않는다.(addr은 무시된다.)

PTRACE_CONT

중지된 자식 프로세스를 다시 시작한다. 만일 data가 0도 SIGSTOP도 아니라면 이것은 자식에게

전달되어야 하는 신호로써 해석된다. 그렇지 않으면 어떤 신호도 전달되지 않는다. 예를 들어

부모 프로세스는 자식에게 보내진 신호가 전달될지 아닐지를 제어할 수 있다.
(data 는 무시된다)

PTRACE_SYSCALL, PTRACE_SINGLESTEP

PTRACE_CONT처럼 중지된 자식의 프로세스를 다시 시작한다. 그러나 자식을 다음 엔트리에서

중지하도록 재 배열하거나 시스템 콜에서 종료하거나 단일 명령어 실행 후에 종료한다.(보통으로,

자식은 또한 신호를 받는 즉시 종료한다)부모프로세스의 관점에서 자식 프로세스는 SIGTRAP을

받아 종료된것철머 보일 것이다. 예를 들어 PTRACE_SYSCALL 를 위한 생각은 처음 중지시

시스템 콜을 위한 인자들을 조사하고 그리고 다른 PTRACE_SYSCALL을 하고 두 번째 중지시에

시스템 콜의 반환값을 조사하는 것이다.(data는 무시된다)

PTRACE_ADDACH

pid로 지정된 프로세스에 부착시키고 현재 프로세스의 "child"를 추적 하도록 한다. 자식 프로세스의

행동은 마치 PTRACE_TRACEME가 하는것 처럼 이다. 실제로 현재 프로세스는 대부분의 목적을

위해 자식 프로세스의 부모가 된다.(즉 자식 이벤트의 통지를 받으며 자식의 부모처럼 ps(1)에

나타난다) 그러나 자식에 의한 getpid(2)는 여전히 원래 부모의 pid를 반환한다. 자식은 SIGSTOP을

보내지만 이 시스템 콜에 의해 중지될 필요는 없다. 지식이 중지되는 것을 기다리기 위해 wait을

사용해라. (addr 과 data는 무시된다.)

2.3 Return values of ptrace()

  ptrace()의 성공적인 리턴은 0이다. 에러가 발생하면 -1을 리턴하고 errno를 설정한다. 리턴 밸류가

성공적이면 PEEKDATA/PEEKTEXT는 아마도 -1이 되며 이것은 errno를 확인하기 좋다. 에러는

다음과 같다.

EPERM : 요청된 프로세스는 추적될 수 없다. 권한이 없다
ESRCH : 요청된 프로세스가 존재하지 않거나 이미 추적중이다.
EIO : request가 잘못되었거나 read/write가 잘못 된 메모리 로부터 혹은 잘못된 메모리로 설정되어

        있다.
EFAULT : read/write가 실제로 매핑되지 않은 곳으로 설정되어 있다.

EIO와 EFAULT는 실제로 매우 구별하기 힘들다. 이것들은 대부분 비슷한 에러들을 리턴한다


3. A small example

  만약 당신이 이 예제에서 잘 모르는 부분이 나온다고 해서 좌절하지 말아라. 나는 단지 위에서

열거한 것을 단순한 프로그램에서 보이기위함이다.

여기 첫 번째 예제가 있다. 부모프로세스는 자식 프로세스가 실행 하는데 수행하는 명령어의 갯수를

센다.

여기 현제 디렉터리의 엔트리를 출력하는 프로그램이 있다.

-------------------------------------------------------------
 #include <stdio.h>
#include <stdlib.h>
#inclu de <signal.h>
#include <syscall.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>


int main(void)
{
       long long counter = 0;  /*  machine instruction counter */
        int wait_val;           /*  child's return value        */
      int pid;                /*  child's process id          */

        puts("Please wait");

        switch (pid = fork()) {
        case -1:
                perror("fork");
                break;
        case 0: /*  child process starts        */
                ptrace(PTRACE_TRACEME, 0, 0, 0);
                /*
                 *  must be called in order to allow the
                 *  control over the child process
                 */
                execl("/bin/ls", "ls", NULL);
                /*
                 *  executes the program and causes
                 *  the child to stop and send a signal
                 *  to the parent, the parent can now
                 *  switch to PTRACE_SINGLESTEP  
                 */
                break;
                /*  child process ends  */
        default:/*  parent process starts       */
                wait(&wait_val);
                /*  
                 *   parent waits for child to stop at next
                 *   instruction (execl())
                 */
                while (wait_val == 1407 ) {
                        counter++;
                        if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0) != 0)
                                perror("ptrace");
                        /*
                         *   switch to singlestep tracing and
                         *   release child
                         *   if unable call error.
                         */
                        wait(&wait_val);
                        /*   wait for next instruction to complete  */
                }
                /*
                 * continue to stop, wait and release until
                 * the child is finished; wait_val != 1407
                 * Low=0177L and High=05 (SIGTRAP)
                 */
        }
        printf("Number of machine instructions : %lld\n", counter);
        return 0;
}
 -------------------------------------------------------------

좋아하는 에디터를 사용해서 위 프로그램을 작성하고 나서 아래와 같이 실행하자

 

cc file.c

 

a.out

 

실행을 하면 현재 디렉터리를 출력하는데 필요한 명령어 갯수를 볼 수 있다 . cd를 사용해서

다른 디렉터리로 가서 실행하면 다른 결과를 볼 수 있을 것이다.

나는 다음 장에서 ptrace의 여러 다양한 익스플로잇을 만들어 볼 것이다.

 

그럼 그때 까지 안뇽 ~~ 

 

'Programming > Linux_Kernel' 카테고리의 다른 글

diff를 이용한 kernel patch 만들기  (0) 2008.11.12
Linux Kernel 에 대한 문서들  (0) 2008.11.12
warning: function declaration isn't a prototype  (0) 2008.11.05
HOWTO do Linux kernel development  (0) 2008.10.28
sigaction()  (0) 2008.10.20