分类

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

文章列表

  • 内联汇编之32位程序

    背景内联汇编是指在 C/C++ 代码中嵌入的汇编代码,与全部是汇编的汇编源文件不同,它们被嵌入到 C/C++ 的大环境中。内联汇编方式两个作用,一是程序的某些关键代码直接用汇编语言编写,可提高代码的执行效率;二是有些操作无法通过高级语言实现,或者实现起来很困难,必须借助汇编语言达到目的。
    32 位程序和 64 位程序下使用内联汇编的方式,有很大的差别。现在,我们对此分别进行介绍。本篇文章主要介绍的是在 32 位程序中使用内联汇编。
    实现过程使用内联汇编要用到 __asm 关键字,它可以出现在任何允许 C/C++ 语句出现的地方。对 __asm 关键字的使用有两种方式:

    __asm 块,要添加大括号
    __asm { // 汇编代码 }
    __asm 语句,在每条汇编指令之前加 __asm 关键字
    __asm // 汇编代码

    显然,第一种方法与 C/C++ 的风格很一致,并且把汇编代码和 C/C++ 代码清楚地分开,还避免了重复输入 __asm 关键字,因此推荐使用第一种方法。
    不像在 C/C++ 中的“{ }”,__asm 块的“{ }”不会影响 C/C++ 变量的作用范围。同时,__asm 块可以嵌套,而且嵌套也不会影响变量的作用范围。
    为了与低版本的 Visual C++ 兼容,_asm 和 __asm 具有相同的意义。另外,Visual C++ 支持标准 C++ 的 asm 关键字,但是它不会生成任何指令,它的作用仅限于使编译器不会出现编译错误。要使用内联汇编,必须使用 __asm 而不是 asm 关键字。
    编码实现void MyFunc(char *pszText){ printf("%s\n", pszText);}int _tmain(int argc, _TCHAR* argv[]){ char str1[] = "__asm{ }"; char str2[] = "__asm"; // 32位程序内联汇编 第一种方式 __asm { lea eax, str1 push eax mov eax, MyFunc call eax } // 32位程序内联汇编 第二种方式 __asm lea eax, str2 __asm push eax __asm mov eax, MyFunc __asm call eax system("pause"); return 0;}
    程序测试我们直接运行程序,程序成功显示两行字符串,说明两种方式的内联汇编均使用成功。

    总结在 32 位程序中使用内联汇编比较方便,就两种形式,一种是有大括号的:
    __asm{ // 汇编代码}
    另一种是没有大括号的:
    __asm // 汇编代码
    这两种方式是等价的,大家可以根据自己的程序需要,自行选择使用哪种方式即可。
    参考参考自《Windows黑客编程技术详解》一书
    2 回答 2019-01-03 10:50:01
  • EXE加载模拟器直接在内存中加载运行EXE不通过API创建进程运行

    背景在网上搜索了很多病毒木马的分析报告,看了一段时间后,发现还是有很多病毒木马都能够模拟PE加载器,把DLL或者是EXE等PE文件,直接从内存中直接加载到自己的内存中执行,不需要通过API函数去操作,以此躲过一些杀软的检测。
    在看到这些技术的描述后,虽然没有详细的实现思路,但是凭借自己的知识积累,我也大概知道是怎么做了。后来,就自己动手写了这么一个程序,实现了从内存中直接加载并运行EXE,不需要通过API函数创建另一个进程启动该EXE。暂时还没有想清楚这种技术有什么积极的一面,不管了,既然都把程序写出来了,那就当作是对PE结构以及编程水平的一次锻炼吧
    现在,把实现的思路和实现过程,写成文档,分享给大家。
    程序实现原理要想完全理解透彻这个程序的技术,需要对PE文件格式有比较详细的了解才行,起码要了解PE格式的导入表、导出表以及重定位表的具体操作过程。
    这个程序和 “DLL加载模拟器直接在内存中加载DLL不通过API加载” 这篇文章原理是一样的,只是一个是EXE,一个是DLL,本质上都是PE文件加载模拟器。
    EXE加载到内存的过程并运行实现原理
    首先,在EXE文件中,根据PE结构格式获取其加载映像的大小SizeOfImage,并根据SizeOfImage在自己的程序中申请一块可读、可写、可执行的内存,那么这块内存的首地址就是EXE程序的加载基址
    然后,根据EXE中的PE结构格式获取其映像对齐大小SectionAlignment,然后把EXE文件数据按照SectionAlignment对齐大小拷贝到上述申请的可读、可写、可执行的内存中
    接着,根据PE结构的重定位表,重新对重定位表进行修正
    接着,根据PE结构的导入表,加载所需的DLL,并获取导入表导入函数的地址并写入导入表中
    接着,修改EXE的加载基址ImageBase
    最后,根据PE结构获取EXE的入口地址AddressOfEntryPoint,然后跳转到入口地址处继续执行

    这样,EXE就成功加载到程序中并运行起来了。要注意的一个问题就是,并不是所有的EXE都有重定位表,对于没有重定位表的EXE程序,那就不适用于本文介绍的方法。
    编码实现// 模拟PE加载器加载内存EXE文件到进程中// lpData: 内存EXE文件数据的基址// dwSize: 内存EXE文件的内存大小// 返回值: 内存EXE加载到进程的加载基址LPVOID MmRunExe(LPVOID lpData, DWORD dwSize){ LPVOID lpBaseAddress = NULL; // 获取镜像大小 DWORD dwSizeOfImage = GetSizeOfImage(lpData); // 在进程中开辟一个可读、可写、可执行的内存块 lpBaseAddress = ::VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (NULL == lpBaseAddress) { ShowError("VirtualAlloc"); return NULL; } ::RtlZeroMemory(lpBaseAddress, dwSizeOfImage); // 将内存PE数据按SectionAlignment大小对齐映射到进程内存中 if (FALSE == MmMapFile(lpData, lpBaseAddress)) { ShowError("MmMapFile"); return NULL; } // 修改PE文件重定位表信息 if (FALSE == DoRelocationTable(lpBaseAddress)) { ShowError("DoRelocationTable"); return NULL; } // 填写PE文件导入表信息 if (FALSE == DoImportTable(lpBaseAddress)) { ShowError("DoImportTable"); return NULL; } //修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。 //统一设置成一个属性PAGE_EXECUTE_READWRITE DWORD dwOldProtect = 0; if (FALSE == ::VirtualProtect(lpBaseAddress, dwSizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect)) { ShowError("VirtualProtect"); return NULL; } // 修改PE文件加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase if (FALSE == SetImageBase(lpBaseAddress)) { ShowError("SetImageBase"); return NULL; } // 跳转到PE的入口点处执行, 函数地址即为PE文件的入口点IMAGE_NT_HEADERS.OptionalHeader.AddressOfEntryPoint if (FALSE == CallExeEntry(lpBaseAddress)) { ShowError("CallExeEntry"); return NULL; } return lpBaseAddress;}
    程序测试在 main 函数中调用上述封装好的函数,加载EXE程序进行测试。main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ char szFileName[] = "KuaiZip_Setup_2.8.28.8.exe"; // 打开EXE文件并获取EXE文件大小 HANDLE hFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL); if (INVALID_HANDLE_VALUE == hFile) { ShowError("CreateFile"); return 1; } DWORD dwFileSize = GetFileSize(hFile, NULL); // 申请动态内存并读取DLL到内存中 BYTE *pData = new BYTE[dwFileSize]; if (NULL == pData) { ShowError("new"); return 2; } DWORD dwRet = 0; ReadFile(hFile, pData, dwFileSize, &dwRet, NULL); CloseHandle(hFile); // 判断有无重定位表 if (FALSE == IsExistRelocationTable(pData)) { printf("[FALSE] IsExistRelocationTable\n"); system("pause"); return 0; } // 将内存DLL加载到程序中 LPVOID lpBaseAddress = MmRunExe(pData, dwFileSize); if (NULL == lpBaseAddress) { ShowError("MmRunExe"); return 3; } system("pause"); return 0;}
    测试结果:
    运行程序后,成功显示“快压”安装程序的对话框界面,而且还显示了“快压”安装程序正在向 “i.kpzip.com” 使用HTTP发送“GET”数据请求:

    所以,程序测试成功。
    总结这个程序你只要熟悉PE格式结构的话,这个程序理解起来会比较容易。其中,需要特别注意的一点是:并不是所有的EXE都是用于本文介绍的方法。因为,对于那些没有重定位表的EXE程序来说,它们加载运行时的加载基址只能是固定的,因为没有重定位表的缘故,所以无法重定位数据,势必不能成功运行程序。同时,对一些MFC程序也不支持,目前正在改进当中。如果遇到不成功的,那就多换几个有重定位表的EXE试试就好。
    参考参考自《Windows黑客编程技术详解》一书
    8 回答 2019-01-02 09:47:41
  • DLL加载模拟器直接在内存中加载DLL不通过API加载

    背景在网上搜索了很多病毒木马的分析报告,看了一段时间后,发现还是有很多病毒木马都能够模拟PE加载器,把DLL或者是EXE等PE文件,直接从内存中直接加载到自己的内存中执行,不需要通过API函数去操作,以此躲过一些杀软的检测。
    在看到这些技术的描述后,虽然没有详细的实现思路,但是凭借自己的知识积累,我也大概知道是怎么做了。后来,就自己动手写了这么一个程序,实现了从内存中直接加载DLL,并获取DLL的导出函数,调用并执行。当然,这种技术并非没有积极的一面。如果你的程序需要很多DLL文件进行动态调用,你完全可以把这些DLL作为资源插入到自己的程序中,然后使用这个内存加载运行DLL的技术,直接在内存中加载运行就好,不需要再将DLL释放到本地上。这样做的好处应该不言而喻了,就是你的程序会因此变得很整洁,只有一个EXE程序,其他的DLL均包含在EXE程序里了。
    现在,把实现的思路和实现过程,写成文档,分享给大家。
    程序实现原理要想完全理解透彻这个程序的技术,需要对PE文件格式有比较详细的了解才行,起码要了解PE格式的导入表、导出表以及重定位表的具体操作过程。
    1. DLL加载到内存的过程实现原理
    首先,从DLL文件中,根据PE结构格式获取其加载映像的大小SizeOfImage,并根据SizeOfImage在自己的程序中申请一块可读、可写、可执行的内存,那么这块内存的首地址就是DLL的加载基址
    然后,根据DLL中的PE结构格式获取其映像对齐大小SectionAlignment,然后把DLL文件数据按照SectionAlignment对齐大小拷贝到上述申请的可读、可写、可执行的内存中
    接着,根据PE结构的重定位表,重新对重定位表进行修正
    接着,根据PE结构的导入表,加载所需的DLL,并获取导入表导入函数的地址并写入导入表中
    接着,修改DLL的加载基址ImageBase
    最后,根据PE结构获取DLL的入口地址,然后构造并调用 DllMain 函数,实现DLL的加载

    2. 获取DLL导出函数的实现过程
    首先,根据DLL的加载基址以及PE结构,获取DLL导出表
    然后,获取导出表的信息,如NumberOfNames、AddressOfNames等信息
    接着,遍历导出表的导出函数名字列表,与请求获取的导出函数名称进行匹配
    匹配成功,则获取导出函数的地址,并返回

    3. DLL的释放DLL的释放就比较好理解了,就是直接释放模拟加载DLL时候申请的可读、可写、可执行的内存即可。
    编码实现DLL加载过程函数// 模拟LoadLibrary加载内存DLL文件到进程中// lpData: 内存DLL文件数据的基址// dwSize: 内存DLL文件的内存大小// 返回值: 内存DLL加载到进程的加载基址LPVOID MmLoadLibrary(LPVOID lpData, DWORD dwSize){ LPVOID lpBaseAddress = NULL; // 获取镜像大小 DWORD dwSizeOfImage = GetSizeOfImage(lpData); // 在进程中开辟一个可读、可写、可执行的内存块 lpBaseAddress = ::VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (NULL == lpBaseAddress) { ShowError("VirtualAlloc"); return NULL; } ::RtlZeroMemory(lpBaseAddress, dwSizeOfImage); // 将内存DLL数据按SectionAlignment大小对齐映射到进程内存中 if (FALSE == MmMapFile(lpData, lpBaseAddress)) { ShowError("MmMapFile"); return NULL; } // 修改PE文件重定位表信息 if(FALSE == DoRelocationTable(lpBaseAddress)) { ShowError("DoRelocationTable"); return NULL; } // 填写PE文件导入表信息 if (FALSE == DoImportTable(lpBaseAddress)) { ShowError("DoImportTable"); return NULL; } //修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。 //统一设置成一个属性PAGE_EXECUTE_READWRITE DWORD dwOldProtect = 0; if (FALSE == ::VirtualProtect(lpBaseAddress, dwSizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect)) { ShowError("VirtualProtect"); return NULL; } // 修改PE文件加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase if (FALSE == SetImageBase(lpBaseAddress)) { ShowError("SetImageBase"); return NULL; } // 调用DLL的入口函数DllMain,函数地址即为PE文件的入口点IMAGE_NT_HEADERS.OptionalHeader.AddressOfEntryPoint if (FALSE == CallDllMain(lpBaseAddress)) { ShowError("CallDllMain"); return NULL; } return lpBaseAddress;}
    获取DLL导出函数// 模拟GetProcAddress获取内存DLL的导出函数// lpBaseAddress: 内存DLL文件加载到进程中的加载基址// lpszFuncName: 导出函数的名字// 返回值: 返回导出函数的的地址LPVOID MmGetProcAddress(LPVOID lpBaseAddress, PCHAR lpszFuncName){ LPVOID lpFunc = NULL; // 获取导出表 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress; PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew); PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); // 获取导出表的数据 PDWORD lpAddressOfNamesArray = (PDWORD)((DWORD)pDosHeader + pExportTable->AddressOfNames); PCHAR lpFuncName = NULL; PWORD lpAddressOfNameOrdinalsArray = (PWORD)((DWORD)pDosHeader + pExportTable->AddressOfNameOrdinals); WORD wHint = 0; PDWORD lpAddressOfFunctionsArray = (PDWORD)((DWORD)pDosHeader + pExportTable->AddressOfFunctions); DWORD dwNumberOfNames = pExportTable->NumberOfNames; DWORD i = 0; // 遍历导出表的导出函数的名称, 并进行匹配 for (i = 0; i < dwNumberOfNames; i++) { lpFuncName = (PCHAR)((DWORD)pDosHeader + lpAddressOfNamesArray[i]); if (0 == ::lstrcmpi(lpFuncName, lpszFuncName)) { // 获取导出函数地址 wHint = lpAddressOfNameOrdinalsArray[i]; lpFunc = (LPVOID)((DWORD)pDosHeader + lpAddressOfFunctionsArray[wHint]); break; } } return lpFunc;}
    DLL释放// 释放从内存加载的DLL到进程内存的空间// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址// 返回值: 成功返回TRUE,否则返回FALSEBOOL MmFreeLibrary(LPVOID lpBaseAddress){ BOOL bRet = FALSE; if (NULL == lpBaseAddress) { return bRet; } bRet = ::VirtualFree(lpBaseAddress, 0, MEM_RELEASE); lpBaseAddress = NULL; return bRet;}
    程序测试我们编写一个用来测试的DLL程序TestDll,导出函数的代码如下所示:
    BOOL ShowMessage(char *lpszText, char *lpszCaption){ ::MessageBox(NULL, lpszText, lpszCaption, MB_OK); return TRUE;}
    在 main 函数中,调用上述封装好的函数接口进行测试。main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ char szFileName[MAX_PATH] = "TestDll.dll"; // 打开DLL文件并获取DLL文件大小 HANDLE hFile = ::CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL); if (INVALID_HANDLE_VALUE == hFile) { ShowError("CreateFile"); return 1; } DWORD dwFileSize = ::GetFileSize(hFile, NULL); // 申请动态内存并读取DLL到内存中 BYTE *lpData = new BYTE[dwFileSize]; if (NULL == lpData) { ShowError("new"); return 2; } DWORD dwRet = 0; ::ReadFile(hFile, lpData, dwFileSize, &dwRet, NULL); // 将内存DLL加载到程序中 LPVOID lpBaseAddress = MmLoadLibrary(lpData, dwFileSize); if (NULL == lpBaseAddress) { ShowError("MmLoadLibrary"); return 3; } printf("DLL加载成功\n"); // 获取DLL导出函数并调用 typedef BOOL(*typedef_ShowMessage)(char *lpszText, char *lpszCaption); typedef_ShowMessage ShowMessage = (typedef_ShowMessage)MmGetProcAddress(lpBaseAddress, "ShowMessage"); if (NULL == ShowMessage) { ShowError("MmGetProcAddress"); return 4; } ShowMessage("CDIY - www.coderdiy.com - 专注计算机技术交流分享\n", "CDIY"); // 释放从内存加载的DLL BOOL bRet = MmFreeLibrary(lpBaseAddress); if (FALSE == bRet) { ShowError("MmFreeLirbary"); } // 释放 delete[] lpData; lpData = NULL; ::CloseHandle(hFile); system("pause"); return 0;}
    测试结果:
    直接成功弹窗!

    所以,DLL文件直接在内存中成功被加载并执行。
    总结这个程序对于初学者来说,理解起来比较复杂。但是,你只要熟悉PE格式结构的话,这个程序理解起来会比较容易。上面讲解中,对于重定位表、导入表以及导出表部分的具体操作并没有细讲,如果你没有了解PE结构,那么理解起来会有困难。如果你了解过PE结构,那么那部分知识应该也不用细讲的。
    2 回答 2019-01-01 13:24:04
  • 劫持ZwQuerySystemInformation函数实现进程隐藏

    背景所谓的进程隐藏,通俗地说指的是某个进程正常工作,不受任何影响,但是,我们使用类似任务管理器、Process Explorer 等进程查看软件查看进程,却看不到这个进程。适合秘密在计算机后台进行操作的程序,而不想让人发现。
    本文讲解的就是实现这样的一个进程隐藏程序的原理和过程,当然,进程隐藏的方法有很多,例如 DLL 劫持、DLL注入、代码注入、进程内存替换、HOOK API 等等。我们本文要介绍的就是 HOOK API 函数 ZwQuerySystemInformation 实现的隐藏指定进程。现在,我就把程序的实现过程整理成文档,分享给大家。
    函数介绍ZwQuerySystemInformation 函数
    获取指定的系统信息。
    函数声明
    NTSTATUS WINAPI ZwQuerySystemInformation( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength);
    参数

    SystemInformationClass [in]要检索的系统信息的类型。 该参数可以是SYSTEM_INFORMATION_CLASS枚举类型中的以下值之一。
    SystemInformation[in,out]指向缓冲区的指针,用于接收请求的信息。 该信息的大小和结构取决于SystemInformationClass参数的值,如下表所示。
    SystemInformationLength [in]SystemInformation参数指向的缓冲区的大小(以字节为单位)。
    ReturnLength [out]
    一个可选的指针,指向函数写入所请求信息的实际大小的位置。 如果该大小小于或等于SystemInformationLength参数,则该函数将该信息复制到SystemInformation缓冲区中; 否则返回一个NTSTATUS错误代码,并以ReturnLength返回接收所请求信息所需的缓冲区大小。

    返回值

    返回NTSTATUS成功或错误代码。NTSTATUS错误代码的形式和意义在DDK中提供的Ntstatus.h头文件中列出,并在DDK文档中进行了说明。
    注意

    ZwQuerySystemInformation函数及其返回的结构在操作系统内部,并可能从一个版本的Windows更改为另一个版本。 为了保持应用程序的兼容性,最好使用前面提到的替代功能。如果您使用ZwQuerySystemInformation,请通过运行时动态链接访问该函数。 如果功能已被更改或从操作系统中删除,这将使您的代码有机会正常响应。 但签名变更可能无法检测。此功能没有关联的导入库。 您必须使用LoadLibrary和GetProcAddress函数动态链接到Ntdll.dll。

    实现原理首先,先来讲解下为什么 HOOK ZwQuerySystemInformation 函数就可以实现指定进程隐藏。是因为我们遍历进程通常是调用系统 WIN32 API 函数 EnumProcess 、CreateToolhelp32Snapshot 等函数来实现,这些 WIN32 API 它们内部最终是通过调用 ZwQuerySystemInformation 这个函数实现的获取进程列表信息。所以,我们只要 HOOK ZwQuerySystemInformation 函数,对它获取的进程列表信息进行修改,把有我们要隐藏的进程信息从中去掉,那么 ZwQuerySystemInformation 就返回了我们修改后的信息,其它程序获取这个被修的信息后,自然获取不到我们隐藏的进程,这样,指定进程就被隐藏起来了。
    其中,我们将HOOK ZwQuerySystemInformation 函数的代码部分写在 DLL 工程中,原因是我们要实现的是隐藏指定进程,而不是单单在自己的进程内隐藏指定进程。写成 DLL 文件,可以方便我们将 DLL 文件注入到其它进程的空间,从而 HOOK 其它进程空间中的 ZwQuerySystemInformation 函数,这样,就实现了在其它进程空间中也看不到指定进程了。
    我们选取 DLL 注入的方法是设置全局钩子,这样就可以快速简单地将指定 DLL 注入到所有的进程空间里了。
    其中,HOOK API 使用的是自己写的 Inline Hook,即在 32 位程序下修改函数入口前 5 个字节,跳转到我们的新的替代函数;对于 64 位程序,修改函数入口前 12 字节,跳转到我们的新的替代函数。
    编码实现HOOK ZwQuerySystemInformationvoid HookApi(){ // 获取 ntdll.dll 的加载基址, 若没有则返回 HMODULE hDll = ::GetModuleHandle("ntdll.dll"); if (NULL == hDll) { return; } // 获取 ZwQuerySystemInformation 函数地址 typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation"); if (NULL == ZwQuerySystemInformation) { return; } // 32 位下修改前 5 字节, 64 位下修改前 12 字节#ifndef _WIN64 // jmp New_ZwQuerySystemInformation // 机器码位:e9 _dwOffset(跳转偏移) // addr1 --> jmp _dwNewAddress指令的下一条指令的地址,即eip的值 // addr2 --> 跳转地址的值,即_dwNewAddress的值 // 跳转偏移 _dwOffset = addr2 - addr1 BYTE pData[5] = { 0xe9, 0, 0, 0, 0}; DWORD dwOffset = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5; ::RtlCopyMemory(&pData[1], &dwOffset, sizeof(dwOffset)); // 保存前 5 字节数据 ::RtlCopyMemory(g_OldData32, ZwQuerySystemInformation, sizeof(pData));#else // mov rax,0x1122334455667788 // jmp rax // 机器码是: // 48 b8 8877665544332211 // ff e0 BYTE pData[12] = {0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0}; ULONGLONG ullOffset = (ULONGLONG)New_ZwQuerySystemInformation; ::RtlCopyMemory(&pData[2], &ullOffset, sizeof(ullOffset)); // 保存前 12 字节数据 ::RtlCopyMemory(g_OldData64, ZwQuerySystemInformation, sizeof(pData));#endif // 设置页面的保护属性为 可读、可写、可执行 DWORD dwOldProtect = 0; ::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect); // 修改 ::RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData)); // 还原页面保护属性 ::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);}
    UNHOOK ZwQuerySystemInformationvoid UnhookApi(){ // 获取 ntdll.dll 的加载基址, 若没有则返回 HMODULE hDll = ::GetModuleHandle("ntdll.dll"); if (NULL == hDll) { return; } // 获取 ZwQuerySystemInformation 函数地址 typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation"); if (NULL == ZwQuerySystemInformation) { return; } // 设置页面的保护属性为 可读、可写、可执行 DWORD dwOldProtect = 0; ::VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect); // 32 位下还原前 5 字节, 64 位下还原前 12 字节#ifndef _WIN64 // 还原 ::RtlCopyMemory(ZwQuerySystemInformation, g_OldData32, sizeof(g_OldData32));#else // 还原 ::RtlCopyMemory(ZwQuerySystemInformation, g_OldData64, sizeof(g_OldData64));#endif // 还原页面保护属性 ::VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);}
    New_ZwQuerySystemInformation 函数NTSTATUS New_ZwQuerySystemInformation( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength ){ NTSTATUS status = 0; PSYSTEM_PROCESS_INFORMATION pCur = NULL, pPrev = NULL; // 要隐藏的进程PID DWORD dwHideProcessId = 1224; // UNHOOK API UnhookApi(); // 获取 ntdll.dll 的加载基址, 若没有则返回 HMODULE hDll = ::GetModuleHandle("ntdll.dll"); if (NULL == hDll) { return status; } // 获取 ZwQuerySystemInformation 函数地址 typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation"); if (NULL == ZwQuerySystemInformation) { return status; } // 调用原函数 ZwQuerySystemInformation status = ZwQuerySystemInformation(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength); if (NT_SUCCESS(status) && 5 == SystemInformationClass) { pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation; while (TRUE) { // 判断是否是要隐藏的进程PID if (dwHideProcessId == (DWORD)pCur->UniqueProcessId) { if (0 == pCur->NextEntryOffset) { pPrev->NextEntryOffset = 0; } else { pPrev->NextEntryOffset = pPrev->NextEntryOffset + pCur->NextEntryOffset; } } else { pPrev = pCur; } if (0 == pCur->NextEntryOffset) { break; } pCur = (PSYSTEM_PROCESS_INFORMATION)((BYTE *)pCur + pCur->NextEntryOffset); } } // HOOK API HookApi(); return status;}
    设置全局消息钩子注入DLLint _tmain(int argc, _TCHAR* argv[]){ // 加载DLL并获取句柄 HMODULE hDll = ::LoadLibrary("HideProcess_ZwQuerySystemInformation_Test.dll"); if (NULL == hDll) { printf("%s error[%d]\n", "LoadLibrary", ::GetLastError()); } printf("Load Library OK.\n"); // 设置全局钩子 g_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, hDll, 0); if (NULL == g_hHook) { printf("%s error[%d]\n", "SetWindowsHookEx", ::GetLastError()); } printf("Set Windows Hook OK.\n"); system("pause"); // 卸载全局钩子 if (FALSE == ::UnhookWindowsHookEx(g_hHook)) { printf("%s error[%d]\n", "UnhookWindowsHookE", ::GetLastError()); } printf("Unhook Windows Hook OK.\n"); // 卸载DLL ::FreeLibrary(hDll); system("pause"); return 0;}
    程序测试我们运行将要隐藏进程的程序 520.exe,然后打开任务管理器,可以查看到 520.exe 是处于可见状态。接着,以管理员权限运行我们的程序,设置全局消息钩子,将 DLL 注入到所有的进程中,DLL 便在 DllMain 入口点函数处 HOOK ZwQuerySystemInformation 函数,成功隐藏 520.exe 的进程。所以,测试成功。
    总结要注意 Inline Hook API 的时候,在 32 位系统和 64 位系统下的差别。
    在 32 位使用 jmp _NewAddress 跳转语句,机器码是 5 字节,而且要注意理解它的跳转偏移的计算方式:
    跳转偏移 = 跳转地址 - 下一跳指令的地址
    在 64 位使用的是的汇编指令是:
    mov rax, _NewAddressjmp rax
    机器码是 12 字节。
    在Windows7 32位旗舰版以及Windows10 64位专业版上进行测试,均能成功隐藏指定进程,程序在32位和64位全平台系统均能正常工作。要注意一点就是,建议以管理员身份运行程序,否则我们的全局钩子不能成功注入到一些高权限的进程中。
    参考参考自《Windows黑客编程技术详解》一书
    1 回答 2018-12-31 16:06:36
  • 使用ADO方式连接并操作SQL数据库Access数据库等常用数据库

    背景对于数据库的操作使用,对于我们编程开发来说,是比较常见的事情,也是常用的技术。所以,应该要熟悉掌握。对于数据库的操作,基本操作就是增、删、改、查。但是,在进行这些基本操作之前,还有至关重要的一步,就是数据库的连接。对于数据库的成功连接,那么,我们对数据库的操作就完成一半了。很多初学者,都会卡死在数据库连接这一步上面。
    本文介绍的是ADO方式连接数据库,并操作数据库。ADO(ActiveX Data Object)具有跨系统平台特性,它直接对DBMS数据库进行操作,即系统中必须有DBMS,但不需要驱动程序,不需要注册数据源,所以具有很好的可移植性。
    本文就给出ADO方式连接并操作 SQL Server数据、Access数据库、Oracle数据库、MySQL数据库等常用数据库,虽然有很多数据库,但是它们之间对于ADO来说,只是连接字符串的区别而已。
    现在,我就把程序实现的过程整理成文档,分享给大家。
    实现原理ADO对象的导入在使用ADO技术时需要导入一个ADO动态链接库msado15.dll,该动态库位于系统盘下的”Program Files\Common Files\System\ado\”目录下。然后,我们在程序头文件中,添加下面的导入代码:
    #import "C:\\Program Files\\common files\\system\\ado\\msado15.dll" no_namespace rename("EOF","adoEOF")
    数据库连接我们在操作数据库之前,首先要连接数据库。数据库连接至关重要一点就是,数据库连接字符串。现在,我们先来介绍下ADO连接数据库的一个流程:

    首先,我们先调用 CoInitialize 初始化COM组件环境,因为ADO方式连接数据库,就是基于COM组件实现的。所以,必须要对COM环境进行初始化
    然后,调用 _ConnectionPtr::CreateInstance 函数创建 Connection 对象
    创建成功后,对 Connection 对象的连接超时ConnectionTimeout进行设置,同时调用 Open 函数,按照数据库连接字符串连接数据库

    经过,这 3 步操作,就成功完成数据库连接的操作。我们上面说,不同数据库,连接字符串也会不同。下面,我就列举常用数据库的连接字符串:
    Access连接字符串
    Provider=Microsoft.Jet.OLEDB.4.0;Data Source=MDB文件路径;Persist Security Info=False;Jet OLEDB:DataBase Password=数据库密码
    数据源连接字符串
    "DSN=TestDatabase;UID=;PWD=;"
    SQL Server连接字符串
    Driver=SQL Server;Server=服务器IP;Database=数据库名称;UID=用户名;PWD=密码
    Oracle连接字符串
    Provider=MSDAORA.1; Password=sa123; User ID=system; Data Source=192.168.0.221/orcl; Persist Security Info=True
    MySQL连接字符串
    Driver=MySQL ODBC 5.2 ANSI Driver;SERVER=192.168.0.221;UID=用户名;PWD=密码;DATABASE=test;PORT=端口(默认填写3306)
    // 以ADO方式连接数据库BOOL ADOConnectDatabase(_bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password){ // 初始化COM对象 ::CoInitialize(NULL); try { // 创建Connection对象 HRESULT hr = g_pConnection.CreateInstance("ADODB.Connection"); if (SUCCEEDED(hr)) { // 连接超时时间 5 秒 g_pConnection->ConnectionTimeout = 5; // 连接数据库 g_pConnection->Open(ConnectionString, UserID, Password, adModeUnknown); return TRUE; } } catch (_com_error e) { ::MessageBox(NULL, e.Description(), e.ErrorMessage(), MB_OK); } return FALSE;}
    执行非查询操作的SQL语句
    首先,我们调用 _ConnectionPtr::BeginTrans 函数,开始事务
    然后,调用 _ConnectionPtr::Execute 函数执行SQL语句,这里可以提交多条SQL语句。在调用 Execute 函数的时候,数据库还没有执行SQL语句,此时SQL语句还没有生效
    最后,我们调用 _ConnectionPtr::CommitTrans 函数提交事务,这时所提交的SQL语句开始按提交顺序执行。如果出错,则调用 _ConnectionPtr::RollbackTrans 函数回滚并结束事务

    // 执行操作SQL语句BOOL ExecuteSQL(char *pszSQL){ _variant_t ra; // 开始事务 g_pConnection->BeginTrans(); try { // 执行SQL语句 g_pConnection->Execute((_bstr_t)pszSQL, &ra, adCmdText); // 提交事务 g_pConnection->CommitTrans(); return TRUE; } catch (_com_error e) { ::MessageBox(NULL, e.Description(), e.ErrorMessage(), MB_OK); } // 如果出现错误,回滚并结束事务 g_pConnection->RollbackTrans(); return FALSE;}
    执行查询操作的SQL语句查询SQL语句与其它操作的SQL语句不一样,它不是使用 _ConnectionPtr 对象进程操作的,而是使用记录集对象 _RecordsetPtr 来进行实现。

    首先,我们调用 _RecordsetPtr::CreateInstance 函数创建并初始化记录集对象
    然后,_RecordsetPtr::Open 函数打开记录集并执行查询SQL语句,将查询结果,返回到记录集中

    // 执行查询SQL语句BOOL SearchSQL(char *pszSQL){ // 初始化记录集对象 g_pRecordset.CreateInstance(_uuidof(Recordset)); // 打开记录集 g_pRecordset->Open((LPCTSTR)pszSQL, g_pConnection.GetInterfacePtr(), adOpenDynamic, adLockOptimistic, adCmdText); if (NULL == g_pRecordset) { ::MessageBox(NULL, "读取数据库记录出错", "ERROR", MB_OK); return FALSE; } return TRUE;}
    程序测试我们在 main 函数中调用上述封装好的函数,连接数据库,执行创建demongan数据库表的SQL语句,执行向demongan表插入5条数据的SQL语句,执行查询demongan表所有数据并显示在程序上。main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ BOOL bRet = FALSE; char szSQL[MAX_PATH] = {0}; // 连接数据库 bRet = ADOConnectDatabase("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb", "", ""); if (FALSE == bRet) { printf("Connect Database Error.\n"); } printf("Connect Database OK.\n"); // 执行SQL语句,创建表 demongan ::wsprintf(szSQL, "CREATE TABLE demongan(ID int, Name varchar(20), Age int)"); bRet = ExecuteSQL(szSQL); if (FALSE == bRet) { printf("Create Table Error.\n"); } printf("Create Table OK.\n"); // 执行SQL语句,插入 5 条记录 for (int i = 0; i < 5; i++) { ::wsprintf(szSQL, "INSERT INTO demongan(ID, Name, Age) VALUES(%d, \'%s%d\', %d)", i, "Name", i, i + 1); bRet = ExecuteSQL(szSQL); if (FALSE == bRet) { printf("Insert Value Error.\n"); } } printf("Insert Value OK.\n"); // 查询数据 ::wsprintf(szSQL, "SELECT * FROM demongan"); bRet = SearchSQL(szSQL); if (FALSE == bRet) { printf("Search Value Error.\n"); } printf("Search Value OK.\n"); // 从记录集中获取数据并显示 _variant_t varID, varName, varAge; while (!g_pRecordset->adoEOF) { // 获取每个字段对应的数据 varID = g_pRecordset->GetCollect("ID"); varName = g_pRecordset->GetCollect("Name"); varAge = g_pRecordset->GetCollect("Age"); // 注意要强制转换下显示类型 printf("%s\t%s\t%s\n", (LPCTSTR)_bstr_t(varID), (LPCTSTR)_bstr_t(varName), (LPCTSTR)_bstr_t(varAge)); // 获取下一行数据 g_pRecordset->MoveNext(); } system("pause"); return 0;}
    我们直接运行程序,程序提示运行成功,成功连接数据库、创建表、插入数据、查询数据并显示查询结果:

    我们打开数据库文件,直接查看,数据成功被插入:

    总结数据库操作要注意 3 个关键点:

    一是与数据库的连接,要注意连接字符串一定要写正确,不同类型的数据库,连接字符串也会不同
    二是执行数据库的增加、删除、修改等除查询操作之外的SQL语句,先要开始事务,待所有SQL语句提交完毕后,再一次提交事务,交由数据库操作
    三是执行数据库的查询语句,查询结果,需要从记录集中一条一条循环获取,要注意循环结束的条件

    其中,在显示从数据集获取的数据的时候,我们要对数据进行(LPCTSTR)强制转化你,例如 (LPCTSTR)_bstr_t(varName),否则数据不能正常显示。
    参考参考自《Windows黑客编程技术详解》一书
    1 回答 2018-12-31 09:58:59
  • 使用VS2013创建并操作SQLite数据库

    背景很早就听说过SQLite数据库了,但是自己一直都没有去接触它。一天,群友在Q群里提问有没有人使用VS写过关于SQLite数据库的例子。霎时间,我知道自己是时候要与SQLite邂逅了。

    SQLite 是一个软件库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite 是在世界上最广泛部署的 SQL 数据库引擎,而且源代码不受版权限制。

    本文就是要实现这样的一个小程序,使用VS2013加载SQLite数据库的库文件,并实现使用SQL语句新建表、对表插入数据并查询数据的功能。现在,我就把实现过程整理成文档,分享给大家。
    使用VS2013编译SQLite数据库的库文件在使用 SQLite 数据库之前,我们需要到 SQLite官网 上下载SQLite数据库的源码文件以及二进制文件。本文演示使用的下载文件是“sqlite-amalgamation-3190300.zi p” 和 “sqlite-dll-win32-x86-3190300.zip”。
    编译库文件首先,我们先解压二进制压缩文件 “sqlite-dll-win32-x86-3190300.zip”,解压后目录下有两个文件,分别是 “sqlite3.dll” 和 “sqlite3.def”,现在,我们需要使用VS2013 来帮助编译得到 .lib 库文件。编译过程就是在命令行CMD下输入:
    "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\lib.exe" /MACHINE:IX86 /DEF:C:\Users\DemonGan\Desktop\sqlite-dll-win32-x86-3190300\SQLite3.def /OUT:C:\Users\DemonGan\Desktop\sqlite-dll-win32-x86-3190300\SQLite3.lib其中,”C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\lib.exe”就是你的VS2013自带的 lib.exe 程序路径;C:\Users\DemonGan\Desktop\sqlite-dll-win32-x86-3190300\SQLite3.def就是解压文件目录中SQLite3.def的路径;C:\Users\DemonGan\Desktop\sqlite-dll-win32-x86-3190300\SQLite3.lib就是保存生成的库文件SQLite.lib的输出路径。
    这样,我们就在目录下生成了“SQLite3.lib”库文件。

    向VS2013工程中导入SQLite库文件配置这时,我们继续解压“sqlite-amalgamation-3190300.zip”,将解压目录下的 “sqlite3.h” 头文件和 “SQLite3.lib” 库文件一起拷贝到工程目录下。然后,在工程中添加头文件和库文件:
    #include "sqlite3.h"#pragma comment(lib, "sqlite3.lib")
    这样,就可以在项目工程中,使用SQLite数据库了。
    实现过程首先,我们使用 sqlite3_open 函数根据数据库文件名称创建 SQLite 数据库。sqlite3_open 函数的第 1 个参数表示要创建的数据库名称,第 2 个参数获取数据库创建成功后的数据库句柄。
    // 打开数据库,创建连接 int iRet = sqlite3_open(szFileName, &conn); if (SQLITE_OK != iRet) { ShowError("sqlite3_open"); return 1; }
    然后,我们就可以直接调用 sqlite3_exec 函数执行SQL语句,来对数据库进行操作。其中, sqlite3_exec 函数的第 1 个参数表示数据库的句柄;第 2 个参数表示SQL语句;第 3 个参数表示回调函数,每成功执行一次SQL语句就执行一次回调函数;第 4 个参数表示回调函数返回的数据信息。
    现在,我们执行SQL语句 “CREATE TABLE demongan(ID int, Name varchar (20), Age int)”来创建一个名为demongan的表:
    // 执行SQL语句,创建表demongan ::wsprintf(szSQL, "CREATE TABLE demongan(ID int, Name varchar(20), Age int)"); iRet = sqlite3_exec(conn, szSQL, NULL, NULL, &szErr); if (SQLITE_OK != iRet) { ShowError("sqlite3_exec", szErr); return 2; }
    然后,继续调用 sqlite3_exec 函数执行SQL语句,将数据插入数据库中:
    // 执行SQL语句,插入10条记录 for (i = 0; i < 10; i++) { ::wsprintf(szSQL, "INSERT INTO demongan(ID, Name, Age) VALUES(%d, \'%s%d\', %d)", i, "Name", i, i + 1); iRet = sqlite3_exec(conn, szSQL, NULL, NULL, &szErr); if (SQLITE_OK != iRet) { ShowError("sqlite3_exec", szErr); return 3; } }
    接着,继续调用 sqlite3_exec 函数执行SQL语句,查询数据库,注意此处需要传入第 3 个参数,也就是回调函数,以此来显示查询结果:
    // 执行SQL语句,查询记录 ::wsprintf(szSQL, "SELECT * FROM demongan"); iRet = sqlite3_exec(conn, szSQL, sqlite3_exec_callback, NULL, &szErr); if (SQLITE_OK != iRet) { ShowError("sqlite3_exec"); return 4; }
    那么,回调函数 sqlite3_exec_callback 的函数名称是任意的,但是参数是固定的。一共有 4 个参数,第 1 个参数是由 sqlite3_exec 函数的第 4 个参数传递而来;第 2 个参数是表的列数;第 3 个参数表示查询到的值的指针数组;第 4 个参数表示列名即字段名指针数组。
    本文回调函数 sqlite3_exec_callback 的代码如下,
    int sqlite3_exec_callback(void *data, int colNum, char **colValue, char **colName){ int i = 0; for (i = 0; i < colNum; i++) { printf("%s[%s]\t", colName[i], colValue[i]); } printf("\n"); return 0;}
    程序测试将解压文件中的 “SQLite3.dll” 拷贝到工程编译链接生成的 exe 程序同一目录下,运行 exe 程序,成功显示数据库查询结果:

    总结使用SQLite数据库确实很方便,不需要安装额外的数据库环境,就可以通过SQL语句去操作数据库。这个程序的实现过程不是很繁杂,只要跟着上述介绍的步骤,仔细编码就可实现。
    同时,也应该注意的是,需要把解压文件中的 “SQLite3.dll” 拷贝到工程编译链接生成的 exe 程序同一目录下,这样程序才能正常运行。
    参考参考自《Windows黑客编程技术详解》一书
    1 回答 2018-12-30 20:40:57
  • 使用VS2013实现对Excel表格的读写

    背景有一天,一位网友加入了我的Q群,然后又通过Q群私信我,向我请教如何使用VS读写excel文件表格的问题。其实,在ta向我请教的时候,我也没有写过这样功能模块或是开发过类似的小程序。但,仍是被ta求知的行为感动了,所以决定花些时间去了解这方面的知识,给ta一个答复。
    于是,经过搜索,找到了相关资料,并写了个示例小程序给ta。当然,那个示例小程序并不是本文给的这个程序,本文的示例小程序是为了配合本文的演示,而故意修改的,更适合初学者使用,两者原理和实现上基本上是一样的。
    现在,我就把这个小程序的实现思路和实现过程,写成文档,分享给大家。
    导入操作EXCEL所需的MFC类库我们需要导入MFC类库,来帮助我们实现对EXCEL表格的操作。那么,就要求我们的项目工程支持MFC。类似,WIN32 控制台程序默认是不支持MFC的,所以,在创建项目的时候,要选择支持MFC。
    现在,本文以WIN32 控制台项目工程为例,讲解导入EXCEL所需类库的操作:
    首先,在“Win32应用程序向导”窗口中,注意要选择“添加公共头文件以用于:MFC”。然后点击“完成”,成功创建项目工程。

    进入项目工程中,选中项目,鼠标右键选择“添加” —> “添加类”。在“添加类”对话框中选择“TypeLib中的MFC类”,即基于类型库添加Microsoft基础类库类。

    接着需要选择OLE/COM 组件的路径,也就是你计算机上excel.exe 所在的路径。我的Microsoft Office是安装在F盘,所以excel.exe路径就是:

    F:\Program Files (x86)\Microsoft Office\Office12\EXCEL.EXE
    路径选择完毕后,需要向项目工程中添加基本的 7 个类( Excel 作为 OLE/COM 库插件,定义好了各类交互的接口,这些接口是跨语言的接口。 VC 可以通过导入这些接口,并通过 接口来对 Excel 的操作), 由于本文只关心对 Excel 表格中的数据的读取,主要关注 7 个接口:_Application、Workbooks、_Workbook、Worksheets、_Worksheet、Range、Font。
    添加完毕后,点击“完成”,即可成功添加 7 个类到项目工程中。

    成功添加 7 个类之后,项目工程会新增 7 个类库的头文件:

    但是,如果我们直接编译项目工程的话,会报错的。所以,现在需要对上述生成的 7 个头文件进行修改:
    将每个头文件顶头的:

    “#import “F:\Program Files (x86)\Microsoft Office\Office12\EXCEL.EXE” no_namespace”
    注释掉。并添加头文件:”#include <afxdisp.h>“

    修改完毕后,再编译程序,若报错,而且错误号为“C2059”,则双击错误,跳转到错误代码行。然后将 将VARIANT DialogBox() 改成 VARIANT _DialogBox() ,再次编译,即可编译通过。


    实现原理从EXCEL表格中读取数据
    首先,使用CApplication::CreateDispatch创建Excel.Application对象,并获取工作簿CWorkbooks
    接着,使用CWorkbooks::Open打开excel表格文件,并获取工作表对象CWorksheets
    然后使用CWorksheets::get_Item获取指定的工作表对象CWorksheet。本文是获取第 1 张工作表
    接着,我们可以调用CWorksheet::get_Range获取读取表格的范围。本文是获取 A1—A1 范围的表格
    然后,对表格内容弹窗输出
    最后,关闭对象,进行清理工作

    向EXCEL表格中写入数据
    首先,使用CApplication::CreateDispatch创建Excel.Application对象,并获取工作簿CWorkbooks
    接着,使用CWorkbooks::Add新添加一个工作簿,并使用CWorkbook::get_Worksheets获取工作表对象
    然后使用CWorksheets::get_Item获取指定的工作表对象CWorksheet。本文是获取第 1 张工作表
    接着,我们可以调用CWorksheet::get_Range获取表格的范围。本文是获取 A1—C3 范围的表格。并调用CRange::put_Value2将表格写入数据,并设置字体以及列宽
    然后,调用CWorkbook::SaveAs保存文件
    最后,关闭对象,进行清理工作

    编码实现从EXCEL表格中读取数据// 读取BOOL MyExcel::ReadExcel(){ //导入 COleVariant covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR); if (!app.CreateDispatch(_T("Excel.Application"))) { ::MessageBox(NULL, "无法创建Excel应用!", "WARNING", MB_OK); return TRUE; } books = app.get_Workbooks(); //打开Excel,其中pathname为Excel表的路径名 lpDisp = books.Open(_T("C:\\Users\\DemonGan\\Desktop\\test.xlsx"), covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional); book.AttachDispatch(lpDisp); sheets = book.get_Worksheets(); sheet = sheets.get_Item(COleVariant((short)1)); //获得坐标为(A,1) -- (A,1)的单元格 range = sheet.get_Range(COleVariant(_T("A1")), COleVariant(_T("A1"))); //获得单元格的内容 COleVariant rValue; rValue = COleVariant(range.get_Value2()); //转换成宽字符 rValue.ChangeType(VT_BSTR); //转换格式,并弹窗输出 ::MessageBox(NULL, CString(rValue.bstrVal), "RESULT", MB_OK); book.put_Saved(TRUE); // 退出 app.Quit(); app.ReleaseDispatch(); app = NULL; return TRUE;}
    向EXCEL表格中写入数据// 写入BOOL MyExcel::WriteExcel(){ //导出 COleVariant covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR); if (!app.CreateDispatch(_T("Excel.Application"))) { ::MessageBox(NULL, "无法创建Excel应用!", "WARNING", MB_OK); return TRUE; } books = app.get_Workbooks(); book = books.Add(covOptional); sheets = book.get_Worksheets(); sheet = sheets.get_Item(COleVariant((short)1)); //获得坐标为(A,1)和(C,3)范围区域的9个单元格 range = sheet.get_Range(COleVariant(_T("A1")), COleVariant(_T("C3"))); //设置单元格类容为World Of Demon range.put_Value2(COleVariant(_T("CDIY"))); //选择整列,并设置宽度为自适应 cols = range.get_EntireColumn(); cols.AutoFit(); //设置字体为粗体 font = range.get_Font(); font.put_Bold(COleVariant((short)TRUE)); //获得坐标为(D,4)单元格 range = sheet.get_Range(COleVariant(_T("D4")), COleVariant(_T("D4"))); //设置公式“=RAND()*100000” range.put_Formula(COleVariant(_T("=RAND()*100000"))); //设置数字格式为货币型 range.put_NumberFormat(COleVariant(_T("$0.00"))); //选择整列,并设置宽度为自适应 cols = range.get_EntireColumn(); cols.AutoFit(); //显示Excel表// app.put_Visible(TRUE);// app.put_UserControl(TRUE); // 保存excel表 COleVariant vTrue((short)TRUE), vFalse((short)FALSE), vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR); COleVariant vFileName(_T("C:\\Users\\DemonGan\\Desktop\\test.xlsx")); book.SaveAs( vFileName, //VARIANT* FileName vOptional, //VARIANT* FileFormat vOptional, //VARIANT* LockComments vOptional, //VARIANT* Password vOptional, //VARIANT* AddToRecentFiles vOptional, //VARIANT* WritePassword 0, //VARIANT* ReadOnlyRecommended vOptional, //VARIANT* EmbedTrueTypeFonts vOptional, //VARIANT* SaveNativePictureFormat vOptional, //VARIANT* SaveFormsData vOptional, //VARIANT* SaveAsAOCELetter vOptional //VARIANT* ReadOnlyRecommended ); // 退出 app.Quit(); app.ReleaseDispatch(); app = NULL; return TRUE;}
    程序测试在 main 函数中调用上述封装好的函数,进行测试。 main 函数为:
    int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]){ int nRetCode = 0; HMODULE hModule = ::GetModuleHandle(NULL); if (hModule != NULL) { // 初始化 MFC 并在失败时显示错误 if (!AfxWinInit(hModule, NULL, ::GetCommandLine(), 0)) { // TODO: 更改错误代码以符合您的需要 _tprintf(_T("错误: MFC 初始化失败\n")); nRetCode = 1; } else { // TODO: 在此处为应用程序的行为编写代码。 MyExcel myExcel; // 写入数据 myExcel.WriteExcel(); printf("Write OK.\n"); system("pause"); // 读取数据 myExcel.ReadExcel(); printf("Read OK.\n"); system("pause"); } } else { // TODO: 更改错误代码以符合您的需要 _tprintf(_T("错误: GetModuleHandle 失败\n")); nRetCode = 1; } return nRetCode;}
    测试结果
    运行程序,提示写入EXCEL表格成功。

    然后,打开生成的“test.xlsx”文件,数据被成功写入。

    然后,我们继续执行程序,EXCEL表格中的“A1”个的数据成功读取,并弹窗显示。

    总结这个小程序,主要是前期创建工程的时候需要注意,如果你创建的是MFC,那么就跟着上述步骤,导入操作EXCEL所需的MFC类库。但,如果你创建的是其他工程,例如Win32工程,那么在创建的过程中,就应该选择包含MFC的功能,因为程序需要导入操作EXCEL所需的MFC类库,所以工程必须要支持MFC。
    1 回答 2018-12-26 12:56:13
  • 内核KUSER_SHARED_DATA共享区域的验证

    背景无论是在 32 位系统内存分布,还是在 64 位系统内存分布中,我们知道高地址空间分配给系统内核使用,低地址空间分配给用户进程使用。
    事实上,用户空间和内核空间其实有一块共享区域,大小为 4 KB。它们的内存地址虽然不一样,但是它们都是有同一块物理内存映射出来的。现在,本文就是要实现一个这样的程序,去验证这块共享区域的存在。
    实现原理用户空间和内核空间的共享区域,大小为 4 KB,内核占用其中一小部分,但 Rootkit 应该大约还有 3 KB 空间可使用。这两个虚拟内存地址都映射到同一物理页面,内核程序对这块共享区域有可读、可写的权限,用户程序对这块共享区域只有只读的权限。
    其中,对于 32 位系统和 64 位系统来说,这块共享区域对应的内核地址范围以及对应用户空间的地址范围如下表所示:




    内核起始地址
    内核结束地址
    用户起始地址
    用户结束地址




    32 系统
    0xFFDF0000
    0xFFDF0FFF
    0x7FFE0000
    0x7FFE0FFF


    64 系统
    0xFFFFF780`00000000
    0xFFFFF780`00000FFF
    0x7FFE0000
    0x7FFE0FFF



    由上面可以看出,32 位系统和 64 位系统下,该共享区域的内核地址是不同的,而用户空间上的地址都是相同的。
    这块共享区域的名称是 KUSER_SHARED_DATA,想要获得关于该共享区域的更过详细解释,可以在 WinDbg 中输入:dt nt!_KUSER_SHARED_DATA 来获取信息。
    本文演示的程序,就是在 KUSER_SHARED_DATA 的内核内存中写入数据,然后,由用户称程序读取写入的数据,以此验证 KUSER_SHARED_DATA 区域的存在。
    编码实现用户层程序int _tmain(int argc, _TCHAR* argv[]){ // 要偏移 1 KB 大小读取数据, 因为写入的时候是偏移 1 KB 大小写入的 void *pBaseAddress = (void *)(0x7FFE0000 + 0x400); printf("[Share Data]%s\n", pBaseAddress); system("pause"); return 0;}
    内核层程序// 向共享区域中写入数据BOOLEAN WriteShareData(PCHAR pszData, ULONG ulDataSize){ PVOID pBaseAddress = NULL; // 偏移 1 KB 写入数据, 因为系统会占用大约 1 KB 的空间#ifdef _WIN64 // 64 Bits pBaseAddress = (PVOID)(0xFFFFF78000000000 + 0x400);#else // 32 Bits pBaseAddress = (PVOID)(0xFFDF0000 + 0x400);#endif // 写入 RtlCopyMemory(pBaseAddress, pszData, ulDataSize); return TRUE;}
    程序测试在 Windows7 32 位系统下,驱动程序正常执行:

    在 Windows10 64 位系统下,驱动程序正常执行:

    总结注意,这块共享区域主要是用来在用户层和内核层之间快速的传递信息的,会占用大约 1 KB 大小的空间。所以,我们通常偏移 0x400 大小处写入我们自己的数据,这样,就不会影响原来的内核代码的正常运行了。
    参考参考自《Windows黑客编程技术详解》一书
    1 回答 2018-12-25 09:01:31
  • 根据PE文件格式从导入表中获取加载的DLL并遍历导入函数名称和地址

    背景了解 PE 文件格式,对于做一些数据分析都是比较重要的基础。在 PE 文件格式中,理解导入表以及导出表的工作原理,又是重中之重。理解了 PE 格式的导入表,就可以修改 PE 格式进行 DLL 注入,也可以修改导入表实现 API HOOK 等。理解了 PE 格式的导出表,可以不需要 WIN32 API 函数就可以根据 DLL 加载基址定位出导出函数的名称和导出函数的地址,这在 SHELLCODE 的编写中,比较重要。
    本文主要介绍导入表。给出一个 DLL 的加载基址,然后我们根据导入表获取它需要加载的 DLL 以及遍历所有导入函数名称及其函数地址。现在,我把实现过程整理成文档,分享给大家。
    实现原理我们根据 PE 文件格式结构中的导入表工作原理来进行实现,实现原理如下所示:

    首先,我们根据 DLL 加载基址,也就是 PE 文件结构的起始地址,从 DOS 头获取 NT 头,并根据 NT 头中的 OptionalHeader.DataDirectory 的导入表选项,获取导入表的偏移位置以及数据大小
    我们来到导入表的数据偏移处,获取导入的 DLL 名称偏移 Name,并显示 DLL 名称
    接着,根据导入表中导入函数名称的地址偏移 OriginalFirstThunk,并根据 IMAGE_IMPORT_BY_NAME 数据结构从中获取导入函数名称及其函数索引并显示。同时,也从 FirstThunk 导入函数地址列表中获取对应位置的导入函数地址并显示。继续循环获取下一个函数名称,直到遍历完毕
    继续获取下一个 DLL,重复上诉第 3 步,直到 DLL 获取完毕

    其中,在导入表中,OriginalFirstThunk 的导入函数名称列表和 FirstThunk 导入函数地址列表一一对应。
    编码实现// 遍历导入表中的DLL、导入函数及其函数地址BOOL GetProcessDllName(PVOID lpBaseAddress){ LPVOID lpFunc = NULL; // 获取导入表 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress; PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE *)pDosHeader + pDosHeader->e_lfanew); PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((BYTE *)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); // 获取导入表的数据 char *pszDllName = NULL; PIMAGE_THUNK_DATA pThunkData = NULL; PIMAGE_IMPORT_BY_NAME pImportByName = NULL; PIMAGE_THUNK_DATA pImportFuncAddr = NULL; while (0 != pImportTable->Name) { // 获取DLL的名称 pszDllName = (char *)((BYTE *)pDosHeader + pImportTable->Name); printf("---------- DLL Name = %s ----------\n", pszDllName); // 遍历 DLL 中导入函数的名称 pThunkData = (PIMAGE_THUNK_DATA)((BYTE *)pDosHeader + pImportTable->OriginalFirstThunk); pImportFuncAddr = (PIMAGE_THUNK_DATA)((BYTE *)pDosHeader + pImportTable->FirstThunk); while (TRUE) { if (0 == pThunkData->u1.AddressOfData) { break; } // 获取导入函数名称和序号 pImportByName = (PIMAGE_IMPORT_BY_NAME)((BYTE *)pDosHeader + pThunkData->u1.AddressOfData); printf("[%d]\t%s\t", pImportByName->Hint, pImportByName->Name); // 获取导入函数地址 printf("[0x%p]\n", (PVOID)((BYTE *)pDosHeader + pImportFuncAddr->u1.Function)); // 获取下一个函数名称和地址 pThunkData++; pImportFuncAddr++; } // 获取下一个DLL pImportTable++; } return TRUE;}
    程序测试我们直接运行程序,程序正确列出导入的DLL及遍历出导入函数名称和地址:

    总结这个程序不是很复杂,但是关键是要理解 PE 文件结构的导入表,要理解清楚导入表的工作原理。
    参考参考自《Windows黑客编程技术详解》一书
    2 回答 2018-12-24 09:14:38
  • 根据PE文件格式从导出表中获取指定导出函数的地址

    背景了解 PE 文件格式,对于做一些数据分析都是比较重要的基础。在 PE 文件格式中,理解导入表以及导出表的工作原理,又是重中之重。理解了 PE 格式的导入表,就可以修改 PE 格式进行 DLL 注入,也可以修改导入表实现 API HOOK 等。理解了 PE 格式的导出表,可以不需要 WIN32 API 函数就可以根据 DLL 加载基址定位出导出函数的名称和导出函数的地址,这在 SHELLCODE 的编写中,比较重要。
    本文主要介绍导出表。给出一个 DLL 的加载基址和导出函数的名称,获取 DLL 中导出函数对应的导出地址。现在我把程序的实现过程整理成文档,分享给大家。
    实现原理我们根据PE文件格式结构中的导出表工作原理来进行实现,实现原理如下所示:

    首先,我们根据 DLL 加载基址,也就是 PE 文件结构的起始地址,从 DOS 头获取 NT 头,并根据 NT 头中的 OptionalHeader.DataDirectory 的导出表选项,获取导出表的偏移位置以及数据大小
    我们来到导出表的数据偏移处,获取导出函数名称的数量以及导出函数名称的地址列表偏移
    接着,我们开始根据导出表中导出名称的地址偏移列表遍历每一个导出函数的名称,与要寻找的导出函数名称进行匹配。若没有找到,则继续匹配下一个函数名称。若找到,则获取对应偏移的 AddressOfNameOrdinals 的值,这个值就是表示该导出函数在导出函数列表中的位置。这样,我们就可以在导出表 AddressOfFunctions 中获取相应的导出函数地址,并结束遍历,执行返回

    其中,在导出表中 AddressOfName 与 AddressOfNameOrdinals 的值是一一对应的,而 AddressOfName 与 AddressOfFunctions 的值并不是一一对应的。因为导出函数中,并不是所有的导出函数都会有名称,有的导出函数只有导出索引而已。所以,PE 结构便创建 AddressOfNameOrdinals 字段来保存有导出函数名称的函数在 AddressOfFunctions 导出函数地址列表中的位置。
    编码实现// 获取DLL的导出函数// lpBaseAddress: 内存DLL文件加载到进程中的加载基址// lpszFuncName: 导出函数的名字// 返回值: 返回导出函数的的地址LPVOID PEGetProcAddress(LPVOID lpBaseAddress, PCHAR lpszFuncName){ LPVOID lpFunc = NULL; // 获取导出表 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress; PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE *)pDosHeader + pDosHeader->e_lfanew); PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((BYTE *)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); // 获取导出表的数据 PDWORD lpAddressOfNamesArray = (PDWORD)((BYTE *)pDosHeader + pExportTable->AddressOfNames); PCHAR lpFuncName = NULL; PWORD lpAddressOfNameOrdinalsArray = (PWORD)((BYTE *)pDosHeader + pExportTable->AddressOfNameOrdinals); WORD wHint = 0; PDWORD lpAddressOfFunctionsArray = (PDWORD)((BYTE *)pDosHeader + pExportTable->AddressOfFunctions); DWORD dwNumberOfNames = pExportTable->NumberOfNames; DWORD i = 0; // 遍历导出表的导出函数的名称, 并进行匹配 for (i = 0; i < dwNumberOfNames; i++) { lpFuncName = (PCHAR)((BYTE *)pDosHeader + lpAddressOfNamesArray[i]); if (0 == ::lstrcmpi(lpFuncName, lpszFuncName)) { // 获取导出函数地址 wHint = lpAddressOfNameOrdinalsArray[i]; lpFunc = (LPVOID)((BYTE *)pDosHeader + lpAddressOfFunctionsArray[wHint]); break; } } return lpFunc;}
    程序测试我们直接运行程序,获取程序中的 GetModuleHandleA 函数的导出地址,并与 API 函数 GetProcAddress 获取的地址进行比较,结果相同。

    总结这个程序不是很复杂,但是关键是要理解 PE 文件结构的导出表,要理解清楚导出表的工作原理。
    参考参考自《Windows黑客编程技术详解》一书
    1 回答 2018-12-23 17:23:48
  • 基于WinInet的HTTP与HTTPS数据传输上传与下载的对比总结

    背景之前就是用WinInet库写了HTTP文件上传和下载以及HTTPS文件上传和下载的小程序,现在,要特意写一篇文章来总结HTTP和HTTPS之间文件上传和文件下载之间的异同点。当然,本文只是从编程开发的角度进行总结,并不是从协议本身去比较。
    HTTP与HTTPS文件下载的异同点


    操作
    HTTP文件下载
    HTTPS文件下载
    异或同




    建立会话
    ::InternetOpen(“WinInetGet/0.1”, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    ::InternetOpen(“WinInetGet/0.1”, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    相同


    建立连接
    :InternetConnect(hInternet, szHostName, INTERNET_DEFAULT_HTTP_PORT, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0);
    ::InternetConnect(hInternet, szHostName, INTERNET_DEFAULT_HTTPS_PORT, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0);
    端口有区别


    打开请求
    dwOpenRequestFlags = INTERNET_FLAG_IGNORE_RE DIRECT_TO_HTTP|INTERNET_FLAG_KEEP_CON NECTION|INTERNET_FLAG_NO_AUTH|INTERNET_FLAG_NO_COOKIES|INTERNET_FLAG_NO_UI;
    dwOpenRequestFlags = INTERNET_FLAG_IGNORE_RE DIRECT_TO_HTTP|INTERNET_FLAG_KEEP_CON NECTION|INTERNET_FLAG_NO_AUTH|INTERNET_FLAG_NO_COOKIES|INTERNET_FLAG_NO_UI|INTERNET_FLAG_SECURE|INTERNET_FLAG_IGNORE_CERT_CN_INVALID|INTERNET_FLAG_RELOAD;
    请求标志不同


    发送请求
    ::HttpSendRequest(hRequest, NULL, 0, NULL, 0);
    dwFlags = dwFlags|SECURITY_FLAG_IGNORE_UNKNOWN_CA; ::InternetSetOption(hRequest, INTERNET_OPTION_SECUR ITY_FLAGS, &dwFlags, sizeof(dwFlags)); ::HttpSendRequest(hRequest, NULL, 0, NULL, 0);
    HTTPS需要设置忽略未知的证书颁发机构


    接收响应信息头
    ::HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, pResponseHeaderIInfo, &dwResponseHeaderIInfoSize, NULL);
    ::HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, pResponseHeaderIInfo, &dwResponseHeaderIInfoSize, NULL);
    相同


    接收数据
    ::InternetReadFile(hRequest, pBuf, dwBufSize, &dwRet);
    ::InternetReadFile(hRequest, pBuf, dwBufSize, &dwRet);
    相同


    关闭句柄
    ::InternetCloseHandle(hRequest); ::InternetCloseHandle(hConnect); ::InternetCloseHandle(hInternet);
    ::InternetCloseHandle(hRequest); ::InternetCloseHandle(hConnect); ::InternetCloseHandle(hInternet);
    相同



    HTTP与HTTPS文件上传的异同点


    操作
    HTTP文件上传
    HTTPS文件上传
    异或同




    建立会话
    ::InternetOpen(“WinInetGet/0.1”, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    ::InternetOpen(“WinInetGet/0.1”, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    相同


    建立连接
    :InternetConnect(hInternet, szHostName, INTERNET_DEFAULT_HTTP_PORT, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0);
    ::InternetConnect(hInternet, szHostName, INTERNET_DEFAULT_HTTPS_PORT, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0);
    端口有区别


    打开请求
    dwOpenRequestFlags = INTERNET_FLAG_IGNORE_RE DIRECT_TO_HTTP|INTERNET_FLAG_KEEP_CON NECTION|INTERNET_FLAG_NO_AUTH|INTERNET_FLAG_NO_COOKIES|INTERNET_FLAG_NO_UI;
    dwOpenRequestFlags = INTERNET_FLAG_IGNORE_RE DIRECT_TO_HTTP|INTERNET_FLAG_KEEP_CON NECTION|INTERNET_FLAG_NO_AUTH|INTERNET_FLAG_NO_COOKIES|INTERNET_FLAG_NO_UI|INTERNET_FLAG_SECURE|INTERNET_FLAG_IGNORE_CERT_CN_INVALID|INTERNET_FLAG_RELOAD;
    请求标志不同


    附加请求头(可写可不写)
    ::HttpAddRequestHeaders(hRequest, szRequestHeaders, ::lstrlen(szRequestHeaders), HTTP_ADDREQ_FLAG_ADD);
    ::HttpAddRequestHeaders(hRequest, szRequestHeaders, ::lstrlen(szRequestHeaders), HTTP_ADDREQ_FLAG_ADD);
    相同


    发送请求
    ::HttpSendRequestEx(hRequest, &internetBuffers, NULL, 0, 0);
    dwFlags = dwFlags|SECURITY_FLAG_IGNORE_UNKNOWN_CA; ::InternetSetOption(hRequest, INTERNET_OPTION_SECUR ITY_FLAGS, &dwFlags, sizeof(dwFlags)); ::HttpSendRequestEx(hRequest, &internetBuffers, NULL, 0, 0);
    HTTPS需要设置忽略未知的证书颁发机构


    发送数据数据
    ::InternetWriteFile(hRequest, pUploadData, dwUploadDataSize, &dwRet);
    ::InternetWriteFile(hRequest, pUploadData, dwUploadDataSize, &dwRet);
    相同


    结束数据请求
    ::HttpEndRequest(hRequest, NULL, 0, 0);
    ::HttpEndRequest(hRequest, NULL, 0, 0);
    相同


    关闭句柄
    ::InternetCloseHandle(hRequest); ::InternetCloseHandle(hConnect); ::InternetCloseHandle(hInternet);
    ::InternetCloseHandle(hRequest); ::InternetCloseHandle(hConnect); ::InternetCloseHandle(hInternet);
    相同



    总结由上述的对比可知,HTTPS可基于HTTP上修改得到。它们的区别主要是体现在 3 点上:

    使用的连接端口不同;HTTP使用的是INTERNET_DEFAULT_HTTP_PORT也就是80端口;HTTPS使用的是INTERNET_DEFAULT_HTTPS_PORT,也就是443端口。
    请求标志不同;
    HTTP的请求标志有:
    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP INTERNET_FLAG_KEEP_CONNECTION INTERNET_FLAG_NO_AUTH INTERNET_FLAG_NO_COOKIES INTERNET_FLAG_NO_UI
    HTTPS的请求标志在HTTP的基础上,还增加多 3 个:
    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP INTERNET_FLAG_KEEP_CONNECTION INTERNET_FLAG_NO_AUTH INTERNET_FLAG_NO_COOKIES INTERNET_FLAG_NO_UI // HTTPS SETTING INTERNET_FLAG_SECURE INTERNET_FLAG_IGNORE_CERT_CN_INVALID INTERNET_FLAG_RELOAD
    发送请求的返回处理不同;HTTP若返回错误,则直接退出;而HTTPS若返回错误,则判断错误的类型是否是ERROR_INTERNET_INVALID_CA,然后设置忽略未知的证书颁发机构的安全标识,确保访问到一些使用自签名证书的HTTPS的网站。

    参考参考自《Windows黑客编程技术详解》一书
    0 回答 2018-12-23 14:31:41
  • 基于WinInet的HTTPS文件下载实现

    背景如果你之前写过基于WinInet库的HTTP下载文件,那么你在看完本文之后,就会发觉,这是和HTTP文件下载的代码几乎是一模一样的,就是有几个地方的区别而已。但是,本文不是对HTTP和HTTPS在WinInet库中的区别进行总结的,总结就另外写。
    本文就是基于WinInet网络库,实现通过HTTPS传输协议下载文件功能的小程序。现在,就把开发过程的思路和编程分享给大家。
    主要函数介绍介绍HTTPS下载文件使用到的主要的WinInet库中的API函数。
    1. InternetOpen介绍
    函数声明
    HINTERNET InternetOpen(In LPCTSTR lpszAgent,In DWORD dwAccessType,In LPCTSTR lpszProxyName,In LPCTSTR lpszProxyBypass,In DWORD dwFlags);
    参数lpszAgent指向一个空结束的字符串,该字符串指定调用WinInet函数的应用程序或实体的名称。使用此名称作为用户代理的HTTP协议。dwAccessType指定访问类型,参数可以是下列值之一:



    Value
    Meaning




    INTERNET_OPEN_TYPE_DIRECT
    使用直接连接网络


    INTERNET_OPEN_TYPE_PRECONFIG
    获取代理或直接从注册表中的配置,使用代理连接网络


    INTERNETOPEN_TYPE_PRECONFIG WITH_NO_AUTOPROXY
    获取代理或直接从注册表中的配置,并防止启动Microsoft JScript或Internet设置(INS)文件的使用


    INTERNET_OPEN_TYPE_PROXY
    通过代理的请求,除非代理旁路列表中提供的名称解析绕过代理,在这种情况下,该功能的使用



    lpszProxyName指针指向一个空结束的字符串,该字符串指定的代理服务器的名称,不要使用空字符串;如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY,则此参数应该设置为NULL。
    lpszProxyBypass指向一个空结束的字符串,该字符串指定的可选列表的主机名或IP地址。如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY的 ,参数省略则为NULL。
    dwFlags参数可以是下列值的组合:



    VALUE
    MEANING




    INTERNET_FLAG_ASYNC
    使异步请求处理的后裔从这个函数返回的句柄


    INTERNET_FLAG_FROM_CACHE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND


    INTERNET_FLAG_OFFLINE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND



    返回值成功:返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinInet函数。失败:返回NULL。

    2. InternetConnect介绍
    函数声明
    HINTERNET WINAPI InternetConnect( HINTERNET hInternet, LPCTSTR lpszServerName, INTERNET_PORT nServerPort, LPCTSTR lpszUserName, LPCTSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD dwContext);
    参数说明hInternet:由InternetOpen返回的句柄。lpszServerName:连接的ip或者主机名nServerPort:连接的端口。lpszUserName:用户名,如无置NULL。lpszPassword:密码,如无置NULL。dwService:使用的服务类型,可以使用以下

    INTERNET_SERVICE_FTP = 1:连接到一个 FTP 服务器上INTERNET_SERVICE_GOPHER = 2INTERNET_SERVICE_HTTP = 3:连接到一个 HTTP 服务器上
    dwFlags:文档传输形式及缓存标记。一般置0。dwContext:当使用回叫信号时, 用来识别应用程序的前后关系。返回值成功返回非0。如果返回0。要InternetCloseHandle释放这个句柄。

    3. HttpOpenRequest介绍
    函数声明
    HINTERNET HttpOpenRequest( _In_ HINTERNET hConnect, _In_ LPCTSTR lpszVerb, _In_ LPCTSTR lpszObjectName, _In_ LPCTSTR lpszVersion, _In_ LPCTSTR lpszReferer, _In_ LPCTSTR *lplpszAcceptTypes, _In_ DWORD dwFlags, _In_ DWORD_PTR dwContext);
    参数
    hConnect:由InternetConnect返回的句柄。
    lpszVerb:一个指向某个包含在请求中要用的动词的字符串指针。如果为NULL,则使用“GET”。
    lpszObjectName:一个指向某个包含特殊动词的目标对象的字符串的指针。通常为文件名称、可执行模块或者查找标识符。
    lpszVersion:一个指向以null结尾的字符串的指针,该字符串包含在请求中使用的HTTP版本,Internet Explorer中的设置将覆盖该参数中指定的值。如果此参数为NULL,则该函数使用1.1或1.0的HTTP版本,这取决于Internet Explorer设置的值。
    lpszReferer:一个指向指定了包含着所需的URL (pstrObjectName)的文档地址(URL)的指针。如果为NULL,则不指定HTTP头。
    lplpszAcceptTypes:一个指向某空终止符的字符串的指针,该字符串表示客户接受的内容类型。如果该字符串为NULL,服务器认为客户接受“text/*”类型的文档 (也就是说,只有纯文本文档,并且不是图片或其它二进制文件)。内容类型与CGI变量CONTENT_TYPE相同,该变量确定了要查询的含有相关信息的数据的类型,如HTTP POST和PUT。
    dwFlags:dwFlags的值可以是下面一个或者多个。



    价值
    说明




    INTERNET_FLAG_DONT_CACHE
    不缓存的数据,在本地或在任何网关。 相同的首选值INTERNET_FLAG_NO_CACHE_WRITE。


    INTERNET_FLAG_EXISTING_CONNECT
    如果可能的话,重用现有的连接到每个服务器请求新的请求而产生的InternetOpenUrl创建一个新的会话。 这个标志是有用的,只有对FTP连接,因为FTP是唯一的协议,通常在同一会议执行多个操作。 在Win 32 API的缓存一个单一的Internet连接句柄为每个HINTERNET处理产生的InternetOpen。


    INTERNET_FLAG -超链接
    强制重载如果没有到期的时间也没有最后修改时间从服务器在决定是否加载该项目从网络返回。


    INTERNET_FLAG_IGNORE_CERT_CN_INVALID
    禁用的Win32上网功能的SSL /厘为基础的打击是从给定的请求服务器返回的主机名称证书检查。 Win32的上网功能用来对付证书由匹配主机名和HTTP请求一个简单的通配符规则比较简单的检查。


    INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
    禁用的Win32上网功能的SSL /厘为基础的HTTP请求适当的日期,证书的有效性检查。


    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP
    禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许对HTTP重定向的URL从HTTPS。


    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS
    禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许从HTTP重定向到HTTPS网址。


    INTERNET_FLAG_KEEP_CONNECTION
    使用保持活动语义,如果有的话,给HTTP请求连接。 这个标志是必需的微软网络(MSN),NT LAN管理器(NTLM)和其他类型的身份验证。


    INTERNET_FLAG_MAKE_PERSISTENT
    不再支持。


    INTERNET_FLAG_MUST_CACHE_REQUEST
    导致一个临时文件如果要创建的文件不能被缓存。 相同的首选值INTERNET_FLAG_NEED_FILE。


    INTERNET_FLAG_NEED_FILE
    导致一个临时文件如果要创建的文件不能被缓存。


    INTERNET_FLAG_NO_AUTH
    不尝试HTTP请求身份验证自动。


    INTERNET_FLAG_NO_AUTO_REDIRECT
    不自动处理HTTP请求重定向只。


    INTERNET_FLAG_NO_CACHE_WRITE
    不缓存的数据,在本地或在任何网关。


    INTERNET_FLAG_NO_COOKIES
    不会自动添加Cookie标头的请求,并不会自动添加返回的Cookie的HTTP请求的Cookie数据库。


    INTERNET_FLAG_NO_UI
    禁用cookie的对话框。


    INTERNET_FLAG_PASSIVE
    使用被动FTP语义FTP文件和目录。


    INTERNET_FLAG_RAW_DATA
    返回一个数据WIN32_FIND_DATA结构时,FTP目录检索信息。 如果这个标志,或者未指定代理的电话是通过一个CERN,InternetOpenUrl返回的HTML版本的目录。


    INTERNET_FLAG_PRAGMA_NOCACHE
    强制要求被解决的原始服务器,即使在代理缓存的副本存在。


    INTERNET_FLAG_READ_PREFETCH
    该标志目前已停用。


    INTERNET_FLAG_RELOAD
    从导线获取数据,即使是一个本地缓存。


    INTERNET_FLAG_RESYNCHRONIZE
    重整HTTP资源,如果资源已被修改自上一次被下载。 所有的FTP资源增值。


    INTERNET_FLAG_SECURE
    请确保在使用SSL或PCT线交易。 此标志仅适用于HTTP请求。



    dwContext:OpenRequest操作的上下文标识符。

    4. InternetReadFile介绍
    函数声明
    BOOL InternetReadFile( __in HINTERNET hFile,__out LPVOID lpBuffer,__in DWORD dwNumberOfBytesToRead,__out LPDWORD lpdwNumberOfBytesRead);
    参数

    hFile[in]
    由InternetOpenUrl,FtpOpenFile, 或HttpOpenRequest函数返回的句柄.
    lpBuffer[out]
    缓冲器指针
    dwNumberOfBytesToRead[in]
    欲读数据的字节量。
    lpdwNumberOfBytesRead[out]
    接收读取字节量的变量。该函数在做任何工作或错误检查之前都设置该值为零

    返回值成功:返回TRUE,失败,返回FALSE

    程序设计原理该部分讲解下程序设计的原理以及实现的流程,让大家有个宏观的认识。原理是:

    首先,使用 InternetCrackUrl 函数分解URL,从URL中提取网站的域名、路径以及URL的附加信息等。关于 InternetCrackUrl 分解URL的介绍和实现,可以参考 “URL分解之InternetCrackUrl” 这篇文章
    使用 InternetOpen 建立会话,获取会话句柄
    使用 InternetConnect 与网站建立连接,获取连接句柄
    设置HTTPS的访问标志,使用 HttpOpenRequest 打开HTTP的“GET”请求
    使用 HttpSendRequest 发送访问请求,同时根据出错返回的错误码,来判断是否设置忽略未知的证书颁发机构,以确保能正常访问HTTPS网站
    根据返回的Response Header的数据中,获取将要接收数据的长度
    使用 InternetReadFile 接收数据
    关闭句柄,释放资源

    其中,上面的 8 个步骤中,要注意第 4 步访问标志的标志设置;注意第 5 步的忽略未知的证书颁发机构的设置;同时,还要注意的就是第 6 步,获取返回的数据长度,是从响应信息头中的获取“Content-Length: ”(注意有个空格)这个字段的数据。
    编程实现1. 导入WinInet库#include <WinInet.h>#pragma comment(lib, "WinInet.lib")
    2. HTTPS文件下载编程实现// 数据下载// 输入:下载数据的URL路径// 输出:下载数据内容、下载数据内容长度BOOL Https_Download(char *pszDownloadUrl, BYTE **ppDownloadData, DWORD *pdwDownloadDataSize){ // INTERNET_SCHEME_HTTPS、INTERNET_SCHEME_HTTP、INTERNET_SCHEME_FTP等 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(szScheme, MAX_PATH); ::RtlZeroMemory(szHostName, MAX_PATH); ::RtlZeroMemory(szUserName, MAX_PATH); ::RtlZeroMemory(szPassword, MAX_PATH); ::RtlZeroMemory(szUrlPath, MAX_PATH); ::RtlZeroMemory(szExtraInfo, MAX_PATH); // 分解URL if (FALSE == Https_UrlCrack(pszDownloadUrl, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, MAX_PATH)) { return FALSE; } // 数据下载 HINTERNET hInternet = NULL; HINTERNET hConnect = NULL; HINTERNET hRequest = NULL; DWORD dwOpenRequestFlags = 0; BOOL bRet = FALSE; unsigned char *pResponseHeaderIInfo = NULL; DWORD dwResponseHeaderIInfoSize = 2048; BYTE *pBuf = NULL; DWORD dwBufSize = 64*1024; BYTE *pDownloadData = NULL; DWORD dwDownloadDataSize = 0; DWORD dwRet = 0; DWORD dwOffset = 0; do { // 建立会话 hInternet = ::InternetOpen("WinInetGet/0.1", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (NULL == hInternet) { Https_ShowError("InternetOpen"); break; } // 建立连接(与HTTP的区别 -- 端口) hConnect = ::InternetConnect(hInternet, szHostName, INTERNET_DEFAULT_HTTPS_PORT, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0); if (NULL == hConnect) { Https_ShowError("InternetConnect"); break; } // 打开并发送HTTPS请求(与HTTP的区别--标志) dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI | // HTTPS SETTING INTERNET_FLAG_SECURE | INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_RELOAD; if (0 < ::lstrlen(szExtraInfo)) { // 注意此处的连接 ::lstrcat(szUrlPath, szExtraInfo); } hRequest = ::HttpOpenRequest(hConnect, "GET", szUrlPath, NULL, NULL, NULL, dwOpenRequestFlags, 0); if (NULL == hRequest) { Https_ShowError("HttpOpenRequest"); break; } // 发送请求(与HTTP的区别--对无效的证书颁发机构的处理) bRet = ::HttpSendRequest(hRequest, NULL, 0, NULL, 0); if (FALSE == bRet) { if (ERROR_INTERNET_INVALID_CA == ::GetLastError()) { DWORD dwFlags = 0; DWORD dwBufferSize = sizeof(dwFlags); // 获取INTERNET_OPTION_SECURITY_FLAGS标志 bRet = ::InternetQueryOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, &dwBufferSize); if (bRet) { // 设置INTERNET_OPTION_SECURITY_FLAGS标志 // 忽略未知的证书颁发机构 dwFlags = dwFlags | SECURITY_FLAG_IGNORE_UNKNOWN_CA; bRet = ::InternetSetOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags)); if (bRet) { // // 再次发送请求 bRet = ::HttpSendRequest(hRequest, NULL, 0, NULL, 0); if (FALSE == bRet) { Https_ShowError("HttpSendRequest"); break; } } else { Https_ShowError("InternetSetOption"); break; } } else { Https_ShowError("InternetQueryOption"); break; } } else { Https_ShowError("HttpSendRequest"); break; } } // 接收响应的报文信息头(Get Response Header) pResponseHeaderIInfo = new unsigned char[dwResponseHeaderIInfoSize]; if (NULL == pResponseHeaderIInfo) { break; } ::RtlZeroMemory(pResponseHeaderIInfo, dwResponseHeaderIInfoSize); bRet = ::HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, pResponseHeaderIInfo, &dwResponseHeaderIInfoSize, NULL); if (FALSE == bRet) { Https_ShowError("HttpQueryInfo"); break; }#ifdef _DEBUG printf("[HTTPS_Download_ResponseHeaderIInfo]\n\n%s\n\n", pResponseHeaderIInfo);#endif // 从 中字段 "Content-Length: "(注意有个空格) 获取数据长度 bRet = Https_GetContentLength((char *)pResponseHeaderIInfo, &dwDownloadDataSize); if (FALSE == bRet) { break; } // 接收报文主体内容(Get Response Body) pBuf = new BYTE[dwBufSize]; if (NULL == pBuf) { break; } pDownloadData = new BYTE[dwDownloadDataSize]; if (NULL == pDownloadData) { break; } ::RtlZeroMemory(pDownloadData, dwDownloadDataSize); do { ::RtlZeroMemory(pBuf, dwBufSize); bRet = ::InternetReadFile(hRequest, pBuf, dwBufSize, &dwRet); if (FALSE == bRet) { Https_ShowError("InternetReadFile"); break; } ::RtlCopyMemory((pDownloadData + dwOffset), pBuf, dwRet); dwOffset = dwOffset + dwRet; } while (dwDownloadDataSize > dwOffset); // 返回数据 *ppDownloadData = pDownloadData; *pdwDownloadDataSize = dwDownloadDataSize; } while (FALSE); // 关闭 释放 if (NULL != pBuf) { delete[]pBuf; pBuf = NULL; } if (NULL != pResponseHeaderIInfo) { delete[]pResponseHeaderIInfo; pResponseHeaderIInfo = NULL; } if (NULL != hRequest) { ::InternetCloseHandle(hRequest); hRequest = NULL; } if (NULL != hConnect) { ::InternetCloseHandle(hConnect); hConnect = NULL; } if (NULL != hInternet) { ::InternetCloseHandle(hInternet); hInternet = NULL; } return bRet;}
    程序测试在main函数中,调用上述封装好的函数,下载文件进行测试。
    main函数为:
    int _tmain(int argc, _TCHAR* argv[]){ char szHttpsDownloadUrl[] = "https://download.microsoft.com/download/0/2/3/02389126-40A7-46FD-9D83-802454852703/vc_mbcsmfc.exe"; BYTE *pHttpsDownloadData = NULL; DWORD dwHttpsDownloadDataSize = 0; // HTTPS下载 if (FALSE == Https_Download(szHttpsDownloadUrl, &pHttpsDownloadData, &dwHttpsDownloadDataSize)) { return 1; } // 将数据保存成文件 Https_SaveToFile("https_downloadsavefile.exe", pHttpsDownloadData, dwHttpsDownloadDataSize); // 释放内存 delete []pHttpsDownloadData; pHttpsDownloadData = NULL; system("pause"); return 0;}
    测试结果:
    根据返回的Response Header知道,成功下载67453208字节大小的数据。

    查看目录,有65873KB大小的“https_downloadsavefile.zip”文件成功生成,所以,数据下载成功。

    总结基于 WinInet 库的 HTTPS 下载文件原理并不复杂,但是,因为涉及较多的 API,每个 API 的执行都需要依靠上一个 API 成功执行返回的数据。所以,要仔细检查。如果出错,也要耐心调试,根据返回的错误码,结合程序前后部分的代码,仔细分析原因。
    同时要注意在使用 HttpOpenRequest 和 HttpSendRequest 函数中,对 HTTPS 的标志设置以及忽略未知的证书颁发机构。
    参考参考自《Windows黑客编程技术详解》一书
    1 回答 2018-12-22 10:15:55
  • 基于WinInet的HTTPS文件上传实现

    背景之前写过基于WinInet的HTTPS文件下载功能的小程序了,那就顺便把HTTPS文件上传也一并写了吧,这样知识才算是比较完整了。相对于文件下载来说,文件上传过程原理也都差不多,只要注意些区别就好了。
    现在,把基于WinInet的HTTPS文件上传功能小程序的开发过程分享给大家,方便大家的参考。
    前期准备前期需要本地搭建一个测试环境,本文搭建的是一个本地的HTTPS的ASP网站,同时,使用asp写了一个接收上传数据存储为文件的小程序test1.asp。
    搭建HTTPS传输的ASP服务器,可以参考本站上写的 “使用Windows7旗舰版64位来搭建本地HTTPS测试的ASP服务器” 这一篇文章,里面详细介绍了搭建过程和注意事项。同时,也可以参考 “修改ASP网站的文件传输大小的默认限制并对限制大小进行探索” 这一篇文章,介绍的是更改ASP网站上传的文件大小的限制。
    搭建测试环境的原因,就是为了测试,才能知道文件有没有成功上传到服务器上。当然,有条件的,也可以自己在公网上搭建服务器,这样测试会更加真实。
    主要函数介绍介绍HTTPS上传文件使用到的主要的WinInet库中的API函数。
    1. InternetOpen介绍
    函数声明
    HINTERNET InternetOpen(In LPCTSTR lpszAgent,In DWORD dwAccessType,In LPCTSTR lpszProxyName,In LPCTSTR lpszProxyBypass,In DWORD dwFlags);
    参数lpszAgent指向一个空结束的字符串,该字符串指定调用WinInet函数的应用程序或实体的名称。使用此名称作为用户代理的HTTP协议。dwAccessType指定访问类型,参数可以是下列值之一:



    Value
    Meaning




    INTERNET_OPEN_TYPE_DIRECT
    使用直接连接网络


    INTERNET_OPEN_TYPE_PRECONFIG
    获取代理或直接从注册表中的配置,使用代理连接网络


    INTERNETOPEN_TYPE_PRECONFIG WITH_NO_AUTOPROXY
    获取代理或直接从注册表中的配置,并防止启动Microsoft JScript或Internet设置(INS)文件的使用


    INTERNET_OPEN_TYPE_PROXY
    通过代理的请求,除非代理旁路列表中提供的名称解析绕过代理,在这种情况下,该功能的使用



    lpszProxyName指针指向一个空结束的字符串,该字符串指定的代理服务器的名称,不要使用空字符串;如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY,则此参数应该设置为NULL。
    lpszProxyBypass指向一个空结束的字符串,该字符串指定的可选列表的主机名或IP地址。如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY的 ,参数省略则为NULL。
    dwFlags参数可以是下列值的组合:



    VALUE
    MEANING




    INTERNET_FLAG_ASYNC
    使异步请求处理的后裔从这个函数返回的句柄


    INTERNET_FLAG_FROM_CACHE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND


    INTERNET_FLAG_OFFLINE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND



    返回值成功:返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinInet函数。失败:返回NULL。

    2. InternetConnect介绍
    函数声明
    HINTERNET WINAPI InternetConnect( HINTERNET hInternet, LPCTSTR lpszServerName, INTERNET_PORT nServerPort, LPCTSTR lpszUserName, LPCTSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD dwContext);
    参数说明hInternet:由InternetOpen返回的句柄。lpszServerName:连接的ip或者主机名nServerPort:连接的端口。lpszUserName:用户名,如无置NULL。lpszPassword:密码,如无置NULL。dwService:使用的服务类型,可以使用以下

    INTERNET_SERVICE_FTP = 1:连接到一个 FTP 服务器上INTERNET_SERVICE_GOPHER = 2INTERNET_SERVICE_HTTP = 3:连接到一个 HTTP 服务器上
    dwFlags:文档传输形式及缓存标记。一般置0。dwContext:当使用回叫信号时, 用来识别应用程序的前后关系。返回值成功返回非0。如果返回0。要InternetCloseHandle释放这个句柄。

    3. HttpOpenRequest介绍
    函数声明
    HINTERNET HttpOpenRequest( _In_ HINTERNET hConnect, _In_ LPCTSTR lpszVerb, _In_ LPCTSTR lpszObjectName, _In_ LPCTSTR lpszVersion, _In_ LPCTSTR lpszReferer, _In_ LPCTSTR *lplpszAcceptTypes, _In_ DWORD dwFlags, _In_ DWORD_PTR dwContext);
    参数
    hConnect:由InternetConnect返回的句柄。
    lpszVerb:一个指向某个包含在请求中要用的动词的字符串指针。如果为NULL,则使用“GET”。
    lpszObjectName:一个指向某个包含特殊动词的目标对象的字符串的指针。通常为文件名称、可执行模块或者查找标识符。
    lpszVersion:一个指向以null结尾的字符串的指针,该字符串包含在请求中使用的HTTP版本,Internet Explorer中的设置将覆盖该参数中指定的值。如果此参数为NULL,则该函数使用1.1或1.0的HTTP版本,这取决于Internet Explorer设置的值。
    lpszReferer:一个指向指定了包含着所需的URL (pstrObjectName)的文档地址(URL)的指针。如果为NULL,则不指定HTTP头。
    lplpszAcceptTypes:一个指向某空终止符的字符串的指针,该字符串表示客户接受的内容类型。如果该字符串为NULL,服务器认为客户接受“text/*”类型的文档 (也就是说,只有纯文本文档,并且不是图片或其它二进制文件)。内容类型与CGI变量CONTENT_TYPE相同,该变量确定了要查询的含有相关信息的数据的类型,如HTTP POST和PUT。
    dwFlags:dwFlags的值可以是下面一个或者多个。



    价值
    说明




    INTERNET_FLAG_DONT_CACHE
    不缓存的数据,在本地或在任何网关。 相同的首选值INTERNET_FLAG_NO_CACHE_WRITE。


    INTERNET_FLAG_EXISTING_CONNECT
    如果可能的话,重用现有的连接到每个服务器请求新的请求而产生的InternetOpenUrl创建一个新的会话。 这个标志是有用的,只有对FTP连接,因为FTP是唯一的协议,通常在同一会议执行多个操作。 在Win 32 API的缓存一个单一的Internet连接句柄为每个HINTERNET处理产生的InternetOpen。


    INTERNET_FLAG -超链接
    强制重载如果没有到期的时间也没有最后修改时间从服务器在决定是否加载该项目从网络返回。


    INTERNET_FLAG_IGNORE_CERT_CN_INVALID
    禁用的Win32上网功能的SSL /厘为基础的打击是从给定的请求服务器返回的主机名称证书检查。 Win32的上网功能用来对付证书由匹配主机名和HTTP请求一个简单的通配符规则比较简单的检查。


    INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
    禁用的Win32上网功能的SSL /厘为基础的HTTP请求适当的日期,证书的有效性检查。


    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP
    禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许对HTTP重定向的URL从HTTPS。


    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS
    禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许从HTTP重定向到HTTPS网址。


    INTERNET_FLAG_KEEP_CONNECTION
    使用保持活动语义,如果有的话,给HTTP请求连接。 这个标志是必需的微软网络(MSN),NT LAN管理器(NTLM)和其他类型的身份验证。


    INTERNET_FLAG_MAKE_PERSISTENT
    不再支持。


    INTERNET_FLAG_MUST_CACHE_REQUEST
    导致一个临时文件如果要创建的文件不能被缓存。 相同的首选值INTERNET_FLAG_NEED_FILE。


    INTERNET_FLAG_NEED_FILE
    导致一个临时文件如果要创建的文件不能被缓存。


    INTERNET_FLAG_NO_AUTH
    不尝试HTTP请求身份验证自动。


    INTERNET_FLAG_NO_AUTO_REDIRECT
    不自动处理HTTP请求重定向只。


    INTERNET_FLAG_NO_CACHE_WRITE
    不缓存的数据,在本地或在任何网关。


    INTERNET_FLAG_NO_COOKIES
    不会自动添加Cookie标头的请求,并不会自动添加返回的Cookie的HTTP请求的Cookie数据库。


    INTERNET_FLAG_NO_UI
    禁用cookie的对话框。


    INTERNET_FLAG_PASSIVE
    使用被动FTP语义FTP文件和目录。


    INTERNET_FLAG_RAW_DATA
    返回一个数据WIN32_FIND_DATA结构时,FTP目录检索信息。 如果这个标志,或者未指定代理的电话是通过一个CERN,InternetOpenUrl返回的HTML版本的目录。


    INTERNET_FLAG_PRAGMA_NOCACHE
    强制要求被解决的原始服务器,即使在代理缓存的副本存在。


    INTERNET_FLAG_READ_PREFETCH
    该标志目前已停用。


    INTERNET_FLAG_RELOAD
    从导线获取数据,即使是一个本地缓存。


    INTERNET_FLAG_RESYNCHRONIZE
    重整HTTP资源,如果资源已被修改自上一次被下载。 所有的FTP资源增值。


    INTERNET_FLAG_SECURE
    请确保在使用SSL或PCT线交易。 此标志仅适用于HTTP请求。



    dwContext:OpenRequest操作的上下文标识符。

    4. WinHttpAddRequestHeaders介绍
    函数声明
    BOOL WINAPI WinHttpAddRequestHeaders( In HINTERNET hRequest, In LPCWSTR pwszHeaders, In DWORD dwHeadersLength, In DWORD dwModifiers);
    作用
    添加一个HTTP的请求头域。
    参数

    hRequest [in]一个HINTERNET句柄通过调用WinHttpOpenRequest返回。pwszHeaders [in]请求的头域字符串,每个头域(多个头域以)使用回车换行(\r\n)结束dwHeadersLength [in]无符号长整型变量,指向pwszHeaders的长度,如果该参数为(ulong)-1L时,自动以”/0”结束来计算pwszHeaders的长度。dwModifiers [in]头域的修改模式。包括如下值:WINHTTP_ADDREQ_FLAG_ADD 添加一个头域,如果头域存在时值将被新添加的值替换。与WINHTTP_ADDREQ_FLAG_REPLAC一起使用WINHTTP_ADDREQ_FLAG_ADD_IF_NEW 添加一个不存在头域,如果该头域存在则返回一个错误。WINHTTP_ADDREQ_FLAG_COALESCE 将同名的头域进行合并。WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA 合并同名的头域,值使用逗号隔开。WINHTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON 合并同名的头域,值使用分号隔开。WINHTTP_ADDREQ_FLAG_REPLACE 替换和删除一个头域,如果值为空,则删除,否则被替换。
    返回值
    返回值为假时,使用GetLastError来得到错误信息。err code:ERROR_WINHTTP_INCORRECT_HANDLE_STATE 请求不能被执行,因为句柄的状态不正确ERROR_WINHTTP_INCORRECT_HANDLE_TYPE 请求的句柄类型不正确ERROR_WINHTTP_INTERNAL_ERROR 内部错误ERROR_NOT_ENOUGH_MEMORY 没有足够的内存来完成操作。

    5. InternetWriteFile介绍
    函数声明
    BOOL InternetWriteFile( __in HINTERNET hFile,__out LPVOID lpBuffer,__in DWORD dwNumberOfBytesToRead,__out LPDWORD lpdwNumberOfBytesRead);
    参数

    hFile[in]
    由InternetOpenUrl,FtpOpenFile, 或HttpOpenRequest函数返回的句柄.
    lpBuffer[out]
    缓冲器指针
    dwNumberOfBytesToRead[in]
    欲写入数据的字节量。
    lpdwNumberOfBytesRead[out]
    接收写入字节量的变量。该函数在做任何工作或错误检查之前都设置该值为零

    返回值成功:返回TRUE,失败,返回FALSE

    程序设计原理该部分讲解下程序设计的原理以及实现的流程,让大家有个宏观的认识。原理是:

    首先,使用 InternetCrackUrl 函数分解URL,从URL中提取网站的域名、路径以及URL的附加信息等。关于 InternetCrackUrl 分解URL的介绍和实现,可以参考 “URL分解之InternetCrackUrl” 这篇文章
    使用 InternetOpen 建立会话,获取会话句柄
    使用 InternetConnect 与网站建立连接,获取连接句柄
    设置HTTPS的访问标志,使用 HttpOpenRequest 打开HTTP的“POST”请求
    构造请求信息头字符串,并使用 HttpAddRequestHeaders 附加请求信息头
    使用 HttpSendRequestEx发送访问请求,同时根据出错返回的错误码,来判断是否设置忽略未知的证书颁发机构,以确保能正常访问HTTPS网站
    使用 InternetWriteFile 上传数据
    数据上传完毕之后,使用 HttpEndRequest 函数结束请求
    关闭句柄,释放资源

    其中,需要注意的是第 5 步,这一步是与HTTPS文件下载不同的地方,这一步需要构造请求信息头,所以构造请求信息头的字符串的时候,一定要严格按照协议格式去构造。例如回车换行、空格之类的。
    编程实现1. 导入WinInet库#include <WinInet.h>#pragma comment(lib, "WinInet.lib")
    2. HTTPS文件上传编程实现// 数据上传// 输入:上传数据的URL路径、上传数据内容、上传数据内容长度BOOL Https_Upload(char *pszUploadUrl, BYTE *pUploadData, DWORD dwUploadDataSize){ // INTERNET_SCHEME_HTTPS、INTERNET_SCHEME_HTTP、INTERNET_SCHEME_FTP等 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(szScheme, MAX_PATH); ::RtlZeroMemory(szHostName, MAX_PATH); ::RtlZeroMemory(szUserName, MAX_PATH); ::RtlZeroMemory(szPassword, MAX_PATH); ::RtlZeroMemory(szUrlPath, MAX_PATH); ::RtlZeroMemory(szExtraInfo, MAX_PATH); // 分解URL if (FALSE == Https_UrlCrack(pszUploadUrl, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, MAX_PATH)) { return FALSE; } // 数据上传 HINTERNET hInternet = NULL; HINTERNET hConnect = NULL; HINTERNET hRequest = NULL; DWORD dwOpenRequestFlags = 0; BOOL bRet = FALSE; DWORD dwRet = 0; unsigned char *pResponseHeaderIInfo = NULL; DWORD dwResponseHeaderIInfoSize = 2048; BYTE *pBuf = NULL; DWORD dwBufSize = 64 * 1024; BYTE *pResponseBodyData = NULL; DWORD dwResponseBodyDataSize = 0; DWORD dwOffset = 0; do { // 建立会话 hInternet = ::InternetOpen("WinInetPost/0.1", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (NULL == hInternet) { Https_ShowError("InternetOpen"); break; } // 建立连接 hConnect = ::InternetConnect(hInternet, szHostName, INTERNET_DEFAULT_HTTPS_PORT, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0); if (NULL == hConnect) { Https_ShowError("InternetConnect"); break; } // 打开并发送HTTP请求 dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI | // HTTPS SETTING INTERNET_FLAG_SECURE | INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_RELOAD; if (0 < ::lstrlen(szExtraInfo)) { // 注意此处的连接 ::lstrcat(szUrlPath, szExtraInfo); } hRequest = ::HttpOpenRequest(hConnect, "POST", szUrlPath, NULL, NULL, NULL, dwOpenRequestFlags, 0); if (NULL == hRequest) { Https_ShowError("HttpOpenRequest"); break; } // 附加 请求头(可以写也可以不写, 不写的话直接发送数据也可以, 主要是看服务端和客户端的数据传输约定; 批量发送的时候要用到) char szBoundary[] = "-------------MyUploadBoundary"; // 数据边界 char szRequestHeaders[MAX_PATH] = { 0 }; ::RtlZeroMemory(szRequestHeaders, MAX_PATH); ::wsprintf(szRequestHeaders, // 构造 请求头数据信息 "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/xaml+xml,*/*\r\n" "Accept-Encoding: gzip, deflate\r\n" "Accept-Language: zh-cn\r\n" "Content-Type: multipart/form-data; boundary=%s\r\n" "Cache-Control: no-cache\r\n\r\n", szBoundary); bRet = ::HttpAddRequestHeaders(hRequest, szRequestHeaders, ::lstrlen(szRequestHeaders), HTTP_ADDREQ_FLAG_ADD); if (FALSE == bRet) { Https_ShowError("HttpAddRequestHeaders"); break; } // 构造将要发送的数据包格式 // 1. 文件数据前缀(可选) char szPreData[1024] = { 0 }; int iPostValue = 7758; char szUploadFileName[] = "C:\\User\\DemonGan.txt"; ::RtlZeroMemory(szPreData, 1024); ::wsprintf(szPreData, "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%d\r\n" "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n" // 二进制数据流 --> (jpg || jpeg file)"Content-Type: image/pjpeg\r\n\r\n" --> // (gif file)"Content-Type: image/gif\r\n\r\n" --> (png file)"Content-Type: image/x-png\r\n\r\n" "Content-Type: application/octet-stream\r\n\r\n", szBoundary, "MyValue", iPostValue, szBoundary, "MyUploadFileName", szUploadFileName); // 2. 上传主体内容数据(可以是多个文件数据, 但是要用 szBoundary 分开) // ----> pUploadData // 3.结束后缀(可选) char szSufData[1024] = { 0 }; ::RtlZeroMemory(szSufData, 1024); ::wsprintf(szSufData, "\r\n--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n--%s--", szBoundary, "MyUploadOver", "OVER", szBoundary); // 计算数据包的大小 = 前缀数据包大小 + 主体数据大小 + 后缀数据包大小 // 并 发送请求, 告诉服务器传输数据的总大小 DWORD dwPostDataSize = ::lstrlen(szPreData) + dwUploadDataSize + ::lstrlen(szSufData); // DWORD dwPostDataSize = dwUploadDataSize; INTERNET_BUFFERS internetBuffers = { 0 }; ::RtlZeroMemory(&internetBuffers, sizeof(internetBuffers)); internetBuffers.dwStructSize = sizeof(internetBuffers); internetBuffers.dwBufferTotal = dwPostDataSize; bRet = ::HttpSendRequestEx(hRequest, &internetBuffers, NULL, 0, 0); if (FALSE == bRet) { if (ERROR_INTERNET_INVALID_CA == ::GetLastError()) { DWORD dwFlags = 0; DWORD dwBufferSize = sizeof(dwFlags); // 获取INTERNET_OPTION_SECURITY_FLAGS标志 bRet = ::InternetQueryOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, &dwBufferSize); if (bRet) { // 设置INTERNET_OPTION_SECURITY_FLAGS标志 // 忽略未知的证书颁发机构 dwFlags = dwFlags | SECURITY_FLAG_IGNORE_UNKNOWN_CA; bRet = ::InternetSetOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags)); if (bRet) { // 再次发送请求 bRet = ::HttpSendRequestEx(hRequest, &internetBuffers, NULL, 0, 0); if (FALSE == bRet) { Https_ShowError("HttpSendRequestEx"); break; } } else { Https_ShowError("InternetSetOption"); break; } } else { Https_ShowError("InternetQueryOption"); break; } } else { Https_ShowError("HttpSendRequestEx"); break; } } // 发送数据 // 发送前缀数据(可选) bRet = ::InternetWriteFile(hRequest, szPreData, ::lstrlen(szPreData), &dwRet); if (FALSE == bRet) { Https_ShowError("InternetWriteFile1"); break; } // 发送主体内容数据 bRet = ::InternetWriteFile(hRequest, pUploadData, dwUploadDataSize, &dwRet); if (FALSE == bRet) { Https_ShowError("InternetWriteFile2"); break; } // 发送后缀数据(可选) bRet = ::InternetWriteFile(hRequest, szSufData, ::lstrlen(szSufData), &dwRet); if (FALSE == bRet) { Https_ShowError("InternetWriteFile3"); break; } // 发送完毕, 结束请求 bRet = ::HttpEndRequest(hRequest, NULL, 0, 0); if (FALSE == bRet) { Https_ShowError("HttpEndRequest"); break; } // 接收来自服务器响应的数据 // 接收响应的报文信息头(Get Response Header) pResponseHeaderIInfo = new unsigned char[dwResponseHeaderIInfoSize]; if (NULL == pResponseHeaderIInfo) { break; } ::RtlZeroMemory(pResponseHeaderIInfo, dwResponseHeaderIInfoSize); bRet = ::HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, pResponseHeaderIInfo, &dwResponseHeaderIInfoSize, NULL); if (FALSE == bRet) { Https_ShowError("HttpQueryInfo"); break; }#ifdef _DEBUG printf("[HTTPS_Upload_ResponseHeaderIInfo]\n\n%s\n\n", pResponseHeaderIInfo);#endif // 从 中字段 "Content-Length: "(注意有个空格) 获取数据长度 bRet = Https_GetContentLength((char *)pResponseHeaderIInfo, &dwResponseBodyDataSize); if (FALSE == bRet) { break; } // 接收报文主体内容(Get Response Body) pBuf = new BYTE[dwBufSize]; if (NULL == pBuf) { break; } pResponseBodyData = new BYTE[dwResponseBodyDataSize]; if (NULL == pResponseBodyData) { break; } ::RtlZeroMemory(pResponseBodyData, dwResponseBodyDataSize); do { ::RtlZeroMemory(pBuf, dwBufSize); bRet = ::InternetReadFile(hRequest, pBuf, dwBufSize, &dwRet); if (FALSE == bRet) { Https_ShowError("InternetReadFile"); break; } ::RtlCopyMemory((pResponseBodyData + dwOffset), pBuf, dwRet); dwOffset = dwOffset + dwRet; } while (dwResponseBodyDataSize > dwOffset); } while (FALSE); // 关闭 释放 if (NULL != pResponseBodyData) { delete[]pResponseBodyData; pResponseBodyData = NULL; } if (NULL != pBuf) { delete[]pBuf; pBuf = NULL; } if (NULL != pResponseHeaderIInfo) { delete[]pResponseHeaderIInfo; pResponseHeaderIInfo = NULL; } if (NULL != hRequest) { ::InternetCloseHandle(hRequest); hRequest = NULL; } if (NULL != hConnect) { ::InternetCloseHandle(hConnect); hConnect = NULL; } if (NULL != hInternet) { ::InternetCloseHandle(hInternet); hInternet = NULL; } return bRet;}
    3. ASP接收文件程序mytest1.asp<%'ASP文件接收程序dim file,obj,fsofile = Trim(Request("file"))If file = "" Then Response.Write "上传错误文件名未指定": Response.EndSet obj = Server.CreateObject("Adodb.Stream")With obj.Type = 1.Mode = 3.Open.Write Request.BinaryRead(Request.TotalBytes).Position = 0.SaveToFile Server.Mappath(file), 2.CloseEnd WithSet obj = NothingSet fso = CreateObject("Scripting.FileSystemObject")If fso.FileExists(Server.Mappath(file)) ThenResponse.Write "上传成功"ElseResponse.Write "上传失败"End IfSet fso = Nothing%>
    程序测试在main函数中,调用上述封装好的函数,上传文件进行测试。
    main函数为:
    int _tmain(int argc, _TCHAR* argv[]){ char szHttpsUploadUrl[] = "https://192.168.28.137/mytest1.asp?file=520.zip"; char szHttpsUploadFileName[] = "C:\\Users\\Desktop\\520.zip"; BYTE *pHttpsUploadData = NULL; DWORD dwHttpsUploadDataSize = 0; DWORD dwRets = 0; // 打开文件 HANDLE hFiles = ::CreateFile(szHttpsUploadFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL); if (INVALID_HANDLE_VALUE == hFiles) { return 1; } // 获取文件大小 dwHttpsUploadDataSize = ::GetFileSize(hFiles, NULL); // 读取文件数据 pHttpsUploadData = new BYTE[dwHttpsUploadDataSize]; ::ReadFile(hFiles, pHttpsUploadData, dwHttpsUploadDataSize, &dwRets, NULL); dwHttpsUploadDataSize = dwRets; // 上传数据 if (FALSE == Https_Upload(szHttpsUploadUrl, pHttpsUploadData, dwHttpsUploadDataSize)) { return 2; } // 释放内存 delete []pHttpsUploadData; pHttpsUploadData = NULL; ::CloseHandle(hFiles); system("pause"); return 0;}
    测试结果:
    根据传输返回的Response Header可知,数据上传成功。

    查看ASP服务器目录,成功获取17795KB大小的“mmyyyytestupload1”文件。

    总结相对与HTTPS的文件下载,HTTPS文件上传需要注意两点。一是要注意HttpOpenRequest 中要打开“POST”请求;二是在构造请求头信息的时候,一定要严格按照协议格式去写,具体格式可以到网上搜索。
    参考参考自《Windows黑客编程技术详解》一书
    2 回答 2018-12-21 08:58:16
  • 基于WinInet实现的HTTP文件下载

    背景之前写过的网络数据传输的小程序都是基于Socket去写的,所以,如果要用Socket传输数据到网站,还需要根据域名获取服务器的IP地址,然后再建立连接,传输数据。虽然,Socket也可以实现网络传输,但是,总感觉不是很方便。所以,后来随着知识面的拓展,了解到Windows还专门提供了WinInet网络库,封装了比较简便的接口,去实现HTTP和FTP等传输协议的数据传输。
    本文就是基于WinInet网络库,实现通过HTTP传输协议下载文件功能的小程序。现在,就把开发过程的思路和编程分享给大家。
    主要函数介绍介绍HTTP下载文件使用到的主要的WinInet库中的API函数。
    1. InternetOpen介绍
    函数声明
    HINTERNET InternetOpen(In LPCTSTR lpszAgent,In DWORD dwAccessType,In LPCTSTR lpszProxyName,In LPCTSTR lpszProxyBypass,In DWORD dwFlags);
    参数lpszAgent指向一个空结束的字符串,该字符串指定调用WinInet函数的应用程序或实体的名称。使用此名称作为用户代理的HTTP协议。dwAccessType指定访问类型,参数可以是下列值之一:



    Value
    Meaning




    INTERNET_OPEN_TYPE_DIRECT
    使用直接连接网络


    INTERNET_OPEN_TYPE_PRECONFIG
    获取代理或直接从注册表中的配置,使用代理连接网络


    INTERNETOPEN_TYPE_PRECONFIG WITH_NO_AUTOPROXY
    获取代理或直接从注册表中的配置,并防止启动Microsoft JScript或Internet设置(INS)文件的使用


    INTERNET_OPEN_TYPE_PROXY
    通过代理的请求,除非代理旁路列表中提供的名称解析绕过代理,在这种情况下,该功能的使用



    lpszProxyName指针指向一个空结束的字符串,该字符串指定的代理服务器的名称,不要使用空字符串;如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY,则此参数应该设置为NULL。
    lpszProxyBypass指向一个空结束的字符串,该字符串指定的可选列表的主机名或IP地址。如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY的 ,参数省略则为NULL。
    dwFlags参数可以是下列值的组合:



    VALUE
    MEANING




    INTERNET_FLAG_ASYNC
    使异步请求处理的后裔从这个函数返回的句柄


    INTERNET_FLAG_FROM_CACHE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND


    INTERNET_FLAG_OFFLINE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND



    返回值成功:返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinInet函数。失败:返回NULL。

    2. InternetConnect介绍
    函数声明
    HINTERNET WINAPI InternetConnect( HINTERNET hInternet, LPCTSTR lpszServerName, INTERNET_PORT nServerPort, LPCTSTR lpszUserName, LPCTSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD dwContext);
    参数说明hInternet:由InternetOpen返回的句柄。lpszServerName:连接的ip或者主机名nServerPort:连接的端口。lpszUserName:用户名,如无置NULL。lpszPassword:密码,如无置NULL。dwService:使用的服务类型,可以使用以下

    INTERNET_SERVICE_FTP = 1:连接到一个 FTP 服务器上INTERNET_SERVICE_GOPHER = 2INTERNET_SERVICE_HTTP = 3:连接到一个 HTTP 服务器上
    dwFlags:文档传输形式及缓存标记。一般置0。dwContext:当使用回叫信号时, 用来识别应用程序的前后关系。返回值成功返回非0。如果返回0。要InternetCloseHandle释放这个句柄。

    3. HttpOpenRequest介绍
    函数声明
    HINTERNET HttpOpenRequest( _In_ HINTERNET hConnect, _In_ LPCTSTR lpszVerb, _In_ LPCTSTR lpszObjectName, _In_ LPCTSTR lpszVersion, _In_ LPCTSTR lpszReferer, _In_ LPCTSTR *lplpszAcceptTypes, _In_ DWORD dwFlags, _In_ DWORD_PTR dwContext);
    参数
    hConnect:由InternetConnect返回的句柄。
    lpszVerb:一个指向某个包含在请求中要用的动词的字符串指针。如果为NULL,则使用“GET”。
    lpszObjectName:一个指向某个包含特殊动词的目标对象的字符串的指针。通常为文件名称、可执行模块或者查找标识符。
    lpszVersion:一个指向以null结尾的字符串的指针,该字符串包含在请求中使用的HTTP版本,Internet Explorer中的设置将覆盖该参数中指定的值。如果此参数为NULL,则该函数使用1.1或1.0的HTTP版本,这取决于Internet Explorer设置的值。
    lpszReferer:一个指向指定了包含着所需的URL (pstrObjectName)的文档地址(URL)的指针。如果为NULL,则不指定HTTP头。
    lplpszAcceptTypes:一个指向某空终止符的字符串的指针,该字符串表示客户接受的内容类型。如果该字符串为NULL,服务器认为客户接受“text/*”类型的文档 (也就是说,只有纯文本文档,并且不是图片或其它二进制文件)。内容类型与CGI变量CONTENT_TYPE相同,该变量确定了要查询的含有相关信息的数据的类型,如HTTP POST和PUT。
    dwFlags:dwFlags的值可以是下面一个或者多个。



    价值
    说明




    INTERNET_FLAG_DONT_CACHE
    不缓存的数据,在本地或在任何网关。 相同的首选值INTERNET_FLAG_NO_CACHE_WRITE。


    INTERNET_FLAG_EXISTING_CONNECT
    如果可能的话,重用现有的连接到每个服务器请求新的请求而产生的InternetOpenUrl创建一个新的会话。 这个标志是有用的,只有对FTP连接,因为FTP是唯一的协议,通常在同一会议执行多个操作。 在Win 32 API的缓存一个单一的Internet连接句柄为每个HINTERNET处理产生的InternetOpen。


    INTERNET_FLAG -超链接
    强制重载如果没有到期的时间也没有最后修改时间从服务器在决定是否加载该项目从网络返回。


    INTERNET_FLAG_IGNORE_CERT_CN_INVALID
    禁用的Win32上网功能的SSL /厘为基础的打击是从给定的请求服务器返回的主机名称证书检查。 Win32的上网功能用来对付证书由匹配主机名和HTTP请求一个简单的通配符规则比较简单的检查。


    INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
    禁用的Win32上网功能的SSL /厘为基础的HTTP请求适当的日期,证书的有效性检查。


    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP
    禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许对HTTP重定向的URL从HTTPS。


    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS
    禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许从HTTP重定向到HTTPS网址。


    INTERNET_FLAG_KEEP_CONNECTION
    使用保持活动语义,如果有的话,给HTTP请求连接。 这个标志是必需的微软网络(MSN),NT LAN管理器(NTLM)和其他类型的身份验证。


    INTERNET_FLAG_MAKE_PERSISTENT
    不再支持。


    INTERNET_FLAG_MUST_CACHE_REQUEST
    导致一个临时文件如果要创建的文件不能被缓存。 相同的首选值INTERNET_FLAG_NEED_FILE。


    INTERNET_FLAG_NEED_FILE
    导致一个临时文件如果要创建的文件不能被缓存。


    INTERNET_FLAG_NO_AUTH
    不尝试HTTP请求身份验证自动。


    INTERNET_FLAG_NO_AUTO_REDIRECT
    不自动处理HTTP请求重定向只。


    INTERNET_FLAG_NO_CACHE_WRITE
    不缓存的数据,在本地或在任何网关。


    INTERNET_FLAG_NO_COOKIES
    不会自动添加Cookie标头的请求,并不会自动添加返回的Cookie的HTTP请求的Cookie数据库。


    INTERNET_FLAG_NO_UI
    禁用cookie的对话框。


    INTERNET_FLAG_PASSIVE
    使用被动FTP语义FTP文件和目录。


    INTERNET_FLAG_RAW_DATA
    返回一个数据WIN32_FIND_DATA结构时,FTP目录检索信息。 如果这个标志,或者未指定代理的电话是通过一个CERN,InternetOpenUrl返回的HTML版本的目录。


    INTERNET_FLAG_PRAGMA_NOCACHE
    强制要求被解决的原始服务器,即使在代理缓存的副本存在。


    INTERNET_FLAG_READ_PREFETCH
    该标志目前已停用。


    INTERNET_FLAG_RELOAD
    从导线获取数据,即使是一个本地缓存。


    INTERNET_FLAG_RESYNCHRONIZE
    重整HTTP资源,如果资源已被修改自上一次被下载。 所有的FTP资源增值。


    INTERNET_FLAG_SECURE
    请确保在使用SSL或PCT线交易。 此标志仅适用于HTTP请求。



    dwContext:OpenRequest操作的上下文标识符。

    4. InternetReadFile介绍
    函数声明
    BOOL InternetReadFile( __in HINTERNET hFile,__out LPVOID lpBuffer,__in DWORD dwNumberOfBytesToRead,__out LPDWORD lpdwNumberOfBytesRead);
    参数

    hFile[in]
    由InternetOpenUrl,FtpOpenFile, 或HttpOpenRequest函数返回的句柄.
    lpBuffer[out]
    缓冲器指针
    dwNumberOfBytesToRead[in]
    欲读数据的字节量。
    lpdwNumberOfBytesRead[out]
    接收读取字节量的变量。该函数在做任何工作或错误检查之前都设置该值为零

    返回值成功:返回TRUE,失败,返回FALSE

    程序设计原理该部分讲解下程序设计的原理以及实现的流程,让大家有个宏观的认识。原理是:

    首先,使用 InternetCrackUrl 函数分解URL,从URL中提取网站的域名、路径以及URL的附加信息等。关于 InternetCrackUrl 分解URL的介绍和实现,可以参考本站上的的 “URL分解之InternetCrackUrl” 这篇文章
    使用 InternetOpen 建立会话,获取会话句柄
    使用 InternetConnect 与网站建立连接,获取连接句柄
    设置HTTP的访问标志,使用 HttpOpenRequest 打开HTTP的“GET”请求
    使用 HttpSendRequest 发送访问请求
    根据返回的Response Header的数据中,获取将要接收数据的长度
    使用 InternetReadFile 接收数据
    关闭句柄,释放资源

    其中,上面的 8 个步骤中,要注意的就是第 6 步,获取返回的数据长度,是从响应信息头中的获取“Content-Length: ”(注意有个空格)这个字段的数据。
    编程实现1. 导入WinInet库#include <WinInet.h>#pragma comment(lib, "WinInet.lib")
    2. HTTP文件下载编程实现// 数据下载// 输入:下载数据的URL路径// 输出:下载数据内容、下载数据内容长度BOOL Http_Download(char *pszDownloadUrl, BYTE **ppDownloadData, DWORD *pdwDownloadDataSize){ // INTERNET_SCHEME_HTTPS、INTERNET_SCHEME_HTTP、INTERNET_SCHEME_FTP等 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(szScheme, MAX_PATH); ::RtlZeroMemory(szHostName, MAX_PATH); ::RtlZeroMemory(szUserName, MAX_PATH); ::RtlZeroMemory(szPassword, MAX_PATH); ::RtlZeroMemory(szUrlPath, MAX_PATH); ::RtlZeroMemory(szExtraInfo, MAX_PATH); // 分解URL if (FALSE == Http_UrlCrack(pszDownloadUrl, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, MAX_PATH)) { return FALSE; } // 数据下载 HINTERNET hInternet = NULL; HINTERNET hConnect = NULL; HINTERNET hRequest = NULL; DWORD dwOpenRequestFlags = 0; BOOL bRet = FALSE; unsigned char *pResponseHeaderIInfo = NULL; DWORD dwResponseHeaderIInfoSize = 2048; BYTE *pBuf = NULL; DWORD dwBufSize = 64 * 1024; BYTE *pDownloadData = NULL; DWORD dwDownloadDataSize = 0; DWORD dwRet = 0; DWORD dwOffset = 0; do { // 建立会话 hInternet = ::InternetOpen("WinInetGet/0.1", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (NULL == hInternet) { Http_ShowError("InternetOpen"); break; } // 建立连接 hConnect = ::InternetConnect(hInternet, szHostName, INTERNET_DEFAULT_HTTP_PORT, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0); if (NULL == hConnect) { Http_ShowError("InternetConnect"); break; } // 打开并发送HTTP请求 dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI; if (0 < ::lstrlen(szExtraInfo)) { // 注意此处的连接 ::lstrcat(szUrlPath, szExtraInfo); } hRequest = ::HttpOpenRequest(hConnect, "GET", szUrlPath, NULL, NULL, NULL, dwOpenRequestFlags, 0); if (NULL == hRequest) { Http_ShowError("HttpOpenRequest"); break; } // 发送请求 bRet = ::HttpSendRequest(hRequest, NULL, 0, NULL, 0); if (FALSE == bRet) { Http_ShowError("HttpSendRequest"); break; } // 接收响应的报文信息头(Get Response Header) pResponseHeaderIInfo = new unsigned char[dwResponseHeaderIInfoSize]; if (NULL == pResponseHeaderIInfo) { break; } ::RtlZeroMemory(pResponseHeaderIInfo, dwResponseHeaderIInfoSize); bRet = ::HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, pResponseHeaderIInfo, &dwResponseHeaderIInfoSize, NULL); if (FALSE == bRet) { Http_ShowError("HttpQueryInfo"); break; }#ifdef _DEBUG printf("[HTTP_Download_ResponseHeaderIInfo]\n\n%s\n\n", pResponseHeaderIInfo);#endif // 从 中字段 "Content-Length: "(注意有个空格) 获取数据长度 bRet = Http_GetContentLength((char *)pResponseHeaderIInfo, &dwDownloadDataSize); if (FALSE == bRet) { break; } // 接收报文主体内容(Get Response Body) pBuf = new BYTE[dwBufSize]; if (NULL == pBuf) { break; } pDownloadData = new BYTE[dwDownloadDataSize]; if (NULL == pDownloadData) { break; } ::RtlZeroMemory(pDownloadData, dwDownloadDataSize); do { ::RtlZeroMemory(pBuf, dwBufSize); bRet = ::InternetReadFile(hRequest, pBuf, dwBufSize, &dwRet); if (FALSE == bRet) { Http_ShowError("InternetReadFile"); break; } ::RtlCopyMemory((pDownloadData + dwOffset), pBuf, dwRet); dwOffset = dwOffset + dwRet; } while (dwDownloadDataSize > dwOffset); // 返回数据 *ppDownloadData = pDownloadData; *pdwDownloadDataSize = dwDownloadDataSize; } while (FALSE); // 关闭 释放 if (NULL != pBuf) { delete[]pBuf; pBuf = NULL; } if (NULL != pResponseHeaderIInfo) { delete[]pResponseHeaderIInfo; pResponseHeaderIInfo = NULL; } if (NULL != hRequest) { ::InternetCloseHandle(hRequest); hRequest = NULL; } if (NULL != hConnect) { ::InternetCloseHandle(hConnect); hConnect = NULL; } if (NULL != hInternet) { ::InternetCloseHandle(hInternet); hInternet = NULL; } return bRet;}
    程序测试在main函数中,调用上述封装好的函数,下载文件进行测试。
    main函数为:
    int _tmain(int argc, _TCHAR* argv[]){ char szHttpDownloadUrl[] = "http://www.demongan.com/source/ccc/dasanxia/520.zip"; BYTE *pHttpDownloadData = NULL; DWORD dwHttpDownloadDataSize = 0; // HTTP下载 if (FALSE == Http_Download(szHttpDownloadUrl, &pHttpDownloadData, &dwHttpDownloadDataSize)) { return 1; } // 将下载数据保存成文件 Http_SaveToFile("http_downloadsavefile.zip", pHttpDownloadData, dwHttpDownloadDataSize); // 释放内存 delete []pHttpDownloadData; pHttpDownloadData = NULL; system("pause"); return 0;}
    测试结果:
    根据返回的Response Header知道,成功下载22761460字节大小的数据。

    查看目录,有22228KB大小的“http_downloadsavefile.zip”文件成功生成,所以,数据下载成功。

    总结基于WinInet库的HTTP下载文件原理并不复杂,但是,因为涉及较多的API,每个API的执行都需要依靠上一个API成功执行返回的数据。所以,要仔细检查。如果出错,也要耐心调试,根据返回的错误码,结合程序前后部分的代码,仔细分析原因。
    参考参考自《Windows黑客编程技术详解》一书
    1 回答 2018-12-20 17:46:07
  • 上传资源,获取积分

    上传资源,获取积分“WRITE-BUG技术共享平台”是一个专注于校园计算机技术交流共享的平台,面向的主要目标群体是我们计算机相关专业的大学生。在平台上,大家既可以交流学校课内学习的心得体会,也可以分享自己课外积累的技术经验。
    为了充实平台的资源库,更好地服务于各位同学,平台决定推出“众源计划”,有偿征集同学们自己计算机专业的作业、课程设计或是毕业设计等资源。“众源计划”的主要目的是创建一个具有一定规模的“技术资源库”,资源库里的每一份资源,都必须有详细的开发文档和可编译的源代码。
    作业、课程设计或是毕业设计等资源是同学们自己辛苦付出的成果,也是自己技术进步的见证。这部分资源通常都有详细的开发文档和完整的程序源代码,能够帮助其他初学者更好地消化和吸收将要学习的技术,降低学习门槛。所以,平台决定积分奖励征集这些资源。
    具体要求活动对象
    在校或者已毕业的计算机相关专业大学生,院校不限
    奖励方式
    资源上传并审核通过后,根据资源质量,奖励每贴 10 - 100 点积分
    上传流程
    会员登录自己的账号上传资源
    资源上传后,管理员会在 24 小时之内审核资源
    审核通过后,管理员会立即发放奖励积分至所对应账户

    审核重点
    重点审核资源是否具有详细的文档和完整的源代码
    审查资源是否原创,切勿重复提交

    资源要求“众源计划”仅对两类资源进行积分奖励征集,分别是“课内资源”和“课外资源”,各类资源具体要求如下所示。

    课内资源

    内容范围:计算机相关专业课内的毕业设计、课程设计、小学期、大作业等课程内开发的程序,程序包括游戏、PC程序、APP、网站或者其他软件形式
    内容要求:资源必须要包括完整的程序源代码和详细的开发文档或报告
    具体“课内资源”征集程序列表见附录一

    课外资源

    内容范围:计算机相关专业的课外自己主导研究游戏、项目、竞赛、个人研究等,区别于课程设计和毕业设计等课内资源
    内容要求:资源必须要包括完整的程序源代码和详细的开发文档或报告
    具体“课外资源”征集程序列表见附录二


    附录一注意:“众源计划”的题目范围包括且不限于以下题目

    汇编语言课程设计题目列表

    屏幕保护程序分类统计字符个数计算机钢琴程序字符图形程序音乐盒程序电子闹钟程序俄罗斯方块打字游戏图形变换程序吃豆子程序其他
    C语言课程设计题目列表

    学生成绩管理系统图书信息管理系统设计销售管理管理系统飞机订票管理系统教师评价系统学校运动会管理系统文本文件加密技术英语字典电话簿管理系统流星雨的实现其他
    C++语言课程设计题目列表

    学生学籍管理系统高校人员信息管理系统学生成绩管理系统车辆管理系统职工工作量统计系统学生考勤管理系统单项选择题标准化考试系统图书管理系统超市商品管理系统模拟ATM机存取款管理系统其他
    JAVA语言课程设计题目列表

    简单投票管理系统数学练习题目自动生成系统华容道小游戏电子英汉词典加密与解密标准化考试系统排球比赛计分系统学籍管理系统绘图系统图书信息管理系统其他
    C#语言课程设计题目列表

    学生信息管理系统学生综合测评系统图书管理系统学校运动会管理系统个人通讯录管理系统教师工资管理系统教师工作量管理系统趣味小游戏物资库存管理系统图形图像处理系统其他
    JSP语言课程设计题目列表

    微博系统基于web的学生信息管理系统在线计算机等级考试报名系统在线问卷调查系统网上销售系统论坛系统图书借阅管理系统网上购物系统工资管理系统酒店管理系统其他
    数据结构与算法课程设计题目列表

    设计哈希表实现电话号码查询系统电报压缩/解压缩系统电费核算系统机房计费管理系统公交线路查询系统用二叉平衡树实现图书管理系统运动会赛事安排动态表达式求值用线性结构实现学生成绩管理求解迷宫问题其他
    编译原理课程设计题目列表

    First集和Follow集生成算法模拟LL(1)分析过程模拟FirstVT集和LastVT集生成算法模拟算符优先分析表生成模拟算符优先分析过程模拟LR分析过程模拟PL/0语言的词法分析程序C语言的预处理程序自动机的状态转换图表示数组越界检查工具其他
    操作系统课程设计题目列表

    动态分区分配方式的模拟进程调度模拟算法请求调页存储管理方式的模拟P、V操作及进程同步的实现银行家算法SPOOLING假脱机输出的模拟程序文件系统设计动态不等长存储资源分配算法磁盘调度算法处理机调度算法模拟其他
    数据库课程设计题目列表

    高校学籍管理系统在线投稿审稿管理系统产品销售管理系统高校人力资源管理系统高校课程管理系统酒店客房管理系统报刊订阅管理系统医药销售管理系统学生学籍管理系统餐饮管理系统其他
    计算机网络课程设计题目列表

    TCP通信功能实现网络游戏的开发基于UDP协议网上聊天程序Ping 程序的实现数据包的捕获与分析FTP客户端设计包过滤防火墙的设计与实现简单的端口扫描器简单Web服务器的设计与实现HTTP客户端的设计与实现其他
    软件工程课程设计题目列表

    学校教材订购系统网上选课管理系统简易办公系统图书馆管理系统校园交流论坛网站超市收银系统ATM柜员机模拟程序企业办公自动化管理系统学生成绩管理系统进销存管理系统其他
    VC++程序设计课程设计题目列表

    模拟时钟程序单向链表的操作演示程序电影院售票系统俄罗斯方块五子棋24点游戏背单词软件的设计与实现酒店管理系统餐厅就餐管理系统吹泡泡游戏其他
    其他课程设计

    PHP语言课程设计PYTHON语言课程设计计算机图形学课程设计机器学习课程设计密码学课程设计其他

    附录二注意:“众源计划”的题目范围包括且不限于以下题目

    人脸识别系统车牌识别系统旅游自助APP疲劳驾驶识别检测系统考试管理系统WINDOWS驱动级安全防御系统WINDOWS平台逆向调试器坦克大战小游戏情感分析系统人机博弈的国际象棋游戏其他
    最终解释权归 WRITE-BUG技术共享平台 所有
    11 回答 2018-12-20 10:09:45
显示 15 到 30 ,共 15 条
eject