mirror of
https://github.com/Satsuoni/widevine-l3-guesser
synced 2025-02-25 00:15:23 +01:00
Added Ghidra scripts. Starting work on Readme
This commit is contained in:
parent
1aae32eb80
commit
8c931c4b83
90
README.md
90
README.md
@ -39,8 +39,98 @@ ffmpeg -decryption_key 100b6c20940f779a4589152b57d2dacb -i encrypted_media.mp4 -
|
||||
|
||||
### Starting point
|
||||
|
||||
It is my honest opinion that DRM is a malignant tumor growing upon various forms of media, and that people that either implement or enforce implementation are morally repugnant and do no good to society. With that in mind, I was sad to learn in May 2021 that the original extension would soon be rendered obsolete. I found myself with some free time on my hands, and so I decided to try and replicate original key extraction. Unfortunately, there was not much data pertaining to what process the original's authors used, and even some confusion as to [who was the one who performed extraction](https://github.com/tomer8007/widevine-l3-decryptor/issues/14). Nevertheless, I decided to give it a go, and hopefully boost my flagging self-confidence a little. I did not succeed in either of those tasks, but I managed to write a barely-functioning decryptor, and decided to document the steps I followed, in case they are of use to somebody else.
|
||||
|
||||
### Reverse enginering and emulating
|
||||
|
||||
In order to deal with executable, I decided to use [Ghidra](https://github.com/NationalSecurityAgency/ghidra), despite its association with NSA, mostly because it is free and has most features that I wanted. I also wrote a simple snippet to be able to debug dll.
|
||||
|
||||
```
|
||||
lib = LoadLibrary(L"widevinecdm_new.dll");
|
||||
if (lib != NULL)
|
||||
{
|
||||
InitializeCdmModule init_mod = (InitializeCdmModule)GetProcAddress(lib, "InitializeCdmModule_4");
|
||||
CreateCdmInstance create = (CreateCdmInstance)GetProcAddress(lib, "CreateCdmInstance");
|
||||
GetCdmVersion getver=(GetCdmVersion)GetProcAddress(lib, "GetCdmVersion");
|
||||
printf("%d\n", (ulonglong)init_mod);
|
||||
init_mod();
|
||||
printf("%d %s\n", (ulonglong)create,getver());
|
||||
getchar();
|
||||
printf("Creating\n");
|
||||
std::string keys = "com.widevine.alpha";
|
||||
std::string clearkeys = "org.w3.clearkey";
|
||||
ContentDecryptionModule_10* cdm =(ContentDecryptionModule_10 *) create(10, keys.c_str(), keys.length(), GetDummyHost, (void*) msg);
|
||||
printf("Created? %d \n", (ulonglong)cdm);
|
||||
unsigned int pid = 10;
|
||||
const char* sid = "Sessid";
|
||||
//pssh box?
|
||||
byte initdata[92]= { 0, 0, 0, 91, 112, 115, 115, 104, 0, 0,
|
||||
0, 0, 237, 239, 139, 169, 121, 214, 74,
|
||||
206, 163, 200, 39, 220, 213, 29, 33, 237, 0, 0,
|
||||
0, 59, 8, 1, 18, 16, 235, 103, 106, 187, 203, 52,
|
||||
94, 150, 187, 207, 97, 102, 48, 241, 163, 218, 26, 13,
|
||||
119, 105, 100, 101, 118, 105, 110, 101, 95, 116, 101,
|
||||
115, 116, 34, 16, 102, 107,106, 51, 108, 106, 97, 83,
|
||||
100, 102, 97, 108, 107, 114, 51, 106, 42, 2, 72, 68,50, 0};
|
||||
gcdm=cdm;
|
||||
printf("First compare: %d\n",(int)((byte *)gcdm)[0x92]);
|
||||
cdm->Initialize(true, false, false);
|
||||
printf("Sc compare: %d\n", (int)((byte*)gcdm)[0x92]);
|
||||
getchar();
|
||||
cdm->CreateSessionAndGenerateRequest(pid,SessionType::kTemporary,InitDataType::kCenc,initdata,91);
|
||||
}
|
||||
```
|
||||
|
||||
All structures in the above snippet are copied from Chromium eme sources, for example [here](https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/cdm/cdm_wrapper.h).
|
||||
|
||||
First things first, I tried running the resulting program, producing proper signature as an (intermediate) result. Trying to trace it in debugger, however, caused problems. At first, it just crashed with access violation. After modifying *BeingDebugged* field in PEB, it instead went into infinite loop.
|
||||
|
||||
Looking at the decompiled code revealed a large amount of strange switch statements in most API functions. It looked somewhat like the following (from Ghidra decompiler):
|
||||
|
||||
```
|
||||
while(true)
|
||||
{
|
||||
uVar5 = (longlong)puVar3 + (longlong)(int)puVar3[uVar5 & 0xffffffff];
|
||||
switch(uVar5) {
|
||||
case 0x1800f489e:
|
||||
uVar5 = 5;
|
||||
goto LAB_1800f488e;
|
||||
case 0x1800f48ad:
|
||||
local_20 = local_2c + 0x47b0e7d4;
|
||||
uVar5 = 3;
|
||||
goto LAB_1800f488e;
|
||||
case 0x1800f48c1:
|
||||
uVar5 = 0x17;
|
||||
goto LAB_1800f488e;
|
||||
case 0x1800f48c8:
|
||||
local_28 = local_20 - local_2c;
|
||||
bVar1 = (int)(uint)local_21 < (int)(local_28 + 0xb84f182c);
|
||||
unaff_RSI = (undefined *)(ulonglong)bVar1;
|
||||
uVar5 = (ulonglong)((uint)bVar1 * 5 + 2);
|
||||
goto LAB_1800f488e;
|
||||
case 0x1800f48f4:
|
||||
local_28 = local_2c + 0xd689ea6;
|
||||
uVar5 = 0x16;
|
||||
goto LAB_1800f488e;
|
||||
case 0x1800f4908:
|
||||
uVar5 = 0x19;
|
||||
goto LAB_1800f488e;
|
||||
case 0x1800f491a:
|
||||
local_2c = local_2c & local_28;
|
||||
uVar5 = 1;
|
||||
goto LAB_1800f488e;
|
||||
case 0x1800f492c:
|
||||
if (true) {
|
||||
*(undefined *)¶m_1->_vtable = *unaff_RSI;
|
||||
unaff_RSI[1] = unaff_RSI[1] - (char)(uVar5 >> 8);
|
||||
/* WARNING: Bad instruction - Truncating control flow here */
|
||||
halt_baddata();
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
After several days of investigation, it became obvious that that is a form of code obfuscation, breaking down code flow into small segments and arranging them in switch statement in order defined by a primitive PRNG.
|
||||
|
||||
### Extracting part of the exponent
|
||||
|
||||
### Descending into despair
|
||||
|
1039
ghidra_scripts/Deobfuscatorr.py
Normal file
1039
ghidra_scripts/Deobfuscatorr.py
Normal file
File diff suppressed because it is too large
Load Diff
121
ghidra_scripts/FixJumptable.py
Normal file
121
ghidra_scripts/FixJumptable.py
Normal file
@ -0,0 +1,121 @@
|
||||
#Simple jump table fixing. Select LEA instruction and JMP RAX (branchcond) instruction and run. Mostly copied from SwitchOverride.java. Does not always fix table correctly.
|
||||
#@author Satsuoni
|
||||
#@category Deobfuscation
|
||||
#@keybinding
|
||||
#@menupath
|
||||
#@toolbar
|
||||
|
||||
from binascii import hexlify
|
||||
from ghidra.app.emulator import EmulatorHelper
|
||||
from ghidra.util.task import ConsoleTaskMonitor
|
||||
from ghidra.program.model.pcode import PcodeOp
|
||||
from ghidra.program.model.symbol import *
|
||||
from ghidra.program.model.pcode import JumpTable
|
||||
from java.util import LinkedList, Arrays, ArrayList
|
||||
from ghidra.app.cmd.function import CreateFunctionCmd
|
||||
from ghidra.app.cmd.disassemble import DisassembleCommand
|
||||
|
||||
def getAddress(offset):
|
||||
return currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(offset)
|
||||
|
||||
def getProgramRegisterList(currentProgram):
|
||||
pc = currentProgram.getProgramContext()
|
||||
return pc.registers
|
||||
|
||||
state = getState()
|
||||
currentProgram = state.getCurrentProgram()
|
||||
name = currentProgram.getName()
|
||||
listing = currentProgram.getListing()
|
||||
|
||||
print(name)
|
||||
def getPossibleConstAddressFromInstruction(instr):
|
||||
raw_pcode = instr.getPcode()
|
||||
for code in raw_pcode:
|
||||
if code.getOpcode()==PcodeOp.COPY:
|
||||
inp=code.getInputs()[0]
|
||||
print(inp)
|
||||
if inp.size==8 and inp.isConstant():
|
||||
return getAddress(inp.getOffset())
|
||||
return None
|
||||
#return toAddr(inp.)
|
||||
def isComputedBranchInstruction( instr):
|
||||
if instr is None:
|
||||
return False
|
||||
flowType = instr.getFlowType()
|
||||
if flowType == RefType.COMPUTED_JUMP:
|
||||
return True
|
||||
if (flowType.isCall()):
|
||||
#is it a callfixup?
|
||||
referencesFrom = instr.getReferencesFrom()
|
||||
for reference in referencesFrom:
|
||||
if reference.getReferenceType().isCall():
|
||||
func = currentProgram.getFunctionManager().getFunctionAt(reference.getToAddress())
|
||||
if func is not None and func.getCallFixup() is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
def tryGetSwitchGuard(instr): #also mnemonic based, so fragile. May not work, did not check in this script
|
||||
prev=instr.getPrevious()
|
||||
if prev is None: return -1
|
||||
prev=prev.getPrevious()
|
||||
nx=prev.getMnemonicString().lower()
|
||||
if nx == "cmp":
|
||||
objects=prev.getOpObjects(1)
|
||||
if len(objects)!=1: return -1
|
||||
try:
|
||||
return int(str(objects[0]),0)
|
||||
except:
|
||||
return -1
|
||||
return -1
|
||||
|
||||
def fixSwitch():
|
||||
selection=state.getCurrentSelection()
|
||||
table_addr=None
|
||||
first_addr=None
|
||||
jumpinstr=None
|
||||
jumpaddr=None
|
||||
if selection is not None:
|
||||
for item in selection:
|
||||
print(item)
|
||||
instr=getInstructionAt(item.getMinAddress())
|
||||
if first_addr is None or item.getMinAddress() < first_addr:
|
||||
first_addr=item.getMinAddress()
|
||||
if table_addr is None:
|
||||
table_addr=getPossibleConstAddressFromInstruction(instr)
|
||||
if isComputedBranchInstruction(instr):
|
||||
jumpinstr=instr
|
||||
jumpaddr=item.getMinAddress()
|
||||
if table_addr is None:
|
||||
print("could not find table, should be in selection")
|
||||
return
|
||||
if jumpinstr is None:
|
||||
print("could not find jump point, should be in selection")
|
||||
return
|
||||
table=table_addr
|
||||
cnt=0
|
||||
addr_list=[]
|
||||
while getInt(table)<0:
|
||||
naddr=table_addr.add(getInt(table))
|
||||
if naddr<first_addr or naddr>table_addr: break
|
||||
addr_list.append(naddr)
|
||||
cnt+=1
|
||||
table=table.add(4)
|
||||
print("Estimated table length: {}".format(len(addr_list)))
|
||||
sGuard=tryGetSwitchGuard(jumpinstr)
|
||||
if sGuard>0:
|
||||
print("Switch guard found: {}".format(sguard))
|
||||
if sguard<len(addr_list):
|
||||
addr_list=addr_list[:sguard]
|
||||
if len(addr_list)==0:
|
||||
print ("Empty table?")
|
||||
return
|
||||
function = getFunctionContaining(jumpaddr)
|
||||
monitor = ConsoleTaskMonitor()
|
||||
for addr in addr_list:
|
||||
discmd = DisassembleCommand(addr, None, True)
|
||||
discmd.applyTo(currentProgram,monitor)
|
||||
jumpinstr.addOperandReference(0, addr, RefType.COMPUTED_JUMP, SourceType.USER_DEFINED)
|
||||
jumpTab = JumpTable(jumpaddr,ArrayList(addr_list),True)
|
||||
#jumpTab.writeOverride(function)
|
||||
CreateFunctionCmd.fixupFunctionBody(currentProgram, function, monitor)
|
||||
fixSwitch()
|
1323
ghidra_scripts/Longsusrunner.py
Normal file
1323
ghidra_scripts/Longsusrunner.py
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user