如何从进程中隐藏 TracerPID?

如何从进程中隐藏 TracerPID?

我猜测Linux 上的 SQL Server 正在检查/proc/self/statusTracerPID如果没有则停止运行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

我建议最简单的解决方案是拦截strstrand strtol,在其中编写代码,strstr当它找到“TracerPid:”时,它将激活一个标志,使下一次strtol调用返回 0。

(我已经仔细检查了二进制文件,并且确实strstrstrtol动态加载的)

另一种选择是拦截fopen,但代码可能会更复杂一些。

  • 二进制文件sqlservr已修补,您将 替换rax = rbxrax = 0, 作为RBX保存strtol“TracerPid:”之后的值的/字符串到长整数的转换。

这种解决方案的缺点是每个新版本都必须重新打补丁。

实际上在大会本身中,RBX调用后立即进行寄存器加载strtol。二进制文件可以从mov rbx, raxxor 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/"。最有可能的是,它后面的几个整数变量只是用来填充一个空格,它将在运行时用来完成字符串的其余部分,这是一个欺骗任何试图查看二进制文件的人的廉价技巧。

相关内容