如何显示Windows标准"运行"对话框

显示Windows的run对话框 通常使用Win键+R来显示Run对话框,这里用几行代码模拟按键完成同样的功能 立即下载
很不错的文章

作 者: zhzhtst
时 间: 2007-08-18,02:03
链 接: http://bbs.pediy.com/showthread.php?t=49874

如何显示Windows标准"运行"对话框

对于喜欢以命令行方式执行程序人来说,Windows中的标准“运行”对话框一定是再熟悉不过了。当我们看到Windows自带的任务管理器以及著名的Process Explorer中都能产生这个对话框时,我们会不会联想到这本身就是Windows提供的一个功能呢?作者深入分析了这个问题,找到了一个只以序号方式导出的未公开的API。本文对反向工程初学者也会有一定帮助。

注:附件中是本文的PDF格式,可读性好

探索如何显示Windows标准“运行”对话框
作者:SmartTech

我们有时在编写一些实用工具时可能想要显示一个类似与Windows中的标准“运行”对话框。虽然我们可以自己手工实现这个对话框,但是标准的“运行“对话框中却可以列出你曾经键入的命令。并且,最后一次输入的命令,如果它能够正常运行,那么它总是出现在组合框的最上端。在Windows中很多数据都是保存在系统注册表中的,我们当然有理由认为像这样的数据就被保存在注册表中。通过键入一些可用命令并在注册表中搜索,我们可以找到这个存储位置为HKEY_CURRENT_USER/Software/Microsoft/Windows/CurrentVersion/Explorer/RunMRU这个子键(SubKey)。这个子键中的每个值(Value)中的数据(Data)就是你曾经键入的命令。其中有个值MRUList保存的是你键入的命令的顺序。不过它保存的是键入的相反顺序,也就是说,最后一次键入并成功运行的命令总是出现在这个值的数据中的第一个位置。当然,我们可以枚举RunMRU这个子键然后将曾经运行过的各个命令添加到自己手工实现的运行对话框中的组合框中,并分析MRUList这个值中的数据来对枚举出的各个命令字符串进行排序。这样就能完全模仿系统的“运行”对话框。这不失为一种方法。但是,一定有许多人像我一样,确实想知道到底系统是如何显示这个对话框的。那就让我们一步一步来分析吧!
在调用这个对话框的系统程序中,我们首先想到就是系统Shell(即explorer.exe)。本想拿它来做实验,但是它太大,也太复杂,并不合适。接下来能想到的程序可能就是任务管理器了(即taskmgr.exe)。当你启动任务管理器后,单击“文件”菜单,就会看到“新建任务”命令,单击它就会出现“运行“对话框。另外一个会显示“运行”对话框的程序就是下面要提到的Process Explorer,并且它的“运行”对话框有标准的,也有定制的。那任务管理器是如何显示这个对话框的呢?这里,我要提到一个工具,那就是Process Explorer,它是由著名的Windows NT黑客Mark E. Russinovich编写的。现在Mark已经进入Microsoft,成为核心开发成员之一。它的网站www.sysinternals.com(现在已经合并到Microsoft网站中)中免费公开许多实用工具(甚至它们的源代码),像著名的FileMon和RegMon等等。在他与David A. Solomon合著的《Inside Windows 2000, Third Edition》中,Solomon曾经提到Mark不看Windows源代码,仅用ntoskrnl.exe(Windows执行内核)的反汇编代码并配合SoftICE就能很快解决遇到的问题。就连Windows NT之父David N. Cutler也佩服他竟然能在正在运行的Windows上运行内核调试器(Microsoft虽然支持内核调试,但是一定要在启动选项加入/DEBUG并重新启动之后才能有效地使用内核调试器),以及能让Windows 9x支持NTFS分区。这些都是十分棘手的问题。否则,Microsoft没有必要不自己实现。Mark的水平由此可见一斑。Process Explorer本来就是被设计用来替代任务管理器的,它实现了任务管理器的一切功能,并且又加入了许多有用的功能。今天我们要用到的就是其中的一个功能。

寻找切入点

