Removed redundant and incorrect character cleaning check from --input-file
[signduterre.git] / proc_PID.py
blobd0c6e94f4413d09a4ffb634ad0b35be6f026c1b5
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)
57 ===================
58 proc_PID
59 Signature-du-Terroir module to access /proc/<pid> pseudo files.
61 copyright 2009, R.J.J.H. van Son
63 This program is free software: you can redistribute it and/or modify
64 it under the terms of the GNU General Public License as published by
65 the Free Software Foundation, either version 3 of the License, or
66 (at your option) any later version.
68 This program is distributed in the hope that it will be useful,
69 but WITHOUT ANY WARRANTY; without even the implied warranty of
70 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
71 GNU General Public License for more details.
73 You should have received a copy of the GNU General Public License
74 along with this program. If not, see <http://www.gnu.org/licenses/>.
75 """
77 import sys;
78 import os;
79 import subprocess;
80 import hashlib;
81 import re;
82 import time;
84 # Basic support routines
85 def create_process (command):
86 devnull = open('/dev/null', 'w');
87 return subprocess.Popen(command.split(), stdin = subprocess.PIPE, stdout = devnull, stderr = subprocess.PIPE, shell=False, universal_newlines=False);
89 def kill_process (process):
90 process.stdin.close();
91 errormess = process.stderr.read();
92 if len(errormess) > 0: print(str(errormess), file=sys.stderr);
94 # Define values
95 file_system = None;
96 address_list= {};
97 map_list = {};
98 path_list = [];
99 mapsfile = None;
100 exe = None;
101 current_command = '';
103 # Determine the file system on which a file is stored (using df)
104 def get_filesystem(path):
105 command = 'df -h '+path+' |tail -1';
106 process = subprocess.Popen(command, stdin = None, stdout = subprocess.PIPE, stderr = sys.stderr, shell=True, universal_newlines=True);
107 df_output = process.stdout.read();
108 return df_output.split()[0];
110 def get_debugfs_command (path, inode):
111 if os.getuid() != 0:
112 print("ERROR: must be root to read disk blocks\n", file=sys.stderr);
113 exit(1);
114 file_system = get_filesystem(path);
115 return "/sbin/debugfs -f - "+file_system+" -R 'cat <"+str(inode)+">'";
117 def reset ():
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;
125 file_system = None;
126 address_list = {};
127 map_list = {};
128 path_list = [];
129 mapsfile = None;
130 exe = None;
131 current_command = '';
133 # Start command and read info from /proc/<pid>/
134 # Do nothing if the command is identical to previous one
135 def getinfo (command): # -> pid
136 global file_system;
137 global address_list;
138 global map_list;
139 global path_list;
140 global mapsfile;
141 global exe;
142 global current_command;
144 if command == current_command:
145 return -1;
147 reset();
148 current_command = command;
150 # Do not create a process if it already exists
151 kill_process = False;
152 if isinstance(command, int):
153 pid = command;
154 elif re.search(r'[^0-9]', command) == None:
155 pid = int(command);
156 else:
157 process = create_process(command);
158 pid = process.pid;
159 # Wait a quarter of a second to allow the loading of the command to finish
160 time.sleep(0.25);
162 dir = '/proc/'+str(pid);
163 exe = os.readlink(dir+'/exe');
165 # Get all mapped memory and files
166 with open(dir+'/maps', 'r') as file:
167 for l in file:
168 record_list = l.split();
169 if len(record_list) == 6 and int(record_list[4]) > 0:
170 (addresses, permissions, offset, device, inode, path) = record_list;
171 if path not in path_list:
172 path_list.append(path);
173 map_list[path] = {'permissions':permissions, 'device':device, 'inode':inode};
174 address_list[path] = [];
175 (address1, address2) = addresses.split('-');
176 address_list[path].append({'start':address1, 'end':address2, 'offset':offset});
177 if kill_process: kill_process(process);
178 mapsfile = path_list[0];
179 file_system = get_filesystem(mapsfile);
180 return pid;
182 # Print out running proc stats
183 def paths (command, key_list = 'inode'): # 'key'/['key1', 'key2', ...] -> text-table
184 getinfo (command);
186 # Normalize keys
187 if isinstance(key_list, str):
188 key_list = [key_list];
189 # Construct a line for each path,
190 result_table = '';
191 for path in path_list:
192 line = path ;
193 for key in key_list:
194 line += '\t'+map_list[path][key];
195 line += '\n';
196 result_table += line;
197 return result_table;
199 # Methods that return the raw file contents (could be LARGE)
200 def file (command):
201 getinfo (command);
202 return_value = b'';
203 with open(mapsfile, 'rb') as file:
204 for l in file:
205 if type(l).__name__ == 'str':
206 l = bytes(l, encoding='utf8');
207 return_value += l;
208 return return_value;
210 def exe (command):
211 getinfo (command);
212 return_value = b'';
213 with open(exe, 'rb') as file:
214 for l in file:
215 if type(l).__name__ == 'str':
216 l = bytes(l, encoding='utf8');
217 return_value += l;
218 kill_process(process);
219 return return_value;
221 def inode (command, path):
222 getinfo(command);
224 # Get file contents on inode number
225 return_value = b'';
226 inode = map_list[path]['inode'];
227 debugfs = subprocess.Popen(get_debugfs_command(path, inode), stdin = None, stdout = subprocess.PIPE, stderr = sys.stderr, shell=True, universal_newlines=False);
228 # read out the file contents
229 for l in debugfs.stdout:
230 if type(l).__name__ == 'str':
231 l = bytes(l, encoding='utf8');
232 return_value += l;
234 return return_value;
236 def maps (command):
237 getinfo(command);
239 # Get file contents on inode number
240 return_value = b'';
241 for file in path_list:
242 inode = map_list[file]['inode'];
243 debugfs = subprocess.Popen(get_debugfs_command(path, inode), stdin = None, stdout = subprocess.PIPE, stderr = sys.stderr, shell=True, universal_newlines=False);
244 # read out the file contents
245 for l in debugfs.stdout:
246 if type(l).__name__ == 'str':
247 l = bytes(l, encoding='utf8');
248 return_value += l;
250 return return_value;
252 # Methods that return the SHA512sum
253 def fileSHA (command, prefix=''):
254 getinfo (command);
255 filehash = hashlib.sha512(bytes(prefix, encoding='ascii'));
256 with open(mapsfile, 'rb') as file:
257 for l in file:
258 if type(l).__name__ == 'str':
259 l = bytes(l, encoding='utf8');
260 filehash.update(l);
261 return str(filehash.hexdigest());
263 def exeSHA (command, prefix=''):
264 getinfo(command);
265 exehash = hashlib.sha512(bytes(prefix, encoding='ascii'));
266 with open(exe, 'rb') as file:
267 for l in file:
268 if type(l).__name__ == 'str':
269 l = bytes(l, encoding='utf8');
270 exehash.update(l);
271 return str(exehash.hexdigest());
273 def inodeSHA (command, path='', prefix=''):
274 getinfo(command);
275 if len(path) == 0: path = mapsfile;
277 # Get file contents on inode number
278 mapshash = hashlib.sha512(bytes(prefix, encoding='ascii'));
279 inode = map_list[path]['inode'];
280 debugfs = subprocess.Popen(get_debugfs_command(path, inode), 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 def mapsSHA (command, prefix=''):
290 getinfo(command);
292 # Get file contents on inode number
293 mapshash = hashlib.sha512(bytes(prefix, encoding='ascii'));
294 for path in path_list:
295 inode = map_list[path]['inode'];
296 debugfs = subprocess.Popen(get_debugfs_command(path, inode), stdin = None, stdout = subprocess.PIPE, stderr = sys.stderr, shell=True, universal_newlines=False);
297 # read out the file contents
298 for l in debugfs.stdout:
299 if type(l).__name__ == 'str':
300 l = bytes(l, encoding='utf8');
301 mapshash.update(l);
303 return str(mapshash.hexdigest());
305 if __name__ == "__main__":
306 if len(sys.argv) == 1:
307 print(help_text);
308 else:
309 for command in sys.argv[1:]:
310 result = fileSHA(command);
311 print(mapsfile+(len("/proc/<pid>/inode") - len(mapsfile))*' ', result);
312 result = exeSHA(command);
313 print("/proc/<pid>/exe"+(len("/proc/<pid>/inode") - len("/proc/<pid>/exe"))*' ', result);
314 if os.getuid() == 0:
315 result = inodeSHA(command);
316 print("/proc/<pid>/inode"+(len(mapsfile) - len("/proc/<pid>/inode"))*' ', result);
317 result = mapsSHA(command);
318 print("/proc/<pid>/maps"+(len("/proc/<pid>/inode") - len("/proc/<pid>/all"))*' ', result);
320 print("");
321 result = paths(command);
322 print(result);
323 print("");