Page 1 of 3

Driver development using PureBasic

Posted: Thu Feb 14, 2013 4:13 pm
by User_Russian
A small addition to PureBasic, allowing you to create kernel-mode drivers for Windows. http://pure-basic.narod.ru/forum_files/ ... Plugin.zip
Addition is intended to PureBasic 5.10.

Installation guide.
Install, PureBasic 5.10 (demo or full version) to any folder.
Open a folder "Compilers" and rename the file "Fasm.exe" in "Fasm_.exe", and the file "Polink.exe" in "Polink_.exe".
Extract the archive to a folder with PureBasic.
Example of a simple driver.

Code: Select all

Declare DriverEntry(*DriverObject, *RegistryPath)

*A=@DriverEntry()
!jmp [p_A]

ImportC "ntoskrnl.lib"
  DbgPrint(String.s)
EndImport

Procedure DriverEntry(*DriverObject.DRIVER_OBJECT, *RegistryPath.UNICODE_STRING)
    
  DbgPrint("LoadDriver")
    
  ; Reports an error, for system to unload driver.
  ProcedureReturn #STATUS_DEVICE_CONFIGURATION_ERROR
EndProcedure
Create the driver of the code (Compiler -> Create Executable ...), with the name "Simple_driver.sys".
Run the program "Dbgview.exe" and "KmdManager.exe", which are in a folder "Tools".
Open the program "KmdManager.exe", driver "Simple_driver.sys".

Warning, this is further actions can lead to BSoD!

Click on the buttons in sequence "Register", "Run", "Stop" and "Unregister".
If done correctly, the debugger "Dbgview.exe" appears "LoadDriver".

Image

---------------------

In folder with examples have these examples, a simple driver, how to work with kernel memory and example drivers direct access to ports, similar Inpout32.dll.

--------------------

New version 2.2. http://www.purebasic.fr/english/viewtop ... 34#p458334

Re: Driver development using PureBasic

Posted: Thu Feb 14, 2013 4:50 pm
by jassing
Nice! So same stuff will work in x64 and x86? Very slick.

Re: Driver development using PureBasic

Posted: Thu Feb 14, 2013 4:52 pm
by Inf0Byt3
Interesting :). This might come in handy sometime. Thanks for sharing.

Re: Driver development using PureBasic

Posted: Thu Feb 14, 2013 5:51 pm
by User_Russian
jassing wrote:Nice! So same stuff will work in x64 and x86? Very slick.
To create the x64 drivers to compile programs and libraries for this platform (see the folder "src").

Re: Driver development using PureBasic

Posted: Thu Feb 14, 2013 10:42 pm
by User_Russian
Consider another example, buyout shows how to work with the kernel memory and how to get a list of loaded modules.

Code: Select all

Declare DriverEntry(*DriverObject, *RegistryPath)

ImportC "ntoskrnl.lib"
	DbgPrint(*String)
	memset(*mem, Byte, Size)
EndImport

Import "ntoskrnl.lib"
  ZwQuerySystemInformation(SysInfoClass, *SysInfo, SysInfoLength.l, *ReturnLength)
  ExAllocatePool(PoolType, NumBytes)
  ExFreePool(*Point)
EndImport

!extrn _memset

 *A=@DriverEntry()
!jmp [p_A]

XIncludeFile "StringDr.pbi"

EnableExplicit

Structure DrArray_SMI
  Arrays.SYSTEM_MODULE_INFORMATION[1]
EndStructure