当任务管理器显示“运行”对话框后,我们启动Process Explorer,双击其中的taskmgr.exe进程,在弹出的属性对话框中单击“Thread”选项卡,如果你正确配置了系统文件的调试符号,你会看到taskmgr.exe的各个线程。在“Start Address”一栏中,双击最上面的taskmgr.exe!ModuleEntry,会弹出一个关于这个线程的堆栈的对话框,查找此对话框,我们会看到这样两行:
SHELL32.dll!RunFileDlg+0xc4
taskmgr.exe!RunDlg+0x99
只所以会注意到这两行,是因为只有它们与运行(Run)对话框(Dialog,在Windows编程中常简称为Dlg)有关。这表明taskmgr.exe中的RunDlg函数调用了Shell32.dll中的RunFileDlg函数。由于各种程序(explorer.exe、taskmgr.exe,以及Process Explorer)所显示的“运行”对话框基本一样,所以我们有理由猜测它是由Windows自身实现的标准对话框。由上面堆栈中的两行内容我们可以初步猜测系统中提供的标准“运行”对话框可能是由Shell32.dll中的RunFileDlg函数实现的。如何验证我们的想法呢?首先,我们可以查看Shell32.dll的导出函数,看其中是否有RunFileDlg函数。令人遗憾的是,Shell32.dll并没有导出以这个名字命名的函数,但是它却导出了许多没有名字的函数,也就是说,只能以序号来调用这些函数。根据Microsoft向来好隐藏一些API函数这一点,我们可以猜测这个函数可能是仅由序号导出的。为了找出到底是如何调用这个函数的,读者当然可以调试这些程序,或者使用一些监视软件,但是我更喜欢使用SoftICE来设置断点。我们用Loader32载入Shell32.dll的调试符号,然后用“bpx RunFileDlg”来设置断点。但是在函数名还没有打完时,按TAB键让SoftICE将命令补全,它却提示“找不到匹配的符号”。由于Win32 API都是stdcall调用约定,而stdcall调用约定会改写函数名(在前面加上下划线,后面加上@符号以及此函数所需参数的总字节数。要详细了解函数调用习惯及其对函数名称与参数的影响,可以参考John Robbins的《Debugging Applications》的Power Debugging with x86 Assembly Language and the Visual C++ Debugger Disassembly Window一章),所以猜测这个函数的名称已经被改写,改用“bpx _RunFileDlg”来设置断点。在输入_RunF后按TAB键,SoftICE就自动将命令补全了。然后,单击“开始”菜单中的“运行”,SoftICE弹出,中断在函数_RunFileDlg的入口处。此时观察堆栈,发现最上面一行为shell32!_RunFileDlg。单击Process Explorer的“File”菜单下的“Run”,结果与上述情况完全一样。单击“任务管理器”中的“新建任务”,情况依然如此。这就充分说明了Shell32.dll中确实存在这个产生标准“运行”对话框的函数_RunFileDlg。由任务管理器中弹出的“运行”对话框的标题与图标与其它两个程序不同,这是不是表示这个对话框可以被定制呢?接下来的任务就是找出它的导出序号和各个参数及其功能了。

寻找导出序号

我们用IDA Pro来反汇编taskmgr.exe程序。载入调试符号后,观察其imports窗口。其中有这样一行:
010011C4 61  __imp__RunFileDlg@24             SHELL32
很明显,RunFileDlg函数的导出序号为61。要验证这一点,可以使用Visual Studio 2005自带的DUMPBIN实用工具。用下面的命令行:
    DUMPBIN /EXPORTS SHELL32.DLL | more
使用管道(|)和more可以让输出满屏后暂停,然后按空格滚动一屏,或者使用回车滚动一行(当然,读者也可以让输出重定向到一个文件再查看)。在其输出中可以看到这样一行:
61  00077077 [NONAME] _RunFileDlg@24
很明显,RunFileDlg函数的导出序号为61,并且带6个参数(Win32 API函数的每个参数均为4字节宽)。

判断参数类型及用途

现在的任务只剩下判断参数的类型和用途了。回过头来看taskmgr.exe的反汇编代码。在taskmgr.exe中,是它的RunDlg函数调用了Shell32.dll中的RunFileDlg函数。我们可以根据它对参数的赋值情况来判断RunFileDlg函数的各个参数。RunDlg函数的反汇编代码如下:
.text:01003110                 unsigned long __stdcall RunDlg(void) proc near
.text:01003110
.text:01003110                         var_61C= dword ptr -61Ch
.text:01003110                         var_414= dword ptr -414h
.text:01003110                         Buffer= word ptr -20Ch
.text:01003110                         var_4= dword ptr -4
.text:01003110
.text:01003110 8B FF                   mov     edi, edi
.text:01003112 55                      push    ebp
.text:01003113 8B EC                   mov     ebp, esp
.text:01003115 81 EC 1C 06 00 00       sub     esp, 61Ch
.text:0100311B A1 94 54 01 01          mov     eax, ___security_cookie
.text:01003120 53                      push    ebx
.text:01003121 6A 40                   push    40h      ; UINT
.text:01003123 6A 00                   push    0           ; int
.text:01003125 6A 00                   push    0           ; int
.text:01003127 6A 01                   push    1           ; UINT
.text:01003129 6A 6B                   push    6Bh         ; LPCWSTR
.text:0100312B FF 35 28 5E 01 01       push    HINSTANCE__ * g_hInstance ; HINSTANCE
.text:01003131 89 45 FC                mov     [ebp+var_4], eax
.text:01003134 FF 15 F0 11 00 01       call    ds:LoadImageW(x,x,x,x,x,x)
.text:0100313A 8B D8                   mov     ebx, eax
.text:0100313C 85 DB                   test    ebx, ebx
.text:0100313E 74 72                   jz      short loc_10031B2
.text:0100313E
.text:01003140 56                      push    esi
.text:01003141 57                      push    edi
.text:01003142 8B 3D BC 13 00 01       mov     edi, ds:LoadStringW(x,x,x,x)
.text:01003148 BE 04 01 00 00          mov     esi, 104h
.text:0100314D 56                      push    esi           ; nBufferMax
.text:0100314E 8D 85 F4 FD FF FF       lea     eax, [ebp+Buffer]
.text:01003154 50                      push    eax           ; lpBuffer
.text:01003155 68 11 27 00 00          push    2711h         ; uID
.text:0100315A FF 35 28 5E 01 01       push    HINSTANCE__ * g_hInstance ; hInstance
.text:01003160 FF D7                   call    edi ; LoadStringW(x,x,x,x)
.text:01003162 56                      push    esi           ; nBufferMax
.text:01003163 8D 85 EC FB FF FF       lea     eax, [ebp+var_414]
.text:01003169 50                      push    eax           ; lpBuffer
.text:0100316A 68 12 27 00 00          push    2712h         ; uID
.text:0100316F FF 35 28 5E 01 01       push    HINSTANCE__ * g_hInstance ; hInstance
.text:01003175 FF D7                   call    edi ; LoadStringW(x,x,x,x)
.text:01003177 8D 85 E4 F9 FF FF       lea     eax, [ebp+var_61C]
.text:0100317D 50                      push    eax           ; lpBuffer
.text:0100317E 56                      push    esi           ; nBufferLength
.text:0100317F FF 15 F0 10 00 01       call    ds:GetCurrentDirectoryW(x,x)
.text:01003185 6A 14                   push    14h
.text:01003187 8D 85 EC FB FF FF       lea     eax, [ebp+var_414]
.text:0100318D 50                      push    eax
.text:0100318E 8D 85 F4 FD FF FF       lea     eax, [ebp+Buffer]
.text:01003194 50                      push    eax
.text:01003195 8D 85 E4 F9 FF FF       lea     eax, [ebp+var_61C]
.text:0100319B 50                      push    eax
.text:0100319C 53                      push    ebx
.text:0100319D FF 35 1C 5E 01 01       push    HWND__ * g_hMainWnd
.text:010031A3 FF 15 C4 11 00 01       call    ds:RunFileDlg(x,x,x,x,x,x)
.text:010031A9 53                      push    ebx           ; hIcon
.text:010031AA FF 15 EC 11 00 01       call    ds:DestroyIcon(x)
.text:010031B0 5F                      pop     edi
.text:010031B1 5E                      pop     esi
.text:010031B1
.text:010031B2
.text:010031B2            loc_10031B2:      ; CODE XREF: RunDlg(void)+2E j
.text:010031B2 8B 4D FC                mov     ecx, [ebp+var_4]
.text:010031B5 33 C0                   xor     eax, eax
.text:010031B7 40                      inc     eax
.text:010031B8 5B                      pop     ebx
.text:010031B9 E8 1C EF 00 00          call    __security_check_cookie(x)
.text:010031B9
.text:010031BE C9                      leave
.text:010031BF C3                      retn
.text:010031BF
.text:010031BF                         unsigned long __stdcall RunDlg(void) endp

