[하루한줄] CVE-2025-24919 : Dell ControlVault3의 입력 검증 미흡으로 인한 RCE 취약점

URL

Target

  • Broadcom BCM5820X
  • Dell ControlVault3 5.14.3.0
  • Dell ControlVault3 Driver and Firmware prior to 5.15.10.14
  • Dell ControlVault3 Plus Driver and Firmware prior to 6.2.26.36

Explain

Dell ControlVault3은 생체인식, 보안코드 등을 저장하는 하드웨어 보안 솔루션으로, Windows 환경에서는 bcmbipdll.dll 내 cvhDecapsulateCmd 함수를 통해 펌웨어와 통신합니다.

먼저 펌웨어에 명령어를 보낼 때 InitParam_List 함수를 통해 두 개의 파라미터가 생성되고, 모두 타입과 길이를 포함한 8바이트 헤더와 데이터로 이뤄져있습니다. 이 두 데이터는 하나는 인풋, 하나는 아웃풋으로 사용되는데, 펌웨어로부터 전달받는 아웃풋은 cvhDecapsulateCmd 함수를 통해 데이터가 처리됩니다.

__int64 __fastcall cvhDecapsulateCmd(
        cv_out_buffer *encapsulated_cmd, //data coming from the firmware
        cv_session_info *session,
        unsigned int *pNumParams,  // ouput of the function, total number of parameters unpacked
        cv_param_list_entry **dst_params)  //[0]
{
    (...) // skipped variable definition

  dst_params_ = dst_params;
  pNumParams_ = pNumParams;
  cur_param_num = 0i64;

  if ( (encapsulated_cmd->header.field_A & 1) == 0 || (decryptedCmdSize = 0, encapsulated_cmd->header.field_8 == 4) )
  {
    cur_pos_buffer = &encapsulated_cmd->first_byte;
    v25 = &encapsulated_cmd->first_byte + encapsulated_cmd->header.cmd_size;
    v39 = v25;
    goto SKIP_DECRYPTION;
  }
    (...) // skipped case where data is encrypted

SKIP_DECRYPTION:
      if ( cur_pos_buffer >= v25 )
      {
        *pNumParams_ = cur_param_num;
        goto DONE;
      }
      param_type = cur_pos_buffer->paramType;
      if ( cur_pos_buffer->paramType >= 2 && cur_pos_buffer->paramType - 2 >= 2 )   // ParamType is between 0 and 3
      {
        if ( bLoggingStuff )
        {
          log_more_stuff(
            "d:\\BuildAutomation\\BCMSecurity_int_cs_5.14\\sw\\phoenixII\\host\\windows\\BCMSecurity\\BCMSecurity\\src\\B"
            "CMIDll\\BCMILib_Src\\HelperFunctions.c");
          log_stuff("%s %s%s:%d %s\n", "Invalid Status Type", v51, &pNumParams_, 888i64, "is_valid_param_type");
        }
        status_ = INVALID_PARAM_TYPE;
        goto DONE;
      }
      status_ = 0;
      status = encapsulated_cmd->header.status;
      if ( !status || status == 143 || status == 41 || status == 154 && param_type != CV_PACK_MAGIC_BUFFER )
      {
COPY_PARAM_CONTENT:
        Size = 0i64;
        param_size = cur_pos_buffer->paramLen + 8;
        decryptedCmdSize = param_size;
        v49 = param_size;
        param_buffer = j__malloc_base(param_size);    // Allocates a buffer with a +8 size to account for the header (type, length)
        param_buffer_ = param_buffer;
        Size = param_buffer;
        if ( !param_buffer )
        {
          status_ = MEM_ERROR;
          free_stuff(&Size, 1u);
          goto DONE;
        }
        memset(param_buffer, 0, param_size);
        v32 = param_size;
        param_size_aligned = param_size & 0xFFFFFFFC;
        param_size_reminder_non_aligned = v32 & 3;
        if ( !param_size_reminder_non_aligned )
          param_size_aligned = decryptedCmdSize;
        v35 = param_buffer_;
        if ( param_size_aligned >= 4 )
        {
          memmove(param_buffer_, cur_pos_buffer, param_size_aligned);    // [1] copy to param_buffer the parameter entry, including the header coming from the cmd header
          v35 = &param_buffer_[param_size_aligned];
          Size = &param_buffer_[param_size_aligned];
          cur_pos_buffer = (cur_pos_buffer + param_size_aligned);
        }

cvhDecapsulateCmd 함수에서는 먼저 인자로 전달된 데이터를 헤더와 함께 저장하기 위해 인자로 전달된 헤더에 저장된 길이보다 8 크게 할당하고 저장합니다.

  if ( param_size_reminder_non_aligned )
  {
    memmove(v35, cur_pos_buffer, param_size_reminder_non_aligned);
    cur_pos_buffer = (cur_pos_buffer + 4);
  }
  Size = param_buffer_;
  v36 = &dst_params_[cur_param_num];
  **v36 = 0i64;
  memmove(*v36, param_buffer_, v49);   //[2] copy the whole parameter entry including its header to the cur_param_num entry of the dst_params buffer (aka the decapsulated parameters) 
  if ( param_buffer_ )
    free(param_buffer_);
  goto NEXT_PARAM;
}

[1]에서 복사한 헤더 정보가 포함된 데이터를 출력 구조체 리스트로 순차적으로 복사합니다.

           (...) // skipped handling of other edge cases

NEXT_PARAM:
      cur_param_num = (cur_param_num + 1);
      cur_param_num_ = cur_param_num;
      if ( cur_param_num > 0x18 )   // [3]  there's no check if the number of parameters parsed doesn't go beyond the request number of parameters in *pNumParameters, leading to a potential wild memcpy at [2]
      {
        status_ = CV_STATUS_TOO_MANY_PARAM;
        goto DONE;
      }
    }
  }

반복적으로 펌웨어로부터 수신된 데이터를 복사하면서 0x18 개 이상 복사할 경우 데이터 복사를 중단합니다. 하지만 출력 데이터 구조체 리스트에 몇 개의 구조체가 할당되었고 복사한 갯수가 해당 리스트 길이보다 높은지는 검사하지 않습니다. 만약 악의적으로 조작된 펌웨어와 통신할 경우, 이를 통해 OOB를 만들어낼 수 있습니다.