Procedure DriverEntry(*DriverObject.DRIVER_OBJECT, *RegistryPath.UNICODE_STRING)
  Protected cb.l, *p, *pArray.DrArray_SMI
  Protected dwNumModules.l
  Protected Buffer.s{128}, Temp
  
  DbgPrint(@"LoadDriver")
  
  cb=0
  ZwQuerySystemInformation(#SystemModuleInformation, @*p, 0, @cb)
  If cb <> 0 ;
    cb + 32
    *p = ExAllocatePool(#PagedPool, cb)
    If *p<>#Null
      If ZwQuerySystemInformation(#SystemModuleInformation, *p, cb, @cb)= #STATUS_SUCCESS

        
        PokeS(@Buffer, "Bytes:  ") : Temp=8
        Temp+StrDr(@Buffer+Temp, 128-Temp, cb)
        PokeS(@Buffer+Temp, ";  Address: 0x") : Temp+14
        Temp+HexDr(@Buffer+Temp, 128-Temp, *p)
        PokeS(@Buffer+Temp, ";  Modules: ") : Temp+12
        Temp+StrDr(@Buffer+Temp, 128-Temp, dwNumModules)
        
        DbgPrint(@Buffer)
        memset(@Buffer, 0, 128)
        
        
        *pArray = *p
        dwNumModules = PeekL(*pArray)
        *pArray + 4
        
        Temp=0
        While Temp < dwNumModules

          DbgPrint(@*pArray\Arrays[Temp]\ImageName[0])
          
          Temp+1
        Wend
          
      EndIf
      
      ExFreePool(*p)
    EndIf
  EndIf
  
	ProcedureReturn #STATUS_DEVICE_CONFIGURATION_ERROR  ;#STATUS_SUCCESS
EndProcedure
Run the program "Dbgview.exe" and "KmdManager.exe"
Click on the buttons in sequence "Register", "Run", "Stop" and "Unregister".

In the debugger, you will see the current list of loaded modules.

Image

Re: Driver development using PureBasic

Posted: Fri Feb 15, 2013 7:50 am
by Rings
thx alot, amazing stuff !!!

Re: Driver development using PureBasic

Posted: Fri Feb 15, 2013 8:24 am
by Didelphodon
finally! can't wait to give it a try :-)

Re: Driver development using PureBasic

Posted: Fri Feb 15, 2013 10:20 am
by sec
Wy do we need replace fasm, polink?

Re: Driver development using PureBasic

Posted: Fri Feb 15, 2013 11:14 am
by User_Russian
Do not need to be replaced.
Need rename.
Install, PureBasic 5.10 (demo or full version) to any folder.
Open a folder "Compilers" and rename the file "Fasm.exe" in "Fasm_.exe", and the file "Polink.exe" in "Polink_.exe".
Extract the archive to a folder with PureBasic.
To create a driver, you need a preprocessor between "pbcompiler.exe" and "Fasm.exe" and "Polink.exe".
This is most easily done by renaming the original file and used in place of their own, and that was done.
In folder "src" you can find the source code for the preprocessor "Fasm.exe" and "Polink.exe".

Re: Driver development using PureBasic

Posted: Sun Feb 17, 2013 1:21 pm
by Thorium
Awesome!
Thanks for sharing.

Re: Driver development using PureBasic

Posted: Tue Feb 19, 2013 8:24 am
by Nituvious
Wow, very cool! I thought driver creation was assumed to be impossible with PureBasic?

Re: Driver development using PureBasic

Posted: Tue Feb 19, 2013 5:45 pm
by Thorium
Nituvious wrote:Wow, very cool! I thought driver creation was assumed to be impossible with PureBasic?
It was... until now!

Re: Driver development using PureBasic

Posted: Tue Feb 19, 2013 10:40 pm
by User_Russian
The previous examples were more theoretical.
Consider one more example driver. This is a complete driver, which can be used not only by example, but also be used in your program. If someone had to work with the ports of the computer, then perhaps you know that modern operating systems Windows, based on the core NT, do not allow direct access to the ports of the user application. In other words, a user-mode prohibited assembly instructions "IN" and "OUT". But these instructions are allowed in kernel mode, if you create a driver, which on command, the user program will work with a given port, the program will be able to using driver to read or write data to the required port. :D
But as we know, everything is invented before us, and there is such a project, as a library Inpout32.dll. http://logix4u.net/
Let's create using PureBasic its analogue. :)

First we create a kernel mode driver.

Code: Select all

Declare DriverEntry(*DriverObject, *RegistryPath)

; Import kernel function of "ntoskrnl.exe".
Import "ntoskrnl.lib"
  IoCompleteRequest(*IRP, PriorityBoost)
  RtlInitUnicodeString(*UString, *String)
  IoCreateDevice(*DriverObject, DeviceExtensionSize, 
                 *UDeviceName, DeviceType,
                 DeviceCharacteristics, Exclusive, *DeviceObject)
  IoDeleteDevice(*DeviceObject)
  IoCreateSymbolicLink(*SymbolicLinkName, *DeviceName)
  IoDeleteSymbolicLink(*SymbolicLinkName)
EndImport


#IOCTL_READ_PORT_UCHAR	 = $200 
#IOCTL_WRITE_PORT_UCHAR	 = $400

; Entry point in main function the driver.
*EntryPoint = @DriverEntry()
!jmp [p_EntryPoint]

EnableExplicit

; Direct write in port.
Procedure.a InPort(Port.u)
  Protected Result.a=0
  EnableASM
  MOV DX, Port
  IN  al,DX
  MOV Result, al
  DisableASM
  ProcedureReturn Result
EndProcedure

; Direct reading from port.
Procedure OutPort(Port.u, Byte.a)
  EnableASM
  MOV al, Byte
  MOV DX, Port
  OUT DX, al
  DisableASM
EndProcedure

; This procedure, called kernel manager,
; calling WinAPI functions DeviceIoControl_().
Procedure DeviceIoControl(*DeviceObject.DEVICE_OBJECT, *pIrp.IRP)
  Protected ntStatus, *Point, *Stack.IO_STACK_LOCATION
  Protected InBuffersize, OutBuffersize, CtrlBuff
  Protected Port.u, Byte.a, InByte.a, Code
  
  *Stack = *pIrp\Tail\Overlay\CurrentStackLocation
  
  ; This is the "dwIoControlCode", sent messages of WinAPI functions DeviceIoControl_().
  Code = *Stack\Parameters\DeviceIoControl\IoControlCode
  
  ; This is the "nInBufferSize", sent messages of WinAPI functions DeviceIoControl_().
  InBuffersize = *Stack\Parameters\DeviceIoControl\InputBufferLength
  
  ; This is the "nOutBufferSize", sent messages of WinAPI functions DeviceIoControl_().
  OutBuffersize = *Stack\Parameters\DeviceIoControl\OutputBufferLength
  
  ; This is the "lpInBuffer", sent messages of WinAPI functions DeviceIoControl_().
  CtrlBuff   = PeekI(*pIrp\SystemBuffer)
  Port = CtrlBuff & $FFFF
  
  ntStatus = #STATUS_SUCCESS ; This value returns WinAPI functions DeviceIoControl_().
  
  Select Code
    Case #IOCTL_READ_PORT_UCHAR ; Reading from a port.
      InByte=InPort(Port)  ; 
      PokeA(*pIrp\SystemBuffer, InByte) ; Read a byte from the port is placed in a buffer.
      *pIrp\IoStatus\Information = 1    ; In buffer, write 1 byte.
      
    Case #IOCTL_WRITE_PORT_UCHAR ; Writing in Port.
      Byte = (CtrlBuff>>16)&255
      OutPort(Port, Byte)
      *pIrp\IoStatus\Information = 0  ; In buffer, write 0 byte.
      
    Default                          ; The command is not supported.
      ntStatus = #STATUS_UNSUCCESSFUL
      *pIrp\IoStatus\Information = 0  ; In buffer, write 0 byte.
  EndSelect
  
  ; Message processed.
  ; More information. http://msdn.microsoft.com/en-us/library/windows/hardware/ff548343(v=vs.85).aspx
  *pIrp\IoStatus\Status = ntStatus
  IoCompleteRequest(*pIrp, #IO_NO_INCREMENT)
  
  ProcedureReturn ntStatus
EndProcedure


Procedure CreateDispatch(*DeviceObject.DEVICE_OBJECT, *pIrp.IRP)
  
  *pIrp\IoStatus\Information = 0
  *pIrp\IoStatus\Status = #STATUS_SUCCESS
  IoCompleteRequest(*pIrp, #IO_NO_INCREMENT)
  
  ProcedureReturn #STATUS_SUCCESS
EndProcedure

; Procedure for unloading driver.
Procedure UnloadDriver(*DriverObject.DRIVER_OBJECT)
  Protected uniDOSString.UNICODE_STRING
  
  RtlInitUnicodeString(@uniDOSString, ?DosDevices)
  IoDeleteSymbolicLink (@uniDOSString)
  IoDeleteDevice(*DriverObject\DeviceObject)
  
EndProcedure

; Procedure initialize driver.
Procedure DriverEntry(*DriverObject.DRIVER_OBJECT, *RegistryPath.UNICODE_STRING)
  Protected deviceObject.DEVICE_OBJECT
  Protected uniNameString.UNICODE_STRING
  Protected uniDOSString.UNICODE_STRING
  Protected status
  
  RtlInitUnicodeString(@uniNameString, ?Device)
  RtlInitUnicodeString(@uniDOSString, ?DosDevices)
  
  status = IoCreateDevice(*DriverObject, 0, @uniNameString, #FILE_DEVICE_UNKNOWN, 0, #False, @deviceObject)
  If status <> #STATUS_SUCCESS
    ProcedureReturn status
  EndIf
  
  status = IoCreateSymbolicLink(@uniDOSString, @uniNameString)
  If status <> #STATUS_SUCCESS
    IoDeleteDevice(@deviceObject) ; We need to remove an object!
                                  ; In mode kernel, of system will
                                  ; not do it even after unloading the driver!
    ProcedureReturn status
  EndIf
  
  
  ; Registration procedures.
  *DriverObject\DriverUnload = @UnloadDriver() ; Procedure for unloading drivers
  *DriverObject\MajorFunction[#IRP_MJ_CREATE] = @CreateDispatch() ; Called when WinAPI, CreateFile_().
  *DriverObject\MajorFunction[#IRP_MJ_CLOSE] = @CreateDispatch() ; Called when WinAPI, CloseHandle_().
  *DriverObject\MajorFunction[#IRP_MJ_DEVICE_CONTROL] = @DeviceIoControl() ; ; Called when WinAPI, DeviceIoControl_().
  
  ProcedureReturn #STATUS_SUCCESS
EndProcedure



; Name of the driver in system.
DataSection
  Device:
  !du '\Device\pbdriverio', 0, 0
  
  DosDevices:
  !du '\DosDevices\pbdriverio', 0, 0
EndDataSection
When you load the driver, will be performed the procedure code DriverEntry(), in which the driver, register (to him will be assigned symbolic names that can be used in CreateFile_()) and will be assigned to unload the driver procedure and call processing WinAPI functions CreateFile_(), CloseHandle_() and DeviceIoControl_().
In procedure of DeviceIoControl() driver that handles the request of the same name WinAPI function, with which the data transmitted to the driver and received from him.
Procedure processes the two queries:
#IOCTL_READ_PORT_UCHAR ; Reading from a port.
#IOCTL_WRITE_PORT_UCHAR ; Writing in Port.
Upon receipt of the first request, the called is procedure InPort (), which performed read data from the port.
Upon receipt of the second request, it calls the procedure OutPort (), writes the data to the port.

----------

Now for a code library (DLL), is being called from the driver. This is just an example. Not necessarily that the driver was in a DLL. It may invoke a directly of the program's.

Code: Select all

#IOCTL_READ_PORT_UCHAR	 = $200 
#IOCTL_WRITE_PORT_UCHAR	 = $400

CompilerIf Defined(PBIO_OK, #PB_Constant)=0
  Enumeration
    #PBIO_OK
    #PBIO_ErrCopyDriver
    #PBIO_ErrInstallDriver
    #PBIO_ErrAccess
  EndEnumeration
CompilerEndIf

Structure Param_Info
  hDriverID.i
  ErrStatus.i
  DrFileName.s
  DriverName.s
  *Point
  Len.i
EndStructure


Declare OpenDriver()
Declare Driver_UnInstall(Drivers_Name.s)

EnableExplicit


ProcedureDLL AttachProcess(Instance)
  
  Global ParamInfo.Param_Info
  
  
  ParamInfo\ErrStatus=#PBIO_OK
  ParamInfo\DrFileName = GetTemporaryDirectory()+"pbdriverio.sys"
  ParamInfo\DriverName = "pbdriverio"
  
  ParamInfo\Point = ?Driver
  ParamInfo\Len   = ?DriverEnd-?Driver
  
  ParamInfo\hDriverID = OpenDriver()
EndProcedure


ProcedureDLL DetachProcess(Instance)
  If ParamInfo\hDriverID
    CloseHandle_(ParamInfo\hDriverID)
    ParamInfo\hDriverID=0
  EndIf
  
  ; Unloading drivers
;  Driver_UnInstall(ParamInfo\DriverName)
;   If FileSize(ParamInfo\DrFileName)>0
;     DeleteFile(ParamInfo\DrFileName)
;   EndIf
EndProcedure


Procedure Driver_Start(Drivers_Name.s)
  Protected hSCManager.i, hServ.i
  Protected ErrorCode, Result
  
  Result=#False
  hSCManager  = OpenSCManager_(#Null, #Null, #SC_MANAGER_ALL_ACCESS)
  If hSCManager = 0
    If GetLastError_() = #ERROR_ACCESS_DENIED
      hSCManager  = OpenSCManager_(#Null, #Null, #GENERIC_READ)
      hServ       = OpenService_(hSCManager, Drivers_Name, #GENERIC_EXECUTE)
      If StartService_(hServ, 0, #Null)
        Result=#True
      EndIf
    EndIf
    
  Else
    hServ       = OpenService_(hSCManager, Drivers_Name, #GENERIC_EXECUTE)
    If StartService_(hServ, 0, #Null)
      Result=#True  
    Else
      ErrorCode=GetLastError_()
      If ErrorCode=1056
        Result=#True
      EndIf
    EndIf
  EndIf
  
  If hServ : CloseServiceHandle_(hServ) : EndIf
  If hSCManager : CloseServiceHandle_(hSCManager) : EndIf
  
  ProcedureReturn Result
EndProcedure


Procedure Driver_UnInstall(Drivers_Name.s)
  Protected hSCManager.i, hServ.i
  Protected ServiceStatus.SERVICE_STATUS
  
  hSCManager  = OpenSCManager_(#Null, #Null, #SC_MANAGER_ALL_ACCESS)
  hServ       = OpenService_(hSCManager, Drivers_Name, #SERVICE_ALL_ACCESS)
  
  If hServ<>#Null
     ControlService_(hServ, #SERVICE_CONTROL_STOP, @ServiceStatus)
     DeleteService_(hServ)
  EndIf
  
  If hServ : CloseServiceHandle_(hServ) : EndIf
  If hSCManager : CloseServiceHandle_(hSCManager) : EndIf
EndProcedure


Procedure Driver_Install(Drivers_Name.s)
  Protected Result, FileID, File.s, Mgr
  Protected ErrorCode, Ser, Count
  Result=#False
  Count=0 : FileID=0 : Mgr=0 : Ser=0

  File=ParamInfo\DrFileName
  
  
  If FileSize(File) <> ParamInfo\Len Or CRC32FileFingerprint(File) <> CRC32Fingerprint(ParamInfo\Point, ParamInfo\Len)
    
    FileID=CreateFile(#PB_Any, File)
    If FileID
      WriteData(FileID, ParamInfo\Point, ParamInfo\Len)
      CloseFile(FileID)
    ElseIf ParamInfo\ErrStatus = #PBIO_OK
      ParamInfo\ErrStatus = #PBIO_ErrCopyDriver
    EndIf
    
  Else
    FileID=2
  EndIf
  
  If FileID
    
    Driver_Install_M1:
    Mgr = OpenSCManager_(#Null, #Null, #SC_MANAGER_ALL_ACCESS)
    If Mgr
      Ser = CreateService_(Mgr, Drivers_Name, Drivers_Name, #SERVICE_ALL_ACCESS, #SERVICE_KERNEL_DRIVER, #SERVICE_DEMAND_START, #SERVICE_ERROR_NORMAL, File, #Null, 0, 0, 0, 0)
      ErrorCode=GetLastError_()
      If Ser 
        Result=#True 
      ElseIf ErrorCode=1073
        Result=#True 
      ElseIf ErrorCode=1072
        If Count=0
          
          If Ser : CloseServiceHandle_(Ser) : EndIf
          If Mgr : CloseServiceHandle_(Mgr) : EndIf
          Driver_UnInstall(Drivers_Name)
          Count+1
          Goto Driver_Install_M1
        EndIf
      EndIf
    EndIf
    
  EndIf
  
  If Ser : CloseServiceHandle_(Ser) : EndIf
  If Mgr : CloseServiceHandle_(Mgr) : EndIf
  
  If Result=#False And ParamInfo\ErrStatus = #PBIO_OK
    ParamInfo\ErrStatus = #PBIO_ErrInstallDriver
  EndIf
  
  ProcedureReturn Result
EndProcedure

Procedure OpenDriver()
  Protected Result, hDriver
  Result=0
  
  hDriver = CreateFile_("\\.\"+ParamInfo\DriverName, #GENERIC_READ | #GENERIC_WRITE, 0, 0, #OPEN_EXISTING, #FILE_ATTRIBUTE_NORMAL, 0)
  If hDriver=#INVALID_HANDLE_VALUE
    If Driver_Install(ParamInfo\DriverName)
      If Driver_Start(ParamInfo\DriverName)
        hDriver = CreateFile_("\\.\"+ParamInfo\DriverName, #GENERIC_READ | #GENERIC_WRITE, 0, 0, #OPEN_EXISTING, #FILE_ATTRIBUTE_NORMAL, 0)
        If hDriver <> #INVALID_HANDLE_VALUE
          Result=hDriver
        EndIf
      EndIf
    EndIf
  Else
    Result=hDriver
  EndIf
  
  If Result=0 And ParamInfo\ErrStatus = #PBIO_OK
    ParamInfo\ErrStatus = #PBIO_ErrAccess
  EndIf
  
  ProcedureReturn Result
EndProcedure


ProcedureDLL Inp(PortAddress.u) ; Read a byte from the port, address given in 'PortAddress'.
  Protected Result, Buffer.i, BytesReturned.i, OutBuff.i
  Result=0 : Buffer=0 : OutBuff=0
  
  If ParamInfo\hDriverID
    Buffer=PortAddress
     DeviceIoControl_(ParamInfo\hDriverID, #IOCTL_READ_PORT_UCHAR, @Buffer, 2, @OutBuff, 1, @BytesReturned, 0)
    Result=OutBuff
  EndIf
  
  ProcedureReturn Result
EndProcedure

ProcedureDLL Out(PortAddress.u, Byte.a) ; Writing a byte to the port, address given in 'PortAddress'.
  Protected Result, Buffer.i, BytesReturned.i
  Result=0
  
  If ParamInfo\hDriverID
    Buffer=PortAddress
    PokeA(@Buffer+2, Byte)
   DeviceIoControl_(ParamInfo\hDriverID, #IOCTL_WRITE_PORT_UCHAR, @Buffer, 3, 0, 0, @BytesReturned, 0)
  EndIf
  
  ProcedureReturn Result
EndProcedure


ProcedureDLL Status() ; Status of the driver. If there are no errors, then 0.
  ProcedureReturn ParamInfo\ErrStatus
EndProcedure


ProcedureDLL.f Version()
  ProcedureReturn 1.4
EndProcedure


DataSection
  Driver:
  IncludeBinary "Driver\pbdriverio.sys"
  DriverEnd::
EndDataSection
The library in the first place trying to call the driver on his symbolic name "\\.\pbdriverio".
If she fails, then checks to see if the driver on the disk and, if necessary, the driver is extracted from the library to the disk, and then registers in the system. Then, again, tries to call the driver on his name symbols.

From the library exports several functions.
Inp(PortAddress.u) ; Read a byte from the port, address given in 'PortAddress'.

Out(PortAddress.u, Byte.a) ; Writing a byte to the port, address given in 'PortAddress'.

Status() ; Status of the driver. If there are no errors, then 0.

Version()
Files: http://pure-basic.narod.ru/forum_files/ ... PortIO.zip

Re: Driver development using PureBasic

Posted: Wed Feb 20, 2013 5:59 am
by Blankname
Very nice, maybe some sort of functionality Fred can add native capabilities to PB. Would be insane to be able to compile at least Windows drivers with PB!

P.S. Is there, and if so what are the limitations?

Re: Driver development using PureBasic

Posted: Sun Mar 03, 2013 11:31 pm
by marroh
Great performance, this opens up many possibilities... :)

Thanks and 10 stars from me!