IT Share you

현재 프로세스가 GDB에서 실행 중인지 감지하는 방법은 무엇입니까?

shareyou 2021. 1. 7. 19:55
반응형

현재 프로세스가 GDB에서 실행 중인지 감지하는 방법은 무엇입니까?


표준 방법은 다음과 같습니다.

if (ptrace(PTRACE_TRACEME, 0, NULL, 0) == -1)
  printf("traced!\n");

이 경우 ptrace는 현재 프로세스가 추적되면 오류를 반환합니다 (예 : gdb로 실행하거나 연결).

그러나 여기에는 심각한 문제가 있습니다. 호출이 성공적으로 반환되면 gdb가 나중에 연결되지 않을 수 있습니다. 안티 디버그 기능을 구현하지 않기 때문에 문제가됩니다. 내 목적은 조건이 충족되고 (즉, 어설 션이 실패 함) gdb가 실행 중일 때 'int 3'을 내보내는 것입니다 (그렇지 않으면 응용 프로그램을 중지하는 SIGTRAP을 얻습니다).

SIGTRAP을 비활성화하고 매번 'int 3'을 내보내는 것은 좋은 해결책이 아닙니다. 테스트중인 응용 프로그램이 다른 용도로 SIGTRAP을 사용할 수 있기 때문입니다 (이 경우 여전히 문제가되지 않으므로 문제가되지 않습니다. 일의 원리 :))

감사


이전에는 주석으로 : PTRACE_ATTACH부모 에게 시도하는 자식을 포크 (필요한 경우 분리)하고 결과를 다시 전달할 수 있습니다. 그래도 약간 우아하지 않은 것 같습니다.

언급했듯이 이것은 비용이 많이 듭니다. 어설 션이 불규칙적으로 실패해도 나쁘지 않다고 생각합니다. 아마도이 작업을 수행하기 위해 하나의 장기 실행 자식을 유지하는 것이 좋습니다. 부모와 자식간에 두 개의 파이프를 공유하고 자식이 바이트를 읽을 때 검사를 수행 한 다음 상태와 함께 바이트를 다시 보냅니다.


Windows에는 프로세스가 디버깅 중인지 확인하는 API IsDebuggerPresent가 있습니다. 리눅스에서는 이것을 다른 방법으로 확인할 수 있습니다 (그다지 효율적이지 않음).

" TracerPid "속성에 대해 " / proc / self / status "를 확인하십시오 .

예제 코드 :

#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>

bool debuggerIsAttached()
{
    char buf[4096];

    const int status_fd = ::open("/proc/self/status", O_RDONLY);
    if (status_fd == -1)
        return false;

    const ssize_t num_read = ::read(status_fd, buf, sizeof(buf) - 1);
    if (num_read <= 0)
        return false;

    buf[num_read] = '\0';
    constexpr char tracerPidString[] = "TracerPid:";
    const auto tracer_pid_ptr = ::strstr(buf, tracerPidString);
    if (!tracer_pid_ptr)
        return false;

    for (const char* characterPtr = tracer_pid_ptr + sizeof(tracerPidString) - 1; characterPtr <= buf + num_read; ++characterPtr)
    {
        if (::isspace(*characterPtr))
            continue;
        else
            return ::isdigit(*characterPtr) != 0 && *characterPtr != '0';
    }

    return false;
}

내가 사용한 코드는 다음과 같습니다.

int
gdb_check()
{
  int pid = fork();
  int status;
  int res;

  if (pid == -1)
    {
      perror("fork");
      return -1;
    }

  if (pid == 0)
    {
      int ppid = getppid();

      /* Child */
      if (ptrace(PTRACE_ATTACH, ppid, NULL, NULL) == 0)
        {
          /* Wait for the parent to stop and continue it */
          waitpid(ppid, NULL, 0);
          ptrace(PTRACE_CONT, NULL, NULL);

          /* Detach */
          ptrace(PTRACE_DETACH, getppid(), NULL, NULL);

          /* We were the tracers, so gdb is not present */
          res = 0;
        }
      else
        {
          /* Trace failed so gdb is present */
          res = 1;
        }
      exit(res);
    }
  else
    {
      waitpid(pid, &status, 0);
      res = WEXITSTATUS(status);
    }
  return res;
}

몇 가지:

  • ptrace (PTRACE_ATTACH, ...)가 성공하면 추적 된 프로세스가 중지되고 계속되어야합니다.
  • 이것은 gdb가 나중에 연결될 때도 작동합니다.
  • 단점은 자주 사용하면 심각한 속도 저하가 발생한다는 것입니다.
  • 또한이 솔루션은 Linux에서만 작동하는 것으로 확인되었습니다. 언급했듯이 BSD에서는 작동하지 않습니다.

어쨌든 답변 주셔서 감사합니다.


나는 비슷한 필요가 있었고 다음과 같은 대안을 생각해 냈습니다.

static int _debugger_present = -1;
static void _sigtrap_handler(int signum)
{
    _debugger_present = 0;
    signal(SIGTRAP, SIG_DFL);
}

void debug_break(void)
{
    if (-1 == _debugger_present) {
        _debugger_present = 1;
        signal(SIGTRAP, _sigtrap_handler);
        raise(SIGTRAP);
    }
}

호출되면 debug_break 함수는 디버거가 연결된 경우에만 중단됩니다.

x86에서 실행 중이고 호출자 ( raise가 아닌)에서 중단되는 중단 점을 원하는 경우 다음 헤더를 포함하고 debug_break 매크로를 사용합니다.

#ifndef BREAK_H
#define BREAK_H

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int _debugger_present = -1;
static void _sigtrap_handler(int signum)
{
    _debugger_present = 0;
    signal(SIGTRAP, SIG_DFL);
}

