热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

静态及动态添加系统调用

静态及动态添加系统调用--Linux通用技术-Linux编程与内核信息,下面是详情阅读。
静态及动态添加系统调用

????????摘之 “Linux1.0核心游记”

A2.系统调用的添加
A2-1静态添加系统调用
所谓的静态静态添加系统调用,是指我们直接通过修改核心的源代码而达到的。只要我们知道Linux下系统调用实现的框架,添加(当然也可以修改)系统调用将会是件非常简单的事情。

该方法的缺点还是有的:

1. 修改好源代码后需要重新编译核心,这是个非常长和容易发生错误的过程。

2. 对于你修改及编译好后所得到的核心,你所做的添加(修改)是静态的,无法在运行时动态改变(所以也就有了下面的动态方法)

A2-1-1讨论Linux系统调用的体系

在Linux的核心中,0x80号中断是所有系统调用的入口(当然你也可以修改,因为我们有源代码吗 :),不过你改了之后只能自己玩玩,要不然人家0x80号中断的程序怎么执行呢?)。但是还是有办法(可能还有其他办法)。办法是在你看了下面的“动态添加系统调用”后就知道,这个就留给读者考虑了。

用0x80中断号功能作为所有的系统调用入口点,是系统编写者定义的(也可以说是Linus定义的)。下面我们看一下其设置的代码(取之2.4核心,我们只看x386)

定义于Arch/i386/kernel/traps.c(很简单,就一个函数调用)

set_system_gate(SYSCALL_VECTOR,&system_call);!设置0x80号中断

SYSCALL_VECTOR默认是0x80(你可以修改)
system_call定义在Arch\i386\kernel\entry.S
set_system_gate定义在Arch/i386/kernel/traps.c,具体的代码分析这里就不做介绍了。大致的功能是把system_call的地址(当然还有其他内容,比如类型值及特权级)设置到IDT(中断描述符表)的第0x80项中(请注意每项是8个字节,在基础有所介绍)。
当用了set_system_gate设置好中断号,并且已经开中断。接下来我们就可以用编程的方式来调用该中断号。调用中断的汇编指令是“int”。









CH3-5/hello.c

!该程序中使用sys_write这个系统调用来输出要打印的字符。
!同时请注意在该程序中我们也用了strlen函数,它是C库中定义的标准函数
!不过,这里我们只需关注代码中的汇编代码即可。

#include

#include

int

main()

{

int value = -1;

char *lpBuffer = "Hello everybody.\n";

unsigned long sys_num = 4;

int iLen = strlen(lpBuffer);

__asm__("int $0x80"
:"=a"(value) //输出值(即printf执行后的返回值)

:"0"((long)(sys_num)), //eax=sys_num=4,sys_write的系统调用号

"b"(1), //参数一:文件描述符(stdout)

"c"(lpBuffer), //参数二:要显示的字符串

"d"(iLen)); //参数三:字符串长度

return value;

}



CH3-5/Makefile

GCC=gcc

OBJS=hello.o



.c.o:

$(GCC) -c $<



all:$(OBJS)
$(GCC) $(OBJS) -o hello

clean:

rm -f *.o core

clobber:clean

rm -f hello

这里的代码编译后,我们便可以执行了。其输出结果就如我们调用标准C库中的printf函数一样。请看下图



我们更关心的是系统调用的实现机制。下面请跟我来看吧。

1. __asm__("int $0x80"
2. :"=a"(value) //输出值(即sys_write执行后的返回值)

3. :"0"((long)(sys_num)), //eax=sys_num=4,sys_write的系统调用号

4. "b"(1), //参数一:文件描述符(stdout)

5. "c"(lpBuffer), //参数二:要显示的字符串

6. "d"(iLen)); //参数三:字符串长度

第1句代码用于执行0x80号中断。当程序执行到这句时,CPU会从用户态切换进核心态(也就是我们通常说的ring0级),并且同时会把ss,esp,eflags,cs,eip按顺序入栈。

从第2句到第6句代码,用于把系统调用号及参数一到到参数三设置到对应的寄存器中。eax=sys_num(系统调用号)

ebx=1(参数一,标准输出)

ecx=lpBuffer的值(参数二,要显示的字符串)

edx=iLen(参数三,字符串长度)

接下来执行system_call函数(所以我们也说该函数是所有系统调用的入口点函数)。于是我们接着看system_call函数。

ENTRY(system_call)

1. pushl %eax # save orig_eax

2. SAVE_ALL

3. ……

4. cmpl $(NR_syscalls),%eax

5. jae badsys

6. call *SYMBOL_NAME(sys_call_table)(,%eax,4)

7. ……

8. restore_all:

9. RESTORE_ALL

第1句代码首先把eax值入栈,我们可以知道该eax中保存的就是我们从用户态传入的系统调用号(从代码的注释也可以看出 :))。

第2句代码是个宏定义,定义如下:

#define SAVE_ALL \
cld; \
pushl %es; \
pushl %ds; \
pushl %eax; \
pushl %ebp; \
pushl %edi; \
pushl %esi; \
pushl %edx; \
pushl %ecx; \
pushl %ebx; \
movl $(__KERNEL_DS),%edx; \
movl %edx,%ds; \
movl %edx,%es;
从宏的代码中,可以得知它把CPU中的一些寄存器值入栈(为了从核心态返回时还能回到进入前(用户态)的状态),同时还把核心态的数据段值写入ds,es(这样的话,我们就可以访问核心的态的数据段了,代码段在执行int指令时已经由CPU自动设置了)。

第4,5句代码是在测试我们传入的系统调用号是否超过了当前系统所支持的最大系统调用数(对于2.4核心,支持的最大系统调用数是260个,当然你可以修改)。

第6句代码用传入的系统调用号,查表获得对应的系统调用函数地址并call之。该表定义如下(定义在Arch/i386/kernel/entry.S):

.data

ENTRY(sys_call_table)

.long SYMBOL_NAME(sys_ni_syscall) /* 0 - old "setup()" system call*/

.long SYMBOL_NAME(sys_exit)

.long SYMBOL_NAME(sys_fork)

.long SYMBOL_NAME(sys_read)

.long SYMBOL_NAME(sys_write)

.long SYMBOL_NAME(sys_open) /* 5 */

……

我们给出的例子程序调用号是4,所以根据上表我们可以知道对应的系统调用函数是sys_write。这样我们就进入了真正的系统调用处理函数了。(关于sys_write实现这里不做介绍)

从第7句向后的代码,便是系统调用执行完成后的善后处理工作。这里我们给出上面的SAVE_ALL宏的相反操作。即RESTORE_ALL,代码如下:

#define RESTORE_ALL \
popl %ebx; \
popl %ecx; \
popl %edx; \
popl %esi; \
popl %edi; \
popl %ebp; \
popl %eax; \
1: popl %ds; \
2: popl %es; \
addl $4,%esp; \
3: iret; \
.section .fixup,"ax"; \
4: movl $0,(%esp); \
jmp 1b; \
5: movl $0,(%esp); \
jmp 2b; \
6: pushl %ss; \
popl %ds; \
pushl %ss; \
popl %es; \
pushl $11; \
call do_exit; \
.previous; \
.section __ex_table,"a";\
.align 4; \
.long 1b,4b; \
.long 2b,5b; \
.long 3b,6b; \
.previous
从宏的代码中,可以得知它把CPU中的一些寄存器值出栈。并且执行iret指令返回到用户态。到这为止,系统调用便执行完成了,即实现了我们所需要的功能。最后我们用一张简单的图来描述之。请看下图(该图为了描述方便,没有把情况都描述清楚,比如系统调用号超过系统定义的最大系统调用数等等):


A2-1-2修改代码来添加系统调用

通过上面的介绍,我们可以知道修改系统调用并不是件难事。那么我们就开始修改吧。(假定你的核心在/usr/src/linux下)

第1步:

我们打开include/linux/sys.h文件,修改

