During a workshop I had the pleasure to have a look at the popular Mirai Botnet malware. Besides all the historical information we had to gather, there was also room for a small technical analysis on the sample. Said analysis has been copied over to the blog and is presented in the upcoming lines of this page. The analysis is by no means exhaustive and shall only present one approach one might take on analysing the malware.
Many variants of the botnet exist and new ones are still being deployed into the wilderness. Most if not all of them have some similarities in common in regards to their behavior.
The botnet generally scans its environment in which it was deployed. It then terminates any services on the target device which might interfere with the malware and it scans for any open telnet ports. In the case that open telnet ports were found on the device, the malware tries to gain access to the device by brute forcing a set of hardcoded default username and password combinations. If successfull, the malware drops a payload, which is used as a callback to the C2 server, which the attack controls. After successfully gaining access to the device, the malware now awaits orders from the C2 server to for example take part in a DDoS attack, spread to a new victim or to self-destruct itself when needed.
So one possible approach is to search for the string decryption function. Using a reverse engineering tool of your liking (I used Ghidra) we can first have a look at the strings page.
Open the "Window -> Defined Strings" view inside of Ghidra.
.shstrtab
.init
.text
.fini
.rodata
.ctors
.dtors
.data
.bss
ELF
POST /cdn-cgi/
HTTP/1.1
User-Agent:
Host:
Cookie:
/proc/net/tcp
/dev/watchdog
/dev/misc/watchdog
abcdefghijklmnopqrstuvw012345678
RCQQUMPF
QOACFOKL
cFOKLKQVPCVMP
OGKLQO
QGPTKAG
QWRGPTKQMP
EWGQV
CFOKLKQVPCVMP
HT@XF
Q[QVGO
FPGCO@MZ
PGCNVGI
OMVJGP
DWAIGP
enter
assword
TKXZT
CFOKL
ZOJFKRA
FGDCWNV
QWRRMPV
RPMA
"
GZG"
QVCVWQ"
jvvrdnmmf"
nmnlmevdm"
XMNNCPF"
egvnmacnkr"
QJGNN"
GLC@NG"
Q[QVGO"
LAMPPGAV"
AJWLIGF"
/dev/null
We for example have the string "ZOJFKRA" which gets passed to the function `FUN_00010778` as parameter.
00010b70 c0 1d 9f e5 ldr r1=>s_ZOJFKRA_0001618c ,[PTR_s_ZOJFKRA_00011938 ] = "ZOJFKRA"
= 0001618c
00010b74 05 20 a0 e3 mov r2,#0x5
00010b78 fe fe ff eb bl FUN_00010778 undefined FUN_00010778()
By looking at the references of the function we can see, that most if not all encrypted strings are passed by parameter to the following function.
void FUN_00010778(undefined4 param_1,undefined4 param_2,uint param_3)
{
int iVar1;
ushort uVar2;
int iVar3;
int iVar4;
int iVar5;
int iVar6;
iVar3 = FUN_0001439c(DAT_0001ec28,DAT_0001ebfc * 0x10 + 0x10);
iVar1 = DAT_0001ebfc;
DAT_0001ec28 = iVar3;
iVar4 = FUN_00012c90(param_1);
iVar5 = FUN_00014100(iVar4 + 1);
FUN_00012d0c(iVar5,param_1,iVar4 + 1);
if (0 < iVar4) {
iVar6 = 0;
do {
*(byte *)(iVar6 + iVar5) = *(byte *)(iVar6 + iVar5) ^ 0x22;
iVar6 = iVar6 + 1;
} while (iVar4 != iVar6);
}
*(char *)(DAT_0001ebfc * 0x10 + DAT_0001ec28 + 0xc) = (char)iVar4;
*(int *)(iVar3 + iVar1 * 0x10) = iVar5;
iVar3 = DAT_0001ec28;
iVar1 = DAT_0001ebfc;
iVar4 = FUN_00012c90(param_2);
iVar5 = FUN_00014100(iVar4 + 1);
FUN_00012d0c(iVar5,param_2,iVar4 + 1);
if (0 < iVar4) {
iVar6 = 0;
do {
*(byte *)(iVar6 + iVar5) = *(byte *)(iVar6 + iVar5) ^ 0x22;
iVar6 = iVar6 + 1;
} while (iVar4 != iVar6);
}
*(char *)(DAT_0001ebfc * 0x10 + DAT_0001ec28 + 0xd) = (char)iVar4;
uVar2 = _DAT_0001ec2c;
iVar4 = DAT_0001ebfc * 0x10 + DAT_0001ec28;
*(char *)(iVar4 + 9) = (char)(_DAT_0001ec2c >> 8);
*(char *)(iVar4 + 8) = (char)uVar2;
iVar6 = (param_3 & 0xffff) + (uint)_DAT_0001ec2c;
*(char *)(iVar4 + 0xb) = (char)((uint)iVar6 >> 8);
*(char *)(iVar4 + 10) = (char)iVar6;
_DAT_0001ec2c = (short)param_3 + _DAT_0001ec2c;
*(int *)(iVar1 * 0x10 + iVar3 + 4) = iVar5;
DAT_0001ebfc = DAT_0001ebfc + 1;
return;
}
So the part we are most interested in is the string decryption route, which is composed of a simple xor-ing of the strings using the value 0x22.
str_len = calc_string_len(enc_text);
ptr = memory_manipulation(str_len + 1U);
str_copy(ptr,enc_text,str_len + 1U);
if (0 < str_len) {
idx = 0;
do {
*(byte *)(idx + (int)ptr) = *(byte *)(idx + (int)ptr) ^ 0x22;
idx = idx + 1;
} while (str_len != idx);
}
Compared to the leaked source code, this slightly varies. However due to the many families of the Mirai botnet that exist, code parts may vary.
static char *deobf(char *str, int *len)
{
int i;
char *cpy;
*len = util_strlen(str);
cpy = malloc(*len + 1);
util_memcpy(cpy, str, *len + 1);
for (i = 0; i < *len; i++)
{
cpy[i] ^= 0xDE;
cpy[i] ^= 0xAD;
cpy[i] ^= 0xBE;
cpy[i] ^= 0xEF;
}
return cpy;
}
Using python we can write a small decryption script, which allows us to retrieve the strings in their plaintext form.
strings = [
"RCQQUMPF",
"QOACFOKL",
"cFOKLKQVPCVMP",
"OGKLQO",
"QGPTKAG",
"QWRGPTKQMP",
"EWGQV",
"CFOKLKQVPCVMP",
"HT@XF",
"Q[QVGO",
"FPGCO@MZ",
"PGCNVGI",
"OMVJGP",
"DWAIGP",
"TKXZT",
"CFOKL",
"ZOJFKRA",
"FGDCWNV",
"QWRRMPV",
"QVCVWQ",
"jvvrdnmmf",
"nmnlmevdm",
"XMNNCPF",
"egvnmacnkr",
"QJGNN",
"GLC@NG",
"Q[QVGO",
"LAMPPGAV",
"AJWLIGF"
]
def str_xor(text):
key = 0x22
return "".join([chr(ord(c) ^ key) for c in text])
def main():
for i in strings:
decoded_text = str_xor(i)
print(decoded_text)
if __name__ == "__main__":
main()
In the end we get the following list of plaintext strings.
password
smcadmin
Administrator
meinsm
s
ervice
supervisor
guest
administrator
jvbzd
system
dreambox
realtek
mother
fucker
vizxv
admin
xmhdipc
default
support
status
HTTPFLOOD
LOLNOGTFO
zollard
GETLOCALIP
shell
enable
system
ncorrect
chunked
A quick search on the internet will tell us, that some of the strings correspond to default credentials for IoT devices in e.g. https://ipcamtalk.com/threads/escam-rt-ipc-telnet-login.6253/.
This is just one aspect that can be analyzed from the malware. The malware is of course more complex and can be analyzed further if one so wishes. Thus if I get the chance in the future I will update this page with more information.