Geoff Chappell - Software Analyst
Unless you’re familiar with the technology of shell extensions, you might read Microsoft’s description of the Shortcut Icon Loading Vulnerability - CVE-2010-2568 as reporting a defect in the parsing of shortcut (.LNK) files. After all, that’s what it says: “Windows incorrectly parses shortcuts”. Knowing no better, you might take Microsoft at its word, perhaps grumbling that Windows can’t be inspected because it’s not open-source. You might form in your head a scenario in which something that ought to be rejected as an error in a .LNK file instead goes unnoticed and induces the parser to misbehave. You might then think that exploiting this vulnerability is a matter of contriving the error so that the misbehaviour is predictable in a way that’s useful to malware. If you’re writing about the particular exploitation by the Stuxnet worm, you might wave your hands around these thoughts and even turn out phrases such as “malformed .LNK file”.
If you have done that in writing about Stuxnet, then you have done your readers a disservice. It’s certainly true that Stuxnet plants a .LNK file on removable media such that merely browsing the media with the Windows Explorer will execute the Stuxnet component for which a pathname can be seen in the .LNK file. Yet it simply isn’t true that this trick depends on the .LNK file being malformed. It’s not even true that the .LNK file must be “specially crafted” (which is Microsoft’s phrase), unless we’re meant to regard every .LNK file as specially crafted just for specifying what it’s a shortcut to. As a demonstration in this article will show, a .LNK file that runs code just from being browsed can be well-formed and can even be created in ordinary usage. Whatever Windows defect it is that Stuxnet exploits with its shortcut, it’s not that the shortcut gets incorrectly parsed—and Microsoft surely knows this.
Microsoft’s description of CVE-2010-2568 doesn’t give anything like enough detail that analysts could reliably find which defect is meant even if they had the source code to look through. To my mind, that’s bad enough for what it says of the general usefulness of the Common Vulnerabilities and Exposures list, of Microsoft’s attitude to that list, and of everyone else’s willingness not just to tolerate such short shrift but to be grateful for Microsoft’s participation. What’s particularly bad is that Microsoft’s description in this case looks like a knowing deflection. If the defect used by Stuxnet is indeed CVE-2010-2568, then to talk of it as any sort of parsing error is beyond any reasonable stretch of interpretation. Indeed, shortcut files are involved as no more than a vector for exploiting the defect.
Of course, where this vector leads to in the way that Stuxnet exploits the defect is found easily enough. Among “official” descriptions to contrast with Microsoft’s, we do at least have Vulnerability Note VU#940193 to record that the defect is somewhere in the Control Panel. Any number of malware analysts have fleshed it out by running the Windows Explorer under a debugger, browsing a directory that contains Stuxnet’s .LNK file, watching for where LoadLibrary gets called, and reporting in more or less detail what they see in the call stack. But there they tend to leave it. Perhaps that’s all to expect of malware analysts. What they’re meant to analyse is the malware, not Windows. Yet from the browsing of Stuxnet’s .LNK file to the execution by Windows of Stuxnet’s code, all the action is in Windows, not in the malware. Between the start and end of this defect’s exploitation by Stuxnet in particular, there’s potentially a lot to miss about the defect in general.
The defect is in the Control Panel’s support for an age-old, long-documented and still-supported, but arguably obscure, feature of CPL modules. It’s a particular case of defect that I would not be surprised to find in most implementations of shell folders, but which is specially dangerous in the Control Panel because items in this shell folder may need to be executed just to enumerate them. This design is not of itself defective, but you would hope it makes the Control Panel specially defensive about what counts as a Control Panel item. It mostly is. When you open the Control Panel, all the items it even considers showing you do at least have to be installed in one way or another.1 Unfortunately, when the Control Panel is asked about any one Control Panel item, it is more than a little bit too trusting that what it is being asked about actually is an installed Control Panel item. In one case, this credulousness means that the Control Panel, just for being asked what icon to use for a supposed Control Panel item, can be induced to execute as a CPL module a DLL that it would otherwise not have considered loading.
A .LNK file is one way to get this “what icon” question delivered to the Control Panel so that the Control Panel can go wrong when answering. It’s a very effective way, especially for malware, but to finger the .LNK file as the vulnerability is, at best, to shoot the messenger. There are other ways to put the question. There are other questions to put. There are other shell folders to put them to. Looking into this bigger picture may not turn up anything as devastating as remote code execution just from browsing a shortcut file, but surely the looking would better be done than not.
For an analogy, suppose Internet Explorer allowed that if a URL has a particular protocol then it names an executable. Suppose that a bug allows that the named executable is accepted as local, by-passing the usual defences against running programs from over the Internet. Suppose further that this bug was exploited by leaving on pages all around the web a hyperlink to a URL that uses this protocol. If you had the job of explaining the exploitation, you would not say the vulnerability is with Internet Explorer’s parsing of hyperlinks (or URLs). Well, you might if Microsoft had directed your attention to the parsing of hyperlinks and you just followed Microsoft’s lead.
A generous rationalisation of Microsoft’s description is that it is not intended as identifying a coding error. Microsoft’s aim is instead to report that because of a recently discovered coding error, Windows has a previously unknown vulnerability. Microsoft means only to sketch this vulnerability and how it might be attacked, to present some workarounds and eventually to fix the defect—and if this can all be done without pinpointing the defect and exposing it to critical comment, then so much the better for Microsoft. But who outside Microsoft should want to play along with that?
Because so many authorities on either the vulnerability itself or its exploitation by Stuxnet have by now entrenched the idea that the fault is in the .LNK file, it may be as well to begin with a demonstration. Let’s forget the typical path of malware analysis, which would trace the particular way that Stuxnet accomplishes its trick for Stuxnet’s particular purposes. Let’s instead reproduce the essence of Stuxnet’s trick in a way that has nothing to do directly with Stuxnet, or with malware, but can instead occur in more or less ordinary usage.
What we want to show is that code of our choice can be got running just by browsing a directory that contains a .LNK file. Of course, we have to get the code in the right packaging for this to work and we have to make a .LNK file that matches the code. But to show that there need be nothing remarkable, let alone wrong, with the .LNK file, we shall create it using nothing more special than the ordinary user-interface support of the Windows shell.
As an aside, I note that a demonstration such as this is a lot easier to devise if you first know the theory to the bug.
The code that we will run is a CPL module whose icon is always to be resolved dynamically. Every installed CPL module is executed even before any Control Panel items supported by the module are ever launched. That each module may turn out to support multiple items is one reason for this implicit execution. Each item in a module can have its own icon, display name and description, and these too are all discovered by executing the module. After one execution though, these properties are typically cached so the module is never again executed just to rediscover them, but only executes again if one of its Control Panel items actually is launched. A Control Panel item can, however, override this caching. It can indicate that it wants the corresponding CPL module to be loaded and called when the item’s icon, display name or description is next sought. Providing for this is where the Control Panel has the exploitable bug.
Since a CPL module that leaves an item’s icon to be resolved dynamically isn’t something you’re certain to have already, the demonstration depends on you to write your own or trust mine. The following must be close to minimal if we want at least some sign that it runs:
#include <windows.h> #include <cpl.h> #include <strsafe.h> #pragma comment (lib, "user32.lib") // for MessageBox CHAR const szName [] = "Test"; LONG APIENTRY CPlApplet ( HWND hwndCpl, UINT uMsg, LPARAM lParam1, LPARAM lParam2) { static BOOL haverun = FALSE; if (!haverun) { haverun = TRUE; MessageBox (hwndCpl, "Did you want me?", szName, MB_OK); } switch (uMsg) { case CPL_INIT: return TRUE; case CPL_GETCOUNT: return 1; case CPL_INQUIRE: { CPLINFO *info = (CPLINFO *) lParam2; info -> idIcon = CPL_DYNAMIC_RES; info -> idName = CPL_DYNAMIC_RES; info -> idInfo = CPL_DYNAMIC_RES; info -> lData = (LONG_PTR) NULL; return 0; } case CPL_NEWINQUIRE: { NEWCPLINFO *info = (NEWCPLINFO *) lParam2; info -> dwSize = sizeof (*info); info -> hIcon = NULL; StringCbCopy (info -> szName, sizeof (info -> szName), szName); StringCbCopy (info -> szInfo, sizeof (info -> szInfo), szName); info -> szHelpFile [0] = '\0'; return 0; } case CPL_DBLCLK: { MessageBox (hwndCpl, "Sorry, this is all I do", szName, MB_OK); return 0; } } return 0; } DWORD WINAPI DllMain ( HINSTANCE hinstDll, DWORD dwReason, LPVOID lpReserved) { return TRUE; }
To make this into a CPL module, compile to taste, and then link with switches for creating a DLL and for naming the ways that a CPL module can be called:
cl /c /Oxs /W3 test.cpp
link /dll /entry:DllMain /export:CPlApplet /out:test.cpl test.obj
The output is a small CPL module that will show a message box to ask “Did you want me?” when the CPlApplet function is first called, whatever the reason. Should that reason turn out to be that the module’s Control Panel item actually is being launched, then another message box apologises for doing next to nothing. The icon shown for the module’s one Control Panel item will be a default, because CPL_INQUIRE is handled by leaving the icon to CPL_NEWINQUIRE, which then doesn’t bother providing one. As I said, it’s close to minimal.
If you have sufficient privilege, then perhaps the easiest way to install a CPL module is to copy it to the Windows System directory. The standard way, however, is to list the module in the registry:
Key: | HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Control Panel\CPLs |
Value: | anything, e.g., Test |
Type: | REG_SZ or REG_EXPAND_SZ |
Data: | path\test.cpl |
After editing this into the registry, open the Control Panel, and check that an item named Test appears. (Classic View is best for simplicity, but if you prefer Category View and want to stick with it, look in the Additional Options category.) See that merely showing it in the Control Panel involves loading it and calling its CPlApplet function. Indeed, it may be loaded multiple times.
With the Control Panel still open, right-click on the Test icon and choose Create Shortcut from the context menu. If you don’t like to disturb your desktop, instead do a Ctrl-Shift drag to create the shortcut at a location of your choice. Either way, note that even something as seemingly inert as creating a shortcut to this Control Panel item gets the corresponding CPL module loaded and executed.
Play with the shortcut, if you like. For instance, ask for its Properties, and confirm that the shortcut is indeed to a Control Panel item, rather than to the CPL module as a file. Again, this involves the Control Panel in loading and executing the target. The shortcut has recorded that, if only at the time the shortcut was created, the target item is one of those inconvenient ones that doesn’t want its icon to be cached but means instead to choose its icon dynamically. Because of this dynamic resolution of the icon, the target item must even be loaded and executed just to show the shortcut when you browse whichever directory you placed it in. Be sure to try this before proceeding: it is your confirmation that executing this type of CPL module merely for browsing a shortcut file is ordinary Windows behaviour, with no malware in sight and no contrivance either.
Though all this loading and executing and unloading is undeniably inefficient, it is not of itself defective. The type of CPL module that we have created to cause all this work is documented and supported, admittedly with strong advice against it because of the inefficiency, but also with a suggestion that the behaviour may be desirable to someone, so that a Control Panel item’s appearance can change according to the current state of whatever the item exists to control.
What would be defective is if all this loading and executing and unloading occurred without the shortcut’s target actually being an installed Control Panel item. You will have guessed already that it does, because that’s obviously where the demonstration is leading. But there’s more. All this loading and executing and unloading can occur even when the shortcut’s target is a CPL module that is explicitly prohibited.
So, before we uninstall our CPL module by deleting the registry value from earlier, let’s have an optional diversion and add the module to either list of modules that the Control Panel is not to load:
Key: | HKEY_CURRENT_USER\Control Panel\don't load
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Control Panel\don't load |
Value: | test.cpl |
Data for the value is immaterial. After adding this value, close and re-open the Control Panel, or just refresh it. As expected, although our CPL module is still installed through the CPLs key, its Control Panel item no longer appears in the Control Panel. Indeed, our CPL module does not get executed.2
Now delete what we added to the CPLs key. You might think that the Test item must now be gone from the Control Panel. Not only is the CPL module no longer installed, but if you took the diversion, it’s even on a list that was seen to stop the module from being loaded while it was installed. So, try finding Test in the Control Panel now. Log off and back on, or even restart Windows, and try it. What you get is indeed that Test does not show as a Control Panel item when you open the Control Panel, and TEST.CPL does not get executed.
So far, so good, and even boring. We installed a CPL module, created a shortcut to it, uninstalled it, and can’t be the slightest bit surprised to find that the Control Panel doesn’t show it any more. But browse now to the directory where you left the shortcut. Oh, now the uninstalled CPL module executes! Double-clicking the shortcut even launches the target item, just as if it were still installed. If you took the optional diversion, you’ll see that this surprise execution even by-passes the don't load list.
Obviously, the uninstalled CPL module ought not get executed. Just as obviously, the shortcut is not to blame for this unwanted behaviour. While the CPL module was installed, the shortcut was perfectly fine. That the CPL module is not currently installed is not the shortcut’s fault. After all, the module may still be installed for another user. The demonstration might just as well have two users. One installs the module, creates a shortcut to the item, and leaves the shortcut in a public or shared location. The other does not install the module, but tries the shortcut anyway. That it works for the second user is unwanted but also means that the shortcut works properly as a shortcut. The most that can be expected of it as inert data in a file is that it correctly holds some sort of description of what may be (or ought once to have been) a Control Panel item. The most that can be expected of whatever code parses the shortcut is that it delivers this description intact to the Control Panel for interpretation. Since it does that, the unwanted behaviour can only be the Control Panel’s.
As we shall see when we progress to the theory, the description that is contained within the .LNK file and is passed to the Control Panel is binary data that everyone but the Control Panel is supposed to treat as opaque. Still, if you don’t already know the theory, you may (and arguably should) be thinking that if the only way this description can get to the Control Panel is from a .LNK file, then I’m just splitting hairs and the problem is still in practice that something’s wrong with .LNK files.
So, let’s redo the demonstration but with a variation. Install our CPL module and open the Control Panel, but instead of creating a shortcut file, drag our Control Panel item to the Start Menu. Uninstall the CPL module. Then log off and back on.
That the CPL module executes automatically when you log back on will, by now, be no surprise. You’ll be thinking that dragging to the Start Menu created a shortcut file in one or another directory from which the shell cobbles together the Start Menu, and restarting the shell automatically browses that directory. That is very much what happens if you use the Classic Start Menu. More likely, however, you use the new style of Start Menu. Dragging to the Start Menu will have pinned our Control Panel item to the Start Menu, above the line in the left pane, but will not have created any shortcut file. The Start Menu’s pinned list does not rely on shortcut files but is instead recorded in the registry:
Key: | HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\StartPage |
Value: | Favorites |
The format of binary data for this value is given separately, under the heading The Pinned List in The Start Menu’s Start. When we drag our Control Panel item to the new style of Start Menu, the very same description that would have gone into a .LNK file instead goes into the registry. Whichever way the description gets to the Control Panel, it’s the Control Panel that goes wrong. The problem may still be one of parsing, but if so, it is of how the Control Panel parses the description, not of how anyone parses shortcut files.
To see where the bug is in the Control Panel, and to confirm formally that it can’t sensibly be blamed on .LNK files, we need to know what a .LNK file is meant to be a shortcut to. Since the answer is any item in the shell namespace, we need to know what such items are and how they are represented. This is, of course, all documented, but since it evidently isn’t understood by all the malware analysts who have made a “close inspection” of the bug or its exploitation, a brief review may help.
The shell namespace generalises what is more familiar as the directory tree of a file system. Indeed, a point to the shell namespace is to include the accessible file systems but to add any number of more or less virtual folders that each abstract something about the computer, its environment and the many ways it might all be used. A shell namespace item that can contain other items is called a shell folder. It is supported by executable code that defines the folder as a container of items and is responsible for access to the contained items, some of which may in turn be folders. This code is specified as loosely as possible to support common behaviour without constraining the implementation. That different folders can have very different implementations is primarily what gives the namespace its generality.
Each item’s path in the namespace has a structural form, known colloquially as a PIDL but properly as an ITEMIDLIST. A PIDL is a sequence of variable-sized SHITEMID structures which each represent one item in the namespace. Successive items have a parent-child relationship. In an absolute PIDL, the first item is relative to the root of the namespace, i.e., to the desktop, so that the whole PIDL is unique to one item in the whole namespace.
The formal specification of an SHITEMID is just a word to be followed immediately by unspecified data. The word gives the total size in bytes, i.e., of the word and the data. An empty SHITEMID is just zero as a word. The point to the unspecified data is that its interpretation is entirely a matter for the shell folder that contains the item. PIDLs are designed to be shared among all parties with an interest in a shell namespace item but to be opaque to everyone who isn’t actually responsible for containing the item within the namespace.
Importantly, the sharing of PIDLs is intended to allow that the interest in a shell namespace item may be expressed at different times, in different Windows sessions, and even on different machines. As Microsoft’s documentation puts it, the unspecified data in a PIDL is to be “persistable and transportable”. PIDLs are designed to be saved for later reference. Where they are saved to can be just about anywhere. How they are saved doesn’t matter either, as long as they can be recovered. As if to prove the point, there are shell functions for reading and writing a PIDL to an arbitrary stream (see IStream_ReadPidl and IStream_WritePidl). We have already seen, in the demonstration, that PIDLs get saved in binary data for registry values. Through all the years of 32-bit Windows, PIDLs have plausibly been saved in any number of proprietary file formats. The most common storage of PIDLs in files, however, is provided by the Windows shell in the form of shortcut files, usually with the .LNK extension for their filenames.
A .LNK file is the shell’s own container for the convenient saving of an absolute PIDL for an arbitrary item so the item can easily be accessed another time from wherever the container has been left. As a container, a .LNK file may have properties of its own and may also specify ways that the target item is to be accessed, but the target item itself is specified entirely by the PIDL.
The flip side to PIDLs being essentially opaque is that once a .LNK file is read and its PIDL is extracted in conformance to the .LNK file format, interpreting the PIDL is no longer the responsibility of whatever code read and interpreted the .LNK file. Interpretation of a PIDL, no matter where the PIDL came from, is necessarily and wholly the responsibility of the succession of shell folders that Microsoft’s documentation makes abundantly plain are the only permitted interpreters of the unspecified data in a PIDL’s succession of SHITEMID structures.
For the purpose of this interpretation, the essence of a shell folder is its implementation of the IShellFolder interface. It will not surprise that most of this interface’s methods either produce PIDLs or accept them.
Of particular interest is the EnumObjects method. It produces an IEnumIDList interface. Repeated calls to that interface’s Next method retrieve PIDLs for items in the folder. Doing this is the essence of browsing the folder. It is perhaps the primary means by which PIDLs are discovered. The secondary means would be the ParseDisplayName method. It takes a more-or-less human-readable name for what its caller presumably hopes is an item in the folder, or might be creatable as an item in the folder, and produces a PIDL for that item.
Whichever way a PIDL was discovered for an item, its main usefulness is that it can be fed back to its folder through the other IShellFolder methods to learn more about the item or even to modify the item. An important example, particularly in the present context of finding an icon to use for an item, is the GetUIObjectOf method. At its simplest, it takes a PIDL for one item and an interface ID and produces the item’s implementation of the indicated interface. Methods of that interface can then be called to work on the item. For instance, if the interface is IExtractIcon, its GetIconLocation method may tell where to get an icon for the item.
Now, as a malware analyst reading this article, you might (reasonably) not know enough about Windows to question Microsoft’s description of a Windows bug, and you might (less reasonably) not let that stop you from saying you have made a detailed study of that bug’s exploitation, but at least you can be depended on for your security radar to sound the alarm at this general category of code that hands out data and assumes it will come back intact, or that it will have the same meaning by the time it comes back, or that what comes “back” ever was passed out. You will already be wondering how many shell folders are lax with PIDLs they receive, what can go wrong if any are, and whether anything that can go wrong is exploitable by malware. You may even be wondering how many other malware analysts have thought of this.
If a shell folder assumes that the PIDLs it can ever receive for methods such as GetUIObjectOf must have been produced by other methods, such as EnumObjects and ParseDisplayName, of the same instantiation of that shell folder, does it give malware an opportunity for mischief or exploitation?
I don’t know what answer might be found for this question, in general. That the answer is yes for the particular shell folder known as the Control Panel has been demonstrated very well by Stuxnet—spectacularly well, even. That a worm can get onto a computer just from browsing shortcut files on removable media, which appears not to have been thought of much as a possibility before Stuxnet, should mean that the answer is not just yes but a resounding yes. Yet there is no resounding, nor even a simple yes, because as far as I can see, nobody has asked the question in public until now. If you take one thing from this article, I should want it to be that Microsoft’s loose talk of parsing shortcut files and malware analysts’ uninformed talk of malformed .LNK files have deflected attention from where it is in Windows that gave this malware such alarming and novel opportunity.
Now that you know the theory, you should see that the demonstration we started with is at least an obvious thing to try as an experiment. It is just the case where the PIDL is produced by an earlier instantiation of the Control Panel, saved somewhere, and then presented to a later instantiation. As a malware analyst, knowing the minds of hackers, you will of course be thinking how much worse things might be if the PIDL were not merely stale but synthesised. Dealing with that question is as good a way as any to derive a demonstration of where the Control Panel goes wrong when exploited by Stuxnet.
To synthesise a PIDL for the Control Panel, we must know the format that the Control Panel uses for the unspecified data in an SHITEMID at its level of any PIDL. Microsoft does not document this format, but once it has been discovered from the Control Panel’s code in SHELL32, the public symbol file for SHELL32 does at least tell us Microsoft’s name for the structure. In fact, there are two formats, represented as IDCONTROL and IDCONTROLW structures, containing ANSI and Unicode strings respectively.3 Given that we understand these structures’ members, synthesising a PIDL and feeding it to the Control Panel can be as simple an exercise as the following console application (from which I have, for brevity, stripped as much error handling as I can bear to):
#include <windows.h> #include <shlobj.h> #include <stdio.h> #pragma comment (lib, "ole32.lib") // for CoCreateInstance and friends /* The (undocumented) ANSI form of PIDL for a Control Panel item formed from a CPL module */ struct IDCONTROL : public SHITEMID { INT Icon; // negative index of icon as resource in module USHORT Name; // offset from end of structure to item's name USHORT Info; // offset from end of structure to item's description // module's pathname at end of structure }; /* A function to compose one of the above */ PITEMID_CHILD MakeCplPidl (PCSTR Module, INT Icon, PCSTR Name, PCSTR Info) { int cbmod = strlen (Module) + 1; int cbname = strlen (Name) + 1; int cbinfo = strlen (Info) + 1; int cb = sizeof (IDCONTROL) + cbmod + cbname + cbinfo; if (cb != (USHORT) cb) return NULL; PITEMID_CHILD pidl = (PITEMID_CHILD) new BYTE [cb + sizeof (SHITEMID)]; if (pidl != NULL) { IDCONTROL *idc = (IDCONTROL *) &pidl -> mkid; idc -> cb = (USHORT) cb; idc -> abID [0] = 0; idc -> abID [1] = 0; idc -> Icon = Icon; PCHAR module = (PCHAR) (idc + 1); PCHAR p = module; memcpy (p, Module, cbmod); p += cbmod; idc -> Name = (USHORT) (p - module); memcpy (p, Name, cbname); p += cbname; idc -> Info = (USHORT) (p - module); memcpy (p, Info, cbinfo); p += cbinfo; ((SHITEMID *) p) -> cb = 0; } return pidl; } int __cdecl main (int argc, char **argv) { /* Get names from the command line. */ PSTR cplname = NULL, itemname = NULL, iteminfo = NULL; while (++ argv, -- argc != 0) { if (cplname == NULL) cplname = *argv; else if (itemname == NULL) itemname = *argv; else if (iteminfo == NULL) iteminfo = *argv; else return -1; } if (cplname == NULL) return -1; if (itemname == NULL) itemname = ""; if (iteminfo == NULL) iteminfo = ""; /* Confect a PIDL for a Control Panel item that is loaded from a CPL module but has no icon known from the module's resources. */ PCITEMID_CHILD pidl = MakeCplPidl (cplname, 0, itemname, iteminfo); if (pidl == NULL) return -1; /* Instantiate the Control Panel and feed it the PIDL to ask what icon is (or would be) used for the Control Panel item. */ HRESULT hr = CoInitialize (NULL); if (SUCCEEDED (hr)) { IShellFolder *sf = NULL; hr = CoCreateInstance (CLSID_ControlPanel, NULL, CLSCTX_INPROC_SERVER, __uuidof (*sf), (PVOID *) &sf); if (SUCCEEDED (hr)) { IExtractIcon *ei; hr = sf -> GetUIObjectOf (NULL, 1, &pidl, __uuidof (*ei), NULL, (PVOID *) &ei); if (SUCCEEDED (hr)) { CHAR path [MAX_PATH]; int index; UINT flags; hr = ei -> GetIconLocation (0, path, RTL_NUMBER_OF (path), &index, &flags); if (SUCCEEDED (hr)) { printf ("Path: %s\n", path); printf ("Index: %d\n", index); printf ("Flags: 0x%08X\n", flags); } ei -> Release (); } sf -> Release (); } CoUninitialize (); } return hr; }
All this program exists to do is abuse the Control Panel’s support for Control Panel items that are loaded from CPL modules and leave their icon to be resolved dynamically. Note that the abuse is of the support, not of any item or module. Where we synthesise a PIDL from the names on our command line, we describe in the Control Panel’s own language a Control Panel item that is coded in a CPL module but is not known to have its icon among the module’s resources. The problem is not that such an item is supported. The problem is that the Control Panel accepts our description without complaint when we ask where might we get an icon for this item. Short of such things as memory allocation errors, the Control Panel will always try to answer the question by loading whatever we name as the module, checking whether the module exports a CPlApplet function, and calling that function potentially many times, hoping to find a match for whatever we named as the item.
All this loading and execution is, of course, just what the Control Panel must do if the named module and item actually are what we make them out to be. But what if we’re lying? Whether it’s just that we have old information or that our intention is malicious, the Control Panel tries to load and execute anyway. If the supposed CPL module is any sort of DLL, it will be loaded and its DllMain function will be executed. If it’s a DLL with a CPlApplet function, then this function will be called. That the supposed CPL module might not be installed, and would therefore not be considered for an enumeration of Control Panel items, is not even suspected. The supposed CPL module could be on the don't load list, and the Control Panel would not notice.
[1] Of the surprisingly many ways to install Control Panel items, the only published list I know that stands any chance of being comprehensive is my own, under the heading Control Panel Items in my documentation of the ControlPanel class. Note, by the way, that installing into the Control Panel does not require administrative privilege.
[2] If you want to see it just get hidden from view, remove the TEST.CPL module from the don't load list and instead put the Test item on the DisallowCPLs list. This latter has user-interface support as an administrative policy, through the Group Policy Object Editor, as “Hide specified Control Panel items” in the Control Panel administrative template. The descriptive text is accurate. The setting really does just hide the item, not disallow it. While our Control Panel item is on the disallowed list, browsing the Control Panel will still execute our CPL module even though its Control Panel item does not show.
[3] The Control Panel works with Unicode internally, of course, but it prefers the ANSI form when producing PIDLs. If all the strings that are to be stored in the PIDL, i.e., the module’s name, the item’s name and the item’s description, have all their characters in the lower half of the ASCII table and fit various limits on their length (MAX_PATH characters for the names and 0x0200 characters for the description), then apparently on the basis that nothing is lost from using ANSI characters, the Control Panel produces the PIDL as an IDCONTROL, not an IDCONTROLW.