1.  前言

在使用 Sysinternals 出品的 Process Explorer 过程中,对 “Run as Limited User” 功能的实现方式颇感兴趣,一番搜寻之下发现Mark大神在《Running as Limited User – the Easy Way》中对该功能的实现做了相关的阐述:

use the CreateRestrictedToken API to create a security context, called a token, that’s a stripped-down version of its own, removing administrative privileges and group membership. After generating a token that looks like one that Windows assigns to standard users Process Explorer calls CreateProcessAsUser to launch the target process with the new token.

使用 CreateRestrictedToken API来创建安全上下文,降低令牌(Token)的管理员权限和组成员资格,使其创建的令牌看起来像Windows赋予普通用户时一样,然后使用此令牌作为传入参数调用CreateProcessAsUser来创建新的子进程。

Process Explorer queries the privileges assigned to the Users group and strips out all other privileges, including powerful ones like SeDebugPrivilege, SeLoadDriverPrivilege and SeRestorePrivilege.

查询赋予用户组的特权并从当前进程权限中剔除这些权限比如SeDebugPrivilege、SeLoadDriverPrivilege和SeRestorePrivilege。

刚好最近有个项目需要实现降低进程权限的功能,在一翻折腾下就将其实现了,下面将谈谈实现的历程,如果纰漏之处,不吝指出。

   2.  知识背书

在列出代码前需要了解一下一些实现原理,下面是一些相关的知识点,如果无耐心往下看,可以直接点击这里跳到代码实现处。

安全对象

有资格拥有安全描述符的对象如文件、管道、进程、进程间同步对象等。所有已命名的Windows对象都是安全的,那些未被命名的对象比如线程或进程对象也可以拥有安全描述符。

对于大多数的安全对象,当创建该对象时可以指定它的安全描述符。当一个安全对象被创建时,系统会对其赋予一个安全描述符,安全描述符包含由其创建者指定的安全信息,或者缺省的安全信息(如果没有特意进行指定的话)。

  • ž   应用程序可以使特定的函数对已有的对象进行操作以来获取和设置安全信息。
  • ž   每种类型的安全对象定义了它自身的访问权限和自身映射的通用访问权限。

更详细内容见:https://msdn.microsoft.com/en-us/library/windows/desktop/aa379557(v=vs.85).aspx

安全描述符(security descriptor)

包含用于保护安全对象的安全信息。

安全描述符描述 对象的所有者(SIDs) 和 以下的访问控制列表:

  • 自由访问控制列表(DACL):指明特定用户或组对该对象的访问是允许或拒绝;
  • 安全访问控制列表(SACL):控制系统审计如何访问对象。

安全标识(Security Identifiers)

一定长度用来表示托管的唯一值。

安全标识主要运用于如下几个方面:

  • 在安全描述符中定义对象的所有者和基本组;
  • 在访问控制项中定义托管的权限是允许、拒绝或是审计;
  • 在访问令牌中定义用户和用户所在的组。

访问令牌

包含登录用户的信息。用来描述一个进程或线程的安全上下文的对象,令牌的信息包含关联到进程或线程的账号的标识和特权。

当一个用户登录时,系统对用户的账号和密码进行认证,如果登录成功,系统则创建一个访问令牌,每个进程运行时都有一个访问令牌代表当前的用户,访问令牌中的安全描述符指明当前用户所属的账号和所属的组账号,令牌也包含一系列由用户或用户所在组进行维护的权限,在一个进程试图进行访问安全对象或执行系统管理员任务过程中需要权限时,系统通过这个令牌来确认关联的用户。

 

访问控制列表及其访问控制项

自由访问控制列表(DACL)包含若干个访问控制项(ACEs)。

约定的执行规则如下:

  • ž   如果对象没有自由访问控制列表(DACL),则任何用户对其均有完全的访问权限;
  • ž   如果对象拥有DACL,那么系统仅允许那些在访问控制项(ACE)显式指明的访问权限;
  • ž   如果在访问控制列表(DACL)中不存在访问控制项(ACE),那么系统不允许任何用户对其进行访问;
  • ž   如果访问控制列表中的访问控制项对准许访问的用户或组数目有限,那么系统会隐式拒绝那些不在访问控制项中的其他托管的访问

