프로세스에서 TracerPID를 숨기는 방법은 무엇입니까?

프로세스에서 TracerPID를 숨기는 방법은 무엇입니까?

내 생각엔Linux의 SQL Server가 확인 /proc/self/status하고 TracerPID그렇지 않은 경우 중지합니다.0. 나는 그것을 테스트하고 싶다. 재미삼아, 이건 strace야.

... lots of stuff
openat(AT_FDCWD, "/proc/self/status", O_RDONLY) = 5
fstat(5, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(5, "Name:\tsqlservr\nUmask:\t0022\nState"..., 1024) = 1024
close(5)                                = 0
rt_sigprocmask(SIG_UNBLOCK, [ABRT], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], [], 8) = 0
getpid()                                = 28046
gettid()                                = 28046
tgkill(28046, 28046, SIGABRT)           = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
--- SIGABRT {si_signo=SIGABRT, si_code=SI_TKILL, si_pid=28046, si_uid=999} ---
gettid()                                = 28046
write(2, "Dump collecting thread [28046] h"..., 59Dump collecting thread [28046] hit exception [6]. Exiting.
) = 59
exit_group(-1)                          = ?

ltrace더 나쁜 것은, 신에게 감사하게도 그들이 이것을 strstr보이도록 사용하고 있다는 것입니다.진짜어쩌면 내 이론이 맞을 수도 있다.

