Go语言实践Golang 入门资料+笔记Golang

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

2019-05-21  本文已影响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端实现。

消息协议

为了方便解析和统一格式,采用了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代码
上一篇下一篇

猜你喜欢

热点阅读