Skip to content

Commit

Permalink
Merge pull request #3 from carbonblack/redxor
Browse files Browse the repository at this point in the history
RedXOR support
  • Loading branch information
knightsc authored Apr 2, 2021
2 parents c87fc37 + c09b7d5 commit 7ac16ee
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ rather than trying to route out over the internet.
* [HOTCROISSANT](docs/hotcroissant.md)
* [MATA](docs/mata.md)
* [ObliqueRAT](docs/obliquerat.md)
* [RedXOR](docs/redxor.md)
* [Rifdoor](docs/rifdoor.md)
* [SLICKSHOES](docs/slickshoes.md)
* [Yort](docs/yort.md)
22 changes: 22 additions & 0 deletions docs/redxor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# RedXOR

RedXOR is a backdoor targetting Linux systems. It masquerades as the [polkitd](https://linux.die.net/man/8/polkitd) daemon. Based on victims as well as Tactics, Techniques, and Procedures (TTPs), it is believed to be attributed to a high profile Chinese threat actor. It makes use of a network communication protocol that looks like normal HTTP traffic but has XOR encoded payloads in the content body.

## Network Setup

The `0423258b94e8a9af58ad63ea493818618de2d8c60cf75ec7980edcaa34dcc919` sample makes use of a DNS name of `update.cloudjscdn.com` for it's C2 communication. Simply modify the `hosts` file on the machine where the malware is running and point it to the IP address of your MockC2 server.

## Links

* [https://www.intezer.com/blog/malware-analysis/new-linux-backdoor-redxor-likely-operated-by-chinese-nation-state-actor/](https://www.intezer.com/blog/malware-analysis/new-linux-backdoor-redxor-likely-operated-by-chinese-nation-state-actor/)

## IOCs

| Indicator | Type | Context |
|------------------------------------------------------------------|----------|-------------------|
| 0423258b94e8a9af58ad63ea493818618de2d8c60cf75ec7980edcaa34dcc919 | SHA256 | RedXOR 64-bit ELF |
| 0a76c55fa88d4c134012a5136c09fb938b4be88a382f88bf2804043253b0559f | SHA256 | RedXOR 64-bit ELF |
| 0423258b94e8a9af58ad63ea493818618de2d8c60cf75ec7980edcaa34dcc919 | SHA256 | RedXOR 64-bit ELF |
| update.cloudjscdn.com | TCP/8080 | RedXOR C2 |
| 158.247.208.230 | TCP/8080 | RedXOR C2 |
| www.centosupdateonline.com | TCP/8080 | RedXOR C2 |
1 change: 1 addition & 0 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func (s *Shell) initCompleters() {
readline.PcItem("hotcroissant"),
readline.PcItem("mata"),
readline.PcItem("obliquerat"),
readline.PcItem("redxor"),
readline.PcItem("rifdoor"),
readline.PcItem("slickshoes"),
readline.PcItem("yort"),
Expand Down
3 changes: 3 additions & 0 deletions pkg/c2/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/carbonblack/mockc2/pkg/protocol/hotcroissant"
"github.com/carbonblack/mockc2/pkg/protocol/mata"
"github.com/carbonblack/mockc2/pkg/protocol/obliquerat"
"github.com/carbonblack/mockc2/pkg/protocol/redxor"
"github.com/carbonblack/mockc2/pkg/protocol/rifdoor"
"github.com/carbonblack/mockc2/pkg/protocol/slickshoes"
"github.com/carbonblack/mockc2/pkg/protocol/yort"
Expand Down Expand Up @@ -51,6 +52,8 @@ func handlerFromString(protocol string) (protocol.Handler, error) {
return &mata.Handler{}, nil
case "obliquerat":
return &obliquerat.Handler{}, nil
case "redxor":
return &redxor.Handler{}, nil
case "rifdoor":
return &rifdoor.Handler{}, nil
case "slickshoes":
Expand Down
252 changes: 252 additions & 0 deletions pkg/protocol/redxor/redxor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
package redxor

import (
"bufio"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"strings"
"strconv"

"github.com/carbonblack/mockc2/internal/log"
"github.com/carbonblack/mockc2/pkg/protocol"
)

const (
opHostInfo = "0000"
opHostInfoResp = "0001"
opDownload = "2054"
opUploadStart = "2055"
opUploadData = "2066"
opDownloadDone = "2088"
opShellStart = "3000"
opShellExec = "3058"
opShellStop = "3999"
)

// Handler is a RedXOR protocol handler.
type Handler struct {
delegate protocol.Delegate
pr *io.PipeReader
pw *io.PipeWriter
b *bufio.Reader
shellStarted bool
source string
destination string
file *os.File
fileSize int64
}

// SetDelegate saves the delegate for later use.
func (h *Handler) SetDelegate(delegate protocol.Delegate) {
h.delegate = delegate
}

// Accept gives the Handler a chance to do something as soon as an agent
// connects.
func (h *Handler) Accept() {
}

// ReceiveData just logs information about data received.
func (h *Handler) ReceiveData(data []byte) {
log.Debug("received\n" + hex.Dump(data))

if h.b == nil {
h.pr, h.pw = io.Pipe()
h.b = bufio.NewReader(h.pr)
go h.processData()
}

h.pw.Write(data)
}

func (h *Handler) processData() {
for {
req, err := http.ReadRequest(h.b)
if err != nil && err != io.EOF {
log.Warn("redxor error reading request: %v", err)
}

if err == io.EOF {
break
}

if req != nil {
contentLength := req.Header.Get("Content-Length")
totalLength := req.Header.Get("Total-Length")

cookie, err := req.Cookie("JSESSIONID")
if err != nil {
h.delegate.CloseConnection()
return
}

log.Debug("JSESSIONID: %s\nContent-Length: %s\nTotal-Length: %s", cookie.Value, contentLength, totalLength)

body, err := ioutil.ReadAll(req.Body)
if err != nil {
h.delegate.CloseConnection()
return
}

key, err := strconv.Atoi(contentLength)
if err != nil {
h.delegate.CloseConnection()
return
}

adder, err := strconv.Atoi(totalLength)
if err != nil {
h.delegate.CloseConnection()
return
}

body = cipher(body, uint8(key), uint8(adder))

log.Debug("body\n" + hex.Dump(body))

switch cookie.Value {
case opHostInfo:
h.sendCommand(opHostInfo, 9, 9, []byte("all right"))
case opHostInfoResp:
hash := sha256.Sum256(body)
id := hex.EncodeToString(hash[:])
h.delegate.AgentConnected(id)
case opShellExec:
log.Info(string(body))
case opDownload:
h.file.Write(body)
case opDownloadDone:
if h.file != nil {
h.file.Close()
}

h.file = nil
h.fileSize = 0
h.source = ""
h.destination = ""

log.Success("Download complete")
case opUploadStart:
buf := make([]byte, 0x1000)
for {
bytesRead, err := h.file.Read(buf)

if err != nil {
if err != io.EOF {
log.Warn("Error reading source file; %v", err)
}
break
}

h.sendCommand(opUploadData, bytesRead, int(h.fileSize), buf[:bytesRead])
}

if h.file != nil {
h.file.Close()
}

h.file = nil
h.fileSize = 0
h.source = ""
h.destination = ""

log.Success("Upload complete")
}
}
}
}

// Execute runs a command on the connected agent.
func (h *Handler) Execute(name string, args []string) {
if !h.shellStarted {
h.sendCommand(opShellStart, 0, 0, []byte{})
h.shellStarted = true
}

commandLine := strings.TrimSpace(name + " " + strings.Join(args, " "))
h.sendCommand(opShellExec, len(commandLine), len(commandLine), []byte(commandLine))
}

// Upload sends a file to the connected agent.
func (h *Handler) Upload(source string, destination string) {
file, err := os.Open(source)
if err != nil {
log.Warn("Error opening source file: %v", err)
return
}

fi, err := file.Stat()
if err != nil {
log.Warn("Error opening source file: %v", err)
return
}

h.file = file
h.fileSize = fi.Size()
h.source = source
h.destination = destination

data := []byte(destination+"#0")
h.sendCommand(opUploadStart, len(data), len(data), data)
}

// Download retrieves a file from the connected agent.
func (h *Handler) Download(source string, destination string) {
file, err := os.Create(destination)
if err != nil {
log.Warn("Error opening destination file: %v", err)
return
}

h.file = file
h.source = source
h.destination = destination

data := []byte(source+"#0")
h.sendCommand(opDownload, len(data), len(data), data)
}

// Close cleans up any uzed resources
func (h *Handler) Close() {
h.sendCommand(opShellStop, 0, 0, []byte{})
h.shellStarted = false
h.pw.Close()
}

// NeedsTLS returns whether the protocol runs over TLS or not.
func (h *Handler) NeedsTLS() bool {
return false
}

func (h *Handler) sendCommand(opcode string, contentLength int, totalLength int, data []byte) {
encrypted := cipher(data, uint8(contentLength), uint8(totalLength))

headerFormat :=
"HTTP/1.1 200 OK\r\n" +
"Set-Cookie: JSESSIONID=%s\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: %010d\r\n" +
"Total-Length: %010d\r\n" +
"\r\n"

header := fmt.Sprintf(headerFormat, opcode, contentLength, totalLength)

h.delegate.SendData([]byte(header))
h.delegate.SendData(encrypted)
}

func cipher(input []byte, key uint8, adder uint8) []byte {
output := make([]byte, len(input))

for i := 0; i < len(input); i++ {
output[i] = input[i] ^ key
key += adder
}

return output
}

0 comments on commit 7ac16ee

Please sign in to comment.