在这个函数中,除了prolog代码和epilog代码以及编译器生成的安全检查代码外,主体是对RunFileDlg这个函数的调用。下面我们来具体分析。

识别出第一个参数

我们先来看RunDlg函数在调用RunFileDlg函数之前向堆栈中都压入了哪些参数。压入的最后一个参数(也就是RunFileDlg函数的第一个参数)已经被IDA Pro识别出来了。根据匈牙利命名法,它应该是一个全局的窗口句柄(即HWND类型的变量)。很明显,这里应该是指任务管理器的主窗口句柄。也就是“运行”对话框的父窗口。为了验证这一点,现在来看Process Explorer的反汇编代码。在其中发现了如下代码:
.text:0041C780 6A 3D                   push    3Dh
.text:0041C782 68 38 56 48 00        push    offset s_Shell32_dll_0 ; "shell32.dll"
.text:0041C787 FF 15 1C 93 46 00       call    ds:LoadLibraryA
.text:0041C78D 50                      push    eax
.text:0041C78E FF 15 24 93 46 00       call    ds:GetProcAddress
.text:0041C794 85 C0                   test    eax, eax
.text:0041C796 A3 C0 FA 49 00          mov     dword_49FAC0, eax
.text:0041C79B 74 14                   jz      short loc_41C7B1
.text:0041C79B
.text:0041C79D 8B 4C 24 04             mov     ecx, [esp+4]
.text:0041C7A1 6A 00                   push    0
.text:0041C7A3 6A 00                   push    0
.text:0041C7A5 6A 00                   push    0
.text:0041C7A7 6A 00                   push    0
.text:0041C7A9 6A 00                   push    0
.text:0041C7AB 51                      push    ecx
.text:0041C7AC FF D0                   call    eax
.text:0041C7AE 33 C0                   xor     eax, eax
.text:0041C7B0 C3                      retn

在这段代码中,先用LoadLibrary函数加载Shell32.dll,获取其模块句柄。然后调用GetProcAddress函数获取Shell32.dll中某个导出函数的入口地址,注意到3Dh就是61,可以肯定它获取的是RunFileDlg函数的入口地址。再看最后对这个函数的调用,压入的参数除了最后一个外全为0。我们在taskmgr.exe的反汇编代码中已经知道这最后一个压入的参数可能为窗口句柄。既然其它参数都可以为0,我们先来实验一下。

插曲—我是如何找到上面那段代码的呢?首先在IDA Pro的Names窗口搜索字符串“run”,会看到有个字符串名称为s_Rundlg。根据IDA Pro的命名习惯,这个字符串是“Rundlg”。由前面我们得到的信息,可以猜测它与我们要找的“运行”对话框有关。双击此字符串,IDA Pro转到程序中此字符串的位置处。在此字符串右边的提示信息中,我们可以看到有三段代码使用了这个字符串。如下图所示。



联想到Process Explorer的“File”菜单中有三个不同的“Run”命令,猜测它们可能就对应着这三段代码。在提示信息的第一行上双击转到相应的代码处。但首先映入我们眼帘的是对DialogBoxParam函数的三次调用。而“RUNDLG”是这三个模式对话框的资源名称。很明显,Process Explorer是自己实现“运行”对话框的。既然这样,那为什么刚才我们在SoftICE中设置断点时它会被中断呢?向前滚动几行,我们就会看到前面的那段代码。原来,Process Explorer首先尝试获取Shell32.dll提供的标准“运行”对话框,如果失败,它就自己生成此对话框。而
jz      short loc_41C7B1
这条指令就是在获取系统的“运行”对话框失败时转到自己生成对话框的代码处。如果你仔细观察由系统生成的“运行”对话框与Process Explorer生成的“运行”对话框就会发现,前者中的组合框中始终都显示滚动条,即使不需要也显示为灰化状态,而后者中的组合框中在不需要时并不显示滚动条。

