For a person who often writes programs, writing drivers is not a difficult task. Because there are a lot of ready-made codes on the Internet, if you want to implement a certain function, just use Ctrl C and Ctrl V to solve the problem. But whether the written driver can be loaded into the kernel is another matter. To be precise, whether it can exist on someone else's hard drive is another matter.
Because many anti-virus software (especially untechnical ones like 360) delete files with the suffix sys when they see them.
There is no chance to even call NtLoadDriver. For general software, it is enough to give a statement explaining the solution
. But for malicious programs, no statement can be given. Therefore, many malware authors find another way to do bad things by using digitally signed drivers written by large companies.
Some people say, how can the drivers that big companies do well be used to do bad things? In fact, this is something very easy to understand.
Many security or system optimization software, and even software unrelated to the system (such as Thunder), come with drivers.
These drivers have a certain degree of versatility. Netizen q_lai_a_qu said in his blog: "ComputerZ.sys...it's okay
I found out that it was Master Lu's driver. I found that this driver has full functions and no caller verification! It can both read and write Msr
Register, you can also use in and out instructions to read and write ports, and the char/short/long data length is complete!” This is
a personal statement, please make your own judgment on the credibility. Here is a more credible example: A virus once used 360's AntiRK.dll to delete anti-virus software files (please search Google for "360 antirk.dll", you will be surprised) Found.
Although AntiRK.dll is not a driver, it has been illegally used). Viruses that damage anti-virus software are already considered childish.
In fact, certain drivers can also damage hardware! I was recently fiddling with the hardware on my laptop, and netizens on the "Friends Club" recommended several software to me: SetFSB, ThrottleStop, NvFlash, and WinFlash. They are software for modifying the CPU FSB, setting the CPU multiplier (which can adjust the CPU voltage), reading and writing the graphics card BIOS, and reading and writing the motherboard BIOS. To sum up their characteristics in one word,
They all support NT x86/x64, and their drivers have formal digital signatures (especially the last two, which have the digital signatures of NVIDIA and ASUS respectively).
The most important thing is that their drivers are not packed or verified.
If you use these drivers and add a little reverse knowledge, you can Can create destructive viruses (the following is excerpted from my post on Zishui
Jing Programming Forum):
1.SetFSB can adjust the FSB of the processor. If you directly set the FSB Adjust to 600MHz, the computer will instantly black screen, which may
damage the CPU or motherboard;
2. ThrottleStop can adjust the CPU multiplier (if the CPU does not lock the multiplier), if directly Adjust the multiplier to 31,
the computer will black out instantly, which may damage the CPU or motherboard; ThrottleStop can also adjust the core voltage of the CPU. If
the core voltage of the CPU is adjusted to 3V can directly burn the CPU or even the motherboard;
3. NvFlash, WinFlash and other software can directly read and write BIOS (graphics card BIOS and motherboard BIOS), we can write all BIOS to zero ;
4. If you create a virus, first write bad graphics card BIOS and motherboard BIOS, and then burn the graphics card and CPU by adjusting the voltage
(it may damage the motherboard together);
Solution
It can be seen that the driver without verifying the caller is really harmful. I was recently commissioned by the college to create a software that requires a driver (the driver will be digitally signed). In order to prevent the above tragedy from happening, I decided to figure out how to prevent my own driver from being used maliciously before officially writing the driver. I have asked this question on the Amethyst Programming Forum before.
Netizens’ answers were varied, but they can be roughly divided into three categories: The first category is information verification, such as applications
The program sends a message to the driver to verify that it is "one of our own"; the second type is shelling protection, such as adding a very strong and difficult-to-remove shell to the driver and application, and using VMP encryption Communication part (similar to XueTr's approach); others have proposed hybrid applications that combine the practices of the first and second categories.
These three ideas all seem good, but I think they are inappropriate. The first one: Others only need to reverse engineer all the drivers. The second one: Although VMP protection and protective shell make it difficult to crack, it does not make it impossible. Moreover
VMP and protective shell can reduce the efficiency of program execution, which I don't like very much. The most disgusting thing is that anti-virus software will report viruses to programs that have been packed (even UPX) and VMP, which is not worth the gain. So I came up with the third idea: verify the caller's
characteristics. If it matches, the function statement will be executed, otherwise it will not be executed. How to verify the caller's signature? Many people think of using CRC32 or MD5. It's not impossible to use them, but I still have my own ideas.
My idea is to design a verification algorithm by myself. Its rules are as follows:
1. Obtain the EPROCESS of the caller
2. Pass the caller EPROCESS gets the caller's file path
3. Gets the entire content of the caller's file and puts it into the byte array buff
4. Adds all the elements in the buff in sequence Subtract (fb1 fb2 - fb3...) to get y1
5. XOR all the elements in the buff in sequence (0 XOR fb1 XOR fb2 XOR fb3...) to get y2
Compare y1 and y2 with the calculated values. If they are the same, the function code will be executed. If they are not the same, the function code will not be executed.
Execute the function code
Get the caller's EPROCESS Just use PsGetCurrentProcess() directly. Obtaining the caller's file path
is troublesome. You can use the code I purchased from an expert before (already encapsulated as a function for easy calling):
//Get the full path of the process based on EPROCESS
VOID GetFullPathByEprocess(ULONG eprocess, PCHAR ProcessImageName)
{
ULONG object;
PFILE_OBJECT FileObject;
UNICODE_STRING FilePath;
UNICODE_STRING DosName;
STRING AnsiString;
FileObject = NULL;
FilePath.Buffer = NULL;
FilePath.Length = 0;
*ProcessImageName = 0;
//Eprocess-gt; sectionobject(offset_SectionObject)
if(MmIsAddressValid((PULONG)(eprocess offset_SectionObject)))
{
object=(*(PULONG)(eprocess offset_SectionObject));
p>
//KdPrint(("[GetProcessFileName] sectionobject: 0xx\n", object));
if(MmIsAddressValid((PULONG)((ULONG)object 0x014))) p>
{
object=*(PULONG)((ULONG)object 0x014);
//KdPrint(("[GetProcessFileName] Segment: 0xx\n", object));
if(MmIsAddressValid((PULONG)((ULONG)object 0x0)))
{
object=*(PULONG)(( ULONG_PTR)object 0x0);
//KdPrint(("[GetProcessFileName]
ControlAera: 0xx\n", object));
if(MmIsAddressVali
d((PULONG)((ULONG)object 0x024)))
{
object=*(PULONG)((ULONG)object 0x024);
if (NtBuildNumber gt;= 6000) object=((ULONG)object amp;
0xfffffff8);
//KdPrint(("[GetProcessFileName]
FilePointer: 0xx\n", object));
}
else
return;
}
else
return;
}
else
return;
}
else
return;
FileObject=(PFILE_OBJECT)object;
FilePath.Buffer = ExAllocatePool(PagedPool, 0x200);
FilePath.MaximumLength = 0x200;
//KdPrint(("[GetProcessFileName]
FilePointer:wZ\n",&FilePointer-gt;FileName));
ObReferenceObjectByPointer((PVOID)FileObject, 0, NULL, KernelMode);
RtlVolumeDeviceToDosName(FileObject-gt; DeviceObject, & DosName);
RtlCopyUnicodeString(amp; FilePath, amp;DosName);
RtlAppendUnicodeStringToString(amp;FilePath, amp;FileObject-gt;FileName);
ObDereferenceObject(FileObject);
RtlUnicodeStringToAnsiString(amp; AnsiString, & FilePath, TRUE);
if (AnsiString.Length >= 216)
{
memcpy(ProcessImageName, AnsiString.Buffer, 0x100u );
*(ProcessImageName 215) = 0;
}
else
{
memcpy( ProcessImageName, AnsiString.Buffer, AnsiString.Length);
ProcessImageName[AnsiString.Length] = 0;
}
RtlFreeAnsiString(amp; AnsiString);
ExFreePool(DosName.Buffer);
ExFreePool(FilePath.Buff
er);
}
The above code requires three hard codes, namely NtBuildNumber (system version number),
SectionObject item and UniqueProcessId item in EPROCESS offset. The operating system I tested on was Windows 2003. So
I defined it as follows in the code:
#define offset_SectionObject 0x124
#define offset_UniqueProcessId 0x94
ULONG NtBuildNumber=3790;
After obtaining the process path, verify the signature.
Since the process has been explained clearly, the code is given directly:
VOID CalcChar(PUNICODE_STRING logFileUnicodeString, LONG *XorChar, LONG
*AnSChar)
{
OBJECT_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK iostatus;
HANDLE hfile;
NTSTATUS ntStatus;
FILE_STANDARD_INFORMATION fsi;
PUCHAR pBuffer;
ULONG i=0, y1=0, y2=0;
//Initialize objectAttributes
InitializeObjectAttributes(amp ;objectAttributes,
logFileUnicodeString,
OBJ_CASE_INSENSITIVE, //case sensitive
NULL,
NULL);
//Create file
ntStatus = ZwCreateFile(amp;hfile,
GENERIC_READ,
amp;objectAttributes,
amp ;iostatus,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN, //Create even if the file exists
p>FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0 );
if (!NT_SUCCESS(ntStatus))
{
dprintf("The file is not exist!\n");
return;
}
//Read the file length
ntStatus = ZwQueryInformationFile(hfile,
amp;iostatus,
amp;fsi,
sizeof(FILE_STANDARD_INFORMATION),
FileStandardInformation);
dprintf("The program want to read d bytes\n", fsi.EndOfFile.QuadPart);
//Allocate for the read file Buffer
pBuffer = (PUCHAR)ExAllocatePool(PagedPool,
(LONG)fsi.EndOfFile.QuadPart);
//Read file
ZwReadFile(hfile, NULL,
NULL, NULL,
amp;iostatus,
pBuffer,
(LONG )fsi.EndOfFile.QuadPart,
NULL, NULL);
dprintf("Th
e program really read d bytes\n",iostatus.Information);
//XOR calculation
for(i=0;ilt;iostatus.Information;i) p>
y1=y1^(LONG)(*(pBuffer i));
*XorChar=y1;
//Addition and subtraction calculation
for(i=0; ilt; iostatus.Information; i )
{
if(i2==0)
y2=y2 (LONG) (*(pBuffer i));
else
y2=y2-(LONG)(*(pBuffer i));
}
*AnSChar=y2;
//Close the file handle
ZwClose(hfile);
//Release the buffer
ExFreePool(pBuffer);
}
The next step is to call it. We need to write a function VerifyCaller, and there are two values ??required in this function
Solidified in the driver, they are the two characteristic values ??of legal callers.
In order to facilitate the calculation of these two characteristic values, I specially wrote an application. The core code is as follows:
Option Explicit
Private Function ReadFile(ByVal strFileName As String, Optional ByVal
lngStartPos As Long = 1, Optional ByVallngFileSize As Long = -1) As Byte()
Dim FilNum As Long
FilNum = FreeFile
Open strFileName For Binary As #FilNum
If lngFileSize = -1 Then
ReDim ReadFile(LOF(FilNum) - lngStartPos)
Else
ReDim ReadFile(lngFileSize - 1)
End If
Get #FilNum, lngStartPos, ReadFile
Close #FilNum
End Function
Private Function WriteFile(ByVal strFileName As String, bytData() As Byte,
Optional ByVal lngStartPos As Long = -1, Optional ByVal OverWrite As Boolean =
True)
On Error GoTo erx
Dim FilNum As Long
FilNum = FreeFile
If OverWrite = True And Dir(strFileName) lt;gt; "" Then
Kill strFileName
End If
Open strFileName For Binary As #FilNum
If lngStartPos = -1 Then
Put #FilNum, LOF(FilNum) 1, bytData
Else
Put #FilNum, lngStartPos , bytData
End If
Close #FilNum
erx:
End Function
Private Sub Command1_Click( )
Dim buff() As Byte, i As Long, y As Long, ub As Long
'text1.text is the file name
buff = ReadFile(Text1.Text, 1, -1)
ub = UBound(buff)
'calc xor char
y = 0
For i = 0 To ub
y = y Xor buff(i)
Next
p>
Text2.Text = CLng(y)
DoEvents
'calc add/sub char
y = 0
For i = 0 To ub
If i Mod 2 = 0 Then
y = y CLng(buff(i))
Else
y = y - CLng(buff(i))
End If
Next
Text3.Text = CLng(y)
End Sub
Private Sub Form_Load()
Me.Icon = LoadPicture("")
End Sub
Driver The VerifyCaller code in is as follows:
LONG VerifyCaller(void)
{
PEPROCESS cur_ep;
char cur_pp[260];
char *nt_cur_pp;
ANSI_STRING asCur_pp;
UNICODE_STRING usCur_pp;
LONG xorc, ansc;
cur_ep =PsGetCurrentProcess();
GetFullPathByEprocess((ULONG)cur_ep, cur_pp);
//Add \?\ in front of the file name
nt_cur_pp=cs ("\\?\\", cur_pp);
DbgPrint("s", nt_cur_pp);
RtlInitAnsiString(amp; asCur_pp, nt_cur_pp);
RtlAnsiStringToUnicodeString(amp;usCur_pp, amp;asCur_pp, TRUE);
DbgPrint("wZ", amp;usCur_pp);
CalcChar(amp;usCur_pp, amp;xorc, amp ;ansc);
DbgPrint("XorChar: ld; AnSChar: ld", xorc, ansc);
//This is the characteristic code of the legal program calculated in advance and must be Solidified in the driver!
if(xorc==186 amp; amp; ansc==136176)
return 1;
else
return 0;
}
Before each function of the DispatchIoctl function is executed, VerifyCaller() is called to verify the caller:
switch(uIoControlCode)
{
case IOCTL_VERIFY:
{
DbgPrint("[MyDriver] DispatchIoctl - IOCTL_VERIFY");
if (VerifyCaller()==1)
DbgPrint("[MyDriver] {IOCTL_VERIFY} Function code run now!");
else
DbgPrint("[MyDriver] {IOCTL_VERIFY} You're illegal caller!");
status = STATUS_SUCCESS;
break;
}
//Omitted below
}
Run the test
3. First Legal callers, illegal callers (use eXeScope to patch the legal callers,
such as deleting the version information of the program) and drivers are copied to the virtual machine
4 .Use a legal caller to load the driver and execute it
5. Use an illegal caller to load the driver and execute it
6. Compare the output of the above two in DbgView
When the caller is legal:
When the caller is illegal:
Write it at the end
After writing this article, I must reiterate again: only When the driver carries a formal digital signature, the code that verifies the caller is valuable. Why do you say that? Because others cannot patch a driver with a formal digital signature (once
the driver is patched, the signature becomes invalid, just like a woman whose virginity has been lost, and is worthless. Although this metaphor is vulgar, it is very
p>
appropriate). A driver without a signature has no use value. Even if others want to use it, just throw the driver into IDA and all the code will come out.