Na verdade, o patch do kernel pode ser uma solução mais interessante se você quiser depurar outros programas protegidos da mesma maneira. Por exemplo, gdb
usa o mesmo truque para detectar se está sendo depurado.
No entanto, com base na sua pergunta, eu investiguei como modificar o comportamento do servidor mssql quando TracerPID
está mostrando um PID diferente de 0; e acredito que descobri uma solução mais limpa.
Eu usei o Hopper para desmontar / descompilar o arquivo binário do servidor MS SQL sqlservr
e localizei a sub-rotina problemática que verifica o TracerPID para evitar a depuração.
Na saída do Hopper, descompilada, a função problemática é:
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;
}
Na interpretação humana (muito editada), o pseudo-código C da função é:
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;
}
Como pode ser visto, se strstr
encontrar a string "TracerPid:", temp / rax será diferente de 0 (NULL).
O strtol
será então invocado para converter o restante da string em um inteiro (longo). O rbx foi então carregado com o valor retornado por strtol
(que, na lista de desmontagem, está em rax ).
Portanto, há mais duas soluções para desabilitar a detecção de rastreamento, além de corrigir o kernel como você mencionou:
- A solução de limpeza: você escreve uma biblioteca para ser carregada com LD_PRELOAD ao invocar
sqlservr
.
O que eu aconselho como a solução mais simples é interceptar strstr
e strtol
, no qual você escreve código em strstr
que quando ele encontrar "TracerPid:", ativará um sinalizador que fará o próximo strtol
retorno de invocação 0.
(Eu já verifiquei o binário e, de fato, strstr
e strtol
são carregados dinamicamente)
Outra opção é interceptar fopen
, mas o código pode ser um pouco mais complicado.
- o binário
sqlservr
está corrigido, você substituirax = rbx
pararax = 0
, pois rbx contém a conversãostrtol
/ string para número inteiro longo após "TracerPid:".
A desvantagem desta solução é que cada nova versão terá que ser corrigida novamente.
Na verdade, na própria montagem, o carregamento do registrador rbx vem logo após chamar strtol
. O binário pode ser corrigido de mov rbx, rax
para xor rbx,rbx
ou mov rbx,0
, qual deles é menor.
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
Obviamente, aconselho usar a solução LD_PRELOAD
em vez de tentar corrigir o kernel ou o próprio binário.
É uma solução muito mais limpa, e não depende de ter que ser feita novamente toda vez que você tiver um MSSQL ou uma atualização do kernel.
Nota: eu baixei o mssql-server_14.0.3008.27-1_amd64.deb
e o descomprimi em um Mac.
Quanto ao código-fonte da biblioteca LD_PRELOAD, a ideia geral é aproximadamente:
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
}
Comentando sobre fopen
apontando apenas para "/proc/self/"
: não é um erro.
Sim, acho estranho o fopen
ser feito apenas para "/proc/self/"
. Muito provavelmente, o par de variáveis inteiras depois que elas estão lá apenas para preencher um espaço, que será usado para completar o resto da string em tempo de execução, e é um truque barato para enganar qualquer um que esteja tentando ver o binário. / p>