View Full Version : Reversing RunDialog (Start+Run or Winkey+R) to Add a 27th entry to RunMRU list

December 29th, 2012, 14:06
Mint77 posted in the forum asking how to increase the run dialog
(Start => Run / Winkey + R) MRU (Most Recently Used ) contents

It is aptly named MRU which implies it holds only a few entries and not all the entries from times immemorial

It seems he had earlier been told by someone at MSDN that the max limit was 26 entries and the older entries drop out on FIFO (First In First Out) basis

So if the MRU list is full the and we have a new contender the oldest entry drops out and the latest entry is dropped in

Logically right and practically right

But logic and practicality never works when it comes to reversing

He posted that he was told there was no way to increase the limit and was looking for some ideas to realize his fantasy

So I tossed in a some time and took a look.

A few minutes in procmon / windbg and the 27th entry is overloaded into RunMruList



do not mimic the methods shown. Without understanding the implications of various locks held like spinlocks , critical sections, , interlocked cmpexchg in critical system processes it may crash and may cause BSOD

RUN Dialog is handled by explorer.exe
Lets fire procmon and capture pertinent events in explorer.exe

(be sure to configure symbols in procmon prior to capturing events so that stack is properly displayed you can point it to your _NT_SYMBOL_PATH cache )

a default procmon captures too much events

procmon monitors file events , registry events network event and profiling events
apart from process / thread events

an enormous amount of spew ensues as a result

we need to set a filter

we know we are interested in explore.exe events only

we also are interested in MRU so set a filter for MRU in path

click filter menu and select filter submenu (ctrl + L hotkey)

process monitor filter will popup

1st time
2nd time

in the 1st drop down select

in the 2nd drop down select

in the 3rd drop down type in

in the 4th drop down select

click add after each time if all went well the pane should look like this


click apply and let procmon roll

goto start => hit run or do Winkey+R and execute a program

preferably one which is not available in MRU

or clear the MRU via taskbar and start menu properties and type in a new program
so that we have a complete log

a fresh run will see the following entries notice reggsetvalue that is highlighted
we are interested in its stack which is show below


so AddMruStringW is where something interesting might be available lets close procmon for now and run a debugger to check th AddMruStringW function in explorer.exe


windbg -pn explorer.exe -c "bp Shell32!AddMruStringW;g"

-pn attaches to a running process by specifying the process name

-c Specifies the initial debugger command to run at start-up.

In the initial command we are setting breakpoint on the api we saw in procmon and ask windbg to continue its execution

We now go to start=>run and enter any program to execute so that we shall break in windbg and we can examine the state


Breakpoint 0 hit
eax=0274ee20 ebx=00000000 ecx=7c9c95ba edx=000000fb esi=01c73ff8 edi=00000009 eip=7ca2b28d esp=0274ebf8 ebp=0274f0b0 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
7ca2b28d 8bff mov edi,edi

lets check the stack


0:017> kb
ChildEBP RetAddr Args to Child
039bebf4 7ca408e2 00139220 039bee20 00150202 SHELL32!AddMRUStringW
039bf0b0 7ca4073f 039bf128 7ca40091 039bf0ec SHELL32!CRunDlg::OKPushed+0x1ce
039bf0c0 7e418734 00150202 00000111 00000001 SHELL32!RunDlgProc+0x121
039bf0ec 7e423ce4 7ca40091 00150202 00000111 USER32!InternalCallWinProc+0x28
039bf158 7e423b30 0009e158 7ca40091 00150202 USER32!UserCallDlgProcCheckWow+0x146
039bf1a0 7e423d5c 00000000 00000111 00000001 USER32!DefDlgProcWorker+0xa8
039bf1bc 7e418734 00150202 00000111 00000001 USER32!DefDlgProcW+0x22
039bf1e8 7e418816 7e423d3a 00150202 00000111 USER32!InternalCallWinProc+0x28
039bf250 7e42927b 0009e158 7e423d3a 00150202 USER32!UserCallWinProcCheckWow+0x150
039bf28c 7e4292e3 005aa750 00566d80 00000001 USER32!SendMessageWorker+0x4a5
039bf2ac 7e431cde 00150202 00000111 00000001 USER32!SendMessageW+0x7f
039bf2dc 7e42763c 00150202 0057edc0 00170222 USER32!IsDialogMessageW+0x41f
039bf318 7e4249c4 00150202 00170222 00000001 USER32!DialogBox2+0x144
039bf340 7e424a06 7c9c0000 7cc26c50 00170222 USER32!InternalDialogBox+0xd0
039bf360 7e4247ea 7c9c0000 7cc26c50 00170222 USER32!DialogBoxIndirectParamAorW+0x37
039bf384 7ca4033c 7c9c0000 000003eb 00170222 USER32!DialogBoxParamW+0x3f
039bf3cc 7ca402c8 7c9c0000 000003eb 00170222 SHELL32!SHFusionDialogBoxParam+0x3b
039bf400 0102129f 00170222 00000000 039bf834 SHELL32!RunFileDlg+0xc4
039bfa40 010210f3 00170222 00000000 01be80c0 Explorer!_RunFileDlg+0x12f
039bfee0 77f69598 000003b8 01be71e0 77f6957b Explorer!CTray::_RunDlgThreadProc+0x29a
039bfef8 7c927ac2 01be71e0 7c97e440 00160a70 SHLWAPI!ExecuteWorkItem+0x1d
039bff40 7c927b03 77f6957b 01be71e0 0009f298 ntdll!RtlpWorkerCallout+0x70
039bff60 7c927bc5 00000000 01be71e0 00160a70 ntdll!RtlpExecuteWorkerRequest+0x1a
039bff74 7c927b9c 7c927ae9 00000000 01be71e0 ntdll!RtlpApcCallout+0x11
039bffb4 7c80b729 00000000 0274ec60 0274ec60 ntdll!RtlpWorkerThread+0x87
039bffec 00000000 7c910250 00000000 00000000 kernel32!BaseThreadStart+0x37

