免责声明:
本公众号致力于安全研究和红队攻防技术分享等内容,本文中所有涉及的内容均不针对任何厂商或个人,同时由于传播、利用本公众号所发布的技术或工具造成的任何直接或者间接的后果及损失,均由使用者本人承担。请遵守中华人民共和国相关法律法规,切勿利用本公众号发布的技术或工具从事违法犯罪活动。最后,文中提及的图文若无意间导致了侵权问题,请在公众号后台私信联系作者,进行删除操作。
经常参加攻防的同学在使用自己开发的工具时都存在下面的问题。
1、自己辛苦开发的工具遗忘在目标服务器导致工具被窃取或外传。
2、工具使用完成后,一般攻防组织方都会要求参与人员将恶意工具从目标服务器上删除,也就是擦屁股。
3、工具留在服务器上后,容易被应急人员发现,导致经验缺乏的应急人员也能发现服务器被攻陷,从而丢失权限。
本文要介绍的是Golang开发的程序,如何做到执行后自删除的操作。
众所周知,windows软件设计之初为了保证程序的安全性,当一个可执行程序运行的时候会处于一种被占用的状态,如果尝试删除程序,会显示程序被占用,一般需要结束掉程序后才能删掉。
自删除利用了NTFS文件特性达到的程序运行时解除文件锁定,最终删除自身的效果。
其中主要原理如下:
-
微软虽然阻止直接删除自己,但是并不阻止重命名自己,我们可以轻松的按下F2修改我们运行的程序名称;
-
文件锁和重命名挂钩,文件锁和着你重命名的文件走,假设我们重命名为Alternate Data Stream类型的文件,文件锁便会锁定我们重命名的文件;
-
要知道所有的Alternate Data Stream都有主的文件,主文件现在没有被使用意味着主文件是可以被删除的;虽然无法直接在系统上进行重命名带有 : 的文件名,但是微软提供的Windows API却不影响我们对本身进行重命名;
-
通过删除自己的主文件,根据微软的系统特性,备选的数据流也会被系统自动删除,即便是有文件锁也会被强制释放;
-
程序走到Main方法,PE文件已经加载到内存里面了,因此删除自己不影响程序的正常运行。
要实现自删除,主要需要使用下面的windows接口(https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileinformationbyhandle)
BOOL SetFileInformationByHandle(
[in] HANDLE hFile,
[in] FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
[in] LPVOID lpFileInformation,
[in] DWORD dwBufferSize
);
由于笔者开发的工具主要是用Golang,所以本文主要介绍Golang程序的自删除方法。
第一步:使用windows.CreateFile
函数以DELETE
权限打开当前可执行文件,获取文件句柄
handle, err := windows.CreateFile(
pwPath,
windows.DELETE,
0,
nil,
windows.OPEN_EXISTING,
windows.FILE_ATTRIBUTE_NORMAL,
0,
)
第二步:创建一个包含:deadExe标记的FILE_RENAME_INFO结构体,使用windows.SetFileInformationByHandle函数将文件重命名为:deadExe。通过调用RtlCopyMemory函数,将:deadExe字符串复制到FILE_RENAME_INFO结构体中。
DS_STREAM_RENAME, err := windows.UTF16FromString(":deadExe")
if err != nil {
return err
}
lpwStream := &DS_STREAM_RENAME[0]
fRename.FileNameLength = uint32(unsafe.Sizeof(lpwStream))
// 将":deadExe"复制到文件重命名信息结构体中
windows.NewLazyDLL("kernel32.dll").NewProc("RtlCopyMemory").Call(
uintptr(unsafe.Pointer(&fRename.FileName[0])),
uintptr(unsafe.Pointer(lpwStream)),
unsafe.Sizeof(lpwStream),
)
// 调用API执行文件重命名
err = windows.SetFileInformationByHandle(
hHandle,
windows.FileRenameInfo,
(*byte)(unsafe.Pointer(&fRename)),
uint32(unsafe.Sizeof(fRename) + unsafe.Sizeof(lpwStream)),
)
第三步:使用windows.CreateFile函数以DELETE权限重新打开当前可执行文件,获取文件句柄。创建一个FILE_DISPOSITION_INFO结构体,将其中的DeleteFile字段设置为true,表示文件可以被删除。使用windows.SetFileInformationByHandle函数将文件标记为可删除。
var fDelete FILE_DISPOSITION_INFO
fDelete.DeleteFile = true
// 调用API执行文件删除标记
err := windows.SetFileInformationByHandle(
hHandle,
windows.FileDispositionInfo,
(*byte)(unsafe.Pointer(&fDelete)),
uint32(unsafe.Sizeof(fDelete)),
)
测试效果
在linux系统中就比较友好,没有那么多弯弯绕,两行代码就可以解决了
#获取可执行文件路径
fileName, _ := os.Executable()
#删除文件
syscall.Unlink(fileName)
Unlink会将文件的目录项从文件系统中移除。如果文件的硬链接计数为零,或者没有其他进程持有该文件的打开文件描述符,那么文件的磁盘空间就会被释放,实现文件删除的效果。
测试效果就不发了,有兴趣的同学随便写个Demo测试即可。
文末还是提一下笔者在测试过程中碰到的一个小问题供大家参考,上述介绍了Win和Linux下自删除的方式,但是在Golang跨平台编译时存在一些问题,由于在windows下引入的某些package,在linux编译时存在链接错误,所以需要Golang的条件编译,Golang不支持C语言中ifdef形式的宏定义条件编译,但是支持另外一种方式,例如:
testA包只有在windows平台编译时调用(编译条件的注释和package 语句之间一定要隔一行)
// +build windows
package testA // 注意 编译条件的注释和package 语句之间一定要隔一行。不然无法识别编译条件
testB包只有在linux平台编译时调用
// +build linux
// 编译条件支持“非逻辑” +build linux 可以用 +build !windows 替换
package testB
也支持多个编译标签
// +build linux darwin
// +build 386
所以上述条件编译就对本文中提到的自删除函数提供了跨平台编译的便利性。
本文介绍的自删除的应用场景较多,同时也可以设置自删除逻辑,比如程序执行时输入密码,输错N次后自删除,用于程序保护,木马执行后自删除等等,更多用途由师傅们开发吧,同时不要把这种技术用于违法途径。
参考 :https://xz.aliyun.com/t/13045#toc-0
原文始发于微信公众号(Lambda小队):RedTeam工具隐蔽技术(一)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论