实验程序如下:

RunFileDlg1.cpp程序

#define WIN32_LEAN_AND_MEAN
#ifdef UNICODE
#undef UNICODE
#endif

#include <windows.h>

char szShell32[]="shell32.dll";

#pragma comment(linker, "/entry:main")

void __declspec(naked) main()
{
  __asm
  {
    xor eax, eax
    push eax
    push eax
    push eax
    push eax
    push eax    //以上是RunFileDlg函数的最后五个参数

    call DWORD PTR [GetDesktopWindow]
    push eax    //这是它的第一个参数

    mov eax, offset szShell32
    push eax
    call DWORD PTR [LoadLibrary]

    push 3Dh  
    push eax
    call DWORD PTR [GetProcAddress]
    call eax    //eax寄存器中即为RunFileDlg函数的入口地址
    call DWORD PTR [ExitProcess]
  }
}
编译后运行它,你会看到桌面左上角真的出现了标准的“运行”对话框!(笔者使用的是英文版的Windows XP SP2)如图1所示。


图1 RunFileDlg1运行结果

根据Windows命名习惯,可以将此参数命名为hwndParent。那现在要问,这个参数是否可以为NULL呢?我们将RunFileDlg1.cpp中的
call DWORD PTR [GetDesktopWindow]
这一句注释掉,重新编译运行,结果同上。这表明,这个函数的所有参数都可以为NULL。也就是说,只要给这个函数传递六个NULL作为参数,它就会显示一个Windows标准“运行”对话框!可能还有比较细心的读者会说,在你的例子中并没有对函数返回值进行检查,如果函数运行失败,而它的返回值刚好是NULL,那以上两种情况不就一样了吗?对于这样简单的程序,验证返回值的语句说不定比有用的语句还多,所以我没有对返回值进行验证。如果读者想确认,方法如下:单步跟踪到
call DWORD PTR [GetDesktopWindow]
这条指令执行完毕,此时观察EAX寄存器的值,在笔者的机器上,它的值为00010014。这时,打开Spy++,你会看到最上面一行显示的桌面的窗口句柄也为此值,如下图所示。

此证明笔者所言不虚。

识别第二个参数
现在再来看其它五个参数。在taskmgr.exe的反汇编代码中,倒数第二个被压入堆栈中的参数是ebx,分析前面ebx寄存器中值的变化,发现ebx中的值是调用LoadImageW函数的返回值。而LoadImageW函数的第三个参数为1,也就是IMAGE_ICON,实际上这个函数就是加载一个图标,ebx中就是此图标的句柄。此时再看其第二个参数。根据MSDN中的描述,很明显这是一个资源的序号。现在看第一个参数,IDA Pro已经认出它是一个全局的实例句柄。显然,这里指的是任务管理器的实例句柄。我们用资源查看工具(例如Visual Studio 2005)来看一下,很容易发现它就是任务管理器的图标ID。当你查看由任务管理器产生的“运行”对话框时,你会发现对话框左边的图标确实是任务管理器的图标。这说明,第二个参数是用来控制这个图标的,并且它是一个图标句柄(即HICON类型的变量)。再看RunFileDlg函数后面对DestroyIcon函数的调用,以ebx为参数,更证明了我们的推断。根据Windows命名习惯,可以将此参数命名为hIcon。

识别第三、四、五个参数

再来看对LoadString函数的第一次调用。此函数的第一个参数同样为任务管理器的实例句柄。第二个参数为一个字符串的ID。同样用资源查看工具,可以发现这个字符串为“Create New Task”,第三个参数eax用来保存这个字符串的地址。而eax中实际是一个局部变量的偏移地址(用ebp寻址这个变量,编译器一般为未使用栈帧指针省略—FPO优化的函数生成这样的代码)。也就是说,这个变量指向了这个字符串。从后面可以看出,这个局部变量作为RunFileDlg函数的第四个参数被压入堆栈。观察由任务管理器产生的“运行”对话框,你会发现此对话框的标题正是这个字符串。根据Windows命名习惯,我们可以将这个参数命名为lpszCaption。但是现在还不知道这个字符串可不可以是ANSI字符串(由taskmgr.exe中的用法知道它可以是UNICODE字符串)。
接下来看对LoadString函数的第二次调用。使用与上面相同的方法分析,可以知道RunFileDlg函数的第五个参数也是一个指向字符串的指针。在任务管理器它指向“Type the name of a program, folder, document, or Internet resource, and Windows will open it for you.”,这个字符串正是运行对话框中显示的说明文字。根据Windows命名习惯,可以将其命名为lpszText。与上面一样,我们现在还不知道它指向的字符串可不可以是ANSI类型的。
接着看对GetCurrentDirectory函数的调用。同样,它将获得的当前目录字符串放入一个局部变量中,然后将此局部变量的地址作为第三个参数传递给RunFileDlg函数。显然,这个参数是一个指向目录字符串的指针,但是现在还不知道它必须指向当前目录,还是可以指向其它目录。根据Windows命名习惯,将其命名为lpszDestDirectory。现在,同样不知道这个参数指向的字符串可不可以是ANSI类型的。
以上三个参数的不确定性可以通过实验来解决。

识别第六个参数

