Some time ago while working on Windows 8, we came across a rather unusual piece of disassembly in some Microsoft binary files. This post describes some of our findings and how they are related to a Windows internal project called Warbird.
Warbird is an enhancement of the license verification of Windows that is introduced in Windows 8/2012. The former system was too easy to intercept and to fake, so Microsoft decided to provide something that is harder to reverse engineer and to fake.
API Lookup
Our investigation begins in the “Windows Calculator” binary file (32-bit version of calc.exe). We found the following piece of disassembly in the WinMain function, which basically contains the code of the program when it is executed:
002a95e0 64a130000000 mov eax,dword ptr fs:[00000030h]
...
002a95ea 8b400c mov eax,dword ptr [eax+0Ch]
002a95ed 83c00c add eax,0Ch
These instructions allow accessing the Ldr (which stands for Loader) field of the current process PEB (Process Environment Block). This field gives access to the list of loaded modules of the current running process.
In a legitimate process, the list of loaded modules shouldn’t be accessed directly. It is either internally used by the Windows loader when it needs to load a binary file in memory and resolve its external dependencies, or used by the LoadLibrary function that can be called by any program.
In a malicious code that is executed when exploiting software vulnerability, the attacker needs to access the list of modules in order to retrieve operating system functions’ address. This listing enables the attacker to perform malicious actions (such as writing a malicious binary file on the file system). To do so, malicious code uses undocumented features in order to access directly the list of loaded modules thanks to the instructions previously noted.
This technique is also used by some packers. Initially packers were used to shrink executable file sizes. Nowadays, they are also used by malware to escape antivirus’ technologies based on signatures.
In this particular context, this technique seems to be used to retrieve required function addresses in a stealthy way.
Back to the Windows Calculator, the list of functions to resolve is contained in a buffer. The buffer will be dynamically decoded at process runtime and thus cannot be extracted from the raw binary file. When the function resolution process begins, the decoded buffer is:
00c48ed8 67 00 64 00 69 00 33 00-32 00 2e 00 64 00 6c 00 g.d.i.3.2...d.l.
00c48ee8 6c 00 00 00 12 00 00 00-42 69 74 42 6c 74 00 43 l.......BitBlt.C
00c48ef8 72 65 61 74 65 43 6f 6d-70 61 74 69 62 6c 65 42 reateCompatibleB
00c48f08 69 74 6d 61 70 00 43 72-65 61 74 65 43 6f 6d 70 itmap.CreateComp
00c48f18 61 74 69 62 6c 65 44 43-00 43 72 65 61 74 65 44 atibleDC.CreateD
00c48f28 49 42 53 65 63 74 69 6f-6e 00 43 72 65 61 74 65 IBSection.Create
00c48f38 46 6f 6e 74 49 6e 64 69-72 65 63 74 57 00 43 72 FontIndirectW.Cr
00c48f48 65 61 74 65 53 6f 6c 69-64 42 72 75 73 68 00 44 eateSolidBrush.D
00c48f58 65 6c 65 74 65 44 43 00-44 65 6c 65 74 65 4f 62 eleteDC.DeleteOb
00c48f68 6a 65 63 74 00 47 64 69-41 6c 70 68 61 42 6c 65 ject.GdiAlphaBle
00c48f78 6e 64 00 47 64 69 47 72-61 64 69 65 6e 74 46 69 nd.GdiGradientFi
00c48f88 6c 6c 00 47 65 74 43 75-72 72 65 6e 74 4f 62 6a ll.GetCurrentObj
00c48f98 65 63 74 00 47 65 74 44-49 42 69 74 73 00 47 65 ect.GetDIBits.Ge
00c48fa8 74 44 65 76 69 63 65 43-61 70 73 00 47 65 74 4f tDeviceCaps.GetO
00c48fb8 62 6a 65 63 74 57 00 47-65 74 53 74 6f 63 6b 4f bjectW.GetStockO
00c48fc8 62 6a 65 63 74 00 53 65-6c 65 63 74 4f 62 6a 65 bject.SelectObje
00c48fd8 63 74 00 53 65 74 42 6b-4d 6f 64 65 00 53 65 74 ct.SetBkMode.Set
00c48fe8 54 65 78 74 43 6f 6c 6f-72 00 6b 00 65 00 72 00 TextColor.k.e.r.
00c48ff8 6e 00 65 00 6c 00 33 00-32 00 2e 00 64 00 6c 00 n.e.l.3.2...d.l.
00c49008 6c 00 00 00 0a 00 00 00-47 65 74 4c 6f 63 61 6c l.......GetLocal
00c49018 65 49 6e 66 6f 45 78 00-47 65 74 55 73 65 72 50 eInfoEx.GetUserP
00c49028 72 65 66 65 72 72 65 64-55 49 4c 61 6e 67 75 61 referredUILangua
00c49038 67 65 73 00 4c 43 49 44-54 6f 4c 6f 63 61 6c 65 ges.LCIDToLocale
00c49048 4e 61 6d 65 00 4c 6f 63-61 6c 65 4e 61 6d 65 54 Name.LocaleNameT
00c49058 6f 4c 43 49 44 00 4d 75-6c 44 69 76 00 4d 75 6c oLCID.MulDiv.Mul
00c49068 74 69 42 79 74 65 54 6f-57 69 64 65 43 68 61 72 tiByteToWideChar
00c49078 00 50 6f 77 65 72 43 6c-65 61 72 52 65 71 75 65 .PowerClearReque
00c49088 73 74 00 50 6f 77 65 72-43 72 65 61 74 65 52 65 st.PowerCreateRe
00c49098 71 75 65 73 74 00 50 6f-77 65 72 53 65 74 52 65 quest.PowerSetRe
00c490a8 71 75 65 73 74 00 53 6c-65 65 70 45 78 00 6e 00 quest.SleepEx.n.
00c490b8 74 00 64 00 6c 00 6c 00-2e 00 64 00 6c 00 6c 00 t.d.l.l...d.l.l.
00c490c8 00 00 01 00 00 00 57 69-6e 53 71 6d 41 64 64 54 ......WinSqmAddT
00c490d8 6f 53 74 72 65 61 6d 00-75 00 73 00 65 00 72 00 oStream.u.s.e.r.
00c490e8 33 00 32 00 2e 00 64 00-6c 00 6c 00 00 00 13 00 3.2...d.l.l.....
00c490f8 00 00 44 72 61 77 54 65-78 74 45 78 57 00 45 6e ..DrawTextExW.En
00c49108 75 6d 44 69 73 70 6c 61-79 53 65 74 74 69 6e 67 umDisplaySetting
00c49118 73 57 00 46 69 6c 6c 52-65 63 74 00 47 65 74 44 sW.FillRect.GetD
00c49128 43 00 47 65 74 44 43 45-78 00 47 65 74 44 65 73 C.GetDCEx.GetDes
00c49138 6b 74 6f 70 57 69 6e 64-6f 77 00 47 65 74 4d 6f ktopWindow.GetMo
00c49148 6e 69 74 6f 72 49 6e 66-6f 57 00 47 65 74 50 72 nitorInfoW.GetPr
00c49158 6f 63 65 73 73 57 69 6e-64 6f 77 53 74 61 74 69 ocessWindowStati
00c49168 6f 6e 00 47 65 74 53 79-73 43 6f 6c 6f 72 00 47 on.GetSysColor.G
00c49178 65 74 53 79 73 74 65 6d-4d 65 74 72 69 63 73 00 etSystemMetrics.
00c49188 47 65 74 54 68 72 65 61-64 44 65 73 6b 74 6f 70 GetThreadDesktop
00c49198 00 47 65 74 55 73 65 72-4f 62 6a 65 63 74 49 6e .GetUserObjectIn
00c491a8 66 6f 72 6d 61 74 69 6f-6e 57 00 49 6e 76 61 6c formationW.Inval
00c491b8 69 64 61 74 65 52 65 63-74 00 49 73 50 72 6f 63 idateRect.IsProc
00c491c8 65 73 73 44 50 49 41 77-61 72 65 00 4d 6f 6e 69 essDPIAware.Moni
00c491d8 74 6f 72 46 72 6f 6d 57-69 6e 64 6f 77 00 4f 66 torFromWindow.Of
00c491e8 66 73 65 74 52 65 63 74-00 52 65 64 72 61 77 57 fsetRect.RedrawW
00c491f8 69 6e 64 6f 77 00 52 65-6c 65 61 73 65 44 43 00 indow.ReleaseDC.
00c49208 53 79 73 74 65 6d 50 61-72 61 6d 65 74 65 72 73 SystemParameters
00c49218 49 6e 66 6f 57 00 00 ab-ab ab ab ab ab ab ab fe InfoW...........
00c49228 00 00 00 00 00 00 00 00-79 43 de af 6c 4a 00 00 ........yC..lJ..
The contents of the buffer are quite simple to understand. This is a list of structures containing:
- The name of the DLL, in Unicode characters (in green);
- The number of functions to resolve (in yellow) ;
- The name of functions to resolve.
This last structure is indicated by a name containing 0.
Initially, unresolved functions point to stubs returning an error code and setting the last error to
ERROR_PROC_NOT_FOUND
:
.text:0045D03B ; void * __stdcall WARBIRD_DELAY_LOAD::PowerCreateRequest(struct _REASON_CONTEXT *)
.text:0045D03B ?PowerCreateRequest@WARBIRD_DELAY_LOAD@@YGPAXPAU_REASON_CONTEXT@@@Z proc near
.text:0045D03B push ERROR_PROC_NOT_FOUND ; dwErrCode
.text:0045D03D call ds:__imp__SetLastError@4 ; SetLastError(x)
.text:0045D043 or eax, 0FFFFFFFFh
.text:0045D046 retn 4
.text:0045D046 ?PowerCreateRequest@WARBIRD_DELAY_LOAD@@YGPAXPAU_REASON_CONTEXT@@@Z endp
The available debugging symbols for Microsoft Calculator point to a rather unusual name: Warbird. We can infer this is the internal name of a project at Microsoft. We can dump the list of available symbols containing this name:
0:000> x calc!*warbird*
0100d021 calc!WARBIRD_DELAY_LOAD::GetDesktopWindow ()
0100d0a3 calc!WARBIRD_DELAY_LOAD::DeleteDC ()
0100d0a3 calc!WARBIRD_DELAY_LOAD::GetSystemMetrics ()
0102e920 calc!WARBIRD::g_FuncAddress =
0100d04e calc!WARBIRD_DELAY_LOAD::LocaleNameToLCID ()
0100d04e calc!WARBIRD_DELAY_LOAD::SelectObject ()
0100d0a3 calc!WARBIRD_DELAY_LOAD::CreateSolidBrush ()
0100d00f calc!WARBIRD_DELAY_LOAD::GetUserObjectInformationW ()
0100d0c7 calc!WARBIRD_DELAY_LOAD::BitBlt ()
0102e2e0 calc!`WarbirdGetDecryptionCipher'::`2'::DecryptionCipher =
0100cffd calc!WARBIRD_DELAY_LOAD::CreateCompatibleBitmap ()
0100d031 calc!WARBIRD_DELAY_LOAD::GdiGradientFill ()
0100cfe5 calc!WARBIRD_DELAY_LOAD::RedrawWindow ()
0100d060 calc!WARBIRD_DELAY_LOAD::MulDiv ()
0100d04e calc!WARBIRD_DELAY_LOAD::SetTextColor ()
0100d0a3 calc!WARBIRD_DELAY_LOAD::GetThreadDesktop ()
0100d0a3 calc!WARBIRD_DELAY_LOAD::CreateCompatibleDC ()
0100d06b calc!WARBIRD_DELAY_LOAD::SystemParametersInfoW ()
0100d04e calc!WARBIRD_DELAY_LOAD::SleepEx ()
0100579e calc!WarbirdThreadCallback ()
0100cffd calc!WARBIRD_DELAY_LOAD::InvalidateRect ()
0100d0a3 calc!WARBIRD_DELAY_LOAD::DeleteObject ()
0100d0a3 calc!WARBIRD_DELAY_LOAD::GetStockObject ()
0100cffd calc!WARBIRD_DELAY_LOAD::GetDCEx ()
0100d021 calc!WARBIRD_DELAY_LOAD::IsProcessDPIAware ()
0100d07d calc!WARBIRD_DELAY_LOAD::MonitorFromWindow ()
0100d0b5 calc!WARBIRD_DELAY_LOAD::CreateDIBSection ()
0100d03b calc!WARBIRD_DELAY_LOAD::PowerCreateRequest ()
0100d087 calc!WARBIRD_DELAY_LOAD::GetDIBits ()
0100cffd calc!WARBIRD_DELAY_LOAD::FillRect ()
0100d099 calc!WARBIRD_DELAY_LOAD::GdiAlphaBlend ()
0100d06b calc!WARBIRD_DELAY_LOAD::GetLocaleInfoEx ()
010312f4 calc!g_WarbirdNotificationInformation =
0102ede0 calc!`WarbirdGetDecryptionKey'::`2'::nDecryptionKey =
0102edd8 calc!`WarbirdGetEncryptionKey'::`2'::nEncryptionKey =
0100d07d calc!WARBIRD_DELAY_LOAD::SetBkMode ()
0100d06b calc!WARBIRD_DELAY_LOAD::GetUserPreferredUILanguages ()
0100d0a3 calc!WARBIRD_DELAY_LOAD::GetDC ()
0100d04e calc!WARBIRD_DELAY_LOAD::PowerClearRequest ()
0100cfef calc!WARBIRD_DELAY_LOAD::OffsetRect ()
0100d04e calc!WARBIRD_DELAY_LOAD::PowerSetRequest ()
0100d021 calc!WARBIRD_DELAY_LOAD::GetProcessWindowStation ()
0100cffd calc!WARBIRD_DELAY_LOAD::EnumDisplaySettingsW ()
0100d0a3 calc!WARBIRD_DELAY_LOAD::CreateFontIndirectW ()
0100d04e calc!WARBIRD_DELAY_LOAD::ReleaseDC ()
0100d031 calc!WARBIRD_DELAY_LOAD::DrawTextExW ()
0100d04e calc!WARBIRD_DELAY_LOAD::GetDeviceCaps ()
0100d07d calc!WARBIRD_DELAY_LOAD::GetMonitorInfoW ()
0100cffd calc!WARBIRD_DELAY_LOAD::GetObjectW ()
0102e240 calc!`WarbirdGetEncryptionCipher'::`2'::EncryptionCipher =
0100d04e calc!WARBIRD_DELAY_LOAD::GetCurrentObject ()
0100d0a3 calc!WARBIRD_DELAY_LOAD::GetSysColor ()
0102dbe8 calc!`WarbirdSecureFunctionsInitialize'::`2'::g_InitFunctions =
0100d06b calc!WARBIRD_DELAY_LOAD::LCIDToLocaleName ()
00fe95d2 calc!WARBIRD::GetFunctionAddress ()
0100d0b5 calc!WARBIRD_DELAY_LOAD::MultiByteToWideChar ()
010312f8 calc!g_WarbirdPaintInitTime =
Once resolved, these functions point to the actual implementation in the appropriate dynamically loaded modules. Warbird code doesn’t try to load the referenced modules (gdi32.dll, kernel32.dll, ntdll.dll and user32.dll); they must be loaded by the hosting process before Warbird code resolves the functions’ addresses
We will not dive into the details of function address resolution. A good write-up can be found at the address rohitab.com/discuss/topic/40877-shellcoding-get-exported-function-pointer-from-name/ for people interested in understanding the techniques used to perform this action.
As part of this process, Microsoft also checks that the found base address looks like a valid PE file by checking some magic values in the header of the mapped file; malware authors are not so paranoiac and usually blindly trust the found base address.
Execution context
Once necessary functions are resolved, Warbird tries to determine if it has to be run on the machine. To do so, it checks the following conditions:
- The program is not running in session 0 (i.e. the program is not a service);
- The current window station name is ‘WinSta0’ (using the newly resolved functions
GetProcessWindowStation
andGetUserObjectInformationW
) ; - The current desktop is ‘Default’ (using the newly resolved functions
GetThreadDesktop
andGetUserObjectInformationW
).
Execution
After checking the execution context, the next step in the execution of Warbird code is related to a group of 3 associated functions: PowerCreateRequest
, PowerSetRequest
and PowerClearRequest
.
These functions were introduced in Windows 7 and allow a program to be involved in the power management of the workstation. For example, the program can force the display to be always on even if the program is performing a lengthy operation.
PowerCreateRequest
creates a request specifying the reason for the request. This function uses a parameter of type _REASON_CONTEXT (msdn.microsoft.com/en-us/library/windows/desktop/dd405536%28v=vs.85%29.aspx) that specifies the reason of the request:
typedef struct _REASON_CONTEXT {
ULONG Version;
DWORD Flags;
union {
struct {
HMODULE LocalizedReasonModule;
ULONG LocalizedReasonId;
ULONG ReasonStringCount;
LPWSTR *ReasonStrings;
} Detailed;
LPWSTR SimpleReasonString;
} Reason;
} REASON_CONTEXT, *PREASON_CONTEXT;
The code which creates the power request and calls PowerCreateRequest
is:
0:000> u calc+20e8
calc!WinMain+0x10bc:
002320e8 8d8424c0020000 lea eax,[esp+2C0h]
002320ef 50 push eax
002320f0 c78424c402000000000000 mov dword ptr [esp+2C4h],0
002320fb c78424c802000000000080 mov dword ptr [esp+2C8h],80000000h
00232106 ff1584e92a00 call dword ptr [calc!WARBIRD::g_FuncAddress+0x64 (002ae984)]
0023210c 8bf0 mov esi,eax
0:000> dps 002ae984 L1
002ae984 75d9dda5 KERNEL32!PowerCreateRequest
At address 002320f0
, the Version
field is initialized to 0 (POWER_REQUEST_CONTEXT_VERSION
). The next instruction initializes the Flags
field with the value 0x80000000, which is an undocumented field (only 0x1 and 0x2 values are documented on MSDN). The remaining of the structure is left uninitialized.
The use of this undocumented flag is not clear; however the maintainers of the drmemory
open source project have already noted that not all the fields were correctly initialized (code.google.com/p/drmemory/issues/detail?id=1247).
When the power request is created, it is activated with a call to PowerSetRequest
with the PowerRequestExecutionRequired
request type. This request type allows the program to run instead of being suspended or terminated by process lifetime management mechanisms.
After this simple step, the remaining code of Warbird is quite difficult to reverse engineer. It seems that Microsoft used techniques such as function inlining in order to hide the sequence of operations.
After a long sequence of cryptographic-related operations, the program calls the versatile NtSetSystemInformation
API with an information type of value 0x86. Microsoft only documents a small subset of the structures returned by this function (msdn.microsoft.com/en-us/library/windows/desktop/ms724509%28v=vs.85%29.aspx). A rather up-to-date definition of the type of information that can be queried can be found in the sources of the Process Hacker open source project (processhacker.sourceforge.net/doc/ntexapi_8h_source.html). In this enumeration, 0x86 corresponds to SystemThrottleNotificationInformation
.
Even if we did not dig into this system call, some people have done it and concluded this is a way to obfuscate calls to retrieve licensing information. In previous versions of the operating system, Microsoft used the NtQueryLicenseValue
and SLGetWindowsInformation
to retrieve licensing information. These calls were quite easy to intercept and fake. Starting from Windows 8, it seems Microsoft has chosen to change its implementation to make the licensing system harder to fake.
Having a look at the other dynamically resolved functions which are related to graphic display (mainly in gdi32.dll and user32.dll), we can assume that the whole process displays a watermark message on the screen if running a non-genuine version of Windows.
Extent of Warbird
So far, we highlighted some findings in the Windows Calculator provided with the 32-bit version of Windows 8.
But Warbird usage is not restricted to this simple program. This technique is embedded in a bunch of other Microsoft binary files, both in 32 bits and 64 bits. This technique is also present in the latest version of the operating system, Windows 8.1 Update 1 at the time of writing.
Microsoft tries to hide the details of this technique to the reversing community. For example, the Windows 8.1 Update 1 version of the Windows Calculator lacks any debug information related to Warbird.
However, some tracks are still present if you are interested in digging into this area.
For example, you can search for other affected binary files using a YARA (plusvic.github.io/yara/) rule matching the unusual pattern highlighted at the beginning of this article (32-bit version only):
/* Match the PEB.Ldr assembly for warbird function resolution */
rule WarBird
{
strings:
$a = {64 A1 30 00 00 00 2B CA D1 F9 8B 40 0C 83 C0 0C}
condition:
$a
}
This pattern will match some binary files, both in the ‘system32’ and the ‘Program Files folder’. You will eventually come across some binary files with debug information containing the private symbols of the Warbird implementation (even for Windows 8.1 Update 1 binary files).
Conclusion
The purpose of this blog post was to unveil the mechanisms used in some Windows binary files to obfuscate licensing related queries, and not licensing itself.
It is clear that Microsoft did some efforts to hide operations related to their licensing starting from Windows 8 version.
Even if some valuable information can still be retrieved from the debug symbols associated with the Windows binary files, Microsoft is about to remove the relevant information. We do not know the whole process of debug symbols publishing at Microsoft, but it seems private symbols are regularly present on their public symbol store. This source of information is quite valuable to reversers to understand new piece of technology or to access internal functionalities of the operating system.