基于Go语言的远程控制程序
大神(github帐号: kingname), 项目源码地址:https://github.com/kingname/RemoteControl/ , 用python实现远程控制。本项目是想采用Go语言实现类似的远程控制。本项目地址:github.com/whuwzp/RemoteControl/
仿写的master端的代码就不再贴了,在我的GitHub中将提供了这些代码,分别为1.0,2.0,3.0,其中1.0为仿写,完成了执行命令,2.0完成了回传(但是,仍然是在之前的架构下写的),3.0(即此版本)为重写部分,重写了消息格式等。
实现目标
- 实现被控端(slave)、中间反弹端(server)和控制端(master)的控制架构,其中被控端可以是多个主机;
- 实现master经由server控制slave执行程序命令和python脚本(以系统自带的计算器为例),如果cmdshell中有信息(如ipconfig),则回传master端;
- 实现master经由server控制slave写文件(以写入python脚本为例)
- 尚未实现:传输文件。
实验环境
- OS:win10
- 编程语言:Go语言(版本:1.9.2)
实验原理
反弹式架构
主要是由slave、master统一连接server端实现。
-
server端,绑定监听127.0.0.1:5000,并接收连接:
listen, err := net.Listen("tcp", "127.0.0.1:5000") conn, err := listen.Accept()
-
master和slave端,都连接server的127.0.0.1:5000:
conn, err := net.Dial("tcp", "127.0.0.1:5000")
消息协议
为了方便解析和统一格式,采用了json格式将发送的消息进行了格式化。其中,消息的结构体为:
type Message struct {
From string //发送方
To string //接收方
Type string //消息类型
Cmd string //指令
Args string //指令参数
}
const (
MASTER = "master"
SLAVE = "slave"
SERVER = "server"
TYPECMD = "cmd"
TYPECODE = "code"
TYPEDATA = "data"
)
-
From
可以是MASTER, SERVER, "127.0.0.1:41431",分别表示控制端,服务端和连接地址为127.0.0.1:41431的slave;
-
To
同From
-
Type
- TYPECMD,表示发送的是指令,Cmd为指令,Args为指令参数(可为空);
- TYPECODE,表示发送的是待写入的代码,此时Cmd为空,Args为代码内容;
- TYPEDATA,表示发送的是文件请求,此时Cmd为空,Args为文件名。
-
Cmd
指令,如"calc"表示计算器,"python"表示执行python(需配置环境变量),"listslave"命令是向server查询现在在线的slave信息;
-
Args
指令参数,或者数据(代码),以及响应的cmdshell中的信息。
构造消息后,用json的MarshalIndent和Unmarshal函数,格式化和解析。
data, err := json.MarshalIndent(m, "", " ")
err := json.Unmarshal(data, &m)
消息接受发送、编码解码代码如下:
package message
import (
"encoding/json"
"fmt"
"log"
"net"
"os"
"io"
)
type Message struct {
From string
To string
Type string
Cmd string
Args string
}
func SendMsg(c net.Conn, m Message) {
data, err := json.MarshalIndent(m, "", " ")
if err != nil {
log.Fatalf("JSON marshaling failed: %s", err)
}
c.Write(data)
}
func RecvMsg(c net.Conn, buf []byte) Message {
nbyte, err := c.Read(buf)
if err != nil {
log.Fatal("recving msg error: ", err)
}
fmt.Println("recv data :", string(buf[:nbyte]))
return AnalyseMsg(buf[:nbyte])
}
func AnalyseMsg(data []byte) Message {
var m Message
err := json.Unmarshal(data, &m)
if err != nil {
log.Fatalf("JSON unmarshaling failed: %s", err)
}
return m
}
func RecvFile(c net.Conn, fn string){
f,err := os.Create(fn)
if err!=nil{
log.Fatal("open file error:", err)
}
finfo, _ := os.Lstat(fn)
io.CopyN(f, c, finfo.Size())
}
func SendFile(c net.Conn, fn string){
f,err := os.Open(fn)
if err!=nil{
log.Fatal("open file error:", err)
}
finfo, _ := os.Lstat(fn)
io.CopyN(c, f, finfo.Size())
}
func TransFile(src, dst net.Conn){
io.CopyN(src, dst, 4)
}
server端
server主要负责以下内容:
- 维护slaveConnPool,slave的连接信息;
- 响应master的listslave请求(获取当前在线的slave端);
- 接收slave和master的消息,由dispatch分流,根据消息的from和to字段分类进行处理(多数根据to信息直接转发);
完整代码如下:
package main
import (
"fmt"
"github.com/whuwzp/RemoteControl/RemoteControl/message"
"net"
"sync"
)
const (
bufsize = 4096 * 100
MASTER = "master"
SLAVE = "slave"
SERVER = "server"
TYPECMD = "cmd"
TYPECODE = "code"
TYPEDATA = "data"
)
var (
slaveConnPool []net.Conn
masterConn net.Conn
mu sync.Mutex
)
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:5000")
if err != nil {
panic(err)
}
for {
conn, err := listen.Accept()
if err != nil {
panic(err)
}
fmt.Println("========================\na new conn: ", conn.RemoteAddr().String())
AddslaveConnPool(conn)
go handleConn(conn)
}
defer listen.Close()
}
func handleConn(c net.Conn) {
defer c.Close()
for {
buf := make([]byte, bufsize)
RecvMsg := message.RecvMsg(c, buf)
//delete the master conn from pool
if RecvMsg.From == "master" {
masterConn = c
DelslaveConnPool(c)
}
dispatch(RecvMsg)
}
}
func AddslaveConnPool(c net.Conn) {
mu.Lock()
defer mu.Unlock()
slaveConnPool = append(slaveConnPool, c)
fmt.Println("conn pool :", slaveConnPool)
}
func DelslaveConnPool(c net.Conn) {
mu.Lock()
defer mu.Unlock()
//fmt.Println("pool before delete", slaveConnPool)
var pool []net.Conn
for _, v := range slaveConnPool {
if v == c {
} else {
pool = append(pool, v)
}
}
slaveConnPool = pool
//fmt.Println("pool after delete", slaveConnPool)
}
func dispatch(m message.Message) {
if m.To == MASTER {
slave2master(m)
} else if m.To == SERVER {
master2server(m)
} else {
master2slave(m)
}
}
func slave2master(m message.Message) {
message.SendMsg(masterConn, m)
}
func master2slave(m message.Message) {
for _, c := range slaveConnPool {
if c.RemoteAddr().String() == m.To {
if m.Type == TYPEDATA{
message.SendMsg(c, m)
message.TransFile(c, masterConn)
} else {
message.SendMsg(c, m)
}
break
}
}
}
func master2server(m message.Message) {
var msg message.Message
if m.Cmd == "listslave" {
msg = message.Message{
SERVER,
MASTER,
TYPEDATA,
"",
string(listslave()),
}
}
message.SendMsg(masterConn, msg)
}
func listslave() string {
mu.Lock()
defer mu.Unlock()
var list string
for _, c := range slaveConnPool {
list += (c.RemoteAddr().String() + " ")
}
fmt.Println("list:", list)
return list
}
master端
master端比较简单,就是输入消息中的各个元素,并根据消息类型(向slave写入数据,即TYPECODE)和具体指令(是否需要等待回复,如VoidCmd中的为不要回复的)进行消息发送和接收。
package main
import (
"fmt"
"net"
//"regexp"
//"os"
//"encoding/json"
"github.com/whuwzp/RemoteControl/RemoteControl/message"
)
const (
MASTER = "master"
SLAVE = "slave"
SERVER = "server"
TYPECMD = "cmd"
TYPECODE = "code"
TYPEDATA = "data"
)
var VoidCmd []string
func init() {
VoidCmd = []string{"python", "calc"}
}
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:5000")
if err != nil {
panic(err)
}
defer conn.Close()
buf := make([]byte, 4096)
flag := false
for {
flag = false
m := GetCmd()
fmt.Println("starting to send msg...")
for _, v := range VoidCmd {
if m.Cmd == v {
//sending
message.SendMsg(conn, m)
flag = true
break
}
}
if !flag {
if m.Type == TYPEDATA{
message.SendMsg(conn, m)
fmt.Println("starting to recv file...")
message.RecvFile(conn, m.Args)
}else {
message.SendMsg(conn, m)
fmt.Println("starting to recv msg...")
RecvMsg := message.RecvMsg(conn, buf)
fmt.Println(RecvMsg.Args)
}
}
}
}
func GetCmd() message.Message {
var from, to, cmdtype, cmd, args string
//from
from = MASTER
//to
fmt.Println("please select your slave (or 0: server): ")
fmt.Scanln(&to)
if to == "0" {
to = SERVER
}
//type
fmt.Println("please select your command type: \n" +
"0: execCmd\n" +
"1: writeCode\n" +
"2: Data")
fmt.Scanln(&cmdtype)
if cmdtype == "0" {
cmdtype = TYPECMD
} else if cmdtype == "1" {
cmdtype = TYPECODE
} else {
cmdtype = TYPEDATA
}
//cmd
fmt.Println("please input your command: (0: listslave)")
fmt.Scanln(&cmd)
if cmd == "0" {
cmd = "listslave"
}
//args
fmt.Println("please input your command args: ")
fmt.Scanln(&args)
return message.Message{from, to, cmdtype, cmd, args}
}
slave端
slave端也需要解析消息,根据消息类型和具体指令进行分流处理dispatch,分流消息至特定的处理函数,如执行命令,写python脚本等;
完整代码如下:
package main
import (
"fmt"
"net"
"log"
"os/exec"
"bufio"
"io"
"os"
"github.com/whuwzp/RemoteControl/RemoteControl/message"
)
const (
bufsize = 4096 * 100
MASTER = "master"
SLAVE = "slave"
SERVER = "server"
TYPECMD = "cmd"
TYPECODE = "code"
TYPEDATA = "data"
)
var (
DATA = make([]string, 4096)
conn net.Conn
)
func main() {
var err error
conn, err = net.Dial("tcp", "127.0.0.1:5000")
if err != nil {
panic(err)
}
defer conn.Close()
buf := make([]byte, 4096)
for {
RecvMsg := message.RecvMsg(conn, buf)
dispatch(RecvMsg)
}
}
func dispatch(m message.Message) {
if m.Type == TYPECMD {
msg := execCommand(m.Cmd, m.Args)
RespMsg := message.Message{
From: SLAVE,
To: MASTER,
Type: TYPECMD,
Cmd: "",
Args: msg,
}
message.SendMsg(conn, RespMsg)
} else if m.Type == TYPECODE {
writeCommand(m.Args)
} else {
//file
message.SendFile(conn, m.Args)
}
}
//for example: python test.py
func execCommand(Cmd string, arg string) string {
cmd := exec.Command(Cmd, arg)
fmt.Println("going to exe command...")
//显示运行的命令
fmt.Println(cmd.Args)
stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Println(err)
}
cmd.Start()
reader := bufio.NewReader(stdout)
msg := ""
//实时循环读取输出流中的一行内容
for {
line, err2 := reader.ReadString('\n')
if err2 != nil || io.EOF == err2 {
break
}
msg += line
}
fmt.Println(msg)
cmd.Wait()
return msg
}
func writeCommand(code string) {
fmt.Println("going to write code...")
f, err := os.Create("python.py")
if err != nil {
log.Println(err)
}
defer f.Close()
f.WriteString(code)
fmt.Println("writing code:", code)
fmt.Println("writen!")
}
func fileCommand(filename string){
}
核心功能
-
执行命令
cmd := exec.Command(Cmd, arg) cmd.Start()
-
cmdshell信息回传
主要是以下方法:
cmd := exec.Command(Cmd, arg) stdout, err := cmd.StdoutPipe() cmd.Start() reader := bufio.NewReader(stdout) msg := "" //实时循环读取输出流中的一行内容 for { line, err2 := reader.ReadString('\n') if err2 != nil || io.EOF == err2 { break } msg += line } fmt.Println(msg) cmd.Wait() return msg
-
文件传输
这主要利用了
io.copyN
函数:func CopyN(dst Writer, src Reader, n int64)
其中,因为net.Conn和os.File都完成了Writer,Reader的接口,因此可以作为其实例。具体步骤思路是:
- slave端:CopyN(Conn, file, n int64) ,这里的Conn是slave和server的连接,file就是待传输的文件,n为文件大小,由os.fileinfo可获取;
- server端:CopyN(masterConn, Conn, n int64) ,这里的masterConn是server和master的连接,Conn是slave和server的连接;
- master端:CopyN(file, masterConn, n int64) ,这里的masterConn是server和master的连接,file就是master接收后的文件。
实验结果展示
- 获取slave
-
执行指令(以calc计算器为例)
我们向127.0.0.1:12296(slave)发送了calc 指令,server端也显示转发这条指令到slave,结果运行起了计算器:
3-3.png
-
写python代码
我们向slave端写了内容为hello的python脚本,可以看到在slave.exe的文件目录下,生产了python.py,其内容为hello。