基于Go语言的远程控制程序

作者: 笔默纸言 | 来源:发表于2019-05-21 07:00 被阅读4次

    大神(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(即此版本)为重写部分,重写了消息格式等。

    实现目标

    1. 实现被控端(slave)、中间反弹端(server)和控制端(master)的控制架构,其中被控端可以是多个主机;
    2. 实现master经由server控制slave执行程序命令和python脚本(以系统自带的计算器为例),如果cmdshell中有信息(如ipconfig),则回传master端;
    3. 实现master经由server控制slave写文件(以写入python脚本为例)
    4. 尚未实现:传输文件。

    实验环境

    1. OS:win10
    2. 编程语言: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"
    )
    
    1. From

      可以是MASTER, SERVER, "127.0.0.1:41431",分别表示控制端,服务端和连接地址为127.0.0.1:41431的slave;

    2. To

      同From

    3. Type

      • TYPECMD,表示发送的是指令,Cmd为指令,Args为指令参数(可为空);
      • TYPECODE,表示发送的是待写入的代码,此时Cmd为空,Args为代码内容;
      • TYPEDATA,表示发送的是文件请求,此时Cmd为空,Args为文件名。
    4. Cmd

      指令,如"calc"表示计算器,"python"表示执行python(需配置环境变量),"listslave"命令是向server查询现在在线的slave信息;

    5. 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主要负责以下内容:

    1. 维护slaveConnPool,slave的连接信息;
    2. 响应master的listslave请求(获取当前在线的slave端);
    3. 接收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){
    
    }
    

    核心功能

    1. 执行命令

      cmd := exec.Command(Cmd, arg)
      cmd.Start()
      
    1. 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
      
    2. 文件传输

      这主要利用了io.copyN函数:

      func CopyN(dst Writer, src Reader, n int64)
      

      其中,因为net.Conn和os.File都完成了Writer,Reader的接口,因此可以作为其实例。具体步骤思路是:

      1. slave端:CopyN(Conn, file, n int64) ,这里的Conn是slave和server的连接,file就是待传输的文件,n为文件大小,由os.fileinfo可获取;
      2. server端:CopyN(masterConn, Conn, n int64) ,这里的masterConn是server和master的连接,Conn是slave和server的连接;
      3. master端:CopyN(file, masterConn, n int64) ,这里的masterConn是server和master的连接,file就是master接收后的文件。

    实验结果展示

    1. 获取slave
    获取slave
    1. 执行指令(以calc计算器为例)

      我们向127.0.0.1:12296(slave)发送了calc 指令,server端也显示转发这条指令到slave,结果运行起了计算器:


      3-3.png
    执行指令
    1. 写python代码

      我们向slave端写了内容为hello的python脚本,可以看到在slave.exe的文件目录下,生产了python.py,其内容为hello。

    python代码

    相关文章

      网友评论

        本文标题:基于Go语言的远程控制程序

        本文链接:https://www.haomeiwen.com/subject/sjklzqtx.html