分类

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

技术文章列表

  • 修改注册表的方式实现程序开机自启动

    背景想必实现程序开机自启动,是很常见的功能了。无论是恶意程序,还是正常的应用软件,都会提供这个功能,方便用户的使用。程序开机自启动,顾名思义,就是计算机开机后,不用人为地去运行程序,程序就可以自己运行起来。对于这个功能的,一直都是杀软重点监测的地方。因为,对于病毒来说,重要的不是如何被破坏,而是如何启动。
    在过去写的大大小小的程序中,我也实现过程序自启动的功能。现在,我把这些自启动功能的实现方式进行下总结。常见的方式有:修改开机自启动注册表、开机自启动目录、创建开机自启计划任务、创建开机自启系统服务等方式。现在对这些技术一一进行分析,并形成文档分享给大家。本文介绍两种修改注册表实现开机自启动的方式,其它的实现方式可以搜索我写的相关系列的文档。
    函数介绍RegOpenKeyEx 函数
    用于打开一个指定的注册表键。
    函数声明
    LONG RegOpenKeyEx( HKEY hKey, // 需要打开的主键的名称 LPCTSTR lpSubKey, //需要打开的子键的名称 DWORD ulOptions, // 保留,设为0 REGSAM samDesired, // 安全访问标记,也就是权限 PHKEY phkResult // 得到的将要打开键的句柄);
    参数

    hKey[输入] 当前打开或者以下预定义的键。​ HKEY_CLASSES_ROOT​ HKEY_CURRENT_USER​ HKEY_LOCAL_MACHINE​ HKEY_USERS​ HKEY_CURRENT_CONFIG
    lpSubKey[输入] 指向一个非中断字符串包含将要打开键的名称。如果参数设置为NULL 或者指向一个空字符串,过程将打开一个新的句柄由hKey参数确定的值。这种情况下,过程不会关闭先前已经打开的句柄。
    ulOptions保留,必须设置为 0。
    samDesired[输入] 对指定键希望得到的访问权限的访问标记。 这个参数可以使下列值的联合。




    VALUE
    MEANING




    KEY_CREATE_LINK
    准许生成符号键


    KEY_CREATE_SUB_KEY
    准许生成子键


    KEY_ENUMERATE_SUB_KEYS
    准许生成枚举子键


    KEY_EXECUTE
    准许进行读操作


    KEY_NOTIFY
    准许更换通告


    KEY_QUERY_VALUE
    准许查询子键


    KEY_ALL_ACCESS
    提供完全访问,是上面数值的组合:KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, KEY_NOTIFY, KEY_CREATE_SUB_KEY, KEY_CREATE_LINK, KEY_SET_VALUE


    KEY_READ
    是KEY_QUERY_VALUE、KEY_ENUMERATE_SUB_KEYS、KEY_NOTIFY的组合


    KEY_SET_VALUE
    准许设置子键的数值


    KEY_WRITE
    是KEY_SET_VALUE、KEY_CREATE_SUB_KEY的组合




    phkResult
    [输出]指向一个变量的指针,该变量用来保存打开注册表键的句柄。如果不再使用返回的句柄,则调用RegCloseKey来关闭它。

    备注

    不像 RegCreateKeyEx 函数,当指定键不存在 RegOpenKeyEx函数不创建新键。
    返回值

    返回值 如果函数调用成功,则返回0(ERROR_SUCCESS)。否则,返回值为文件WINERROR.h中定义的一个非零的错误代码。

    RegSetValueEx 函数
    有名称值的数据和类型时设置指定值的数据和类型。
    函数声明
    LONG RegSetValueEx( HKEY hKey, LPCTSTR lpValueName, DWORD Reserved, DWORD dwType, CONST BYTE *lpData, DWORD cbData);
    参数

    hKey
    一个已打开项的句柄,或指定一个标准项名
    lpValueName
    指向一个字符串的指针,该字符串包含了欲设置值的名称。若拥有该值名称的值并不存在于指定的注册表项中,则此函数将其加入到该项。如果此值是NULL,或指向空字符串,则此函数为该项的默认值或未命名值设置类型和数据。
    Reserved
    保留值,必须强制为0。
    dwType
    指定将被存储的数据类型,该参数可以为:




    VALUE
    MeANING




    REG_BINARY
    任何形式的二进制数据


    REG_DWORD
    一个32位的数字


    REG_DWORD_LITTLE_ENDIAN
    一个“低字节在前”格式的32位数字


    REG_DWORD_BIG_ENDIAN
    一个“高字节在前”格式的32位数字


    REG_EXPAND_SZ
    一个以0结尾的字符串,该字符串包含对环境变量(如“%PAHT”)的


    REG_LINK
    一个Unicode格式的带符号链接


    REG_MULTI_SZ
    一个以0结尾的字符串数组,该数组以连接两个0为终止符


    REG_NONE
    未定义值类型


    REG_RESOURCE_LIST
    一个设备驱动器资源列表


    REG_SZ
    一个以0结尾的字符串




    lpData
    指向一个缓冲区,该缓冲区包含了欲为指定值名称存储的数据。
    cbData
    指定由lpData参数所指向的数据的大小,单位是字节。

    返回值

    返回0(ERROR_SUCCESS)表示成功;返回其他任何值都代表一个错误代码。

    RegCloseKey 函数
    函数功能是释放指定注册键的句柄。
    函数声明
    LONG RegCloseKey( HKEY hKey // 释放键的句柄);
    参数

    hKey
    [输入] 想要关闭的已经打开的键。

    返回值

    如果过程执行成功,返回值是 ERROR_SUCCESS。如果功能失败,返回一个非零值,错误码在 Winerror.h 定义。

    实现原理理解修改注册表实现开机自启动功能的一个重要的知识前提就是,Windows提供了专门的开机自启动注册表,每次开机完成后,都会在这个注册表键下遍历键值,获取键值中的程序路径,并创建进程启动程序。所以,要想修改注册表实现开机自启动,那么,我们只需要在这个注册表键下添加我们想要设置自启动程序的程序路径就可以了。
    本文介绍所谓的修改注册表的两种方式,就是指这个特殊的注册表键路径的区别而已。也就是说,Windows不单单只有一个开机自启动注册表键,而是有多个。本文只介绍其中常见的两个路径,分别是:
    HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run以及
    HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run这二者路径之间的区别,仅是主键的区别而已。一个是HKEY_CURRENT_USER,另一个是HKEY_LOCAL_MACHINE。但是,这两者功能是相似的。都可以实现开机自启动。
    那么,经过了解上述知识点后,应该可以知道,我们程序实现的原理就是,编程实现对上面两个注册表键设置一个新的键值,写入我们自启动程序的路径。
    其中,需要注意的是,在编程实现上,要修改HKEY_LOCAL_MACHINE主键的注册表,要求程序要有管理员权限的。而修改HKEY_CURRENT_USER主键的注册表,只需要用户默认权限就可以实现。
    编码实现HKEY_CURRENT_USER 实现方式BOOL Reg_CurrentUser(char *lpszFileName, char *lpszValueName){ // 默认权限 HKEY hKey; // 打开注册表键 if (ERROR_SUCCESS != ::RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hKey)) { ShowError("RegOpenKeyEx"); return FALSE; } // 修改注册表值,实现开机自启 if (ERROR_SUCCESS != ::RegSetValueEx(hKey, lpszValueName, 0, REG_SZ, (BYTE *)lpszFileName, (1 + ::lstrlen(lpszFileName)))) { ::RegCloseKey(hKey); ShowError("RegSetValueEx"); return FALSE; } // 关闭注册表键 ::RegCloseKey(hKey); return TRUE;}
    HKEY_LOCAL_MACHINE 实现方式BOOL Reg_LocalMachine(char *lpszFileName, char *lpszValueName){ // 管理员权限 HKEY hKey; // 打开注册表键 if (ERROR_SUCCESS != ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hKey)) { ShowError("RegOpenKeyEx"); return FALSE; } // 修改注册表值,实现开机自启 if (ERROR_SUCCESS != ::RegSetValueEx(hKey, lpszValueName, 0, REG_SZ, (BYTE *)lpszFileName, (1 + ::lstrlen(lpszFileName)))) { ::RegCloseKey(hKey); ShowError("RegSetValueEx"); return FALSE; } // 关闭注册表键 ::RegCloseKey(hKey); return TRUE;}
    程序测试在 main 函数中,调用上述封装好的函数,进行测试。
    main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ // 第一种方式修改注册表:HKEY_CURRENT_USER if (FALSE == Reg_CurrentUser("C:\\Users\\DemonGan\\Desktop\\520_1.exe", "520")) { printf("Reg_CurrentUser Error!\n"); } // 第二种方式修改注册表:HKEY_LOCAL_MACHINE if (FALSE == Reg_LocalMachine("C:\\Users\\DemonGan\\Desktop\\520_2.exe", "520")) { printf("Reg_LocalMachine Error!\n"); } printf("Reg OK.\n"); system("pause"); return 0;}
    测试结果:
    “以管理员身份运行程序”打开程序,提示成功:

    HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run路径下,有我们新添加的键值:“520”。

    HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run路径下,并没有我们新添加的键值。那是因为程序出错了吗?其实,程序并没有问题,而是我是在 64 位 系统上进行测试的,所以对于系统注册表会有个注册表重定位的问题,重定位后的路径是:
    HKEY_LOCAL_MACHINE\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Run然后,我们去到HKEY_LOCAL_MACHINE\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Run 路径下查看,发现有新添加的键值:“520”。

    然后关机,重启后,程序自启动成功。
    总结对于上面的程序,需要注意以下几点:
    一是权限问题:在编程实现上,要修改HKEY_LOCAL_MACHINE主键的注册表,要求程序要有管理员权限的。而修改HKEY_CURRENT_USER主键的注册表,只需要用户默认权限就可以实现。
    二是注册表重定位问题:在 64 位 系统上,系统注册表会有个注册表重定位的问题,
    HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run被重定位到
    HKEY_LOCAL_MACHINE\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Run参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-15 18:08:19 奖励5点积分
  • 使用MoveFileEx函数实现重启删除文件

    背景在使用一些管理监控软件的时候,都会发现这些软件里都会提供这样的一个功能:重启删除文件。也就是,让计算机重启之后,立马删除文件。删除发生在AUTOCHK执行之后,在页面文件创建之前。而此时用户还没有完全的进入操作系统,所以可以应用这点删除那些正常情况下很难删除的文件甚至是页面文件。
    起初,我还以为这种技术实现起来很难。想不明白如何设置重启之后,程序会自动删除指定文件,是将信息存储在注册表里?还是存储在某个特殊的文件里?答案其实都不是。Windows已经给我们提供了现成的API函数给我们使用,用来实现这个功能,我们只需熟练地调用它就可以了。这个API函数就是:MoveFileEx。
    现在,我把程序实现思路和过程写成文档,分享给大家。
    函数介绍
    函数声明
    BOOL MoveFileEx( LPCTSTR lpExistingFileName, // file name LPCTSTR lpNewFileName, // new file name DWORD dwFlags // move options);
    参数

    lpExistingFileName:一个存在的文件或者文件夹字符串指针lpNewFileName :一个还没存在的文件或者文件夹的字符串指针dwFlags :一个或多个下述常数:



    VALUE
    MEANING




    MOVEFILE_REPLACE_EXISTING
    覆盖已存在的目标文件,如果来源文件和目标文件指定的是一个目录,则不能使用此标记


    MOVEFILE_COPY_ALLOWED
    如果目标文件被移动到不同的卷上,则函数通过拷贝后删除来源文件的方法来模拟移动文件操作


    MOVEFILE_DELAY_UNTIL_REBOOT
    在系统重新启动前,不执行移动操作,直到系统启动后,磁盘检测完毕后,创建页面文件之前,执行移动操作。因此,这个参数可以删除系统之前启用的页面文件。这个参数只能被拥有 管理员权限 或 LocalSystem权限 的程序使用。这个参数不能和 MOVEFILE_COPY_ALLOWED 一起使用


    MOVEFILE_WRITE_THROUGH
    这个标记允许函数在执行完文件移动操作后才返回,否者不等文件移动完毕就直接返回。如果设置了 MOVEFILE_DELAY_UNTIL_REBOOT 标记,则 MOVEFILE_WRITE_THROUGH 标记将被忽略


    MOVEFILE_CREATE_HARDLINK
    系统保留,以供将来使用


    MOVEFILE_FAIL_IF_NOT_TRACKABLE
    如果来源文件是一个 LINK 文件,但是文件在移动后不能够被 TRACKED,则函数执行失败。如果目标文件在一个 FAT 格式的文件系统上,则上述情况可以发生。这个参数不支持 NT 系统



    返回值

    执行成功,返回TRUE;否则返回FALSE。
    说明

    MoveFileEx 函数在 ANSI 版本中,文件名长度被限制在 MAX_PATH 个字符内,为了将这个这个限制扩展到 32767 个宽字符长度,请使用 UNICODE 版本的函数,并且使用“//?/”作为路径前缀。 在 WINDOWS 20000 中,如果你使用了“//?/”前缀,则不能使用 MOVEFILE_DELAY_UNTIL_REBOOT 标记。 当你移动一个文件时,目标文件和来源文件可能在不同文件系统或卷上,如果目标文件在其他驱动器上,你必须指定 MOVEFILE_COPY_ALLOWED 标记。 如果移动的是一个目录,则目标目录必须和来源目录在同一个驱动器上。 如果指定了 MOVEFILE_DELAY_UNTIL_REBOOT 标记,而且目标文件指定为空路径,则来源文件将在系统启动时被删除。如果来源文件指定的是一个空目录,则来源目录将在系统启动时被删除,如果是非空目录,则无法删除。

    实现原理MoveFileEx 函数不单单可以用来删除文件,同样可以用来移动文件。本文只介绍重启删除文件的例子,对于其它功能的开发探索,在此就不进行深究了。
    重启删除的实现原理就是:
    使用 MoveFileEx 函数的 MOVEFILE_DELAY_UNTIL_REBOOT 标志,实现重启后删除文件。注意文件的移动是发生在 AUTOCHK执行之后,在页面文件创建之前。而此时用户还没有完全的进入操作系统,所以可以应用这点删除那些正常情况下很难删除的文件甚至是页面文件。很多杀毒软件和一些恶意程序删除工具就是利用了 MoveFileEx函数的这个特性来实现的重启后删除病毒。
    同时,需要注意的就是:

    删除文件的路径开头需要加上“\\\\?\\”的前缀。设置重启删除成功后,一定要重启,不能是关机或是其它操作。程序运行的时候,需要以“管理员权限”运行。
    编程实现BOOL RebootDelete(char *pszFileName){ // 重启删除文件 char szTemp[MAX_PATH] = "\\\\?\\"; ::lstrcat(szTemp, pszFileName); BOOL bRet = ::MoveFileEx(szTemp, NULL, MOVEFILE_DELAY_UNTIL_REBOOT); return bRet;}
    程序测试在 main 函数中,调用上述封装好的函数,进行测试。
    main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ if (FALSE == RebootDelete("C:\\Users\\DemonGan\\Desktop\\520.exe")) { printf("Set Reboot Delete Error.\n"); } else { printf("Set Reboot Delete OK.\n"); } system("pause"); return 0;}
    测试结果:
    程序“以管理员身份运行程序”,提示执行成功,然后,直接点击“重启”计算机。

    重启之后,文件删除成功。
    总结重启后删除文件,这一功能的实现。仅仅是一个 MoveFileEx 函数就可以实现。但是,在使用这个函数的过程中,需要注意以下几点:

    删除文件的路径开头需要加上“\\\\?\\”的前缀
    设置重启删除成功后,一定要重启,不能是关机或是其它操作
    程序运行的时候,需要以“管理员权限”运行

    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-15 10:14:41 奖励6点积分
  • 基于Crypto++库的AES对称加密实现对数据的加解密

    背景写了一个基于Crypto++加密库中AES对称加密算法实现的对数据加密和解密的一个小程序,Crypto++加密库就不详细介绍了,这个库提供了很多知名的加解密算法,直接调用就好了,使用起来还是比较方便的。
    写这篇文章,就是分享自己的学习心得。自己的密码学部分的知识学得不怎么好,还好有Crypto++开源库可以使用,弥补了对加解密部分的不足。现在,向大家分享使用Crypto++中的AES对称加密算法实现对数据的加密解密方面的知识。
    程序编译设置注意事项首先,先要下载Crypto++库的开源代码,然后,自己编译得到Crypto++的库文件。下载链接还有具体的编译步骤,可以参考平台上其他用户写的分享文章“使用VS2013编译Crypto++加密库”,里面有详细介绍。
    在导入Crypto++的库文件到自己的工程项目的时候,要对自己的工程项目进行编译设置。主要一点就是:项目工程的属性中的“运行库”设置,要与编译Crypto++的库文件时设置的“运行库”选项要对应一致,否则程序会编译不过的。也就是要检查LIB库工程和本测试工程的:属性 —> C/C++ —> 代码生成 —> 运行库 是否统一。
    如果编译出错,报告XX重复定义等错误,同样,要检查LIB库工程和本测试工程的:属性 —> C/C++ —> 代码生成 —> 运行库 是否统一。
    程序设计原理1. AES加密原理AES对称加密就是对16byte(128bit)数据进行加密的过程,也就是把这128位数据通过一系列的变化变成另一个128位数据。
    由于,我们加密的数据大小长度都是未知的,也不都是128位数据对齐。所以,在加密过程的时候,要对自己的数据进行填充,以128位大小对齐,本文的例子是以0作为填充数填充的。
    加密实现的核心部分,即使用Crypto++库中提供的AES加密器 AESEncryption 来进行实现的。数据加密主要分成三个步骤:

    声明AES加密器
    设置AES加密密钥
    对数据进行加密,返回加密结果

    // 声明AES加密器AESEncryption aesEncryptor;… …(省略)// 设置AES加密密钥aesEncryptor.SetKey(pAESKey, dwAESKeySize);… …(省略)// 对数据进行加密,返回加密结果aesEncryptor.ProcessAndXorBlock(inBlock, xorBlock, outBlock);… …(省略)
    对于本文介绍的程序,它可以对任意数据长度的数据进行加密,所以,和上面介绍的加密步骤相比,会多两个步骤:

    声明AES加密器
    将加密数据用0填充,按128位大小进行对齐
    设置AES加密密钥
    获取128位数据,并对数据进行加密,返回128位加密结果
    继续获取下一个128位数据,重复第 4 步操作,直到数据获取完毕

    2. AES解密原理解密部分的原理和加密部分原理是一样的,因为AES是对称加密,所以原理相同。
    解密实现的核心部分,即使用Crypto++库中提供的AES解密器 AESDecryption来进行实现的。数据加密主要分成三个步骤:

    声明AES加密器
    设置AES加密密钥
    对数据进行加密,返回加密结果

    // 声明AES解密器AESDecryption aesDecryptor;… …(省略)// 设置AES解密密钥aesDecryptor.SetKey(pAESKey, dwAESKeySize);… …(省略)// 对数据进行解密,返回解密结果aesDecryptor.ProcessAndXorBlock(inBlock, xorBlock, outBlock);… …(省略)
    理论上,解密的密文长度是按128位长度对齐的。但是,为了程序容错性更好,所以,在解密之前,还是和加密一样,先对解密的数据以0填充,按128位数据大小对齐。如果输入的密文长度正确,那么做这一步操作是不影响的;如果输入的密文长度不正确,那么这一步操作可以确保程序不出现错误。
    那么,解密部分的解密流程和加密流程是一样的:

    声明AES解密器
    将解密数据用0填充,按128位大小进行对齐
    设置AES解密密钥
    获取128位数据,并对数据进行解密,返回128位解密结果
    继续获取下一个128位数据,重复第 4 步操作,直到数据获取完毕

    编程实现1. 导入Crypto++库文件//*************************************************// crypt++加密库的头文件和静态库//*************************************************#include "crypt\\include\\aes.h"using namespace CryptoPP; // 命名空间#ifdef _DEBUG #ifdef _WIN64 #pragma comment(lib, "crypt\\lib\\x64\\debug\\cryptlib.lib") #else #pragma comment(lib, "crypt\\lib\\x86\\debug\\cryptlib.lib") #endif#else #ifdef _WIN64 #pragma comment(lib, "crypt\\lib\\x64\\release\\cryptlib.lib") #else #pragma comment(lib, "crypt\\lib\\x86\\release\\cryptlib.lib") #endif#endif//*************************************************
    2. AES加密实现// 加密// 输入:原文内容、原文内容长度、密钥内容、密钥内容长度// 输出:密文内容、密文内容长度BOOL AES_Encrypt(BYTE *pOriginalData, DWORD dwOriginalDataSize, BYTE *pAESKey, DWORD dwAESKeySize, BYTE **ppEncryptData, DWORD *pdwEncryptData){ // 加密器 AESEncryption aesEncryptor; // 加密原文数据块 unsigned char inBlock[AES::BLOCKSIZE]; // 加密后密文数据块 unsigned char outBlock[AES::BLOCKSIZE]; // 必须设定全为0 unsigned char xorBlock[AES::BLOCKSIZE]; DWORD dwOffset = 0; BYTE *pEncryptData = NULL; DWORD dwEncryptDataSize = 0; // 计算原文长度, 并按 128位 即 16字节 对齐, 不够则 填充0 对齐 // 商 DWORD dwQuotient = dwOriginalDataSize / AES::BLOCKSIZE; // 余数 DWORD dwRemaind = dwOriginalDataSize % AES::BLOCKSIZE; if (0 != dwRemaind) { dwQuotient++; } // 申请动态内存 dwEncryptDataSize = dwQuotient * AES::BLOCKSIZE; pEncryptData = new BYTE[dwEncryptDataSize]; if (NULL == pEncryptData) { return FALSE; } // 设置密钥 aesEncryptor.SetKey(pAESKey, dwAESKeySize); do { // 置零 ::RtlZeroMemory(inBlock, AES::BLOCKSIZE); ::RtlZeroMemory(xorBlock, AES::BLOCKSIZE); ::RtlZeroMemory(outBlock, AES::BLOCKSIZE); // 获取加密块 if (dwOffset <= (dwOriginalDataSize - AES::BLOCKSIZE)) { ::RtlCopyMemory(inBlock, (PVOID)(pOriginalData + dwOffset), AES::BLOCKSIZE); } else { ::RtlCopyMemory(inBlock, (PVOID)(pOriginalData + dwOffset), (dwOriginalDataSize - dwOffset)); } // 加密 aesEncryptor.ProcessAndXorBlock(inBlock, xorBlock, outBlock); // 构造 ::RtlCopyMemory((PVOID)(pEncryptData + dwOffset), outBlock, AES::BLOCKSIZE); // 更新数据 dwOffset = dwOffset + AES::BLOCKSIZE; dwQuotient--; } while (0 < dwQuotient); // 返回数据 *ppEncryptData = pEncryptData; *pdwEncryptData = dwEncryptDataSize; return TRUE;}
    3. AES解密实现// 解密// 输入:密文内容、密文内容长度、密钥内容、密钥内容长度// 输出:解密后明文内容、解密后明文内容长度BOOL AES_Decrypt(BYTE *pEncryptData, DWORD dwEncryptData, BYTE *pAESKey, DWORD dwAESKeySize, BYTE **ppDecryptData, DWORD *pdwDecryptData){ // 解密器 AESDecryption aesDecryptor; // 解密密文数据块 unsigned char inBlock[AES::BLOCKSIZE]; // 解密后后明文数据块 unsigned char outBlock[AES::BLOCKSIZE]; // 必须设定全为0 unsigned char xorBlock[AES::BLOCKSIZE]; DWORD dwOffset = 0; BYTE *pDecryptData = NULL; DWORD dwDecryptDataSize = 0; // 计算密文长度, 并按 128位 即 16字节 对齐, 不够则填充0对齐 // 商 DWORD dwQuotient = dwEncryptData / AES::BLOCKSIZE; // 余数 DWORD dwRemaind = dwEncryptData % AES::BLOCKSIZE; if (0 != dwRemaind) { dwQuotient++; } // 申请动态内存 dwDecryptDataSize = dwQuotient * AES::BLOCKSIZE; pDecryptData = new BYTE[dwDecryptDataSize]; if (NULL == pDecryptData) { return FALSE; } // 设置密钥 aesDecryptor.SetKey(pAESKey, dwAESKeySize); do { // 置零 ::RtlZeroMemory(inBlock, AES::BLOCKSIZE); ::RtlZeroMemory(xorBlock, AES::BLOCKSIZE); ::RtlZeroMemory(outBlock, AES::BLOCKSIZE); // 获取解密块 if (dwOffset <= (dwDecryptDataSize - AES::BLOCKSIZE)) { ::RtlCopyMemory(inBlock, (PVOID)(pEncryptData + dwOffset), AES::BLOCKSIZE); } else { ::RtlCopyMemory(inBlock, (PVOID)(pEncryptData + dwOffset), (dwEncryptData - dwOffset)); } // 解密 aesDecryptor.ProcessAndXorBlock(inBlock, xorBlock, outBlock); // 构造 ::RtlCopyMemory((PVOID)(pDecryptData + dwOffset), outBlock, AES::BLOCKSIZE); // 更新数据 dwOffset = dwOffset + AES::BLOCKSIZE; dwQuotient--; } while (0 < dwQuotient); // 返回数据 *ppDecryptData = pDecryptData; *pdwDecryptData = dwDecryptDataSize; return TRUE;}
    程序测试我们在main函数中,调用上面封装好的加解密函数,对数据进行加解密进行测试。main函数为:
    int _tmain(int argc, _TCHAR* argv[]){ BYTE *pEncryptData = NULL; DWORD dwEncryptDataSize = 0; BYTE *pDecryptData = NULL; DWORD dwDecryptDataSize = 0; char szOriginalData[] = "CDIY - www.coderdiy.com - 专注计算机技术交流分享"; char szAESKey[] = "DemonGanDemonGan"; BOOL bRet = FALSE; // 加密 bRet = AES_Encrypt((BYTE *)szOriginalData, (1 + ::lstrlen(szOriginalData)), (BYTE *)szAESKey, ::lstrlen(szAESKey), &pEncryptData, &dwEncryptDataSize); if (FALSE == bRet) { return 1; } // 解密 bRet = AES_Decrypt(pEncryptData, dwEncryptDataSize, (BYTE *)szAESKey, ::lstrlen(szAESKey), &pDecryptData, &dwDecryptDataSize); if (FALSE == bRet) { return 2; } // 显示 printf("原文数据:\n"); ShowData((BYTE *)szOriginalData, (1 + ::lstrlen(szOriginalData))); printf("密文数据:\n"); ShowData(pEncryptData, dwEncryptDataSize); printf("解密后数据:\n"); ShowData(pDecryptData, dwDecryptDataSize); // 释放内存 delete[]pEncryptData; pEncryptData = NULL; delete[]pDecryptData; pDecryptData = NULL; system("pause"); return 0;}
    测试的结果为:

    通过上述结果,测试成功。
    总结本身使用Crypto++库中的AES加解密不是很复杂,也容易理解。其中,需要注意一点就是,如果实现对任意长度数据的加密,那么就必须要对数据填充,按128位大小进行对齐,填充的数据任意。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-14 18:25:53 奖励6点积分
  • Socket通信之UDP文件传输实验小程序

    背景如果你看到这篇文章的标题,会感到奇怪吧。UDP传输是一个无连接传输,数据发送之后,接收端接不接收得到,发送端是不理会的,所以,会发生丢包的情况。但是,我之所以写这个小程序,是因为一位网友本来打算请我帮ta写个小程序,应付作业的,作业要求是:

    基于UDP测试MP3文件(大于10M)的传输,并测试接收到的文件与发送文件是否一致。还可以调整发送端读取数据缓冲区的大小和延迟时间,以及接收端缓冲区的大小来解决。

    最终,因为我出价太高,而没有交易完成。但是,我就在和ta谈价期间,完成了这个小程序。这个小程序要求有界面的,但是,为了方便初学者的学习理解,本文特地重新开发了个新的程序,是基于控制台程序实现的。
    现在,就把实现过程和原理整理成文档,分享给大家。
    实现过程这个程序的实现,是根据我之前写的《Socket通信之UDP通信小程序》这篇文章修改而来的,大家可以参考这篇文章。在此,不仔细讲解UDP Socket如何使用。我只是大概讲下开发这个UDP文件传输小程序的实现原理。
    我们首先开发接收端程序,所谓的接收端程序,就是把接收的数据,不断地写入到文件中。具体实现过程就是:

    首先,从 recvfrom 函数获取到数据
    然后,根据判断生成文件路径是否存在,若不存在,则创建文件;若存在,则打开文件
    将文件指针移到文件的末尾,接着向文件中写入上述接收到的数据
    关闭文件,释放句柄
    重复上面 4 个步骤,直到文件接收完毕。

    // 数据接收void RecvMsg(){ int iSize = 40960; BYTE *lpBuf = new BYTE[iSize]; int iLen = 0; int iRet = 0; while (TRUE) { sockaddr_in addr = { 0 }; // 注意此处, 既是输入参数也是输出参数 int iLen = sizeof(addr); // 接收数据 int iRet = ::recvfrom(g_sock, (char *)lpBuf, iSize, 0, (sockaddr *)(&addr), &iLen); // 存储为文件 if (0 < iRet) { RecvFile(lpBuf, iRet); } } delete[] lpBuf; lpBuf = NULL;}
    接着,我们就可以开发发送端。发送端的开发流程就是:

    首先,我们根据文件路径打开文件,获取文件句柄
    然后,获取文件的大小,并读取文件的所有数据
    接着,根据发送缓冲区的大小,循环读取数据,并调用发送数据的函数,同时根据延迟时间进行发送延迟
    发送完毕,则释放内存,关闭句柄

    // 文件数据传输void SendFileData(char *pszFileName, char *pszDestIp, int iDestPort, int iBufferSize, int iElapseTime){ // 打开文件 HANDLE hFile = ::CreateFile(pszFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL); if (INVALID_HANDLE_VALUE == hFile) { ::MessageBox(NULL, "CreateFile Error!", NULL, MB_OK); return; } // 申请动态内存 BYTE *lpBuf = new BYTE[iBufferSize]; if (NULL == lpBuf) { ::CloseHandle(hFile); ::MessageBox(NULL, "申请动态内存错误!", NULL, MB_OK); return; } // 获取文件大小 DWORD dwFileSize = ::GetFileSize(hFile, NULL); DWORD dwRet = 0; BOOL bStop = FALSE; // 读取文件并发送 do { // 读取 ::RtlZeroMemory(lpBuf, iBufferSize); ::ReadFile(hFile, lpBuf, iBufferSize, &dwRet, NULL); if (dwRet < iBufferSize) { bStop = TRUE; } // 发送 SendMsg((char *)lpBuf, dwRet, pszDestIp, iDestPort); // 时间间隔 Sleep(iElapseTime); } while (FALSE == bStop); // 释放 delete[] lpBuf; lpBuf = NULL; ::CloseHandle(hFile); ::MessageBox(NULL, "发送完毕!", "DONE", MB_OK);}
    程序测试接收端绑定的地址和端口是:127.0.0.1:4321,发送端绑定的地址和端口是:127.0.0.1:12345,发送一个10.3M大小的MP3文件。设置发送缓冲区大小是10240字节,延时是100毫秒;接收端接收缓冲区大小为40960字节。
    我们先运行接收端,然后在运行发送段。等待一段时间后,文件接收完毕,打开接受到的MP3文件,居然可以正常播放。大家可以自己调整参数试试吧,或许就不能播放了哦。


    总结要特别注意一点就是,接收端使用 recvfrom 函数接收程序的时候,如果返回值为 -1,这么这时就要检查是否发送端发送数据的缓冲区比接收端接收数据的缓冲区还要大,若接收端接收数据的缓冲区较小,则应该调整缓冲区大小,使接收端接缓冲区大小比发送区缓冲区大小大才行。因为 recvfrom 函数返回值为 -1 的原因,可能是上面提到的情况,也可能是 recvfrom 函数的参数有误。
    1 留言 2018-11-14 14:52:06 奖励15点积分
  • Socket通信之UDP通信小程序

    背景之前自己做过关于Socket通信的视频教程,主要是教大家怎么使用Windows提供的Socket函数接口去实现网络通信的。当时讲了两个小程序的实现,一个是TCP通信,另一个是UDP通信。
    如今,我把视频教程讲解的内容,重新整理成文档的形式,并对程序简化,使用使用控制台重新开发,方便初学者的理解。本文先讲解使用Socket实现UDP通信的小程序,先把程序实现过程和原理整理成文档,分享给大家。
    函数介绍socket 函数
    根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源的函数。
    函数声明
    SOCKET WSAAPI socket( _In_ int af, _In_ int type, _In_ int protocol);
    参数

    af [in]地址族规范。 地址系列的可能值在Winsock2.h头文件中定义。当前支持的值为AF_INET或AF_INET6,它们是IPv4和IPv6的Internet地址族格式。 type[in]指定Socket类型,SOCK_STREAM类型指定产生流式套接字,SOCK_DGRAM类型指定产生数据报式套接字,而SOCK_RAW类型指定产生原始套接字(只有管理员权限用户才能创建原始套接字)。protocol[in]与特定的地址家族相关的协议IPPROTO_TCP、IPPROTO_UDP和IPPROTO_IP,如果指定为0,那么系统就会根据地址格式和套接字类别,自动选择一个合适的协议。
    返回值

    如果没有发生错误,套接字返回引用新套接字的描述符。 否则,返回值为INVALID_SOCKET,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    bind 函数
    将本地地址与套接字相关联。
    函数声明
    int bind( _In_ SOCKET s, _In_ const struct sockaddr *name, _In_ int namelen);
    参数

    s [in]标识未绑定套接字的描述符。名称[in]指向本地地址的sockaddr结构的指针,以分配给绑定的套接字。namelen [in]name参数指向的值的长度(以字节为单位)。
    返回值

    如果没有发生错误,则bind返回零。 否则,它返回SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    htons 函数
    将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为高位字节存放在内存的低地址处。
    函数声明
    u_short WSAAPI htons( _In_ u_short hostshort);
    参数

    hostshort [in]
    主机字节顺序为16位。

    返回值

    返回TCP / IP网络字节顺序。

    inet_addr 函数
    将一个点分十进制的IP转换成一个长整数型数。
    函数声明
    unsigned long inet_addr( _In_ const char *cp);
    参数

    cp [in]
    点分十进制的IP字符串,以NULL结尾。

    返回值

    如果没有发生错误,则inet_addr函数将返回一个无符号长整型值,其中包含给定的Internet地址的适当的二进制表示形式。

    sendto 函数
    将数据发送到特定目的地。
    函数声明
    int sendto( _In_ SOCKET s, _In_ const char *buf, _In_ int len, _In_ int flags, _In_ const struct sockaddr *to, _In_ int tolen);
    参数

    s [in]标识(可能连接)套接字的描述符。buf [in]指向包含要发送的数据的缓冲区的指针。len [in]由buf参数指向的数据的长度(以字节为单位)。flags[in]一组指定呼叫方式的标志。to[in]指向包含目标套接字地址的sockaddr结构的可选指针。tolen[in]由to参数指向的地址的大小(以字节为单位)。
    返回值

    如果没有发生错误,sendto返回发送的总字节数,可以小于len指示的数字。 否则,返回值SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    recvfrom 函数
    接收数据报并存储源地址。
    函数声明
    int recvfrom( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags, _Out_ struct sockaddr *from, _Inout_opt_ int *fromlen);
    参数

    s [in]标识绑定套接字的描述符。buf [out]用于传入数据的缓冲区。len [in]由buf参数指向的缓冲区的长度(以字节为单位)。flags[in]一组修改函数调用行为的选项,超出了为关联套接字指定的选项。 有关详细信息,请参阅下面的备注。from[out]指向sockaddr结构中缓冲区的可选指针,它将在返回时保存源地址。fromlen [in,out,optional]指向from参数指向的缓冲区的大小(以字节为单位)的可选指针。
    返回值

    如果没有发生错误,recvfrom返回接收到的字节数。 如果连接已正常关闭,返回值为零。 否则,返回值SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    实现原理如下图所示:

    我们可以看到,UDP通信时,是无连接的,是不区分 Server 与 Client 的,所以,我们在程序实现的时候,只需要一个程序就可以了。
    和TCP通信程序一样,都首先要初始化Winsock服务环境。
    初始化Winsock环境后,便调用 socket 函数创建数据报套接字;然后对sockaddr_in结构体进行设置,设置绑定的IP地址和端口等信息并调用 bind 函数绑定;绑定成功后,就可以使用 recvfrom 函数和 sendto 函数与另一UDP程序进行数据的收发。通信结束后,变关闭套接字,释放资源。
    编码实现导入库文件#include <Winsock2.h>#pragma comment(lib, "Ws2_32.lib")
    UDP通信程序初始化Winsock库环境,创建数据报套接字,绑定IP地址和端口。
    // 绑定IP地址和端口BOOL Bind(char *lpszIp, int iPort){ // 初始化 Winsock 库 WSADATA wsaData = { 0 }; ::WSAStartup(MAKEWORD(2, 2), &wsaData); // 创建数据报套接字 g_sock = socket(AF_INET, SOCK_DGRAM, 0); if (INVALID_SOCKET == g_sock) { return FALSE; } // 设置绑定IP地址和端口信息 sockaddr_in addr = { 0 }; addr.sin_family = AF_INET; addr.sin_port = ::htons(iPort); addr.sin_addr.S_un.S_addr = ::inet_addr(lpszIp); // 绑定IP地址和端口 if (0 != bind(g_sock, (sockaddr *)(&addr), sizeof(addr))) { return FALSE; } // 创建接收信息多线程 ::CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)RecvThreadProc, NULL, NULL, NULL); return TRUE;}
    数据发送。
    // 数据发送void SendMsg(char *lpszText, char *lpszIp, int iPort){ // 设置目的主机的IP地址和端口等地址信息 sockaddr_in addr = { 0 }; addr.sin_family = AF_INET; addr.sin_port = ::htons(iPort); addr.sin_addr.S_un.S_addr = ::inet_addr(lpszIp); // 发送数据到目的主机 ::sendto(g_sock, lpszText, (1 + ::lstrlen(lpszText)), 0, (sockaddr *)(&addr), sizeof(addr)); printf("[sendto]%s\n", lpszText);}
    数据接收。
    // 数据接收void RecvMsg(){ char szBuf[MAX_PATH] = { 0 }; while (TRUE) { sockaddr_in addr = { 0 }; // 注意此处, 既是输入参数也是输出参数 int iLen = sizeof(addr); // 接收数据 ::recvfrom(g_sock, szBuf, MAX_PATH, 0, (sockaddr *)(&addr), &iLen); printf("[recvfrom]%s\n", szBuf); }}
    程序测试我们进行本机测试,先开启一个程序输入绑定的地址和端口为:127.0.0.1:12345。
    再启动另一个程序,绑定的地址和端口为:127.0.0.1:4321。
    然后,直接相互发送数据,进行数据通信。

    总结有 2 个地方如果稍不注意的话,便很容易出错:
    一是在使用Socket函数之前,一定要对Winsock服务进行初始化,初始化是由WSAStartup函数实现的。如果不进行初始化操作,而直接使用Socket函数,会报错。
    二是对于接收数据的 recvfrom 函数,最后一个参数一定要格外注意,它是既是输入参数也是输出参数,也就是说,一定要给它一个初值,初值大小就是sockaddr_in结构体的大小。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-14 07:53:01 奖励5点积分
  • Socket通信之TCP通信小程序

    背景之前自己做过关于Socket通信的视频教程,主要是教大家怎么使用Windows提供的Socket函数接口去实现网络通信的。当时讲了两个小程序的实现,一个是TCP通信,另一个是UDP通信。
    如今,我把视频教程讲解的内容,重新整理成文档的形式,并对程序简化,使用使用控制台重新开发,方便初学者的理解。本文先讲解使用Socket实现TCP通信的小程序,先把程序实现过程和原理整理成文档,分享给大家。
    函数介绍socket 函数
    根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源的函数。
    函数声明
    SOCKET WSAAPI socket( _In_ int af, _In_ int type, _In_ int protocol);
    参数

    af [in]地址族规范。 地址系列的可能值在Winsock2.h头文件中定义。当前支持的值为AF_INET或AF_INET6,它们是IPv4和IPv6的Internet地址族格式。 type[in]指定Socket类型,SOCK_STREAM类型指定产生流式套接字,SOCK_DGRAM类型指定产生数据报式套接字,而SOCK_RAW类型指定产生原始套接字(只有管理员权限用户才能创建原始套接字)。protocol[in]与特定的地址家族相关的协议IPPROTO_TCP、IPPROTO_UDP和IPPROTO_IP,如果指定为0,那么系统就会根据地址格式和套接字类别,自动选择一个合适的协议。
    返回值

    如果没有发生错误,套接字返回引用新套接字的描述符。 否则,返回值为INVALID_SOCKET,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    bind 函数
    将本地地址与套接字相关联。
    函数声明
    int bind( _In_ SOCKET s, _In_ const struct sockaddr *name, _In_ int namelen);
    参数

    s [in]标识未绑定套接字的描述符。名称[in]指向本地地址的sockaddr结构的指针,以分配给绑定的套接字。namelen [in]name参数指向的值的长度(以字节为单位)。
    返回值

    如果没有发生错误,则bind返回零。 否则,它返回SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    htons 函数
    将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为高位字节存放在内存的低地址处。
    函数声明
    u_short WSAAPI htons( _In_ u_short hostshort);
    参数

    hostshort [in]
    主机字节顺序为16位。

    返回值

    返回TCP / IP网络字节顺序。

    inet_addr 函数
    将一个点分十进制的IP转换成一个长整数型数。
    函数声明
    unsigned long inet_addr( _In_ const char *cp);
    参数

    cp [in]
    点分十进制的IP字符串,以NULL结尾。

    返回值

    如果没有发生错误,则inet_addr函数将返回一个无符号长整型值,其中包含给定的Internet地址的适当的二进制表示形式。

    listen函数
    将一个套接字置于正在监听传入连接的状态。
    函数声明
    int listen( _In_ SOCKET s, _In_ int backlog);
    参数

    s [in]标识绑定的未连接套接字的描述符。backlog[in]待连接队列的最大长度。 如果设置为SOMAXCONN,负责套接字的底层服务提供商将积压设置为最大合理值。 如果设置为SOMAXCONN_HINT(N)(其中N是数字),则积压值将为N,调整为范围(200, 65535)。
    返回值

    如果没有发生错误,listen将返回零。否则,返回值SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    accept 函数
    允许在套接字上进行连接尝试。
    函数声明
    SOCKET accept( _In_ SOCKET s, _Out_ struct sockaddr *addr, _Inout_ int *addrlen);
    参数

    s [in]一个描述符,用于标识使用listen功能处于侦听状态的套接字。 连接实际上是由accept返回的套接字。addr [out]一个可选的指向缓冲区的指针,它接收通信层已知的连接实体的地址。 addr参数的确切格式由创建sockaddr结构的套接字时建立的地址族确定。addrlen [in,out]指向一个整数的可选指针,其中包含addr参数指向的结构长度。
    返回值

    如果没有发生错误,则accept返回一个SOCKET类型的值,该值是新套接字的描述符。 此返回值是实际连接所在的套接字的句柄。否则,返回值为INVALID_SOCKET,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    send 函数
    在建立连接的套接字上发送数据。
    函数声明
    int send( _In_ SOCKET s, _In_ const char *buf, _In_ int len, _In_ int flags);
    参数

    s [in]标识连接的套接字的描述符。buf [in]指向包含要发送的数据的缓冲区的指针。len [in]由buf参数指向的缓冲区中数据的长度(以字节为单位)。标志[in]一组指定呼叫方式的标志。
    返回值

    如果没有发生错误,发送返回发送的总字节数,可以小于len参数中要发送的数量。 否则,返回值SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    recv 函数
    从连接的套接字或绑定的无连接套接字接收数据。
    函数声明
    int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);
    参数

    s [in]标识连接的套接字的描述符。buf [out]指向缓冲区的指针,用于接收传入的数据。len [in]由buf参数指向的缓冲区的长度(以字节为单位)。标志[in]一组影响此功能的行为的标志。
    返回值

    如果没有发生错误,则recv返回接收的字节数,buf参数指向的缓冲区将包含接收到的数据。 如果连接已正常关闭,返回值为0。

    实现原理如下图所示:

    无论对服务器端来说还是客户端来说,都首先要初始化Winsock服务环境。
    服务器端初始化Winsock环境后,便调用 socket 函数创建流式套接字;然后对sockaddr_in结构体进行设置,设置服务器绑定的IP地址和端口等信息并调用 bind 函数绑定;绑定成功后,便可以调用 listen 函数设置连接数量,并进行监听。直到有来自客户端的连接请求,服务器便调用 accept 函数接受连接请求,建立连接。这时,便可以使用 recv 函数和 send 函数与客户端进行数据的收发。通信结束后,变关闭套接字,释放资源。
    客户端初始化环境后,便调用 socket 函数创建流式套接字;然后对sockaddr_in结构体进行设置,设置服务器的IP地址和端口等信息并调用 connect 函数向服务器发送连接请求,并等待服务器的响应。服务器接受连接请求后,便成功与服务器建立连接,这时,便可以使用 recv 函数和 send 函数与客户端进行数据的收发。通信结束后,变关闭套接字,释放资源。
    编码实现导入库文件#include <Winsock2.h>#pragma comment(lib, "Ws2_32.lib")
    服务器端初始化Winsock库环境,创建流式套接字,绑定服务器IP地址和端口,并进行监听。
    // 绑定端口并监听BOOL SocketBindAndListen(char *lpszIp, int iPort){ // 初始化 Winsock 库 WSADATA wsaData = {0}; ::WSAStartup(MAKEWORD(2, 2), &wsaData); // 创建流式套接字 g_ServerSocket = ::socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == g_ServerSocket) { return FALSE; } // 设置服务端地址和端口信息 sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = ::htons(iPort); addr.sin_addr.S_un.S_addr = ::inet_addr(lpszIp); // 绑定IP和端口 if (0 != ::bind(g_ServerSocket, (sockaddr *)(&addr), sizeof(addr))) { return FALSE; } // 设置监听 if (0 != ::listen(g_ServerSocket, 1)) { return FALSE; } // 创建接收数据多线程 ::CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)RecvThreadProc, NULL, NULL, NULL); return TRUE;}
    服务器端数据发送。
    // 发送数据void SendMsg(char *pszSend){ // 发送数据 ::send(g_ClientSocket, pszSend, (1 + ::lstrlen(pszSend)), 0); printf("[send]%s\n", pszSend);}
    服务器端接收连接请求并接收数据。
    // 接受连接请求 并 接收数据void AcceptRecvMsg(){ sockaddr_in addr = { 0 }; // 注意:该变量既是输入也是输出 int iLen = sizeof(addr); // 接受来自客户端的连接请求 g_ClientSocket = ::accept(g_ServerSocket, (sockaddr *)(&addr), &iLen); printf("accept a connection from client!\n"); char szBuf[MAX_PATH] = { 0 }; while (TRUE) { // 接收数据 int iRet = ::recv(g_ClientSocket, szBuf, MAX_PATH, 0); if (0 >= iRet) { continue; } printf("[recv]%s\n", szBuf); }}
    客户端初始化Winsock库环境,创建流式套接字,并连接服务器。
    // 连接到服务器BOOL Connection(char *lpszServerIp, int iServerPort){ // 初始化 Winsock 库 WSADATA wsaData = { 0 }; ::WSAStartup(MAKEWORD(2, 2), &wsaData); // 创建流式套接字 g_ClientSocket = ::socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == g_ClientSocket) { return FALSE; } // 设置服务端地址和端口信息 sockaddr_in addr = { 0 }; addr.sin_family = AF_INET; addr.sin_port = ::htons(iServerPort); addr.sin_addr.S_un.S_addr = ::inet_addr(lpszServerIp); // 连接到服务器 if (0 != ::connect(g_ClientSocket, (sockaddr *)(&addr), sizeof(addr))) { return FALSE; } // 创建接收数据多线程 ::CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)RecvThreadProc, NULL, NULL, NULL); return TRUE;}
    客户端发送数据。
    // 发送数据void SendMsg(char *pszSend){ // 发送数据 ::send(g_ClientSocket, pszSend, (1 + ::lstrlen(pszSend)), 0); printf("[send]%s\n", pszSend);}
    客户端接收数据。
    // 接收数据void RecvMsg(){ char szBuf[MAX_PATH] = { 0 }; while (TRUE) { // 接收数据 int iRet = ::recv(g_ClientSocket, szBuf, MAX_PATH, 0); if (0 >= iRet) { continue; } printf("[recv]%s\n", szBuf); }}
    程序测试我们进行本机测试,服务器地址端口为:127.0.0.1:12345。
    先运行服务器,进行绑定并监听,然后再运行客户端进行连接,连接成功后,就可以相互进行数据通信。

    总结有 3 个地方如果稍不注意的话,便很容易出错:
    一是在使用Socket函数之前,一定要对Winsock服务进行初始化,初始化是由WSAStartup函数实现的。如果不进行初始化操作,而直接使用Socket函数,会报错。
    二是对于服务端中的接受来自客户端连接请求的accept函数,第三个参数一定要格外注意,它是既是输入参数也是输出参数,也就是说,一定要给它一个初值,初值大小就是sockaddr_in结构体的大小。
    三是测试的时候,如果服务端和客户端通信一直不成功,可以试着使用CMD命令的ping指令ping下两台主机是否能ping通,若不能,则检查是否在同一网段内或者防火墙是否关闭;若ping通,则检查自己的代码是否有误,可以单步进行调试。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-13 12:46:41 奖励5点积分
  • Py-基本变量笔记

    基本变量数字
    整数,浮点数,复数。

    初始化: data = 3 3.2 3E9 (8+6i)
    字符串
    默认Unicode编码格式

    初始化: str_data = ‘this is str’
    相互转换int/float('data')#格式化为整型,浮点型complex(real,imag)#real数据转化成imag形式(default 0)str(data)#数字到字符串repr(str)#返回对象str格式格式化输入输出print data ;print str; #cure dataprint ('this data is :',data)#数字和字符串混合空格填充print ('this data is :'+ str(data))#字符串链接'{id:standard}'.format()#str.format()"{1} {0} {1}".format("hello", "world") # 设置指定位置"{} {}".format("hello", "world") # 默认顺序位置
    0 留言 2018-11-09 21:07:10 奖励3点积分
  • C语言-基于Huffman编码原理的文本编码压缩程序

    C语言实现基于Huffman编码原理的编码压缩程序
    huffman编码原理这里不做介绍,梳理一下编码的代码实现。
    编码压缩部分输入待编码文本,输出压缩文本和编码表(供解码使用)

    1.获取txt文件文本,用char字符串保存。void readfile(){ printf("请输入文本:"); gets();}2.分析文本字符频率,保存字符数据,同时创建字符队列以便生成huffman树void getFrequency(){ int i = 0,j = 0,k = 0; char temp = 0; int tag = 0;//标记是否存在 while (str_text[i] != '\0') { for (temp = 0; temp < charmaxsize; temp++) { tag = 0; for ( k = 0; k < j; k++) { if (str_text[i] == chartype[k].character) { chartype[k].count++; tag = 1; break; } } if (tag) break; if (str_text[i] == temp) { chartype[j].character = temp; chartype[j].count++; j++; break; } } i++; }}3.每次返回构建huffman树时所需要队列中的两个最小值,并返回出队后元素数量,方便新的元素插入队列int getMinArr(Huffchar chartype[], Huffchar minarr[]){ int i = 0; int min;//记录最小节点位置 int max = 2;//记录数组元素个数 Huffchar temp; //直接排序选出最小结点 minarr[0] = chartype[0]; min = 0; while (chartype[i].count>0) { if (minarr[0].count>chartype[i].count) { minarr[0] = chartype[i]; min = i; } i++; } //删除min结点 while (chartype[min+1].count>0) { chartype[min] = chartype[min + 1]; min++; } chartype[min].count = 0; //直接排序选出次小结点 minarr[1] = chartype[0]; min = i = 0; while (chartype[i].count>0) { if (minarr[1].count>chartype[i].count) { minarr[1] = chartype[i]; min = i; } i++; } //删除min结点 while (chartype[min+1].count>0) { chartype[min] = chartype[min + 1]; min++; } chartype[min].count = 0; return min;}4.根据字符队列创建huffman树void buildTree(Huffchar chartype[]){int max = 2;Huffchar minarr[2];//两个最小字符结点Hufftree *rp, *lp, *p;if (chartype[1].count == 0)//考虑单一字符文本,不参与队列排序{ if (lp = (Hufftree *)malloc(sizeof(Hufftree))) { lp->character = chartype[0].character; lp->power = chartype[0].count; lp->self = NULL; lp->lchild = NULL; lp->rchild = NULL; } else { printf("内存分配错误!"); exit(0); } if(p = (Hufftree *)malloc(sizeof(Hufftree))) { p->character = 0; p->power = chartype[0].count; p->self = NULL; p->lchild = lp; p->rchild = NULL; chartype[0].self = p; } else { printf("内存分配错误!"); exit(0); }}while (chartype[1].count > 0)//当数组剩余2个及以上输出最小节点{ max = getMinArr(chartype, minarr); //右子树填充最小值 if (rp = (Hufftree *)malloc(sizeof(Hufftree))) { if (minarr[0].self != NULL)//不为叶子节点 rp = minarr[0].self; else { rp->character = minarr[0].character; rp->power = minarr[0].count; rp->rchild = NULL; rp->lchild = NULL; } } else { printf("内存分配错误!"); exit(0); } //左子树填充次小值 if (lp = (Hufftree *)malloc(sizeof(Hufftree))) { if (minarr[1].self != NULL)//不为叶子节点 lp = minarr[1].self; else { lp->character = minarr[1].character; lp->power = minarr[1].count; lp->rchild = NULL; lp->lchild = NULL; } } else { printf("内存分配错误!"); exit(0); } //更新父节点权值 if (p = (Hufftree *)malloc(sizeof(Hufftree))) { p->character = 0; p->power = minarr[0].count + minarr[1].count; p->self = p; p->lchild = lp; p->rchild = rp; } else { printf("内存分配错误!"); exit(0); } ////更新字符数组,将两个最小节点作为整体放入数组 chartype[max].character = 0;//非叶子字符数据记录NULL; chartype[max].count = minarr[0].count + minarr[1].count; chartype[max].self = p;}Root = chartype[0].self;}5.以上完成了huffman树的构建,然后深度优先遍历得到每个字符的编码void dfs(Hufftree * node,int lev){ int i = 0; static int node_i = 0; if (node->lchild == node->rchild) { char_type[node_i].character = node->character; char_type[node_i].count = node->power; for ( i = 0; i < lev; i++) { char_type[node_i].code[i] = code_temp[i]; } node_i++; } else { if (node->lchild) { code_temp[lev] = '0'; dfs(node->lchild,lev + 1); } if (node->rchild) { code_temp[lev] = '1'; dfs(node->rchild, lev + 1); } } }6.根据每个字符的编码,逐个替换原文得到伪二进制流,并转化为ASC||码形式输出//字符编码后转化为二进制流void converToBit(){ int text_i = 0,char_i = 0,i = 0,j = 0; int num = 0; bool tag = true; char tempc; //转换成伪二进制流。 for (text_i = 0; text_i < strmaxsize; text_i++) { for ( char_i= 0; char_i < charmaxsize; char_i++) { if (char_type[char_i].character == str_text[text_i]) { strcat_s(strbit_temp, char_type[char_i].code); } } } puts(strbit_temp); //转换成二进制流,ASCII码形式输出 text_i = 0; while (tag) { for (i = 0; i < 6; i++, text_i++) { if (strbit_temp[text_i] != '\0') { num += ((int)strbit_temp[text_i] - 48)*(1<<(5 - i)); } else {//结束编码 tag = false; break; } } tempc = (char)num+31;//转化为可显示字符 strcode[j++] = tempc; num = 0; } strcode[j] = '\0';//尾部加结束标志 puts(strcode);}7.输出压缩文件和码表//输出字符对应的词频与编码void puttree(){ for (size_t i = 0; i < charmaxsize; i++) { if (char_type[i].character > 0) { printf("%c---%d---", char_type[i].character, char_type[i].count); for (size_t j = 0; j < 20; j++) { printf("%c", char_type[i].code[j]); } printf("\n"); } }}void outfile(){ int i = 0; FILE* fp; fp = fopen(".\\outfile.txt", "w"); fputs(strcode, fp); fclose(fp);}至此完成编码部分tip编码使用的ASC||字符应该使用可显示字符,避开控制字符。所以使用6位为一组转换为二进制。
    附:结构体定义//字符编码信息typedef struct Huff_char { char character = 0; int count = 0; char code[20];}Huff_char;//字符队列typedef struct Huffchar { char character = 0; int count = 0;//统计词频 struct Hufftree* self = NULL;}Huffchar;//哈夫曼树节点typedef struct Hufftree { char character;//节点字符信息 int power;//权值 struct Hufftree* self, *rchild, *lchild;//自身地址,左右孩子}Hufftree;
    0 留言 2018-11-09 21:03:10 奖励5点积分
  • C语言-关于指针的初始化

    指针笔记//测试内存分配bool memory(){int *p;if ((p = (int *)malloc(sizeof(int)))) printf("p分配内存");if ((p = NULL)) printf("p不分配内存");return true;}//测试未初始化 空指针bool voidpoint(){int *p;int *q = (int *)malloc(sizeof(int));p = q;return true;}
    0 留言 2018-11-09 21:01:41 奖励3点积分
  • C语言-for循环中break与false

    for循环笔记循环结束for ( i = 0; i < 5; i++){ printf("%d ",i);}printf("%d ", i);//0,1,2,3,4,5
    i++为循环内操作,条件为false仍执行。

    break中断for ( i = 0; i < 5; i++){ break;}printf("%d ", i); // 0
    break为终止循环,i++不执行。
    0 留言 2018-11-09 20:58:18 奖励3点积分
  • 运行单一实例

    背景病毒木马在使用各种手段植入到用户计算机后,也会使用浑身解数使自己被用户执行激活。但是,如果病毒木马自己被多次重复运行,系统中存在多分病毒木马的进程,那么,这就有可能增加被暴露的风险。所以,要想解决上述问题,就要确保系统上只运行一个病毒木马的进程实例。
    确保运行一个进程实例的实现方法有很多,可以是通过扫描进程列表来实现,也可以通过枚举程序窗口的方式来实现或者可以通过共享全局变量来实现。接下来,本文将介绍一种被病毒木马广泛使用而且使用简单的方法,即通过创建系统命名互斥对象的方式实现。
    函数介绍1. CreateMutex函数
    创建或打开一个已命名或未命名的互斥对象。
    HANDLE WINAPI CreateMutex( _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, _In_ BOOL bInitialOwner, _In_opt_ LPCTSTR lpName);
    参数
    lpMutexAttributes [in, optional]指向SECURITY_ATTRIBUTES结构的指针。如果此参数为NULL,则该句柄不能由子进程继承。bInitialOwner [in]如果此值为TRUE并且调用者创建了互斥锁,则调用线程将获得互斥锁对象的初始所有权。否则,调用线程不会获得互斥锁的所有权。lpName [in, optional]互斥对象的名称。该名称仅限于MAX_PATH字符。名称比较区分大小写。如果lpName为NULL,则会创建不带名称的互斥体对象。如果lpName与现有事件,信号量,等待定时器,作业或文件映射对象的名称匹配,则该函数将失败,并且GetLastError函数返回ERROR_INVALID_HANDLE。这是因为这些对象共享相同的名称空间。该名称可以具有“Global”或“Local”前缀以在全局或会话名称空间中显式创建对象。名称的其余部分可以包含除反斜杠字符(\)以外的任何字符。
    返回值
    如果函数成功,则返回值是新创建的互斥对象的句柄。如果函数失败,返回值为NULL。 要获得扩展的错误信息,请调用GetLastError。如果互斥锁是一个已命名的互斥锁,并且该对象在此函数调用之前就存在,则返回值是现有对象的句柄,GetLastError返回ERROR_ALREADY_EXISTS。

    实现原理通常情况下,系统上的进程是相互独立的,每个进程都拥有自己独立资源和地址空间,进程间互不影响。所以,同一个程序可以重复运行,在系统上的进程互不影响。但是,在一些特殊的情况,需要程序在系统上只存在一份进程实例,这就引出了进程互斥的问题。
    微软提供了CreateMutex函数来实现创建或者打开一个已命名或未命名的互斥对象,程序每次运行的时候,通过判断系统上是否存在相同的命名互斥对象来确定程序是否重复运行。
    CreateMutex函数一共有三个参数,第一个参数表示互斥对象的安全设置,是一个指向SECURITY_ATTRIBUTES结构的指针,在该程序中直接设置为NULL即可。第二个参数表示线程是否获得互斥锁对象的初始所有权,在该程序中,无论该参数为TRUE或者FALSE,均不影响程序的正常执行。第三个参数表示互斥对象的名称,对于通过过互斥对象来判断进程实例是否重复运行的程序来说,该参数一定要设置,而且设置的名称要保证唯一性。
    程序的判断原理是通过CreateMutex函数创建一个命名互斥对象,如果对象创建成功,而且通过调用GetLastError函数获取的返回码的值为ERROR_ALREADY_EXISTS,则表示该命名互斥对象存在,即程序重复运行。否则,认为是首次运行程序。
    编码实现// 判断是否重复运行BOOL IsAlreadyRun(){ HANDLE hMutex = NULL; hMutex = ::CreateMutex(NULL, FALSE, "TEST"); if (hMutex) { if (ERROR_ALREADY_EXISTS == ::GetLastError()) { return TRUE; } } return FALSE;}
    测试直接运行上面的程序,第一次运行的时候,程序提示“NOT Already Run!“,如图2-1所示,意思是系统上没有该实例运行。接着继续双击执行程序,这次程序提示”Already Run!!!!“,如图2-2所示,意思是系统上已经存在该实例正在运行着。所以,程序成功判断出程序是否重复运行。


    小结这个程序实现起来不难,关键是熟悉对CreateMutex函数的调用。在调用CreateMutex函数来创建命名的互斥对象的时候,注意互斥对象的名称不要与现有事件、信号量或者文件映射对象等的名称相同,否则创建互斥对象失败。
    在实现的过程中,特别要注意,程序一定不要调用CloseHandle函数来关闭CreateMutex函数创建出来的互斥对象的句柄,否则会导致互斥对象判断失败。因为CloseHandle函数会关闭互斥对象的句柄,释放资源。这样,系统上便不会存在对应的命名互斥对象了。这样,通过CreateMutex来创建命名互斥对象都不会重复的。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-07 11:53:44 奖励5点积分
  • DLL延迟加载

    背景我们在开发程序的时候,通常会使用第三方库,但是并不是所有的第三方库都会提供静态库文件,而是提供动态库文件。这样,我们的程序就需要相应的DLL文件才能加载启动。
    现在,我们想大家介绍一种DLL延迟加载技术,使用延迟加载方式编译链接可执行文件。这样,可执行程序就可以先加载执行,所依赖的DLL在正式调用时才被加载进来。
    这样做的好处就是,我们可以把必需的DLL文件以资源的形式包含到我们的程序中,并使用DLL延迟加载技术延迟加载必需的DLL。这样,在正式调用必需的DLL之前,程序都是可以正常执行的,我们可以在程序运行的时候,就把在资源中的DLL释放到本地,所以,程序所有功能就可以正常执行了。我们只需把EXE文件发送给用户,而不需要附加DLL文件了,也不需要担心程序会丢失DLL文件。
    实现原理DLL延迟加载原理本程序以加载第三方库——skin++库为例子进行讲解演示。我们在导入库文件,编码完成后,对程序编译链接等到可执行文件。我们使用PEview.exe查看可执行文件的导入表,就可知道可执行文件所必需的DLL文件。

    上面可以知道,导入表中有SkinPPWTL.dll文件,也就是说,程序加载运行的时候,SkinPPWTL.dll文件必需存在,否则程序不能正常加载启动。
    DLL延迟加载技术的原理,其实就是在导入表中去掉SkinPPWTL.dll这一项,等到DLL被正式调用的时候,才会加载DLL文件。这样,程序在正式调用DLL之前,都是可以正常执行的。
    DLL延迟加载实现DLL延迟加载的实现,并不需要任何编码,只需要设置下开发环境的链接选项即可。本程序使用的是VS2013开发环境,还是继续上述skin++皮肤库的例子进行讲解。DLL延迟加载实现的步骤为:
    属性—>链接器—>输入—>延迟加载的DLL—>输入:SkinPPWTL.dll

    这样,DLL延迟加载就完成了。这时,我们编译链接,再用PEview.exe查看可执行程序的导入表信息:

    可见,导入表中没有SkinPPWTL.dll文件了。所以,程序在正式调用该DLL之前,程序都是可以加载执行的。
    接下来,我们就把SkinPPWTL.dll作为资源导入,在加载皮肤之前就释放DLL到本地上,这样程序正常加载执行了。
    编码实现皮肤加载// 加载界面皮肤void LoadSkin(){ // 加载皮肤 // 获取当前目录 char szCurrentPath[MAX_PATH] = { 0 }; GetCurrentPath(szCurrentPath, MAX_PATH); // 构造路径 ::lstrcat(szCurrentPath, "\\Skins\\"); ::lstrcat(szCurrentPath, "dogmax2.ssk"); // 加载皮肤 ::skinppLoadSkin(szCurrentPath);}
    资源释放// 释放资源BOOL FreeMyResource(UINT uiResouceName, char *lpszResourceType, char *lpszSaveFileName){ HRSRC hRsrc = ::FindResource(NULL, MAKEINTRESOURCE(uiResouceName), lpszResourceType); if (NULL == hRsrc) { FreeRes_ShowError("FindResource"); return FALSE; } DWORD dwSize = ::SizeofResource(NULL, hRsrc); if (0 >= dwSize) { FreeRes_ShowError("SizeofResource"); return FALSE; } HGLOBAL hGlobal = ::LoadResource(NULL, hRsrc); if (NULL == hGlobal) { FreeRes_ShowError("LoadResource"); return FALSE; } LPVOID lpVoid = ::LockResource(hGlobal); if (NULL == lpVoid) { FreeRes_ShowError("LockResource"); return FALSE; } FILE *fp = NULL; fopen_s(&fp, lpszSaveFileName, "wb+"); if (NULL == fp) { FreeRes_ShowError("LockResource"); return FALSE; } fwrite(lpVoid, sizeof(char), dwSize, fp); fclose(fp); return TRUE;}
    程序测试我们直接运行EXE程序,程序正常执行,DLL成功释放,皮肤加载成功:

    总结该程序实现并不难,只是多了个设置链接选项。这样,程序就会更加简洁,而且方便易用。这算是程序开发的一个小技巧吧,希望能对大家有所帮助。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-07 11:50:46 奖励6点积分
  • 判断指定进程是否具有管理员权限

    背景某天,一个小伙伴突然问我,用什么程序或者软件可以查看计算机上进程是否具有管理员或者管理员以上的权限。我一下子懵了,印象中好像还真没有遇到过可以判断进程是否有管理员权限的软件。我运行了我电脑上的 Process Explorer 进行查看,好像确实没有检测管理员权限的功能。
    但是,作为一个动手能力较强的程序员,没有的程序可以自己写。于是,自己上网搜索了判断进程是否具有管理员权限的实现原理。原来,就是通过获取进程令牌的令牌特权提升信息,令牌特权提升信息中就专门有一个标志表示进程权限是否提升。如果进程权限已经提升,则说明是具有管理员或者管理员以上的权限,否则,只是普通权限。
    现在,我就把程序实现的过程及其原理进行整理,形成文档,分享给大家。
    函数介绍OpenProcess 函数
    打开一个已存在的进程对象,并返回进程的句柄。
    函数声明
    HANDLE OpenProcess( DWORD dwDesiredAccess, //渴望得到的访问权限(标志) BOOL bInheritHandle, // 是否继承句柄 DWORD dwProcessId // 进程标示符);
    参数

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

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

    OpenProcessToken 函数
    打开与进程关联的访问令牌。
    函数声明
    BOOL WINAPI OpenProcessToken( _In_ HANDLE ProcessHandle, _In_ DWORD DesiredAccess, _Out_ PHANDLE TokenHandle);
    参数

    ProcessHandle [in]要打开访问令牌的进程的句柄。 该进程必须具有PROCESS_QUERY_INFORMATION访问权限。DesiredAccess [in]指定一个访问掩码,指定访问令牌的请求类型。 这些请求的访问类型与令牌的自由访问控制列表(DACL)进行比较,以确定哪些访问被授予或拒绝。TokenHandle [出]指向一个句柄的指针,用于标识当函数返回时新打开的访问令牌。
    返回值

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

    GetTokenInformation 函数
    获取有关访问令牌的指定类型的信息。 调用进程必须具有获取信息的适当访问权限。
    函数声明
    BOOL WINAPI GetTokenInformation( _In_ HANDLE TokenHandle, _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, _Out_opt_ LPVOID TokenInformation, _In_ DWORD TokenInformationLength, _Out_ PDWORD ReturnLength);
    参数

    TokenHandle [in]检索信息的访问令牌的句柄。如果TokenInformationClass指定TokenSource,则该句柄必须具有TOKEN_QUERY_SOURCE访问权限。对于所有其他TokenInformationClass值,句柄必须具有TOKEN_QUERY权限。TokenInformationClass[in]指定来自TOKEN_INFORMATION_CLASS枚举类型的值,以识别函数检索的信息类型。检查TokenIsAppContainer并返回0的任何呼叫者也应该验证呼叫者令牌不是识别级别的模拟令牌。如果当前令牌不是应用程序容器,而是身份级别令牌,则应返回AccessDenied。TokenInformation[out]指向缓冲区的指针,该函数填充所请求的信息。放入此缓冲区的结构取决于由TokenInformationClass参数指定的信息类型。TokenInformationLength[in]指定由TokenInformation参数指向的缓冲区的大小(以字节为单位)。如果TokenInformation为NULL,则此参数必须为 0。ReturnLength [out]指向一个变量的指针,该变量接收TokenInformation参数指向的缓冲区所需的字节数。如果该值大于TokenInformationLength参数中指定的值,则该函数将失败,并且不将数据存储在缓冲区中。如果TokenInformationClass参数的值为TokenDefaultDacl,并且令牌没有默认DACL,则该函数将ReturnLength指向的变量设置为sizeof(TOKEN_DEFAULT_DACL),并将TOKEN_DEFAULT_DACL结构的DefaultDacl成员设置为NULL。
    返回值

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

    实现原理判断一个进程是否具有管理员权限,可以分成 4个步骤:

    首先,使用 OpenProcess 打开进程,并获取 PROCESS_ALL_ACCESS 权限的进程句柄
    然后,根据进程句柄,可以调用 OpenProcessToken 函数打开进程令牌,并获取具有 TOKEN_QUERY 权限的进程令牌。因为下面我们需要查询进程令牌的权限提升信息,所以要求进程令牌必须具有 TOKEN_QUERY 权限
    接着,我们就可以调用 GetTokenInformation 函数,获取令牌权限提信息 TokenElevation,获取的信息存储在 TOKEN_ELEVATION 结构体缓冲区里。TOKEN_ELEVATION 结构体的成员 TokenIsElevated 就表示进程令牌权限是否提升。TRUE,表示已经提升,则是管理员或者管理员以上的权限;FALSE,表示没有提升,则表示普通权限
    最后,我们关机进程句柄,返回判断结果

    编码实现// 判断指定进程是否具有管理员或者管理员以上的权限BOOL IsProcessRunAsAdmin(DWORD dwProcessId){ HANDLE hProcess = NULL; HANDLE hToken = NULL; TOKEN_ELEVATION tokenEle = {0}; BOOL bElevated = FALSE; DWORD dwRet = 0; BOOL bRet = FALSE; // 打开进程, 获取进程句柄 hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (NULL == hProcess) { ShowError("OpenProcess"); return FALSE; } // 打开进程令牌, 获取进程令牌句柄 bRet = ::OpenProcessToken(hProcess, TOKEN_QUERY, &hToken); if (FALSE == bRet) { ShowError("OpenProcessToken"); return FALSE; } // 获取进程令牌特权提升信息 bRet = ::GetTokenInformation(hToken, TokenElevation, &tokenEle, sizeof(tokenEle), &dwRet); if (FALSE == bRet) { ShowError("GetTokenInformation"); return FALSE; } // 获取进程是否提升的结果 bElevated = tokenEle.TokenIsElevated; // 关闭进程句柄 ::CloseHandle(hToken); return bElevated;}
    程序测试我们以普通权限运行 520.exe 程序,然后使用我们的程序进行判断,结果判断正确:

    然后,我们以管理员权限运行 520.exe 程序,并使用我们的程序进行判断,结果正确:

    总结要注意一点就是,我们运行自己的判断程序的时候,建议使用管理员权限运行。因为我们判断一个程序是否具有管理员权限之前,先调用 OpenProcess 函数打开进程,那么,低权限程序打开高权限的程序,执行这个函数就会因为权限不足而报错。所以,如果一开始我们就以管理员权限运行我们的判断程序,就可以避免这种情况的发生。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-07 11:37:51 奖励3点积分
  • 在DllMain中测试调用LoadLibrary和CreateThread函数可正常使用 精华

    背景某天,在网上无意中看到一篇博文,标题是说在DLL的入口点函数DllMain中不能调用 LoadLibrary 加载DLL,因为会造成死锁。看到这里我楞了一下,因为我之前写过很多DLL程序,在入口点函数DllMain中也加载过其它的DLL,从没有出现过什么问题。然后,我便仔细阅读了这篇博文,大概理解了它的意思。它应该想表达的是在DLL的DllMain函数中谨慎使用 LoadLibrary,以防发生死锁情况。
    虽然都懂了它的意思,我还是决定自己再亲自动手写一下代码看看。现在,我就把实现的过程和测试心得整理成文档,分享给大家。
    实现过程首先,我直接创建一个名为 Dll_LoadLibrary_CreateThread_Test 的DLL项目工程,在 DllMain 的 DLL_PROCESS_ATTACH 时候直接调用 CreateThread 函数创建一个多线程,同时,也调用 LoadLibrary 加载另一个 DLL 文件。
    BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { // 创建多线程 ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 加载DLL HMODULE hDll = ::LoadLibrary("testdll.dll"); if (NULL == hDll) { ::MessageBox(NULL, "load testdll.dll error.", "error", MB_OK); } break; } … …(省略) } return TRUE;}
    其中,ThreadProc 多线程函数,执行的操作就是每个 5000 毫秒就弹窗提示一次。
    UINT ThreadProc(LPVOID lpVoid){ while (TRUE) { Sleep(5000); ::MessageBox(NULL, "this si from createthread.", "createthread", MB_OK); } return 0;}
    在 testdll.dll 这个DLL的入口点函数DllMain中,直接弹窗提示。
    BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { // 弹窗 ::MessageBox(NULL, "this is from testdll.dll DllMain function.", "testdll.dll", MB_OK); break; } … …(省略) } return TRUE;}
    然后,我们直接加载 Dll_LoadLibrary_CreateThread_Test.dll 文件,成功弹出下图所示的两个提示框,说明 CreateThread 函数成功创建多线程,LoadLibrary 成功加载DLL。


    总结经过上面的例子测试,说明在 DllMain 函数中,是可以使用 CreateThread 以及 LoadLibrary 函数的。只要我们避免相互在 DllMain 中相互调用,出现死锁,如下面这种相互调用的情况:

    DllB 在 DllMain 里调用 LoadLibrary 加载 DllA
    DllA 在 DllMain 里调用 LoadLibrary 加载 DllB

    这样,就会无限循环加载下去,形成死锁。
    所以,之所以说 在DllMain里不能调用LoadLibrary函数,其实,并不是说只要是在 DllMain 中,都不能调用 LoadLibrary 函数,而是说,如果这两个 Dll 如果在 DllMain 中相互调用的情况下,是会出错的,所以,为了避免这种相互调用死锁的情况发生,就不提倡在 DllMain 中调用 LoadLibrary!实际上,只要避免这种相互调用的情况,LoadLibrary 还是可以使用的!
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-07 11:31:42 奖励15点积分
  • 编程实现对ini配置文件的读写

    背景在安装一些应用程序的时候,我们经常可以其安装目录下看到有 .ini 格式的配置文件,这种格式配置文件是我们比较常见的。在我们自己电脑的系统上也会有,而且还很多。很多系统自带的程序,都会有一个 .ini 格式的配置文件。
    为此,Windows还特地提供了相应的WIN32 API函数去对 .ini 格式的配置文件进行读写操作。所以,本文就主要介绍下如何使用API函数来实现对 .ini 配置文件的读写。现在,我就把实现过程整理成文档,分享给大家。
    函数介绍WritePrivateProfileString 函数
    将一个字符串复制到INI文件的指定的字段中。
    函数声明
    BOOL WINAPI WritePrivateProfileString( _In_ LPCTSTR lpAppName, _In_ LPCTSTR lpKeyName, _In_ LPCTSTR lpString, _In_ LPCTSTR lpFileName);
    参数

    lpAppName [in]要复制字符串的字段名称。 如果该字段不存在,则创建它。
    lpKeyName [in]
    与字符串关联的键的名称。 如果该键在指定的字段下不存在,则创建它。 如果此参数为NULL,则整个字段(包括该字段中的所有的键)将被删除。
    lpString [in]要写入文件的以NULL结尾的字符串。 如果此参数为NULL,则lpKeyName参数指向的键将被删除。
    lpFileName [in]INI文件的名称。如果文件是使用Unicode字符创建的,则该函数将Unicode字符写入该文件。 否则,函数写入ANSI字符。

    返回值

    如果函数成功将字符串复制到初始化文件,则返回值不为零。如果函数失败,或者刷新最近访问的初始化文件的缓存版本,返回值为零。 要获取扩展错误信息,请调用GetLastError。

    GetPrivateProfileString 函数
    从INI文件中的指定的字段中获取一个字符串。
    函数声明
    DWORD WINAPI GetPrivateProfileString( _In_ LPCTSTR lpAppName, _In_ LPCTSTR lpKeyName, _In_ LPCTSTR lpDefault, _Out_ LPTSTR lpReturnedString, _In_ DWORD nSize, _In_ LPCTSTR lpFileName);
    参数

    lpAppName [in]字段的名称。如果此参数为NULL,则GetPrivateProfileString函数将文件中的所有字段名复制到提供的缓冲区里。lpKeyName [in]要检索的键名。如果此参数为NULL,则由lpAppName参数指定的字段中的所有键名将复制到由lpReturnedString参数指定的缓冲区中。lpDefault [in]默认字符串。如果在INI文件中找不到lpKeyName键,则GetPrivateProfileString将默认字符串复制到lpReturnedString缓冲区。如果此参数为NULL,则默认值为空字符串“”。避免指定一个带有空白字符的默认字符串。该函数在lpReturnedString缓冲区中插入一个空字符以去除任何尾随的空白。lpReturnedString [out]指向接收检索字符串的缓冲区的指针。nSize [in]lpReturnedString参数指向的缓冲区的大小,以字符为单位。lpFileName [in]INI文件的名称。如果此参数不包含文件的完整路径,系统将在Windows目录中搜索该文件。
    返回值
    返回复制到缓冲区的字符数,不包括终止空字符。

    GetPrivateProfileInt 函数
    在INI文件中的指定的字段中获取它的整数值。
    函数声明
    UINT WINAPI GetPrivateProfileInt( _In_ LPCTSTR lpAppName, _In_ LPCTSTR lpKeyName, _In_ INT nDefault, _In_ LPCTSTR lpFileName);
    参数

    lpAppName [in]INI文件中的字段名称。lpKeyName [in]要检索其值的键名。此值为字符串的形式,GetPrivateProfileInt函数将字符串转换为整数并返回整数。nDefault [in]如果在初始化文件中找不到键名,返回的默认值。lpFileName [in]INI文件的名称。 如果此参数不包含文件的完整路径,系统将在Windows目录中搜索该文件。
    返回值

    返回INI文件中指定键名后的字符串转换后的整数。 如果找不到键,则返回值是指定的默认值。

    实现过程根据上面的函数介绍,我们直接调用上述函数对INI配置文件进行操作。本文的例子如下:
    首先,我们调用 WritePrivateProfileString 函数创建一个名为Config.ini的INI文件,并添加INFORMATION和OTHER字段;在INFORMATION字段下,有两个键名分别为name和age的键,其中键名为name中存储的键值是DemonGan,键名为age中存储的键值为18。在OTHER字段中,键名为class的键值为no1。
    char szFileName[] = "C:\\Users\\DemonGan\\Desktop\\ReadWriteIniFile_Test\\Debug\\Config.ini"; // 向INI文件中写入数据 ::WritePrivateProfileString("INFORMATION", "name", "DemonGan", szFileName); ::WritePrivateProfileString("INFORMATION", "age", "18", szFileName); ::WritePrivateProfileString("OTHER", "class", "no1", szFileName);
    那么,生成的INI文件的数据内容如下所示:

    然后,我们调用 GetPrivateProfileString 函数,读取INI文件中的INFORMATION字段下的name键的数据。
    // 从INI文件中读取字符串数据 char szReturnString[MAX_PATH] = {0}; ::GetPrivateProfileString("INFORMATION", "name", NULL, szReturnString, MAX_PATH, szFileName); printf("name=%s\n", szReturnString);
    接着,我们调用 GetPrivateProfileInt 函数,读取INI文件中的INFORMATION字段下的age键的数据。
    // 从INI文件中读取整型数据 int iReturnInt = ::GetPrivateProfileInt("INFORMATION", "age", 0, szFileName); printf("age=%d\n", iReturnInt);
    对于类似age=18的整型数据的读取,我们除了可以使用 GetPrivateProfileInt 函数读取之外,还可以使用 GetPrivateProfileString 函数去读取,只要将获取的字符串转换为整型就可以了。其中,GetPrivateProfileInt 函数也是先调用获取字符串,然后再转换为整型,所以,GetPrivateProfileInt 函数是对 GetPrivateProfileString 函数的封装和拓展。
    程序测试1我们直接运行程序,则成功从INI文件中获取数据并显示出来,同时目录下有 Config.ini 文件生成,打开文件可以查看里面的数据内容。

    总结要注意的是,上面 3 个函数的INI文件路径最好都写上绝对路径,否则系统会自动在Windows目录中搜索,这样,会导致文件数据读取失败。
    1 留言 2018-11-07 11:13:17 奖励5点积分
显示 270 到 285 ,共 15 条
eject