需要注意的是访问控制项的排序很重要。因为系统按照队列的方式读取访问控制项,直到访问被拒绝或允许。用户的访问拒绝ACE必须放在访问允许ACE的前头,否则当系统读到对组的访问允许ACE时,它会给当前限制的用户赋予访问的权限。系统在检测到请求访问被允许或拒绝后就不再往下检查。

你可以通过标识允许访问的ACE来控制对对象的访问,你无需显式地拒绝一个对象的访问。

线程和安全对象间的交互

当一个线程想要使用一个安全对象时,系统在线程执行前会进行访问审核,在访问审核中,系统将线程访问令牌中的安全信息与对象安全描述符中的安全信息进行比对。

访问令牌中包含的安全标识(SIDs)可以指明与线程关联的用户,系统查看线程访问令牌中用户或组的SID,同时检查对象的自由访问控制列表(DACL),自由访问控制列表(DACL)中包含存储有指明对指定的用户或组的访问权限是允许或拒绝信息的访问控制项(ACE),系统检查每个访问控制项(ACE)直至出现指明针对此线程(的用户或组的SID)的访问权限是允许还是拒绝的ACE,或者到最终都没有对应的ACEs可以检查。

(图片出处:https://msdn.microsoft.com/en-us/library/windows/desktop/aa378890(v=vs.85).aspx

系统按照序列检查每个ACE,查询ACE中的托管与定义在线程中的托管(根据托管的SID)一致的ACE,直到如下的情况出现:

  • ž   表明访问拒绝的ACE显式拒绝在线程的访问令牌中的一个托管的任何访问权限;
  • ž   一个或多个表明访问允许的ACEs显式地为线程访问令牌中的托管提供所有访问权限;
  • ž   所有ACEs已经比对审核完但至少有一个请求访问权限没有显式允许,这种情况下该访问权限则被隐式拒绝。

一个访问控制列表(ACL)可以有多个的访问控制项(ACE)针对令牌的(同一个)SIDs,这种情况下每个ACE授予的访问权限是可以进行累积叠加,比如,如果一个ACE对一个组允许读的访问权限,而另一个ACE对该组内的一个用户允许写的访问权限,那么该用户对于当前对象就拥有了读和写的访问权限。

(图片出处:https://msdn.microsoft.com/en-us/library/windows/desktop/aa446597(v=vs.85).aspx

如上图所示,对于线程A,尽管在ACE@3中允许写权限,但因为在ACE@1中已经显式拒绝“Andrew”用户的访问权限,所以该安全对象对于线程A是不可访问的;对于线程B,在ACE@2中显式指明A组用户可以有写的权限,并且在ACE@3中允许任何用户读和执行的权限,所以线程B对这个安全对象拥有读、写以及执行的权限。

完整性级别

Windows完整性机制是对Windows安全架构的扩展,该完整性机制通过添加完整性等级到安全访问令牌和添加强制性标记访问控制项到安全描述符中的系统访问控制列表(SACL)

进程在安全访问令牌中定义完整性等级,IE在保护模式下的完整性等级为低,从开始菜单运行的应用程序的等级为中等,需要管理员权限并以管理员权限运行的应用程序的等级为高。

保护模式能够有效地减少IE进程附带的攻击行为如篡改和摧毁数据、安装恶意程序;相比其他程序,连接网络的程序更容易遭受网络的攻击因为它们更可能从未知源地址下载未受信任的内容,通过降低完整性等级或限制对其的允许权限,可以减少篡改系统或污染用户数据文件的风险。

在系统访问控制列表(SACL)中有一个称为强制标识的访问控制项(ACE),该控制项的安全描述符定义完整性等级或允许访问当前对象需要达到的等级,安全对象如果没有该控制项则默认拥有中等的完整性等级;

即使用户在自由访问控制列表(DACL)中已经明确授予相应的写权限,低等级的进程也不能获取比其高等级的安全对象的写权限,完整性等级检验在用户访问权限审查之前完成。

所有的文件和注册表键在缺省下的完整性等级为中,而由低等完整性进程创建的安全对象,系统会自动地赋予其低等完整性强制标志,同样,由低等完整性进程创建的子进程也是在低完整性等级下运行。

完整性访问等级(IL)

系统权限

安全标识

System

System

S-1-16-16384

High

Administrative

S-1-16-12288

可安装文件到Program Files文件夹、往敏感的注册表中如HKEY_LOCAL_MACHINE写数据

Medium

User

S-1-16-8192

创建和修改用户文档中的文件、往特定用户的注册表如HKEY_CURRENT_USER中写数据

Low

Untrusted

S-1-16-4096

仅能往低等级位置写数据如临时网络文件夹和注册表

HKEY_CURRENT_USER\Software\LowRegistry

低完整性进程可以往用户存档文件下写文件,通常为%USER PROFILE%\AppData\LocalLow,可以通过调用SHGetKnownFolderPath 函数并传入FOLDERID_LocalAppDataLow参数来获取完整的路径名称

SHGetKnownFolderPath(FOLDERID_LocalAppDataLow, 0, NULL, szPath, ARRAYSIZE(szPath));

同样低完整性进程可以往指定的注册表下创建和修改子键,该注册表路径通常为HKEY_CURRENT_USER\Software\AppDataLow

   3.  代码实现

实现思路

  1. 创建新的普通用户组和系统管理员的安全描述符标识;
  2. 获取当前进程的令牌,并根据令牌句柄获取当前进程所拥有的特权;
  3. 通过已创建的普通用户组的安全描述符标识获取普通用户组所拥有的特权;
  4. 给当前进程令牌中的管理员安全描述符添加Deny-only属性,以此达到避免新创建的进程以管理员作为其所有者;
  5. 从当前进程拥有的特权中剔除普通用户组所没有的特权;
  6. 从新的受限令牌中复制为模拟令牌;
  7. 将模拟令牌的完整性特权设为低级,以限制新创建的进程对普通文档、可执行程序的写、执行等访问权限;

代码实现

 void CreateRestrictedProcess()
{
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL; TCHAR szCmdLine[CMDLINE_SIZE] = {};
HANDLE hToken = NULL;
HANDLE hNewToken = NULL;
HANDLE hNewExToken = NULL; CHAR szIntegritySid[] = "S-1-16-4096";
PSID pIntegritySid = NULL;
PSID pUserGroupSID = NULL;
PSID pAdminSID = NULL; TOKEN_MANDATORY_LABEL tml = {}; PROCESS_INFORMATION pi;
STARTUPINFO si; BOOL bSuc = FALSE;
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
GetStartupInfo(&si);
DWORD fdwCreate = ; __try
{ if (!OpenProcessToken(GetCurrentProcess(),
//MAXIMUM_ALLOWED,
TOKEN_DUPLICATE |
TOKEN_ADJUST_DEFAULT |
TOKEN_QUERY |
TOKEN_ASSIGN_PRIMARY,
&hToken))
{
char szMsg[DEFAULT_MSG_SIZE] = {};
Dbg("OpenProcessToken failed, GLE = %u.", GetLastError());
__leave;
} Dbg("Using RestrictedTokens way !!!");
DWORD dwSize = ;
DWORD dwTokenInfoLength = ;
SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
if(!AllocateAndInitializeSid(
&SIDAuthNT,
0x2,
SECURITY_BUILTIN_DOMAIN_RID/*0×20*/,
DOMAIN_ALIAS_RID_USERS,
, , , , , ,
&pUserGroupSID))
{
Dbg("AllocateAndInitializeSid for UserGroup Error %u", GetLastError());
__leave;
} // Create a SID for the BUILTIN\Administrators group.
if(! AllocateAndInitializeSid( &SIDAuth, ,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
, , , , , ,
&pAdminSID) )
{
Dbg("AllocateAndInitializeSid for AdminGroup Error %u", GetLastError());
__leave;
} SID_AND_ATTRIBUTES SidToDisable[] = {};
SidToDisable[].Sid = pAdminSID;
SidToDisable[].Attributes = ; PTOKEN_PRIVILEGES pTokenPrivileges = NULL;
PTOKEN_PRIVILEGES pTokenPrivilegesToDel = NULL;
if(!GetTokenInformation(hToken, TokenPrivileges, NULL, , &dwSize))
{
if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
pTokenPrivileges = (PTOKEN_PRIVILEGES)LocalAlloc(, dwSize);
pTokenPrivilegesToDel = (PTOKEN_PRIVILEGES)LocalAlloc(, dwSize);
if(pTokenPrivileges != NULL && pTokenPrivilegesToDel != NULL)
{
if(!GetTokenInformation(hToken, TokenPrivileges, pTokenPrivileges, dwSize, &dwSize))
{
Dbg("GetTokenInformation about TokenPrivileges failed GTE = %u.", GetLastError());
__leave;
}
}
else
{
char szMsg[DEFAULT_MSG_SIZE] = {};
Dbg("LocalAlloc for pTokenPrivileges failed GTE = %u.", GetLastError());
__leave;
}
}
} LUID_AND_ATTRIBUTES *pTokenLUID = pTokenPrivileges->Privileges;
Dbg("CurrentToken's TokenPrivileges Count: %u", pTokenPrivileges->PrivilegeCount);
DWORD dwLuidCount = ;
PLUID pPrivilegeLuid = NULL;
if(!CTWProcHelper::GetPrivilegeLUIDWithSID(pUserGroupSID, &pPrivilegeLuid, &dwLuidCount))
{
Dbg("GetPrivilegeLUIDWithSID failed GTE = %u.", GetLastError());
if(pPrivilegeLuid)
{
//HeapFree(GetProcessHeap(), 0, pPrivilegeLuid);
LocalFree(pPrivilegeLuid);
pPrivilegeLuid = NULL;
}
__leave;
}
Dbg("UserGroup's TokenPrivileges Count: %u", dwLuidCount); DWORD dwDelPrivilegeCount = ;
for(DWORD dwIdx=; dwIdx<(pTokenPrivileges->PrivilegeCount); dwIdx++)
{
BOOL bFound = FALSE;
DWORD dwJdx = ;
for(; dwJdx<dwLuidCount; dwJdx++)
{
//if(memcmp(&(pTokenLUID[dwIdx].Luid), &(pPrivilegeLuid[dwJdx]), sizeof(LUID)) == 0)
if((pTokenLUID[dwIdx].Luid.HighPart == pPrivilegeLuid[dwJdx].HighPart)
&&
(pTokenLUID[dwIdx].Luid.LowPart == pPrivilegeLuid[dwJdx].LowPart))
{
bFound = TRUE;
break;
}
}
if(!bFound)
{
char szPrivilegeName[MAX_PATH] = {};
DWORD dwNameSize = MAX_PATH;
if(!LookupPrivilegeName(NULL, &(pTokenLUID[dwIdx].Luid), szPrivilegeName, &dwNameSize))
{
Dbg("LookupPrivilegeName failed GTE = %u.", GetLastError());
//Dbg("NoFound[%u]: i=%u, j=%u", dwDelPrivilegeCount, dwIdx, dwJdx);
}
//else
//{
// Dbg("NoFound[%u]: i=%u, j=%u -> %s", dwDelPrivilegeCount, dwIdx, dwJdx, szPrivilegeName);
//}
pTokenPrivilegesToDel->Privileges[dwDelPrivilegeCount++].Luid = pTokenLUID[dwIdx].Luid;
}
}
pTokenPrivilegesToDel->PrivilegeCount = dwDelPrivilegeCount;
Dbg("TokenPrivileges to delete Count: %u", dwDelPrivilegeCount);
if(pPrivilegeLuid)
{
//HeapFree(GetProcessHeap(), 0, pPrivilegeLuid);
LocalFree(pPrivilegeLuid);
pPrivilegeLuid = NULL;
} if(!CreateRestrictedToken(hToken,
,
, SidToDisable,
//0, NULL,
dwDelPrivilegeCount, pTokenPrivilegesToDel->Privileges,
, NULL,
&hNewToken
))
{
char szMsg[DEFAULT_MSG_SIZE] = {};
Dbg("CreateRestrictedToken failed GTE = %u.", GetLastError());
__leave;
} // Duplicate the primary token of the current process.
if (!DuplicateTokenEx(hNewToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation,
TokenPrimary, &hNewExToken))
{
Dbg("DuplicateTokenEx failed GTE = %u.", GetLastError());
hNewExToken = NULL;
//__leave;
}
else
{
if (ConvertStringSidToSid(szIntegritySid, &pIntegritySid))
{
tml.Label.Attributes = SE_GROUP_INTEGRITY;
tml.Label.Sid = pIntegritySid; // Set the process integrity level
if (!SetTokenInformation(hNewExToken, TokenIntegrityLevel, &tml,
sizeof(TOKEN_MANDATORY_LABEL) + GetLengthSid(pIntegritySid)))
{
Dbg("SetTokenInformation failed GTE = %u.", GetLastError());
//__leave;
}
else
{
CloseHandle(hNewToken);
hNewToken = hNewExToken;
hNewExToken = NULL;
Dbg("Assign Low Mandatory Level to New Token which used to CreateProcessAsUser.");
}
} } if(!(bSuc = CreateProcessAsUser(hNewToken, NULL,
szCmdLine, // command line
NULL, // TODO: process security attributes
NULL, // TODO: primary thread security attributes
TRUE, // handles are inherited ??
fdwCreate, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&si, // STARTUPINFO pointer
&pi))) // receives PROCESS_INFORMATION
{
Dbg("CreateProcessAsUser failed GTE = %u.", GetLastError());
__leave;
} if(pTokenPrivileges)
{
LocalFree(pTokenPrivileges);
}
if(pTokenPrivilegesToDel)
{
LocalFree(pTokenPrivilegesToDel);
}
}
__finally
{
if(pIntegritySid)
{
LocalFree(pIntegritySid);
}
if(pUserGroupSID)
{
LocalFree(pUserGroupSID);
}
if(pAdminSID)
{
LocalFree(pAdminSID);
}
//
// Close the access token.
//
if (hToken)
{
CloseHandle(hToken);
}
if(hNewToken)
{
CloseHandle(hNewToken);
}
if(hNewExToken)
{
CloseHandle(hNewExToken);
}
if(!bSuc)
{
Dbg("Retry to Create process in normal way.");
//Create process.
bSuc = CreateProcess(NULL,
szCmdLine, // command line
NULL, // TODO: process security attributes
NULL, // TODO: primary thread security attributes
TRUE, // handles are inherited ??
fdwCreate, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&si, // STARTUPINFO pointer
&pi); // receives PROCESS_INFORMATION
}
}
}

其中 GetPrivilegeLUIDWithSID 函数的实现如下:

 BOOL GetPrivilegeLUIDWithSID(PSID pSID, PLUID *pLUID, PDWORD pDwCount)
{
LSA_OBJECT_ATTRIBUTES ObjectAttributes;
NTSTATUS ntsResult;
LSA_HANDLE lsahPolicyHandle; // Object attributes are reserved, so initialize to zeros.
ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes)); // Get a handle to the Policy object.
ntsResult = LsaOpenPolicy(
NULL, //Name of the target system.
&ObjectAttributes, //Object attributes.
POLICY_ALL_ACCESS, //Desired access permissions.
&lsahPolicyHandle //Receives the policy handle.
); if (ntsResult != STATUS_SUCCESS)
{
printf("OpenPolicy failed returned %lu", LsaNtStatusToWinError(ntsResult));
return FALSE;
} PLSA_UNICODE_STRING UserRights = NULL;
ULONG uRightCount;
ntsResult = LsaEnumerateAccountRights(lsahPolicyHandle, pSID, &UserRights, &uRightCount);
if (ntsResult != STATUS_SUCCESS)
{
printf("LsaEnumerateAccountRights failed returned %lu", LsaNtStatusToWinError(ntsResult));
LsaClose(lsahPolicyHandle);
return FALSE;
} printf("LsaEnumerateAccountRights returned Right count: %lu", uRightCount); (*pDwCount) = ;
//pLUID = (PLUID)HeapAlloc(GetProcessHeap(), 0, uRightCount*sizeof(LUID));
(*pLUID) = (PLUID)LocalAlloc(LPTR, uRightCount*sizeof(LUID));
if((*pLUID) == NULL)
{
printf("HeapAlloc for PLUID failed returned %u", GetLastError());
LsaClose(lsahPolicyHandle);
return FALSE;
} for(ULONG uIdx=; UserRights != NULL && uIdx<uRightCount; uIdx++)
{
int nLenOfMultiChars = WideCharToMultiByte(CP_ACP, , UserRights[uIdx].Buffer, UserRights[uIdx].Length,
NULL, , NULL, NULL);
PTSTR pMultiCharStr = (PTSTR)HeapAlloc(GetProcessHeap(), , nLenOfMultiChars*sizeof(char));
if(pMultiCharStr != NULL)
{
WideCharToMultiByte(CP_ACP, , UserRights[uIdx].Buffer, UserRights[uIdx].Length,
pMultiCharStr, nLenOfMultiChars, NULL, NULL);
LUID luid;
if(!LookupPrivilegeValue(NULL, pMultiCharStr, &luid))
{
printf("LookupPrivilegeValue about %s failed, GLE=%u.", pMultiCharStr, GetLastError());
HeapFree(GetProcessHeap(), , pMultiCharStr);
continue;
}
(*pLUID)[(*pDwCount)++] = luid;
HeapFree(GetProcessHeap(), , pMultiCharStr);
}
}
if((ntsResult = LsaFreeMemory(UserRights)) != STATUS_SUCCESS)
{
printf("LsaFreeMemory failed returned %lu", LsaNtStatusToWinError(ntsResult));
}
LsaClose(lsahPolicyHandle);
return TRUE;
}

