前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >让应用程序同时只启动一次

让应用程序同时只启动一次

作者头像
河边一枝柳
发布2021-08-06 15:13:54
8350
发布2021-08-06 15:13:54
举报

软件的实现方式或者第三方的依赖只能保证单进程运行,也就是说只能让程序同一时间启动一个进程。

本文将讲解通过内核命名对象来保证进程只启动一次,并且描述了如何防止拒绝服务攻击。

一. 命名的内核对象

利用 内核中的命名对象 名称不能重复这一特性,来实现 应用程序的只启动一次的请求。

以Mutex为例,可以利用CreateMutex函数:

代码语言:javascript
复制
HANDLE CreateMutex(
  LPSECURITY_ATTRIBUTESlpMutexAttributes,    // 指向安全属性的指针
  BOOLbInitialOwner,                         // 初始化互斥对象的所有者,TRUE表示创建锁线程所拥有这个锁,反之则表示没有线程占用这个锁
  LPCTSTRlpName                              // 指向互斥对象名
  );

使用锁来实现应用程序启动一次:

代码语言:javascript
复制
  //在应用程序入口处,创建一个"TestForApp"的锁
  HANDLE g_hMtx = CreateMutex(NULL, FALSE, "TestForApp");
  //如果已经存在这个锁,则认为应用程序已经启动
  if (GetLastError() == ERROR_ALREADY_EXISTS)
  {
    printf("应用程序实例已经启动!\n");
    return -1;
  }
  //应用程序内容
  //...

二. 唯一的锁名

第一节中给出的例子,如果另外的应用程序刚好也创建了一个名为"TestForApp"名字的锁,将会导致此应用程序无法启动。那如何保证锁名的唯一性呢?

2.1 GUID保证锁名唯一性

GUID(Global Unique Identifier,全球唯一标示符),它由128位的整数表示,表现为格式:"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"(每一个X表示0-9或者A-F的字符),其用来保证不同计算机,不同时间产生唯一的标示符。

可以利用GUID生成一个唯一的名字,作为锁名;

2.2 GUID产生锁名

GUID可以网络搜索"在线生成GUID"便可以找到一个在线网站,快速创建一个GUID。 如果你愿意也可以自己写个小工具生成GUID.

代码语言:javascript
复制
  GUID guid;
  CoCreateGuid(&guid);
  printf("%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X\n",
    guid.Data1, guid.Data2, guid.Data3,
    guid.Data4[0], guid.Data4[1],
    guid.Data4[2], guid.Data4[3],
    guid.Data4[4], guid.Data4[5],
    guid.Data4[6], guid.Data4[7]);
  //运行产生GUID"D5F78344-5051-4CB3-9CA7-A5A4B5AAEBD1"

举例,将"D5F78344-5051-4CB3-9CA7-A5A4B5AAEBD1" GUID取代 "TestForApp"作为锁名,来保证锁名的唯一性。

三. 避免Dos(Denial Of Service, 拒绝服务攻击)

3.1 攻击方法

第二节中的方法,虽然避免了锁名冲突的可能性,但并没有避免 有目的制造冲突。

通过Process Explorer工具查看到应用程序所创建的内核对象的名称,如下图所示,能够显示刚创建的名为"D5F78344-5051-4CB3-9CA7-A5A4B5AAEBD1"锁。

黑客很可能利用这个锁名,在应用服务程序启动之前,先创建这个锁,从而导致应用服务程序启动失败。

3.2 解决方法

创建专有的命名空间,专有命名空间就类似于 在内核名称之前在加上一个目录名称,ProcessExplorer中显示为 "\..\锁名",而不会暴露专有命名空间名字。

专有命名空间关联一个 边界描述符 (Boundary Descriptor), 边界描述符 至少包含一个SID;

代码语言:javascript
复制
//参考<<windows核心编程>>
#include<windows.h>
#include <Sddl.h>
#include<stdio.h>
#include<string.h>
#include <strsafe.h>
 
int main ()
{
  //创建边界描述符
  PCTSTR g_szBoundary = TEXT("TestForBoundary");
  HANDLE g_hBoundary = CreateBoundaryDescriptor(g_szBoundary, 0);
 
  //添加管理员组SID到边界描述符
  BYTE localAdminSID[SECURITY_MAX_SID_SIZE];
  PSID pLocalAdminSID = &localAdminSID;
  DWORD cbSID = sizeof(localAdminSID);
  if (!CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, pLocalAdminSID, &cbSID))
  {
    printf("CreateWellKnownSid Failed!\n");
    return -1;
  }
 
  if (!AddSIDToBoundaryDescriptor(&g_hBoundary, pLocalAdminSID))
  {
    printf("AddSIDToBoundaryDescriptor Failed!\n");
    return -1;
  }
 
  //产生安全信息
  SECURITY_ATTRIBUTES sa;
  sa.nLength = sizeof(sa);
  sa.bInheritHandle = FALSE;
  if (!ConvertStringSecurityDescriptorToSecurityDescriptor(TEXT("D:(A;;GA;;;BA)"),
    SDDL_REVISION_1, &sa.lpSecurityDescriptor, NULL))
  {
    printf("ConvertString Failed!\n");
    return -1;
  }
 
  //创建专有命名空间 "TestForNamespace"
  PCTSTR g_szNamespace = TEXT("TestForNamespace");
  HANDLE g_hNamespace = CreatePrivateNamespace(&sa, g_hBoundary, g_szNamespace);
  if (g_hNamespace == NULL)   
  {
    //这里没有进行处理,如果已经创建,可以打开专有命名空间
    //OpenPrivateNamespace API
    printf("CreatePrivateNamespace Failed!\n");
    return -1;
  }
  LocalFree(sa.lpSecurityDescriptor);
 
  //创建锁
  TCHAR szMutexName[64];
  StringCchPrintf(szMutexName, _countof(szMutexName), TEXT("%s\\%s"),
    g_szNamespace, TEXT("TestForApp"));
  HANDLE g_hSingleton = CreateMutex(NULL, FALSE, szMutexName);
  if (GetLastError() == ERROR_ALREADY_EXISTS)
  {
    printf("应用程序实例已经启动!\n");
    return -1;
  }
  return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一个程序员的修炼之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. 命名的内核对象
  • 三. 避免Dos(Denial Of Service, 拒绝服务攻击)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档