一些绕过 A V 进行UserAdd的方法总结及实现
liebian365 2024-10-20 09:59 20 浏览 0 评论
一些绕过 A V 进行UserAdd的方法总结及实现
相关项目:https://github.com/crisprss/BypassUserAdd
0x01 前置了解
我们知道,一般我们想要进行添加用户等操作时,基本运行的是:
net user username password /add
net localgroup administrators username /add
其实一般杀软只会检测前面添加用户的命令,而后面的命令并不会触发杀软的报警行为,其实在这里net是在C:\Windows\System32下的一个可执行程序,并且该目录下还有net1.exe,这两个程序的功能是一模一样的:
不论我们是使用net或者是net1都会被杀软检测,至于是不是对底层API的Hook操作,我们还并不清楚
因此在这里,我们来监测系统使用user add时具体对应的API:
实际上当我们使用net user username password /add时net程序会去调用net1.exe程序,然后使用相同的命令:
当尝试跟进net1.exe来跟踪相关操作时发现应该是使用RPC,并且endpoint是\PIPE\lsarpc来进行其他的操作
到这里就没有进一步跟进下去,其实在这里了解到实际上是通过MS-SAMR协议通过RPC实现的,MS-SAMR的官方IDL文档贴出:https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/1cd138b9-cc1b-4706-b115-49e53189e32e
可以看到其中SamrSetInformationDomain等方法都是其接口方法,这也为后文埋下了一定伏笔,因此使用BypassAV进行AddUser的方法并没有结束
0x02 正文
1.Win API 进行UserAdd
在MSDN搜索添加用户相关资料时就会发现微软官方是提供了标准API进行添加用户的,可以参考https://docs.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netuseradd
NetUserAdd其函数原型如下:
NET_API_STATUS NET_API_FUNCTION NetUserAdd(
[in] LPCWSTR servername,
[in] DWORD level,
[in] LPBYTE buf,
[out] LPDWORD parm_err
);
其中level=1时,指定有关用户帐户的信息。此时BUF参数指向一个 USER_INFO_1结构:
typedef struct _USER_INFO_1 {
LPWSTR usri1_name;
LPWSTR usri1_password;
DWORD usri1_password_age;
DWORD usri1_priv;
LPWSTR usri1_home_dir;
LPWSTR usri1_comment;
DWORD usri1_flags;
LPWSTR usri1_script_path;
} USER_INFO_1, *PUSER_INFO_1, *LPUSER_INFO_1;
这意味着我们本地实现时需要注意设置user_info_1这样一个结构体,在这里使用Golang进行简单的实现,其中核心源码如下:
果然不出意外,杀软应该是对该API进行Hook了,即使通过调用API这一方式也会轻易被杀软检测到相关可疑操作
互联网上一顿搜索发现21年6月份时该杀软就已拦截netapi加用户的方式:
到这里只好硬着头皮去尝试对netapi32.dll进行逆向分析,看其中是否有更加底层的API实现,如果本身NetUserAdd也是封装的话,那我们完全可以实现自己封装从而绕过该API
2.对NetAddUser的底层封装调用
在互联网上搜索看看有没有前人已经进行过相关工作,结果发现确实有前人已经做过相关逆向的工作:
https://idiotc4t.com/redteam-research/netuseradd-ni-xiang
在Win10中的netapi32.dll已经找不到相关添加用户的函数,只有一个NetUserAdd的导出函数,我们尝试逆向XP中的netapi32.dll:
Security Account Manager (SAM) 是运行 Windows 操作系统的计算机上的数据库,该数据库存储本地计算机上用户的用户帐户和安全描述符。
这里对UserAdd的实现也是首先尝试连接SAM数据库,判断SAM中是否已经存在该用户,然后利用RtlInitUnicodeString对新建用户信息等做一个初始化操作,最后调用SamCreateUser2InDomain来创建用户账户,创建成功会继续调用UserpSetInfo设置用户密码,因此实际上NetUserAdd就是被这样几个关键函数进行封装,因此我们需要做的是哪些函数能够直接调用,而哪些函数是还需要自己进一步封装
其中UaspOpenSam没有导出,而实际上对应的是SamConnect:
UaspOpenDomain同样没有导出,实际上对应的也是Sam系的函数:
这里SamOpenDomain的函数原型大致如下:
SamOpenDomain(ServerHandle, DesiredAccess, DomainSid, &DomainHandle)
因此我们是需要DomainSid的,也就是说我们还需要获取账户所在域的SID信息,经过搜索发现可以使用Sam函数获取的,而在ReactOS和mimikatz中就是使用的 LSA 函数进行查询的:
在MSDN中查询该函数(LsaQueryInformationPolicy)发现存在:
函数原型如下:
NTSTATUS LsaQueryInformationPolicy(
[in] LSA_HANDLE PolicyHandle,
[in] POLICY_INFORMATION_CLASS InformationClass,
[out] PVOID *Buffer
);
// in Advapi32.dll
继续跟进,发现UserpSetInfo同样没有导出函数,继续跟进这个函数:
而在React OS的SetUserInfo函数中同样找到该方法的调用:
这里的UserAllInfo对应的就是USER_INFO结构体,而通常情况下我们都是使用USER_INFO_1,并且将值设置为1
因此大致过程已经比较清楚:
- 1.调用SamConnect连接SAM数据库
- 2.通过LsaQueryInformationPolicy获取SID信息后调用SamOpenDomain
- 3.验证完成后调用SamCreateUser2InDomain创建用户信息
- 4.最后通过SamSetInformationUser来设置新建用户的密码
最后成功绕过杀软进行用户添加。
3.Cobalt Strike argue参数欺骗
在CS 3.1版本后引入了argue参数欺骗技术,使得进程在创建时记录的参数与实际运行时不同
windows系统从进程的进程控制块的commandline中读取参数,并对参数做相应的处理,在线程未初始化完成前,我们可以修改这些参数,达到伪装commandline的目的
操作其实就是读取进程中PEB内RTL_USER_PROCESS_PARAMETERS结构体,在该结构体中对CommandLine指针进行修改
typedef struct _RTL_USER_PROCESS_PARAMETERS {
BYTE Reserved1[16];
PVOID Reserved2[10];
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
这里我们使用argue进行参数混淆污染net1程序,然后在通过execute net1 username password /add方式来进行添加用户,笔者反复试验了多次,均为检测到,因此判断通过参数欺骗这种方式还是可以逃过杀软的检测,毕竟杀软对于commandline的检测也是通过读取进程PEB表实现的
4.C#利用命名空间和目录服务 添加用户
这其实是微软文档中自己给出的一种方式,具体可以参考:
https://docs.microsoft.com/zh-cn/troubleshoot/dotnet/csharp/add-user-local-system
注意需要添加对程序集System.DirectoryServices.dll的引用
这种方式通过C#中调用DirectoryServices添加本地用户,同时支持删除用户、添加用户组等实现
这里直接参考官方文档的描述就行,核心代码如下:
DirectoryEntry AD = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
DirectoryEntry addUser = AD.Children.Add(username, "user");
addUser.Invoke("SetPassword", new object[] { password });
addUser.CommitChanges();
DirectoryEntry grp;
grp = AD.Children.Find("Administrators", "group");
if (grp != null) {
grp.Invoke("Add", new object[] { addUser.Path.ToString() });
}
grp = AD.Children.Find("Remote Desktop Users", "group");
if (grp != null) {
grp.Invoke("Add", new object[] { addUser.Path.ToString() });
}
经过检测这种方式同样不会被杀软拦截,并且利用C#还有代码简洁,可以结合CS内存加载运行的特点,我们知道CS 3.1后便支持使用execute-assembly方式来调用NET程序集文件
5.编写反射DLL以及CS插件化实现
在编写的过程中其实已经发现,数字杀软已经对反射注入的行为特征进行检测,这里或许可以对反射DLL的一些属性进行修改,看到一些思路说是先不使用RWE属性的内存页,先使用RW属性执行DLL加载,加载完成后再将代码段改为RE可以绕过,在这里没有进行相关修改
在反射DLL编写中实现了2种方式,通过Win API创建用户和重构NetUserAdd,实现底层创建用户,当使用反射注入方式时2种方法都会被检测到
而前文已经提到,重构NetUserAdd实现底层创建,即通过SamSetInformationUser创建是不会被杀软拦截的,因此插件的实现只是包含反射注入的功能作为参考(或许对其他EDR比较好使),结合其他两种方式实现绕过杀软进行用户添加的功能
基于上述原理和方法,实现了一个简单的Bypass添加用户的插件,主要实现的有利用反射注入实现API添加用户和底层实现API添加用户,以及内存加载NET程序集实现C#活动目录添加用户和底层实现API的可执行程序上传再执行
实现效果如图:
但令我不解的是前一天利用C#编写的添加用户还能够成功绕过该杀软,第二天将上述这些能够绕过的封装到反射DLL或者封装到插件里面去之后,杀软都检测到了…这更新速度未必也太快了?
而且连前一天的argue参数欺骗都被检测了,中间间隔不到24h时间,在这里不得惊叹速度之快(可以看截图信息里的时间)
因此我也比较无奈,刚写好插件就发现没办法Bypass杀软了,只好作为实现原理和姿势分析,还有一个思路就是直接调用RPC接口方法,不过这种方式其实本质和重新封装NetUserAdd是一样的,具体能不能绕过我没有实现了…
发现@loong716师傅之前分享过利用ms-samr RPC实现过更改用户密码,可以参考:
https://github.com/loong716/CPPPractice/tree/master/ChangeNTLM_SAMR
这里应该在此基础上进行修改就能够实现创建用户,稍微留个坑等之后再来重写下
相关参考文章:
https://idiotc4t.com/redteam-research/netuseradd-ni-xiang
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/1cd138b9-cc1b-4706-b115-49e53189e32e
https://loong716.top/posts/Set_ChangeNTLM_SAMR/#2-%E7%9B%B4%E6%8E%A5%E8%B0%83%E7%94%A8ms-samr
本文由Crispr原创发布
转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/264890
安全客 - 有思想的安全新媒体
相关推荐
- 看黑客是如何获取你电脑最高权限的,一定要看
-
在渗透过程中,通过各种方式获取到一枚cmdshell,但是这个shell的权限比较低,无法让我们做我们想要做的一些操作,比如说获取系统密码,获取数据库信息,又或者比如说拿到服务器中的另一个站点的权限,...
- 是50个常用的Visual Basic代码示例:
-
以下是50个常用的VisualBasic代码示例:1.声明变量```vb...
- 电脑系统型号怎么看版本(如何看电脑系统型号)
-
有时候我们会需要进行查看电脑上安装的windows系统版本及系统版本号,但对于不懂电脑知识的小白来说要怎么查看电脑系统版本信息呢?别着急,有小编在接下来,就将查看电脑系统版本的教程来分享给你们,希望对...
- dos命令systeminfo,查看系统启动时间。电脑卡慢,win10怎么了?
-
最近一段时间,有几个反应电脑卡慢的,都是windows10的系统。询问得知每天电脑有关机,打开任务管理器,内存使用量达到百分之九十多,而程序只打开微信、wps、360浏览器。cmd窗口运行命令syst...
- systeminfo命令:全面解析系统信息!
-
你是否曾想过,仅凭一条简单的命令,就能深入了解计算机的"内心世界"?是不是有点不可思议?那么,让我们一起探寻这个神奇的命令,揭开它背后的奥秘吧!它能提供的信息超乎你的想象,从操作系统到硬件配置,再到驱...
- 电脑序列号怎么查询?只需两行命令一键查询
-
当我们的电脑出问题需要保修的时候,需要查询到电脑的型号和序列号才更便于进行下一步的操作,有包装盒的朋友还可以在包装盒上查询,笔记本用户可以在电脑底部标签上查询,没有包装盒和标签破损的用户就无从下手了。...
- 快速显示系统信息:Systeminfo命令详解
-
Systeminfo命令是windows系统中显示系统信息的命令,此命令可以显示出计算机的操作系统的详细配置信息,包括操作系统配置、安全信息、产品ID和硬件属性(如RAM、磁盘空间和网卡)。使用...
- dos命令systeminfo图文教程,显示操作系统配置信息msinfo32
-
大家好,我是老盖,首先感谢观看本文,本篇文章做的有视频,视频讲述的比较详细,也可以看我发布的视频。今天我们学习systeminfo命令,该工具显示本地或远程机器(包括服务包级别)的操作系统配置的信息,...
- 基于uniapp+vue3跨端仿制chatgpt实例uniapp-chatgpt
-
#夏日生活打卡季#...
- 原创新作uniapp+vue3+pinia2高仿微信App聊天
-
前段时间有给大家分享一个flutter3.x桌面端os系统。今天再分享一款最新原创之作uniapp-vue3-wechat聊天实例。uni-vue3-wechat采用...
- UniApp开发的设备适配(uniapp服务器配置)
-
UniApp是一个跨平台开发框架,支持多端应用(如H5、小程序、iOS、Android等)。由于不同设备的屏幕尺寸、分辨率、操作系统等存在差异,设备适配是开发过程中需要重点关注的问题。以下是Uni...
- 如何用服务器搭建自己的个人网站(自己服务器怎么做网站)
-
这篇教程主要是告诉大家如何利用TCP和HTTP协议来完成网站的搭建。首先你需要有C/C++语言基础,且有服务器、客户端概念,如果你了解TCP或者HTTP协议的话,那么将会帮助你更快的学会如何搭建个人网...
- 大话C语言:字符数组(c语言字符数组教学视频)
-
1字符数组概述C语言中没有字符串这种数据类型,可以通过char的数组来替代。数字0(和字符'\0'等价)结尾的char数组就是一个字符串,字符串是一种特殊的char的数组。...
- 源码分享:在pdf上加盖电子签章(pdf怎么加电子签章)
-
在pdf上加盖电子签章,并不是只是加个印章图片,。而是要使用一对密钥中的私钥对文件进行签字。为啥要用私钥呢?很简单,因为公钥是公开的,其他人才可以用公钥为你证明,这个文件是你签的。这就是我们常说的:私...
- C语言wcstombs函数详解:宽字符字符串到多字节的「翻译官」
-
核心定位wcstombs是C语言中用于将宽字符字符串转换为多字节字符串的「翻译官」,它能将宽字符(wchar_t)转换为多字节字符(如UTF-8编码的中文)。就像一位翻译官,它能将一种语言(宽字符...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)