渗透测试场景下,基于命令行检测恶意行为是一个常见的防御方案,例如 xxx -h 10.0.0.1/24
就容易被认为是一个恶意的扫描命令。对于这种检测方式,容易想到的是通过修改程序对读取参数的逻辑做修改。但是在渗透测试场景下,通常需要使用各种不同的工具,如代理、网络转发等,逐一修改并跟踪上游软件的更新是一件繁琐的工作。因而本文尝试讨论一种相对通用的参数变形与隐藏方案。
已有方案
最简单的方案是从日志、shell history中隐藏命令,例如alias、脚本可以从历史记录中隐藏命令执行的参数,环境变量如 example -arg $ARG
的方式可以隐藏一些关键的参数。然而,这些方案只能绕过极少部分防御措施,要真正实现参数隐藏,我们需要采取更加深入的方法。
一个入门级的方案是在程序中修改参数。例如,在C语言中,我们可以通过修改argv来隐藏敏感信息。下面这个程序是一个简单的sample,将输入的参数做一次 rot13 变换之后再处理。这种方案下,可以绕过大部分基于参数的检测方案。
1 |
|
执行程序的一个示例输入输出如下
1 | $ ./a.out --name grfg |
然而,这种方法并不一定完全通用,因为程序可能在初始化之后仍然依赖这些参数,如果初始化之后再次通过 argv 来获取相关的参数值,这种方法就可能会影响程序的正常运行,
在有高权限的情况下,可以通过内核机制来实现参数隐藏,例如可以使用基于内核hook get_cmdline等函数、隐藏进程结构体等方式来实现参数隐藏,也可以通过eBPF等机制hook对应的处理逻辑,相对简单的来实现参数的隐藏。
Golang方案
前文提到了一些已有的方案,但是C语言修改源码方案可能因为后续调用而没有足够的稳定性。使用rootkit等方案又会引入额外的复杂度和渗透痕迹。
那么,是否存在一种相对简单、稳定、没有权限要求的方案来实现参数隐藏呢?
初看之下没有很合适的方案,而在探索过程中,本文发现许多常用的渗透工具,如gost、frp和fscan,都是使用Golang实现的。
因此,本文尝试先缩小问题范围,考虑是否有合适的方案来实现面向Golang应用程序的参数隐藏方案。
首先,让我们来看看Golang的参数实现。从源码中不难发现,无论是 cobra 等框架,还是使用flag等Golang自带的实现,它们都是基于os.Args[1:]来获取参数。
那么,os.Args是如何赋值的呢?在 src/os/proc 中可以看到 Args = runtime_args()
。其中 runtime_args 实际是返回了 argslice 的数组。
最后在汇编代码中可以看到 runtime 相关的参数实际是从 argv 中拷贝出来的。
1 | MOVW 8(RSP), R0 // copy argc |
也就是说,由于 os.Args
是复制的,直接修改 Golang 程序的 os.Args
是不会影响程序的原本的 argv 参数,即修改这个变量不会导致 ps aux 等工具的输出被修改,也不会有coredump等问题,全程所有的引用也都是面向 os.Args
,不会有不一致的问题。
基于这个原理,可以提出一个隐藏方案,下面是一个示例代码:
1 | // cmdline.go |
在这个示例程序中,通过从环境变量中读取参数实现了隐藏真实参数的效果,执行以下命令的结果如下:
1 | $ export ARG='-name real_passed_name' |
为了更加通用,我们可以将上述方案封装成一个库。只需要导入该库,即可在其他程序中实现参数隐藏,无需重新编译。对于上文的程序,可以用这种方式实现同样的效果。
1 | package main |
再返回来考虑一下原理,不难想到,由于高级语言都有自己的字符串结构而不是简单的一段内存,所以这种隐藏方式同样适用于其他脚本语言和高级语言,只需要每种语言实现一种对应的 hook 方能即可。
通用方案
上文提到的方式对单个语言有效,但是仍然需要重新编译。那么是否有不需要重新编译的方案呢?回顾上文提到的方案,其实也是有的。我们可以通过实现一个C语言 wrapper 的方式来实现上文同样的效果。具体来说,实现逻辑如下:
- C程序通过execve启动子程序,子进程通过
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
即可设置父进程不需要 root /CAP_SYS_PTRACE
权限可以 ptrace 到子进程。 - 父进程通过解析子进程对应的可执行程序,获取 main 函数地址
- 父进程通过 ptrace 在子程序 main 函数处下断点
- 执行到子进程 main 函数时,父进程通过 PTRACE_POKEDATA 将 argv 修改为环境变量中的参数
- 执行一段时间后,父进程再次修改子进程为其它参数
总结
综上所述,本文讨论了参数隐藏的技巧与实现方法。通过这些方案,我们可以在渗透测试等场景中实现参数隐藏和变形,提高工具的隐蔽性。