3 # Signature-du-Terroir module to handle /proc/<pid>
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.
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
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
50 > python3 proc_PID.py [path1 ....]
51 Will run for each path:
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
);
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 (file_system
, inode
):
93 print("ERROR: must be root to read disk blocks\n", file=sys
.stderr
);
95 return "/sbin/debugfs -f - "+file_system
+" -R 'cat <"+str(inode
)+">'";
104 global current_command
;
111 current_command
= '';
113 # Start command and read info from /proc/<pid>/
114 # Do nothing if the command is identical to previous one
115 def getinfo (command
): # -> pid
122 global current_command
;
124 if command
== current_command
:
128 current_command
= command
;
130 # Do not create a process if it already exists
131 kill_process
= False;
132 if isinstance(command
, int):
134 elif re
.search(r
'[^0-9]', command
) == None:
137 process
= create_process(command
);
139 # Wait a quarter of a second to allow the loading of the command to finish
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:
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
);
162 # Print out running proc stats
163 def paths (command
, key_list
= 'inode'): # 'key'/['key1', 'key2', ...] -> text-table
167 if isinstance(key_list
, str):
168 key_list
= [key_list
];
169 # Construct a line for each path,
171 for path
in path_list
:
174 line
+= '\t'+map_list
[path
][key
];
176 result_table
+= line
;
179 # Methods that return the raw file contents (could be LARGE)
183 with
open(mapsfile
, 'rb') as file:
185 if type(l
).__name
__ == 'str':
186 l
= bytes(l
, encoding
='utf8');
193 with
open(exe
, 'rb') as file:
195 if type(l
).__name
__ == 'str':
196 l
= bytes(l
, encoding
='utf8');
198 kill_process(process
);
201 def inode (command
, path
):
204 # Get file contents on inode number
206 inode
= map_list
[path
]['inode'];
207 debugfs
= subprocess
.Popen(get_debugfs_command(file_system
, inode
), stdin
= None, stdout
= subprocess
.PIPE
, stderr
= sys
.stderr
, shell
=True, universal_newlines
=False);
208 # read out the file contents
209 for l
in debugfs
.stdout
:
210 if type(l
).__name
__ == 'str':
211 l
= bytes(l
, encoding
='utf8');
219 # Get file contents on inode number
221 for file in path_list
:
222 inode
= map_list
[file]['inode'];
223 debugfs
= subprocess
.Popen(get_debugfs_command(file_system
, inode
), stdin
= None, stdout
= subprocess
.PIPE
, stderr
= sys
.stderr
, shell
=True, universal_newlines
=False);
224 # read out the file contents
225 for l
in debugfs
.stdout
:
226 if type(l
).__name
__ == 'str':
227 l
= bytes(l
, encoding
='utf8');
232 # Methods that return the SHA512sum
233 def fileSHA (command
, prefix
=''):
235 filehash
= hashlib
.sha512(bytes(prefix
, encoding
='ascii'));
236 with
open(mapsfile
, 'rb') as file:
238 if type(l
).__name
__ == 'str':
239 l
= bytes(l
, encoding
='utf8');
241 return str(filehash
.hexdigest());
243 def exeSHA (command
, prefix
=''):
245 exehash
= hashlib
.sha512(bytes(prefix
, encoding
='ascii'));
246 with
open(exe
, 'rb') as file:
248 if type(l
).__name
__ == 'str':
249 l
= bytes(l
, encoding
='utf8');
251 return str(exehash
.hexdigest());
253 def inodeSHA (command
, path
='', prefix
=''):
255 if len(path
) == 0: path
= mapsfile
;
257 # Get file contents on inode number
258 mapshash
= hashlib
.sha512(bytes(prefix
, encoding
='ascii'));
259 inode
= map_list
[path
]['inode'];
260 debugfs
= subprocess
.Popen(get_debugfs_command(file_system
, inode
), stdin
= None, stdout
= subprocess
.PIPE
, stderr
= sys
.stderr
, shell
=True, universal_newlines
=False);
261 # read out the file contents
262 for l
in debugfs
.stdout
:
263 if type(l
).__name
__ == 'str':
264 l
= bytes(l
, encoding
='utf8');
267 return str(mapshash
.hexdigest());
269 def mapsSHA (command
, prefix
=''):
272 # Get file contents on inode number
273 mapshash
= hashlib
.sha512(bytes(prefix
, encoding
='ascii'));
274 for path
in path_list
:
275 inode
= map_list
[path
]['inode'];
276 debugfs
= subprocess
.Popen(get_debugfs_command(file_system
, inode
), stdin
= None, stdout
= subprocess
.PIPE
, stderr
= sys
.stderr
, shell
=True, universal_newlines
=False);
277 # read out the file contents
278 for l
in debugfs
.stdout
:
279 if type(l
).__name
__ == 'str':
280 l
= bytes(l
, encoding
='utf8');
283 return str(mapshash
.hexdigest());
285 if __name__
== "__main__":
286 if len(sys
.argv
) == 1:
289 for command
in sys
.argv
[1:]:
290 result
= fileSHA(command
);
291 print(mapsfile
+(len("/proc/<pid>/inode") - len(mapsfile
))*' ', result
);
292 result
= exeSHA(command
);
293 print("/proc/<pid>/exe"+(len("/proc/<pid>/inode") - len("/proc/<pid>/exe"))*' ', result
);
295 result
= inodeSHA(command
);
296 print("/proc/<pid>/inode"+(len(mapsfile
) - len("/proc/<pid>/inode"))*' ', result
);
297 result
= mapsSHA(command
);
298 print("/proc/<pid>/maps"+(len("/proc/<pid>/inode") - len("/proc/<pid>/all"))*' ', result
);
301 result
= paths(command
);