Reorganized proc_PID to use path dependend file system in inode search
[signduterre.git] / proc_PID.py
blobf8bbc82c6d79d49858a8e9af2c13bbca726e3b8d
1 # proc_PID
2 #
3 # Signature-du-Terroir module to handle /proc/<pid>
5 # Help text
6 help_text = """
7 Start a command that will stall on reading STDIN, eg, grep, cat, dd, perl, python, ruby, bash.
8 Or enter the PID of an existing, running, task.
10 Use /proc/<pid>/{exe, maps} to collect information, eg, all loaded libraries, the file paths,
11 inode numbers, and mem addresses. Then use dbugfs to read out the inode numbers. The results can be
12 hashed with sha512sum (recommended) or returned raw.
14 System commands
15 reset() : Empty cache, next call to get_info() will run the command
16 get_info(command) : Run command and read all info, if command differs from current_command
17 get_info(PID) : Read all info from running process PID, if command differs from current pid
19 Values
20 current_command : Command (path) for which the current values are relevant
21 address_list : The start, end, and offset addresses for each file (path) loaded (/proc/<pid>/maps). Ie, each address_list[path] is a list of address lists.
22 map_list : Information for /proc/<pid>/maps - [path]={'permissions', 'device', 'inode'}
23 path_list : All files (paths) loaded for the current command (/proc/<pid>/maps).
24 mapsfile : The first file path in /proc/<pid>/maps, ie, the file with the command
25 file_system : Device, /dev/..., on which mapsfile is stored
26 exe : The path to which /proc/<pid>/exe links
28 Methods returning contents of /proc/<pid>/maps as text tables
29 paths (command, key_list) : A table of all paths with one or more of ['permissions', 'device', 'inode'] (inode is default)
31 The following methods return the SHA512 hash. They use the prefix to start the hash
32 Files are read using the inode numbers with debugfs (sudo only)
33 inodeSHA (command, file, prefix): The file contents based on the inode number in /proc/<pid>/maps, if 'file' is '', the mapsfile is used.
34 mapsSHA (command, prefix) : All files in /proc/<pid>/maps, based on inode number
36 For debugging purposes:
37 fileSHA (command, prefix) : Simple file read from disk using mapsfile
38 exeSHA (command, prefix) : File read using the exe link
40 The following methods return the WHOLE, binary file content
41 Files are read using the inode numbers with debugfs (sudo only)
42 inode (command, file): The file based on the inode number in /proc/<pid>/maps
43 maps (command) : All files in /proc/<pid>/maps, based on inode number
45 For debugging purposes:
46 file (command) : Simple file read from disk using mapsfile
47 exe (command) : File read using the exe link
49 Standalone use:
50 > python3 proc_PID.py [path1 ....]
51 Will run for each path:
52 fileSHA (path)
53 exeSHA (path)
54 inodeSHA (path, '')
55 mapsSHA (path)
56 """
58 import sys;
59 import os;
60 import subprocess;
61 import hashlib;
62 import re;
63 import time;
65 # Basic support routines
66 def create_process (command):
67 devnull = open('/dev/null', 'w');
68 return subprocess.Popen(command.split(), stdin = subprocess.PIPE, stdout = devnull, stderr = subprocess.PIPE, shell=False, universal_newlines=False);
70 def kill_process (process):
71 process.stdin.close();
72 errormess = process.stderr.read();
73 if len(errormess) > 0: print(str(errormess), file=sys.stderr);
75 # Define values
76 file_system = None;
77 address_list= {};
78 map_list = {};
79 path_list = [];
80 mapsfile = None;
81 exe = None;
82 current_command = '';
84 # Determine the file system on which a file is stored (using df)
85 def get_filesystem(path):
86 command = 'df -h '+path+' |tail -1';
87 process = subprocess.Popen(command, stdin = None, stdout = subprocess.PIPE, stderr = sys.stderr, shell=True, universal_newlines=True);
88 df_output = process.stdout.read();
89 return df_output.split()[0];
91 def get_debugfs_command (path, inode):
92 if os.getuid() != 0:
93 print("ERROR: must be root to read disk blocks\n", file=sys.stderr);
94 exit(1);
95 file_system = get_filesystem(path);
96 return "/sbin/debugfs -f - "+file_system+" -R 'cat <"+str(inode)+">'";
98 def reset ():
99 global file_system;
100 global address_list;
101 global map_list;
102 global path_list;
103 global mapsfile;
104 global exe;
105 global current_command;
106 file_system = None;
107 address_list = {};
108 map_list = {};
109 path_list = [];
110 mapsfile = None;
111 exe = None;
112 current_command = '';
114 # Start command and read info from /proc/<pid>/
115 # Do nothing if the command is identical to previous one
116 def getinfo (command): # -> pid
117 global file_system;
118 global address_list;
119 global map_list;
120 global path_list;
121 global mapsfile;
122 global exe;
123 global current_command;
125 if command == current_command:
126 return -1;
128 reset();
129 current_command = command;
131 # Do not create a process if it already exists
132 kill_process = False;
133 if isinstance(command, int):
134 pid = command;
135 elif re.search(r'[^0-9]', command) == None:
136 pid = int(command);
137 else:
138 process = create_process(command);
139 pid = process.pid;
140 # Wait a quarter of a second to allow the loading of the command to finish
141 time.sleep(0.25);
143 dir = '/proc/'+str(pid);
144 exe = os.readlink(dir+'/exe');
146 # Get all mapped memory and files
147 with open(dir+'/maps', 'r') as file:
148 for l in file:
149 record_list = l.split();
150 if len(record_list) == 6 and int(record_list[4]) > 0:
151 (addresses, permissions, offset, device, inode, path) = record_list;
152 if path not in path_list:
153 path_list.append(path);
154 map_list[path] = {'permissions':permissions, 'device':device, 'inode':inode};
155 address_list[path] = [];
156 (address1, address2) = addresses.split('-');
157 address_list[path].append({'start':address1, 'end':address2, 'offset':offset});
158 if kill_process: kill_process(process);
159 mapsfile = path_list[0];
160 file_system = get_filesystem(mapsfile);
161 return pid;
163 # Print out running proc stats
164 def paths (command, key_list = 'inode'): # 'key'/['key1', 'key2', ...] -> text-table
165 getinfo (command);
167 # Normalize keys
168 if isinstance(key_list, str):
169 key_list = [key_list];
170 # Construct a line for each path,
171 result_table = '';
172 for path in path_list:
173 line = path ;
174 for key in key_list:
175 line += '\t'+map_list[path][key];
176 line += '\n';
177 result_table += line;
178 return result_table;
180 # Methods that return the raw file contents (could be LARGE)
181 def file (command):
182 getinfo (command);
183 return_value = b'';
184 with open(mapsfile, 'rb') as file:
185 for l in file:
186 if type(l).__name__ == 'str':
187 l = bytes(l, encoding='utf8');
188 return_value += l;
189 return return_value;
191 def exe (command):
192 getinfo (command);
193 return_value = b'';
194 with open(exe, 'rb') as file:
195 for l in file:
196 if type(l).__name__ == 'str':
197 l = bytes(l, encoding='utf8');
198 return_value += l;
199 kill_process(process);
200 return return_value;
202 def inode (command, path):
203 getinfo(command);
205 # Get file contents on inode number
206 return_value = b'';
207 inode = map_list[path]['inode'];
208 debugfs = subprocess.Popen(get_debugfs_command(path, inode), stdin = None, stdout = subprocess.PIPE, stderr = sys.stderr, shell=True, universal_newlines=False);
209 # read out the file contents
210 for l in debugfs.stdout:
211 if type(l).__name__ == 'str':
212 l = bytes(l, encoding='utf8');
213 return_value += l;
215 return return_value;
217 def maps (command):
218 getinfo(command);
220 # Get file contents on inode number
221 return_value = b'';
222 for file in path_list:
223 inode = map_list[file]['inode'];
224 debugfs = subprocess.Popen(get_debugfs_command(path, inode), stdin = None, stdout = subprocess.PIPE, stderr = sys.stderr, shell=True, universal_newlines=False);
225 # read out the file contents
226 for l in debugfs.stdout:
227 if type(l).__name__ == 'str':
228 l = bytes(l, encoding='utf8');
229 return_value += l;
231 return return_value;
233 # Methods that return the SHA512sum
234 def fileSHA (command, prefix=''):
235 getinfo (command);
236 filehash = hashlib.sha512(bytes(prefix, encoding='ascii'));
237 with open(mapsfile, 'rb') as file:
238 for l in file:
239 if type(l).__name__ == 'str':
240 l = bytes(l, encoding='utf8');
241 filehash.update(l);
242 return str(filehash.hexdigest());
244 def exeSHA (command, prefix=''):
245 getinfo(command);
246 exehash = hashlib.sha512(bytes(prefix, encoding='ascii'));
247 with open(exe, 'rb') as file:
248 for l in file:
249 if type(l).__name__ == 'str':
250 l = bytes(l, encoding='utf8');
251 exehash.update(l);
252 return str(exehash.hexdigest());
254 def inodeSHA (command, path='', prefix=''):
255 getinfo(command);
256 if len(path) == 0: path = mapsfile;
258 # Get file contents on inode number
259 mapshash = hashlib.sha512(bytes(prefix, encoding='ascii'));
260 inode = map_list[path]['inode'];
261 debugfs = subprocess.Popen(get_debugfs_command(path, inode), stdin = None, stdout = subprocess.PIPE, stderr = sys.stderr, shell=True, universal_newlines=False);
262 # read out the file contents
263 for l in debugfs.stdout:
264 if type(l).__name__ == 'str':
265 l = bytes(l, encoding='utf8');
266 mapshash.update(l);
268 return str(mapshash.hexdigest());
270 def mapsSHA (command, prefix=''):
271 getinfo(command);
273 # Get file contents on inode number
274 mapshash = hashlib.sha512(bytes(prefix, encoding='ascii'));
275 for path in path_list:
276 inode = map_list[path]['inode'];
277 debugfs = subprocess.Popen(get_debugfs_command(path, inode), stdin = None, stdout = subprocess.PIPE, stderr = sys.stderr, shell=True, universal_newlines=False);
278 # read out the file contents
279 for l in debugfs.stdout:
280 if type(l).__name__ == 'str':
281 l = bytes(l, encoding='utf8');
282 mapshash.update(l);
284 return str(mapshash.hexdigest());
286 if __name__ == "__main__":
287 if len(sys.argv) == 1:
288 print(help_text);
289 else:
290 for command in sys.argv[1:]:
291 result = fileSHA(command);
292 print(mapsfile+(len("/proc/<pid>/inode") - len(mapsfile))*' ', result);
293 result = exeSHA(command);
294 print("/proc/<pid>/exe"+(len("/proc/<pid>/inode") - len("/proc/<pid>/exe"))*' ', result);
295 if os.getuid() == 0:
296 result = inodeSHA(command);
297 print("/proc/<pid>/inode"+(len(mapsfile) - len("/proc/<pid>/inode"))*' ', result);
298 result = mapsSHA(command);
299 print("/proc/<pid>/maps"+(len("/proc/<pid>/inode") - len("/proc/<pid>/all"))*' ', result);
301 print("");
302 result = paths(command);
303 print(result);
304 print("");