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



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 (







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

yc2lvbiI6IDEsCiAgICAgICAgImNpc GhlclN1aXRlIjogM

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

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.



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: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 ).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.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 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: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.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.The YAJL library is initialized with a call to, which has the following prototype.This function creates an opaque yajl_handle that is later passed as a parameter to, the function responsible for parsing a block of JSON text. The first parameter of thefunction 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. Thestructure is as follows.As described in the comments, the YAJL parser will invoke these callbacks whenever the corresponding elements within a JSON block is encountered. Theparameter passed to each of these callbacks is the sameparameter passed by the called as the third parameter to, 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 thefunction with callbacks to custom allocation and free routines. Thestructure that encapsulates these callbacks is defined as follows.After a handle is allocated, a block of JSON text can be parsed by calling yajl_parse(), which has the following API.As can be seen, this function simply takes a previously-created, followed by the block of text to be parsed and its length. Callingon 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.When parsing the above block, the following sequence of callbacks will be invoked:yajl_start_map() - called when parsing the initial "{" tokenyajl_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 "}" tokenThe vulnerability in question occurs during JSON deserialization of incoming SCIMP messages within libscimp, which is performed by thefunction (defined in). The code is shown (edited for brevity).This function simply allocates and zeroes out a context structure (, which is astructure), establishes a YAJL handle, then invokes the. The following code snippet shows the functionsand- the pair of functions that are invoked whenever an opening brace ('{') or a closing brace ('}') is encountered respectively.We can see inthat thepointer will be initialized with a pointer to astructure if it hasn't been initialized already, which has the following definition:This is a very simple structure used to contain a SCIMP message - the type is denoted by thefield, and the data values for the message are stored within the union, which is interpreted depending on the value of thefield. The message type structures contained within the above union are as follows.We will revisit some of these later.After initializingthen increments theinteger. This integer indicates the nesting level within the JSON message that is currently being examined. Thefunction performs the corresponding decrement operation on thevariable, to indicate the end of the currently nested block. This nesting level is utilized by thecallback 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):When parsing the top level of the JSON block (nesting level 1), the map key must be a valid message type, which is determined byAssuming a recognized message type is received, thefield of thestructure originally allocated inwill 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 thefunction (since thevariable will be set to 2 when parsing this submap). Thefunction is as follows.As can be seen, depending on the message type,andwill point to the relevant field within thestructure to be filled out, along with information about what type of data the given field should be. These fields are then filled out by theandcallbacks passed to YAJL, which are shown.The problem with the way SCIMP employs the YAJL library is that thefunction will call thefunction 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. For example, consider what happens when the following message is received: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 1sParse_map_key() - indicates that this is a "dh2" messagesParse_start_map() - Increments jctx->level to 2sParse_map_key() - Parse and set pk and pkLen fields in the SCimpMsg structuresParse_map_key() - Parse the "maci" field and set Maci[] arraysParse_end_map() - Decrements jctx->level to 1sParse_map_key() - indicates this is a "data" message ** OVERWRITES msg->msgType field with a new type ***sParse_start_map() - Increments jctx->field to 2sParse_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 previouslysParse_end_map() - Decrements jctx->level to 1sParse_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 thefield 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:The highlighted line shows the invalid address 6 comments: Subscribe to Post Comments [Atom] << Home