#define NR_syscalls 260



#define NR_syscalls 261(假设我们只要添加一个系统调用)

第2步:

个人认为这一步,也可以不做,因为作为系统调用的添加者,你当然是知道你加的系统调用号的。不过我们还是不忽略它。请打开include/asm-i386/unistd.h添加如下代码

#define __NR_helloworld 259

这个名字,你可以自己决定用什么,只要不和系统冲突。

第3步:

打开arch/i386/kernel/entry.S,在 .long SYMBOL_NAME(sys_set_tid_address)的后面加入你要添加的系统调用函数名。假设我们要添加的函数名是sys_helloworld,于是我们写成这样:

.long SYMBOL_NAME(sys_helloworld)

第4步:

在第3步我们只是添加了系统调用的声明,还要添加系统调用的实现体才行(关于系统调用实现体的添加,有两个方法:第一个方法是写在系统核心的某个文件中,第二个方法是在核心中新添加一个文件,不过用该方法你需要修改对应的Makefile文件。这里我们采用第一个方法。)。

对于本例,我们把实现体写在fs/read_write.c中。添加代码如下:

asmlinkage void sys_helloworld (void)

{

printk(“Hello world.\n”);

}

第4步:

编译核心。

第5步:

核心编译成功后,我们便可以编写代码测试了。这里我们就修改上面的CH3-5/hello.c代码就可以的。Makefile不变。

修改的代码如下:

CH3-5/hello.c

#include

#include

int

main()

{

unsigned long sys_num = 259;

__asm__("int $0x80"

::"a"((long)(sys_num))); //系统调用号

return 0;

}

修改好后,我们就可以编译并执行了。执行情况如下:


到这为止,静态添加系统调用便完成了。



A2-2动态添加系统调用
所谓动态添加系统调用,就是在Linux运行的时候把新的系统调用加入。从而避免了编译核心的问题。

A2-2-1动态添加系统调用的原理

动态添加系统可能会有很多种方法,这里我们只讨论一种方法。个人认为本书讨论的这种方法是比较好的。同样你也要具有超级用户的权限。

通过上面的对1.0的代码的分析,我们可以知道0x80中断号是整个系统调用的入口点。进入该入口点后,通过比较传入各种系统调用号来查表得到对应的系统调用处理函数。所以这里我们要是能够取得中断描述符表的基地址,然后以此为起点计算出0x80项的地址值。在从该地址处中分解出系统调用的入口点,然后用我们自己定义的入口点替代它。在我们定义的入口点函数中处理我们要处理的系统调用号(随便你干什么)。处理完后在扔给被我们替代掉的入口点函数,即可!下面还是用图来表示,我怕写的不明白!


通过上图我们可以知道,idt表是个什么样子,并且其中每项的内容。那么谁来描述idt表呢?即谁来定位idt表呢?在X86 CPU上有两条指令来和其相关,它们是“sidt”和“lidt”,分别用来复制、加载idt。请注意这两条指令的操作数均为48位。我们可以定义如下结构来描述之:

struct idt_48 {

unsigned short limit; !描述idt表的大小

unsigned long base; !idt表的基地址,看到了吧这就是基地址了

};

所以,我们可以用sidt指令来获取系统idt表的48位值,然后从中获取基地址,再根据取得的基地址计算得到第0x80项中的值,取得第0x80项的值后,我们便可以根据上图中定义的结构分解来得到系统调用的入口点函数地址。

啊!等等!如果我把这个地址修改了,不就实现了动态添加、修改系统调用了吗?

恭喜你!你说对了,下面我们可以看代码了。

A2-2-2实现动态添加、修改系统调用

CH3-6/capturemod.c

#ifndef MODULE

#define MODULE

#endif



#ifndef __KERNEL__

#define __KERNEL__

#endif



#ifndef NULL

#define NULL 0L

#endif



#include

#include

#include

#include
MODULE_LICENSE("GPL");

//禁止警告提示,加上这句表明你的模块符合GPL
//请查看模块的编写那节有描述