只剩下最后一个参数了。但是我们无法这样判断了。因为taskmgr.exe并未对这个参数进行操作,仅将14h作为这个参数的值压入堆栈。根据Windows编程经验,这很可能是一个标志位(Windows API中经常把标志作为最后一个参数,而在反汇编代码中,它就是一个常量)。现在只能通过反汇编Shell32.dll来寻找RunFileDlg函数是如何使用这一参数的。不幸的是,RunFileDlg函数并没有很明确地使用这个参数。所以我们最好根据实验来确定。根据Windows命名习惯,将此参数命名为dwFlags。

利用实验消除不确定性因素

下面用实验来具体确定各个参数的类型。
首先,我们来确定其中指向字符串的参数可不可以指向ANSI字符串。程序如下:

RunFileDlg2.cpp程序

#define  WIN32_LEAN_AND_MEAN
#ifndef UNICODE
#define UNICODE
#endif

#include <windows.h>
#include <tchar.h>

TCHAR szShell32[]=TEXT("shell32.dll");
char szCaption[]="测试/"运行/"对话框";
TCHAR szText[]=TEXT("这是一个对/"运行/"对话框进行实验的程序");
TCHAR szDestDirectory[MAX_PATH];

#pragma comment(linker, "/entry:main")

void __declspec(naked) main()
{
  __asm
  {
    xor eax, eax
    push eax    ;dwFlags

    mov eax, offset szText
    push eax

    mov eax, offset szCaption
    push eax

    mov eax, offset szDestDirectory
    push eax
    push MAX_PATH
    call DWORD PTR [GetCurrentDirectory]
    mov eax, offset szDestDirectory
    push eax    ;lpszCurrentDirectory

    push 32516    ;IDI_INFORMATION 的硬编码
    xor eax, eax
    push eax
    call DWORD PTR [LoadIcon]
    push eax    ;hIcon

    call DWORD PTR [GetDesktopWindow]
    push eax    ;hwndParent

    mov eax, offset szShell32
    push eax
    call DWORD PTR [LoadLibrary]

    push 3Dh
    push eax
    call DWORD PTR [GetProcAddress]

    call eax

    call DWORD PTR [ExitProcess]
  }
}
编译运行它,结果如图2所示。

图2 RunFileDlg2.cpp运行结果

注意到程序中,我给第四个参数lpszCaption传递的是一个指向ANSI字符串的指针。结果,“运行”对话框的标题变成了乱码。给第五个参数传递的是一个指向UNICODE字符串的指针,显示一切正常。而且图标也换成了指定的图标。大家还可以继续做其它实验,都可以证明这三个指向字符串的指针必须指向UNICODE字符串。
现在来看一下lpDestDirectory参数可否指向其它目录。程序如下:

RunFileDlg3.cpp程序

#define  WIN32_LEAN_AND_MEAN
#ifndef UNICODE
#define UNICODE
#endif

#include <windows.h>
#include <tchar.h>

TCHAR szShell32[]=TEXT("shell32.dll");
TCHAR szCaption[]=TEXT("测试/"运行/"对话框");
TCHAR szText[]=TEXT("这是一个对/"运行/"对话框进行实验的程序");
TCHAR szDestDirectory[]=TEXT("C://windows");

#pragma comment(linker, "/entry:main")

void __declspec(naked) main()
{
  __asm
  {
    xor eax, eax
    push eax    ;dwFlags

    mov eax, offset szText
    push eax

    mov eax, offset szCaption
    push eax

    mov eax, offset szDestDirectory
    push eax

    push 32516      ;IDI_INFORMATION 的硬编码
    xor eax, eax
    push eax
    call DWORD PTR [LoadIcon]
    push eax    ;hIcon

    call DWORD PTR [GetDesktopWindow]
    push eax    ;hwndParent

    mov eax, offset szShell32
    push eax
    call DWORD PTR [LoadLibrary]

    push 3Dh
    push eax
    call DWORD PTR [GetProcAddress]

    call eax

    call DWORD PTR [ExitProcess]
  }
}
在第二个实验中,如果我们在“运行”对话框中输入“CMD”,单击“确定”按钮,结果如下图所示。

在本次实验中,结果如下图所示:

即命令行解释器的当前目录已经被设置为我们指定的目录。这说明第三个参数可以为其它目录。
下面来确定最后一个参数。
Windows中的标志参数往往都是每个标志占一个位,最后把它们用位或组合起来。因此我们可以手工为这个参数赋值,以观察效果。
当为此参数赋值0时,结果如下图所示。


当为此参数赋值1时,结果如下图所示(不显示“浏览”按钮)。


当为此参数赋值2时,结果如下图所示(不显示最后一次输入的命令)。


当为此参数赋值3时,结果如下图所示(以上两种情况的组合)。


其它情况读者可以自行实验。可见,这个参数可能控制着“运行”对话框的外观。

结论

根据IDA Pro对Shell32.dll的反汇编结果可以看出,它的返回值为int类型,但是对于这个函数来说,返回值并不重要。

最后,我们得到RunFileDlg函数的原型为
int RunFileDlg( HWND hwndParent,
HICON hIcon,
LPWSTR lpszDestDirectory ,
LPWSTR lpszCaption,
LPWSTR lpszText,
DWORD dwFlags);
参数:
  hwndParent
“运行”对话框父窗口句柄,如果为NULL,则父窗口为桌面。
hIcon
  “运行”对话框中图标句柄,如果为NULL,则由系统提供默认图标。
    lpszDestDirectory
      设置要运行的程序的当前目录,如果为NULL,则设置为程序所在目录。
  lpszCaption
      指向“运行”对话框标题文本字符串,如果为NULL,则由系统提供默认标题。
  lpszText
      指向“运行”对话框内容文本字符串,如果为NULL,则由系统提供默认内容。
  dwFlags
