Overtaking Firefox Profiles: Vulnerabilities in Firefox for Android


Published on

Abstract|We present newly-found vulnerabilities in the Firefox Android Application. Exploiting them allows a malicious application to successfully derandomize the Firefox pro le directory name in a practical amount of time and ex ltrate sensitive data (such as cookies and cached information) which reside in that directory, breaking Android's sandbox.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Overtaking Firefox Profiles: Vulnerabilities in Firefox for Android

  1. 1. 1 OVERTAKING FIREFOX PROFILES Vulnerabilities in Firefox for Android Roee Hay IBM Security Systems roeeh@il.ibm.com Abstract—We present newly-found vulnerabili- ties in the Firefox Android Application. Exploiting them allows a malicious application to successfully derandomize the Firefox profile directory name in a practical amount of time and exfiltrate sensitive data (such as cookies and cached information) which reside in that directory, breaking Android’s sandbox. Index Terms—CVE-2014-1484; CVE-2014- 1506; CVE-2014-1515; CVE-2014-1516. I. Android basics A. Threat model Android applications are executed in a sandbox environment to ensure that no application can access sensitive information held by another without proper privileges. For example, Android’s browser appli- cation holds sensitive information such as cookies, cache and history which cannot be accessed by third- party apps. An android application may request spe- cific privileges (permissions) during its installation; if granted by the user, the capabilities are extended. Permissions are defined under the application’s man- ifest file, namely AndroidManifest.xml. B. Activities and Services Android applications are composed of compo- nents of different types including activities and services. An Activity, implemented by the an- droid.content.Activity class [1], defines a single UI, e.g. a browsing window or a preferences screen. Services [2] are application components that are used for background tasks. C. Inter-Process Communication (IPC) and Intents Android applications communicate with each other through Intents. These are messaging objects that contain several attributes such as an action, data, category, target and extras. The data attribute is a URI which identifies the Intent (e.g. tel:0422123). Each Intent can also contain extra data fields, namely Intent extras, which reside inside a Bundle (imple- mented by the android.os.Bundle class [3]). Intent extras can be set by using the Intent.putExtra API or by manipulating the extras Bundle directly. It is important to emphasize that intents provide a channel for a malicious application to inject malicious data into a target, potentially vulnerable application. Intents can have either explicit or implicit destina- tion, depending whether targets are specified or not. Intents can be broadcast, passed to the startActiv- ity call (when an application starts another activ- ity), or passed to the startService call (when an application starts a service). Under the application’s manifest file, an application component may claim whether it can be invoked externally using an Intent, and if so which set of permissions is required. We define a public application component as one which can be invoked externally by a (potentially malicious) application without any permissions. All other com- ponents are defined as private, i.e. they can only be invoked by other application components of the same package, or externally with proper privileges. II. Generation of the Profile Directory Firefox stores the personal data under the profile directory, located at files/mozilla/X.default/ where X is a randomly chosen 8-byte word over the alphanumeric alphabet [a-z0-9]. The gener- ation of X.default is implemented at GeckoPro- file.saltProfileName (Figure II.1). Since the profile directory name is random, Firefox stores a mapping under the files/mozilla/pro- files.ini file (Figure II.2) If Firefox does not find a valid profile, for example due to a missing profiles.ini, it creates a new one by invoking GeckoProfile.createProfileDir. III. Vulnerabilities A. Profile Directory Name Weak Randomization The Math.random() static method returns a uni- formly distributed pseudo-random number between
  2. 2. 2 1 private static String 2 saltProfileName ( String name) 3 { 4 String allowedChars = 5 ”abcdefghijklmnopqrstuvwxyz0123456789 ” ; 6 7 StringBuilder s a l t = 8 new StringBuilder ( 1 6 ) ; 9 10 for ( int i = 0; i < 8; i++) { 11 s a l t . append ( allowedChars . charAt (( int ) 12 (Math . random () ∗ 13 allowedChars . length ( ) ) ) ) ; 14 } 15 s a l t . append ( ’ . ’ ) ; 16 s a l t . append (name ) ; 17 return s a l t . toString ( ) ; 18 } Figure II.1. GeckoProfile.saltProfileName(String name) [Profile0] Default=1 Name=default IsRelative=1 Path=475jbgu6.default [General] StartWithLastProfile=1 Figure II.2. profiles.ini 0 and 1. In Android, the implementation is under libcore1 : 1 public static synchronized double random () 2 { 3 i f ( random == null ) 4 { 5 random = new Random ( ) ; 6 } 7 return random . nextDouble ( ) ; 8 } Figure III.1. Math.random() We can see that this method relies on the java.util.Random class which is seeded in its de- fault constructor. In Android 4.4 and below, it is implemented as follows2 : 1https://android.googlesource.com/platform/libcore/ 2https://android.googlesource.com/platform/libcore/+/ android-4.4 r1.2/luni/src/main/java/java/util/Random.java 1 public Random() 2 { 3 setSeed ( System . currentTimeMillis () 4 + System . identityHashCode ( this ) ) ; 5 } Figure III.2. java.util.Random’s constructor Therefore the seed depends on a couple of values: 1) The current time in milliseconds precision (System.currentTimeMillis()) 2) The identity hash code of the Random object (System.identityHashCode(this)) System.identityHashCode is implemented by na- tive code in the Dalvik VM implementation3 . The identity hash code value is the object’s Virtual Ad- dress (VA) which resides in the heap of the Dalvik VM process. GeckoProfile.saltProfileName uses Math.random() which is cryptographically insecure. We have seen that its seed relies on the inner- Random object creation time (in ms precision) and its VA. Both factors are not random. The creation time can be leaked by an adversary (see IV-A1b) and the VA lacks randomness due to ineffective ASLR in the Dalvik VM process [4], [5]. Since the Dalvik VM is forked from the Zygote process, the VA of the Dalvik Heap is the same for all Android Dalvik applications. To conclude, the seed is not random, thus the profile directory name entropy is far from the ideal 41.36 random bits (log2 368 ) and can be predicted by the adversary as we will see next. B. Profile Directory Name Leaks to Android System Log The random Profile Directory Name is written to the Android System Log (logcat) in various locations. For instance, upon Firefox launch, the following data is written: D/GeckoProfile( 4766): Found profile dir: /data/data/.../files/mozilla/24pd90uh.default In Android 4.0 and below, the Android log can easily be read by all applications including malicious ones by acquiring the READ_LOGS permission. Android 4.1 has introduced a change to this behavior to prevent such log leakage attacks: the READ_LOGS permission is no longer required, however applications can only listen to their own log. In section IV-B we show how a malicious app can manipulate Firefox to leak its own private log in order to overcome this obstacle. 3https://android.googlesource.com/platform/dalvik.git/+/ android-4.4 r1.2/vm/Sync.cpp
  3. 3. 3 C. Automatic File Download to SD Card Any file which cannot be rendered by Fire- fox is automatically downloaded to the SD card (/mnt/sdcard/Download), a folder which can be read by a malicious application by acquiring the READ_EXTERNAL_STORAGE permission. Interestingly, this permission was not even enforced before Android 4.44 . This allows a malicious application to extract non-renderable data such as the cookies database, once it has managed to derandomize the profile directory name. D. Crash Reporter File Manipulation The org.mozilla.gecko.CrashReporter class is a public activity. Its purpose is to send crash dumps to Mozilla when needed (Figure III.3). The CrashRe- porter activity receives the dump file path as an input (an Intent extra parameter). When the activity is launched, its onCreate method (Figure III.4) is executed and the following actions take place: 1) Using the moveFile (Figure III.5) method, the given minidump file is moved to the pending minidumps path, files/mozilla/Crash Re- ports/pending. 2) A meta-data filename is deduced from the given minidump file path, by replacing all ’.dmp’ occurrences with ’.extra’. i.e. <filename>.dmp becomes <filename>.extra. 3) The meta-data file is moved to the pending minidumps path using the moveFile method. 4) The meta-data file is parsed as a key/value file format (Figure III.6). The target server URL (i.e. the one that the crash information is sent to) is specified here using the ServerURL key. 5) The crash dialog is presented to the user (Fig- ure III.3). If the user presses the Close or Restart buttons with the Send report check-box enabled, the minidump alongside with other sensitive information is sent to the specified server by calling the sendReport method. It should be noted that if the user has also checked the Include the address check-box, then Android logs are sent as well. The CrashReporter activity consumes the minidump path from the input intent (Figure III.4, Line 4) although it should be considered untrusted data since the activity is public as defined in the Android Manifest file. Therefore, a malicious application can control the source path of the moved minidump file and the deduced extra file 4http://developer.android.com/reference/android/ Manifest.permission.html#READ EXTERNAL STORAGE Figure III.3. Crash Reporter dialog (Figure III.4, Lines 8, 11). We will see in the next section that this allows that attacker to control the destination server for the crash report, hence the response value used as a file path in Figure III.7, Line 19 should be untrusted as well. IV. Exploitation The attacker (by the use of a malicious appli- cation) can exploit a subset of the aforementioned vulnerabilities in order to: 1) Derandomize the profile directory path. 2) Exfiltrate sensitive data (such as cookies and cached information) from the profile directory. In this section we describe several attractive exploita- tion suites. A. Suite I: Exploiting Vulnerabilities III-A, III-C and optionally III-D 1) Derandomizing the Profile Path: Let Y be the seed chosen by Firefox. The goal of the attacker is to derandomize it by recording some value which shares a strong relation with it. Both the VA and the time parts of the seed cannot be entirely determined by the attacker, however, most of their bits can be leaked to the attacker using simple actions: a) Derandomizing the VA Value: To obtain parts of the VA, as per the ineffective ASLR, the attacker can query its own process using the Sys- tem.identitiyHashCode routine on some object.
  4. 4. 4 b) Derandomizing the Time Value: The ms time value can be leaked in two different ways. First, if Firefox had been installed after the ma- licious app, the latter can record the first Firefox run by simply monitoring the device’s process list. Otherwise, the attacker app can exploit Vulnera- bility III-D to move the profiles.ini file (which has a deterministic path) to the different direc- tory. The exploitation is done by sending an Intent to the Crash Reporter with minidumpPath Intent extra set to /data/data/org.mozilla.firefox/- files/mozilla/profiles.ini. Firefox will create a new profile on its next run, which can be forced by crashing it (this can be done by exploiting Vulnera- bility III-D, sending an Intent to the Crash Reporter without a minidumpPath extra). Again the attacker can record the Firefox launch time. Let X be the attacker’s inaccurate sample where X = V A + MeasuredTime. Let ε be the error, i.e. ε = Y − X. In our brute-force attack we try values in the order of their inferred probabilities which can be computed offline by the attacker (see Section V). Let N be the number of attempts until a success, then the expected number of attempts is E(N) = n k=1 k·p(k) where {p(k)}n k=1 are the ordered inferred probabilities (p(1) ≥ p(2) ≥ · · · ≥ p(n)). Please note that the expression for E(N) proves that this is the optimal search algorithm (in terms of the expectation). A simpler model is to assume that ε is a (discrete) bell-curve with the center estimated by ¯ε = ¯x-¯y. It can be shown that E(N) = O(E|X − EX|) = O (σε) so a more narrow bell- curve yields a shorter attack time (on average). By using the inferred probabilities of ε and the sampled seed, X, the attacker creates a list of profile names by mimicking the GeckoProfile.saltProfileName implementation (this computation can be also done off the device and downloaded from the attacker). 2) Data Exfiltration: This phase is online. The attacker creates a specially crafted world-readable HTML file and commands Firefox to load it (by using an Intent). This file includes the list of profile names ordered by their probabilities generated previously. The JavaScript code in the HTML file goes over the list, searching for the correct profile. When there is a match, it can download any file under the profiles directory by creating an iframe targeting the file- name. As per Vulnerability III-C, if Firefox cannot render the file, it will automatically download the file to the SD card (/mnt/sdcard/Download), a folder which can be read by the attacker. B. Suite II: Exploiting Vulnerabilities III-B, III-C and optionally III-D 1) Derandomizing the Profile Path : On Android 4.0 and below, the Android System Log can easily be read by the malicious app. This allows the malicious app to exploit Vulnerability III-B and deduce the profile directory path. Android 4.1 prevents this attack as applications can only listen to their own logs. Since Firefox sends its own private log alongside the crash report (see Section III-D) , Vulnerability III-D can be exploited for sending the logs to the attacker: • The malicious app creates two world readable files under its data directory: /data/data/<malicious app>/foo.dmp with an arbitrary content (can be left empty) and /data/data/<malicious app>/foo.extra which contains the attacker’s server, ServerURL=http://<attacker>/. • The malicious app generates an Intent ob- ject which targets the CrashReporter activity with a minidumpPath parameter set to /data/- data/<malicious app>/foo.dmp After the above actions take place, according to the operation of the CrashReporter activity (see Section III-D), Firefox will move foo.dmp and foo.extra from the malicious app data directory to the Fire- fox crash reports pending path and then parse foo.extra for key-value pairs. Since the extra file now contains the attacker’s IP as the ServerURL, if the user pressed one of the buttons on the CrashRe- porter dialog, Firefox would send the crash report to the attacker. If the user has also checked the Include the address check-box, then Firefox would also append the Android Log output, which contains the Firefox private logs that include the random profile directory name. It should be noted that since Firefox only sends a 200 lines window of the log, the leaked path can be out of that window since it is logged upon Firefox’s launch. In order to make sure that the path is within the window, the attacker can restart Firefox by crashing it. This can be easily done by invoking the CrashReporter activity without a minidumpPath parameter. 2) Data Exfiltration: Once the attacker has learnt the profile path, he can leak files by exploiting Vul- nerability III-C. The attacker simply invokes Firefox using an Intent with a payload set to the target file path. C. Suite III: Exploiting Vulnerabilities III-B and III-D 1) Derandomizing the Profile Path Seed: This part is exactly as described in Section IV-B1.
  5. 5. 5 2) Data Exfiltration: Here we exploit Vulnerability III-D. We indirectly inject the ServerURL=http://<attacker> string to the target file and trick Firefox to use this file as the minidump extra. For instance, by using this method, the attacker can leak the cache. First, he opens Firefox (using an Intent) on an attacker’s controlled website, which contains the above string in its HTML body (Figure IV.1). After the cache has been prepared, the attacker can leak it by generating another Intent that targets the CrashReporter activity, with the minidump path parameter set to the cache file. Since the cache file path has no ’.dmp’ substring, the computed extra file will be the same, and the target server URL will be parsed out of the cache file. 1 <HTML> 2 <BODY> 3 ServerURL=http : //<ATTACKER> 4 </BODY> 5 </HTML> Figure IV.1. Injected payload into Firefox’s cache V. Evaluation We tested our Suite I exploit on Firefox 25.0.1 running on Samsung GT-I9500 Galaxy S4 device equipped with Android 4.2.2. In order to infer the probabilities of ε we ran 404 independent runs of the following test: 1) Call Math.Random() and retrieve its identi- tyHashCode (Figure V.1) 2) Start monitoring the process list in a frequency of 20 Hz for recording the Firefox launch time. 3) Remove the profiles.ini file from the Firefox directory by exploiting Vulnerability III-D. 4) Record the created profile directory. 5) Kill Firefox. 6) Restart Firefox. 7) Print the needed values. 1 Math . random ( ) ; 2 Field f = Math . class . 3 getDeclaredField ( ”random ” ) ; 4 f . s e t A c c e s s i b l e ( true ) ; 5 Random r = (Random) f . get ( null ) ; 6 Long addr = System . identityHashCode ( r ) ; Figure V.1. Retrieving the identityHashCode of the Math inner Random object For example, for each run we received and recorded the following data: Sampled VA: 42dc6cd8 Sampled Time: 1385337348079 Sampled Seed: 142cf66ccc7 Created profile: xa1x453r.default Later we calculated the real seed and ε by an offline brute-force. For example, for the data above, the real seed is 1386459232784 and ε = 142665. We witnessed three strong linear correlations be- tween Y and X (Figure V.2). We believe that the reason for the less dense lines rely on Dalvik Heap internals which caused the offset to ’jump’ with a fixed number for a few tests (i.e. the probability for this to happen is low). Ϭ ϮϬϬϬϬϬϬ ϰϬϬϬϬϬϬ ϲϬϬϬϬϬϬ ϴϬϬϬϬϬϬ ϭϬϬϬϬϬϬϬ ϭϮϬϬϬϬϬϬ ϭϰϬϬϬϬϬϬ ϭϲϬϬϬϬϬϬ Ϭ ϮϬϬϬϬϬϬ ϰϬϬϬϬϬϬ ϲϬϬϬϬϬϬ ϴϬϬϬϬϬϬ ϭϬϬϬϬϬϬϬ ϭϮϬϬϬϬϬϬ ϭϰϬϬϬϬϬϬ Figure V.2. Linear correlation between the real seed and the sampled one We created a histogram for inferring the distribu- tion of ε (Figure V.3) and calculated the expected number of attempts, E(N), which is 152779.65 and is equivalent to 17.22 bits. Note that the Shannon entropy is very close, 18.63 bits. We’ve got an entropy which is much lower than the ideal 64 bits (the java long primitive size) seed entropy, thus the attack is feasible. Ϭ Ϭ͘ϬϮ Ϭ͘Ϭϰ Ϭ͘Ϭϲ Ϭ͘Ϭϴ Ϭ͘ϭ Ϭ͘ϭϮ Ϭ͘ϭϰ ͲϭϬϬϬϬϬ ϮϬϬϬϬϬ ϱϬϬϬϬϬ ϴϬϬϬϬϬ ϭϭϬϬϬϬϬ ϭϰϬϬϬϬϬ ϭϳϬϬϬϬϬ Figure V.3. ε Histogram We then adhered to the steps of section IV-A, taken the simpler approach where we assert a sym- metric bell curve. Since we received three linear
  6. 6. 6 correlations, we have taken a slightly different ap- proach. We denote p1, p2, p3 as the probabilities for the error to be taken from each curve and assert that each error is normally distributed with its respective variance (the actual discrete error is the rounded value, see Figure V.4). Therefore ε1 ∼ N 0, σ2 1 with probability p1 , ε2 ∼ N 0, σ2 2 with probability p2 and ε3 ∼ N 0, σ2 3 with probability p3. We brute- force in the order of the probabilities. We ran our exploit and successfully caused Firefox to download sensitive files into the SD card which can be read by the attacker (Figure V.5). It should be noted that according to the ’normalized’ model, the expected number of tries is 214373.3 which is equivalent to 17.709 bits. Ϭ Ϭ͘ϬϮ Ϭ͘Ϭϰ Ϭ͘Ϭϲ Ϭ͘Ϭϴ Ϭ͘ϭ Ϭ͘ϭϮ ͲϭϬϬϬϬϬ ϮϬϬϬϬϬ ϱϬϬϬϬϬ ϴϬϬϬϬϬ ϭϭϬϬϬϬϬ ϭϰϬϬϬϬϬ ϭϳϬϬϬϬϬ Figure V.4. Normalized−ε histogram VI. Identifiers & Fix Versions Vulnerability CVE ID Fix Version III-A CVE-2014-1516 TBA5 III-B CVE-2014-1484 Firefox 27 III-C CVE-2014-1515 Firefox 28.0.1 III-D CVE-2014-1506 Firefox 28 VII. Disclosure Timeline 03/25/2014 Public disclosure. 03/25/2014 Firefox 28.0.1 is released, fixing Vulnerability III-C 03/18/2014 Firefox 28 is released, fixing Vulnerability III-D 03/15/2014 Permission granted. 03/12/2014 Requested permission to disclose Vulnerability III-A despite lack of fix. 02/04/2014 Firefox 27 is released, fixing Vulnerability III-B 11/28/2013 Reported Vulnerabilities. 5As its severity is now much lower with the CVE-2014-1515 fix, we were allowed by Mozilla to publicly disclose this unfixed vulnerability. Figure V.5. Automatic File Download (exploiting Vulnerabil- ity III-C) References [1] Activity class reference. http://developer.android.com/ reference/android/app/Activity.html. [2] Service class reference. http://developer.android.com/ reference/android/app/Service.html. [3] Bundle class reference. http://developer.android.com/ reference/android/os/Bundle.html. [4] Byoungyoung Lee, Long Lu, Tielei Wang, Taesoo Kim, and Wenke Lee. From Zygote to Morula: Fortifying Weakened ASLR on Android. In IEEE Symposium on Security and Privacy, 2014. [5] Jon Oberheide. A look at ASLR in An- droid Ice Cream Sandwich 4.0, February 2012. https://www.duosecurity.com/blog/ a-look-at-aslr-in-android-ice-cream-sandwich-4-0.
  7. 7. 7 1 @Override 2 public void onCreate ( Bundle savedInstanceState ) { 3 . . . 4 String passedMinidumpPath = getIntent ( ) . getStringExtra (PASSED MINI DUMP KEY) ; 5 F i l e passedMinidumpFile = new F i l e ( passedMinidumpPath ) ; 6 F i l e pendingDir = new F i l e ( g e t F i l e s D i r ( ) , PENDING SUFFIX) ; 7 pendingDir . mkdirs ( ) ; 8 mPendingMinidumpFile = new F i l e ( pendingDir , passedMinidumpFile . getName ( ) ) ; 9 moveFile ( passedMinidumpFile , mPendingMinidumpFile ) ; 10 F i l e e x t r a s F i l e = new F i l e ( passedMinidumpPath . r e p l a c e A l l ( ” .dmp” , ” . extra ” ) ) ; 11 mPendingExtrasFile = new F i l e ( pendingDir , e x t r a s F i l e . getName ( ) ) ; 12 moveFile ( extrasFile , mPendingExtrasFile ) ; 13 mExtrasStringMap = new HashMap<String , String >(); 14 readStringsFromFile ( mPendingExtrasFile . getPath ( ) , mExtrasStringMap ) ; 15 . . . 16 } Figure III.4. onCreate method under CrashReporter 1 private boolean moveFile ( F i l e inFile , F i l e outFile ) { 2 Log . i (LOGTAG, ”moving ” + i n F i l e + ” to ” + outFile ) ; 3 i f ( i n F i l e . renameTo ( outFile )) 4 return true ; 5 try { 6 outFile . createNewFile ( ) ; 7 Log . i (LOGTAG, ”couldn ’ t rename minidump f i l e ” ) ; 8 // so copy i t instead 9 FileChannel inChannel = new FileInputStream ( i n F i l e ) . getChannel ( ) ; 10 FileChannel outChannel = new FileOutputStream ( outFile ) . getChannel ( ) ; 11 long t r a n s f e r r e d = inChannel . transferTo (0 , inChannel . s i z e ( ) , outChannel ) ; 12 inChannel . c l o s e ( ) ; 13 outChannel . c l o s e ( ) ; pr i or to 14 i f ( t r a n s f e r r e d > 0) 15 i n F i l e . d e l e t e ( ) ; 16 } catch ( Exception e ) { 17 Log . e (LOGTAG, ”exception while copying minidump f i l e : ” , e ) ; 18 return false ; 19 } 20 return true ; 21 } Figure III.5. moveFile method under CrashReporter
  8. 8. 8 1 private boolean readStringsFromFile ( String filePath , Map<String , String> stringMap ) { 2 try { 3 BufferedReader reader = new BufferedReader (new FileReader ( f i l e P a t h ) ) ; 4 return readStringsFromReader ( reader , stringMap ) ; 5 } catch ( Exception e ) { 6 Log . e (LOGTAG, ”exception while reading s t r i n g s : ” , e ) ; 7 return false ; 8 } 9 } 10 11 private boolean readStringsFromReader ( BufferedReader reader , Map<String , String> stringMap ) 12 throws IOException { 13 String l i n e ; 14 while (( l i n e = reader . readLine ( ) ) != null ) { 15 int equalsPos = −1; 16 i f (( equalsPos = l i n e . indexOf ( ’=’ )) != −1) { 17 String key = l i n e . substring (0 , equalsPos ) ; 18 String val = unescape ( l i n e . substring ( equalsPos + 1 ) ) ; 19 stringMap . put ( key , val ) ; 20 } 21 } 22 reader . c l o s e ( ) ; 23 return true ; 24 } Figure III.6. File parsing under CrashReporter 1 private void sendReport ( F i l e minidumpFile , Map<String , String> extras , F i l e e x t r a s F i l e ) { 2 final CheckBox includeURLCheckbox = ( CheckBox ) findViewById (R. id . i n c l u d e u r l ) ; 3 String spec = extras . get (SERVER URL KEY) ; 4 . . . 5 try { 6 URL url = new URL( spec ) ; 7 HttpURLConnection conn = ( HttpURLConnection ) url . openConnection ( ) ; 8 . . . 9 i f ( Build .VERSION.SDK INT >= 16 && includeURLCheckbox . isChecked ( ) ) { 10 sendPart ( os , boundary , ”Android Logcat ” , readLogcat ( ) ) ; 11 } 12 . . . 13 sendFile ( os , boundary , MINI DUMP PATH KEY, minidumpFile ) ; 14 . . . 15 i f ( conn . getResponseCode () == HttpURLConnection .HTTP OK) { 16 F i l e submittedDir = new F i l e ( g e t F i l e s D i r ( ) , SUBMITTED SUFFIX) ; 17 . . . 18 String crashid = responseMap . get ( ”CrashID ” ) ; 19 F i l e f i l e = new F i l e ( submittedDir , crashid + ” . txt ” ) ; 20 FileOutputStream f o s = new FileOutputStream ( f i l e ) ; 21 f o s . write ( ”Crash ID : ” . getBytes ( ) ) ; 22 f o s . write ( crashid . getBytes ( ) ) ; 23 f o s . c l o s e ( ) ; 24 } 25 . . . 26 } 27 . . . 28 } Figure III.7. sendReport under CrashReporter