私は推測しています 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
にアクセスできないラッパーを作成することは可能ですか。 ?
カーネルにパッチを適用する によってこれを行うことがわかった唯一の方法。 LD_PRELOAD
でこれをハックすることも可能かもしれないと思いますが、後でチェックします。
実際、同じ方法で保護されている他のプログラムをデバッグする場合は、カーネルパッチの方が興味深いソリューションになる可能性があります。たとえば、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でロードされるライブラリを作成します。最も簡単な解決策として私がアドバイスするのは、strstr
とstrtol
をインターセプトすることです。ここでは、strstr
にコードを記述し、「TracerPid:」が見つかると、次のフラグをアクティブにします。次のstrtol
呼び出しは0を返します。
(私はすでにバイナリをダブルチェックしました、そして実際、strstr
とstrtol
は動的にロードされます)
別のオプションはfopen
をインターセプトすることですが、コードはもう少し複雑かもしれません。
sqlservr
バイナリにパッチが適用されている場合は、rax = rbx
をrax = 0
に置き換えます。rbxはstrtol
/文字列から長整数への変換を保持するためです。 「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/"
だけに行われるのは奇妙だと思います。おそらく、スペースを埋めるためにそこにある後のいくつかの整数変数は、実行時に文字列の残りを完了するために使用されます。これは、バイナリを見ようとしている人を欺くための安価なトリックです。