在某些方面控制着“运行”对话框外观,如以上各图所示。如果为NULL,则显示系统默认的三个按钮。

它由Shell32.dll导出,导出序号为61,也就是3Dh。

在C语言中使用

现在根据以上原型,用C语言编写一个样例程序。如下所示。

RunFileDlg4.cpp文件

#define WIN32_LEAN_AND_MEAN
#ifndef UNICODE
#define UNICODE
#endif

#include <Windows.h>
#include "resource.h"

INT_PTR CALLBACK DialogProc(  HWND hwndDlg,
                  UINT uMsg,
                WPARAM wParam,
                LPARAM lParam
                );


typedef int (* pfnRunFileDlg)(HWND, HICON, LPWSTR, LPWSTR, LPWSTR, DWORD);

#pragma comment(linker, "/entry:main")

void ShowRunDlg(HWND hwndParent, DWORD dwFlags)
{
  pfnRunFileDlg pRunFileDlg=(pfnRunFileDlg)GetProcAddress(LoadLibrary(TEXT("Shell32.dll")), (LPCSTR)61);
  pRunFileDlg(hwndParent, LoadIcon(NULL, IDI_INFORMATION), TEXT("c://windows"), TEXT("测试/"运行/"对话框"), TEXT("这是一个/"运行/"对话框测试程序。"), dwFlags);
}

void main(void)
{
  DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOGTEST), NULL, DialogProc, NULL);

  ExitProcess(0);
}

INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch(uMsg)
  {
    case WM_INITDIALOG:
      return TRUE;

    case WM_COMMAND:
      switch(LOWORD(wParam))
      {
        case IDOK:
          ShowRunDlg(hwndDlg, 0);
          break;

        case IDCANCEL:
          EndDialog(hwndDlg, 0);  
          break;
      }
    break;

    case WM_CLOSE:
      EndDialog(hwndDlg, 0);
      break;
    
    default:
      return FALSE;
  }

  return TRUE;
}

RunFileDlg4.rc文件

#include "resource.h"

IDD_DIALOGTEST DIALOGEX 0, 0, 256, 122
STYLE DS_CENTER | DS_FIXEDSYS | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Windows运行对话框测试程序"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "运行(&R)...",IDOK,47,54,50,14
    PUSHBUTTON      "退出(&X)",IDCANCEL,158,54,50,14
END

resource.h文件

#include <windows.h>

#define IDD_DIALOGTEST    301

在编译上述文件时,记得使用stdcall调用约定(加上/Gz编译器选项)。运行结果如下图所示。


单击“运行”按钮,结果如下图所示。



说明

读者可能已经看到了,我在所有的示例程序中都使用了
#pragma comment(linker, "/entry:main")
这条链接器指令。因为Visual C++默认生成的程序要在调用你的入口函数前进行许多初始化操作,包括初始化一些全局变量、堆等,你可以直接在C语言中使用的argc和argv变量都是由它处理生成的。(详细信息可以参考Jeffrey Richter的《Programming Applications for Microsoft Windows,Fourth Edition》一书第四章Processes以及Microsoft Systems Journal—现在已合并到MSDN Magazine中Under the Hood专栏的部分文章)它将这些代码都静态链接到你的程序中(这也是你用C语言写一个Hello World程序也会很大的一个主要原因)。而在我们的程序中,我们只调用Win32 API,并不调用任何C/C++运行时库函数,也不使用任何它的全局变量,当然不需要它进行任何的初始化操作。这样,便于我们调试自己的发行版程序,而不是在由它生成的指令堆中挣扎了半天,还没有到我们自己写的代码中。

思考

在前面找RunFileDlg函数的导出序号时,我是从IDA Pro显示的taskmgr.exe的导入表中的
010011C4 61  __imp__RunFileDlg@24             SHELL32
这一行得到线索的。不知读者有没有注意到这样一个事实,那就是:Shell32.dll中的RunFileDlg函数并没有使用名称导出,在我们使用的导入库文件(Shell32.lib)中并没有RunFileDlg这个函数的相关导入信息(可以使用DUMPBIN工具来验证)。因此,在我们的程序中就不可能直接调用这个函数,即使你自己声明这个函数的原型。但是,Microsoft的程序taskmgr.exe中却有此函数的相关导入信息,也就是这个函数的导入序号3Dh。也就是说,taskmgr.exe中的这个函数是通过导入库引用的。这到底是为什么呢?是不是说明了Microsoft使用的导入库与他分发给我们使用的导入库不一样呢?我将这个问题留给读者思考
时序约束实战:从setup/hold到时钟路径约束的深度解析 本文深度解析数字设计中的时序约束实战,重点探讨如何从setup/hold检查的核心原理出发,理解并应用时钟路径约束。文章通过剖析Path Requirement的计算逻辑,结合同频、倍频及异步时钟域等典型场景,提供了从解读时序报告到编写、调试SDC约束的系统方法,帮助工程师有效应对复杂SoC设计中的时序收敛挑战。 阅读详情

相关推荐

ROS环境下UR机械臂与Realsense相机手眼标定实战指南

本文提供了在ROS环境下,对UR机械臂与Realsense相机进行手眼标定的完整实战指南。详细介绍了从环境搭建、软件包安装、硬件配置到分步数据采集与计算的完整流程,并分享了常见的避坑技巧与精度优化方法,旨在帮助开发者高效、准确地完成标定,为后续的机器人视觉应用奠定坚实基础。

sunny的博客 648

.rc file

// Microsoft Visual C++ generated resource script.//#include "resource.h"#define APSTUDIO_READONLY_SYMBOLS///////////////////////////////////////////////////////////////////////////////// Generated fr

