Exploiting the Dolphin Browser for Android’s Backup & Restore Feature

Exploiting the Dolphin Browser for Android’s Backup & Restore Feature


On December 11, 2017 VerSprite published the following advisory for the Dolphin Browser.


[VS-2017-001] Dolphin Browser for Android Backup & Restore Arbitrary File Write Vulnerability
Dolphin Browser for Android < 12.0.2
Vulnerability Details
The Backup and Restore feature in Mobotap’s Dolphin Browser for Android 12.0.2, suffers from an arbitrary file write vulnerability when attempting to restore browser settings from a malicious Dolphin Browser backup file. This arbitrary file write vulnerability, allows an attacker to overwrite a specific executable in the Dolphin Browser’s data directory with a crafted malicious executable. Every time the Dolphin Browser is launched, it will attempt to run the malicious executable from disk, thus executing the attacker’s code.

In this blog post we will cover the vulnerability’s technical details and how to exploit the Dolphin Browser for Android’s Backup and Restore feature.

Attack Surface Analysis

I’ve always been really interested in alternative Android Browsers because of their feature sets. These features introduce additional attack surfaces that in some cases can result in crushing vulnerabilities. In the case of the Dolphin Browser for Android, it has plenty of features to go around, giving it a large attack surface.

The vulnerability was discovered by searching for ZipInputStream and ZipFileAPI usage. The com.dolphin.browser.util.g class contains a method with cross references to the ZipInputStream class.

try {
v1_2 = new ZipInputStream(((InputStream)v3));
try {
File v0_6 = new File(this.b.getApplicationInfo().dataDir);
while(true) {
ZipEntry v2_1 = v1_2.getNextEntry();
if(v2_1 == null) {
BufferedOutputStream v2_2 = new BufferedOutputStream(new FileOutputStream(new File(v0_6 + File.separator + v2_1.getName())));
IOUtilities.copy(((InputStream)v1_2), ((OutputStream)v2_2));

For the zip archive that is being processed here, the code loops through each zip entry and writes that entry as a new file to disk using the application’s data directory as the root directory. If we control the contents of the archive, this will result in an arbitrary file write primitive. Now we need to figure out what this functionality is being used for! After looking through the com.dolphin.util.gclass, I found more interesting code.

try {
Log.e(&amp;amp;quot;BackupHelper&amp;amp;quot;, ((Throwable)v0));
throw new a(((Throwable)v0));
catch(Throwable v0_4) {
goto label_135;

What is this browser trying to backup? I immediately jumped into the AndroidManifest.xml and found the following Activity.

&amp;amp;amp;amp;amp;lt;activity android_configChanges=&amp;amp;quot;keyboard|keyboardHidden|orientation|screenSize&amp;amp;quot; android_name=&amp;amp;quot;mobi.mgeek.TunnyBrowser.BackupRestoreActivity&amp;amp;quot; android_theme=&amp;amp;quot;@android:style/Theme.NoTitleBar&amp;amp;quot;/&amp;amp;amp;amp;amp;gt;

After digging around in the browser’s UI, I found exactly what I was looking for.


backup restore

Alright, now I was getting somewhere. After playing around with the Backup and Restore feature, I was able to extract the following information about its functionality.

  • Basic browser settings can be saved and restored
  • The default location for the backup file is always /storage/emulated/0
  • The backup file has a custom file extension -> .dbk
  • Setting a password on the backup is not required
  • The backup’s name is [ year:month:day:time ]
  • When you try and restore a backup, the browser will always access the most recently generated backup

The most important piece of information I was able to extract, was that the backup file was indeed the zip archive being processed in the vulnerable code, which results in the file write vulnerability!

Backup File Format


After continuing to reverse engineer the backup file, I quickly figured out that the backups were being encrypted. However, this also had a flaw in its implementation. The browser encrypts the backup by handling two possible scenarios.

  • Password set
  • Password not set

If a password is set for the backup, first, that password is concatenated with a static key, then, the resulting string is hashed, and finally, the bytes from the hash are padded with 0x10 extra bytes and passed to the SecretKeySpec()constructor. If a password is not set, the entire process is exactly the same, except in the place of the password, another static key is used.


The backup file also includes a custom header, which during the restoration process is used in order to verify that the file being restored is a legitimate backup created by the browser.

0D00010F 01000000 00010000 [sig]

The 0D00010F value can be considered the backup file’s magic identifier. The header also includes a signature directly after the value 00010000. The signature is generated through the same encryption implementation used for the backup file itself.

Finally we take the header’s contents and generate a CRC checksum, which is then appended to the file after the signature

0D00010F 01000000 00010000 [sig] [checksum]

When a backup file is restored, a CRC checksum is generated from the file’s header and then compared against the appended CRC checksum after the header’s signature. If these do not match, the restoration process is aborted.

Now we know how to generate a valid backup file.

Backup File Generation

  • Create a new zip archive containing our desired contents
  • Append the header to the zip archive -> 0D00010F 01000000 00010000 [sig]
  • Append the header’s checksum
  • Encrypt the archive


File Write Targets

Having an arbitrary file write primitive in the context of an Android application is only as good as the files the application includes in its package. I typically look for the following when I am trying to turn a file write into code execution:

  • DEX
  • JAR
  • ELF

Luckily the Dolphin Browser includes a great target for our file write primitive.

/data/data/mobi.mgeek.TunnyBrowser/files # ls -la
-rwxr-xr-x u0_a195  u0_a195      9496 2017-12-12 14:26 watch_server
$ file watch_server
watch_server: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /system/bin/linker, stripped

We should have zero problems overwriting the watch_server with something we control in the backup file, but how and when is the watch_server executed by the browser?


In order to figure out when the watch_server is executed, I simply searched through the browser for Runtime.getRuntime().exec() references. I found that the watch_server was getting executed every time the browser was launched! Talk about persistence and energy.

Proof of Concept

The biggest caveat to exploiting the Backup and Restore feature in the Dolphin Browser is the requirement for user interaction. The victim must restore a malicious backup that we’ve created and written to the sdcard. I wasn’t able to find a way to trigger the restoration process via IPC or through other means. The only upside to this fact, is the ability to potentially control which backup is selected.

  • The backup’s name is [ year:month:day:time ]
  • When you try and restore a backup, the browser will always access the most recently generated backup

After the restoration process is finished, the browser does a quick restart, which immediately executes our payload!

[+] Building ... [!]
[armeabi-v7a] Install        : payload =&amp;amp;amp;amp;amp;gt; libs/armeabi-v7a/payload
[+] Creating tmp.zip and injecting payload [!]
[+] Launching backup file format generation [!]
[+] Generating cipher [!]
[+] key --&amp;amp;amp;amp;amp;gt; 95acde261f3e09d281498163958dd366
[+] Building backup  --&amp;amp;amp;amp;amp;gt; ./backup.dbk [!]
[+] Encrypting and saving backup [!]
[+] Cleaning up [!]
[+] Pushing backup.dbk to the device [!]
backup.dbk: 1 file pushed. 1.0 MB/s (13926 bytes in 0.014s)
[+] Waiting on restore process ... [!]
--------- beginning of system
--------- beginning of main
V/FlipperFlapper(14975): uid=10195(u0_a195) gid=10195(u0_a195) groups=1015(sdcard_rw),1028(sdcard_r),3003(inet),9997(everybody),50195(all_a195) context=u:r:untrusted_app:s0