下图是普通创建子进程效果(使用 Process Explorer获取的进程信息,下同),可以看到:

  • 该进程的所有者为 Administrators;
  • 强制性标识等级为:高
  • 部分特权为 Enabled

通过执行上方的代码对新进程的创建令牌进行一系列的限制后,可以看到:

  • 进程的所有者:添加了Deny属性;
  • 强制性标识等级:低;
  • 部分特权被删除。

  4.  扩展延伸

托管

一个托管可以是用户账号、组账号或者登陆会话。是由访问控制项(ACE)赋予的,每个访问控制项(ACE)中都有一个安全标识(SID)用来表明特定的托管。

特权(Privilege)

特权用于对一个对象或服务的访问控制,比自由访问控制更为严格,一个系统管理员通过使用特权控制那些用户可以操纵系统资源,一个应用程序在修改系统层级的资源需要使用到特权,比如修改系统时间和关闭系统。

更多内容请查看如下链接:

Windows Integrity Mechanism Design

Designing Applications to Run at a Low Integrity Level

Understanding and Working in Protected Mode Internet Explorer

Browsing the Web and Reading E-mail Safely as an Administrator(DropMyRight.exe的实现)

Windows下如何创建低权限进程的更多相关文章

  1. [笔记]linux下和windows下的 创建线程函数

    linux下和windows下的 创建线程函数 #ifdef __GNUC__ //Linux #include <pthread.h> #define CreateThreadEx(ti ...

  2. 在Windows下如何创建指定的虚拟环境

    前几天给大家分享了如何在默认的情况下创建虚拟环境,没来得及上车的伙伴,可以戳这篇文章:在Windows下如何创建虚拟环境(默认情况下).今天小编给大家分享一下,如何创建的指定的Python环境. 创建 ...

  3. windows service(system权限)创建用户权限进程

    windows编程的人都知道,在其操作系统下,进程被创建,通常被赋予很多属性,其中一项属性就是用户名,及进程所属的权限.打开任务管理器,可查看到. 通常桌面系统explorer的权限是User权限,即 ...

  4. windows下捕获dump之守护进程

    一两个月前为产品写了一个独立的exe,由于产品使用的捕获dump是一个现成的进程外exe,如果以资源的方式集成它容易出现安全警告,由于时间关系没有寻求新的解决方法,还是遵循旧方案,不捕获dump. 最 ...

  5. windows下bat批处理实现守护进程

    本文转自网络,由于找不到原作者,因而无法知道出处.如果有幸让原作者看到,请联系我加上.先转载至此. 最近几天加班加疯掉了,天天晚上没法睡.开发部的一个核心程序总是会自己宕机,然后需要手工去起,而这个服 ...

  6. windows下根据端口号杀死进程

    Windows不像Linux,Unix那样,ps -ef 查出端口和进程号,然后根据进程号直接kill进程. Windows根据端口号杀死进程要分三步: 第一步 根据端口号寻找进程号 C:\>n ...

  7. windows下bat批处理实现守护进程(有日志)

    开发部的一个核心程序总是会自己宕机,然后需要手工去起,而这个服务的安全级别又很高,只有我可以操作,搞得我晚上老没法睡,昨晚实在受不了了,想起以前在hp-ux下写的shell守护进程,这回搞个windo ...

  8. 在Windows下如何创建虚拟环境(默认情况下)

    很多小伙伴平时在使用Python的时候,有的项目需要使用Python2来进行开发,有的项目则是需要Python3来进行开发.当不清楚怎么分开环境的时候,此时两个环境开始打架,彼此傻傻分不清楚.虚拟环境 ...

  9. Windows 下 pycharm 创建Django 项目【用虚拟环境的解释器】

    1.  背景 我在 Windows 下的 pycharm  直接创建 全新 Django  项目 会  pip 和其他报错 ,暂时解决不了,另外后续的多个项目只需要一套python 环境, 所以可以 ...