it looks the same as in procmon

lets check the arguments to the function

since this is x86 32bit three arguments are shown in stack if we want more we need to play with esp register

note: be aware in x64 the first three four args are passed in registers

the first three args as per kb


ChildEBP RetAddr Args to Child
039bebf4 7ca408e2 00139220 039bee20 00150202 SHELL32!AddMRUStringW

what is the first argument

0:017> dd poi(esp+4) l4
00139220 00000002 0000001a 7c80aa36 00000828

you should recognize the 0x1a

0:017> .formats 1a
Evaluate expression:
Hex: 0000001a
Decimal: 26 <--------------------

0:017> du poi(esp+8)
039bee20 "cmd\1" <------------- yes this is our input

so since this is an argument check who pushed it

ub return address in stack viz


0:017> ub 7ca408e2

7ca408c2 68b4959c7c push offset SHELL32!`string' (7c9c95b4)
7ca408c7 8d8570fdffff lea eax,[ebp-290h]
7ca408cd 50 push eax
7ca408ce ff15341c9c7c call dword ptr [SHELL32!_imp__StrCatBuffW (7c9c1c34)]
7ca408d4 8d8570fdffff lea eax,[ebp-290h]
7ca408da 50 push eax
7ca408db 56 push esi <-------------------
7ca408dc ff1584b2a27c call dword ptr [SHELL32!_imp__AddMRUStringW (7ca2b284)]

what is in esi

0:017> r esi

where did esi get that value

ub retn_address length > default on trial and error till we locate esi
or use ida
or ollydbg register highlighting functionality

we see esi got the value from the return value of a Function


0:017> ub 7ca408e2 l40

7ca408b2 e8b2faffff call SHELL32!OpenRunDlgMRU (7ca40369)
7ca408b7 8bf0 mov esi,eax <------------------------

7ca408b9 3bf3 cmp esi,ebx
7ca408bb 742b je SHELL32!CRunDlg::OKPushed+0x1d4 (7ca408e8)
7ca408bd 6806010000 push 106h
7ca408c2 68b4959c7c push offset SHELL32!`string' (7c9c95b4)
7ca408c7 8d8570fdffff lea eax,[ebp-290h]
7ca408cd 50 push eax
7ca408ce ff15341c9c7c call dword ptr [SHELL32!_imp__StrCatBuffW (7c9c1c34)]
7ca408d4 8d8570fdffff lea eax,[ebp-290h]
7ca408da 50 push eax
7ca408db 56 push esi
7ca408dc ff1584b2a27c call dword ptr [SHELL32!_imp__AddMRUStringW (7ca2b284)]

so we would need to check this function