void new80_handle();

//新的0x80处理句柄



static unsigned long old80_handle;

//用于保存老的0x80处理句柄



extern char * getname(const char * filename);

//取的用户空间的程序名

extern kmem_cache_t *names_cachep;

//用于释放内存



static unsigned long eax, ebx,ecx;

//保存寄存器内容



struct descriptor_idt

{

unsigned short offset_low;

unsigned short ignore1;

unsigned short ignore2;

unsigned short offset_high;

};

//用于描述idt表中的一项,共8个字节



static struct {

unsigned short limit;

unsigned long base;

}__attribute__ ((packed)) idt48;

//用于定位idt表



static void puppet_handle(void)

{

//首先一点要明确的是,我们不是替代系统中所有系统
//调用,而只是在原来的基础上修改或者新增系统调用.
//对原来的没有被你修改的系统调用不能有任何的影响.
//所以要保证堆栈的正确.





//看到这个函数名(puppet_handle),你可能会感到这是个伪函数。
//没错,确实是的,该函数没有什么大的作用,只是用来包裹下面的
//一段嵌入汇编.代码.
//因为在*.c文件中,你不能直接写汇编代码,
//所以我们只有将其写成嵌入汇编的形式了.



//现在我们讨论一下为什么要写用嵌入汇编.



//我们先回忆一下发生系统调用时的堆栈的情况
//系统调用发生时,CPU会按顺序将SS,ESP,EFLAGS,CS,EIP
//这几个寄存器压入堆栈.,然后会调用” system_call”函数(也就
//是系统调用的总入口点函数),在该函数中会按顺序压入如下
//寄存器值ORIG_EAX,ES,DS,EAX,EBP,EDI,ESI,EDX,ECX,EBX(这
//里压入的寄存器值其实就是系统调用函数将会用的各个参数).通过这里
//的描述,我们可以知道如果” new80_handle”,用C语言来写的是不可能
//完成的.因为你要在调用” new80_handle”前把原来在system_call中压入的
//寄存器内容先压入堆栈,并且还要修改从new80_handle返回地址为原系统的
//总的入口点函数.可是系统的核心原来已经是编译好的了,总不能让你去修改
//编译好的二进制文件吧.



//所以,我们用汇编代码绕过去,并且也不需要知道核心的入栈顺序了.从而保证

//了在进入老的系统调用总的入口点前堆栈是正确的.

//而在该汇编代码中调用另一个辅助的函数,
//在该辅助的函数中完成要做的所有工作

//这里还有特别的一点说明是,为了不污染正在运行核心的函数命名空间
//(因为模块被安装后,其全局变量或者函数会到处),所以,所有的函数和变量

//被我们用了static来做修饰.也许读者对void new80_handle();这个函数有疑问

//因为我们没有用static来做修饰啊!难道它不会污染命名空间吗,会的.

//不过,我们在写嵌入汇编代码时,用了".type new80_handle,@function\n"

//做了修饰,这样的话new80_handle就如static类型的函数一样了



//不要担心,在下面我们会讨论这个用C语言编写的模块的
//反汇编代码的.你会更加明白的.



__asm__ (

".type new80_handle,@function\n" //用于修饰函数,以让其和用static修饰的一样
".align 4\n" //内存对齐方式
"new80_handle: \n" //new80_handle入口点
"pusha \n" //所有通用寄存器入栈

"pushl %%es\n" //段寄存器fs入栈

"pushl %%ds\n" //段寄存器ds入栈

"movl %%eax,%0\n"

"movl %%ebx,%1\n"

"movl %%ecx,%2\n" //取出我们要用的寄存器值

"call real_handler \n" //调用real_handle,正如其名.在该函数中
//可以完成我们想做的任何事情

"popl %%ds\n" //段寄存器ds出栈

"popl %%es\n" //段寄存器fs出栈

"popa \n" //所有通用寄存器出栈

"jmp *old80_handle" //我们的工作完成后,调用系统起先的
//系统调用入口点,一来可以让它处理
//我们没有做的事情(比如从核心态返回到
//用户态).二来如果不是我们添加的系统调用
//它还能继续处理

::"m"(eax),"m"(ebx),"m"(ecx)

);

}



