Results 1 to 4 of 4

Thread: [twinBASIC] Kernel mode driver for 64-bit Windows

  1. #1

    Thread Starter
    Join Date
    Jul 2010

    Cool [twinBASIC] Kernel mode driver for 64-bit Windows

    TBHWldDrv v1.2.3.4
    twinBASIC HelloWorld Driver

    twinBASIC continues to impress.

    This is a demonstration of using twinBASIC (Current releases and community on GitHub) to create a kernel mode driver compatible with x64 versions of Windows. I became fascinated with the idea after The_trick figured out how to make them in VB6, and when I saw twinBASIC could compile VB6 code to 64bit... the prospect was fascinating, since VB6 was limited to x86 and there's no WOW64 for kernel mode. We could make drivers for the Windows 64bit OS everyone is running, and even easier with far more features since tB has no runtime... if twinBASIC supported a few features to replicate the hacks that made it possible in VB6. So I made a feature request, and the awesome Wayne Phillips was interested.

    For testing purposes, I first made a working VB6 version (included), and then a tB version with all the definitions updated to x64 and taking advantage of a feature tB has to put in-project API declares into the IAT like TLB APIs to avoid late bound resolving requiring external APIs, and a controller (written in tB) to load/unload the driver.

    Running the project

    Build the binaries:

    The tB version only needs to be built. The required settings applied for making kernel mode drivers were creating a standard exe, removing the current references, enabling the settings Project: Native subsystem->YES, Project: Override entry point->DriverEntry, Project: Runtime binding of DLL declares->NO.

    If you want to build the VB6 version to compare:
    The VBP includes the undocumented link switches and enables the optimizations needed, so just needs to be opened and compiled.
    After compiling the .sys, use The_trick's Patcher project (included) to strip the msvbvm60.dll dependency from the .sys.

    Running the driver

    I've been testing on Windows 7 via VM software, since it's less anal about unsigned drivers, and VMs because errors in drivers typically result in a bluescreen instead of error message or app crash. It's recommended you do the same, but not required.

    1. Microsoft has been heading down the road to where you don't own your computer, they do. Windows Vista and newer do not normally allow unsigned drivers, or even self-signed drivers. To get them signed, you have to provide a ton of personal information and pay hundreds of dollars. One way around this is via the Advanced Boot Menu:
    You'll need to boot using the 'Disable driver signature enforcement' advanced boot option. Reboot and press F8 right when Windows starts loading. VMs like VirtualBox makes it nigh impossible to get an F8 keypress in, so instead, you can also open a command prompt as administrator, and enter the following:
    bcdedit /set {globalsettings} advancedoptions true
    Then restart. This will bring up the Advanced Boot Options menu, containing "Disable driver signature enforcement". You need to do this every boot for which you want to load unsigned drivers; and unfortunately it disables them globally.
    NOTE: When you start the driver, Windows will pop up a box saying a signature is required. But this doesn't mean the driver didn't load, as the log will show.

    2. Create a folder for the project, and put TBHWldDrv.sys and HelloWorldDriverController.exe all in the same folder (and/or VBHWldDrv.sys if you're testing that too). The folder cannot be named TBHWldDrv.

    3. Run HelloWorldDriverController.exe as administrator (it has a manifest option requiring this).

    4. Click 'Load driver' (Connect is only for if it's already running, e.g. if it's been installed in the boot sequence, it isn't by default and isn't necessary). This will create a service for a kernel driver and start it.

    5. If it successfully loads and connects (there's a log that will tell you), you can send the version command to get a response back from the driver.

    6. When done, click Unload and delete. This will remove the service created to load the driver. Then Exit.

    How it works
    The first step is the DriverEntry function. Normally VB and tB exes have a hidden function that's the first to run (even before Sub Main), this is used to set up various things like COM. But a driver can't have any of that; it must enter through the DriverEntry function. You won't be able to use any APIs besides ones in ntoskrnl.exe, the Windows kernel module. This is because there's a barrier between user mode and kernel mode, and you can't load user mode DLLs into a kernel mode driver.

    In VB, this dramatically limited what you can do, because virtually everything relies on the msvbvm60.dll runtime. But twinBASIC doesn't rely on an external runtime; all of the VB runtime stuff is built right into the exe. It also provides an option to put API declares in the Import Address Table, in VB they're late-bound and called using runtime APIs and only added to the IAT if they're in a TLB. This makes programming drivers quite a bit easier, because for instance you can use VarPtr directly; in VB you'd need to use something like InterlockedExchange to copy the pointer into a new variable.

    There are some limitations. You still can't use strings or arrays (besides 1D arrays inside UDTs) because these are managed with APIs behind the scenes, so this project uses The_trick's BinaryString method of putting strings into the 1D UDT arrays that don't use SAFEARRAY and thus don't need APIs. But generally, there's a hell of a lot more twinBASIC lets you do.

    In the DriverEntry function we do 4 things: Initialize our string types, create the DEVICE_OBJECT that descibes our driver to the system, create a symbolic link that allows the driver to communicate with user mode apps via CreateFile, and set up the function table for the IRP major functions. For most of these, we just implement default handlers that pass on IRPs (I/O request packets) to the next driver in the stack. The one we're primarily interested in for this demo is IRP_MJ_DEVICE_CONTROL: If you've ever used the DeviceIoControl API, this is where those commands are going, to device drivers.

    Public Function DriverEntry(ByRef DriverObject As DRIVER_OBJECT, ByRef RegistryPath As UNICODE_STRING) As Long
        DbgPrint VarPtr(dbgsEntry)
        Dim ntStatus As Long
        ntStatus = IoCreateDevice(DriverObject, 0&, DeviceName, FILE_DEVICE_UNKNOWN, 0&, False, Device)
        If NT_SUCCESS(ntStatus) Then
            ntStatus = IoCreateSymbolicLink(DeviceLink, DeviceName)
            If Not NT_SUCCESS(ntStatus) Then
                IoDeleteDevice Device
                DriverEntry = ntStatus
                Exit Function
            End If
           Dim i As Long
           For i = 0 To IRP_MJ_MAXIMUM_FUNCTION
                   DriverObject.MajorFunction(i) = AddressOf OnOther
            DriverObject.MajorFunction(IRP_MJ_CREATE) = AddressOf OnCreate
            DriverObject.MajorFunction(IRP_MJ_CLOSE) = AddressOf OnClose
            DriverObject.MajorFunction(IRP_MJ_DEVICE_CONTROL) = AddressOf OnDeviceControl
            DriverObject.DriverUnload = AddressOf OnUnload
        End If
        DriverEntry = ntStatus
    End Function
    We define a custom command for our project using the CTL_CODE macro, which can be implemented easily thanks to tB having << and >> bitshift operators. The command we're defining is used to send the driver a verification number to check to make sure everything is ok as input. The output is the HelloWorldVersion UDT, a custom structure we use for the driver to pass it's version data to us to test that everything is working and we've successfully created a driver that takes commands and exchanges data with user mode applications.

    In the driver controller, after starting the driver with the service APIs and connecting to it with CreateFile, the command is sent:
        Dim tVer As HelloWorldVersion
        Dim lVerify As Long
        Dim cbRet As Long
        Dim result As Long
        AppendLog "Sending IOCTL_HWRLD_VERSION to driver..."
        result = DeviceIoControl(hDev, IOCTL_HWRLD_VERSION, lVerify, 4&, tVer, LenB(tVer), cbRet, ByVal 0&)
        AppendLog "Result: ret=0x" & Hex$(result) & ",cbRead=" & cbRet & vbCrLf & "Version (Expecting" & tVer.Major & "." & tVer.Minor & "." & tVer.Build & "." & tVer.Revision
    If we get the version numbers we expect, SUCCESS! Everything has worked.
    In the driver, we receive that command:
    Public Function OnDeviceControl(ByRef DriverObject As DRIVER_OBJECT, ByRef pIrp As IRP) As Long
        DbgPrint VarPtr(dbgsDevIoEntry)
        Dim lpStack As LongPtr
        Dim ioStack As IO_STACK_LOCATION
        Dim ntStatus As Long
        pIrp.IoStatus.Information = 0
        lpStack = IoGetCurrentIrpStackLocation(pIrp)
        If lpStack Then
            DbgPrint VarPtr(dbgsStackOk)
            CopyMemory ioStack, ByVal lpStack, LenB(ioStack)
            If (ioStack.DeviceIoControl.IoControlCode = IOCTL_HWRLD_VERSION) And (pIrp.AssociatedIrp <> 0) Then
                DbgPrint VarPtr(dbgsCmdOk)
                Dim tVer As HelloWorldVersion
                Dim lpBuffer As LongPtr
                Dim cbIn As Long, cbOut As Long
                lpBuffer = pIrp.AssociatedIrp
                cbIn = ioStack.DeviceIoControl.InputBufferLength
                cbOut = ioStack.DeviceIoControl.OutputBufferLength
                If (lpBuffer = 0&) Or (cbIn <> 4) Or (cbOut <> LenB(tVer)) Then
                    If (lpBuffer = 0&) Then
                        ntStatus = STATUS_BUFFER_ALL_ZEROS
                        DbgPrint VarPtr(dbgsNoBuffer)
                    ElseIf (cbOut <> LenB(tVer)) Then
                        ntStatus = STATUS_INVALID_BUFFER_SIZE
                        DbgPrint VarPtr(dbgsBadSize)
                        ntStatus = STATUS_INVALID_DEVICE_REQUEST
                        DbgPrint VarPtr(dbgsNoVal)
                    End If
                    DbgPrint VarPtr(dbgsCopyOut)
                    tVer.Major = 1
                    tVer.Minor = 2
                    tVer.Build = 3
                    tVer.Revision = 4
                    CopyMemory ByVal lpBuffer, tVer, cbOut
                    pIrp.IoStatus.Information = LenB(tVer)
                End If
                pIrp.IoStatus.StatusPointer = ntStatus
                IoCompleteRequest pIrp, IO_NO_INCREMENT
                OnDeviceControl = ntStatus
                Exit Function
    The current location on the stack has all the DeviceIoControl arguments from when we called that API, we check if everything is ok, if so, we copy the data to the i/o packet.

    The last thing we handle is the Unload function, where we delete the symbolic link and device object:
    Public Sub OnUnload(DriverObject As DRIVER_OBJECT)
        If Device.Size = 0 Then Exit Sub
        IoDeleteSymbolicLink DeviceLink
        IoDeleteDevice ByVal DriverObject.DeviceObject
    End Sub
    This project shows the use of the DbgPrint function so we can more easily debug our driver like a normal VB/tB project instead of the extraordinarily difficult WinDbg's kernel debugger. It's a CDecl function, but fortunately tB supports that natively so we don't have to worry about the workarounds VB requires for that. It has a ... argument that maps to a ParamArray; but right now tB manages those behind the scenes with APIs, so can't use them here... but those are all optional, we can supply just the string, then use DebugView to see the output. Note that you must open DebugView (as administrator) after clicking load driver, or it won't attach properly.

    Self-signed kernel mode drivers
    According to Geoff Chappell, it's possible to use a bunch of undocumented functionality to load self-signed drivers under Windows 10/11 x64, with SecureBoot enabled, without disabling signature checks. Have a read of his article on it if you're interested in trying it. This is not for the faint of heart however. It's extremely complicated. You'll need the Windows SDK installed for the signing tools, and Windows 10/11 Enterprise or, oddly, Education editions, to compile the policy files (though you may be able to use the ones he provides).

    twinBASIC Beta 95 or newer (v0.15.95)
    This will build for x86 or x64. You cannot use the x86 version of the driver on x64; though you could probably use the controller.
    I've only tested this on Windows 7+.

    This project reuses many definitions and techniques from o.g. BASIC driver developer The_trick. The rest of it is heavily based on Geoff Chappell's SelfSign driver from the article above. I'm brand new to driver development myself, and had never attempted it before. If I can follow those projects and do it, so can you.

    This project also has a GitHub, which includes a release with pre-compiled binaries.
    Attached Files Attached Files
    Last edited by fafalone; Aug 20th, 2022 at 08:31 PM.

  2. #2

    Thread Starter
    Join Date
    Jul 2010

    Re: [twinBASIC] Kernel mode driver for 64-bit Windows

    Kernel mode strings

    Here's a tool I made to generate the code to set up and define the String arrays used in the project.

    You'll find this very useful if you want to add DbgPrint statements or make your own driver.
    Attached Files Attached Files

  3. #3

  4. #4

    Thread Starter
    Join Date
    Jul 2010

    Re: [twinBASIC] Kernel mode driver for 64-bit Windows

    Starting in twinBASIC 97 it's no longer neccessary to use those unfriendly string byte arrays, you can enable the new Constant Function Folding setting, and make the following replacements:

    Private strName As String = "\Device\TBHWldDrv" ' // Device name string
    Private strLink As String = "\DosDevices\TBHWldDrv" ' // Device link string
    'Here's our debug strings for DbgPrint
    Private dbgsEntry As String
    Private dbgsDevIoEntry As String
    Private dbgsStackOk As String
    Private dbgsStackFail As String
    Private dbgsCmdOk As String
    Private dbgsCmdFailCode As String
    Private dbgsCmdFailPtr As String
    Private dbgsNoBuffer As String
    Private dbgsBadSize As String
    Private dbgsNoVal As String
    Private dbgsCopyOut As String
    Private Declare PtrSafe Sub RtlInitUnicodeString Lib "ntoskrnl.exe" (DestinationString As UNICODE_STRING, ByVal SourceString As LongPtr)
    [ ConstantFoldable ]
    Private Function ToANSI(sIn As String) As String
        Return StrConv(sIn, vbFromUnicode)
    End Function
    Private Sub InitDebugStrings()
    dbgsEntry = ToANSI("TBHWldDrv: Entry point success")
    dbgsDevIoEntry = ToANSI("TBHWldDrv: DeviceIoEntry")
    dbgsStackOk = ToANSI("TBHWldDrv: Validated stack location")
    dbgsStackFail = ToANSI("TBHWldDrv: Null stack pointer")
    dbgsCmdOk = ToANSI("TBHWldDrv: Validated Cmd Code and assoccirp")
    dbgsCmdFailCode = ToANSI("TBHWldDrv: Invalid command code")
    dbgsCmdFailPtr = ToANSI("TBHWldDrv: Invalid assocIrp pointer")
    dbgsNoBuffer = ToANSI("TBHWldDrv: Null out buffer pointer")
    dbgsBadSize = ToANSI("TBHWldDrv: Out buffer size incorrect")
    dbgsNoVal = ToANSI("TBHWldDrv: Invalid input validation number")
    dbgsCopyOut = ToANSI("TBHWldDrv: All ok, copying out")
    End Sub
    Private Sub InitUnicodeStrings()
    RtlInitUnicodeString DeviceName, StrPtr(strName)
    RtlInitUnicodeString DeviceLink, StrPtr(strLink)
    End Sub
    and change the DbgPrint statements to use StrPtr instead of VarPtr.

    This is a much friendlier alternative

    I haven't updated the project yet; want to have the next version do a little more.
    Last edited by fafalone; Aug 23rd, 2022 at 05:01 AM.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts

Click Here to Expand Forum to Full Width