免责声明:
本公众号致力于安全研究和红队攻防技术分享等内容,本文中所有涉及的内容均不针对任何厂商或个人,同时由于传播、利用本公众号所发布的技术或工具造成的任何直接或者间接的后果及损失,均由使用者本人承担。请遵守中华人民共和国相关法律法规,切勿利用本公众号发布的技术或工具从事违法犯罪活动。最后,文中提及的图文若无意间导致了侵权问题,请在公众号后台私信联系作者,进行删除操作。
上期提到的是工具隐蔽技术中的自删除,其实主要是为了保护工具不外传,同事不留痕。但是其中还有一些问题。
1、自己打的点,然后其他队伍也打进来了,对方在ps查看进程的时候,一眼就看到了某工具挂着明显的ip或者口令在运行,典型的比如frp代理,非常明显,frp -c /xxx/frp.ini,这样瞬间就暴露了该主机已经被入侵的特征,而且对于溯源人员来说可以快速定位文件位置,同时还泄露了很多敏感信息。
本文要介绍的是Golang开发的程序,如何做到程序进程执行后不显示任何命令行参数。
其实对于隐藏命令行参数在C语言中是非常简单的,因为C语言本身就是可以直接操作内存的底层语言。直接通过main函数传入的argc和argv即可对参数进行遍历和修改。
int main(int argc, char *argv[]) {
// argc 表示传递给程序的命令行参数的数量
// argv 是一个指针数组,其中每个元素是一个指向传递给程序的参数的指针
int i, j;
for (i = 1; i < argc; i++) {
// 内层循环遍历当前参数字符串的每个字符
for (j = strlen(argv[i]) - 1; j >= 0; j--) {
// 将当前字符修改为 'x'
argv[i][j] = 'x';
}
}
getchar();
return 0;
}
测试效果如下
type commandSlice []uint16
// next 函数将命令行字符串 cmd 分割为下一个参数和剩余的命令行部分
func (cmd commandSlice) next(is2erase bool) commandSlice {
var inquote bool // 是否在引号内
var nslash int // 反斜杠的数量
var erasenum int // 要擦除的参数的数量
for ; len(cmd) > 0; cmd = cmd[1:] {
switch cmd[0] {
case uint16(' '), uint16('t'): // 如果遇到空格或制表符
if !inquote {
return cmd
}
case uint16('"'):
if nslash%2 == 0 { // 没有转义的情况下
if inquote && len(cmd) > 1 && cmd[1] == uint16('"') {
cmd = cmd[1:]
}
inquote = !inquote // 切换引号状态
}
nslash = 0 // 重置反斜杠数量
continue
case uint16('\'): // 如果遇到反斜杠
nslash++
fallthrough // 继续执行 default 分支的逻辑
default:
if is2erase { // 如果需要擦除参数
erasenum++ // 增加擦除参数的数量
if erasenum > 3 { // 如果擦除参数的数量超过3个
cmd[0] = uint16(' ') // 将当前参数替换为空格
} else {
cmd[0] = uint16('*') // 否则将当前参数替换为星号
}
}
}
nslash = 0
}
return cmd
}
// erase 函数擦除命令行切片中指定位置的参数
func (cmd commandSlice) erase(pos uint) {
var p uint
for len(cmd) > 0 && p <= pos {
if cmd[0] == uint16(' ') || cmd[0] == uint16('t') {
cmd = cmd[1:] // 跳过空格或制表符
continue
}
cmd = cmd.next(p == pos) // 调用 next 函数,如果当前位置是要擦除的位置,则擦除该参数
p++
}
}
// utf16PtrToCommandSlice 函数将UTF-16指针转换为命令行切片
func utf16PtrToCommandSlice(p *uint16) commandSlice {
if p == nil {
return nil
}
// 寻找NUL终止符
end := unsafe.Pointer(p)
start := end
n := uintptr(0)
for *(*uint16)(end) != 0 {
end = unsafe.Pointer(uintptr(end) + unsafe.Sizeof(*p))
n++
}
return (commandSlice)(uint16Slice(start, n))
}
// Hide 函数隐藏指定位置的参数
func Hide(position int) {
if position < 0 || position >= len(os.Args) {
panic("无效的参数位置" + strconv.Itoa(position))
}
utf16PtrToCommandSlice(syscall.GetCommandLine()).erase(uint(position))
hideOSArgA(position)
}
// HideAll 函数隐藏所有参数
func HideAll() {
for i := 1; i < len(os.Args); i++ {
utf16PtrToCommandSlice(syscall.GetCommandLine()).erase(uint(i))
hideOSArg(i)
}
}
直接运行进行测试
对运行进程进行查询,如下图,发现参数已经被替代并隐藏
wmic process get caption,commandline /value | findstr zaku
对于Linux环境来说,总体复杂度总是没有windows高,这大概率归咎于windows的系统限制更多。
type slice struct {
data unsafe.Pointer
len uintptr
cap uintptr
}
func stringToBytes(s string) (b []byte) {
bh := (*slice)(unsafe.Pointer(&b))
sh := (*slice)(unsafe.Pointer(&s))
bh.data = sh.data
bh.len = sh.len
bh.cap = sh.len
return b
}
func uint16Slice(ptr unsafe.Pointer, n uintptr) (s []uint16) {
hdr := (*slice)(unsafe.Pointer(&s))
hdr.data = ptr
hdr.cap = n
hdr.len = n
return
}
func hideOSArg(position int) {
if position < 0 || position >= len(os.Args) {
panic("invalid gohideparam position" + strconv.Itoa(position))
}
if len(os.Args[position]) == 0 {
return
}
argp := stringToBytes(os.Args[position])
for i := 0; i < len(os.Args[position]); i++ {
argp[i] = ' '
}
}
func hideAllArg() {
for i := 1; i < len(os.Args); i++ {
hideOSArg(i)
}
return
}
直接运行程序进行测试
如下图,通过ps命令查询该进行没有任何参数显示,隐藏成功。
0x04 关于Golang编译
本文介绍的自程序运行参数隐藏并非唯一的方式,还有多种隐藏运行参数的方式,但是可能导致程序运行不稳定或者崩溃的情况,笔者将继续探索后,将来分享更多隐藏的稳定方法,同时下期将带来新的程序隐蔽技术,敬请关注。
原文始发于微信公众号(Lambda小队):RedTeam工具隐蔽技术(二)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论