#define debug_break()                       \
do {                                        \
    if (-1 == _debugger_present) {          \
        _debugger_present = 1;              \
        signal(SIGTRAP, _sigtrap_handler);  \
        __asm__("int3");                    \
    }                                       \
} while(0)

#endif

나는 파일 기술자 "해킹"의 수정 된 버전 것을 발견 Silviocesare 설명xorl에 의해 블로그가 나를 위해 잘 작동.

이것은 내가 사용하는 수정 된 코드입니다.

#include <stdio.h>
#include <unistd.h>

// gdb apparently opens FD(s) 3,4,5 (whereas a typical prog uses only stdin=0, stdout=1,stderr=2)
int detect_gdb(void)
{
    int rc = 0;
    FILE *fd = fopen("/tmp", "r");

    if (fileno(fd) > 5)
    {
        rc = 1;
    }

    fclose(fd);
    return rc;
}

gdb디버깅 목적으로 앱이 실행 중인지 여부 만 알고 싶은 경우 Linux에서 가장 간단한 솔루션은를 사용 readlink("/proc/<ppid>/exe")하고 "gdb".


이것은 terminus의 대답과 비슷하지만 통신을 위해 파이프를 사용합니다.

#include <unistd.h>
#include <stdint.h>
#include <sys/ptrace.h>
#include <sys/wait.h>

#if !defined(PTRACE_ATTACH) && defined(PT_ATTACH)
#  define PTRACE_ATTACH PT_ATTACH
#endif
#if !defined(PTRACE_DETACH) && defined(PT_DETACH)
#  define PTRACE_DETACH PT_DETACH
#endif

#ifdef __linux__
#  define _PTRACE(_x, _y) ptrace(_x, _y, NULL, NULL)
#else
#  define _PTRACE(_x, _y) ptrace(_x, _y, NULL, 0)
#endif

/** Determine if we're running under a debugger by attempting to attach using pattach
 *
 * @return 0 if we're not, 1 if we are, -1 if we can't tell.
 */
static int debugger_attached(void)
{
    int pid;

    int from_child[2] = {-1, -1};

    if (pipe(from_child) < 0) {
        fprintf(stderr, "Debugger check failed: Error opening internal pipe: %s", syserror(errno));
        return -1;
    }

    pid = fork();
    if (pid == -1) {
        fprintf(stderr, "Debugger check failed: Error forking: %s", syserror(errno));
        return -1;
    }

    /* Child */
    if (pid == 0) {
        uint8_t ret = 0;
        int ppid = getppid();

        /* Close parent's side */
        close(from_child[0]);

        if (_PTRACE(PTRACE_ATTACH, ppid) == 0) {
            /* Wait for the parent to stop */
            waitpid(ppid, NULL, 0);

            /* Tell the parent what happened */
            write(from_child[1], &ret, sizeof(ret));

            /* Detach */
            _PTRACE(PTRACE_DETACH, ppid);
            exit(0);
        }

        ret = 1;
        /* Tell the parent what happened */
        write(from_child[1], &ret, sizeof(ret));

        exit(0);
    /* Parent */
    } else {
        uint8_t ret = -1;

        /*
         *  The child writes a 1 if pattach failed else 0.
         *
         *  This read may be interrupted by pattach,
         *  which is why we need the loop.
         */
        while ((read(from_child[0], &ret, sizeof(ret)) < 0) && (errno == EINTR));

        /* Ret not updated */
        if (ret < 0) {
            fprintf(stderr, "Debugger check failed: Error getting status from child: %s", syserror(errno));
        }

        /* Close the pipes here, to avoid races with pattach (if we did it above) */
        close(from_child[1]);
        close(from_child[0]);

        /* Collect the status of the child */
        waitpid(pid, NULL, 0);

        return ret;
    }
}

OSX에서 원본 코드를 시도해 보면 waitpid (부모)가 항상 EINTR (시스템 호출 중단)과 함께 -1을 반환한다는 것을 알았습니다. 이것은 pattach로 인해 부모에게 연결되고 통화가 중단되었습니다.

It wasn't clear whether it was safe to just call waitpid again (that seemed like it might behave incorrectly in some situations) so I just used a pipe to do the communication instead. It's a bit of extra code, but will probably work reliably across more platforms.

This code has been tested on OSX 10.9.3, Ubuntu 14.04 (3.13.0-24-generic) and FreeBSD 10.0.

For linux, which implements process capabilities, this method will only work if the process has the CAP_SYS_PTRACE capability, which is typically set when the process is run as root.

Other utilities (gdb and lldb) also have this capability set as part of their filesystem metadata.

You can detect whether the process has effective CAP_SYS_PTRACE by linking against -lcap,

#include <sys/capability.h>

cap_flag_value_t value;
cap_t current;

/*
 *  If we're running under linux, we first need to check if we have
 *  permission to to ptrace. We do that using the capabilities
 *  functions.
 */
current = cap_get_proc();
if (!current) {
    fprintf(stderr, "Failed getting process capabilities: %s\n", syserror(errno));
    return -1;
}

if (cap_get_flag(current, CAP_SYS_PTRACE, CAP_PERMITTED, &value) < 0) {
    fprintf(stderr, "Failed getting permitted ptrace capability state: %s\n", syserror(errno));
    cap_free(current);
    return -1;
}

if ((value == CAP_SET) && (cap_get_flag(current, CAP_SYS_PTRACE, CAP_EFFECTIVE, &value) < 0)) {
    fprintf(stderr, "Failed getting effective ptrace capability state: %s\n", syserror(errno));
    cap_free(current);
    return -1;
}

ReferenceURL : https://stackoverflow.com/questions/3596781/how-to-detect-if-the-current-process-is-being-run-by-gdb

반응형