1 little known secret of ShellExec_RunDLL

The ShellExec_RunDLL API is now exposed by both shell32.dll and windows.storage.dll.

It is not the only curiosity about this function. Analysing its code one can discover that is accepts a secret command line argument.

If we provide a question mark in the command line argument, the function will interpret the string that follows the question mark as a number. It will then convert that numerical value into a number using StrToIntExW with a STIF_SUPPORT_HEX flag (accepts either decimal or hexadecimal number), and then add that value to 0x100 (SEE_MASK_NOASYNC/SEE_MASK_FLAG_DDEWAIT). Finally, use the resulting total to set the SHELLEXECUTEINFO.fMask value passed to ShellExecuteEx. The function then searches for the second question mark and then uses the position following that question mark as a place where the actual command line passed to ShellExecuteEx starts:

If it sounds too complicated, the basic idea is that function can be invoked in 2 modes:

  • regular invocation
shell32.dll, ShellExec_RunDLL <cmd line argument>
windows.storage.dll, ShellExec_RunDLL <cmd line argument>
  • invocation that modifies fmask
shell32.dll, ShellExec_RunDLL <?fmaskvalue?> <cmd line argument> 
windows.storage.dll, ShellExec_RunDLL <?fmaskvalue?> <cmd line argument>

f.ex.:

ShellExec_RunDLL ?100?calc.exe

And if you are looking for a better example:

ShellExec_RunDLL ?0x00800000?<file>

can be used to bypass Zone.Identifier checks (0x00800000 = SEE_MASK_NOZONECHECKS) for the executed file.

Since the parsing of the fmask value is done with a code that allows for many different inputs, many interesting invocations are possible:

ShellExec_RunDLL ?100?calc.exe
ShellExec_RunDLL ? 100 ?calc.exe
ShellExec_RunDLL ? 0x100 0x200 ?calc.exe
ShellExec_RunDLL ?0x100 notepad.exe?calc.exe
ShellExec_RunDLL ?0x100 format c: ?calc.exe
ShellExec_RunDLL ?0x100 https://google.com ?calc.exe
ShellExec_RunDLL ?0x100 c:\programdata\malware\calc.exe ?calc.exe

Every single one of them will launch Calculator.

Mapping the API mapping/code redundancy

In my last post I have shown that some of the shell32.dll functions are now mapped to windows.storage.dll.

This sort of API mapping, as well as blatant code redundancy present in many Windows binaries is not new, and we have seen many instances of it over the years:

  • Windows API sets
  • gdi32.dll and gdi32full.dll
  • gdi32full.dll and win32u.dll
  • combase.dll and ole32.dll
  • kernel32.dll and KernelBase.dll
  • IEAdvpack.dll and advpack.dll
  • crtdll.dll, msvcirt.dll, ucrtbase.dll and their many, many versions over the years
  • ntdll.dll and ntoskrnl.exe (user mode vs. kernel mode mapping)

and so on, and so forth.

It is probably not surprising that after that latest discovery it was only natural for me to build a list of APIs (API names) that are shared between many libraries to see if I can discover more interesting bits.

Looking at the list of API names that appear to be shared between at least 2 DLL libraries on the Windows 11 24 H2 build – win11_24H2_list_64_shared.txt – one can immediately see a lot of interesting findings:

  • sqlite functions are exported by SearchIndexerCore.dll, StateRepository.Core.dll, winsqlite3.dll
  • apart from kernel32.dll and KernelBase.dll there is now also kernel.appcore.dll
  • code base of tcblaunch.exe and winload.exe seems to be overlapping a lot
  • edgehtml.dll replaces mshtml.dll

Unfortunately, I have not seen anything similar to ShellExec_RunDLL – a discovery that kicked off this research 🙁