RSS Feed
更好更安全的互联网
  • 使用 IDA 处理 U-Boot 二进制流文件

    2019-12-04

    作者:Hcamael@知道创宇404实验室
    时间:2019年11月29日

    最近在研究IoT设备的过程中遇到一种情况。一个IoT设备,官方不提供固件包,网上也搜不到相关的固件包,所以我从flash中直接读取。因为系统是VxWorks,能看到flash布局,所以能很容易把uboot/firmware从flash中分解出来。对于firmware的部分前一半左右是通过lzma压缩,后面的一半,是相隔一定的区间有一部分有lzma压缩数据。而固件的符号信息就在这后半部分。因为不知道后半部分是通过什么格式和前半部分代码段一起放入内存的,所以对于我逆向产生了一定的阻碍。所以我就想着看看uboot的逻辑,但是uboot不能直接丢入ida中进行分析,所以有了这篇文章,分析uboot格式,如何使用ida分析uboot。

    uboot格式

    正常的一个uboot格式应该如下所示:

    而这uboot其实还得分为三部分:

    1.从0x00 - 0x346C是属于bootstrap的部分 
    2.0x346C-0x34AC有0x40字节的uboot image的头部信息 
    3.从0x34AC到结尾才是uboot image的主体,经过lzma压缩后的结果

    那么uboot是怎么生成的呢?Github上随便找了一个uboot源码: https://github.com/OnionIoT/uboot,编译安装了一下,查看uboot的生成过程。

    1.第一步,把bootstrap和uboot源码使用gcc编译成两个ELF程序,得到bootstrapuboot 
    2.第二步,使用objcopy把两个文件分别转换成二进制流文件。

    3.把u-boot.bin使用lzma算法压缩,得到u-boot.bin.lzma

    4.使用mkimage,给u-boot.bin.lzma加上0x40字节的头部信息得到u-boot.lzming

    5.最后把bootstrap.binu-boot.lzming合并到一起,然后根据需要uboot的实际大小,比如需要一个128k的uboot,在末尾使用0xff补齐到128k大小

    使用ida处理bootstrap二进制流文件

    在上面的结构中,需要注意几点:

    1.Data Address: 0x80010000, Entry Point: 0x80010000 表示设备启动后,会把后续uboot通过lzma解压出来的数据存入内存地址0x80010000,然后把$pc设置为: 0x80010000,所以uboot最开头4字节肯定是指令。

    2.uncompressed size: 161184 bytes,可以使用dd把LZMA数据单独取出来,然后使用lzma解压缩,解压缩后的大小要跟这个字段一样。如果还想确认解压缩的结果有没有问题,可以使用CRC算法验证。

    接下来就是通过dd或者其他程序把二进制流从uboot中分离出来,再丢到ida中。先来看看bootstrap,首先指定相应的CPU类型,比如对于上例,则需要设置MIPS大端。

    uboot01

    随后我们暂时设置一下起始地址为0x80010000,通电以后CPU第一个执行的地址默认情况下我们是不知道的,不同CPU有不同的起始地址。设置如下图所示:

    uboot02

    bootstrap最开头也指令,所以按C转换成指令,如下图所示:

    uboot03

    跳转到0x80010400, 随后是一段初始化代码,下一步我们需要确定程序基地址,因为是mips,所以我们可以根据$gp来判断基地址。

    uboot04

    如上图所示,因为bootstrap的大小为0x3a3c bytes,所以可以初步估计基地址为0x9f000000,所以下面修改一下基地址:

    uboot05

    并且修改在Options -> General -> Analysis -> Processor specific ......设置$gp=0x9F0039A0

    uboot06

    0x9F0039A0地址开始属于got表的范围,存储的是函数地址,所以把0x9F0039A0地址往后的数据都转成word:

    uboot07

    到此就处理完毕了,后面就是存逆向的工作了,具体bootstrap代码都做了什么,不是本文的重点,所以暂不管。

    使用ida处理uboot流文件

    处理bootstrap,我们再看看uboot,和上面的处理思路大致相同。

    1.使用dd或其他程序,把uboot数据先分离出来。 2.使用lzma解压缩 3.丢到ida,设置CPU类型,设置基地址,因为uboot头部有明确定义基地址为0x80010000,所以不用再自己判断基地址 4.同样把第一句设置为指令

    uboot08

    正常情况下,uboot都是这种格式,0x80010008为got表指针,也是$gp的值。

    5.根据0x80010008的值,去设置$gp 6.处理got表,该地址往后基本都是函数指针和少部分的字符串指针。结尾还有uboot命令的结构体。

    到此uboot也算基础处理完了,后续也都是逆向的工作了,也不是本文的关注的内容。

    编写idapython自动处理uboot

    拿uboot的处理流程进行举例,使用Python编写一个ida插件,自动处理uboot二进制流文件。

    1.我们把0x80010000设置为__start函数

    2.0x80010008是got表指针,因为我们处理了0x80010000,所以got表指针地址也被自动翻译成了代码,我们需要改成word格式。

    3.把got表都转成Word格式,如果是字符串指针,在注释中体现出来

    基本都这里就ok了,后面还可以加一些.text段信息,但不是必要的,最后的源码如下:


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1090/

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • 从 0 开始入门 Chrome Ext 安全(一) — 了解一个 Chrome Ext

    2019-11-29

    作者:LoRexxar'@知道创宇404实验室
    时间:2019年11月21日

    在2019年初,微软正式选择了Chromium作为默认浏览器,并放弃edge的发展。并在19年4月8日,Edge正式放出了基于Chromium开发的Edge Dev浏览器,并提供了兼容Chrome Ext的配套插件管理。再加上国内的大小国产浏览器大多都是基于Chromium开发的,Chrome的插件体系越来越影响着广大的人群。

    在这种背景下,Chrome Ext的安全问题也应该受到应有的关注,《从0开始入门Chrome Ext安全》就会从最基础的插件开发开始,逐步研究插件本身的恶意安全问题,恶意网页如何利用插件漏洞攻击浏览器等各种视角下的安全问题。

    第一部分我们就主要来聊聊关于Chrome Ext的一些基础。

    获取一个插件的代码

    Chrome Ext的存在模式类似于在浏览器层新加了一层解释器,在我们访问网页的时候,插件会加载相应的html、js、css,并解释执行。

    所以Chrome Ext的代码也就是html、js、css这类,那我们如何获取插件的代码呢?

    当我们访问扩展程序的页面可以获得相应的插件id

    然后我们可以在https://chrome-extension-downloader.com/中下载相应的crx包。

    把crx改名成zip之后解压缩就可以了

    manifest.json

    在插件的代码中,有一个重要的文件是manifest.json,在manifest.json中包含了整个插件的各种配置,在配置文件中,我们可以找到一个插件最重要的部分。

    首先是比较重要的几个字段

    • browser_action
      • 这个字段主要负责扩展图标点击后的弹出内容,一般为popup.html
    • content_scripts
      • matches 代表scripts插入的时机,默认为document_idle,代表页面空闲时
      • js 代表插入的scripts文件路径
      • run_at 定义了哪些页面需要插入scripts
    • permissions
      • 这个字段定义了插件的权限,其中包括从浏览器tab、历史纪录、cookie、页面数据等多个维度的权限定义
    • content_security_policy
      • 这个字段定义了插件页面的CSP
      • 但这个字段不影响content_scripts里的脚本
    • background
      • 这个字段定义插件的后台页面,这个页面在默认设置下是在后台持续运行的,只随浏览器的开启和关闭
      • persistent 定义了后台页面对应的路径
      • page 定义了后台的html页面
      • scripts 当值为false时,background的页面不会在后台一直运行

    在开始Chrome插件的研究之前,除了manifest.json的配置以外,我们还需要了解一下围绕chrome建立的插件结构。

    Chrome Ext的主要展现方式

    browserAction - 浏览器右上角

    浏览器的右上角点击触发的就是mainfest.json中的browser_action

    其中页面内容来自popup.html

    pageAction

    pageAction和browserAction类似,只不过其中的区别是,pageAction是在满足一定的条件下才会触发的插件,在不触发的情况下会始终保持灰色。

    contextMenus 右键菜单

    通过在chrome中调用chrome.contextMenus这个API,我们可以定义在浏览器中的右键菜单。

    当然,要控制这个api首先你必须申请控制contextMenus的权限。

    一般来说,这个api会在background中被定义,因为background会一直在后台加载。

    https://developer.chrome.com/extensions/contextMenus

    override - 覆盖页面

    chrome提供了override用来覆盖chrome的一些特定页面。其中包括历史记录、新标签页、书签等...

    比如Toby for Chrome就是一个覆盖新标签页的插件

    devtools - 开发者工具

    chrome允许插件重构开发者工具,并且相应的操作。

    插件中关于devtools的生命周期和F12打开的窗口时一致的,当F12关闭时,插件也会自动结束。

    而在devtools页面中,插件有权访问一组特殊的API,这组API只有devtools页面中可以访问。

    https://developer.chrome.com/extensions/devtools

    option - 选项

    option代表着插件的设置页面,当选中图标之后右键选项可以进入这个页面。

    omnibox - 搜索建议

    在chrome中,如果你在地址栏输入非url时,会将内容自动传到google搜索上。

    omnibox就是提供了对于这个功能的魔改,我们可以通过设置关键字触发插件,然后就可以在插件的帮助下完成搜索了。

    这个功能通过chrome.omnibox这个api来定义。

    notifications - 提醒

    notifications代表右下角弹出的提示框

    权限体系和api

    在了解了各类型的插件的形式之后,还有一个比较重要的就是Chrome插件相关的权限体系和api。

    Chrome发展到这个时代,其相关的权限体系划分已经算是非常细致了,具体的细节可以翻阅文档。

    https://developer.chrome.com/extensions/declare_permissions

    抛开Chrome插件的多种表现形式不谈,插件的功能主要集中在js的代码里,而js的部分主要可以划分为5种injected script、content-script、popup js、background js和devtools js.

    • injected script 是直接插入到页面中的js,和普通的js一致,不能访问任何扩展API.
    • content-script 只能访问extension、runtime等几个有限的API,也可以访问dom.
    • popup js 可以访问大部分API,除了devtools,支持跨域访问
    • background js 可以访问大部分API,除了devtools,支持跨域访问
    • devtools js 只能访问devtools、extension、runtime等部分API,可以访问dom
    JS是否能访问DOM是否能访问JS是否可以跨域
    injected script可以访问可以访问不可以
    content script可以访问不可以不可以
    popup js不可直接访问不可以可以
    background js不可直接访问不可以可以
    devtools js可以访问可以访问不可以

    同样的,针对这多种js,我们也需要特殊的方式进行调试

    • injected script: 直接F12就可以调试
    • content-script:在F12中console选择相应的域
    • popup js: 在插件右键的列表中有审查弹出内容
    • background js: 需要在插件管理页面点击背景页然后调试

    通信方式

    在前面介绍过各类js之后,我们提到一个重要的问题就是,在大部分的js中,都没有给与访问js的权限,包括其中比较关键的content script.

    那么插件怎么和浏览器前台以及相互之间进行通信呢?

    -injected-scriptcontent-scriptpopup-jsbackground-js
    injected-script-window.postMessage--
    content-scriptwindow.postMessage-chrome.runtime.sendMessage chrome.runtime.connectchrome.runtime.sendMessage chrome.runtime.connect
    popup-js-chrome.tabs.sendMessage chrome.tabs.connect-chrome.extension. getBackgroundPage()
    background-js-chrome.tabs.sendMessage chrome.tabs.connectchrome.extension.getViews-
    devtools-jschrome.devtools.inspectedWindow.eval-chrome.runtime.sendMessagechrome.runtime.sendMessage

    popup和background两个域互相直接可以调用js并且访问页面的dom。

    popup可以直接用chrome.extension.getBackgroundPage()获取background页面的对象,而background可以直接用chrome.extension.getViews({type:'popup'})获取popup页面的对象。

    popup\background 和 content js

    popup\background 和 content js之间沟通的方式主要依赖chrome.tabs.sendMessagechrome.runtime.onMessage.addListener这种有关事件监听的交流方式。

    发送方使用chrome.tabs.sendMessage,接收方使用chrome.runtime.onMessage.addListener监听事件。

    接收方

    injected script 和 content-script

    由于injected script就相当于页面内执行的js,所以它没权限访问chrome对象,所以他们直接的沟通方式主要是利用window.postMessage或者通过DOM事件来实现。

    injected-script中:

    content script中:

    popup\background 动态注入js

    popup\background没办法直接访问页面DOM,但是可以通过chrome.tabs.executeScript来执行脚本,从而实现对页面DOM的操作。

    要注意这种操作要求必须有页面权限

    js

    chrome.storage

    chrome 插件还有专门的储存位置,其中包括chrome.storage和chrome.storage.sync两种,其中的区别是:

    • chrome.storage 针对插件全局,在插件各个位置保存的数据都会同步。
    • chrome.storage.sync 根据账户自动同步,不同的电脑登陆同一个账户都会同步。

    插件想访问这个api需要提前声明storage权限。

    总结

    这篇文章主要描述了关于Chrome ext插件相关的许多入门知识,在谈及Chrome ext的安全问题之前,我们可能需要先了解一些关于Chrome ext开发的问题。

    在下一篇文章中,我们将会围绕Chrome ext多个维度的安全问题进行探讨,在现代浏览器体系中,Chrome ext到底可能会带来什么样的安全问题。

    re


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1082/

    作者:吴烦恼 | Categories:安全研究技术分享 | Tags:
  • 代码分析引擎 CodeQL 初体验

    2019-11-19

    作者:w7ay@知道创宇404实验室
    日期:2019年11月18日

    QL是一种查询语言,支持对C++,C#,Java,JavaScript,Python,go等多种语言进行分析,可用于分析代码,查找代码中控制流等信息。

    之前笔者有简单的研究通过JavaScript语义分析来查找XSS,所以对于这款引擎有浓厚的研究兴趣 。

    安装

    1.下载分析程序:https://github.com/github/codeql-cli-binaries/releases/latest/download/codeql.zip

    分析程序支持主流的操作系统,Windows,Mac,Linux

    2.下载相关库文件:https://github.com/Semmle/ql

    库文件是开源的,我们要做的是根据这些库文件来编写QL脚本。

    3.下载最新版的VScode,安装CodeQL扩展程序:https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-codeql

    • 用vscode的扩展可以方便我们看代码
    • 然后到扩展中心配置相关参数
    image-20191116223514188
    image-20191116223649659

    4.

    • cli填写下载的分析程序路径就行了,windows可以填写codeql.cmd
    • 其他地方默认就行

    建立数据库

    以JavaScript为例,建立分析数据库,建立数据库其实就是用分析程序来分析源码。到要分析源码的根目录,执行codeql database create jstest --language=javascript

    image-20191117111305487

    接下来会在该目录下生成一个jstest的文件夹,就是数据库的文件夹了。

    接着用vscode打开之前下载的ql库文件,在ql选择夹中添加刚才的数据库文件,并设置为当前数据库。

    image-20191117111940680

    接着在QL/javascript/ql/src目录下新建一个test.ql,用来编写我们的ql脚本。为什么要在这个目录下建立文件呢,因为在其他地方测试的时候import javascript导入不进来,在这个目录下,有个javascript.qll就是基础类库,就可以直接引入import javascript,当然可能也有其他的方法。

    看它的库文件,它基本把JavaScript中用到的库,或者其他语言的定义语法都支持了。

    image-20191117113240934

    输出一段hello world试试?

    image-20191118130324959

    语义分析查找的原理

    刚开始接触ql语法的时候可能会感到它的语法有些奇怪,它为什么要这样设计?我先说说自己之前研究基于JavaScript语义分析查找dom-xss是怎样做的。

    首先一段类似这样的javascript代码

    常规的思路是,我们先找到document.write函数,由这个函数的第一个参数回溯寻找,如果发现它最后是location.hash.split("#")[1];,就寻找成功了。我们可以称document.writesink,称location.hash.splitsource。基于语义分析就是由sink找到source的过程(当然反过来找也是可以的)。

    而基于这个目标,就需要我们设计一款理解代码上下文的工具,传统的正则搜索已经无法完成了。

    第一步要将JavaScript的代码转换为语法树,通过pyjsparser可以进行转换

    最终就得到了如下一个树结构

    image-20191118131714042

    这些树结构的一些定义可以参考:https://esprima.readthedocs.io/en/3.1/syntax-tree-format.html

    大概意思可以这样理解:变量param是一个Identifier类型,它的初始化定义的是一个MemberExpression表达式,该表达式其实也是一个CallExpression表达式,CallExpression表达式的参数是一个Literal类型,而它具体的定义又是一个MemberExpression表达式。

    第二步,我们需要设计一个递归来找到每个表达式,每一个Identifier,每个Literal类型等等。我们要将之前的document.write转换为语法树的形式

    location.hash也是同理

    在找到了这些sinksource后,再进行正向或反向的回溯分析。回溯分析也会遇到不少问题,如何处理对象的传递,参数的传递等等很多问题。之前也基于这些设计写了一个在线基于语义分析的demo

    QL语法

    QL语法虽然隐藏了语法树的细节,但其实它提供了很多类似,函数的概念来帮助我们查找相关'语法'。

    依旧是这段代码为例子

    上文我们已经建立好了查询的数据库,现在我们分别来看如何查找sink,source,以及怎样将它们关联起来。

    我也是看它的文档:https://help.semmle.com/QL/learn-ql/javascript/introduce-libraries-js.html 学习的,它提供了很多方便的函数,我没有仔细看。我的查询语句都是基于语法树的查询思想,可能官方已经给出了更好的查询方式,所以看看就行了,反正也能用。

    查询 document.write

    这段语句的意思是查找document.write,并输出它的第一个参数

    image-20191118134431944

    查找 location.hash.split

    查找location.hash.split并输出

    image-20191118134554200

    数据流分析

    接着从sink来找到source,将上面语句组合下,按照官方的文档来就行

    image-20191118134945286

    将source和sink输出,就能找到它们具体的定义。

    我们找到查询到的样本

    image-20191118135549113

    可以发现它的回溯是会根据变量,函数的返回值一起走的。

    当然从source到sink也不可能是一马平川的,中间肯定也会有阻挡的条件,ql官方有给出解决方案。总之就是要求我们更加细化完善ql查询代码。

    接下来放出几个查询还不精确的样本,大家可以自己尝试如何进行查询变得精确。

    最后

    CodeQL将语法树抽离出来,提供了一种用代码查询代码的方案,更增强了基于数据分析的灵活度。唯一的遗憾是它并没有提供很多查询漏洞的规则,它让我们自己写。这也不由得让我想起另一款强大的基于语义的代码审计工具fortify,它的规则库是公开的,将这两者结合一下说不定会有不一样的火花。

    Github公告说将用它来搜索开源项目中的问题,而作为安全研究员的我们来说,也可以用它来做类似的事情?


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1078/

    作者:吴烦恼 | Categories:技术分享 | Tags:
  • WebLogic EJBTaglibDescriptor XXE漏洞(CVE-2019-2888)分析

    2019-10-30

    作者:Longofo@知道创宇404实验室
    时间:2019年10月16日

    这个漏洞和之前@Matthias Kaiser提交的几个XXE漏洞是类似的,而EJBTaglibDescriptor应该是漏掉的一个,可以参考之前几个XXE的分析。我和@Badcode师傅反编译了WebLogic所有的Jar包,根据之前几个XXE漏洞的特征进行了搜索匹配到了这个EJBTaglibDescriptor类,这个类在反序列化时也会进行XML解析。

    Oracle发布了10月份的补丁,详情见链接(https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html)

    环境

    • Windows 10
    • WebLogic 10.3.6.0.190716(安装了19年7月补丁)
    • Jdk160_29(WebLogic 自带的JDK)

    漏洞分析

    weblogic.jar!\weblogic\servlet\ejb2jsp\dd\EJBTaglibDescriptor.class这个类继承自java\io\Externalizable

    因此在序列化与反序列化时会自动调用子类重写的writeExternalreadExternal

    看下writeExternal的逻辑与readExternal的逻辑,

    readExternal中,使用ObjectIutput.readUTF读取反序列化数据中的String数据,然后调用了load方法,

    在load方法中,使用DocumentBuilder.parse解析了反序列化中传递的XML数据,因此这里是可能存在XXE漏洞的

    writeExternal中,调用了本身的toString方法,在其中又调用了自身的toXML方法

    toXML的作用应该是将this.beans转换为对应的xml数据。看起来要构造payload稍微有点麻烦,但是序列化操作是攻击者可控制的,所以我们可以直接修改writeExternal的逻辑来生成恶意的序列化数据:

    漏洞复现

    1.重写 EJBTaglibDescriptor中的writeExternal函数,生成payload

    2.发送payload到服务器

    在我们的HTTP服务器和FTP服务器接收到了my.dtd的请求与win.ini的数据

    3.在打了7月份最新补丁的服务器上能看到报错信息


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1067/

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • PHP-fpm 远程代码执行漏洞(CVE-2019-11043)分析

    2019-10-28

    作者:LoRexxar'@知道创宇404实验室 
    时间:2019年10月25日

    国外安全研究员 Andrew Danau在解决一道 CTF 题目时发现,向目标服务器 URL 发送 %0a 符号时,服务返回异常,疑似存在漏洞。

    2019年10月23日,github公开漏洞相关的详情以及exp。当nginx配置不当时,会导致php-fpm远程任意代码执行。

    下面我们就来一点点看看漏洞的详细分析,文章中漏洞分析部分感谢团队小伙伴@Hcamael#知道创宇404实验室

    漏洞复现

    为了能更方便的复现漏洞,这里我们采用vulhub来构建漏洞环境。

    git pulldocker-compose up -d

    访问http://{your_ip}:8080/

    下载github上公开的exp(需要go环境)。

    然后编译

    使用exp攻击demo网站

    攻击成功

    漏洞分析

    在分析漏洞原理之前,我们这里可以直接跟入看修复的commit

    -https://github.com/php/php-src/commit/ab061f95ca966731b1c84cf5b7b20155c0a1c06a#diff-624bdd47ab6847d777e15327976a9227

    从commit中我们可以很清晰的看出来漏洞成因应该是path_info的地址可控导致的,再结合漏洞发现者公开的漏洞信息中提到

    也就是说,当path_info被%0a截断时,path_info将被置为空,回到代码中我就不难发现问题所在了。

    其中env_path_info就是变量path_info的地址,path_info为0则plien为0.

    slen变量来自于请求后url的长度

    其中

    这两个变量的差就是后面的路径长度,由于路径可控,则path_info可控。

    由于path_info可控,在1222行我们就可以将指定地址的值置零,根据漏洞发现者的描述,通过将指定的地址的值置零,可以控制使_fcgi_data_seg结构体的char* pos置零。

    其中script_name同样来自于请求的配置

    而为什么我们使_fcgi_data_seg结构体的char* pos置零,就会影响到FCGI_PUTENV的结果呢?

    这里我们深入去看FCGI_PUTENV的定义.

    跟入函数fcgi_quick_putenv

    https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L1703

    函数直接操作request的env,而这个参数在前面被预定义。

    https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L908

    继续跟进初始化函数fcgi_hash_init.

    https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L254

    也就是说request->env就是前面提到的fcgi_data_seg结构体,而这里的request->env是nginx在和fastcgi通信时储存的全局变量。

    部分全局变量会在nginx的配置中定义

    其中变量会在堆上相应的位置储存

    回到利用过程中,这里我们通过控制path_info指向request->env来使request->env->pos置零。

    继续回到赋值函数fcgi_hash_set函数

    紧接着进入fcgi_hash_strndup

    这里h->data-》pos的最低位被置为0,且str可控,就相当于我们可以在前面写入数据。

    而问题就在于,我们怎么能向我们想要的位置写数据呢?又怎么向我们指定的配置写文件呢?

    这里我们拿exp发送的利用数据包做例子

    在数据包中,header中的最后两部分就是为了完成这部分功能,其中D-Gisos负责位移,向指定的位置写入数据。

    Ebut会转化为HTTP_EBUT这个fastcgi_param中的其中一个全局变量,然后我们需要了解一下fastcgi中全局变量的获取数据的方法。

    https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L328

    可以看到当fastcgi想要获取全局变量时,会读取指定位置的长度字符做对比,然后读取一个字符串作为value.

    也就是说,只要位置合理,var值相同,且长度相同,fastcgi就会读取相对应的数据

    HTTP_EBUTPHP_VALUE恰好长度相同,我们可以从堆上数据的变化来印证这一点。

    在覆盖之前,该地址对应数据为

    然后执行fcgi_quick_putenv

    该地址对应数据变为

    我们成功写入了PHP_VALUE并控制其内容,这也就意味着我们可以控制PHP的任意全局变量。

    当我们可以控制PHP的任意全局变量就有很多种攻击方式,这里直接以EXP中使用到的攻击方式来举例子。

    exp作者通过开启自动包含,并设置包含目录为/tmp,之后设置log地址为/tmp/a并将payload写入log文件,通过auto_prepend_file自动包含/tmp/a文件构造后门文件。

    漏洞修复

    在经过对漏洞的深入研究后,我们推荐两种方案修复这个漏洞。

    • 临时修复:

    修改nginx相应的配置,并在php相关的配置中加入

    在这种情况下,会有nginx去检查文件是否存在,当文件不存在时,请求都不会被传递到php-fpm。

    漏洞影响

    结合EXP github中提到的利用条件,我们可以尽可能的总结利用条件以及漏洞影响范围。

    1、Nginx + php_fpm,且配置location ~ [^/]\.php(/|$)会将请求转发到php-fpm。
    2、Nginx配置fastcgi_split_path_info并且以^开始以$,只有在这种条件下才可以通过换行符来打断正则表达式判断。 ps: 则允许index.php/321 -> index.php

    3、fastcgi_paramPATH_INFO会被定义通过fastcgi_param PATH_INFO $fastcgi_path_info;,当然这个变量会在fastcgi_params默认定义。
    4、在nginx层面没有定义对文件的检查比如try_files $uri =404,如果nginx层面做了文件检查,则请求不会被转发给php-fmp。

    这个漏洞在实际研究过程中对真实世界危害有限,其主要原因都在于大部分的nginx配置中都携带了对文件的检查,且默认的nginx配置不包含这个问题。

    但也正是由于这个原因,在许多网上的范例代码或者部分没有考虑到这个问题的环境,例如Nginx官方文档中的范例配置、NextCloud默认环境,都出现了这个问题,该漏洞也正真实的威胁着许多服务器的安全。

    在这种情况下,这个漏洞也切切实实的陷入了黑暗森林法则,一旦有某个带有问题的配置被传播,其导致的可能就是大批量的服务受到牵连,确保及时的更新永远是对保护最好的手段:>

    参考链接


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1063/

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • 硬件学习之通过树莓派操控 jtag

    2019-10-28

    作者:Hcamael@知道创宇404实验室
    时间:2019年10月21日

    最近在搞路由器的时候,不小心把CFE给刷挂了,然后发现能通过jtag进行救砖,所以就对jtag进行了一波研究。

    最开始只是想救砖,并没有想深入研究的想法。

    救砖尝试

    变砖的路由器型号为:LinkSys wrt54g v8

    CPU 型号为:BCM5354

    Flash型号为:K8D6316UBM

    首先通过jtagulator得到了设备上jtag接口的顺序。

    正好公司有一个jlink,但是参试了一波失败,识别不了设备。

    随后通过Google搜到发现了一个工具叫: tjtag-pi

    可以通树莓派来控制jtag,随后学习了一波树莓派的操作。

    树莓派Pins

    我使用的是rpi3,其接口编号图如下:

    或者在树莓派3中可以使用gpio readall查看各个接口的状态:

    rpi3中的Python有一个RPi.GPIO模块,可以控制这些接口。

    举个例子:

    首先是需要进行初始化GPIO的模式,BCM模式对应的针脚排序是上面图中橙色的部门。

    然后可以对各个针脚进行单独设置,比如上图中,把2号针脚设置为输出,3号针脚设置为输入。

    使用output函数进行二进制输出

    使用input函数获取针脚的输入。

    我们可以用线把两个针脚连起来测试上面的代码。

    将树莓派对应针脚和路由器的连起来以后,可以运行tjtag-pi程序。但是在运行的过程中却遇到了问题,经常会卡在写flash的时候。通过调整配置,有时是可以写成功的,但是CFE并没有被救回来,备份flash的数据,发现并没有成功写入数据。

    因为使用轮子失败,所以我只能自己尝试研究和造轮子了。

    jtag

    首先是针脚,我见过的设备给jtag一般是提供了5 * 2以上的引脚。其中有一般都是接地引脚,另一半只要知道4个最重要的引脚。

    这四个引脚一般情况下的排序是:

    TDI表示输入,TDO表示输出,TMS控制位,TCK时钟输入。

    jtag大致架构如上图所示,其中TAP-Controller的架构如下图所示:

    根据上面这两个架构,对jtag的原理进行讲解。

    jtag的核心是TAP-Controller,通过解析TMS数据,来决定输入和输出的关系。所以我们先来看看TAP-Controller的架构。

    从上面的图中我们可以发现,在任何状态下,输出5次1,都会回到TEST LOGIC RESET状态下。所以在使用jtag前,我们先通过TMS端口,发送5次为1的数据,jtag的状态机将会进入到RESET的复原状态。

    当TAP进入到SHIFT-IR的状态时,Instruction Register将会开始接收TDI传入的数据,当输入结束后,进入到UPDATE-IR状态时将会解析指令寄存器的值,随后决定输出什么数据。

    SHIFT-DR则是控制数据寄存器,一般是在读写数据的时候需要使用。

    讲到这里,就出现一个问题了,TMS就一个端口,jtag如何知道TMS每次输入的值是多少呢?这个时候就需要用到TCK端口了,该端口可以称为时钟指令。当TCK从低频变到高频时,获取一比特TMS/TDI输入,TDO输出1比特。

    比如我们让TAP进行一次复位操作:

    再比如,我们需要给指令寄存器传入0b10:

    1.复位

    2.进入RUN-TEST/IDLE状态

    3.进入SELECT-DR-SCAN状态

    4.进入SELECT-IR-SCAN状态

    5.进入CAPTURE-IR状态

    6.进入SHIFT-IR状态

    7.输入0b10

    随后就是进入EXIT-IR -> UPDATE-IR

    根据上面的理论我们就可以通过写一个设置IR的函数:

    把上面的代码理解清楚后,基本就理解了TAP的逻辑。接下来就是指令的问题了,指令寄存器的长度是多少?指令寄存器的值为多少时是有意义的?

    不同的CPU对于上面的答案都不一样,通过我在网上搜索的结果,每个CPU应该都有一个bsd(boundary scan description)文件。本篇文章研究的CPU型号是BCM5354,但是我并没有在网上找到该型号CPU的bsd文件。我只能找了一个相同厂商不同型号的CPU的bsd文件进行参考。

    bcm53101m.bsd

    在该文件中我们能看到jtag端口在cpu端口的位置:

    能找到指令长度的定义:

    能找到指令寄存器的有效值:

    当指令寄存器的值为IDCODE的时候,IDCODE寄存器的输出通道开启,我们来看看IDCODE寄存器:

    从这里我们能看出IDCODE寄存器的固定输出为: 0b00000000000011011111001011111111

    那我们怎么获取TDO的输出呢?这个时候数据寄存器DR就发挥作用了。

    1. TAP状态机切换到SHIFT-IR
    2. 输出IDCODE到IR中
    3. 切换到SHIFT-DR
    4. 获取INSTRUCTION_LENGTH长度的TDO输出值
    5. 退出

    用代码形式的表示如下:

    因为我也是个初学者,边界扫描描述文件中的内容并不是都能看得懂,比如在边界扫描文件中并不能看出BYPASS指令是做什么的。但是在其他文档中,得知BYPASS寄存器一般是用来做测试的,在该寄存器中,输入和输出是直连,可以通过比较输入和输出的值,来判断端口是否连接正确。

    另外还有边界扫描寄存器一大堆数据,也没完全研究透,相关的资料少的可怜。而且也找不到对应CPU的文档。

    当研究到这里的时候,我只了解了jtag的基本原理,只会使用两个基本的指令(IDCODE, BYPASS)。但是对我修砖没任何帮助。

    没办法,我又回头来看tjtag的源码,在tjtag中定义了几个指令寄存器的OPCODE:

    照抄着tjtag中flash AMD的操作,可以成功对flash进行擦除,写入操作读取操作。但是却不知其原理。

    这里分享下我的脚本:jtag.py

    flash文档:https://www.dataman.com/media/datasheet/Samsung/K8D6x16UTM_K8D6x16UBM_rev16.pdf

    接下来将会对该flash文档进行研究,并在之后的文章中分享我后续的研究成果。


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1060/

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • WhatsApp UAF 漏洞分析(CVE-2019-11932)

    2019-10-28

    作者:SungLin@知道创宇404实验室
    时间:2019年10月23日

    0x00

    新加坡安全研究员Awakened在他的博客中发布了这篇[0]对whatsapp的分析与利用的文章,其工具地址是[1],并且演示了rce的过程[2],只要结合浏览器或者其他应用的信息泄露漏洞就可以直接在现实中远程利用,并且Awakened在博客中也提到了:

    1、攻击者通过任何渠道将GIF文件发送给用户其中之一可以是通过WhatsApp作为文档(例如,按“Gallery”按钮并选择“Document”以发送损坏的GIF)

    如果攻击者在用户(即朋友)的联系人列表中,则损坏的GIF会自动下载,而无需任何用户交互。

    2、用户想将媒体文件发送给他/她的任何WhatsApp朋友。因此,用户按下“Gallery”按钮并打开WhatsApp Gallery以选择要发送给他的朋友的媒体文件。请注意,用户不必发送任何内容,因为仅打开WhatsApp Gallery就会触发该错误。按下WhatsApp Gallery后无需额外触摸。

    3、由于WhatsApp会显示每个媒体(包括收到的GIF文件)的预览,因此将触发double-free错误和我们的RCE利用。

    此漏洞将会影响WhatsApp版本2.19.244之前的版本,并且是Android 8.1和9.0的版本。

    我们来具体分析调试下这个漏洞。

    0x01

    首先呢,当WhatsApp用户在WhatsApp中打开“Gallery”视图以发送媒体文件时,WhatsApp会使用一个本机库解析该库,libpl_droidsonroids_gif.so以生成GIF文件的预览。libpl_droidsonroids_gif.so是一个开放源代码库,其源代码位于[3],新版本的已经修改了decoding函数,为了防止二次释放,在检测到传入gif帧大小为0的情况下就释放info->rasterBits指针,并且返回了:

    而有漏洞的版本是如何释放两次的,并且还能利用,下面来调试跟踪下。

    0x02

    Whatsapp在解析gif图像时会调用Java_pl_droidsonroids_gif_GifInfoHandle_openFile进行第一次初始化,将会打开gif文件,并创建大小为0xa8的GifInfo结构体,然后进行初始化。

    之后将会调用Java_pl_droidsonroids_gif_GifInfoHandle_renderFrame对gif图像进行解析。

    关键的地方是调用了函数DDGifSlurp(GifInfo *info, bool decode, bool exitAfterFrame)并且传入decode的值为true,在未打补丁的情况下,我们可以如Awakened所说的,构造三个帧,连续两个帧的gifFilePtr->Image.Width或者gifFilePtr->Image.Height为0,可以导致reallocarray调用reallo调用free释放所指向的地址,造成double-free:

    然后android中free两次大小为0xa8内存后,下一次申请同样大小为0xa8内存时将会分配到同一个地址,然而在whatsapp中,点击gallery后,将会对一个gif显示两个Layout布局,将会对一张gif打开并解析两次,如下所示:

    所以当第二次解析的时候,构造的帧大小为0xa8与GifInfo结构体大小是一致的,在解析时候将会覆盖GifInfo结构体所在的内存。

    0x03

    大概是这样,和博客那个流程大概一致:

    第一次解析:

    申请0xa8大小内存存储数据

    第一次free

    第二次free

    ..

    .. 第二次解析:

    申请0xa8大小内存存储info

    申请0xa8大小内存存储gif数据->覆盖info

    Free

    Free

    ..

    ..

    最后跳转info->rewindFunction(info)

    X8寄存器滑到滑块指令

    滑块执行我们的代码

    0x04

    制作的gif头部如下:

    解析的时候首先调用Java_pl_droidsonroids_gif_GifInfoHandle_openFile创建一个GifInfo结构体,如下所示:

    我们使用提供的工具生成所需要的gif,所以说newRasterSize = gifFilePtr->Image.Width * gifFilePtr->Image.Height==0xa8,第一帧将会分配0xa8大小数据

    第一帧头部如下:

    接下来解析到free所需要的帧如下,gifFilePtr->Image.Width为0,gifFilePtr->Image.Height为0xf1c,所以newRasterSize的大小将会为0,reallocarray(info->rasterBits, newRasterSize, sizeof(GifPixelType))的调用将会free指向的info->rasterBits

    连续两次的free掉大小为x0寄存器指向的0x6FDE75C580地址,大小为0xa8,而x19寄存器指向的0x6FDE75C4C0,x19寄存器指向的就是Info结构体指针

    第一次解析完后info结构体数据如下,info->rasterBits指针指向了0x6FDE75C580,而这里就是我们第一帧数据所在,大小为0xa8:

    经过reallocarray后将会调用DGifGetLine解码LZW编码并拷贝到分配内存:

    第一帧数据如下,info->rasterBits = 0x6FDE75C580

    在经过double-free掉0xa8大小内存后,第二次解析中,首先创建一个大小为0xa8的info结构体,之后将会调用DDGifSlurp解码gif,并为gif分配0xa8大小的内存,因为android的两次释放会导致两次分配同一大小内存指向同一地址特殊性,所以x0和x19都指向了0x6FDE75C580,x0是gif数据,x19是info结构体:

    此时结构体指向0x6FDE75C580

    之后经过DGifGetLine拷贝数据后,我们gif的第一帧数据将会覆盖掉0x6FDE75C580,最后运行到函数末尾,调用info->rewindFunction(info)

    此时运行到了info->rewindFunction(info),x19寄存器保存着我们覆盖了的info指针,

    此时x8寄存器指向了我们需要的指令,在libhwui中:

    此时我们来分析下如何构造的数据,在我的本机上泄露了俩个地址,0x707d540804和0x707f3f11d8,如上所示,运行到info->rewindFunction(info)后,x19存储了我们覆盖的数据大小为0xa8,汇编代码如下:

    所以我们需要泄露的第一个地址要放在X19+0X80处为0x707d540804,而0x707d540804的指令如下,所以以如下指令作为跳板执行我们的代码:

    所以刚好我们x19+0x18放的是执行libc的system函数的地址0x707f3f11d8,而x19+20是我们执行的代码所在位置:

    提供的测试小工具中,我们将会遍历lib库中的指令直到找到我们所需滑板指令的地址:

    还有libc中的system地址,将这两个地址写入gif

    跳转到libhwui后,此地址指令刚好和我们构造的数据吻合

    X8寄存器指向了libc的system调用

    X0寄存器指向我们将要运行的代码:

    0x05

    参考链接如下:

    [0] https://awakened1712.github.io/hacking/hacking-whatsapp-gif-rce
    [1] https://github.com/awakened1712/CVE-2019-11932
    [2] https://drive.google.com/file/d/1T-v5XG8yQuiPojeMpOAG6UGr2TYpocIj/view
    [3] https://github.com/koral--/android-gif-drawable/releases


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1061/

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • 使用 Ghidra 分析 phpStudy 后门

    2019-10-22

    作者:lu4nx@知道创宇404积极防御实验室
    作者博客:《使用 Ghidra 分析 phpStudy 后门》

    这次事件已过去数日,该响应的也都响应了,虽然网上有很多厂商及组织发表了分析文章,但记载分析过程的不多,我只是想正儿八经用 Ghidra 从头到尾分析下。

    1 工具和平台

    主要工具:

    • Kali Linux
    • Ghidra 9.0.4
    • 010Editor 9.0.2

    样本环境:

    • Windows7
    • phpStudy 20180211

    2 分析过程

    先在 Windows 7 虚拟机中安装 PhpStudy 20180211,然后把安装完后的目录拷贝到 Kali Linux 中。

    根据网上公开的信息:后门存在于 php_xmlrpc.dll 文件中,里面存在“eval”关键字,文件 MD5 为 c339482fd2b233fb0a555b629c0ea5d5。

    因此,先去找到有后门的文件:

    将文件 ./PHPTutorial/php/php-5.4.45/ext/php_xmlrpc.dll 单独拷贝出来,再确认下是否存在后门:

    从上面的搜索结果可以看到文件中存在三个“eval”关键字,现在用 Ghidra 载入分析。

    在 Ghidra 中搜索下:菜单栏“Search” > “For Strings”,弹出的菜单按“Search”,然后在结果过滤窗口中过滤“eval”字符串,如图:

    从上方结果“Code”字段看的出这三个关键字都位于文件 Data 段中。随便选中一个(我选的“@eval(%s(‘%s’));”)并双击,跳转到地址中,然后查看哪些地方引用过这个字符串(右击,References > Show References to Address),操作如图:

    结果如下:

    可看到这段数据在 PUSH 指令中被使用,应该是函数调用,双击跳转到汇编指令处,然后 Ghidra 会自动把汇编代码转成较高级的伪代码并呈现在 Decompile 窗口中:

    如果没有看到 Decompile 窗口,在菜单Window > Decompile 中打开。

    在翻译后的函数 FUN_100031f0 中,我找到了前面搜索到的三个 eval 字符,说明这个函数中可能存在多个后门(当然经过完整分析后存在三个后门)。

    这里插一句,Ghidra 转换高级代码能力比 IDA 的 Hex-Rays Decompiler 插件要差一些,比如 Ghidra 转换的这段代码:

    在IDA中翻译得就很直观:

    还有对多个逻辑的判断,IDA 翻译出来是:

    Ghidra 翻译出来却是:

    而多层 if 嵌套阅读起来会经常迷路。总之 Ghidra 翻译的代码只有反复阅读后才知道是干嘛的,在理解这类代码上我花了好几个小时。

    2.1 第一个远程代码执行的后门

    第一个后门存在于这段代码:

    阅读起来非常复杂,大概逻辑就是通过 PHP 的 zend_hash_find 函数寻找 $_SERVER 变量,然后找到 Accept-Encoding 和 Accept-Charset 两个 HTTP 请求头,如果 Accept-Encoding 的值为 gzip,deflate,就调用 zend_eval_string 去执行 Accept-Encoding 的内容:

    这里 zend_eval_string 执行的是 local_10 变量的内容,local_10 是通过调用一个函数赋值的:

    函数 FUN_100040b0 最后分析出来是做 Base64 解码的。

    到这里,就知道该如何构造 Payload 了:

    朝虚拟机构造一个请求:

    结果如图:

    2.2 第二处后门

    沿着伪代码继续分析,看到这一段代码:

    重点在这段:

    变量 puVar8 是作为累计变量,这段代码像是拷贝地址 0x1000d66c 至 0x1000e5c4 之间的数据,于是选中切这行代码:

    双击 DAT_1000d66c,Ghidra 会自动跳转到该地址,然后在菜单选择 Window > Bytes 来打开十六进制窗口,现已处于地址 0x1000d66c,接下来要做的就是把 0x1000d66c~0x1000e5c4 之间的数据拷贝出来:

    1. 选择菜单 Select > Bytes;
    2. 弹出的窗口中勾选“To Address”,然后在右侧的“Ending Address”中填入 0x1000e5c4,如图:

    按回车后,这段数据已被选中,我把它们单独拷出来,点击右键,选择 Copy Special > Byte String (No Spaces),如图:

    然后打开 010Editor 编辑器:

    1. 新建文件:File > New > New Hex File;
    2. 粘贴拷贝的十六进制数据:Edit > Paste From > Paste from Hex Text

    然后,把“00”字节全部去掉,选择 Search > Replace,查找 00,Replace 那里不填,点“Replace All”,处理后如下:

    把处理后的文件保存为 p1。通过 file 命令得知文件 p1 为 Zlib 压缩后的数据:

    用 Python 的 zlib 库就可以解压,解压代码如下:

    执行结果如下:

    用 base64 命令把这段 Base64 代码解密,过程及结果如下:

    2.3 第三个后门

    第三个后门和第二个实现逻辑其实差不多,代码如下:

    重点在这段:

    后门代码在地址 0x1000d028~0x1000d66c 中,提取和处理方法与第二个后门的一样。找到并提出来,如下:

    把这段Base64代码解码:

    3 参考


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1058/

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • CVE-2019-14287(Linux sudo 漏洞)分析

    2019-10-22

    作者:lu4nx@知道创宇404积极防御实验室
    作者博客:《CVE-2019-14287(Linux sudo 漏洞)分析》

    近日 sudo 被爆光一个漏洞,非授权的特权用户可以绕过限制获得特权。官方的修复公告请见:https://www.sudo.ws/alerts/minus_1_uid.html

    1. 漏洞复现

    实验环境:

    操作系统CentOS Linux release 7.5.1804
    内核3.10.0-862.14.4.el7.x86_64
    sudo 版本1.8.19p2

    首先添加一个系统帐号 test_sudo 作为实验所用:

    然后用 root 身份在 /etc/sudoers 中增加:

    表示允许 test_sudo 帐号以非 root 外的身份执行 /usr/bin/id,如果试图以 root 帐号运行 id 命令则会被拒绝:

    sudo -u 也可以通过指定 UID 的方式来代替用户,当指定的 UID 为 -1 或 4294967295(-1 的补码,其实内部是按无符号整数处理的) 时,因此可以触发漏洞,绕过上面的限制并以 root 身份执行命令:

    [test_sudo@localhost ~]

    $ sudo -u#4294967295 id uid=0(root) gid=1004(test_sudo) 组=1004(test_sudo) 环境=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

    2. 漏洞原理分析

    在官方代码仓库找到提交的修复代码:https://www.sudo.ws/repos/sudo/rev/83db8dba09e7

    从提交的代码来看,只修改了 lib/util/strtoid.c。strtoid.c 中定义的 sudo_strtoid_v1 函数负责解析参数中指定的 UID 字符串,补丁关键代码:

    llval 变量为解析后的值,不允许 llval 为 -1 和 UINT_MAX(4294967295)。

    也就是补丁只限制了取值而已,从漏洞行为来看,如果为 -1,最后得到的 UID 却是 0,为什么不能为 -1?当 UID 为 -1 的时候,发生了什么呢?继续深入分析一下。

    我们先用 strace 跟踪下系统调用看看:

    因为 strace -u 参数需要 root 身份才能使用,因此上面命令需要先切换到 root 帐号下,然后用 test_sudo 身份执行了 sudo -u#-1 id 命令。从输出的系统调用中,注意到:

    sudo 内部调用了 setresuid 来提升权限(虽然还调用了其他设置组之类的函数,但先不做分析),并且传入的参数都是 -1。

    因此,我们做一个简单的实验来调用 setresuid(-1, -1, -1) ,看看为什么执行后会是 root 身份,代码如下:

    注意,需要将编译后的二进制文件所属用户改为 root,并加上 s 位,当设置了 s 位后,其他帐号执行时就会以文件所属帐号的身份运行。

    为了方便,我直接在 root 帐号下编译,并加 s 位:

    [root@localhost tmp]

    # chmod +s a.out

    然后以 test_sudo 帐号执行 a.out:

    可见,运行后,当前身份变成了 root。

    其实 setresuid 函数只是系统调用 setresuid32 的简单封装,可以在 GLibc 的源码中看到它的实现:

    setresuid32 最后调用的是内核函数 sys_setresuid,它的实现如下:

    简单来说,内核在处理时,会调用 prepare_creds 函数创建一个新的凭证结构体,而传递给函数的 ruid、euid和suid 三个参数只有在不为 -1 的时候,才会将 ruid、euid 和 suid 赋值给新的凭证(见上面三个 if 逻辑),否则默认的 UID 就是 0。最后调用 commit_creds 使凭证生效。这就是为什么传递 -1 时,会拥有 root 权限的原因。

    我们也可以写一段 SystemTap 脚本来观察下从应用层调用 setresuid 并传递 -1 到内核中的状态:

    然后执行:

    接着运行前面我们编译的 a.out,看看 stap 捕获到的:


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1057/

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • 从 Masscan, Zmap 源码分析到开发实践

    2019-10-14

    作者:w7ay@知道创宇404实验室 
    日期:2019年10月12日

    Zmap和Masscan都是号称能够快速扫描互联网的扫描器,十一因为无聊,看了下它们的代码实现,发现它们能够快速扫描,原理其实很简单,就是实现两种程序,一个发送程序,一个抓包程序,让发送和接收分隔开从而实现了速度的提升。但是它们识别的准确率还是比较低的,所以就想了解下为什么准确率这么低以及应该如何改善。

    Masscan源码分析

    首先是看的Masscan的源码,在readme上有它的一些设计思想,它指引我们看main.c中的入口函数main(),以及发送函数和接收函数transmit_thread()receive_thread(),还有一些简单的原理解读。

    理论上的6分钟扫描全网

    在后面自己写扫描器的过程中,对Masscan的扫描速度产生怀疑,目前Masscan是号称6分钟扫描全网,以每秒1000万的发包速度。

    image-20191010142518478

    但是255^4/10000000/60 ≈ 7.047 ???

    之后了解到,默认模式下Masscan使用pcap发送和接收数据包,它在Windows和Mac上只有30万/秒的发包速度,而Linux可以达到150万/秒,如果安装了PF_RING DNA设备,它会提升到1000万/秒的发包速度(这些前提是硬件设备以及带宽跟得上)。

    注意,这只是按照扫描一个端口的计算。

    PF_RING DNA设备了解地址:http://www.ntop.org/products/pf_ring/

    那为什么Zmap要45分钟扫完呢?

    在Zmap的主页上说明了

    image-20191010151936899

    用PF_RING驱动,可以在5分钟扫描全网,而默认模式才是45分钟,Masscan的默认模式计算一下也是45分钟左右才扫描完,这就是宣传的差距吗 (-

    历史记录

    观察了readme的历史记录 https://github.githistory.xyz/robertdavidgraham/Masscan/blob/master/README.md

    之前构建时会提醒安装libpcap-dev,但是后面没有了,从releases上看,是将静态编译的libpcap改为了动态加载。

    C10K问题

    c10k也叫做client 10k,就是一个客户端在硬件性能足够条件下如何处理超过1w的连接请求。Masscan把它叫做C10M问题。

    Masscan的解决方法是不通过系统内核调用函数,而是直接调用相关驱动。

    主要通过下面三种方式:

    • 定制的网络驱动
      • Masscan可以直接使用PF_RING DNA的驱动程序,该驱动程序可以直接从用户模式向网络驱动程序发送数据包而不经过系统内核。
    • 内置tcp堆栈
      • 直接从tcp连接中读取响应连接,只要内存足够,就能轻松支持1000万并发的TCP连接。但这也意味着我们要手动来实现tcp协议。
    • 不使用互斥锁
      • 锁的概念是用户态的,需要经过CPU,降低了效率,Masscan使用rings来进行一些需要同步的操作。与之对比一下Zmap,很多地方都用到了锁。
        • 为什么要使用锁?
          • 一个网卡只用开启一个接收线程和一个发送线程,这两个线程是不需要共享变量的。但是如果有多个网卡,Masscan就会开启多个接收线程和多个发送线程,这时候的一些操作,如打印到终端,输出到文件就需要锁来防止冲突。
        • 多线程输出到文件
          • Masscan的做法是每个线程将内容输出到不同文件,最后再集合起来。在src/output.c中,

    随机化地址扫描

    在读取地址后,如果进行顺序扫描,伪代码如下

    但是考虑到有的网段可能对扫描进行检测从而封掉整个网段,顺序扫描效率是较低的,所以需要将地址进行随机的打乱,用算法描述就是设计一个打乱数组的算法,Masscan是设计了一个加密算法,伪代码如下

    随机种子就是i的值,这种加密算法能够建立一种一一对应的映射关系,即在[1...range]的区间内通过i来生成[1...range]内不重复的随机数。同时如果中断了扫描,只需要记住i的值就能重新启动,在分布式上也可以根据i来进行。

    无状态扫描的原理

    回顾一下tcp协议中三次握手的前两次

    1. 客户端在向服务器第一次握手时,会组建一个数据包,设置syn标志位,同时生成一个数字填充seq序号字段。
    2. 服务端收到数据包,检测到了标志位的syn标志,知道这是客户端发来的建立连接的请求包,服务端会回复一个数据包,同时设置syn和ack标志位,服务器随机生成一个数字填充到seq字段。并将客户端发送的seq数据包+1填充到ack确认号上。

    在收到syn和ack后,我们返回一个rst来结束这个连接,如下图所示

    image-20191003223330374
    image-20191003230816536

    Masscan和Zmap的扫描原理,就是利用了这一步,因为seq是我们可以自定义的,所以在发送数据包时填充一个特定的数字,而在返回包中可以获得相应的响应状态,即是无状态扫描的思路了。 接下来简单看下Masscan中发包以及接收的代码。

    发包

    main.c中,前面说的随机化地址扫描

    image-20191003232846484

    接着生成cookie并发送

    image-20191003233102015

    看名字我们知道,生成cookie的因子有源ip,源端口,目的ip,目的端口,和entropy(随机种子,Masscan初始时自动生成),siphash24是一种高效快速的哈希函数,常用于网络流量身份验证和针对散列dos攻击的防御。

    组装tcp协议template_set_target(),部分代码

    发包函数

    可以看到它是分三种模式发包的,PF_RING,WinPcap,LibPcap,如果没有装相关驱动的话,默认就是pcap发包。如果想使用PF_RING模式,只需要加入启动参数--pfring

    接收

    在接收线程看到一个关于cpu的代码

    image-20191004003419241

    大意是锁住这个线程运行的cpu,让发送线程运行在双数cpu上,接收线程运行在单数cpu上。但代码没怎么看懂

    接收原始数据包

    主要是使用了PFRING和PCAP的api来接收。后面便是一系列的接收后的处理了。在mian.c757行

    image-20191004004238243

    后面还会判断是否为源ip,判断方式不是相等,是判断某个范围。

    接着后面的处理

    Zmap源码分析

    Zmap官方有一篇paper,讲述了Zmap的原理以及一些实践。上文说到Zmap使用的发包技术和Masscan大同小异,高速模式下都是调用pf_ring的驱动进行,所以对这些就不再叙述了,主要说下其他与Masscan不同的地方,paper中对丢包问题以及扫描时间段有一些研究,简单整理下

    1. 发送多个探针:结果表明,发送8个SYN包后,响应主机数量明显趋于平稳
    2. 哪些时间更适合扫描
      1. 我们观察到一个±3.1%的命中率变化依赖于日间扫描的时间。最高反应率在美国东部时间上午7时左右,最低反应率在美国东部时间下午7时45分左右。
      2. 这些影响可能是由于整体网络拥塞和包丢失率的变化,或者由于只间断连接到网络的终端主机的总可用性的日变化模式。在不太正式的测试中,我们没有注意到任何明显的变化

    还有一点是Zmap只能扫描单个端口,看了一下代码,这个保存端口变量的作用也只是在最后接收数据包用来判断srcport用,不明白为什么还没有加上多端口的支持。

    宽带限制

    相比于Masscan用rate=10000作为限制参数,Zmap用-B 10M的方式来限制

    image-20191010154942162

    我觉得这点很好,因为不是每个使用者都能明白每个参数代表的原理。实现细节

    image-20191010155045099
    image-20191010155334018

    发包与解包

    Zmap不支持Windows,因为Zmap的发包默认用的是socket,在window下可能不支持tcp的组包(猜测)。相比之下Masscan使用的是pcap发包,在win/linux都有支持的程序。Zmap接收默认使用的是pcap。

    在构造tcp包时,附带的状态信息会填入到seq和srcport中

    image-20191010161356014

    在解包时,先判断返回dstport的数据

    image-20191012110543094

    再判断返回的ack中的数据

    image-20191012110655331

    用go写端口扫描器

    在了解完以上后,我就准备用go写一款类似的扫描器了,希望能解决丢包的问题,顺便学习go。

    在上面分析中知道了,Masscan和Zmap都使用了pcap,pfring这些组件来原生发包,值得高兴的是go官方也有原生支持这些的包 https://github.com/google/gopacket,而且完美符合我们的要求。

    image-20191012111724556

    接口没问题,在实现了基础的无状态扫描功能后,接下来就是如何处理丢包的问题。

    丢包问题

    按照tcp协议的原理,我们发送一个数据包给目标机器,端口开放时返回ack标记,关闭会返回rst标记。

    但是通过扫描一台外网的靶机,发现扫描几个端口是没问题的,但是扫描大批量的端口(1-65535),就可能造成丢包问题。而且不存在的端口不会返回任何数据。

    控制速率

    刚开始以为是速度太快了,所以先控制下每秒发送的频率。因为发送和接收都是启动了一个goroutine,目标的传入是通过一个channel传入的(go的知识点)。

    所以控制速率的伪代码类似这样

    本地状态表

    即使将速度控制到了最小,也存在丢包的问题,后经过一番测试,发现是防火墙的原因。例如常用的iptables,其中拒绝的端口不会返回信息。将端口放行后再次扫描,就能正常返回数据包了。

    此时遇到的问题是有防火墙策略的主机如何进行准确扫描,一种方法是扫描几个端口后就延时一段时间,但这不符合快速扫描的设想,所以我的想法是维护一个本地的状态表,状态表中能够动态修改每个扫描结果的状态,将那些没有返回包的目标进行重试。

    Ps:这是针对一个主机,多端口(1-65535)的扫描策略,如果是多个IP,Masscan的随机化地址扫描策略就能发挥作用了。

    设想的结构如下

    初始数据时status为0,当发送数据时,将status变更为1,同时记录发送时间time,接收数据时通过返回的标记,dstport,seq等查找到本地状态表相应的数据结构,变更status为2,同时启动一个监控程序,监控程序每隔一段时间对所有的状态进行检查,如果发现stauts为1并且当前时间-发送时间大于一定值的时候,可以判断这个ip+端口的探测包丢失了,准备重发,将retry+1,重新设置发送时间time后,将数据传入发送的channel中。

    概念验证程序

    因为只是概念验证程序,而且是自己组包发送,需要使用到本地和网关的mac地址等,这些还没有写自动化程序获取,需要手动填写。mac地址可以手动用wireshark抓包获得。

    如果你想使用该程序的话,需要修改全局变量中的这些值

    整个go语言源程序如下,单文件。

    运行结果如下

    image-20191012135527477

    但这个程序并没有解决上述说的防火墙阻断问题,设想很美好,但是在实践的过程中发现这样一个问题。比如扫描一台主机中的1000个端口,第一次扫描后由于有防火墙的策略只检测到了5个端口,剩下995个端口会进行第一次重试,但是重试中依然会遇到防火墙的问题,所以本质上并没有解决这个问题。

    Top端口

    这是Masscan源码中一份内置的Top端口表

    可以使用--top-ports = n来选择数量。

    这是在写完go扫描器后又在Masscan中发现的,可能想象到Masscan可能也考虑过这个问题,它的方法是维护一个top常用端口的排行来尽可能减少扫描端口的数量,这样可以覆盖到大多数的端口(猜测)。

    总结

    概念性程序实践失败了,所以再用go开发的意义也不大了,后面还有一个坑就是go的pcap不能跨平台编译,只能在Windows下编译windows版本,mac下编译mac版本。

    但是研究了Masscan和Zmap在tcp协议下的syn扫描模式,还是有很多收获,以及明白了它们为什么要这么做,同时对网络协议和一些更低层的细节有了更深的认识。

    这里个人总结了一些tips:

    • Masscan源码比Zmap读起来更清晰,注释也很多,基本上一看源码就能明白大致的结构了。
    • Masscan和Zmap最高速度模式都是使用的pfring这个驱动程序,理论上它两的速度是一致的,只是它们宣传口径不一样?
    • 网络宽带足够情况下,扫描单个端口准确率是最高的(通过自己编写go扫描器的实践得出)。
    • Masscan和Zmap都能利用多网卡,但是Zmap线程切换用了锁,可能会消耗部分时间。
    • 设置发包速率时不仅要考虑自己带宽,还要考虑目标服务器的承受情况(扫描多端口时)

    参考链接


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1052/

    作者:吴烦恼 | Categories:安全研究 | Tags: