百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

一些绕过 A V 进行UserAdd的方法总结及实现

liebian365 2024-10-20 09:59 19 浏览 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
安全客 - 有思想的安全新媒体

相关推荐

C++零基础入门学习指南(中篇)

目标:像拼装乐高一样理解程序模块,掌握内存管理核心技能...

“5 分钟 CMake 使用指南,解决我的 C++ 打包问题!”

...

Linux下跨语言调用C++实践

不同的开发语言适合不同的领域,例如Python适合做数据分析,C++适合做系统的底层开发,假如它们需要用到相同功能的基础组件,组件使用多种语言分别开发的话,不仅增加了开发和维护成本,而且不能确保多种语...

输入格式控制:C++程序中的数据接收与处理技巧

在C++编程中,输入输出是非常基本且重要的操作。尤其是输入部分,程序员通常需要从用户那里获取数据,并根据不同的输入格式进行处理。然而,用户的输入往往是多样化的,如何有效地控制输入格式,确保程序正确接收...

常见读写excel文件的库/类

在C++语言中读写EXCEL表格,有这几种方法:COM方式、ODBC方式、OLE方式、纯底层格式分析方式。Basicexcel使用方法:https://www.cnblogs.com/paullam/...

C++文档识别接口如何实现 高效办公

  数字化信息爆炸时代,办公效率的提升成为企业和个人的迫切需求。人工智能技术的飞速发展,为我们带来了前所未有的便利,翔云文档识别接口便是其中之一。  与传统的人工手动录入相比,文档识别接口优势显著。人...

C++如何生成Microsoft Word文档

...

超实用C++学习指南:语法要点、经典书籍、实战案例全汇总!

以下是为您整理的C++学习指南,综合了语法要点、资源推荐及实战方向,结合搜索结果和经典知识体系,帮助您系统学习:一、C++基础语法学习指南1.核心概念oC++是静态类型、编译式语言,支持面向对象和...

掌握C++文件读写,让代码更灵动!

文章改写指令通常涉及对原有文本进行调整、重组或重新表达,以保持或增强信息的准确性和可读性,同时可能改变风格、语气或目标受众。以下是一些具体的文章改写指令示例:·2.简化语言:→指令:将文章中的复杂词汇...

闲置宽带能换钱?P2P CDN、无线宝、赚钱宝到底靠不靠谱

无线宝类产品其实由来已久,无线宝类产品即与支付宝、余额宝、余利宝等货币基金毫无干系,与区块链“挖币”更存在本质的不同,而是一种利用家庭中的闲置宽带,通过流量来换取佣金的产品。无线宝类产品其实在过去几年...

攻略什么?闲置宽带还可以赚钱?

现在很多朋友在使用10Mbps、50Mbps甚至100Mbps的高速宽带,不过普通用户并不是长时间都需要这么高速的宽带。比如对于100Mbps的宽带用户,在日常浏览网页时,基本上2Mbps左右的带宽即...

明日学业水平考试开始报名 详细步骤都在这里

点击上面蓝字关注我们哦~日前,山东省教育考试院发布了《山东省2019年夏季学业水平考试报名考生操作说明》(点击文末阅读原文查看),明天就到了报名的时候了,详细的报名步骤、网上缴费流程、追加报考科目等...

瞄准用户上传带宽:HiWiFi 极路由 联合 迅雷 推出 “极赚钱”套餐

上次总理谈到宽带降价问题时,很多网友除了吐槽网速慢费用贵,还反映宽带网络的上下行速度不对等。比如说以前ADSL2M的宽带只有512Kbps的上行速度,现在升级到光纤网络之后,按理说技术上实现上下行...

揭秘P2P平台刷数据:交易额从100万到1200万

(作者:峰岭、刘珺、周娜)从默默无闻到万众瞩目,从“零数据”到“大数据”,从小众投资到大众理财,从个人借贷到企业借款,从个人信用到车、房、资产抵押……近两年来P2P行业以迅雷之速快速爆发,P2P平台也...

运营商让我签这个宽带违规使用告知函,我懵逼了

特么的是爱奇艺迅雷自己上传的p2p数据,btpt也会上传,直播也会上传,监控也会,传文件也会,到底他么的运营商你要干个啥啊,我不仅没捞着一分好处,夹在中间两头受气!真特么晦气这特么是谁弄的函?完全没搞...

取消回复欢迎 发表评论: