GoShell之路(一)

2020年5月8日 0 作者 y1nhui

前言

惯例前言,想用go写shell其实是因为看书看到了go的tcp监听,突然就想写个go的shell了,分为客户端和服务端。

这个文章打算写三篇,分别是初阶:基础的远控操作。进阶:反向代理以及一些方便的操作,高阶:免杀。当然如果进阶里面可以方便的东西太少了,可能就会把后两篇合并了吧(:з」∠)

学习的参考资料惯例是官方标准库,如果有其他好的参考资料我会在结尾写上。

初阶

写这个的话,肯定要学标准库的(:з」∠)

os/exec

官方库带的包,用于执行外部命令

先放一串代码吧

 cmd := exec.Command("calc")
 err := cmd.Start()
 if err != nil {
     log.Fatal(err)
 }
 fmt.Println("Wait for cmd ")
 cmd.Wait()//阻塞进程直到该命令执行完成

很明显看出,cmd声明了一个exec.Comand类,并且赋值calc。之后我们通过cmd.start来运行该命令。这里的参数可以直接使用字符串变量。如:

test := "calc"
cmd := exec.Command(test)

那么新的问题出现了,这样的话只是calc这样的无参命令还好,有参数的话就会报错,那我们还需要区分命令和参数。比如这样:

var shell string
shell = "ping www.baidu.com"
args := strings.Split(shell," ")
cmd := exec.Command(args[0],args[1])

然后新的问题出现辣,多个参数的情况下总不能一直写下去吧,交互时的数量可是不可控的。那就用一个可变长参数吧
cmd := exec.Command(args[0],args[1:]...)

给的示例代码中有一个问题,就是运行用的是cmd.Start方法可以执行命令,但是有个问题,他不会等待命令完成就会返回,所以我们会在底下使用Wait,而Wait操作的命令只能是start方法开始执行的。于是我们使用cmd.Run执行包含的命令,并阻塞直到完成.
那么示例代码就可以改改了:

var shell string
shell = "calc"
args := strings.Fields(shell)
cmd := exec.Command(args[0],args[1:]...)
err := cmd.Run()
 if err != nil {
     log.Fatal(err)
 }

接下来再改一改,首先我们想看到回显,加上

cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout

其次,只是cmd运行错误就终止程序是不行的,用panic引发恐慌也不行,那就改成

if err!= nil {
        fmt.Fprintln(os.Stderr,err)
    }

同时到这里会发现只是一个可以调用控制台的exe文件是不会被扫描器抓的,至少火绒没抓,接下来组合一下其他功能看看会不会被抓

一个小点

go的输出是utf-8,而cmd活动页的默认编码是GBK,所以如果是通过编译器(如goland)运行的话会输出乱码,不过直接用cmd使用go run 或者运行go build后的exe就不会输出乱码了(:з」∠)

net

net包的话就是用来建立tcp通信的了(:з」∠)这个一开始感觉麻烦,后来发现其实挺简单的。
先放个代码

listen,err := net.Listen(Network,Address)
if err != nil {
    fmt.Println("error tcp! err:",err)
}
defer listen.Close()

这就是服务端开始代码,这段代码里面network规定协议,如tcp协议,address规定的是服务端ip与端口,如:192.168.1.222:9000。然后放上一个defer listen.Close()语句用于在程序最后的时候关闭tcp。

但是这个只是定义了连接,还没有开始连接。

conn,err :=listen.Accept()
defer conn.Close()
if err != nil{
    fmt.Println("Accept err! err",err)
}

这个,通过Accept方法来正式连接。到了这一步,我们就可以传输与发送数据了。

net包的发送与接受数据有最简单的方法。Write与Read

read:

n,err := conn.Read(buf)
if err != nil {
    fmt.Fprintln(os.Stderr,err)
}

cmdstring := string(buf[0:n])

write:

buf := make([]byte,1024) //write

read :=bufio.NewReader(os.Stdin)
buf,err := read.ReadBytes('\n')
if err!= nil {
    fmt.Fprintln(os.Stderr,err)
}

conn.Write(buf)

无论是write还是read,他们的参数类型都是[]byte。在read中,buf与底下write的代码中的buf是一个类型的,他的作用是缓存传入数据,返回出一个n值(数据字节数),然后我们在接下来就可以通过buf[0:n]来表示传入的全部值。

接着是客户端,客户端读写与服务端一样,不过接听定义用的是
conn,err := net.Dial(Network,Address)
if err != nil {
fmt.Println(“connection err! err:”,err)
}
这样之后就会自动向目标地址发出握手请求,如果在段时间内没有收到回应或者被拒绝会立刻输出报错。

结尾

然后就没啦,其实只是个初阶是很简单的,进阶也知道写什么了,反向代理,客户端一段时间无请求后自动关闭,多线程操作多个shell等。不过感觉高阶可能会咕,因为我发现这个玩意啥都没加写出来后没被360抓,可能是动作太小了?等后面完善完善再看看有没有问题吧。

github项目代码:https://github.com/zzy2210/EasyShell
其实非常简单。。。