Added documentation and example of modules (ie, proc_PID.py)
[signduterre.git] / proc_PID.py
blob84046f622e790a75836fac54ac38c2fb769779ea
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
32 Files are read using the inode numbers with debugfs (sudo only)
33 inodeSHA (command, file): The file contents based on the inode number in /proc/<pid>/maps, if 'file' is '', the mapsfile is used.
34 mapsSHA (command) : All files in /proc/<pid>/maps, based on inode number
36 For debugging purposes:
37 fileSHA (command) : Simple file read from disk using mapsfile
38 exeSHA (command) : 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;
64 # Define prefix
65 if __name__ == "__main__":
66 prefix = b'';
67 else:
68 prefix = bytes(mainprefix, encoding='ascii');
70 # Basic support routines
71 def create_process (command):
72 devnull = open('/dev/null', 'w');
73 return subprocess.Popen(command.split(), stdin = subprocess.PIPE, stdout = devnull, stderr = subprocess.PIPE, shell=False, universal_newlines=False);
75 def kill_process (process):
76 process.stdin.close();
77 errormess = process.stderr.read();
78 if len(errormess) > 0: print(str(errormess), file=sys.stderr);
80 # Define values
81 file_system = None;
82 address_list= {};
83 map_list = {};
84 path_list = [];
85 mapsfile = None;
86 exe = None;
87 current_command = '';
89 # Determine the file system on which a file is stored (using df)
90 def get_filesystem(path):
91 command = 'df -h '+path+' |tail -1';
92 process = subprocess.Popen(command, stdin = None, stdout = subprocess.PIPE, stderr = sys.stderr, shell=True, universal_newlines=True);
93 df_output = process.stdout.read();
94 return df_output.split()[0];
96 def get_debugfs_command ():
97 return "/sbin/debugfs -f - "+file_system+" -R ";
99 def reset ():
100 global file_system;
101 global address_list;
102 global map_list;
103 global path_list;
104 global mapsfile;
105 global exe;
106 global current_command;
107 file_system = None;
108 address_list = {};
109 map_list = {};
110 path_list = [];
111 mapsfile = None;
112 exe = None;
113 current_command = '';
115 # Start command and read info from /proc/<pid>/
116 # Do nothing if the command is identical to previous one
117 def getinfo (command): # -> pid
118 global file_system;
119 global address_list;
120 global map_list;
121 global path_list;
122 global mapsfile;
123 global exe;
124 global current_command;
126 if command == current_command:
127 return -1;
129 reset();
130 current_command = command;
132 # Do not create a process if it already exists
133 kill_process = False;
134 if isinstance(command, int):
135 pid = command;
136 elif re.search(r'[^0-9]', command) == None:
137 pid = int(command);
138 else:
139 process = create_process(command);
140 pid = process.pid;
142 dir = '/proc/'+str(pid);
143 exe = os.readlink(dir+'/exe');
145 # Get all mapped memory and files
146 with open(dir+'/maps', 'r') as file:
147 for l in file:
148 record_list = l.split();
149 if len(record_list) == 6 and int(record_list[4]) > 0:
150 (addresses, permissions, offset, device, inode, path) = record_list;
151 if path not in path_list:
152 path_list.append(path);
153 map_list[path] = {'permissions':permissions, 'device':device, 'inode':inode};
154 address_list[path] = [];
155 (address1, address2) = addresses.split('-');
156 address_list[path].append({'start':address1, 'end':address2, 'offset':offset});
157 if kill_process: kill_process(process);
158 mapsfile = path_list[0];
159 file_system = get_filesystem(mapsfile);
160 return pid;
162 # Print out running proc stats
163 def paths (command, key_list = 'inode'): # 'key'/['key1', 'key2', ...] -> text-table
164 getinfo (command);
166 # Normalize keys
167 if isinstance(key_list, str):
168 key_list = [key_list];
169 # Construct a line for each path,
170 result_table = '';
171 for path in path_list:
172 line = path ;
173 for key in key_list:
174 line += '\t'+map_list[path][key];
175 line += '\n';
176 result_table += line;
177 return result_table;
179 # Methods that return the raw file contents (could be LARGE)
180 def file (command):
181 getinfo (command);
182 return_value = b'';
183 with open(mapsfile, 'rb') as file:
184 for l in file:
185 if type(l).__name__ == 'str':
186 l = bytes(l, encoding='utf8');
187 return_value += l;
188 return return_value;
190 def exe (command):
191 getinfo (command);
192 return_value = b'';
193 with open(exe, 'rb') as file:
194 for l in file:
195 if type(l).__name__ == 'str':
196 l = bytes(l, encoding='utf8');
197 return_value += l;
198 kill_process(process);
199 return return_value;
201 def inode (command, path):
202 getinfo(command);
204 # Get file contents on inode number
205 return_value = b'';
206 inode = map_list[path]['inode'];
207 cat = "'cat <"+str(inode)+">'";
208 debugfs = subprocess.Popen(get_debugfs_command()+cat, 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 cat = "'cat <"+str(inode)+">'";
225 debugfs = subprocess.Popen(get_debugfs_command()+cat, stdin = None, stdout = subprocess.PIPE, stderr = sys.stderr, shell=True, universal_newlines=False);
226 # read out the file contents
227 for l in debugfs.stdout:
228 if type(l).__name__ == 'str':
229 l = bytes(l, encoding='utf8');
230 return_value += l;
232 return return_value;
234 # Methods that return the SHA512sum
235 def fileSHA (command):
236 getinfo (command);
237 filehash = hashlib.sha512(prefix);
238 with open(mapsfile, 'rb') as file:
239 for l in file:
240 if type(l).__name__ == 'str':
241 l = bytes(l, encoding='utf8');
242 filehash.update(l);
243 return str(filehash.hexdigest());
245 def exeSHA (command):
246 getinfo(command);
247 exehash = hashlib.sha512(prefix);
248 with open(exe, 'rb') as file:
249 for l in file:
250 if type(l).__name__ == 'str':
251 l = bytes(l, encoding='utf8');
252 exehash.update(l);
253 return str(exehash.hexdigest());
255 def inodeSHA (command, path=''):
256 getinfo(command);
257 if len(path) == 0: path = mapsfile;
259 # Get file contents on inode number
260 mapshash = hashlib.sha512(prefix);
261 inode = map_list[path]['inode'];
262 cat = "'cat <"+str(inode)+">'";
263 debugfs = subprocess.Popen(get_debugfs_command()+cat, stdin = None, stdout = subprocess.PIPE, stderr = sys.stderr, shell=True, universal_newlines=False);
264 # read out the file contents
265 for l in debugfs.stdout:
266 if type(l).__name__ == 'str':
267 l = bytes(l, encoding='utf8');
268 mapshash.update(l);
270 return str(mapshash.hexdigest());
272 def mapsSHA (command):
273 getinfo(command);
275 # Get file contents on inode number
276 mapshash = hashlib.sha512(prefix);
277 for path in path_list:
278 inode = map_list[path]['inode'];
279 cat = "'cat <"+str(inode)+">'";
280 debugfs = subprocess.Popen(get_debugfs_command()+cat, stdin = None, stdout = subprocess.PIPE, stderr = sys.stderr, shell=True, universal_newlines=False);
281 # read out the file contents
282 for l in debugfs.stdout:
283 if type(l).__name__ == 'str':
284 l = bytes(l, encoding='utf8');
285 mapshash.update(l);
287 return str(mapshash.hexdigest());
289 if __name__ == "__main__":
290 if len(sys.argv) == 1:
291 print(help_text);
292 else:
293 for command in sys.argv[1:]:
294 result = fileSHA(command);
295 print(mapsfile+(len("/proc/<pid>/inode") - len(mapsfile))*' ', result);
296 result = exeSHA(command);
297 print("/proc/<pid>/exe"+(len("/proc/<pid>/inode") - len("/proc/<pid>/exe"))*' ', result);
298 if os.getuid() == 0:
299 result = inodeSHA(command);
300 print("/proc/<pid>/inode"+(len(mapsfile) - len("/proc/<pid>/inode"))*' ', result);
301 result = mapsSHA(command);
302 print("/proc/<pid>/maps"+(len("/proc/<pid>/inode") - len("/proc/<pid>/all"))*' ', result);
304 print("");
305 result = paths(command);
306 print(result);
307 print("");