Пример #1
0
CFX_ByteString CPDF_StandardSecurityHandler::GetUserPassword(
    const uint8_t* owner_pass,
    FX_DWORD pass_size,
    int32_t key_len) {
  CFX_ByteString okey = m_pEncryptDict->GetString(FX_BSTRC("O"));
  uint8_t passcode[32];
  FX_DWORD i;
  for (i = 0; i < 32; i++) {
    passcode[i] = i < pass_size ? owner_pass[i] : defpasscode[i - pass_size];
  }
  uint8_t digest[16];
  CRYPT_MD5Generate(passcode, 32, digest);
  if (m_Revision >= 3) {
    for (int i = 0; i < 50; i++) {
      CRYPT_MD5Generate(digest, 16, digest);
    }
  }
  uint8_t enckey[32];
  FXSYS_memset(enckey, 0, sizeof(enckey));
  FX_DWORD copy_len = key_len;
  if (copy_len > sizeof(digest)) {
    copy_len = sizeof(digest);
  }
  FXSYS_memcpy(enckey, digest, copy_len);
  int okeylen = okey.GetLength();
  if (okeylen > 32) {
    okeylen = 32;
  }
  uint8_t okeybuf[64];
  FXSYS_memset(okeybuf, 0, sizeof(okeybuf));
  FXSYS_memcpy(okeybuf, okey.c_str(), okeylen);
  if (m_Revision == 2) {
    CRYPT_ArcFourCryptBlock(okeybuf, okeylen, enckey, key_len);
  } else {
    for (int i = 19; i >= 0; i--) {
      uint8_t tempkey[32];
      FXSYS_memset(tempkey, 0, sizeof(tempkey));
      for (int j = 0; j < m_KeyLen; j++) {
        tempkey[j] = enckey[j] ^ i;
      }
      CRYPT_ArcFourCryptBlock(okeybuf, okeylen, tempkey, key_len);
    }
  }
  int len = 32;
  while (len && defpasscode[len - 1] == okeybuf[len - 1]) {
    len--;
  }
  return CFX_ByteString(okeybuf, len);
}
Пример #2
0
FX_BOOL CPDF_StandardSecurityHandler::CheckUserPassword(
    const uint8_t* password,
    FX_DWORD pass_size,
    FX_BOOL bIgnoreEncryptMeta,
    uint8_t* key,
    int32_t key_len) {
  CalcEncryptKey(m_pEncryptDict, password, pass_size, key, key_len,
                 bIgnoreEncryptMeta, m_pParser->GetIDArray());
  CFX_ByteString ukey = m_pEncryptDict
                            ? m_pEncryptDict->GetString(FX_BSTRC("U"))
                            : CFX_ByteString();
  if (ukey.GetLength() < 16) {
    return FALSE;
  }
  uint8_t ukeybuf[32];
  if (m_Revision == 2) {
    FXSYS_memcpy(ukeybuf, defpasscode, 32);
    CRYPT_ArcFourCryptBlock(ukeybuf, 32, key, key_len);
  } else {
    uint8_t test[32], tmpkey[32];
    FX_DWORD copy_len = sizeof(test);
    if (copy_len > (FX_DWORD)ukey.GetLength()) {
      copy_len = ukey.GetLength();
    }
    FXSYS_memset(test, 0, sizeof(test));
    FXSYS_memset(tmpkey, 0, sizeof(tmpkey));
    FXSYS_memcpy(test, ukey.c_str(), copy_len);
    for (int i = 19; i >= 0; i--) {
      for (int j = 0; j < key_len; j++) {
        tmpkey[j] = key[j] ^ i;
      }
      CRYPT_ArcFourCryptBlock(test, 32, tmpkey, key_len);
    }
    uint8_t md5[100];
    CRYPT_MD5Start(md5);
    CRYPT_MD5Update(md5, defpasscode, 32);
    CPDF_Array* pIdArray = m_pParser->GetIDArray();
    if (pIdArray) {
      CFX_ByteString id = pIdArray->GetString(0);
      CRYPT_MD5Update(md5, (uint8_t*)id.c_str(), id.GetLength());
    }
    CRYPT_MD5Finish(md5, ukeybuf);
    return FXSYS_memcmp(test, ukeybuf, 16) == 0;
  }
  if (FXSYS_memcmp((void*)ukey.c_str(), ukeybuf, 16) == 0) {
    return TRUE;
  }
  return FALSE;
}
Пример #3
0
void CJS_GlobalData::SaveGlobalPersisitentVariables() {
  uint32_t nCount = 0;
  CFX_BinaryBuf sData;
  for (const auto& pElement : m_arrayGlobalData) {
    if (pElement->bPersistent) {
      CFX_BinaryBuf sElement;
      MakeByteString(pElement->data.sKey, &pElement->data, sElement);
      if (sData.GetSize() + sElement.GetSize() > JS_MAXGLOBALDATA)
        break;

      sData.AppendBlock(sElement.GetBuffer(), sElement.GetSize());
      nCount++;
    }
  }

  CFX_BinaryBuf sFile;
  uint16_t wType = (uint16_t)(('X' << 8) | 'F');
  sFile.AppendBlock(&wType, sizeof(uint16_t));
  uint16_t wVersion = 2;
  sFile.AppendBlock(&wVersion, sizeof(uint16_t));
  sFile.AppendBlock(&nCount, sizeof(uint32_t));
  uint32_t dwSize = sData.GetSize();
  sFile.AppendBlock(&dwSize, sizeof(uint32_t));

  sFile.AppendBlock(sData.GetBuffer(), sData.GetSize());

  CRYPT_ArcFourCryptBlock(sFile.GetBuffer(), sFile.GetSize(), JS_RC4KEY,
                          sizeof(JS_RC4KEY));
  WriteFileBuffer(m_sFilePath.c_str(), (const FX_CHAR*)sFile.GetBuffer(),
                  sFile.GetSize());
}
Пример #4
0
void CPDF_CryptoHandler::CryptBlock(bool bEncrypt,
                                    uint32_t objnum,
                                    uint32_t gennum,
                                    const uint8_t* src_buf,
                                    uint32_t src_size,
                                    uint8_t* dest_buf,
                                    uint32_t& dest_size) {
  if (m_Cipher == FXCIPHER_NONE) {
    FXSYS_memcpy(dest_buf, src_buf, src_size);
    return;
  }
  uint8_t realkey[16];
  int realkeylen = 16;
  if (m_Cipher != FXCIPHER_AES || m_KeyLen != 32) {
    uint8_t key1[32];
    PopulateKey(objnum, gennum, key1);

    if (m_Cipher == FXCIPHER_AES) {
      FXSYS_memcpy(key1 + m_KeyLen + 5, "sAlT", 4);
    }
    CRYPT_MD5Generate(
        key1, m_Cipher == FXCIPHER_AES ? m_KeyLen + 9 : m_KeyLen + 5, realkey);
    realkeylen = m_KeyLen + 5;
    if (realkeylen > 16) {
      realkeylen = 16;
    }
  }
  if (m_Cipher == FXCIPHER_AES) {
    CRYPT_AESSetKey(m_pAESContext, 16, m_KeyLen == 32 ? m_EncryptKey : realkey,
                    m_KeyLen, bEncrypt);
    if (bEncrypt) {
      uint8_t iv[16];
      for (int i = 0; i < 16; i++) {
        iv[i] = (uint8_t)rand();
      }
      CRYPT_AESSetIV(m_pAESContext, iv);
      FXSYS_memcpy(dest_buf, iv, 16);
      int nblocks = src_size / 16;
      CRYPT_AESEncrypt(m_pAESContext, dest_buf + 16, src_buf, nblocks * 16);
      uint8_t padding[16];
      FXSYS_memcpy(padding, src_buf + nblocks * 16, src_size % 16);
      FXSYS_memset(padding + src_size % 16, 16 - src_size % 16,
                   16 - src_size % 16);
      CRYPT_AESEncrypt(m_pAESContext, dest_buf + nblocks * 16 + 16, padding,
                       16);
      dest_size = 32 + nblocks * 16;
    } else {
      CRYPT_AESSetIV(m_pAESContext, src_buf);
      CRYPT_AESDecrypt(m_pAESContext, dest_buf, src_buf + 16, src_size - 16);
      dest_size = src_size - 16;
      dest_size -= dest_buf[dest_size - 1];
    }
  } else {
    ASSERT(dest_size == src_size);
    if (dest_buf != src_buf) {
      FXSYS_memcpy(dest_buf, src_buf, src_size);
    }
    CRYPT_ArcFourCryptBlock(dest_buf, dest_size, realkey, realkeylen);
  }
}
Пример #5
0
void CJS_GlobalData::SaveGlobalPersisitentVariables() {
  FX_DWORD nCount = 0;
  CFX_BinaryBuf sData;

  for (int i = 0, sz = m_arrayGlobalData.GetSize(); i < sz; i++) {
    CJS_GlobalData_Element* pElement = m_arrayGlobalData.GetAt(i);
    if (pElement->bPersistent) {
      CFX_BinaryBuf sElement;
      MakeByteString(pElement->data.sKey, &pElement->data, sElement);

      if (sData.GetSize() + sElement.GetSize() > JS_MAXGLOBALDATA)
        break;

      sData.AppendBlock(sElement.GetBuffer(), sElement.GetSize());
      nCount++;
    }
  }

  CFX_BinaryBuf sFile;

  FX_WORD wType = (FX_WORD)(('X' << 8) | 'F');
  sFile.AppendBlock(&wType, sizeof(FX_WORD));
  FX_WORD wVersion = 2;
  sFile.AppendBlock(&wVersion, sizeof(FX_WORD));
  sFile.AppendBlock(&nCount, sizeof(FX_DWORD));
  FX_DWORD dwSize = sData.GetSize();
  sFile.AppendBlock(&dwSize, sizeof(FX_DWORD));

  sFile.AppendBlock(sData.GetBuffer(), sData.GetSize());

  CRYPT_ArcFourCryptBlock(sFile.GetBuffer(), sFile.GetSize(), JS_RC4KEY,
                          sizeof(JS_RC4KEY));
  WriteFileBuffer(m_sFilePath.c_str(), (const FX_CHAR*)sFile.GetBuffer(),
                  sFile.GetSize());
}
Пример #6
0
void CPDF_SecurityHandler::OnCreate(CPDF_Dictionary* pEncryptDict,
                                    CPDF_Array* pIdArray,
                                    const uint8_t* user_pass,
                                    uint32_t user_size,
                                    const uint8_t* owner_pass,
                                    uint32_t owner_size,
                                    FX_BOOL bDefault,
                                    uint32_t type) {
  int cipher = 0, key_len = 0;
  if (!LoadDict(pEncryptDict, type, cipher, key_len)) {
    return;
  }
  if (bDefault && (!owner_pass || owner_size == 0)) {
    owner_pass = user_pass;
    owner_size = user_size;
  }
  if (m_Revision >= 5) {
    int t = (int)time(nullptr);
    uint8_t sha[128];
    CRYPT_SHA256Start(sha);
    CRYPT_SHA256Update(sha, (uint8_t*)&t, sizeof t);
    CRYPT_SHA256Update(sha, m_EncryptKey, 32);
    CRYPT_SHA256Update(sha, (uint8_t*)"there", 5);
    CRYPT_SHA256Finish(sha, m_EncryptKey);
    AES256_SetPassword(pEncryptDict, user_pass, user_size, FALSE, m_EncryptKey);
    if (bDefault) {
      AES256_SetPassword(pEncryptDict, owner_pass, owner_size, TRUE,
                         m_EncryptKey);
      AES256_SetPerms(pEncryptDict, m_Permissions,
                      pEncryptDict->GetBooleanBy("EncryptMetadata", true),
                      m_EncryptKey);
    }
    return;
  }
  if (bDefault) {
    uint8_t passcode[32];
    for (uint32_t i = 0; i < 32; i++) {
      passcode[i] =
          i < owner_size ? owner_pass[i] : defpasscode[i - owner_size];
    }
    uint8_t digest[16];
    CRYPT_MD5Generate(passcode, 32, digest);
    if (m_Revision >= 3) {
      for (uint32_t i = 0; i < 50; i++)
        CRYPT_MD5Generate(digest, 16, digest);
    }
    uint8_t enckey[32];
    FXSYS_memcpy(enckey, digest, key_len);
    for (uint32_t i = 0; i < 32; i++) {
      passcode[i] = i < user_size ? user_pass[i] : defpasscode[i - user_size];
    }
    CRYPT_ArcFourCryptBlock(passcode, 32, enckey, key_len);
    uint8_t tempkey[32];
    if (m_Revision >= 3) {
      for (uint8_t i = 1; i <= 19; i++) {
        for (int j = 0; j < key_len; j++)
          tempkey[j] = enckey[j] ^ i;
        CRYPT_ArcFourCryptBlock(passcode, 32, tempkey, key_len);
      }
    }
    pEncryptDict->SetAtString("O", CFX_ByteString(passcode, 32));
  }
  CalcEncryptKey(m_pEncryptDict, (uint8_t*)user_pass, user_size, m_EncryptKey,
                 key_len, FALSE, pIdArray);
  if (m_Revision < 3) {
    uint8_t tempbuf[32];
    FXSYS_memcpy(tempbuf, defpasscode, 32);
    CRYPT_ArcFourCryptBlock(tempbuf, 32, m_EncryptKey, key_len);
    pEncryptDict->SetAtString("U", CFX_ByteString(tempbuf, 32));
  } else {
    uint8_t md5[100];
    CRYPT_MD5Start(md5);
    CRYPT_MD5Update(md5, defpasscode, 32);
    if (pIdArray) {
      CFX_ByteString id = pIdArray->GetStringAt(0);
      CRYPT_MD5Update(md5, (uint8_t*)id.c_str(), id.GetLength());
    }
    uint8_t digest[32];
    CRYPT_MD5Finish(md5, digest);
    CRYPT_ArcFourCryptBlock(digest, 16, m_EncryptKey, key_len);
    uint8_t tempkey[32];
    for (uint8_t i = 1; i <= 19; i++) {
      for (int j = 0; j < key_len; j++) {
        tempkey[j] = m_EncryptKey[j] ^ i;
      }
      CRYPT_ArcFourCryptBlock(digest, 16, tempkey, key_len);
    }
    CRYPT_MD5Generate(digest, 16, digest + 16);
    pEncryptDict->SetAtString("U", CFX_ByteString(digest, 32));
  }
}
Пример #7
0
void CPDF_StandardSecurityHandler::OnCreate(CPDF_Dictionary* pEncryptDict, CPDF_Array* pIdArray,
        FX_LPCBYTE user_pass, FX_DWORD user_size,
        FX_LPCBYTE owner_pass, FX_DWORD owner_size, FX_BOOL bDefault, FX_DWORD type)
{
    int cipher = 0, key_len = 0;
    if (!LoadDict(pEncryptDict, type, cipher, key_len)) {
        return;
    }
    if (bDefault && (owner_pass == NULL || owner_size == 0)) {
        owner_pass = user_pass;
        owner_size = user_size;
    }
    if (m_Revision >= 5) {
        int t = (int)time(NULL);
        FX_BYTE sha[128];
        CRYPT_SHA256Start(sha);
        CRYPT_SHA256Update(sha, (FX_BYTE*)&t, sizeof t);
        CRYPT_SHA256Update(sha, m_EncryptKey, 32);
        CRYPT_SHA256Update(sha, (FX_BYTE*)"there", 5);
        CRYPT_SHA256Finish(sha, m_EncryptKey);
        AES256_SetPassword(pEncryptDict, user_pass, user_size, FALSE, m_EncryptKey);
        if (bDefault) {
            AES256_SetPassword(pEncryptDict, owner_pass, owner_size, TRUE, m_EncryptKey);
            AES256_SetPerms(pEncryptDict, m_Permissions, pEncryptDict->GetBoolean(FX_BSTRC("EncryptMetadata"), TRUE), m_EncryptKey);
        }
        return;
    }
    if (bDefault) {
        FX_BYTE passcode[32];
        FX_DWORD i;
        for (i = 0; i < 32; i ++) {
            passcode[i] = i < owner_size ? owner_pass[i] : defpasscode[i - owner_size];
        }
        FX_BYTE digest[16];
        CRYPT_MD5Generate(passcode, 32, digest);
        if (m_Revision >= 3) {
            for (int i = 0; i < 50; i ++) {
                CRYPT_MD5Generate(digest, 16, digest);
            }
        }
        FX_BYTE enckey[32];
        FXSYS_memcpy32(enckey, digest, key_len);
        for (i = 0; i < 32; i ++) {
            passcode[i] = i < user_size ? user_pass[i] : defpasscode[i - user_size];
        }
        CRYPT_ArcFourCryptBlock(passcode, 32, enckey, key_len);
        FX_BYTE tempkey[32];
        if (m_Revision >= 3) {
            for (i = 1; i <= 19; i ++) {
                for (int j = 0; j < key_len; j ++) {
                    tempkey[j] = enckey[j] ^ (FX_BYTE)i;
                }
                CRYPT_ArcFourCryptBlock(passcode, 32, tempkey, key_len);
            }
        }
        pEncryptDict->SetAtString(FX_BSTRC("O"), CFX_ByteString(passcode, 32));
    }
    CalcEncryptKey(m_pEncryptDict, (FX_LPBYTE)user_pass, user_size, m_EncryptKey, key_len, FALSE, pIdArray);
    if (m_Revision < 3) {
        FX_BYTE tempbuf[32];
        FXSYS_memcpy32(tempbuf, defpasscode, 32);
        CRYPT_ArcFourCryptBlock(tempbuf, 32, m_EncryptKey, key_len);
        pEncryptDict->SetAtString(FX_BSTRC("U"), CFX_ByteString(tempbuf, 32));
    } else {
        FX_BYTE md5[100];
        CRYPT_MD5Start(md5);
        CRYPT_MD5Update(md5, defpasscode, 32);
        if (pIdArray) {
            CFX_ByteString id = pIdArray->GetString(0);
            CRYPT_MD5Update(md5, (FX_LPBYTE)(FX_LPCSTR)id, id.GetLength());
        }
        FX_BYTE digest[32];
        CRYPT_MD5Finish(md5, digest);
        CRYPT_ArcFourCryptBlock(digest, 16, m_EncryptKey, key_len);
        FX_BYTE tempkey[32];
        for (int i = 1; i <= 19; i ++) {
            for (int j = 0; j < key_len; j ++) {
                tempkey[j] = m_EncryptKey[j] ^ (FX_BYTE)i;
            }
            CRYPT_ArcFourCryptBlock(digest, 16, tempkey, key_len);
        }
        CRYPT_MD5Generate(digest, 16, digest + 16);
        pEncryptDict->SetAtString(FX_BSTRC("U"), CFX_ByteString(digest, 32));
    }
}
Пример #8
0
void CJS_GlobalData::LoadGlobalPersistentVariables() {
  uint8_t* pBuffer = nullptr;
  int32_t nLength = 0;

  LoadFileBuffer(m_sFilePath.c_str(), pBuffer, nLength);
  CRYPT_ArcFourCryptBlock(pBuffer, nLength, JS_RC4KEY, sizeof(JS_RC4KEY));

  if (pBuffer) {
    uint8_t* p = pBuffer;
    uint16_t wType = *((uint16_t*)p);
    p += sizeof(uint16_t);

    if (wType == (uint16_t)(('X' << 8) | 'F')) {
      uint16_t wVersion = *((uint16_t*)p);
      p += sizeof(uint16_t);

      ASSERT(wVersion <= 2);

      uint32_t dwCount = *((uint32_t*)p);
      p += sizeof(uint32_t);

      uint32_t dwSize = *((uint32_t*)p);
      p += sizeof(uint32_t);

      if (dwSize == nLength - sizeof(uint16_t) * 2 - sizeof(uint32_t) * 2) {
        for (int32_t i = 0, sz = dwCount; i < sz; i++) {
          if (p > pBuffer + nLength)
            break;

          uint32_t dwNameLen = *((uint32_t*)p);
          p += sizeof(uint32_t);

          if (p + dwNameLen > pBuffer + nLength)
            break;

          CFX_ByteString sEntry = CFX_ByteString(p, dwNameLen);
          p += sizeof(char) * dwNameLen;

          JS_GlobalDataType wDataType =
              static_cast<JS_GlobalDataType>(*((uint16_t*)p));
          p += sizeof(uint16_t);

          switch (wDataType) {
            case JS_GlobalDataType::NUMBER: {
              double dData = 0;
              switch (wVersion) {
                case 1: {
                  uint32_t dwData = *((uint32_t*)p);
                  p += sizeof(uint32_t);
                  dData = dwData;
                } break;
                case 2: {
                  dData = *((double*)p);
                  p += sizeof(double);
                } break;
              }
              SetGlobalVariableNumber(sEntry, dData);
              SetGlobalVariablePersistent(sEntry, true);
            } break;
            case JS_GlobalDataType::BOOLEAN: {
              uint16_t wData = *((uint16_t*)p);
              p += sizeof(uint16_t);
              SetGlobalVariableBoolean(sEntry, (bool)(wData == 1));
              SetGlobalVariablePersistent(sEntry, true);
            } break;
            case JS_GlobalDataType::STRING: {
              uint32_t dwLength = *((uint32_t*)p);
              p += sizeof(uint32_t);

              if (p + dwLength > pBuffer + nLength)
                break;

              SetGlobalVariableString(sEntry, CFX_ByteString(p, dwLength));
              SetGlobalVariablePersistent(sEntry, true);
              p += sizeof(char) * dwLength;
            } break;
            case JS_GlobalDataType::NULLOBJ: {
              SetGlobalVariableNull(sEntry);
              SetGlobalVariablePersistent(sEntry, true);
            }
            case JS_GlobalDataType::OBJECT:
              break;
          }
        }
      }
    }
    FX_Free(pBuffer);
  }
}
Пример #9
0
void CJS_GlobalData::LoadGlobalPersistentVariables() {
  uint8_t* pBuffer = NULL;
  int32_t nLength = 0;

  LoadFileBuffer(m_sFilePath.c_str(), pBuffer, nLength);
  CRYPT_ArcFourCryptBlock(pBuffer, nLength, JS_RC4KEY, sizeof(JS_RC4KEY));

  if (pBuffer) {
    uint8_t* p = pBuffer;
    FX_WORD wType = *((FX_WORD*)p);
    p += sizeof(FX_WORD);

    // FX_WORD wTemp = (FX_WORD)(('X' << 8) | 'F');

    if (wType == (FX_WORD)(('X' << 8) | 'F')) {
      FX_WORD wVersion = *((FX_WORD*)p);
      p += sizeof(FX_WORD);

      ASSERT(wVersion <= 2);

      FX_DWORD dwCount = *((FX_DWORD*)p);
      p += sizeof(FX_DWORD);

      FX_DWORD dwSize = *((FX_DWORD*)p);
      p += sizeof(FX_DWORD);

      if (dwSize == nLength - sizeof(FX_WORD) * 2 - sizeof(FX_DWORD) * 2) {
        for (int32_t i = 0, sz = dwCount; i < sz; i++) {
          if (p > pBuffer + nLength)
            break;

          FX_DWORD dwNameLen = *((FX_DWORD*)p);
          p += sizeof(FX_DWORD);

          if (p + dwNameLen > pBuffer + nLength)
            break;

          CFX_ByteString sEntry = CFX_ByteString(p, dwNameLen);
          p += sizeof(char) * dwNameLen;

          FX_WORD wDataType = *((FX_WORD*)p);
          p += sizeof(FX_WORD);

          switch (wDataType) {
            case JS_GLOBALDATA_TYPE_NUMBER: {
              double dData = 0;
              switch (wVersion) {
                case 1: {
                  FX_DWORD dwData = *((FX_DWORD*)p);
                  p += sizeof(FX_DWORD);
                  dData = dwData;
                } break;
                case 2: {
                  dData = *((double*)p);
                  p += sizeof(double);
                } break;
              }
              SetGlobalVariableNumber(sEntry, dData);
              SetGlobalVariablePersistent(sEntry, TRUE);
            } break;
            case JS_GLOBALDATA_TYPE_BOOLEAN: {
              FX_WORD wData = *((FX_WORD*)p);
              p += sizeof(FX_WORD);
              SetGlobalVariableBoolean(sEntry, (bool)(wData == 1));
              SetGlobalVariablePersistent(sEntry, TRUE);
            } break;
            case JS_GLOBALDATA_TYPE_STRING: {
              FX_DWORD dwLength = *((FX_DWORD*)p);
              p += sizeof(FX_DWORD);

              if (p + dwLength > pBuffer + nLength)
                break;

              SetGlobalVariableString(sEntry, CFX_ByteString(p, dwLength));
              SetGlobalVariablePersistent(sEntry, TRUE);
              p += sizeof(char) * dwLength;
            } break;
            case JS_GLOBALDATA_TYPE_NULL: {
              SetGlobalVariableNull(sEntry);
              SetGlobalVariablePersistent(sEntry, TRUE);
            }
          }
        }
      }
    }
    FX_Free(pBuffer);
  }
}