static void real_handler()

{

char *pName = NULL; //指向拷贝到核心中程序名

if(eax == __NR_execve)

{//捕获sys_execve调用
pName = getname((char*)ebx); //getname用于把用户态的数据拷贝到
//核心态,ebx中保存了sys_execve系统调用
//的第一个参数,也就是所要执行文件的文件名
//的地址

if(pName)

{

printk(KERN_INFO"The program is %s.\n",pName);

//打印提示信息

kmem_cache_free(names_cachep, (void *)( pName));

//释放在getname中所分配的内存

}

}

else if(eax == 0x200)

{ //截获0x200号系统调用,该系统调用不存在,用我给的程序测试!
//假设这里只打印eax,ebx,ecx,当然你也可以做其他事情!

printk(KERN_INFO"eax=0x%x, ebx=0x%x, ecx=0x%x\n",eax,ebx,ecx);

}

}



int init_module(void)

{

__asm__ volatile ("sidt %0": "=m" (idt48));

//取得idt表的48位值

struct descriptor_idt *pIdt80 = (struct descriptor_idt *)(idt48.base + 8*0x80);

//并让pIdt80指向idt表中第0x80项

old80_handle = (pIdt80->offset_high<<16 | pIdt80->offset_low);

//保存老的总入口点

unsigned long new80_addr = (unsigned long)new80_handle;

//把新的0x80入口点函数地址转换成unsigned long,为了便于下面可以分解

pIdt80->offset_low = (unsigned short) (new80_addr & 0x0000FFFF);

//把新入口点的低16位设置到0x80项中对应处

pIdt80->offset_high = (unsigned short) (new80_addr >> 16);

//把新入口点的高16位设置到0x80项中对应处



//另外要注意的是,我们没有改变ignore1和ignore2的内容.如果你很想修改这两个字
//段的内容,请确认你知道在做什么,这里不讨论.



printk(KERN_INFO"Ok,we capture syscall successful.\n");

//打印提示信息

return 0;

}



void cleanup_module()

{

__asm__ volatile ("sidt %0": "=m" (idt48));

//取得idt表的48位值

struct descriptor_idt *pIdt80 = (struct descriptor_idt *)(idt48.base + 8*0x80);

//并让pIdt80指向idt表中第0x80项

pIdt80->offset_low = (unsigned short) (old80_handle & 0x0000FFFF);

//恢复老的入口点的低16位

pIdt80->offset_high = (unsigned short) (old80_handle >> 16);

//恢复老的入口点的高16位

printk(KERN_INFO"Ok,we leave capture.\n");

//打印提示信息

}



CH3-6/Makefile

GCC=gcc

KERNELDIR=/usr/src/linux/include

OBJS=capturemod.o

TESTMOD=testmod/testmod



.c.o:

$(GCC) -D__KERNEL__ -I$(KERNELDIR) -c $^ -o $@



all:$(OBJS) $(TESTMOD)



$(TESTMOD):
make -C testmod

insert:$(OBJS)
/sbin/insmod $(OBJS)
remove:
/sbin/rmmod helloworld