donkey301's blog 706

Lumerical-定向耦合器1

概述:本文章使用FDE求解器对定向耦合器进行了数值仿真,讨论了耦合器的间距相关性,耦合长度相关性,波长相关性。讨论了脊形波导和条形波导两种情况。

m0_61776728的博客 4615

自动执行命令、window弹窗(golang练手小项目)

自动执行命令、window弹窗(golang练手小项目)

weixin_41673092的博客 1888

altium room 布局_ALtium 16相同重复模块PCB布局,复制布局走线

自己做了实际工程以后发现PCB器件名字后缀太长了,所以文章最后写了一下后缀的调整方法。第一种方法:1、建立子原理图如下,文件名CANM 2、之后新建一个顶层文件,文件名TOP,之后design—create sheet symbol form sheet or hdl。选择CANM,并放置到TOP中,如下 3、由于我们要多次调用CANM所以使用repeat命令修改如下: 4、之后根据自己的定义,连...

weixin_39609573的博客 5752

极简JEPA世界模型实战:低门槛复现LeWorldModel,CPU可跑

这次我们来看一个能让普通开发者真正上手跑起来的“世界模型”项目。如果你对Yann LeCun提出的JEPA(联合嵌入预测架构)框架感兴趣,但又苦于Meta官方实现过于庞大复杂,那么这个GitHub上4k star的项目就是为你准备的。它用极简的PyTorch代码(核心文件仅160-278行)完整复刻了包括LeWorldModel在内的五个JEPA核心变体,最大的亮点是: 门槛极低,普通笔记本甚至CPU就能跑,显存占用友好,非常适合学习和原理验证。 这个项目名为 keon/jepa ,完全开源。它剥离了工业级

weixin_29031161的博客 423

如何调用系统对话框

CString pszDllName="shell32.dll"; HINSTANCE hLib = ::LoadLibrary(pszDllName); char p[256]; HICON ico; //这儿我感觉参数应该是LPCWSTR, 但是网上说得是LPCTSTR typedef void (__stdcall *pRunFileDlg)(HWND,HICON, LPCTSTR

求知者 451

RK3588+YT9215快速拓展以太网口

YT9215支持7个端口的非阻塞数据传输,确保高效的数据流处理;在开发过程中,最重要的一点就是YT9215与RK3588的GMAC连接和配置部分,下面是gmac1对应dtsi文件的修改,包括注释掉rgmii_phy1对应的mdio1,以及gmac1对应的phy-handle部分,同时将gmac1修改成1000Mbps全双工模式。这就是最简单,最快速的RK3588拓展网口的方法了,如果需要用到交换芯片的其他功能,那就不可以直接使用原厂提供的bin,而是需要对YT9215进行另外的驱动开发。

带着耳朵取流浪的博客 2796

技术分享:经典内核漏洞调试笔记之二

  作者:k0pwn_ko   预估稿费:500RMB(不服你也来投稿啊!)   投稿方式:发送邮件至linwei#360,或登陆网页版在线投稿   传送门   经典内核漏洞调试笔记   前言   上一次我发了一篇自己在一个经典内核漏洞CVE-2014-4113中挣扎的经历,以及一些调试细节的分享。   总结过后感觉自己收获很多,后来一个偶然的机会,我看到了百度安全实验室发的一篇文章,是关于另一个经典的内核漏洞,也就是今天的主角----CVE-2022-2546这个漏洞的从补丁对比到E...

linjingyg的博客 395

Qt C++ + Qt 3D 的数字孪生工厂

以下是一个基于 **Qt C++ + Qt 3D** 的数字孪生工厂完整源码示例,集成了 **Qt 3D** 产线建模和 **WebSocket** 实时数据交互。ui->qualifiedProductsLabel->setText(QString("合格产品: %1").arg(data.qualifiedProducts));ui->totalProductsLabel->setText(QString("总产量: %1").arg(data.totalProducts));

寒寒豆豆软件公司的博客 149

ethernet eth0: Could not attach to PHY

整个流程看下来就是MAC connect PHY的过程失败了,失败的具体原因根据config_init,上图(bcm54xx_config_init) ,从此方向入手去定位原因;我这里的问题是has_gmac4没有置1,根据 has_gmac4: uses GMAC4 core.按照上面的调用逻辑,添加了部分打印,定位到__phy_read接口返回失败。stmmac_mdio_read 大部分都是linux的标准接口。最终取决于平台用的是哪家的phy,调用config_init。

无线互联&网络介绍 2704

IDA8.3 - make sig

有个reverseMe用VS2015 x64 release static MFC Dlg写的,IDA8.3只能识别一部分库函数。想做个VS2015的IDA sig库, 让IDA8.3加载sig库后再分析。

博客标题不能为空 2146

Autosar MCAL-S32K324 Fee配置-基于EB

Autosar MCAL S32K324 Fee配置详解

赞的博客 1546

一文搞懂如何创建基于对话框的模态对话框和非模态对话框

Windows应用程序工作的基本流程是从用户那里得到数据,经过相应的处理之后,再把处理结果输出到屏幕、打印机或者其他的输出设备上。那么,应用程序是如何从用户那里得到数据,并且再将修改后的数据显示给用户的呢?这就需要用到 Windows应用程序中一个很重要的用户接口 ——对话框。 对话框基本认识常用控件介绍对话框的种类模态对话框非模态对话框对话框的新建和显示模态对话框的创建非模态对话框的创建 对话框...

MMagicLoren的博客 5239

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Run 11:为Windows 11打造的现代运行对话框

