web-dev-qa-db-ja.com

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 ...>
_

abort()の前に(strstrで)チェックするファイルの最後の行は_TracerPid:_の行ですが、私の_/proc/self/status_ではその後に多くの行があります。

優先順に_/proc/self/status_に報告してもらいたい

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

このプロセスのために。それが達成できない場合は、すべてのプロセスについて_0_を報告してほしい。

_/proc/self/status_のTracerPIDの値を変更し、次にexecに与えられた引数を変更して、TracerPIDにアクセスできないラッパーを作成することは可能ですか。 ?

3
Evan Carroll

カーネルにパッチを適用する によってこれを行うことがわかった唯一の方法。 LD_PRELOADでこれをハックすることも可能かもしれないと思いますが、後でチェックします。

1
Evan Carroll

実際、同じ方法で保護されている他のプログラムをデバッグする場合は、カーネルパッチの方が興味深いソリューションになる可能性があります。たとえば、gdbは同じトリックを使用して、デバッグされているかどうかを検出します。

ただし、あなたの質問に基づいて、TracerPIDが0とは異なるPIDを示している場合に、mssqlサーバーの動作を変更する方法を調査しました。そして私はよりクリーンな解決策を見つけたと信じています。

MSSQLサーバーのバイナリファイルsqlservrを逆アセンブル/逆コンパイルするためにHopperを使用し、デバッグを防止するためにTracerPIDをチェックする問題のあるサブルーチンを見つけました。

逆コンパイルされたホッパー出力では、問題のある関数は次のとおりです。

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/raxは0(NULL)とは異なります。

次に、strtolが呼び出され、残りの文字列が(長い)整数に変換されます。 rbxは、strtolによって返された値でロードされます(実際には逆アセンブリリストにあり、raxにあります)。

したがって、前述のようにカーネルにパッチを適用する以外に、トレース検出を無効にするための2つの解決策があります。

  • よりクリーンな解決策:sqlservrを呼び出すときに、LD_PRELOADでロードされるライブラリを作成します。

最も簡単な解決策として私がアドバイスするのは、strstrstrtolをインターセプトすることです。ここでは、strstrにコードを記述し、「TracerPid:」が見つかると、次のフラグをアクティブにします。次のstrtol呼び出しは0を返します。

(私はすでにバイナリをダブルチェックしました、そして実際、strstrstrtolは動的にロードされます)

別のオプションはfopenをインターセプトすることですが、コードはもう少し複雑かもしれません。

  • sqlservrバイナリにパッチが適用されている場合は、rax = rbxrax = 0に置き換えます。rbxstrtol /文字列から長整数への変換を保持するためです。 「TracerPid:」の後の値。

このソリューションの欠点は、新しいバージョンごとに再度パッチを適用する必要があることです。

実際、アセンブリ自体では、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.debをダウンロードし、Macで解凍しました。

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/"だけに行われるのは奇妙だと思います。おそらく、スペースを埋めるためにそこにある後のいくつかの整数変数は、実行時に文字列の残りを完了するために使用されます。これは、バイナリを見ようとしている人を欺くための安価なトリックです。

0
Rui F Ribeiro