0:016> uf SHELL32!OpenRunDlgMRU
7ca4002f 2145fc and dword ptr [ebp-4],eax
7ca40032 8d45e8 lea eax,[ebp-18h]
7ca40035 50 push eax
7ca40036 c745e818000000 mov dword ptr [ebp-18h],18h
7ca4003d c745ec1a000000 mov dword ptr [ebp-14h],1Ah
7ca40044 c745f002000000 mov dword ptr [ebp-10h],2
7ca4004b c745f401000080 mov dword ptr [ebp-0Ch],80000001h
7ca40052 c745f810109d7c mov dword ptr [ebp-8],offset SHELL32!`string'+0x10 (7c9d1010)
7ca40059 ff15b0b2a27c call dword ptr [SHELL32!_imp__CreateMRUListW (7ca2b2b0)]
7ca4005f e922030000 jmp SHELL32!OpenRunDlgMRU+0x49 (7ca40386)

7ca40369 8bff mov edi,edi
7ca4036b 55 push ebp
7ca4036c 8bec mov ebp,esp
7ca4036e 83ec18 sub esp,18h
7ca40371 6a00 push 0
7ca40373 6894f6bc7c push offset SHELL32!g_hMRURunDlg (7cbcf694)
7ca40378 ff15b4139c7c call dword ptr [SHELL32!_imp__InterlockedExchange (7c9c13b4)]
7ca4037e 85c0 test eax,eax
7ca40380 0f84a9fcffff je SHELL32!OpenRunDlgMRU+0x19 (7ca4002f)

7ca40386 c9 leave
7ca40387 c3 ret

so either InterlockedExchange return

0:016> ln poi(SHELL32!_imp__CreateMRUListW)
(7ca2b2b9) SHELL32!CreateMRUListW | (7ca2b320) SHELL32!CFSFolder::_CreatePerClassDefExtIcon
Exact matches:
SHELL32!CreateMRUListW (<no parameter info> return Is passed into esi

Remember the warnings about lock synchronization

This structure is protected by lock is what we can understand

Something like


void somecrapfunct(void)
If ( (intcmpexch(global_) ) == 0)
return ;

I leave the CreateMruList() analysis to readers

Hint it makes an indirect call Š and goes on to set a reg key


7ca4004b c745f401000080 mov dword ptr [ebp-0Ch],80000001h
7ca40052 c745f810109d7c mov dword ptr [ebp-8],offset SHELL32!`string'+0x10 (7c9d1010)

0:016> du /c 40 7c9d1010
7c9d1010 "Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU"

C:\WinDDK\7600.16385.1\inc\api>grep -ir "#define HKEY_current_user" *
)0x80000001) )
)0x80000007) )


Lets see what this global contains


0:016> dd poi(SHELL32!g_hMRURunDlg) l4
xxxxxxxx 00000002 0000001a 7c80aa36 000008fc

i removed the address coz i wrote this blog over several days so the address might confuse reader
for a single session the allocated address will remain same here xxxxxxxxx will be 00139220
in a single session output

there lies our esi to AddStringMruW

lets memory modify it


read warning in last paragraph about address assume 00139220 instead of 02522df4 for a single uninterrupted
session (it will be whatever it was in [esp+4] when you broke into windbg)

0:016> dd poi(SHELL32!g_hMRURunDlg)+4 L1
02522df4 0000001a

0:016> ed poi(SHELL32!g_hMRURunDlg)+4 0x1b < lets add one more string

0:016> dd poi(SHELL32!g_hMRURunDlg)+4 L1
02522df4 0000001b

lets detach windbg from explorer and keep adding strings to runmru
and check was it successful did we get our 27th string in runmru ?

yes we got it

warning again
please do not mimic this in system critical process

also this is not a full reverse it doesn�t mean that you can set the value to 0xffffffff
and have the full liberty of 2^32 � 1 strings in runmru

think before hand
what may happen to alloc memory
how the buffers are allocated why is it a protected global
can increasing beyond 26 corrupt further structures
where did the RunMRULIST get its next character from apart from �abcdef�..xyz�


0:016> .formats 61+0n26
Chars: ...{ +1 from z
what happens when this runs out of byte limit 0xff recycle or crash ? is it bytelimit or wordlimit ? or omg INT64

who else uses this set ba r4 breaks

happy runmruing

December 30th, 2012, 15:52
Nice reversing, and use of Procmon.

After reading that, I think ultimately if one wanted more than the 26 a-z entries it would be easiest to build their own ShellExec tray icon. Googling for 'RunDlg' brings up a few examples of exactly that, as well as adding MRU's to menus and such.

Parsing the leaked Win2k source will also net the original RunDlg code too


January 5th, 2013, 15:27
"note: be aware in x64 the first three args are passed in registers."

first 4 actually... rcx, rdx, r8, r9, (or floats passed via xmm)

also, bsod risk when debugging.... kinda doubt it considering you're in userland code..

January 7th, 2013, 00:52
thanks for pointing out the error in x64 arg passing evlncrn8

well bsod risk who knows maybe yes if sufficiently mangling some yet to be found returntolibheapnullderefappspecificdataoverwrittenheapedjitspray

January 8th, 2013, 07:12
Wow very nice writeup I'm kinda missing these old-good-style writeups with all details so ppl can actually learn way of RCE, not just outcome

April 13th, 2013, 16:35
Fun walk-through. I wonder if the same behaviour could be done by hooking OpenRunDlgMRU(), and patching it to use more memory. It is ODD seeing a hard coded value. 26 slots, 26 letters?

I got distracted and looked at windows 7's run list. It has a huge MRU, it is so long I wish it were possible to search it.



history | grep "obscure command"