내 생각엔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
모든 프로세스 에 대해 보고하고 싶습니다 .
TracerPID
for의 값을 변경 /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
.strtol
strstr
strtol
(바이너리를 두 번 확인했는데 실제로 동적으로 로드 strstr
되었습니다 strtol
.)
또 다른 옵션은 가로채기 fopen
이지만 코드가 좀 더 복잡할 수 있습니다.
- 바이너리 가 패치되었으며 다음과 같이 로
sqlservr
대체됩니다 .rax = rbx
rax = 0
RBXstrtol
"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.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/"
. 아마도 그 뒤의 몇 가지 정수 변수는 런타임에 문자열의 나머지 부분을 완성하는 데 사용되는 공간을 채우는 데 사용됩니다. 이는 바이너리를 보려고 하는 사람을 속이기 위한 값싼 속임수입니다.