Windows Mobile cabwiz.exe bug and fix
I recently had to create a SmartDevice project of a VB.NET application (Yuck!) with multiple localized resources in it. So that means that there is a subdirectory for each language that contains a lang.dll file holding the language ressourcesin the appropriate language.
When creating the setup project, everything works fine, but when trying to deploy the application, I saw that the .dll file was the same in every directory installed. It seems that sabwiz.exe has a bug that prevents you from adding multiple files with the same filename to a project, even though they go to different target directories. After googling a bit, i found this blog post that explains the issue and also contains a fix for that.
The fix offered there works fine, but its code is overly complicated and not available in source form. Now as I had to modify other things in the .inf file too, I just wanted to add my postprocessing to the fixer-Application.
So I wrote my own fixer that is smaller in size (only 6kb!) and doesn’t depend on ugly .NET framework, but is native Win32 code. Feel free to use it, source code can be found here. Precompiled executable can be found here.
To install, just:
cd [Visual Studio Dir]\SmartDevices\SDK\SDKTools\ move cabwiz.exe ___cabwiz.exe move cabwizfix.exe cabwiz.exe
I hope it’s useful to you.
Alternative to netstat -p
On SuSe Linux SLES 10.2, netstat -a -p for some reason doesn’t output any PID that belongs to a socket. This old trick helps:
netstat -a -e # Shows the inode-number for the process
lsof | grep <inode number>
Windows XP Updates
In case you missed the news: There are still XP Updates available after official end of support:
https://sebijk.com/community/board9-community/board5-pc/2984-xp-updates-weiterbeziehen/
Booting Memtest86+ from Harddisk
If there is no bootable media available, you can easily boot memtest86+ also via Windows boot.ini using GRUB4DOS. You just need to follow the instructions from this post.
In case this gets lost somehow, here are the step-by-step instructions:
1) Download current grub4dos package and extract grldr to c:\
2) Download current memtest86 Pre-compiled bootable binary and extract it to c:\memtest.bin
3) Create c:\menu.lst with the following content:
color black/cyan yellow/cyan timeout 2 default 0 title start Memtest86+ kernel /memtest.bin title Back to NTDLR chainloader /ntldr title Restart machine reboot
4) Add the line
c:\grldr="Memtest86+"
to the [operating systems] section of your c:\boot.ini, it may read something like that:
[boot loader] timeout=30 default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS [operating systems] multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /fastdetect /NoExecute=Opt c:\grldr="Memtest86+"
5) Reboot and start memtest86.
Android auf Motorola Milestone 2
Unlängst habe ich Android auf einem Motorola Milestone 2 Mobiltelefon auf ein CanogenMod 9 aktualisiert, um es als Testgerät für QT-Entwicklung verwenden zu können.
Das hat schlussendlich auch geklappt, aber ich habe ein paar kleinere Probleme damit gehabt, daher eine kleine Anleitung, wie es klappt:
Diese Installationsanleitung für CM9 befolgen.
Wenn man cm5.zip flasht kommt der bootloader aber nicht automatisch, wenn das
blaue Licht zu blinken beginnt, Volume-Down Knopf drücken!
Sonst wie in der Anleitung angegeben vorgehen.
Nach der Installation hat man ein Problem mit adb shell, weil
von einem Farbterminal ausgegangen wird, daher hat man dann
die Escape-Sequenzen für Farbe in der Ausgabe z.B. bei LS
Hier kann man das colored-Putty verwenden.
Man kann nun keine Programme mehr mit adb install installieren.
Um das zu beheben:
adb shell vi /data/local/userinit.sh
Eintragen:
ln -s /system/bin/ /bin
Telefon neu starten.
Reading the version number of a .NET assembly in plain C
I recently was assigned the task to write an Autoupdater application for Windows Mobile 6.5. Now my idea was to read the version resource from the application and determine its version and depending on the version propose an update to it or not. Now we all know that this is easy using the GetFileVersionInfo API. So I tried that and then notices that this works well for normal, native applications, but not for .NET Applications. It seems, that they don’t automatically create a Version Info resource section but instead just write the version to some structure that is called “Assembly”.
So I was in need for an API to read that information from the file. After some research, I found an API called Fusion that can be used to read Assembly informations from .NET executables. As it is a COM interface, it can be used from C code. Here is some C++ code that shows how to use this interface for this purpose.
But I wasn’t able to use it on Windows Mobile, because there is no such API on that operating system. This article explains how it works von Windows Mobile. So I finally ended up with parsing the file format and extracting the needed information. It’s not really hard to find the location in the PE file which contains all that tables that also contain Assembly information, but unfortunately, the tables have to be walked and processed according to their size information in order to find the real offset, as there are no RVA-pointers stored in the file that act as an index into the tables, but instead only variable size information is provided. For more information about the format, see this description.
Fortunately I found out that the WINE project already wrote an Assembly parser. So I stripped down the parser to the bare minimum required for getting the version info and packed it into a module that is easy to use. You can download it here.
Reenable UDMA on IDE-Channels in WinXP
I reently had the problem that there was a controller error on the Bus where I attached my SATA-Drivers (which are running in IDE compatibility mode) and Windows XP has the bad behaviour to reset the channel to PIO in that case and not set it back to UDMA.
Microsoft Knowledge Base suggests to reinstall the IDE Channel drivers in such a case, but due to another bug in gpt_loader.sys, this action results in a BSOD.
So there is a description how to reenable UDMA on the channels without the need to delete them (Basically delete “MasterIdDataChecksum” for the IDE master and “SlaveIdDataChecksum” for the IDE slave on the appropriate IDE channel
in HKLM\SYSTEM\CurrentControlSet\Control\Class\
{4D36E96A-E325-11CE-BFC1-08002BE10318}
Note that the subkeys under that reg key can be a lot and you hve to pick the correct one that corresponds to your IDE channel.
VueScan – Alte Scanner wiederbeleben
Ein kleiner Tip: Wenn man auf eine neue Windows-Version umsteigt, hat man oft das Problem, dass es für die alte Hardware keine Treiber mehr gibt. Das gilt auch für Scanner. Aber bevor man das Gerät dann schlimmstenfalls entsorgen muss, gibt es eine Abhilfe: Vuescan unterstützt viele Scanner nativ und arbeitet selbst als transparenter TWAIN-Treiber, der dann die Ansteuerung der Hardware übernimmt.. Sehr praktisch.
Paragon gpt_loader.sys BSOD analysis and fix
Update 27.09.2019
I forgot to patch an entry in the relocation table, causing a 0xc000001d bugcheck,
as soon as the driver is relocated. This is fixed now, sorry for the inconvenience.
Update 29.03.2014
I found a better solution that the one published 2 days ago which should really
fix the problem. In case that you downloaded and used the patcher earlier,
please copy back the gpt_loader.sys.bak that the patcher generated in your
SYSTEM32\Drivers directory to gpt_loader.sys, download the new patcher and
patch again.
The Problem
I recently came across a Bugcheck 0x00000024 (NTFS_FILE_SYSTEM) in
gpt_loader.sys on a friend’s PC.
gpt_loader.sys is a driver that belongs to Paragon GPT Loader
which allows a Windows XP user to use drives with a capacity over
2TB (which isn’t normally possible). This software also comes bundled
with Hiatchi-drives.
This driver hasn’t been updated since the end of 2010 (Version 8.0.1.0) and
unfortunately has a very nasty bug:
On high I/O loads on the target drive, the driver sometimes crashes with
a BSOD, if read/write-Operations are performed at sectors above the
2TB Limit, as it seems. That would explain, why the problem hasn’t occured
earlier but starts to get problematic with the drive being filled more and
more:
BUGCHECK 0x00000024 (0x001902FE, 0xB73A5D00,0xB73A59FC,0xBA92E9E2) BA92E92E base at BA928000
It seems that there are more people with this problem, but until now,
no fix has been found :
The analysis
So I was forced to analyze the probem myself using a minidump. I loaded
it into WinDbg and found the following interesting parts:
eax=00000000 ebx=89d69e70 ecx=ae1d5c38 edx=00000000 esi=89d69f4c edi=8af0f978 eip=b810e9e6 esp=ae1d5bac ebp=ae1d5bc4 iopl=0 nv up ei pl nz na po nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010202 gpt_lo_1+0x69e6: b810e9e6 80480301 or byte ptr [eax+3],1 ds:0023:00000003=??
DEFAULT_BUCKET_ID: NULL_CLASS_PTR_DEREFERENCE
STACK_TEXT: WARNING: Stack unwind information not available. Following frames may be wrong. ae1d5bc4 b810ea5d ae1d5c38 89d69e70 ae1d5d34 gpt_lo_1+0x69e6 ae1d5bd4 b810f3d7 ae1d5c38 89d69e70 89d69e70 gpt_lo_1+0x6a5d ae1d5d34 b7781411 89d69f4c 89d69e70 89eb2950 gpt_lo_1+0x73d7 ae1d5e00 804ef1f9 8af769e0 89d69f4c 89bc5bf8 fltsrv+0x4411
...
So this is a classic NULL-Pointer dereferencing bug.
I therefore loaded the driver into the disassembler and found the
following code in the function where it crashed:
Now let’s have a look at the position where it crashed:
.text:00016942 ; int __stdcall Z3TbGptLoader__irpReadWrite(int, PIRP Irp)
.text:00016942 sub_16942 proc near ; DATA XREF: .rdata:0001897Co
.text:00016942 ; .rdata:00018A1Co
.text:00016942
.text:00016942 var_C = dword ptr -0Ch
.text:00016942 var_8 = dword ptr -8
.text:00016942 var_1 = byte ptr -1
.text:00016942 arg_0 = dword ptr 8
.text:00016942 Irp = byte ptr 0Ch
.text:00016942
.text:00016942 mov edi, edi
.text:00016944 push ebp
.text:00016945 mov ebp, esp
.text:00016947 sub esp, 0Ch
.text:0001694A push ebx
.text:0001694B mov ebx, dword ptr [ebp+Irp]
.text:0001694E push esi
.text:0001694F mov esi, [ebx+60h]
.text:00016952 cmp byte ptr [esi], 3
.text:00016955 push edi
.text:00016956 setz [ebp+var_1]
.text:0001695A mov edi, ecx
.text:0001695C xor ecx, ecx
.text:0001695E xor eax, eax
.text:00016960 mov [ebp+var_C], ecx
.text:00016963 cmp [edi+7Ch], ecx
.text:00016966 jz short loc_1698B
.text:00016968 mov ebx, [edi+7Ch]
.text:0001696B push ecx
.text:0001696C push ebx
.text:0001696D push dword ptr [esi+10h]
.text:00016970 push dword ptr [esi+0Ch]
.text:00016973 call _alldiv
.text:00016978 mov ecx, edx
.text:0001697A mov [ebp+var_C], eax
.text:0001697D mov eax, [esi+4]
.text:00016980 xor edx, edx
.text:00016982 div ebx
.text:00016984 mov ebx, dword ptr [ebp+Irp]
.text:00016987 test ecx, ecx
.text:00016989 jnz short loc_1699E
.text:0001698B
.text:0001698B loc_1698B: ; CODE XREF: sub_16942+24j
.text:0001698B xor edx, edx
.text:0001698D add eax, [ebp+var_C]
.text:00016990 adc edx, ecx
.text:00016992 mov [ebp+var_8], edx
.text:00016995 js short loc_169A7
.text:00016997 jg short loc_1699E
.text:00016999 cmp eax, 0FFFFFFFFh
.text:0001699C jbe short loc_169A7
.text:0001699E
.text:0001699E loc_1699E: ; CODE XREF: sub_16942+47j
.text:0001699E ; sub_16942+55j
.text:0001699E cmp byte ptr [edi+0E2h], 0
.text:000169A5 jnz short loc_169C1
.text:000169A7
.text:000169A7 loc_169A7: ; CODE XREF: sub_16942+53j
.text:000169A7 ; sub_16942+5Aj
.text:000169A7 cmp [ebp+var_1], 0
.text:000169AB push ebx
.text:000169AC push [ebp+arg_0]
.text:000169AF mov ecx, edi
.text:000169B1 jz short loc_169BA
.text:000169B3 call sub_12B96
.text:000169B8 jmp short loc_16A37
.text:000169BA ; ---------------------------------------------------------------------------
.text:000169BA
.text:000169BA loc_169BA: ; CODE XREF: sub_16942+6Fj
.text:000169BA call sub_12B96
.text:000169BF jmp short loc_16A37
.text:000169C1 ; ---------------------------------------------------------------------------
.text:000169C1
.text:000169C1 loc_169C1: ; CODE XREF: sub_16942+63j
.text:000169C1 lea eax, [ebp+Irp]
.text:000169C4 push eax ; Irp
.text:000169C5 lea ecx, [edi+0FCh]
.text:000169CB call sub_166B2
.text:000169D0 test al, al
.text:000169D2 jz short loc_169F4
.text:000169D4 lea ecx, [edi+20h] ; Tag
.text:000169D7 call sub_10A04
.text:000169DC mov eax, [ebx+60h]
.text:000169DF mov ecx, [ebp+arg_0]
.text:000169E2 or byte ptr [eax+3], 1 ; CRASH!!!
.text:000169E6 add edi, 11Ch
.text:000169EC push edi
.text:000169ED call sub_11B14
.text:000169F2 jmp short loc_16A37
.text:000169F4 ; ---------------------------------------------------------------------------
.text:000169F4
.text:000169F4 loc_169F4: ; CODE XREF: sub_16942+90j
.text:000169F4 mov eax, dword_19024
.text:000169F9 mov esi, [eax]
.text:000169FB mov [ebp+var_8], eax
.text:000169FE cmp esi, eax
.text:00016A00 jz short loc_16A21
eax is obviously NULL from what we can see from the WinDbg output.
eax in turn is the pointer found at ebx+60h.
ebx is dword ptr [ebp+Irp], as we can also verify from
the Windbg output regarding registers and stack.
When checking the call stack, we can soon find out that this is
a handler function for the IRP_MJ_WRITE request.
Now we can apply the IRP-Structure from NTDDK to it and we find
out that ebx+60h actually is the member:
struct _IO_STACK_LOCATION *CurrentStackLocation;
So basically this resembles the call to IoGetCurrentIrpStackLocation().
Byte 3 there is the Control-Member, so the OR here tries to add
1 (SL_PENDING_RETURNED) to the Flags. This is what the DDK macro IoMarkIrpPending does.
When reading the docs to IRP_MJ_WRITE, we are told that this member
has to be set upon this IRP, so why the heck is it NULL there?
The next thing, that can be checked, is, if it was really NULL upon
function entry. As it gets already dereferenced at 00016952, it must
have been sane there, otherwise the crash would have occured there:
.text:0001694F mov esi, [ebx+60h] .text:00016952 cmp byte ptr [esi], 3
So the reason for the crash must be somewhere within that location
and 000169E2 where the crash finally occurs.
The only reference that we can see here which does not just read
the contents of the CurrentStackLocation is at 000169C1, where a
pointer to it gets loaded into register eax and then passed to the
function sub_166B2 via the stack:
.text:000169C1 lea eax, [ebp+Irp] .text:000169C4 push eax ; Irp .text:000169C5 lea ecx, [edi+0FCh] .text:000169CB call sub_166B2
Now let’s see what this function does:
.text:000166B2 ; int __stdcall AddIRPToList(KIRQL NewIrql) .text:000166B2 sub_166B2 proc near ; CODE XREF: sub_16942+8Dp .text:000166B2 ; sub_16B7E+20p .text:000166B2 .text:000166B2 NewIrql = byte ptr 8 .text:000166B2 .text:000166B2 mov edi, edi .text:000166B4 push ebp .text:000166B5 mov ebp, esp .text:000166B7 push esi .text:000166B8 push edi .text:000166B9 push 0 ; PoolType .text:000166BB push 0Ch ; NumberOfBytes .text:000166BD mov esi, ecx .text:000166BF call sub_16E36 ; ExAllocatePoolWithTag .text:000166C4 pop ecx .text:000166C5 pop ecx .text:000166C6 test eax, eax .text:000166C8 jz short loc_166D6 .text:000166CA mov ecx, dword ptr [ebp+NewIrql] .text:000166CD mov ecx, [ecx] .text:000166CF mov [eax+8], ecx .text:000166D2 mov edi, eax .text:000166D4 jmp short loc_166D8 .text:000166D6 ; --------------------------------------------------------------------------- .text:000166D6 .text:000166D6 loc_166D6: ; CODE XREF: sub_166B2+16j .text:000166D6 xor edi, edi .text:000166D8 .text:000166D8 loc_166D8: ; CODE XREF: sub_166B2+22j .text:000166D8 test edi, edi .text:000166DA jnz short loc_166E0 .text:000166DC xor al, al .text:000166DE jmp short loc_16712 .text:000166E0 ; --------------------------------------------------------------------------- .text:000166E0 .text:000166E0 loc_166E0: ; CODE XREF: sub_166B2+28j .text:000166E0 push ebx .text:000166E1 lea ebx, [esi+1Ch] .text:000166E4 mov ecx, ebx ; SpinLock .text:000166E6 call sub_1631C ; AcquireSpinLock .text:000166EB mov [ebp+NewIrql], al .text:000166EE mov eax, [esi+4] .text:000166F1 push dword ptr [ebp+NewIrql] ; NewIrql .text:000166F4 mov [edi], esi .text:000166F6 mov [edi+4], eax .text:000166F9 mov [eax], edi .text:000166FB mov ecx, ebx ; SpinLock .text:000166FD mov [esi+4], edi .text:00016700 call sub_1634C ; ReleaseSpinLock .text:00016705 push 0 ; int .text:00016707 lea ecx, [esi+8] ; Semaphore .text:0001670A call sub_16398 ; ReleaseSemaphore .text:0001670F mov al, 1 .text:00016711 pop ebx .text:00016712 .text:00016712 loc_16712: ; CODE XREF: sub_166B2+2Cj .text:00016712 pop edi .text:00016713 pop esi .text:00016714 pop ebp .text:00016715 retn 4 .text:00016715 sub_166B2 endp
Don’t get confused by the misleading name NewIrql, in fact it’s still our
PIRP pointer.
It also has 2 parameters, not one. First one is a pointer to the start
of a linked IRP-list, partly seems the be a structure like this:
typedef struct tag_PENDINGIRPS { LIST_ENTRY ListHead; KSEMAPHORE Semaphore; // +8 KSPIN_LOCK Lock; // +28 } PENDINGIRPS;
And the second parameter is our PIRP.
What this function basically does (here as C-Code for easier understanding):
typedef struct tag_IRPLIST { LIST_ENTRY ListHead; PIRP *Irp; // +8 } IRPLIST, *PIRPLIST; BOOL AddIRPToList(PENDINGIRPS *pIrps, PIRP **ppMyIrp) { PIRPLIST pList; if (pList = ExAllocatePoolWithTag(sizeof(IRPLIST), NonPagedPool)) { KIRQL irql; pList->Irp = *ppMyIrp; irql = AcquireSpinlock(&pIrps->Lock); InsertTailList(&pIrps->ListHead, pList); ReleaseSpinLock(&pIrps->Lock, irql); KeReleaseSemaphore(&pIrps->Semaphore, 1, 1, FALSE); return TRUE; } return FASE; }
So this function acquires a mutex and then adds our PIRP to some
linked list, presumably for processing by another worker.
As adding to the list is done secured by spinlocks, this tells us
that we are not the only one messing with the list at the same time.
Additionally there is a Semaphore that gets triggered after adding, which makes
us believe that this finally fires up the processing of the IRP.
As the bugcheck clearly shows that CurrentStackLocation member of our
IRP is NULL, we must assume that the worker is consuming and
completing our IRP from the list, before we can add 1 to the
CurrentStackLocation->Control member resulting in the given NULL pointer
dereference.
Upon further investigation, we can actually find the routine that is
processing the entries in the list ( I named some functions to
better specify what they are doing):
.text:00016A6A IRPProcessingLoop proc near ; DATA XREF: .rdata:00018980o .text:00016A6A ; .rdata:00018A20o .text:00016A6A mov edi, edi .text:00016A6C push ebx .text:00016A6D push esi .text:00016A6E mov esi, ecx .text:00016A70 lea ebx, [esi+0FCh] .text:00016A76 mov ecx, ebx .text:00016A78 call GetNextIRPFromList .text:00016A7D test eax, eax .text:00016A7F jz short loc_16AAC .text:00016A81 push edi .text:00016A82 lea edi, [esi+20h] .text:00016A85 .text:00016A85 loc_16A85: ; CODE XREF: IRPProcessingLoop+3Fj .text:00016A85 mov edx, [esi] .text:00016A87 push eax .text:00016A88 mov ecx, esi .text:00016A8A call dword ptr [edx+8Ch] .text:00016A90 cmp byte ptr [edi+18h], 0 .text:00016A94 jnz short loc_16AA0 .text:00016A96 push 18h ; RemlockSize .text:00016A98 push edi ; Tag .text:00016A99 push edi ; RemoveLock .text:00016A9A call ds:IoReleaseRemoveLockEx .text:00016AA0 .text:00016AA0 loc_16AA0: ; CODE XREF: IRPProcessingLoop+2Aj .text:00016AA0 mov ecx, ebx .text:00016AA2 call GetNextIRPFromList .text:00016AA7 test eax, eax .text:00016AA9 jnz short loc_16A85 .text:00016AAB pop edi .text:00016AAC .text:00016AAC loc_16AAC: ; CODE XREF: IRPProcessingLoop+15j .text:00016AAC pop esi .text:00016AAD xor eax, eax .text:00016AAF pop ebx .text:00016AB0 retn .text:00016AB0 IRPProcessingLoop endp .text:00016AB0 .text:00016AB0 ; --------------------------------------------------------------------------- .text:00016AB1 db 5 dup(0CCh)
If we transform the function to C-Code, this would read like:
int __fastcall IRPProcessingLoop(my_class *this) { PIRP irp; for (irp = GetNextIRPFromList(this->PendingIRPs); irp; irp = GetNextIRPFromList(this->PendingIRPs) ) { this->lpVtbl->processIRP(this, irp); if ( !this->b56) IoReleaseRemoveLock(&this->IORemoveLock, &this->IORemoveLock); } return 0; }
So now take a look at the function that I call GetNextIRPFromList:
.text:0001665E GetNextIRPFromList proc near ; CODE XREF: FreeIRPList+14p .text:0001665E ; IRPProcessingLoop+Ep ... .text:0001665E .text:0001665E var_8 = dword ptr -8 .text:0001665E NewIrql = byte ptr -4 .text:0001665E .text:0001665E mov edi, edi .text:00016660 push ebp .text:00016661 mov ebp, esp .text:00016663 push ecx .text:00016664 push ecx .text:00016665 push ebx .text:00016666 push esi .text:00016667 push edi .text:00016668 mov esi, ecx .text:0001666A lea edi, [esi+8] .text:0001666D push edi ; Semaphore .text:0001666E call ds:KeReadStateSemaphore .text:00016674 mov ecx, edi ; Object .text:00016676 call WaitForSingleObject .text:0001667B lea ebx, [esi+1Ch] .text:0001667E mov ecx, ebx ; SpinLock .text:00016680 call AcquireSpinlock .text:00016685 mov edi, [esi] .text:00016687 mov [ebp+NewIrql], al .text:0001668A mov eax, [edi+8] .text:0001668D push dword ptr [ebp+NewIrql] ; NewIrql .text:00016690 mov [ebp+var_8], eax .text:00016693 mov eax, [edi] .text:00016695 mov [esi], eax .text:00016697 mov ecx, ebx ; SpinLock .text:00016699 mov [eax+4], esi .text:0001669C call ReleaseSpinlock .text:000166A1 push edi ; NewIrql .text:000166A2 call FreePoolWithTag .text:000166A7 mov eax, [ebp+var_8] .text:000166AA pop ecx .text:000166AB pop edi .text:000166AC pop esi .text:000166AD pop ebx .text:000166AE leave .text:000166AF retn .text:000166AF GetNextIRPFromList endp
Translating it to more readable C-Code reveals how it works:
struct _IO_STACK_LOCATION *__fastcall GetNextIRPFromList(PENDINGIRPS *pIrps) { KIRQL irql; IRPLIST Entry; PIRP irp; KeReadStateSemaphore(&pIrps->Semaphore); WaitForSingleObject(&pIrps->Semaphore); irql = AcquireSpinlock(&pIrps->Lock); Entry = RemoveHeadList(&pIrps->ListHead); irp = Entry->IrpStackLocation; ReleaseSpinLock(&pIrps->Lock, irql); FreePoolWithTag(Entry); return irp; }
So it’s clearly waiting for the Semaphore that is set in AddIRPToList.
Now for the sake of completeness, let’s also translate the Z3TbGptLoader__irpReadWrite
function to some sort of readable C-code so tht we can finally see
what’s going on here and how to fix it. I didn’t do a full translation
of this, as we are only interested in the relevant parts:
int __thiscall Z3TbGptLoader__irpReadWrite(my_class *this, int a2, PIRP Irp)
{
if (this->dw226 && this->dw124 &&
/* Some unnown division, no time to find out what it does, doesn't matter
for us */
Irp->CurrentStackLocation->Parameters.Write.ByteOffset / this->dw124
! (Irp->CurrentStackLocation->Parameters.Write.ByteOffset.LowPart == FILE_WRITE_TO_END_OF_FILE &&
Irp->CurrentStackLocation->Parameters.Write.ByteOffset.HighPart == -1)
{
if (AddIRPToList(this->PendingIRPs, &Irp))
{
AcquireRemoveLock(&this->IORemoveLock);
IoMarkIrpPending(Irp);
EndThis(arg2, this->lpVtbl->Func284);
}
else
{
AddErrorToSyslog("Z3TbGptLoader::irpReadWrite", ...);
Irp->IoStatus.Status = STATUS_NO_MEMORY;
EndThis(arg2, this->lpVtbl->IRPProcessingLoop);
}
}
else
{
EndThis(arg2, this->lpVtbl->PassOn);
}
}
So putting one and one together, we see what’s going wrong here:
AddIRPToList triggers the semaphore to process the IRP, after
it has added it to the list, but in irpReadWrite, it is attempted to
modify the IRP afterwards (mark IRP pending by setting control-member)
and acquiring a remove lock.
Fixing it
Now how to fix the bug?
First we can see from WinDbg output, that ESI still holds a pointer to
the CurrentStackLocation, so wo could just change
.text:000169E2 or byte ptr [eax+3], 1
to
.text:000169E2 or byte ptr [esi+3], 1
But that wouldn’t be a proper fix, because maybe the pointer may not
be valid anymore at that time and additionally, the intention of the
code is to set that control flag before the IRP gets processed by the worker
thread.
So we must think about a different solution.
First guess would be that we could just set it in AddIRPToList.
But this doesn’t work out well, because the function is also called
at in another funtion, so no luck with that. Additionally, we learned
from above that there is also a RemoveLock in place which also should
be set before the IRP is being processed.
Let’s have a look at this other function that is calling AddIRPToList
which I called close_driver:
.text:00016B82 close_driver proc near ; CODE XREF: sub_15DC2+Ep .text:00016B82 ; sub_16C60+8p .text:00016B82 .text:00016B82 NewIrql = byte ptr -4 .text:00016B82 .text:00016B82 mov edi, edi .text:00016B84 push ebp .text:00016B85 mov ebp, esp .text:00016B87 push ecx .text:00016B88 and dword ptr [ebp+NewIrql], 0 .text:00016B8C push esi .text:00016B8D mov esi, ecx .text:00016B8F push edi .text:00016B90 lea eax, [ebp+NewIrql] .text:00016B93 lea edi, [esi+0FCh] .text:00016B99 push eax ; NewIrql .text:00016B9A mov ecx, edi .text:00016B9C mov dword ptr [esi], offset off_18998 .text:00016BA2 call AddIRPToList .text:00016BA7 test al, al .text:00016BA9 jz short loc_16BB6 .text:00016BAB lea ecx, [esi+128h] .text:00016BB1 call DoWaitForSingleObject .text:00016BB6 .text:00016BB6 loc_16BB6: ; CODE XREF: close_driver+27j .text:00016BB6 lea ecx, [esi+148h] .text:00016BBC call sub_16722 .text:00016BC1 lea ecx, [esi+128h] .text:00016BC7 call DoCloseHandle .text:00016BCC lea ecx, [esi+11Ch] .text:00016BD2 call sub_16722 .text:00016BD7 mov ecx, edi .text:00016BD9 call FreeIRPList .text:00016BDE mov ecx, esi .text:00016BE0 call sub_12BB2 .text:00016BE5 pop edi .text:00016BE6 pop esi .text:00016BE7 leave .text:00016BE8 retn .text:00016BE8 close_driver endp .text:00016BE8 ; --------------------------------------------------------------------------- .text:00016BE9 db 5 dup(0CCh)
Now the following idea arises:
1) Remove the KeReleaseSemaphore from AddIRPToList.
2) Release the Semaphore in irpReadWrite AFTER PIRP was
setup correctly.
3) Modify close_driver to also do a manual KeReleaseSemaphore
after AddIRPToList so that the call to it really can be removed
from that function.
Let’s see if there is sufficient space for this. Have a look at AddIRPToList
and check what’s done there:
.text:00016705 push 0 ; int .text:00016707 lea ecx, [esi+8] ; Semaphore .text:0001670A call sub_16398 ; ReleaseSemaphore
So semaphore is loaded into ECX, The PUSH 0 takes 2 bytes,
the CALL to ReleaseSemaphore takes 5 bytes, in total 7 bytes provided
that we can reuse ecx after function call, which fortuntely is the case
as ECX doesn’t get restored at function end of AddIRPToList.
First let’s check the possibility for close_driver:
We have 5 bytes in alignment space AFTER the function end.
Too bad, that’s not enough.. Now what to do.. Fortunately
the MS C ompiler always generates a pointless 2 Byte mov edi, edi
instruction at the start of each function whic actually is meant
to be used for a detour hook in case someone wants to patch the
function during update -> Success, we have 7 bytes!
Now we can move up the function by 2 bytes, then insert our
call to ReleseSemaphore and we’re set.
Next, let’s chec irpReadWrite:
We can save 2 bytes by the fact that
.text:000169DC mov eax, [ebx+60h]
is unnecessary, as we already have the pointer to it in esi.
Not enough, though. But let’s have a look above that function for a moment:
.text:000169AF mov ecx, edi .text:000169B1 jz short loc_169BA .text:000169B3 call sub_12B96 .text:000169B8 jmp short loc_16A37 .text:000169BA ; --------------------------------------------------------------------------- .text:000169BA .text:000169BA loc_169BA: ; CODE XREF: sub_16942+6Fj .text:000169BA call sub_12B96 .text:000169BF jmp short loc_16A37
Now we can agree that this is really useless code! So the idea is to move
up the following code, relocate the calls, and insert our ReleaseSemaphore
function code. Plenty of space for us, hoorray! So with adjustments
to acommodate the length of near jumps to the function exit point, our
code will finally look like this:
.text:00016942 ; int __stdcall Z3TbGptLoader__irpReadWrite(int, KIRQL NewIrql) .text:00016942 Z3TbGptLoader__irpReadWrite proc near ; DATA XREF: .rdata:0001897Co .text:00016942 ; .rdata:00018A1Co .text:00016942 .text:00016942 var_C = dword ptr -0Ch .text:00016942 var_8 = dword ptr -8 .text:00016942 var_1 = byte ptr -1 .text:00016942 arg_0 = dword ptr 8 .text:00016942 NewIrql = byte ptr 0Ch .text:00016942 .text:00016942 mov edi, edi .text:00016944 push ebp .text:00016945 mov ebp, esp .text:00016947 sub esp, 0Ch .text:0001694A push ebx .text:0001694B mov ebx, dword ptr [ebp+NewIrql] .text:0001694E push esi .text:0001694F mov esi, [ebx+60h] .text:00016952 cmp byte ptr [esi], 3 .text:00016955 push edi .text:00016956 setz [ebp+var_1] .text:0001695A mov edi, ecx .text:0001695C xor ecx, ecx .text:0001695E xor eax, eax .text:00016960 mov [ebp+var_C], ecx .text:00016963 cmp [edi+7Ch], ecx .text:00016966 jz short loc_1698B .text:00016968 mov ebx, [edi+7Ch] .text:0001696B push ecx .text:0001696C push ebx .text:0001696D push dword ptr [esi+10h] .text:00016970 push dword ptr [esi+0Ch] .text:00016973 call _alldiv .text:00016978 mov ecx, edx ; Semaphore .text:0001697A mov [ebp+var_C], eax .text:0001697D mov eax, [esi+4] .text:00016980 xor edx, edx .text:00016982 div ebx .text:00016984 mov ebx, dword ptr [ebp+NewIrql] .text:00016987 test ecx, ecx .text:00016989 jnz short loc_1699E .text:0001698B .text:0001698B loc_1698B: ; CODE XREF: Z3TbGptLoader__irpReadWrite+24j .text:0001698B xor edx, edx .text:0001698D add eax, [ebp+var_C] .text:00016990 adc edx, ecx .text:00016992 mov [ebp+var_8], edx .text:00016995 js short loc_169A7 .text:00016997 jg short loc_1699E .text:00016999 cmp eax, 0FFFFFFFFh .text:0001699C jbe short loc_169A7 .text:0001699E .text:0001699E loc_1699E: ; CODE XREF: Z3TbGptLoader__irpReadWrite+47j .text:0001699E ; Z3TbGptLoader__irpReadWrite+55j .text:0001699E cmp byte ptr [edi+0E2h], 0 .text:000169A5 jnz short loc_169B8 .text:000169A7 .text:000169A7 loc_169A7: ; CODE XREF: Z3TbGptLoader__irpReadWrite+53j .text:000169A7 ; Z3TbGptLoader__irpReadWrite+5Aj .text:000169A7 push ebx .text:000169A8 push [ebp+arg_0] .text:000169AB mov ecx, edi .text:000169AD call sub_12B96 .text:000169B2 nop .text:000169B3 nop .text:000169B4 nop .text:000169B5 nop .text:000169B6 jmp short loc_16A37 ; JMP here so that short JMP is enough .text:000169B8 ; --------------------------------------------------------------------------- .text:000169B8 .text:000169B8 loc_169B8: ; CODE XREF: Z3TbGptLoader__irpReadWrite+63j .text:000169B8 lea eax, [ebp+NewIrql] .text:000169BB push eax ; NewIrql .text:000169BC lea ecx, [edi+0FCh] .text:000169C2 call AddIRPToList .text:000169C7 test al, al .text:000169C9 jz short loc_169F4 .text:000169CB push ecx .text:000169CC lea ecx, [edi+20h] ; Tag .text:000169CF call AcquireRemoveLock .text:000169D4 or byte ptr [esi+3], 1 .text:000169D8 pop ecx .text:000169D9 push 0 ; int .text:000169DB call ReleaseSemaphore .text:000169E0 mov ecx, [ebp+arg_0] .text:000169E3 nop .text:000169E4 nop .text:000169E5 nop .text:000169E6 add edi, 11Ch .text:000169EC push edi .text:000169ED call sub_11B14 .text:000169F2 jmp short loc_16A37 .text:000169F4 ; --------------------------------------------------------------------------- .text:000169F4 .text:000169F4 loc_169F4: ; CODE XREF: Z3TbGptLoader__irpReadWrite+87j .text:000169F4 mov eax, dword_19024 .text:000169F9 mov esi, [eax] .text:000169FB mov [ebp+var_8], eax .text:000169FE cmp esi, eax .text:00016A00 jz short loc_16A21 .text:00016A02 .text:00016A02 loc_16A02: ; CODE XREF: Z3TbGptLoader__irpReadWrite+DDj .text:00016A02 mov ecx, [esi+0Ch] .text:00016A05 mov eax, [ecx] .text:00016A07 push 0 .text:00016A09 push 0 .text:00016A0B push offset aZ3tbgptloaderI ; "Z3TbGptLoader::irpReadWrite" .text:00016A10 push 1 .text:00016A12 push 0C0050007h .text:00016A17 call dword ptr [eax+8] .text:00016A1A mov esi, [esi] .text:00016A1C cmp esi, [ebp+var_8] .text:00016A1F jnz short loc_16A02 .text:00016A21 .text:00016A21 loc_16A21: ; CODE XREF: Z3TbGptLoader__irpReadWrite+BEj .text:00016A21 mov ecx, [ebp+arg_0] .text:00016A24 add edi, 8Ch .text:00016A2A push edi .text:00016A2B mov dword ptr [ebx+18h], 0C0000017h .text:00016A32 .text:00016A32 loc_16A32: .text:00016A32 call sub_11B14 .text:00016A37 .text:00016A37 loc_16A37: ; CODE XREF: Z3TbGptLoader__irpReadWrite+74j .text:00016A37 ; Z3TbGptLoader__irpReadWrite+B0j .text:00016A37 mov eax, [ebp+arg_0] .text:00016A3A pop edi .text:00016A3B pop esi .text:00016A3C pop ebx .text:00016A3D leave .text:00016A3E retn 8 .text:00016A3E Z3TbGptLoader__irpReadWrite endp
Finally, NOP out the calls to ReleaseSemaphore in AddIRPToList
(Remove the PUSH 0 and the CALL) and we’re finished patching it.
I made this modification and until now the driver seems to remain stable
(crossing fingers that it remains stable and I fixed the bug 😉 )
Patch
As manually patching this is a real pain and I’m also not allowed to
redistribute a patched copy of the file. So I wrote a little patcher that
patches the driver accordingly. Just run it and
if it patched successfully, reboot the system to load the fixed version of the
driver.
Feel free to try it and if you are also suffering from this problem, you can
leave a comment if this actually fixes it for you too.
For those who use crappy Antivirus programs like Antivir, don’t get fooled by the generic Antivirus signature-match for compressed Executables XPACK.GEN, you can check with Virustotal.
If you have such an Antivirus program, use this build instead which is a larger executable but isn’t subject to false positives.
“Server ist Ausgelastet” bei Systemstart mit Avira Antivirus
Heute hatte ich ein Notebook in Behandlung, das zeigte beim Systemstart immer “Server is ausgelastet” an und dann “Wechseln zu, Warten, …”. Dabei handelt es sich eindeutig um eine COM-Fehlermeldung, wo vergeblich auf die Antwort eines Servers gewartet wird.
Nun ist mir aufgefallen, dass der Spuk immer genau dann zu Ende ist, wenn der Echtzeitschutz von Avira Antivirus fertig gestartet ist. Also lag die Vermutung nahem, dass es am Antivirus liegt, zumal es hier auch bereits einen Thread im Avira-Supportforum dazu gibt. Nun ist mir weiters aufgefallen, dass bis zu diesem Zeitpunkt keine weiteren Programme geladen werden, die beim Systemstart mitstarten sollten.
Als Erstes hatte ich einmal den Avira Antivirus in Verdacht und habe ihn deinstalliert, besagte Fehlermeldung ist damit auch wirklich verschwunden. Die Startverzögerung beeinflusste die Deinstallation des Programms allerdings nicht, es war nach wie vor ca. eine zweiminütige Pause beim Systemstart beim Laden der Anwendungen zu beobachten, die sich insofern unangenehm bemerkbar machte, als dass der Explorer in dieser Zeit unzureichend bzw. garnicht reagierte und man damit auch nicht wirklich arbeiten konnte.
Nach näherer Analyse des Umstands habe ich festgestellt, dass der Arbeitsstationsdienst scheinbar genau diese 2 Minuten lang, in denen sich nichts tut, beim Start hängen bleibt. Deaktiviert man den Arbeitsstationsdienst, so ist die Startverzögerung weg. Das ist natürlich keine Lösung für das Problem, denn der Dienst wird ja benötigt. Startet man den deaktivierten Dienst nach dem Startcorgang manuell, so startet dieser hingegen in akzeptabler Startzeit. Hänger beim Start des Arbeitsstationsdienstes haben meistens etwas mit den Netzwerkverbindungen zu tun, also habe ich durch Schrittweisene deaktivieren und aktivieren der verfügbaren Netzwerkverbindungen herausgefunden, dass es scheinbar mit der WLAN-Verbindung zusammenhängt. Dabei ist unerheblich, ob eine WLAN-VErbindung besteht oder das WLAN-Modu physisch am Rechner deaktiviert ist. Sobald man den WLAN-Adapter deaktiviert, startete der Arbeitsstationsdienst wieder normal.
Nun war im Notebook eine Intel Pro Wireless Karet verbaut mit entsprechender Verbindungsverwaltungssoftware von Intel. Diese Software ersetzt die Windowseigene WLAN-Konfiguration, was mich Schließlich auf den Schuldigen Dienst bracht, der da alles aufhielt: Der Dienst “Konfigurationsfreie drahtlose Verbindung“, welcher eigentlich von Windows benutzt wird, blockiert scheinbar den Intel-Dienst für die Konfiguration für 2 Minuten beim Start, was wiederum den Arbeitsstationsdienst beim Start so lange aufhält und die 2 Minuten “Hänger” beim Starten verursacht. Ich habe daher den Dienst deaktiviert, und siehe da, der Rechner startete wieder normal, die WLAN-Verbindung wurde schneller beim Start aufgebaut und Avira Antivirus war nicht mehr ausgelastet, somit war die störende Fehlermeldung damit auch beseitigt.