clean:
rm -f *.o core
rm -f testmod/*.o
clobber:clean
rm -f $(TESTMOD)



CH3-6/ testmod/ testmod.c(用于测试上面的模块程序)

#include

#include

int

main()

{

int sys_num = 0x200;//512号系统调用(不存在,用来测试上面的模块而已)

long value = 0;

__asm__("int $0x80"

:"=a"(value)

:"0"((int)sys_num));

printf("The value is %d.\n",value);

return value;

}



CH3-6/testmod/ Makefile

GCC=gcc

OBJS=testmod.o



.c.o:
$(GCC) -c $<

all:$(OBJS)

$(GCC) $(OBJS) -o testmod



clean:

rm -f *.o core

clobber:clean

rm -f testmod



下面请看编译和执行情况,这里直给出截图,正所谓一图千言吗!

下图是编译的情况


下图是我们插入模块时的情况


下图是测试程序执行的情况,请读者仔细看屏幕的提示,我不做解释了


下图是模块注销的情况




A2-2-3 反汇编capturemod.o并分析之

本节来做capturemod.o的反汇编代码的分析,这样我们能够更好的理解上面的C语言所完成的代码.通过objdump命令可以反汇编上面的capturemod.o模块.

请在命令行下输入如下命令”objdump ?D capturemod.o > mod.s”打开后便是下面的内容.请跟着我看吧



capturemod.o: file format elf32-i386

//识别出该模块格式是elf

Disassembly of section .text:

//代码段的反汇编

00000000 :

0: 55 push %ebp

1: 89 e5 mov %esp,%ebp

3: 90 nop

//上面的3句代码,是puppet_handle反汇编代码,什么也不做,并且没有任何地方调用它
00000004 : //new80_handle函数的反汇编代码
4: 60 pusha //所有通常寄存器值内容入栈

5: 06 push %es //段寄存器es 入栈

6: 1e push %ds //段寄存器ds 入栈

7: a3 04 00 00 00 mov %eax,0x4

c: 89 1d 08 00 00 00 mov %ebx,0x8

12: 89 0d 0c 00 00 00 mov %ecx,0xc //把eax,ebx,ecx放入0x4,0x8,0xc处(看到
//这个样子大家可能会有点奇怪,为什么
//地址这么小呢?原因在于在该模块被
//插入核心时,才会被重定位)

18: e8 0b 00 00 00 call 28 //调用我们真正的处理程序

1d: 1f pop %ds

1e: 07 pop %es

1f: 61 popa //把上面入栈的内容退回到对应的寄
//存器中,这样我们就保证了堆栈的
//正确行,让真正的系统调用处理
//函数感觉不到我们已经做过了
//我们需要做的事
20: ff 25 00 00 00 00 jmp *0x0 //直接跳转到老的系统调用入口处

26: c9 leave //下面这两句代码是不可能被执行的

27: c3 ret //因为跳到老的系统调用入口点时用的

//是jmp指令



00000028 : //真正完成工作的函数,这里就不做
//分析了,因为它会随着你的不同实现
//而不同

28: 55 push %ebp

29: 89 e5 mov %esp,%ebp

2b: 83 ec 08 sub $0x8,%esp

2e: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp)

35: 83 3d 04 00 00 00 0b cmpl $0xb,0x4

3c: 75 43 jne 81

3e: 83 ec 0c sub $0xc,%esp

41: ff 35 08 00 00 00 pushl 0x8

47: e8 fc ff ff ff call 48

4c: 83 c4 10 add $0x10,%esp

4f: 89 45 fc mov %eax,0xfffffffc(%ebp)

52: 83 7d fc 00 cmpl $0x0,0xfffffffc(%ebp)

56: 74 54 je ac

58: 83 ec 08 sub $0x8,%esp

5b: ff 75 fc pushl 0xfffffffc(%ebp)

5e: 68 00 00 00 00 push $0x0

63: e8 fc ff ff ff call 64

68: 83 c4 10 add $0x10,%esp

6b: 83 ec 08 sub $0x8,%esp

6e: ff 75 fc pushl 0xfffffffc(%ebp)

71: ff 35 00 00 00 00 pushl 0x0

77: e8 fc ff ff ff call 78

7c: 83 c4 10 add $0x10,%esp

7f: eb 2b jmp ac

81: 81 3d 04 00 00 00 00 cmpl $0x200,0x4

88: 02 00 00

8b: 75 1f jne ac

8d: ff 35 0c 00 00 00 pushl 0xc

93: ff 35 08 00 00 00 pushl 0x8

99: ff 35 04 00 00 00 pushl 0x4

9f: 68 20 00 00 00 push $0x20

a4: e8 fc ff ff ff call a5

a9: 83 c4 10 add $0x10,%esp

ac: c9 leave

ad: c3 ret



000000ae : //模块插入时执行的函数

ae: 55 push %ebp

af: 89 e5 mov %esp,%ebp

b1: 83 ec 08 sub $0x8,%esp
//在栈上留下空间,该空间是为pIdt80和new80_addr
//留的
b4: 0f 01 0d 10 00 00 00 sidtl 0x10 //取得idt的48位指针
bb: a1 12 00 00 00 mov 0x12,%eax //把后32位值送入eax,也就是idt表
//的基址
c0: 05 00 04 00 00 add $0x400,%eax //取得第0x80项后,放入eax中

c5: 89 45 fc mov %eax,0xfffffffc(%ebp)

c8: 8b 45 fc mov 0xfffffffc(%ebp),%eax

cb: 0f b7 40 06 movzwl 0x6(%eax),%eax //取出第0x80项的第6个字节开始

//的两个字节(高两个字节)

cf: 89 c2 mov %eax,%edx//把取出的字节放入edx

d1: c1 e2 10 shl $0x10,%edx //左移16位

d4: 8b 45 fc mov 0xfffffffc(%ebp),%eax

d7: 0f b7 00 movzwl (%eax),%eax //低16位
da: 09 d0 or %edx,%eax//相或后便是老的系统入口点函数
dc: a3 00 00 00 00 mov %eax,0x0 //保存起来

e1: c7 45 f8 04 00 00 00 movl $0x4,0xfffffff8(%ebp)

e8: 8b 55 fc mov 0xfffffffc(%ebp),%edx

eb: 8b 45 f8 mov 0xfffffff8(%ebp),%eax

ee: 66 89 02 mov %ax,(%edx)//取的低16位,刚好放在
//(edx)的低16位,也就是第
//0-1两个字节中

f1: 8b 55 fc mov 0xfffffffc(%ebp),%edx

f4: 8b 45 f8 mov 0xfffffff8(%ebp),%eax

f7: c1 e8 10 shr $0x10,%eax //高16位

fa: 66 89 42 06 mov %ax,0x6(%edx)//从高6个字节处开始放
//到此时,也就意味着
//修改了老的入口点了

fe: 83 ec 0c sub $0xc,%esp

101: 68 60 00 00 00 push $0x60

106: e8 fc ff ff ff call 107 //调用printk函数

10b: 83 c4 10 add $0x10,%esp

10e: b8 00 00 00 00 mov $0x0,%eax

113: c9 &
推荐阅读
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 本文介绍了在Hibernate配置lazy=false时无法加载数据的问题,通过采用OpenSessionInView模式和修改数据库服务器版本解决了该问题。详细描述了问题的出现和解决过程,包括运行环境和数据库的配置信息。 ... [详细]
  • 树莓派Linux基础(一):查看文件系统的命令行操作
    本文介绍了在树莓派上通过SSH服务使用命令行查看文件系统的操作,包括cd命令用于变更目录、pwd命令用于显示当前目录位置、ls命令用于显示文件和目录列表。详细讲解了这些命令的使用方法和注意事项。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • Python语法上的区别及注意事项
    本文介绍了Python2x和Python3x在语法上的区别,包括print语句的变化、除法运算结果的不同、raw_input函数的替代、class写法的变化等。同时还介绍了Python脚本的解释程序的指定方法,以及在不同版本的Python中如何执行脚本。对于想要学习Python的人来说,本文提供了一些注意事项和技巧。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • Linux磁盘的分区、格式化的观察和操作步骤
    本文介绍了如何观察Linux磁盘的分区状态,使用lsblk命令列出系统上的所有磁盘列表,并解释了列表中各个字段的含义。同时,还介绍了使用parted命令列出磁盘的分区表类型和分区信息的方法。在进行磁盘分区操作时,根据分区表类型选择使用fdisk或gdisk命令,并提供了具体的分区步骤。通过本文,读者可以了解到Linux磁盘分区和格式化的基本知识和操作步骤。 ... [详细]
author-avatar
我木良心c
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有