Added Ghidra scripts. Starting work on Readme

This commit is contained in:
Satsuoni 2021-07-29 15:43:18 +09:00
parent 1aae32eb80
commit 8c931c4b83
4 changed files with 2573 additions and 0 deletions

View File

@ -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 *)&param_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

File diff suppressed because it is too large Load Diff

View 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()

File diff suppressed because it is too large Load Diff