Run 11:为Windows 11打造的现代运行对话框 项目介绍 Run 11 是一款专为Windows 11用户设计的现代运行对话框。它不仅继承了传统运行对话框的便捷功能,还融入了现代化的设计元素,为用户提供了更加直观、美观的操作体验。无论你是开发者、系统管理员,还是普通用户,Run 11都能让你在Windows 11系统中轻松执行命令,提升工作效率。 项目技术分析 Run 11采用了现代化的...

gitblog_00117的博客 1459

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

标准文件对话框的扩展使用方法

 1 引言在windows的程序设计中、为了界面的一致性、微软提供了一组标准的标准对话框,以此方便用户的操作使用,同时也减轻了程序设计人员的工作,使其不必浪费时间去做重复性的工作。但是,在实际的使用中,系统提供的标准对话框并不能满足用户的需要,于是,微软为此提供了让程序设计人员可以在标准对话框的基础上增加新的控件的功能。但这一功能是在SDK层上提供的,而如今大多数开发人员都不用SDK进行开

helloworldY的专栏 1180

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

如何让程序出现windows标准对话框(如:打开文件时的对话框)式样的帮助?

--------------------------------------------------------------------------如何让程序出现windows标准对话框(如:打开文件时的对话框)式样的帮助? --------------------------------------------------------------------------unit Unit1;

zhlwyy的专栏 1132

用 AutoIt v3 编写 Windows “运行”对话框历史记录清理工具

在这个万物皆可 Python 的时代,像 AutoIt v3 这样原生、高效、小而美的工具已经不多见了。它虽然现在不那么热了,但在处理 Windows 系统运维、UI 自动化和小工具快速开发上,依然有着不可替代的地位。

张赐荣的技术博客 265

如何隐藏显示在任务栏中的对话框程序

最近有个朋友做了一个基于对话框的小程序,大家知道,一般具有用户界面的 Windows 程序运行起来后,通常都会在任务栏里体现出来。我的这个朋友不想让她做的对话框程序运行的时候显示在任务栏里。问我如何隐藏?我参考了 MSDN 后告诉她说使用 WS_EX_TOOLWINDOW 扩展窗口式样。她按照我说的方法试了一下,结果没有成功。后来我琢磨了半天,发现这件事情并不像文档中说的那么简单。MSDN 里对 ...

weixin_34362991的博客 1404

windows 对话框创建及其标准控件的使用

非模态,模态对话框,按钮单选,多选,列表视图,滑轮,滚动条,复选框

ylh的博客 1280

“关于”对话框

软件的“关于”对话框实际上是一个模态窗体,主要用来向用户提供产品的一些特殊信息(比如版权、技术支持等)。虽然它不提供特殊的功能,但对于一套完整的商业软件来说是必不可缺的。 “关于” 对话框一般用来介绍一款软件的相关信息,例如版本号、版权以及作者等信息。通常情况下,对于微软公司的软件产品,其打开该对话框的菜单项位于“帮助”菜单下。如图为Windows 7操作系统中记事本的“关于”对话框。

ルーキーの殇 1180

MFC 通用对话框之“查找替换“对话框

CFindReplaceDialog类封装了查找/替换对话框,使用CFindReplaceDialog类可以,可以轻松地在应用程序中实现标准的字符串查找/替换对话框 。不像其他通用对话框是模态对话框,查找/替换对话框是非模态对话框。CFindReplaceDialog对象有两种:查找对话框和查找/替换对话框。尽管对话框允许用户输入搜索和搜索/替换字符串,但它们不执行任何搜索或替换函数。 您必须将它们添加到应用程序中。 要构造一个CFindReplaceDialog对象,使用提供的构造函数(它没有参数)。 由

m0_67392931的博客 1080

扩展windows的打开文件的对话框

版权声明请尊重原创作品。转载请保持文章完整性,并以超链接形式注明原始作者“tingsking18”和主站点地址,方便其他朋友提问和指正。 效果如下图: 调用打开文件对话框只需要调用windows API函数GetOpenFileName就可以了,上面的效果需要设置GetOpenFileName函数的参数LPOPENFILENAME lpofn   lpfnHook

我一路走来--- 2746

MFC 对话框

MFC 对话框,模态和非模态,动态创建按钮,控件访问等等

qq_61553520的博客 4106

通过 Windows CBT 钩子 API 实现修改 MessageBox 阻塞式模态对话框按钮的显示文字

在开发 Windows 软件时,经常会调用MessageBox函数,它是一个极其常用的阻塞式消息模态对话框。然而,系统默认只提供“确定”、“取消”、“是”、“否”等标准文本。如果想实现自定义按钮的显示文本,比如把“确定”/“取消”按钮改为“好的”或“不好”,通常的方法是自己写一个窗体。 但通过 Windows 挂钩(Hook)技术,开发者可以“拦截入侵”系统标准的 MessageBox,在其显示前的瞬间完成“整容”。 本文作者:张赐荣,视障者,信息无障碍解决方案资深研发专家。

张赐荣的技术博客 407

Native Windows GUI 对话框使用指南

Native Windows GUI 对话框使用指南 对话框概述 在 Native Windows GUI 框架中,对话框是预制的特殊窗口,主要用于获取用户输入值。与常规控件不同,对话框属于资源(Resources)而非控件(Controls),这意味着它们不需要父控件,也不能包含子控件。 框架提供了三种内置对话框类型,每种都通过特性(feature)开关控制: 文件对话框 (file-dial...

gitblog_00750的博客 528
上一篇: Windows加载器与模块初始化
下一篇: 一个http代理的代码
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值