随机推荐

  1. python中字符串中一些函数的用法

    1..capitalize():字符串的首字母大写: 2..count():字符串中的某个字母的个数: 3..center(50,'-'):对象居中,且左右用'-'补齐: 4..encode():吧字 ...

  2. Java中 Comparator接口 与Comparable 的区别

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt159 comparator接口与Comparable接口的区别 1. Com ...

  3. Java企业微信开发_09_身份验证之移动端网页授权(有完整项目源码)

    注: 源码已上传github: https://github.com/shirayner/WeiXin_QiYe_Demo 一.本节要点 1.1 授权回调域(可信域名) 在开始使用网页授权之前,需要先 ...

  4. Python3 多线程的两种实现方式

    最近学习 Python3 ,希望能掌握多线程的使用,在此做个笔记.同时也希望Python 牛人指点错误.关于线程的概念,前面简单总结了一下 java 的多线程,传送门:java 多线程概念,三种创建多 ...

  5. 八,ESP8266 文件保存数据

    应该是LUA介绍8266的最后一篇,,,,,,下回是直接用SDK,,然后再列个12345.......不过要等一两个星期,先忙完朋友的事情 前面几篇 用AT指令版本的 一,  http://www.c ...

  6. RobotFramework安装完成后怎么在桌面显示ride图标

    安装了RobotFramework后,怎么让桌面上显示带有机器人的图标呢? 一.桌面上创建ride快捷方式 进入到python的安装目录的/Scripts目录下,找到ride.py文件-->右键 ...

  7. 【Beta】 第一次Daily Scrum Meeting

    一.本次会议为第一次meeting会议 二.时间:20::0AM-20:50AM 地点:宿舍楼下 三.会议站立式照片 四.今日任务安排 成员 昨日任务 今日任务 林晓芳   对已完成的功能进行进一步测 ...

  8. 201521123111《Java程序设计》第10周学习总结

    1. 本章学习总结 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容 线程不是程序 线程必须在程序对应的进程中运行 一个进程中可以同时执行多个线程,这些线程可以完成不同的功能. 2. 书面 ...

  9. 201521123057 《Java程序设计》第11周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线程 1.互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问) ...

  10. BlockingQueue<> 队列的作用

    BlockingQueue<> 队列的作用 BlockingQueue 实现主要用于生产者-使用者队列 BlockingQueue 实现主要用于生产者-使用者队列,BlockingQueu ...