strstr("PPid:\t28515\n", "TracerPid:")                                                                  = nil
__getdelim(0x7ffc0b7d2330, 0x7ffc0b7d2328, 10, 0x7f12f5811980)                                          = 17
strstr("TracerPid:\t28515\n", "TracerPid:")                                                             = "TracerPid:\t28515\n"
strtol(0x7f12f581840b, 0x7ffc0b7d2320, 10, 0)                                                           = 0x6f63
free(0x7f12f5818400)                                                                                    = <void>
fclose(0x7f12f5811980)                                                                                  = 0
abort( <no return ...>
--- SIGABRT (Aborted) ---
syscall(186, 6, 0, 0)                                                                                   = 0x6f64
fprintf(0x7f12f6ec4640, "Dump collecting thread [%d] hit "..., 28516, 6Dump collecting thread [28516] hit exception [6]. Exiting.
)                                = 59
fflush(0x7f12f6ec4640)                                                                                  = 0
exit(-1 <unfinished ...>

를 사용하여 확인하는 파일의 마지막 줄 은 strstr앞에 있는 abort()줄이지 만 TracerPid:my /proc/self/status뒤에는 줄이 많이 있습니다 .

/proc/self/status우선적으로 신고 하고 싶습니다.

...stuff...
TracerPid:  0
...stuff...

이 과정을 위해. 이것이 가능하지 않다면 0모든 프로세스 에 대해 보고하고 싶습니다 .

TracerPIDfor의 값을 변경 /proc/self/status한 다음 exec지정된 매개변수를 변경하여 액세스할 수 없게 만드는 래퍼를 만드는 것이 가능합니까 TracerPID?

답변1

내가 찾은 유일한 방법은 다음과 같습니다.커널을 패치하다. 이 것을 해킹하는 데에도 사용될 수 있다고 생각하지만 LD_PRELOAD나중에 확인해 보겠습니다.

답변2

실제로 동일한 방식으로 보호되는 다른 프로그램을 디버그하려는 경우 커널 패치가 더 흥미로운 솔루션이 될 수 있습니다. 예를 들어, gdb동일한 트릭을 사용하여 디버깅 중인지 감지합니다.

그러나 귀하의 질문에 따라 TracerPID표시된 PID가 0과 다를 때 mssql 서버 동작을 수정하는 방법을 조사한 결과 더 깨끗한 솔루션을 찾았습니다.

나는 Hopper를 사용하여 MS SQL 서버 바이너리를 분해/역컴파일했고 sqlservr디버깅을 방지하기 위해 TracerPID를 확인하는 문제가 있는 서브루틴을 발견했습니다.

Hopper 디컴파일 출력에서 ​​문제의 함수는 다음과 같습니다.

int sub_2d6d0() {
    r14 = fopen(0xa9b4e, 0xb6444);
    rbx = 0x0;
    if (r14 == 0x0) goto loc_2d791;

loc_2d702:
    var_30 = 0x0;
    var_38 = 0x0;
    r15 = &var_30;
    r12 = &var_38;
    goto loc_2d730;

loc_2d730:
    rbx = 0x0;
    if (__getdelim(r15, r12, 0xa, r14) < 0x0) goto loc_2d77b;

loc_2d74a:
    rax = strstr(var_30, "TracerPid:");
    if (rax == 0x0) goto loc_2d730;

loc_2d75b:
    var_40 = 0x0;
    rbx = strtol(rax + 0xb, &var_40, 0xa);
    goto loc_2d77b;

loc_2d77b:
    rdi = var_30;
    if (rdi != 0x0) {
            free(rdi);
    }
    fclose(r14);
    goto loc_2d791;

loc_2d791:
    rax = rbx;
    return rax;
}

(심하게 편집된) 인간의 해석에서 이 함수에 대한 C 의사 코드는 다음과 같습니다.

int  IsMonitorProcess() {                          ; sub_2d6d0
    FILE * f = fopen("/proc/self/", "r" );
    int pid = 0;                                   ; rbx
    char *s = NULL;

    if (f != NULL ) 
    {             
        while (__getdelim(s, 0, 0xa, f) >= 0x0) 
        {
            char *temp;

            temp = strstr(s, "TracerPid:");
            pid = 0;
            if (temp != NULL)
                pid = strtol(temp + 0xb, NULL, 10);
        }

        if (s != NULL) {
               free(s);
        }

        fclose(f);
    }

    return pid;
}

strstr문자열 "TracerPid:"가 발견 되면 temp/럭스0(NULL)과 다릅니다.

그런 다음 strtol호출은 문자열의 나머지 부분을 (긴) 정수로 변환합니다.RBX반환된 값 strtol(실제로는 디스어셈블리 목록에 있음)럭스).

따라서 언급한 대로 커널을 패치하는 것 외에도 추적 감지를 비활성화하는 두 가지 솔루션이 있습니다.

  • 더 깔끔한 솔루션: 호출 시 LD_PRELOAD를 사용하여 로드되는 라이브러리를 작성합니다 sqlservr.

가장 간단한 해결책은 "TracerPid:"를 발견하면 다음 호출이 0을 반환 하도록 플래그를 활성화하는 코드를 가로채서 작성하는 것입니다 strstr.strtolstrstrstrtol

(바이너리를 두 번 확인했는데 실제로 동적으로 로드 strstr되었습니다 strtol.)

또 다른 옵션은 가로채기 fopen이지만 코드가 좀 더 복잡할 수 있습니다.

  • 바이너리 가 패치되었으며 다음과 같이 로 sqlservr대체됩니다 .rax = rbxrax = 0RBXstrtol"TracerPid:" 이후 값의 긴 변환을 위해 /String을 저장합니다 .

이 솔루션의 단점은 새 버전이 나올 때마다 다시 패치해야 한다는 것입니다.

실제로 회의 자체 내에서는RBX레지스터 로딩은 호출 직후에 발생합니다 strtol. 바이너리는 mov rbx, rax에서 또는 까지 중 더 짧은 xor rbx,rbx것으로 패치 될 수 있습니다 mov rbx,0.

000000000002d75b         mov        qword [rbp+var_40], 0x0
000000000002d763         add        rax, 0xb
000000000002d767         lea        rsi, qword [rbp+var_40]                     ; argument "__endptr" for method j_strtol
000000000002d76b         mov        edx, 0xa                                    ; argument "__base" for method j_strtol
000000000002d770         mov        rdi, rax                                    ; argument "__nptr" for method j_strtol
000000000002d773         call       j_strtol                                    ; strtol
000000000002d778         mov        rbx, rax   <----------- xor rbx,rbx

                     loc_2d77b:
000000000002d77b         mov        rdi, qword [rbp+var_30]                     ; CODE XREF=sub_2d6d0+120
000000000002d77f         test       rdi, rdi
000000000002d782         je         loc_2d789

000000000002d784         call       j_free                                      ; free

                     loc_2d789:
000000000002d789         mov        rdi, r14                                    ; argument "__stream" for method j_fclose, CODE XREF=sub_2d6d0+178
000000000002d78c         call       j_fclose 

LD_PRELOAD분명히 커널이나 바이너리 자체를 패치하는 것보다 이 솔루션을 사용하는 것이 좋습니다 .

이는 보다 깔끔한 솔루션이며 MSSQL 또는 커널 업그레이드를 수행할 때마다 이 작업을 다시 수행할 필요가 없습니다.

참고: 저는 mssql-server_14.0.3008.27-1_amd64.debMac에서 다운로드하여 압축을 풀었습니다.

LD_PRELOAD 라이브러리의 소스 코드에 대한 일반적인 아이디어는 다음과 같습니다.

int flag = 0;

char * strstr (const char *s1, const char *s2)
{
    if(!strcmp(s2, "TracerPid:"))
    {
        flag = 1;
    }
    .... rest of usual code
}

long strtol(const char *nptr, char **endptr, register int base)
{
    if(flag)
    {
        flag = 0;
        return 0;
    }
    .... rest of usual code
}

fopen요점만 언급한 내용 "/proc/self/": 이것은 버그가 아닙니다.

응, 그렇게 fopen하면 기분이 이상해 "/proc/self/". 아마도 그 뒤의 몇 가지 정수 변수는 런타임에 문자열의 나머지 부분을 완성하는 데 사용되는 공간을 채우는 데 사용됩니다. 이는 바이너리를 보려고 하는 사람을 속이기 위한 값싼 속임수입니다.

관련 정보