分类

类型:
不限 游戏开发 计算机程序开发 Android开发 网站开发 笔记总结 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
原创:
不限
年份:
不限 2018 2019

技术文章列表

  • 动态导入DLL动态链接库并调用其导出函数

    背景DLL调用有两种方式,一种是静态调用,另一种是动态调用。对于静态调用主要是对开发环境进行设置,导入DLL所需的头文件和库文件,就可以直接调用DLL中导出的函数了,这种调用方式会使程序较大,占用内存较大,但速度较快,而且使用起来比较方便。
    但是,本文要介绍的是动态调用的方式,动态调用可以根据需要加载响应函数,随时可卸载。不会因为找不到dll,导致程序不能运行。但是,使用上相对于静态调用来说比较麻烦。
    现在,就把DLL动态调用及调用其导出函数的实现过程整理成文档,分享给大家。
    函数介绍LoadLibrary 函数
    将指定的模块加载到调用进程的地址空间中。 指定的模块可能会导致其他模块被加载。函数声明
    HMODULE WINAPI LoadLibrary( _In_ LPCTSTR lpFileName);
    参数

    lpFileName [in]
    模块的名称。这可以是库模块(.dll文件)或可执行模块(.exe文件)。指定的名称是模块的文件名,与库模块本身中存储的名称无关,由模块定义(.def)文件中的LIBRARY关键字指定。
    如果字符串指定完整路径,则该函数仅搜索该模块的该路径。
    如果字符串指定相对路径或没有路径的模块名称,则该函数使用标准搜索策略来查找模块;有关更多信息,请参阅备注。
    如果该功能找不到模块,该功能将失败。指定路径时,请确保使用反斜杠(\),而不是斜杠(/)。有关路径的更多信息,请参阅命名文件或目录。
    如果字符串指定了没有路径的模块名称,并且省略了文件扩展名,则该函数将默认库扩展名.dll附加到模块名称。要防止函数将.dll附加到模块名称,请在模块名称字符串中包含一个尾点字符(.)。

    返回值

    如果函数成功,则返回值是模块的句柄。如果函数失败,返回值为NULL。 要获取扩展错误信息,请调用GetLastError。

    GetProcAddress 函数
    从指定的动态链接库(DLL)检索导出的函数或变量的地址。
    函数声明
    FARPROC WINAPI GetProcAddress( _In_ HMODULE hModule, _In_ LPCSTR lpProcName);
    参数

    hModule [in]包含该函数或变量的DLL模块的句柄。 LoadLibrary,LoadLibraryEx,LoadPackagedLibrary或GetModuleHandle函数返回此句柄。GetProcAddress函数不会从使用LOAD_LIBRARY_AS_DATAFILE标志加载的模块中检索地址。 有关更多信息,请参阅LoadLibraryEx。lpProcName [in]函数或变量名,或函数的序数值。 如果此参数是序数值,则它必须在低位字中,高阶字必须为零。
    返回值

    如果函数成功,则返回值是导出函数或变量的地址。如果函数失败,返回值为NULL。 要获取扩展错误信息,请调用GetLastError。

    实现原理本文调用的 DLL 使用的是 《两种方式实现DLL导出函数之dllexport与def文件导出》文章中实现的 DLL。动态加载的过程如下:
    首先,我们使用 LoadLibrary 函数加载DLL到进程中,并获取 DLL 的加载句柄。
    /* 加载DLL */ HMODULE hDll = ::LoadLibrary("DllExport_Test.dll"); if (NULL == hDll) { ShowError("LoadLibrary"); }
    然后,根据DLL中的导出函数,声明导出函数指针。
    // 声明函数指针typedef BOOL(*typedef_MyExportFunc_def)(char *pszText, char *pszCaption);
    接着,调用 GetProcAddress 函数,根据导出函数的名称获取导出函数的地址。
    // 根据名称获取导出函数地址 typedef_MyExportFunc_def MyFunc1 = (typedef_MyExportFunc_def)::GetProcAddress(hDll, "MyExportFunc_def"); if (NULL == MyFunc1) { ShowError("GetProcAddress"); }
    同时,我们也可以根据导出函数的导出序号来获取导出函数的地址,使用如下所示,要注意第 2 个参数是序数值,则它必须在低位字中,高阶字必须为 0。
    // 根据导出序号获取导出函数地址 DWORD dwExportOrder = 1; typedef_MyExportFunc_def MyFunc2 = (typedef_MyExportFunc_def)::GetProcAddress(hDll, (LPCSTR)dwExportOrder); if (NULL == MyFunc2) { ShowError("GetProcAddress"); }
    然后,我们就可以直接调用导出函数了。
    /* 调用导出函数 */MyFunc1("This Is Export Function By Function Name!", "1");MyFunc1("This Is Export Function By Export Order!", "2");
    最后,当我们程序不在使用到 DLL 的时候,要对它进行卸载释放资源。
    /* 卸载DLL */ ::FreeLibrary(hDll);
    程序测试我们直接运行程序,则成功显示两个弹窗,第一个弹窗如下:

    第二个弹窗如下所示:

    总结对于上面的 GetProcAddress 从 DLL 获取导出函数的地址,它既可以根据导出函数的名称获取也可以根据导出函数的导出序号进行获取,要注意区分两种的使用形式,注意区别。
    1 留言 2018-11-07 11:06:39 奖励5点积分
  • 最简单的图片格式转换支持4种图片类型基于CImage实现

    背景图片文件我们常常在日常生活中都会使用,与我们的生活息息相关。你有没有想过,图片文件不同格式之间有什么差别吗?它们之间是如何进行转换的?好吧,老实说,我也没有想过。如果让你开发一个图片格式转换器,估计你也会觉得很难吧,毕竟不同图片类型的文件格式都不清楚是怎样的。
    如果,我告诉你有这么一种方法,你不需要了解任何的图片格式,而且三四行代码就可以实现图片格式转换了,你会相信吗?事实上,这是可以实现的,本文讲的就是这么一种最简单的方法。使用 CImage 类提供的接口实现,支持.PNG、.JPG、 .GIF、.BMP 等 4 种格式图片的转换。
    那么,我就把程序的实现原理和过程写成文档,分享给大家。
    实现原理先来介绍下 CImage 类,CImage类是ATL和MFC共用的一个类,其头文件为atlimage.h,主要用于图片文件的打开,显示与保存。这里需要注意的是,VC6.0版本不支持 CImage!
    使用 CImage 实现图片类型转换的原理是:

    首先,CImage::Load函数,根据图片文件路径,加载图片到 CImage 对象中
    然后,调用CImage::Save函数,实现将位图存储为图片文件,图片类型根据传入的保存文件名确定。支持.PNG、.JPG、.GIF、.BMP等 4 种格式图片的生成

    就这样,简单的两步操作,就可以将不同格式的图片转换成任意格式的图片文件了,支持.PNG、.JPG、.GIF、.BMP等 4 种格式文件的相互转换。
    编程实现头文件#include <atlimage.h>
    图片类型转换BOOL ConverPicture(){ CImage image; // 加载图片 image.Load("1.jpg"); // 保存图片 // bmp格式 image.Save("ConverPicture.bmp"); // png格式 image.Save("ConverPicture.png"); // jpg格式 image.Save("ConverPicture.jpg"); // gif格式 image.Save("ConverPicture.gif"); return TRUE;}
    程序测试直接运行程序,1.jpg 图片成功转换成.PNG、.JPG、.GIF、.BMP 4 种格式图片文件:

    总结这个程序原理很简单,但是要注意的是,这种方法不适用于VC6.0开发环境,因为VC6.0上不支持 CImage 类的使用。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-07 10:21:23 奖励5点积分
  • 使用AnimateWindow函数实现窗口显示关闭滚动和滑动特效

    背景我们在开发自己的程序的时候,应该都想把自己的程序加上酷炫的界面和酷炫的特效,让人眼前一亮吧。不管你想不想,反正,我一直都想这么做的,只不过是由于水平有限而已。
    在没有了解 AnimateWindow 这个API函数之前,我以为要实现一个窗口淡出淡入的特效是比较复杂的。但,在了解 AnimateWindow 函数之后才发现,原来就是那么一两行代码的问题。
    现在,我把使用 AnimateWindow 函数实现窗口淡出淡入效果的实现思路以及实现过程,写成文档分享给大家。
    函数介绍AnimateWindow 函数
    在显示与隐藏窗口时能产生特殊的效果。有两种类型的动画效果:滚动动画和滑动动画。
    函数声明
    BOOL AnimateWindow( HWND hWnd, DWORD dwTime, DWORD dwFlags);
    参数

    hWnd:指定产生动画的窗口的句柄。
    dwTime:指明动画持续的时间(以微秒计),完成一个动画的标准时间为200微秒。
    dwFags:指定动画类型。这个参数可以是一个或多个下列标志的组合。标志描述:




    VALUE
    MEANING




    AW_SLIDE
    使用滑动类型。缺省则为滚动动画类型。当使用AW_CENTER标志时,这个标志就被忽略


    AW_ACTIVATE
    激活窗口。在使用了AW_HIDE标志后不要使用这个标志


    AW_BLEND
    使用淡出效果。只有当hWnd为顶层窗口的时候才可以使用此标志


    AW_HIDE
    隐藏窗口,缺省则显示窗口


    AW_CENTER
    若使用了AW_HIDE标志,则使窗口向内重叠;若未使用AW_HIDE标志,则使窗口向外扩展


    AW_HOR_POSITIVE
    自左向右显示窗口。该标志可以在滚动动画和滑动动画中使用。当使用AW_CENTER标志时,该标志将被忽略


    AW_VER_POSITIVE
    自顶向下显示窗口。该标志可以在滚动动画和滑动动画中使用。当使用AW_CENTER标志时,该标志将被忽略


    AW_VER_NEGATIVE
    自下向上显示窗口。该标志可以在滚动动画和滑动动画中使用。当使用AW_CENTER标志时,该标志将被忽略


    AW_HOR_NEGATIVE
    自右向左显示窗口。该标志可以在滚动动画和滑动动画中使用。当使用AW_CENTER标志时,该标志将被忽略



    返回值

    如果函数成功,返回值为非零;
    如果函数失败,返回值为零。在下列情况下函数将失败:
    窗口使用了窗口边界;窗口已经可见仍要显示窗口;窗口已经隐藏仍要隐藏窗口。若想获得更多错误信息,请调用GetLastError函数。


    实现原理根据 AnimateWindow 函数的介绍,我们可以知道只要设置 AnimateWindow 函数中的参数,就可以实现窗口的特效效果。
    其中,最后一个参数,则指定了使用哪种特效。
    例如,本文给出的示例:
    滚动效果,自下向上显示窗口:
    ::AnimateWindow(hWnd, 3000, AW_VER_NEGATIVE);
    滑动效果,淡出:
    ::AnimateWindow(hWnd, 3000, AW_HIDE | AW_BLEND);
    编码实现BOOL CALLBACK ProgMainDlg(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam){ if (WM_INITDIALOG == uiMsg) { // 滚动 ::AnimateWindow(hWnd, 3000, AW_VER_NEGATIVE); } else if (WM_CLOSE == uiMsg) { // 滑动 ::AnimateWindow(hWnd, 3000, AW_HIDE | AW_BLEND); // 关闭窗口 ::EndDialog(hWnd, NULL); } return FALSE;}
    程序测试直接运行程序,窗口弹出时淡入,关闭窗口时淡出。所以,测试成功。由于特效是动态的,所以从截图上可能看不出什么变化,但是还是放 2 张截图。


    总结AnimateWindow 函数的使用不是很复杂,大家只要对照着函数介绍去正确使用参数就可以实现想要实现的效果了。
    1 留言 2018-11-07 10:15:31 奖励3点积分
  • 判断操作系统及指定进程是32位还是64位

    背景应该很多人开发程序的时候,都会遇到这样的一个功能需求,怎么判断一个进程是32位进程还是64位进程呢?通常,我们的解决方法是,调用IsWow64Process函数进行判断。本文介绍的也是这种方法。
    但是,本文的这种方法考虑的更加全面。因为,大都数网上的方法都只是调用IsWow64Process函数来进行判断,这只考虑了64位操作系统,却没有考虑过程序如果运行在32位操作系统,那么刚才的判断方法是否会出错!
    本文就完善使用IsWow64Process函数判断指定进程是64位还是32位,完美支持32位操作系统和64位操作系统。现在,把程序的实现思路和实现方法写成文档,分享给大家。
    函数介绍OpenProcess 函数
    打开一个已存在的进程对象,并返回进程的句柄。
    函数声明
    HANDLE OpenProcess( DWORD dwDesiredAccess, //渴望得到的访问权限(标志) BOOL bInheritHandle, // 是否继承句柄 DWORD dwProcessId // 进程标示符);
    参数

    dwDesiredAccess [in]访问进程对象。 此访问权限将针对进程的安全描述符进行检查。 此参数可以是一个或多个进程访问权限。如果调用者启用了SeDebugPrivilege权限,则无论安全描述符的内容如何,都会授予所请求的访问权限。bInheritHandle [in]如果此值为TRUE,则此进程创建的进程将继承该句柄。 否则,进程不会继承此句柄。dwProcessId [in]要打开的本地进程的标识符。
    返回值

    如果函数成功,则返回值是指定进程的打开句柄。如果函数失败,返回值为NULL。要获取扩展错误信息,请调用GetLastError。

    GetCurrentProcess 函数
    获取当前程序进程的句柄。
    函数声明
    HANDLE WINAPI GetCurrentProcess(void);
    参数

    无参数
    返回值

    返回当前程序进程的句柄。

    IsWow64Process 函数
    判断指定的进程是否在WOW64下运行。
    函数声明
    BOOL WINAPI IsWow64Process( _In_ HANDLE hProcess, _Out_ PBOOL Wow64Process);
    参数

    hProcess [in]过程的句柄。 句柄必须具有PROCESS_QUERY_INFORMATION或PROCESS_QUERY_LIMITED_INFORMATION权限。 有关更多信息,请参阅流程安全和访问权限。Wow64Process [out]指向如果进程在WOW64下运行时设置为TRUE的值的指针。 如果进程在32位Windows下运行,则该值设置为FALSE。 如果进程是在64位Windows下运行的64位应用程序,则该值也设置为FALSE。
    返回值

    如果函数成功,则返回值为非零值。如果函数失败,返回值为零。 要获取扩展错误信息,请调用GetLastError。

    实现原理先简单介绍下什么是WOW64,WOW64 (Windows-on-Windows 64-bit)是一个Windows操作系统的子系统,它为现有的 32 位应用程序提供了 32 位的模拟,可以使大多数 32 位应用程序在无需修改的情况下运行在 Windows 64 位版本上。也就是说,WOW64就是一个 64 位操作系统模拟 32 位操作系统的一个子系统,而且32位程序都运行在它上面。
    那么,WIN32 API函数 IsWow64Process,就是判断指定程序是否运行在 WOW64 模拟系统中。而且,根据文档可知:




    OS 32bits
    OS 64BITS




    32bits process
    FALSE
    TRUE


    64bits process
    ERROR
    FALSE



    上面意思是说:

    使用IsWow64Process在32位操作系统上获取32位进程的返回结果是FALSE
    使用IsWow64Process在64位操作系统上获取32位进程的返回结果是TRUE
    使用IsWow64Process在32位操作系统上获取64位进程的返回结果是ERROR,因为64位程序不能在32位系统上运行
    使用IsWow64Process在64位操作系统上获取64位进程的返回结果是FALSE

    也就是说,如果我们要写一个判断进程位数的程序,还要能在32位系统和64位系统上运行,那么就必须先判断操作系统的位数。
    判断操作系统位数的原理是:
    利用上述的结果,对于32位的进程,在32位系统上,IsWow64Process返回FALSE,而64位系统返回TRUE。而且,我们程序本身就是32位,只要我们获取程序自身的进程句柄,然后传入IsWow64Process函数中判断是否在仿真环境中,根据返回结果可以知道32位系统还是64位系统。
    那么,判断全系统上,进程的位数的原理是:

    程序自身是32位进程。首先,先判断操作操作系统的位数。若是32位操作系统,那么它上面的所有进程都是32位的
    若是64位进程,那么就打开指定进程获取进程句柄。然后将进程句柄传递给IsWow64Process函数,进行判断。若是返回TRUE,则说明进程运行在WOW64模拟环境中,是一个32位进程;否则,是64位进程

    编码实现判断操作系统的位数BOOL Is64BitsOperateSystem(){ // 判断当前计算机操作系统是32位操作系统还是64位操作系统 BOOL bWow64Process = FALSE; // 获取当前程序进程句柄 HANDLE hProcess = ::GetCurrentProcess(); if (NULL == hProcess) { ShowError("OpenProcess"); return bWow64Process; } // 判断进程是否处于WOW64仿真环境中 ::IsWow64Process(hProcess, &bWow64Process); return bWow64Process;}
    判断指定进程的位数BOOL Is64BitsProcess(DWORD dwProcessId){ // 判断64位系统下, 进程指定是32位还是64位 // 先判断计算机操作系统位数, 32位系统只能执行32位进程 if (FALSE == Is64BitsOperateSystem) { return FALSE; } BOOL bWow64Process = FALSE; // 打开进程, 获取进程句柄 HANDLE hProcess = NULL; hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (NULL == hProcess) { ShowError("OpenProcess"); return bWow64Process; } // 判断进程是否处于WOW64仿真环境中 ::IsWow64Process(hProcess, &bWow64Process); return bWow64Process;}
    程序测试在 main 函数中调用上述封装好的函数进行测试,main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ // 11360 --> 32位进程 if (Is64BitsProcess(11360)) { printf("32 bits\n"); } else { printf("64 bits\n"); } // 7400 --> 64位进程 if (Is64BitsProcess(7400)) { printf("32 bits\n"); } else { printf("64 bits\n"); } system("pause"); return 0;}
    测试结果
    测试的例子中,进程PID为11360的是32位程序,PDI为7400的是64位程序。
    运行程序,程序判断正确。

    总结很多人使用IsWow64Process来判断进程的位数,往往会忽略考虑32位系统的情况,而造成程序在32位系统上运行出错。所以,这一点一定要考虑全面。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-06 22:48:44 奖励5点积分
  • 获取指定进程的加载基址

    背景之前,自己写过一个进程内存分析的小程序,其中,就有一个功能是获取进程在内存中的加载基址。由于现在Windows系统引入了ASLR (Address Space Layout Randomization)机制,加载程序时候不再使用固定的基址加载。VS默认是开启基址随机化的,我们也可以设置它使用固定加载基址。至于什么是ASLR?或者ASLR有什么作用?本文就不深入探讨了,感兴趣的,可以自己私下了解了解。
    本文就是开发这样的一个小程序,使用两种方法来获取获取指定进程的加载基址。一种是使用进程模块快照方式,然后遍历加载模块并获取基址,另一种是直接调用WIN32 API函数EnumProcessModules,遍历加载模块基址。这两种方法本质上都是一样的。现在,我就把分析过程和实现方式写成文档,分享给大家。
    函数介绍CreateToolhelp32Snapshot 函数
    可以通过获取进程信息为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照。
    函数声明
    HANDLE WINAPI CreateToolhelp32Snapshot( DWORD dwFlags, DWORD th32ProcessID );
    参数

    dwFlags指定快照中包含的系统内容,这个参数能够使用下列数值(常量)中的一个或多个:



    VALUE
    MEANING




    TH32CS_INHERIT
    声明快照句柄是可继承的


    TH32CS_SNAPALL
    在快照中包含系统中所有的进程和线程


    TH32CS_SNAPHEAPLIST
    在快照中包含在th32ProcessID中指定的进程的所有的堆


    TH32CS_SNAPMODULE
    在快照中包含在th32ProcessID中指定的进程的所有的模块


    TH32CS_SNAPPROCESS
    在快照中包含系统中所有的进程


    TH32CS_SNAPTHREAD
    在快照中包含系统中所有的线程




    th32ProcessID指定将要快照的进程ID。如果该参数为0表示快照当前进程。该参数只有在设置了TH32CS_SNAPHEAPLIST或者TH32CS_SNAPMODULE后才有效,在其他情况下该参数被忽略,所有的进程都会被快照。
    返回值

    调用成功,返回快照的句柄;调用失败,返回INVALID_HANDLE_VALUE 。

    Module32First 和 Module32Next 函数当我们利用函数CreateToolhelp32Snapshot()获得指定进程的快照后,我们可以利用Module32First函数来获得进程第一个模块的句柄,Module32Next函数来获得进程下一个模块的句柄。
    OpenProcess 函数
    打开一个已存在的进程对象,并返回进程的句柄。
    函数声明
    HANDLE OpenProcess( DWORD dwDesiredAccess, //渴望得到的访问权限(标志) BOOL bInheritHandle, // 是否继承句柄 DWORD dwProcessId// 进程标示符);
    参数

    dwDesiredAccess [in]访问进程对象。 此访问权限将针对进程的安全描述符进行检查。 此参数可以是一个或多个进程访问权限。如果调用者启用了SeDebugPrivilege权限,则无论安全描述符的内容如何,都会授予所请求的访问权限。bInheritHandle [in]如果此值为TRUE,则此进程创建的进程将继承该句柄。 否则,进程不会继承此句柄。dwProcessId [in]要打开的本地进程的标识符。
    返回值

    如果函数成功,则返回值是指定进程的打开句柄。如果函数失败,返回值为NULL。要获取扩展错误信息,请调用GetLastError。

    EnumProcessModules 函数
    在指定的进程中检索每个模块的句柄。要控制64位应用程序是否枚举32位模块,64位模块或两种类型的模块,请使用EnumProcessModulesEx函数。
    函数声明
    BOOL WINAPI EnumProcessModules( _In_ HANDLE hProcess, _Out_ HMODULE *lphModule, _In_ DWORD cb, _Out_ LPDWORD lpcbNeeded);
    参数

    hProcess [in]过程的句柄。lphModule [out]接收模块句柄列表的数组。cb [in]lphModule数组的大小,以字节为单位。lpcbNeeded [out]将所有模块句柄存储在lphModule数组中所需的字节数。
    返回值

    如果函数成功,返回值不为零。如果函数失败,返回值为零。 要获取扩展错误信息,请调用GetLastError。

    实现思路该程序实现进程基址的主要原理是,遍历进程里的所有加载的模块,那么,第一个加载模块的加载基址就是该进程的加载基址。
    那么,对于进程模块加载基址的遍历就有两种两种方法。一种是根据进程模块快照获取,另一种市直接调用EnumProcessModules函数获取。现在,对这两种方法的原理分别进行介绍。
    使用进程模块快照的方式获取模块基址的原理是:

    首先,使用CreateToolhelp32Snapshot 函数获取指定进程的所有模块快照。然后,根据模块快照,使用Module32First 和 Module32Next 函数进行遍历快照,并获取快照信息。其中,就包括有模块的加载基址信息。第一个模块的加载基址便是该进程的加载基址。最后,关闭上面获取的快照的句柄。
    使用EnumProcessModules函数获取模块基址的原理:

    首先,我们需要使用OpenProcess函数打开指定进程并获取进程的句柄
    然后,根据进程句柄调用EnumProcessModules函数获取进程加载的所有模块的加载基址,并保存在数组中。那么,第一个模块的加载基址便是该进程的加载基址
    关闭打开的进程句柄

    编程实现获取指定进程模块快照的方式遍历进程模块PVOID GetProcessImageBase1(DWORD dwProcessId){ PVOID pProcessImageBase = NULL; MODULEENTRY32 me32 = { 0 }; me32.dwSize = sizeof(MODULEENTRY32); // 获取指定进程全部模块的快照 HANDLE hModuleSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId); if (INVALID_HANDLE_VALUE == hModuleSnap) { ShowError("CreateToolhelp32Snapshot"); return pProcessImageBase; } // 获取快照中第一条信息 BOOL bRet = ::Module32First(hModuleSnap, &me32); if (bRet) { // 获取加载基址 pProcessImageBase = (PVOID)me32.modBaseAddr; } // 关闭句柄 ::CloseHandle(hModuleSnap); return pProcessImageBase;}
    直接使用EnumProcessModules函数获取进程模块基址PVOID GetProcessImageBase2(DWORD dwProcessId){ PVOID pProcessImageBase = NULL; //打开进程, 获取进程句柄 HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (NULL == hProcess) { ShowError("OpenProcess"); return pProcessImageBase; } // 遍历进程模块, HMODULE hModule[100] = {0}; DWORD dwRet = 0; BOOL bRet = ::EnumProcessModules(hProcess, (HMODULE *)(hModule), sizeof(hModule), &dwRet); if (FALSE == bRet) { ::CloseHandle(hProcess); ShowError("EnumProcessModules"); return pProcessImageBase; } // 获取第一个模块加载基址 pProcessImageBase = hModule[0]; // 关闭句柄 ::CloseHandle(hProcess); return pProcessImageBase;}
    程序测试我们在 main 函数中调用上述封装的函数进行测试,main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ PVOID pProcessImageBase1 = NULL; PVOID pProcessImageBase2 = NULL; pProcessImageBase1 = GetProcessImageBase1(4500); pProcessImageBase2 = GetProcessImageBase2(4500); printf("pProcessImageBase1=0x%p\npProcessImageBase2=0x%p\n", pProcessImageBase1, pProcessImageBase2); system("pause"); return 0;}
    测试结果
    我们运行程序,程序执行成功,并显示两种方法获取的进程加载基址,而且获取结果都相同。

    然后,我们使用 Process Explorer 软件查看PID为 4500 的进程的加载基址,程序基址获取正确,程序测试成功。

    总结这两种方式,本质上都是一样的,只是遍历进程加载模块所使用的方法不相同。那么,进程加载的第一个模块的加载基址,便是进程的加载基址。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-06 22:45:42 奖励5点积分
  • 编程实现遍历进程遍历线程遍历进程加载模块

    背景在我们开发程序的时候,经常需要在程序中实现遍历进程、遍历线程和遍历进程加载模块等获取系统信息的操作。
    本文是基于Win32 API函数实现这三个常用操作,当然,方法肯定不止有一种。比如,我写的《在VS2013中编程使用WMI》这篇文章中,使用的是WMI去遍历进程的。
    现在,我把这个小程序实现的思路以及实现过程,写成文档分享给大家。
    函数介绍CreateToolhelp32Snapshot 函数
    可以通过获取进程信息为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照。
    函数声明
    HANDLE WINAPI CreateToolhelp32Snapshot( DWORD dwFlags, DWORD th32ProcessID );
    参数

    dwFlags指定快照中包含的系统内容,这个参数能够使用下列数值(常量)中的一个或多个:



    VALUE
    MEANING




    TH32CS_INHERIT
    声明快照句柄是可继承的


    TH32CS_SNAPALL
    在快照中包含系统中所有的进程和线程


    TH32CS_SNAPHEAPLIST
    在快照中包含在th32ProcessID中指定的进程的所有的堆


    TH32CS_SNAPMODULE
    在快照中包含在th32ProcessID中指定的进程的所有的模块


    TH32CS_SNAPPROCESS
    在快照中包含系统中所有的进程


    TH32CS_SNAPTHREAD
    在快照中包含系统中所有的线程




    th32ProcessID指定将要快照的进程ID。如果该参数为0表示快照当前进程。该参数只有在设置了TH32CS_SNAPHEAPLIST或者TH32CS_SNAPMODULE后才有效,在其他情况下该参数被忽略,所有的进程都会被快照。
    返回值

    调用成功,返回快照的句柄;调用失败,返回INVALID_HANDLE_VALUE 。

    Process32First 和 Process32Next 函数当我们利用函数CreateToolhelp32Snapshot()获得当前运行所有进程的快照后,我们可以利用Process32First函数来获得第一个进程的句柄,Process32Next函数来获得下一个进程的句柄。
    Thread32First 和 Thread32Next 函数当我们利用函数CreateToolhelp32Snapshot()获得当前运行所有线程的快照后,我们可以利用Thread32First函数来获得第一个线程的句柄,Thread32Next函数来获得下一个线程的句柄。
    Module32First 和 Module32Next 函数当我们利用函数CreateToolhelp32Snapshot()获得指定进程的快照后,我们可以利用Module32First函数来获得进程第一个模块的句柄,Module32Next函数来获得进程下一个模块的句柄。
    实现原理大家可以从上面的函数介绍可以看出,重点理解CreateToolhelp32Snapshot这个函数的操作。我们遍历进程、遍历线程以及遍历进程加载模块等3个操作,都是基于CreateToolhelp32Snapshot函数进行下一步实现的。
    对于遍历进程的实现原理是:

    首先,使用CreateToolhelp32Snapshot 函数获取所有进程的快照
    然后,根据进程快照,使用Process32First 和 Process32Next 函数进行遍历快照,并获取快照信息
    最后,关闭上面获取的快照的句柄

    对于遍历线程的实现原理是:

    首先,使用CreateToolhelp32Snapshot 函数获取所有线程的快照
    然后,根据线程快照,使用Thread32First 和 Thread32Next 函数进行遍历快照,并获取快照信息
    最后,关闭上面获取的快照的句柄

    对于遍历进程模块的实现原理是:

    首先,使用CreateToolhelp32Snapshot 函数获取指定进程的所有模块快照。
    然后,根据模块快照,使用Module32First 和 Module32Next 函数进行遍历快照,并获取快照信息
    最后,关闭上面获取的快照的句柄。

    程序实现遍历进程BOOL EnumProcess(){ PROCESSENTRY32 pe32 = { 0 }; pe32.dwSize = sizeof(PROCESSENTRY32); // 获取全部进程快照 HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (INVALID_HANDLE_VALUE == hProcessSnap) { ShowError("CreateToolhelp32Snapshot"); return FALSE; } // 获取快照中第一条信息 BOOL bRet = ::Process32First(hProcessSnap, &pe32); while (bRet) { // 显示 Process ID printf("[%d]\t", pe32.th32ProcessID); // 显示 进程名称 printf("[%s]\n", pe32.szExeFile); // 获取快照中下一条信息 bRet = ::Process32Next(hProcessSnap, &pe32); } // 关闭句柄 ::CloseHandle(hProcessSnap); return TRUE;}
    遍历线程BOOL EnumThread(){ THREADENTRY32 te32 = { 0 }; te32.dwSize = sizeof(THREADENTRY32); // 获取全部线程快照 HANDLE hThreadSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (INVALID_HANDLE_VALUE == hThreadSnap) { ShowError("CreateToolhelp32Snapshot"); return FALSE; } // 获取快照中第一条信息 BOOL bRet = ::Thread32First(hThreadSnap, &te32); while (bRet) { // 显示 Owner Process ID printf("[%d]\t", te32.th32OwnerProcessID); // 显示 Thread ID printf("[%d]\n", te32.th32ThreadID); // 获取快照中下一条信息 bRet = ::Thread32Next(hThreadSnap, &te32); } // 关闭句柄 ::CloseHandle(hThreadSnap); return TRUE;}
    遍历指定进程模块BOOL EnumProcessModule(DWORD dwProcessId){ MODULEENTRY32 me32 = { 0 }; me32.dwSize = sizeof(MODULEENTRY32); // 获取指定进程全部模块的快照 HANDLE hModuleSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId); if (INVALID_HANDLE_VALUE == hModuleSnap) { ShowError("CreateToolhelp32Snapshot"); return FALSE; } // 获取快照中第一条信息 BOOL bRet = ::Module32First(hModuleSnap, &me32); while (bRet) { // 显示 Process ID printf("[%d]\t", me32.th32ProcessID); // 显示 模块加载基址 printf("[0x%p]\t", me32.modBaseAddr); // 显示 模块名称 printf("[%s]\n", me32.szModule); // 获取快照中下一条信息 bRet = ::Module32Next(hModuleSnap, &me32); } // 关闭句柄 ::CloseHandle(hModuleSnap); return TRUE;}
    程序测试在 main 函数中调用上述封装好的函数,进行测试。main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ // 遍历进程 if (FALSE == EnumProcess()) { printf("Enum Process Error!\n"); } system("pause"); system("cls"); // 遍历线程 if (FALSE == EnumThread()) { printf("Enum Thread Error!\n"); } system("pause"); system("cls"); // 遍历指定进程模块 if (FALSE == EnumProcessModule(6876)) { printf("Enum Process Module Error!\n"); } system("pause"); return 0;}
    测试结果:
    运行程序,首先成功显示系统上所有进程的信息:

    然后,成功显示系统上所有线程的信息:

    最后,成功显示指定进程“6876”的所有模块信息:

    总结这个小程序看似功能比较多,感觉比较复杂,实际上,它们三者的实现方式和思路都是相同的。你只要会一个,那么这三个应该都会了,这便是举一反三啊。
    其中,这个小程序重点理解CreateToolhelp32Snapshot 函数的参数就好,对于快照的遍历,根据你获取的快照信息不同,调用的快照遍历函数也不同。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-06 22:23:53 奖励5点积分
  • URL分解之InternetCrackUrl函数

    背景近期使用WININET库写的一个数据上传、下载的客户端小程序,上传数据到网站服务器和从网站服务器下载数据到本地。由于,对WININET库部分用的比较少,所以,对这部分知识比较陌生。也遇到一些波折,其中一个就是URL的分解问题。
    起初,使用WININET库连接服务器并发送访问请求的时候,调用这两个API函数:InternetConnect 和 HttpOpenRequest,需要填写网站的信息。我知道URL是什么,但是需要从URL中手动提取信息,比较麻烦。
    后来了解到,原来WININET库专门提供了 InternetCrackUrl 函数来对URL进行分解,提取URL中的信息。现在,总结一下 InternetCrackUrl 的用法。
    函数介绍下面是 MSDN 2001 解释:
    Cracks a URL into its component parts.

    Syntax
    BOOL InternetCrackUrl( LPCTSTR lpszUrl, DWORD dwUrlLength, DWORD dwFlags, LPURL_COMPONENTS lpUrlComponents);
    Parameters

    lpszUrl
    [in] Pointer to a string that contains the canonical URL to crack.
    dwUrlLength
    [in] Unsigned long integer value that contains the length of the lpszUrl string in TCHAR, or zero if lpszUrl is an ASCIIZ string.
    dwFlags
    [in] Unsigned long integer value that contains the flags controlling the operation. This can be one of the following values:ICU_DECODEConverts encoded characters back to their normal form. This can be used only if the user provides buffers in the URL_COMPONENTS structure to copy the components into.ICU_ESCAPEConverts all escape sequences (%xx) to their corresponding characters. This can be used only if the user provides buffers in the URL_COMPONENTS structure to copy the components into.
    lpUrlComponents
    [in, out] Pointer to a URL_COMPONENTS structure that receives the URL components.

    Return Value
    Returns TRUE if the function succeeds, or FALSE otherwise. To get extended error information, call GetLastError.

    其中,URL_COMPONENTS 结构体的内容是:

    typedef struct { DWORD dwStructSize; LPTSTR lpszScheme; DWORD dwSchemeLength; INTERNET_SCHEME nScheme; LPTSTR lpszHostName; DWORD dwHostNameLength; INTERNET_PORT nPort; LPTSTR lpszUserName; DWORD dwUserNameLength; LPTSTR lpszPassword; DWORD dwPasswordLength; LPTSTR lpszUrlPath; DWORD dwUrlPathLength; LPTSTR lpszExtraInfo; DWORD dwExtraInfoLength;} URL_COMPONENTS, *LPURL_COMPONENTS;
    dwStructSize用于表明该结构体大小,一般我们都是传递sizeof(URL_COMPONENTS)。
    lpszSheme指向一段用于保存协议类型的内存空间。dwSchemeLength用于描述传入空间的大小(以TCHARS为单位的大小,下面其他空间大小描述字段都是以TCHARS单位)。
    lpHostName指向一段用于保存域名信息的内存空间。dwHostNameLength用于描述传入空间的大小。nPort用于接收端口号。lpszUserName和lpszPassword分别用于保存URL中携带的用户名和密码。lpszUrlPath指向保存URL的路径——不包含域名的一段内存空间。lpszExtraInfo指向保存URL中参数信息的一段内容空间。

    使用例子1. 导入WININET库#include <WinInet.h>#pragma comment(lib, "Wininet.lib")
    2. 调用 InternetCrackUrl 函数传入要分解的URL链接字符串,调用 InternetCrackUrl 函数分解,并输出分解结果。
    void CrackURL(char *pszURL){ URL_COMPONENTS uc = { 0 }; char szScheme[MAX_PATH] = { 0 }; char szHostName[MAX_PATH] = { 0 }; char szUserName[MAX_PATH] = { 0 }; char szPassword[MAX_PATH] = { 0 }; char szUrlPath[MAX_PATH] = { 0 }; char szExtraInfo[MAX_PATH] = { 0 }; // 初始化缓冲区 ::RtlZeroMemory(&uc, sizeof(uc)); ::RtlZeroMemory(szScheme, MAX_PATH); ::RtlZeroMemory(szHostName, MAX_PATH); ::RtlZeroMemory(szUserName, MAX_PATH); ::RtlZeroMemory(szPassword, MAX_PATH); ::RtlZeroMemory(szUrlPath, MAX_PATH); ::RtlZeroMemory(szExtraInfo, MAX_PATH); // 设置 URL_COMPONENTS 结构体数据 uc.dwStructSize = sizeof(uc); uc.lpszScheme = szScheme; uc.lpszHostName = szHostName; uc.lpszUserName = szUserName; uc.lpszPassword = szPassword; uc.lpszUrlPath = szUrlPath; uc.lpszExtraInfo = szExtraInfo; uc.dwSchemeLength = MAX_PATH; uc.dwHostNameLength = MAX_PATH; uc.dwUserNameLength = MAX_PATH; uc.dwPasswordLength = MAX_PATH; uc.dwUrlPathLength = MAX_PATH; uc.dwExtraInfoLength = MAX_PATH; // 调用函数 if (FALSE == ::InternetCrackUrl(pszURL, 0, 0, &uc)) { printf("Error[%d]\n", ::GetLastError()); } // 显示分解结果 printf("szScheme = %s\n", szScheme); printf("szHostName = %s\n", szHostName); printf("szUserName = %s\n", szUserName); printf("szPassword = %s\n", szPassword); printf("szUrlPath = %s\n", szUrlPath); printf("szExtraInfo = %s\n", szExtraInfo); printf("uc.nPort = %d\n", uc.nPort);}
    我们传入的URL为:http://www.demongan.com/upload.asp?file=520.zip 。主函数部分的代码为:
    int _tmain(int argc, _TCHAR* argv[]){ char szURL[] = "http://www.demongan.com/upload.asp?file=520.zip"; printf("URL: %s\n", szURL); CrackURL(szURL); printf("\n"); system("pause"); return 0;}
    3. 运行结果大家可以看到分解的结果显示,其中:
    协议类型szScheme为http;
    传输端口号uc.nPort为80;
    域名szHostName为 www.demongan.com ;
    用户名szUserName为空;
    用户密码szPassword为空;
    URL路径szUrlPath为/upload.asp;
    URL中的参数信息szExtraInfo为?file=520.zip。

    总结单单这个理解不是很难,主要是之前没有接触过这方面的知识,所以才会感到陌生,希望方便大家的参考吧。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-06 22:06:48 奖励3点积分
  • 修改ASP网站的文件传输大小的默认限制并对限制大小进行探索

    背景用Windows系统自带的WININET库,开发了一个基于HTTP和HTTPS协议传输的小程序客户端,可以文件或是数据传输到服务器上。本地测试服务器搭建的是ASP服务器。但在进行程序测试的时候,发现有的文件可以传输成功,有的文件则失败,而且传输成功的都是比较小的文件,都是几KB或是十几KB的文件。后来在网上查了相关信息后发现,原来ASP服务器会默认对上传的数据有大小的限制。
    经过修改,可以上传更大的文件了,后来,经过测试上传多个不同大小的文件,发现ASP上传文件大小的上限是30M左右。先把修改的过程和测试的过程,分享给大家。
    起因使用自己些的程序,上传不同大小的文件,发现大的文件一直传输不成功,返回的数据如下所示:

    经过查询,才知道原来ASP有个默认上传文件大小的限制,于是便手动修改ASP服务器的配置。
    修改ASP服务器上传文件大小的限制步骤打开“Internet 信息服务(IIS)管理器”页面,选中ASP服务器,接着在“功能视图”中选中并双击运行“ASP”。

    在“ASP”页面中,点击“限制属性”前面的“+”,展开“限制属性”,其中“最大请求实体主题限制”这一设置项就是限制了上传文件的大小的。默认值是“200000”字节,大约是200KB左右。所以,这也就是只能上传数据小的文件,对于一些大的文件上传失败的原因。

    将“最大请求实体主题限制”修改为“104857600”字节,也就是100M大小,然后点击右侧的“应用”,即完成修改。

    对ASP上传文件大小的探索修改完成后,分别使用上传小程序测试了上传不同大小的文件的结果,结果如下:



    测试文件大小
    测试结果




    10M
    成功


    26M
    成功


    34M
    失败


    50M
    失败



    所以,从上述测试数据,肤浅地认为:按上述方法修改,最多只能上传30M左右的数据大小。
    总结按照上述修改的方法,确实上限是30M左右。后来了解到,还有其他上传大文件限制的真正解决办法,和上文方法不同,可以解决上线30M的问题。由于,测试小程序能否成功上传数据的测试结果已经知道了,所以,上传多大我并不是很关心。所以,亲爱的读者,你如果看到这篇文章,你可以试着去查找下上传大文件限制的真正解决办法,本文就不深入了。
    1 留言 2018-11-06 21:52:44 奖励3点积分
显示 285 到 293 ,共 8 条
eject