- 1. Install Google Chrome 32bit version (version 32 is easy to debug with tools)
- 2. Chrome's built-in CDM is Widevine (acquired a few years ago). The directory is Google Chrome Application 58.0.3029.110 WidevineCdm, and the subdirectory _platform_specific win_x86 has the following two dlls:
- Widevinecdm.dll - widevine core module. The exported functions are: InitializeCdmModule_4, InitializeCdmModule, CreateCdmInstance, GetCdmVersion, GetHandleVerifier
- Widevinecdmadapter.dll - PPAPI plug-in standard adapter library. Export functions include: PPP_GetInterface, PPP_InitializeModule, PPP_Shutdown Module
- 3. Flow of DRM video playing under normal conditions:
- Chrome -> Widevine CDM Adapter(widevinecdmadapter.dll) -> Widevine CDM Module (widevinecdm.dll)
- Widevinecdmadapter.dll calls the exported function CreateCdmInstance of widevinecdm.dll to create a CDM instance
- 4. The CDM framework is the standard of Google Chrome, so the API parameters and interfaces have C++ include File, such as API CreateCdmInstance:
- CDM_API void* CreateCdmInstance(int cdm_interface_version, const char* key_system, uint32_t key_system_size, GetCdmHostFunc get_cdm_host_func, void* user_data);
-
- CDM instances should inherit from class ContentDecryptionModule_8, view class ContentDecryptionModule_8, found a very important function: Decrypt, which is mainly used to transfer encrypted data into and decrypted data out. My God, as long as this function is intercepted, it will be done!!!
- class CDM_CLASS_API ContentDecryptionModule_8 {
-
-
-
-
-
-
-
-
-
- virtual Status Decrypt( const InputBuffer& encrypted_buffer,
- DecryptedBlock* decrypted_buffer) = 0;
- };
5. After understanding the API parameters and class definitions, it is assumed that if the decrypted data is intercepted when widevinecdmadapter.dll and widevinecdm.dll call each other, the DRM protection mechanism can be bypassed
6. Start writing a DLL called CdmProxy.dll, my own CreateCdmInstance function, which does not explain:
- extern "C" __declspec(dllexport) void * CDMAPI_DEFINE my_CreateCdmInstance(int cdm_interface_version, const char* key_system,
- uint32_t key_system_size, GetCdmHostFunc get_cdm_host_func, void* user_data)
- {
- gHostUserData = user_data;
- wsprintf(wchLog, L "CdmProxy - call CreateCdmInstance(%d, %S, %d, 0x%08X, 0x%08X)" ,
- cdm_interface_version, key_system, key_system_size, get_cdm_host_func, user_data);
- OutputDebugStringW(wchLog);
- void *p = pCreateCdmInstance(cdm_interface_version, key_system, key_system_size, get_cdm_host_func, user_data);
-
- cdm::ContentDecryptionModule_8 *pCdmModule = (cdm::ContentDecryptionModule_8 *)(p);
- MyContentDecryptionModuleProxy *pMyCdmModule = new MyContentDecryptionModuleProxy(pCdmModule);
-
- return pMyCdmModule;
- }
My generation {passes} {filters} manages the class MyContentDecryptionModuleProxy, and the code does not explain:
// class MyContentDecryptionModuleProxy
- class MyContentDecryptionModuleProxy : public cdm::ContentDecryptionModule_8
- {
- public :
- MyContentDecryptionModuleProxy(cdm::ContentDecryptionModule_8 *pCdm)
- {
- mCdm = pCdm;
- }
- private :
- cdm::ContentDecryptionModule_8 *mCdm;
-
- public :
-
- virtual cdm::Status Decrypt( const cdm::InputBuffer& encrypted_buffer, cdm::DecryptedBlock* decrypted_buffer)
- {
- cdm::Status status = cdm::kSuccess;
- codelive();
- DebugDecryptBreak(encrypted_buffer.iv, encrypted_buffer.key_id, encrypted_buffer.data);
- status = mCdm->Decrypt(encrypted_buffer, decrypted_buffer);
-
- string strIV = data2HexString(( const char *)encrypted_buffer.iv, encrypted_buffer.iv_size);
- string strEncData = data2HexString(( const char *)encrypted_buffer.data, min(encrypted_buffer.data_size, 32));
- string strDecData = data2HexString(( const char *)decrypted_buffer->DecryptedBuffer()->Data(),
- min(decrypted_buffer->DecryptedBuffer()->Size(), 32));
- wsprintf(wchLog, L "CdmProxy - call Decrypt(IV:%S, encData(%d):%S, decData(%d):%S)" ,
- strIV.c_str(), encrypted_buffer.data_size, strEncData.c_str(),
- decrypted_buffer->DecryptedBuffer()->Size(), strDecData.c_str());
- OutputDebugStringW(wchLog);
-
- if (mEncFile == NULL)
- {
- mEncFile = fopen ( "d:\\cdm_enc.bin" , "wb" );
- }
- if (mEncFile != NULL)
- {
- fwrite(encrypted_buffer.data, 1, encrypted_buffer.data_size, mEncFile);
- }
- if (mDecFile == NULL)
- {
- mDecFile = fopen ( "d:\\cdm_dec.bin" , "wb" );
- }
- if (mDecFile != NULL)
- {
- fwrite(decrypted_buffer->DecryptedBuffer()->Data(), 1, decrypted_buffer->DecryptedBuffer()->Size(), mDecFile);
- }
- return status;
- }
- };
7. After the core code is written, we need to solve the problem of DLL loading. We tried several simple ways to rename widevinecdm.dll and widevinecdmadapter.dll to widevinecdm_org.dll and widevinecdmadapter_org.dll, and then wrote a DLL by ourselves, export the same API as widevinecdm.dll or widevinecdmadapter.dll, and then call the original DLL, However, this method is not feasible because of Chrome's security sandbox. All plug-ins are loaded sandbox process space, and sensitive APIs, such as ReadProcessMemory, CeateFile, OutputDebugString, cannot be used Sandbox Escape is a very high bonus for Google, up to $15000
8. Since all processes are created by Chrome, I directly started with chrome.exe, directly patched chrome.exe, and asked it to load my CdmProxy.dll. The result was successful. After all, the sandbox mechanism was not enabled when Chrome started, so it solved the DLL loading problem without spending too much technology
9. Change the API interception method, dynamically patch widevinecdmadapter.dll, change the address of calling API CreateCdmInstance to my own API my_CreateCdmInstance, and then perform the following processing when loading the DLL:
- BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
- {
- switch (ul_reason_for_call)
- {
- case DLL_PROCESS_ATTACH:
- {
- hWideVineCdm = LoadLibraryW(L "{PATH}\\widevinecdm.dll" );
-
- pInitializeCdmModule_4 = (InitializeCdmModule_4Func)GetProcAddress(hWideVineCdm, "InitializeCdmModule_4" );
- pDeinitializeCdmModule = (DeinitializeCdmModuleFunc)GetProcAddress(hWideVineCdm, "DeinitializeCdmModule" );
- pCreateCdmInstance = (CreateCdmInstanceFunc)GetProcAddress(hWideVineCdm, "CreateCdmInstance" );
- pGetCdmVersion = (GetCdmVersionFunc)GetProcAddress(hWideVineCdm, "GetCdmVersion" );
-
- hWideVineCdmAdapter = LoadLibraryW(L "{PATH}\\widevinecdmadapter.dll" );
- if (hWideVineCdmAdapter != NULL)
- {
- DWORD dwSrcAddr = (DWORD)hWideVineCdmAdapter + 0x0000446D;
- const BYTE chVerify[] = { 0xFF, 0x15 };
- BOOL isOK = patch_DsCallFunction(dwSrcAddr, (DWORD)my_CreateCdmInstance, chVerify, sizeof(chVerify));
- wsprintf(wchLog, L "CdmProxy - patch CreateCdmInstance, Address:0x%08X-0x%08X, %s." ,
- dwSrcAddr, (uint32_t)my_CreateCdmInstance,
- isOK ? L "OK" : L "FAILED" );
- OutputDebugStringW(wchLog);
- }
- }
- break ;
- }
- }
10. Then test and play a DASH video with DRM protection:
https://shaka-player-demo.appspo ... gleKey/Manifest.mpd
It is found that the file has not been saved and LOG has not been output. It must be that the safety sandbox works.
11. It is also necessary to escape the sandbox. After research, it is found that it is not necessary at all. Just add no sandbox to the chrome startup parameter. My God, why should we provide such a back door!!!
12. Play the encrypted video again, and the file is saved successfully. LOG is also output successfully. After verification, the decrypted data is consistent with the previously unencrypted data.
13. Google Chrome's CDM has been cracked in this way. It is very simple to bypass the Widevine DRM algorithm. This should be a serious problem in the framework design of Chrome CDM. It is estimated that it is not easy to change it.
This is LOG data:
- ( "CdmProxy - call CreateCdmInstance(8, com.widevine.alpha, 18, 0x70F86310, 0x00D75A88)" ) zero point zero zero zero one three nine nine
- ( "CdmProxy - call CreateCdmInstance(8, com.widevine.alpha, 18, 0x70F86310, 0x00D757C8)" ) zero point zero zero zero zero nine three seven
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818F00000000000000000, encData(348):FFF158402B9FFC2FF05300F2BF83E9A0, decData(0):)" ) zero point zero zero zero one three five zero
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818F00000000000000000, encData(348):FFF158402B9FFC2FF05300F2BF83E9A0, decData(348):FFF158402B9FFC00D03403E95B8639BD)" ) zero point zero zero zero one three three five
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818F00000000000000000, encData(348):FFF158402B9FFC2FF05300F2BF83E9A0, decData(348):FFF158402B9FFC00D03403E95B8639BD)" ) zero point zero zero zero one zero three two
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818F10000000000000000, encData(348):FFF158402B9FFC487380B8930FFFAB41, decData(348):FFF158402B9FFC00F43420C24620902C)" ) zero point zero zero zero one three nine two
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818F20000000000000000, encData(349):FFF158402BBFFC1175E15FE4B6154B30, decData(349):FFF158402BBFFC00FA342D90762A3188)" ) zero point zero zero zero one zero three two
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818F30000000000000000, encData(348):FFF158402B9FFCC45D5715E87235E5CF, decData(348):FFF158402B9FFC00F8342CEC825A2D85)" ) zero point zero zero zero zero nine nine four
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818F40000000000000000, encData(348):FFF158402B9FFC6749FBAF64926471DE, decData(348):FFF158402B9FFC00F83421884529290A)" ) zero point zero zero zero zero eight eight zero
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818F50000000000000000, encData(349):FFF158402BBFFCF8132EFC31C186DDE1, decData(349):FFF158402BBFFC00F2342D9049124988)" ) zero point zero zero zero one zero eight eight
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818F60000000000000000, encData(348):FFF158402B9FFC82EDA0BD4AB7158938, decData(348):FFF158402B9FFC00EE342D7475223D85)" ) zero point zero zero zero one zero three five
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818F70000000000000000, encData(348):FFF158402B9FFC4B2C585CC10F74036E, decData(348):FFF158402B9FFC00F4342D74662A2088)" ) zero point zero zero zero one five five five
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818F80000000000000000, encData(349):FFF158402BBFFCCF33665AC4E219EC92, decData(349):FFF158402BBFFC00FA342E30547B0604)" ) zero point zero zero zero one four nine four
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818F90000000000000000, encData(348):FFF158402B9FFC2C9A7362594261CE23, decData(348):FFF158402B9FFC00F4342E305429150A)" ) zero point zero zero zero four zero three five
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818FA0000000000000000, encData(348):FFF158402B9FFC1905A086AE3CEF0AEC, decData(348):FFF158402B9FFC00EE342E3456391906)" ) zero point zero zero zero five nine one three
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818FB0000000000000000, encData(349):FFF158402BBFFC8D0EB865013262FB6E, decData(349):FFF158402BBFFC00F6342E34563A2186)" ) zero point zero zero zero one four seven nine
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818FC0000000000000000, encData(348):FFF158402B9FFC484211C612F22283FB, decData(348):FFF158402B9FFC0102342E74482A2E02)" ) zero point zero zero zero two five zero seven
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818FD0000000000000000, encData(348):FFF158402B9FFC283122B1DDE740DAC2, decData(348):FFF158402B9FFC0104342EB464190D08)" ) zero point zero zero zero three zero one one
- ( "CdmProxy - call Decrypt(IV:6CD1F4FBCE5818FE0000000000000000, encData(349):FFF158402BBFFCAC8759D48FF1A258A3, decData(349):FFF158402BBFFC010E342ED04A1A2982)" ) zero point zero zero zero three zero nine five
DRM has done a lot of strict protection mechanisms and algorithms for many years, but it was betrayed by Zhu's teammates.
The purpose of making this research public is to make the majority of video companies not think DRM is very secure. Sometimes DRM is really vulnerable. If there is a loophole in a certain link, they will also face great security problems.
After I submitted it, the Chromium team replied quickly. They confirmed that this is a major security issue and affects all operating systems running Chrome, including Linux, Windows, Chrome, Mac, and so on However, another employee said that this was a known problem and provided an issue number: 658022. However, I checked indefinitely whether the vulnerability content was consistent with my submission. Later, I sent an email to several members of the Google team, saying that since it was a known problem, it was not in line with the reward rules, so I could also publish details to let the video content company pay attention to this problem, so as to discuss a more secure solution as soon as possible. If this article is not suitable for publication, please let me know. Thank you.