#include "drmkPCH.h" #include "KList.h" #include "StreamMgr.h" #include "SBuffer.h" #include "CryptoHelpers.h" #include "HandleMgr.h" #include "KRMStubs.h" #include "encraption.h" //------------------------------------------------------------------------------ // // These are not the actual keys. The encraption algorithm in encraption.h // is used to get clear keys. // static const BYTE DRMKpriv[20] = { 0xDC, 0xC4, 0x26, 0xB2, 0x4F, 0x11, 0x24, 0x8A, 0x51, 0xAC, 0x88, 0xF5, 0x47, 0x4B, 0xD5, 0x8C, 0x3C, 0x45, 0x29, 0xA1}; static const BYTE DRMKCert[104] = { 0xD4, 0x3F, 0xC8, 0x44, 0xCD, 0x86, 0x41, 0xE9, 0x7C, 0x23, 0x36, 0xAD, 0xC3, 0x22, 0x4F, 0x27, 0xC6, 0x1B, 0x5B, 0x9C, 0x75, 0x2A, 0x86, 0x32, 0x7E, 0x37, 0x24, 0x8D, 0x2B, 0x51, 0xF6, 0x6A, 0x31, 0x69, 0xA3, 0x66, 0xA8, 0x30, 0xC9, 0x4A, 0x23, 0xCC, 0x30, 0xD8, 0x19, 0x19, 0x7B, 0x9A, 0xF6, 0x32, 0xB5, 0xD8, 0x4C, 0x37, 0x1A, 0x91, 0x13, 0x71, 0xF6, 0x63, 0x41, 0x1B, 0x1A, 0x06, 0x57, 0xEC, 0x7A, 0xF8, 0x47, 0x41, 0xEF, 0x5E, 0xB9, 0x02, 0xE9, 0xE9, 0xA1, 0x52, 0x34, 0xC4, 0xCD, 0x7F, 0xDE, 0xF6, 0x09, 0x27, 0xE8, 0xB6, 0x27, 0xF0, 0x93, 0xD8, 0xE2, 0x07, 0xD2, 0xD1, 0x64, 0x8B, 0xF6, 0xD7, 0x57, 0x2C, 0xB2, 0x37}; //------------------------------------------------------------------------------ const DWORD KrmVersionNumber=100; //------------------------------------------------------------------------------ KRMStubs* TheKrmStubs=NULL; //------------------------------------------------------------------------------ DRM_STATUS GetKernelDigest( BYTE *startAddress, ULONG len, DRMDIGEST *pDigest ) { BYTE* seed = (BYTE*) "a3fs9F7012341234KS84Wd04j=c50asj4*4dlcj5-q8m;ldhgfddd"; CBCKey key; CBCState state; CBC64Init(&key, &state, seed); CBC64Update(&key, &state, len/16*16, startAddress); pDigest->w1=CBC64Finalize(&key, &state, (UINT32*) &pDigest->w2); return DRM_OK; } // GetKernelDigest //------------------------------------------------------------------------------ KRMStubs::KRMStubs(){ ASSERT(TheKrmStubs==NULL); TheKrmStubs=this; return; }; //------------------------------------------------------------------------------ KRMStubs::~KRMStubs(){ return; }; //------------------------------------------------------------------------------ // Main entry point for KRM IOCTL processing. KRMINIT1 and KRMINIT2 are // plaintext commands, after this, the command block and the reply // are digested and encrypted. NTSTATUS KRMStubs::processIoctl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp){ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); DWORD comm; DWORD inSize=irpStack->Parameters.DeviceIoControl.InputBufferLength; DWORD outSize=irpStack->Parameters.DeviceIoControl.OutputBufferLength; DWORD bufSize=inSize>outSize?inSize:outSize; if(!critMgr.isOK()){ _DbgPrintF(DEBUGLVL_VERBOSE,("Out of memory")); return STATUS_INSUFFICIENT_RESOURCES; }; _DbgPrintF(DEBUGLVL_VERBOSE,("inSize, outSize %d, %d\n", inSize, outSize)); for(DWORD j=0;jAssociatedIrp.SystemBuffer)+j))); }; return processCommandBuffer((BYTE* ) Irp->AssociatedIrp.SystemBuffer, inSize, outSize, Irp); }; //------------------------------------------------------------------------------ NTSTATUS KRMStubs::processCommandBuffer(IN BYTE* InBuf, IN DWORD InLen, IN DWORD OutBufSize, IN OUT PIRP Irp){ _DbgPrintF(DEBUGLVL_VERBOSE,("Process command buffer (command size= %d)", InLen)); DWORD bufSize=InLen>OutBufSize?InLen:OutBufSize; // // We must have at least communication code + terminator input space. // if (bufSize < 2 * sizeof(DWORD)) { _DbgPrintF(DEBUGLVL_TERSE, ("Input buffer too small")); return STATUS_BUFFER_TOO_SMALL; } PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT file=irpStack->FileObject; ConnectStruct* connection=TheHandleMgr->getConnection(file); if(connection==NULL) { _DbgPrintF(DEBUGLVL_TERSE, ("Connection does not exist %d\n", file)); return STATUS_BAD_DESCRIPTOR_FORMAT; }; bool secureStreamWillStart=false; if(connection->secureStreamStarted) { if (STATUS_SUCCESS != postReceive(InBuf, InLen, connection)) { _DbgPrintF(DEBUGLVL_TERSE, ("PostReceive error")); return STATUS_BAD_DESCRIPTOR_FORMAT; } } SBuffer s(InBuf, bufSize); DWORD comm; s >> comm; if (KRM_OK != s.getLastError()) { _DbgPrintF(DEBUGLVL_TERSE, ("Bad communication code")); return STATUS_BAD_DESCRIPTOR_FORMAT; } // // if secure communication is not established reject all requests except // the initialization calls. // if (!connection->secureStreamStarted && (_KRMINIT1 != comm && _KRMINIT2 != comm)) { _DbgPrintF(DEBUGLVL_TERSE, ("Bad communication pattern")); return STATUS_BAD_DESCRIPTOR_FORMAT; } DRM_STATUS stat; switch(comm){ case _GETKERNELDIGEST: { // // ISSUE: 04/05/2002 ALPERS. // Note that this handler is not 64 bit compatible. Just follows // the rest of the property handler. // DWORD startAddress, len; DRMDIGEST newDigest = { 0, 0 }; s >> startAddress >> len; stat = s.getLastError(); if (KRM_SUCCESS(stat)) { stat = checkTerm(s); } // // Make sure the output buffer can hold len bytes. // if (KRM_SUCCESS(stat)) { if (s.getLen() < sizeof(stat) + sizeof(newDigest) + sizeof(DWORD) + 64) { stat = KRM_BUFSIZE; _DbgPrintF(DEBUGLVL_TERSE, ("_GETKERNELDIGEST - invalid output buffer size")); } } if (KRM_SUCCESS(stat)) { // // ISSUE: 04/05/2002 ALPERS // (SECURITY NOTE: Potential DOS attack) // Note that startAddress and Len are coming from user mode // and there is no validation. // This IOCTL can only be send through the secure IOCTL // interface. In order to attack here, the attacker has to // figure out the secure IOCTL channel. // There is one level of defense. // // TODO: As a second line of defense, DRMK can collect the // same module information and compare the given address // to its list. // // The reason we get the KernelAddress from UserMode is // because of the relocation code in UserMode. The code reads // the driver image from disk, parses PE format and finds // the section that contains the provingFunction. // startAddress is the beginning of the section that // contains provingFunction. // stat = GetKernelDigest((BYTE *) ULongToPtr(startAddress), len, &newDigest); } s.reset(); s << stat << newDigest.w1 << newDigest.w2; break; } //----------------- case _KRMINIT1: { // return the version number and the cert DWORD drmVersionNumber; CERT krmCert; s >> drmVersionNumber; stat = s.getLastError(); if (KRM_SUCCESS(stat)) { stat = checkTerm(s); } if (KRM_SUCCESS(stat)) { _DbgPrintF(DEBUGLVL_VERBOSE,("Doing KRMINIT1, for DRM version %d", drmVersionNumber)); NTSTATUS Status = ClearKey(DRMKCert, (BYTE *) &krmCert, sizeof(DRMKCert), 5); if (NT_SUCCESS(Status)) { s.reset(); s << (DWORD) KRM_OK << KrmVersionNumber; s.append((BYTE*) &krmCert, sizeof(krmCert)); stat = s.getLastError(); } else { stat = KRM_SYSERR; } } if (!KRM_SUCCESS(stat)) { s.reset(); s << stat; } break; }; //----------------- case _KRMINIT2: { DWORD datLen; s >> datLen; stat = s.getLastError(); if (KRM_SUCCESS(stat)) { DWORD bufLenShouldBe=PK_ENC_CIPHERTEXT_LEN; if (bufLenShouldBe == datLen) { unsigned int pos; stat = s.getGetPosAndAdvance(&pos, datLen); if (KRM_SUCCESS(stat)) { BYTE* cipherText=s.getBuf()+pos; stat=initStream(cipherText, connection); if (stat != DRM_OK) { _DbgPrintF(DEBUGLVL_TERSE, ("BAD InitString")); }; } } else { _DbgPrintF(DEBUGLVL_TERSE, ("KRMINIT2 - bad string")); stat = KRM_SYSERR; }; } if (KRM_SUCCESS(stat)) { stat = checkTerm(s); } s.reset(); s << stat; if (KRM_SUCCESS(stat)) { secureStreamWillStart=true; } break; }; //----------------- case _CREATESTREAM: { KCritical sect(critMgr); DWORD handle; DRMRIGHTS rights; STREAMKEY key; DWORD streamId = 0; s >> handle >> &rights >> &key; stat = s.getLastError(); if (KRM_SUCCESS(stat)) { stat = checkTerm(s); } // // The input buffer is much bigger than output buffer. // Therefore we are sure that SBuffer has enough space // for the output buffer. // if (KRM_SUCCESS(stat)) { stat = TheStreamMgr->createStream(ULongToPtr(handle), &streamId, &rights, &key); } if (KRM_SUCCESS(stat)) { connection->streamId = streamId; } s.reset(); s << stat << streamId; break; }; //----------------- case _DESTROYSTREAM: { KCritical sect(critMgr); DWORD streamId; s >> streamId; stat = s.getLastError(); if (KRM_SUCCESS(stat)) { stat = checkTerm(s); } if (KRM_SUCCESS(stat)) { stat = TheStreamMgr->destroyStream(streamId); } s.reset(); s << stat; break; }; //----------------- case _DESTROYSTREAMSBYHANDLE: { KCritical sect(critMgr); DWORD handle; s >> handle; stat = s.getLastError(); if (KRM_SUCCESS(stat)) { stat = checkTerm(s); } if (KRM_SUCCESS(stat)) { stat = TheStreamMgr->destroyAllStreamsByHandle(ULongToHandle(handle)); } s.reset(); s << stat; break; }; //----------------- case _WALKDRIVERS: { KCritical sect(critMgr); DWORD StreamId, MaxDrivers, len; s >> StreamId >> MaxDrivers; stat = s.getLastError(); if (KRM_SUCCESS(stat)) { stat = checkTerm(s); } // check buffer size. if (KRM_SUCCESS(stat)) { len = sizeof(DWORD) * MaxDrivers; if ((s.getLen() < len + 64) || (len > len + 64)) { stat = KRM_BUFSIZE; _DbgPrintF(DEBUGLVL_TERSE,("_WALKDRIVERS : Invalid buffer size")); } } s.reset(); // // Due to difficulties in maintaining security in the presence of Verifier // we return an error if Verifier is detected. // if (KRM_SUCCESS(stat)) { ULONG VerifierFlags; if (NT_SUCCESS(MmIsVerifierEnabled(&VerifierFlags))) { stat = DRM_VERIFIERENABLED; } } if (KRM_SUCCESS(stat)) { if (MaxDrivers==0) { // just check that the stream is good DWORD errorCode; stat = TheStreamMgr->getStreamErrorCode(StreamId, errorCode); if (KRM_SUCCESS(stat)) { stat = errorCode; } if (KRM_SUCCESS(stat)) { ULONG numDrivers; stat = TheStreamMgr->walkDrivers(StreamId, NULL, numDrivers, 0); if (KRM_SUCCESS(stat)) { stat=TheStreamMgr->getStreamErrorCode(StreamId, errorCode); if (KRM_SUCCESS(stat)) { stat = errorCode; } } } s << stat << (DWORD) 0; } else { // do a full authentication run PVOID* drivers = new PVOID[MaxDrivers]; if (drivers!=NULL) { DWORD numDrivers = 0; stat = TheStreamMgr->walkDrivers(StreamId, drivers, numDrivers, MaxDrivers); s << stat << numDrivers; // // We checked the buffer size upfront. This should not // fail during stream operations. // if ((stat==DRM_OK) || (stat==DRM_BADDRMLEVEL)) { // todo - perhaps a block copy for (DWORD j = 0; j < numDrivers; j++) { s << drivers[j]; ASSERT(KRM_SUCCESS(s.getLastError())); }; } delete[] drivers; } else { // allocation failed s << (DWORD) DRM_OUTOFMEMORY << (DWORD) 0; }; }; } else { s << stat << (DWORD) 0; } break; } //----------------- default: { s.reset(); s << KRM_BADIOCTL; break; }; }; term(s); // // We are ignoring if we cannot put the terminator here. // KRMProxy does not care anyway. // if (connection->secureStreamStarted) { // // ignore the return value. In case of failure we will have crap // in SBuffer. And we will return it to user mode. // preSend(s, connection); } if (secureStreamWillStart) { connection->secureStreamStarted = true; } _DbgPrintF(DEBUGLVL_VERBOSE,("Returning %d bytes", s.getPutPos())); Irp->IoStatus.Information=s.getPutPos(); return STATUS_SUCCESS; }; //------------------------------------------------------------------------------ NTSTATUS KRMStubs::initStream(BYTE* encText, ConnectStruct* Conn){ PRIVKEY myPrivKey; NTSTATUS Status; Status = ClearKey(DRMKpriv, myPrivKey.x, sizeof(DRMKpriv), 2); if (NT_SUCCESS(Status)) { // // ISSUE: 04/24/2002 ALPERS // CDRMPKCrypto allocates memory in its constructor. If the memory // allocation fails, all functions in that object return error codes. // Yet we are not checking the error code from PKdecrypt. // CDRMPKCrypto decryptor; BYTE decryptedText[PK_ENC_PLAINTEXT_LEN]; decryptor.PKdecrypt(&myPrivKey, encText, decryptedText); bv4_key_C(&Conn->serverKey, sizeof(decryptedText),decryptedText ); CryptoHelpers::InitMac(Conn->serverCBCKey, Conn->serverCBCState, decryptedText, sizeof(decryptedText)); } return Status; }; //------------------------------------------------------------------------------ NTSTATUS InitializeDriver(){ NTSTATUS DriverInitializeStatus; _DbgPrintF(DEBUGLVL_VERBOSE,("Initializing Driver")); // Note - these dynamic allocations are 'global objects' that offer services to // the DRMK driver. // The services are referenced through the global pointers: // TheStreamManager, TheTGBuilder, TheKrmStubs, and TheHandleMgr void* temp=NULL; #pragma prefast(suppress:14, "There is really no leak here. The cleanup is CleanupDriver") temp=new StreamMgr; if (temp) { temp = new KRMStubs; } if (temp) { temp = new HandleMgr; } // // Make sure the internal states of the objects are OK. // if (temp) { if (!TheStreamMgr->getCritMgr().isOK() || !TheKrmStubs->getCritMgr().isOK() || !TheHandleMgr->getCritMgr().isOK()) { _DbgPrintF(DEBUGLVL_TERSE, ("CritMgr allocation failed in DRMK:InitializeDriver")); temp = NULL; } } if (temp) { DriverInitializeStatus = STATUS_SUCCESS; } else { _DbgPrintF(DEBUGLVL_TERSE,("operator::new failed in DRMK:InitializeDriver")); DriverInitializeStatus = STATUS_INSUFFICIENT_RESOURCES; CleanupDriver(); } return DriverInitializeStatus; }; //------------------------------------------------------------------------------ NTSTATUS CleanupDriver(){ _DbgPrintF(DEBUGLVL_VERBOSE,("Cleaning up Driver")); delete TheStreamMgr;TheStreamMgr=NULL; delete TheKrmStubs;TheKrmStubs=NULL; delete TheHandleMgr;TheHandleMgr=NULL; return STATUS_SUCCESS; }; //------------------------------------------------------------------------------ NTSTATUS KRMStubs::InitializeConnection(PIRP Pirp){ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Pirp); PFILE_OBJECT file=irpStack->FileObject; _DbgPrintF(DEBUGLVL_VERBOSE,("InititializeConnection %d", file)); ConnectStruct* conn; bool ok=TheHandleMgr->newHandle(file, conn); if(!ok){ _DbgPrintF(DEBUGLVL_VERBOSE,("Out of memory")); return STATUS_INSUFFICIENT_RESOURCES; }; return STATUS_SUCCESS; }; //------------------------------------------------------------------------------ NTSTATUS KRMStubs::CleanupConnection(PIRP Pirp){ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Pirp); PFILE_OBJECT file=irpStack->FileObject; _DbgPrintF(DEBUGLVL_VERBOSE,("CleanupConnection %x", file)); ConnectStruct* conn=TheHandleMgr->getConnection(file); if(conn==NULL){ _DbgPrintF(DEBUGLVL_VERBOSE,("Connection does not exist ")); return STATUS_INVALID_PARAMETER_1; }; TheStreamMgr->destroyStream(conn->streamId); TheHandleMgr->deleteHandle(file); return STATUS_SUCCESS; }; //------------------------------------------------------------------------------ // see twin function in KComm NTSTATUS KRMStubs::preSend(class SBuffer& Msg, ConnectStruct* Conn){ // first digest DRMDIGEST digest; DRM_STATUS stat=CryptoHelpers::Mac(Conn->serverCBCKey, Msg.getBuf(), Msg.getPutPos(), digest); if(stat!=DRM_OK){ _DbgPrintF(DEBUGLVL_VERBOSE,("Bad MAC")); return STATUS_DRIVER_INTERNAL_ERROR; }; Msg << &digest; stat = Msg.getLastError(); if (KRM_OK == stat) { // then encrypt msg + digest stat=CryptoHelpers::Xcrypt(Conn->serverKey, Msg.getBuf(), Msg.getPutPos()); if(stat!=DRM_OK){ _DbgPrintF(DEBUGLVL_VERBOSE,("Bad XCrypt")); return STATUS_DRIVER_INTERNAL_ERROR; }; } return STATUS_SUCCESS; }; //------------------------------------------------------------------------------ // see twin function in KComm NTSTATUS KRMStubs::postReceive(BYTE* Data, DWORD DatLen, ConnectStruct* Conn){ _DbgPrintF(DEBUGLVL_VERBOSE,("PostReceive on %d", DatLen)); // decrypt DRM_STATUS stat=CryptoHelpers::Xcrypt(Conn->serverKey, Data, DatLen); if(stat!=DRM_OK){ _DbgPrintF(DEBUGLVL_VERBOSE,("Bad XCrypt(2)")); return STATUS_DRIVER_INTERNAL_ERROR; }; // check digest DRMDIGEST digest; if (DatLen <= sizeof(DRMDIGEST)) return STATUS_INVALID_PARAMETER; stat=CryptoHelpers::Mac(Conn->serverCBCKey, Data, DatLen-sizeof(DRMDIGEST), digest); if(stat!=DRM_OK){ _DbgPrintF(DEBUGLVL_VERBOSE,("Bad MAC(2)")); return STATUS_DRIVER_INTERNAL_ERROR; }; DRMDIGEST* msgDigest=(DRMDIGEST*) (Data+DatLen-sizeof(DRMDIGEST)); int match=memcmp(&digest, msgDigest, sizeof(DRMDIGEST)); if(match==0)return STATUS_SUCCESS; memset(Data, 0, DatLen); _DbgPrintF(DEBUGLVL_VERBOSE,("MAC does not match(2)")); return STATUS_DRIVER_INTERNAL_ERROR; }; //------------------------------------------------------------------------------