当前位置:文档之家› 天书夜读(反汇编)

天书夜读(反汇编)

天书夜读(反汇编)
天书夜读(反汇编)

天书夜读

(试读版)

汇编语言是一门本来是很基础很古老的语言,由于它的代码可读性可移植性较差,现在已经很少有人用。但它的优点也是显而易见的,很高的效率,不受编译器限制的随意性,对硬件的直接操作(保护模式下需要系统支持),以及逆向工程时不可或缺的反汇编调试等。随着你越来越深入的了解计算机,你会越来越觉得这个古老的东西是最重要的,是那些时髦的编程语言不可比拟的。

我们每天使用的Windows内核部分,几乎完全用C语言开发。只可惜MS并不公开源代码。

虽然如此,却没有谁能阻止你看汇编的代码。MS对Windows既没有加壳加密,也没有花指令,洋洋洒洒上千万行代码,数千精英程序员的智慧结晶,如今就在你的电脑内。工作结束,夜晚无聊之时,难道不想读一读,这深不可测的浩瀚天书吗?

以前曾经有人把Windows的dll反汇编后改写为C语言的资料。后来又有WindowsNT和2000的源代码泄漏。不过到如今vista 都已经发布了。想要自己随心所欲的阅读,还是要自己掌握Windows程序的汇编写法吧。获人之鱼,不如师人之渔。

本文知识初浅,并不是能用于破解,反工程,或者编写病毒等的高级技术。仅供读者业余聊以自娱之用.这本书的文字部分主要由楚狂人编写。部分代码和技术细节由wowocock提供.

现在本版本为完整的试读版本,包括完整的1-3节。今后如果需要新的章节,将按读者的意见,加入到正式版本中。

楚狂人,wowocock

2007年1月于上海

目录

第一节入手:基本C反汇编 (3)

1-1. 函数与调用栈 (3)

1-2.循环 (5)

1-3.判断与分支 (7)

1-4.数组与结构 (11)

1-5.共用体,枚举类型 (12)

1-6.算法的反汇编 (13)

1-7.发行版的反汇编 (15)

1-8.汇编反C练习 (18)

第二节演习:内核代码阅读 (21)

2-1.认识内核代码,新的函数调用方式 (21)

2-2.尝试反C内核代码 (23)

2-3.寻找需要的信息 (25)

2-4.了解内核调用的位置 (27)

2-5.自己实现XP的新调用,新的函数调用方式 (29)

2-6.没有符号表的反汇编 (31)

第三节实战:反汇编引擎, HOOK系统调用 (31)

3-1 反汇编引擎XDE32之熟悉指令 (31)

3-2 反汇编引擎XDE32之具体实现 (34)

3-3 XP下HOOK系统调用IoCallDriver (37)

3-4 Vista下IofCallDriver的跟踪 (39)

3-5 Vista下实现Hook IofCallDriver (41)

3-6 总结与展望 (44)

第一节入手:基本C反汇编

1-1. 函数与调用栈

函数和堆栈的关系密切,这是因为:我们通过堆栈把参数从外部传入到内部。此外,我们在堆栈中划分区域来容纳函数的内部变量。

调用push和pop指令的时候,寄存器esp用于指向栈顶的位置。栈顶总是栈中地址最小的位置。push执行的结果,esp总是减少。pop则增加。

C语言所写的程序中,堆栈用于传递函数参数。这时称为调用栈。

写一个简单的函数如下:

void myfunction(int a,int b)

{

int c = a+b;

}

这是标准的C函数调用方式.其过程是:

1)调用方把参数反序的压入堆栈中。

2)调用函数。

3)调用方把堆栈复原。

而被调用函数需要做以下一些事情:

1)保存ebp. ebp总是被我们用来保存这个函数执行之前的esp的值。执行完毕之后,我们用ebp恢复esp.同时,调用我们的上

层函数也用ebp做同样的事情。所以我们之前先把ebp压入堆栈。返回之前弹出,避免ebp被我们改动。

2)保存esp到ebp中。

上面两步的代码如下:

push ebp ;保存ebp,并把esp放入ebp中

;此时ebp与esp同。

;都是这次函数调用时的栈顶。

mov

ebp,esp

3)在堆栈中腾出一个区域用来保存局部变量。这就是常说的所谓局部变量在栈空间中。方法为把esp减少一个数值。这样等于压

入了一堆变量。日后要恢复时,只要把esp恢复成ebp中保存的数据就可以了.

4)保存ebx,esi,edi到堆栈中。函数调用完后恢复。这是一个编程规范。

对应的代码如下:

;把esp往上移动一个范围,等于在堆栈中放出一片新

sub

esp,0cch

;的空间用来存局部变量.

push

ebx ;下面保存三个寄存器:ebx,esi,edi,这也是C规范.

esi

push

push edi

5)把局部变量区域初始化成0cccccccch。0cccccccch实际是INT 3.这是一个中断指令。因为局部变量不可能被执行,如果执行

了必然程序有错,这时发生中断来提示开发者。这是VC编译DEBUG版本的特有操作。

相关代码如下:

;本来是要mov edi,ebp-0cch,但是mov不支持-操作。所

edi,[ebp-0cch]

lea

;以对ebp-0cch取内容,而lea把内容的地址也就是ebp

;-0cch加载到edi中.目的是把保存局部变量的区域(从

;ebp-0cch开始的区域)初始化成全部0cccccccch.

mov ecx,33h

mov eax,0cccccccch

rep stos dword ptr [edi] ;拷贝字符串

6)然后做函数里应该做的事情。参数的获取是ebp+8字节为第一个参数,ebp+12为第二个参数,依次增加。ebp+4字节处是

要返回的地址。

7)恢复ebx,esi,edi ,esp,ebp,最后返回。

代码如下:

pop edi ;恢复edi,esi,ebx

pop esi

pop ebx

;恢复原来的ebp和esp,让上一个调用的函数正常使用.

esp,ebp

mov

pop ebp

ret

为了简单起见,我的函数没有返回值。如果要返回值,函数应该在返回之前,把返回值放入eax中。外部通过eax得到返回值。

所以用VC2003编译Debug版本,反汇编代码如下:

void myfunction(int a,int b)

{

ebp ;保存ebp,并把esp放入ebp中。此时ebp与esp同。

push

mov ebp,esp ;都是这次函数调用时的栈顶。

;把esp往上移动一个范围,等于在堆栈中放出一片新

sub

esp,0cch

;的空间用来存局部变量.

ebx ;下面保存三个寄存器:ebx,esi,edi,这也是C规范.

push

esi

push

push edi

;本来是要mov edi,ebp-0cch,但是mov不支持-操作。所

edi,[ebp-0cch]

lea

;以对ebp-0cch取内容,而lea把内容的地址也就是ebp

;-0cch加载到edi中.目的是把保存局部变量的区域(从

;ebp-0cch开始的区域)初始化成全部0cccccccch.

mov ecx,33h

mov eax,0cccccccch

rep stos dword ptr [edi] ;拷贝字符串

a+b;

int c=

mov eax,dword ptr [a] ;简单的相加操作.这里从堆栈中取得从外部传入的参数。那么

add eax,dword ptr[b] ;a和b到底是怎么取得的呢,通过ida反汇编可以看到,其实

;这两条指令是mov eax, [ebp+8] ,add eax, [ebp+0Ch],参数是

;通过ebp从堆栈中取得的。这里看到的是VC调试器的显示结

;果,为了阅读方便直接加上了参数名。

mov dword ptr[c],eax

}

pop edi ;恢复edi,esi,ebx

pop esi

pop ebx

;恢复原来的ebp和esp,让上一个调用的函数正常使用.

esp,ebp

mov

pop ebp

ret

而这个函数的调用方式是:

mov eax,dword ptr[b] ; 把b,a两个参数压入堆栈

push eax

mov ecx,dword ptr[a]

push ecx

call myfunction ; 调用函数myfunction.

add esp,8 ; 恢复堆栈.

这样一来,函数调用的过程就很清楚了。

1-2.循环

下面我把函数改得复杂一点,增加了一个循环,来看反汇编的结果:

int myfunction(int a,int b)

{

int c = a+b;

int i;

for(i=0;i<50;i++)

{

c = c+i;

}

return c;

}

前面的反汇编结果和前一节的一样了,现在从for的地方开始反汇编,结果如下:

for(i=0;i<50;i++)

00412BC7 mov dword ptr [i],0

00412BCE jmp myfunction+39h (412BD9h)

00412BD0 mov eax,dword ptr [i] 00412BD3 add eax,1

00412BD6 mov dword ptr [i],eax 00412BD9 cmp dword ptr [i],32h

00412BDD jge myfunction+4Ah (412BEAh) {

c = c+i;

00412BDF mov eax,dword ptr [c] 00412BE2 add eax,dword ptr [i] 00412BE5 mov dword ptr [c],eax }

00412BE8 jmp myfunction+30h (412BD0h) 00412BEA mov eax,dword ptr [c] }

… 后面省略的部分也和上一节相同。

可以看到循环主要用这么几条指令来实现:mov 进行初始化。jmp 跳过循环变量改变代码。cmp 实现条件判断,jge 根据条件跳转。用jmp 回到循环改变代码进行下一次循环。所以结构.大体如下:

mov <循环变量>,<初始值> ;给循环变量赋初值 jmp B

;跳到第一次循环处 A: (改动循环变量)

;修改循环变量。

B: cmp <循环变量>,<限制变量> ;检查循环条件 jgp 跳出循环 (循环体) …

jmp A

;跳回去修改循环变量

我们再看一下do 循环,因为do 循环没有修改循环变量的部分,所以比for 循环要简单一些。 do {

c =

c+i;

00411A55 mov eax,dword ptr [c] 00411A58 add eax,dword ptr [i] 00411A5B mov dword ptr [c],eax } while(c< 100);

00411A5E cmp dword ptr [c],64h

00411A62 jl myfunction+35h (411A55h) return c; …

do 循环就是一个简单的条件跳转回去。只有两条指令: cmp <循环变量>,<限制变量> jl <循环开始点>

下面看看while 的情况:

while(c<100){

00411A55 cmp dword ptr [c],64h

00411A59 jge myfunction+46h (411A66h) c = c+i;

00411A5B mov eax,dword ptr [c] 00411A5E add eax,dword ptr [i] 00411A61 mov dword ptr [c],eax

}

00411A64 jmp myfunction+35h (411A55h) return c;

你会发现while 要更复杂一点。因为while 除了开始的时候判断循环条件之外,后面还必须有一条无条件跳转回到循环开始的地方,共用三条指令实现:

A: cmp <循环变量>,<限制变量> jge B

(循环体)

… jmp A B: (循环结束了)

1-3.判断与分支

写一个简单的if 判断结构: if(c>0 && c<10) {

printf(“c>0”);

}

else if( c>10 && c<100) {

printf(“c>10 && c<100”);

}

else {

printf(“c>10 && c<100”);

}

if 判断都是使用cmp 再加上条件跳转指令。对于if( A && B)的情况,一般都是使用否决法。如果A 不成立,立刻跳下一个分支。依次,如果B 不成立,同样跳下一分支。:

cmp <条件> jle <下一个分支>

所以开始部分的反汇编为:

if(c>0 && c<10)

00411A66 cmp dword ptr [c],0

00411A6A jle myfunction+61h (411A81h) ; 跳下一个else if的判断点

00411A6C cmp dword ptr [c],0Ah

00411A70 jge myfunction+61h (411A81h) ; 跳下一个else if的判断点

{

printf(“c>0”);

00411A72 push offset string “c>0” (4240DCh)

00411A77 call @ILT+1300(_printf) (411519h)

00411A7C add esp,4

}

else if的和else的特点是,开始都有一条无条件跳转到判断结束处,阻止前面的分支执行结束后,直接进入这个分支。这个分支能执行到的唯一途径只是,前面的判断条件不满足。

else则在jmp之后直接执行操作。而else if则开始重复if之后的操作,用cmp比较,然后用条件跳转指令进行跳转.

else if( c>10 && c<100)

00411A7F jmp myfunction+89h (411AA9h) ;直接跳到判断块外

00411A81 cmp dword ptr [c],0Ah ;比较+条件跳转,目标为下一个分支处

00411A85 jle myfunction+7Ch (411A9Ch)

00411A87 cmp dword ptr [c],64h

00411A8B jge myfunction+7Ch (411A9Ch)

{

printf(“c>10 && c<100”);

00411A8D push offset string “c>10 && c<100” (424288h)

00411A92 call @ILT+1300(_printf) (411519h)

00411A97 add esp,4

}

else

00411A9A jmp myfunction+89h (411AA9h) ;这里是else,所以只有简单的一条跳转。

{

printf(“c>10 && c<100”);

00411A9C push offset string “c>10 && c<100” (424288h)

00411AA1 call @ILT+1300(_printf) (411519h)

00411AA6 add esp,4

}

return c;

条件分支中,有比较特殊的情况是switch.switch的特点是有多个判断。因为swtich显然不用判断大于小于,所以都是je,分别跳到每个case处。最后一个是无条件跳转,直接跳到default处。以下的代码:

switch(c)

{

case 0:

printf(“c>0”);

case 1:

{

printf(“c>10 && c<100”);

break;

}

default:

printf(“c>10 && c<100”);

}

反汇编的结果是:

switch(c)

00411A66 mov eax,dword ptr [c]

00411A69 mov dword ptr [ebp-0E8h],eax

00411A6F cmp dword ptr [ebp-0E8h],0

00411A76 je myfunction+63h (411A83h)

00411A78 cmp dword ptr [ebp-0E8h],1

00411A7F je myfunction+70h (411A90h)

00411A81 jmp myfunction+7Fh (411A9Fh)

{

显然是比较c是否是0,1,这两个数字。至于先把c移动到ebp-0E8h这个地址,然后再比较,这是调试版本编译的特点。可能是为了防止直接操作堆栈而导致堆栈破坏?最后一条直接跳转到default处。如果没有default,就跳到swtich之外。

case 0:

printf(“c>0”);

00411A83 push offset string “c>0” (4240DCh)

00411A88 call @ILT+1300(_printf) (411519h)

00411A8D add esp,4

case 1:

{

printf(“c>10 && c<100”);

00411A90 push offset string “c>10 && c<100” (424288h)

00411A95 call @ILT+1300(_printf) (411519h)

00411A9A add esp,4

break;

00411A9D jmp myfunction+8Ch (411AACh)

}

default:

printf(“c>10 && c<100”);

00411A9F push offset string “c>10 && c<100” (424288h)

00411AA4 call @ILT+1300(_printf) (411519h)

00411AA9 add esp,4

}

至于case和default都非常简单。如果有break,则会增加一个无条件跳转。没有break的情况下,没有任何循环控制代码。

下面是一个练习,内容是把下面的汇编代码还原成c语言。这种练习非常有用处。

00411A20 push ebp

00411A21 mov ebp,esp

00411A23 sub esp,0E8h

00411A29 push ebx

00411A2A push esi

00411A2B push edi

00411A2C lea edi,[ebp-0E8h]

00411A32 mov ecx,3Ah

00411A37 mov eax,0CCCCCCCCh

00411A3C rep stos dword ptr [edi]

00411A3E mov eax,dword ptr [a]

00411A41 add eax,dword ptr [b]

00411A44 mov dword ptr [d],eax

00411A47 mov dword ptr [i],1

00411A4E mov dword ptr [c],0

00411A55 cmp dword ptr [c],64h

00411A59 jge myfunction+46h (411A66h)

00411A5B mov eax,dword ptr [c]

00411A5E add eax,dword ptr [i]

00411A61 mov dword ptr [c],eax

00411A64 jmp myfunction+35h (411A55h)

00411A66 mov eax,dword ptr [c]

00411A69 mov dword ptr [ebp-0E8h],eax

00411A6F cmp dword ptr [ebp-0E8h],0

00411A76 je myfunction+63h (411A83h)

00411A78 cmp dword ptr [ebp-0E8h],1

00411A7F je myfunction+6Ah (411A8Ah)

00411A81 jmp myfunction+72h (411A92h)

00411A83 mov dword ptr [d],1

00411A8A mov eax,dword ptr [c]

00411A8D mov dword ptr [d],eax

00411A90 jmp myfunction+79h (411A99h)

00411A92 mov dword ptr [d],0

00411A99 mov eax,dword ptr [d]

00411A9C pop edi

00411A9D pop esi

00411A9E pop ebx

00411A9F mov esp,ebp

00411AA1 pop ebp

00411AA2 ret

1-4.数组与结构

写一个简单的函数,用到结构体和数组。

typedef struct {

int a;

int b;

int c;

} mystruct;

int myfunction(int a,int b)

{

unsigned char *buf[100];

mystruct *strs = (mystruct *)buf;

int i;

for(i=0;i<5;i++)

{

strs[i].a = 0;

strs[i].b = 1;

strs[i].c = 2;

}

return 0;

}

对结构体和数组的访问是我们感兴趣的地方。相关的反汇编结果是这样的:

int i;

for(i=0;i<5;i++)

0041367A mov dword ptr [i],0 ;典型的for循环过程

00413684 jmp myfunction+45h (413695h)

00413686 mov eax,dword ptr [i]

0041368C add eax,1

0041368F mov dword ptr [i],eax

00413695 cmp dword ptr [i],5

0041369C jge myfunction+94h (4136E4h)

{

strs[i].a = 0;

0041369E mov eax,dword ptr [i] ;目的是把i*0ch放入到eax中。

004136A4 imul eax,eax,0Ch ;0Ch是结构的大小。

004136A7 mov ecx,dword ptr [strs] ;把strs的地址放入ecx 004136AD mov dword ptr [ecx+eax],0 ;计算得到strs[i]的地址,并赋0 strs[i].b = 1;

004136B4 mov eax,dword ptr [i]

004136BA imul eax,eax,0Ch

004136BD mov ecx,dword ptr [strs]

004136C3 mov dword ptr [ecx+eax+4],1 ;这里与.a不同,增加偏移取得b的位置strs[i].c = 2;

004136CB mov eax,dword ptr [i]

004136D1 imul eax,eax,0Ch

004136D4 mov ecx,dword ptr [strs]

004136DA mov dword ptr [ecx+eax+8],2

}

004136E2 jmp myfunction+36h (413686h) ;典型的循环结束

004136E4 xor eax,eax ;eax清0

1-5.共用体,枚举类型

不过你可能会感到失望。因为共用体和枚举类型都是在C语言中为了让内容更加易读而引入的东西。实际上,只要有结构体和基本的数据类型就足够了。所以在汇编中,这些多余的东西都消失不见了。

为了实验一下我写一段有共用体和枚举类型的代码。然后反汇编一下看看效果。

// 定义一个枚举类型

typedef enum {

ENUM_1 = 1,

ENUM_2 = 2,

ENUM_3,

ENUM_4,

} myenum;

// 定义一个结构体

typedef struct {

int a;

int b;

int c;

} mystruct;

typedef union {

mystruct s;

myenum e[3];

} myunion;

int myfunction(int a,int b)

{

unsigned char buf[100] = { 0 };

myunion *uns = (myunion *)buf;

int i;

// 访问共用体,结构体,使用枚举类型的变量

for(i=0;i<5;i++) {

uns[i].s.a = 0; uns[i].s.b = 1;

uns[i].e[2] = ENUM_4;

}

return 0; }

反汇编的结果和上一小节基本没有区别: for(i=0;i<5;i++) 00411A57 mov dword ptr [i],0

00411A5E jmp myfunction+49h (411A69h) 00411A60 mov eax,dword ptr [i] 00411A63 add eax,1

00411A66 mov dword ptr [i],eax 00411A69 cmp dword ptr [i],5

00411A6D jge myfunction+83h (411AA3h) {

uns[i].s.a = 0;

00411A6F mov eax,dword ptr [i]

;基本没有区别。直接去找.a 的地址。

00411A72 imul eax,eax,0Ch

00411A75 mov ecx,dword ptr [uns] 00411A78 mov dword ptr [ecx+eax],0 uns[i].s.b =

1;

00411A7F mov eax,dword ptr [i] 00411A82 imul eax,eax,0Ch

00411A85 mov ecx,dword ptr [uns] 00411A88 mov dword ptr [ecx+eax+4],1

uns[i].e[2] = ENUM_4;

00411A90 mov eax,dword ptr [i] 00411A93 imul eax,eax,0Ch

00411A96 mov ecx,dword ptr [uns] 00411A99 mov dword ptr [ecx+eax+8],4 }

00411AA1 jmp myfunction+40h (411A60h)

汇编里一切花哨的东西都消失不见了。和单纯用结构体没有区别。用枚举类型和一般常数也没有任何区别,这是编译器处理的结果。

1-6.算法的反汇编

C 语言的各种控制流程在反汇编中绝对是最让人舒服的部分。而最痛苦的莫过算法的部分。一个简单的算法就会让复杂到让人眼花缭乱的程度,更不用说复杂的算法了。

显然没有完美的办法来解决这个问题,只能靠你的耐心和智力了。下面举一个3*3矩阵相乘的例子。

int myfunction(int a[3][3],int b[3][3],int c[3][3]) {

int i,j;

for(i=0;i<3;i++) {

for(j=0;j<3;j++)

c[i][j] = a[i][0]*b[0][j]+a[i][1]*b[1][j]+a[i][2]*b[2][j];

}

return 0; }

这个代码很简单易懂,不过汇编的写法真的很富有挑战性: int i,j;

for(i=0;i<3;i++)

00411A3E mov dword ptr [i],0

00411A45 jmp myfunction+30h (411A50h) 00411A47 mov eax,dword ptr [i] 00411A4A add eax,1

00411A4D mov dword ptr [i],eax 00411A50 cmp dword ptr [i],3

00411A54 jge myfunction+0AEh (411ACEh) {

for(j=0;j<3;j++)

00411A56 mov dword ptr [j],0

00411A5D jmp myfunction+48h (411A68h) 00411A5F mov eax,dword ptr [j] 00411A62 add eax,1

00411A65 mov dword ptr [j],eax 00411A68 cmp dword ptr [j],3

00411A6C jge myfunction+0A9h (411AC9h)

c[i][j] =

a[i][0]*b[0][j]+a[i][1]*b[1][j]+a[i][2]*b[2][j]; 00411A6E mov eax,dword ptr [i] ;从这里开始的指令只有mov,add 和 00411A71 imul eax,eax,0Ch

;imul,尝试把它们还原成表达式吧。

00411A74 mov ecx,dword ptr [a] 00411A77 mov edx,dword ptr [j] 00411A7A mov esi,dword ptr [b]

00411A7D mov eax,dword ptr [ecx+eax] 00411A80 imul eax,dword ptr [esi+edx*4] 00411A84 mov ecx,dword ptr [i] 00411A87 imul ecx,ecx,0Ch 00411A8A mov edx,dword ptr [a] 00411A8D mov esi,dword ptr [j] 00411A90 mov edi,dword ptr [b]

00411A93 mov ecx,dword ptr [edx+ecx+4]

00411A97 imul ecx,dword ptr [edi+esi*4+0Ch]

00411A9C add eax,ecx

00411A9E mov edx,dword ptr [i]

00411AA1 imul edx,edx,0Ch

00411AA4 mov ecx,dword ptr [a]

00411AA7 mov esi,dword ptr [j]

00411AAA mov edi,dword ptr [b]

00411AAD mov edx,dword ptr [ecx+edx+8]

00411AB1 imul edx,dword ptr [edi+esi*4+18h]

00411AB6 add eax,edx

00411AB8 mov ecx,dword ptr [i]

00411ABB imul ecx,ecx,0Ch

00411ABE add ecx,dword ptr [c]

00411AC1 mov edx,dword ptr [j]

00411AC4 mov dword ptr [ecx+edx*4],eax

00411AC7 jmp myfunction+3Fh (411A5Fh)

}

00411AC9 jmp myfunction+27h (411A47h)

要阅读这样的代码,首先把流程控制的代码与数值计算的代码分开是关键。因此你前面的练习能起很大的帮助。当你得到值计算的代码的部分后,你必须判断输入与输出(一般自然是被读的内部变量为输入,被写的内部变量为输出),然后把它还原成一个C语言的表达式。任何一段中间不加任何跳转,连续的mov和加减乘除的指令一般都可以还原为一个C表达式。当然,这可不是一个轻松的工作。

在这里顺便可以看到,二维数组a[x][y],处理等同与一个大小为a[y]的结构的长度大小为x的数组。所以,前面讲到的数组访问的代码非常有价值。基本的方法如下:

mov eax,<我要取的数组元素的下标>

imul eax,eax,<结构的大小>

mov ecx,<结构数组开始的地址>

mov eax,dword ptr [ecx+eax] ;取得数组元素的内容,放到eax中.

访问结构内部变量的时候最后面一个指令还会加上一个数字:

ptr[ecx+eax+0Ch]

eax,dword

mov

看到这样的代码,我们应该联想到表达式中含有的数组或结构体。

1-7.发行版的反汇编

前面费了很多工夫解析代码。但是那都是调试版本的代码。使用他们是因为那样汇编代码会更好理解。实际上,到非调试版本的时候,编译器将进行非常多的优化,使汇编代码变得简单的同时,阅读的困难也大大增加了。但是实际上,你碰到的代码显然都是发行版本。

首先是函数调用的过程如下:

0040108D push eax

0040108E push esi

0040108F call myfunction1 (401000h)

00401094 add esp,8

优化之后,两个参数根本没有放入内部变量中,而是放在eax和esi中直接push了。然后调用myfunction1.最后是恢复esp,与调试版本的情况相同。

然后是函数的执行过程,在堆栈中划分内部区域的代码可能被省略。因为内部变量比较少的情况,都直接使用寄存器了。把内部变量初始化为INT 3的代码也被省略。取参数也不会放入内部变量中,而是用esp直接取。同时,ebp根本没被使用。ebx,esi和edi的压入弹出是函数调用和结束最明显的标记。无论调试版本还是发行版本都是如此.

下面是一个循环的简单函数:

int myfunction1(int a,int b)

{

00401000 push ebx ;保存ebx,esi,edi

00401001 push esi

int i;

for(i=0;i<5;i++)

00401002 mov esi,dword ptr [esp+0Ch] ; 取第一个参数.本来第一个参数是esp+4h,但是因为前面有

; 两个push,所以变成esp+Ch.

00401006 push edi

00401007 mov edi,dword ptr [esp+14h] ; 取第二个参数,计算方法同上

0040100B xor ebx,ebx ; 清空ebx,显然没有定义内部变量i,而是直接用了ebx.

0040100D lea ecx,[ecx] ; 无意义的指令。

{

循环的结尾处是这样的:

00401020 inc ebx

00401021 cmp ebx,5

00401024 jl myfunction1+10h (401010h)

可见优化后的for循环喜欢模仿相对简单的do循环的方式。把判断和跳转放在最后。

前面的矩阵相乘的例子如下:请注意这时候c代码和后面的汇编已经没有正确的对应关系了:

int myfunction(int a[3][3],int b[3][3],int c[3][3])

{

int i,j;

for(i=0;i<3;i++)

00401000 mov eax,dword ptr [esp+4] ;a保存到eax中

00401004 mov edx,dword ptr [esp+0Ch] ;c保存到edx中

00401008 mov ecx,dword ptr [esp+8] ;b保存到ecx中。

0040100C push ebx

0040100D push esi

0040100E add eax,4 ;这里已经开始处理数据。必须把这些指令和

;函数入口指令和循环控制指令分开

00401011 push edi

00401012 add edx,8 ;同前面的add eax,4

00401015 mov esi,3 ;循环变量取3

0040101A lea ebx,[ebx] ;无意义指令

{

for(j=0;j<3;j++)

c[i][j] = a[i][0]*b[0][j]+a[i][1]*b[1][j]+a[i][2]*b[2][j];

00401020 mov ebx,dword ptr [eax] ;显然从这里开始,是数值计算过程。代码的优化,使内部00401022 imul ebx,dword ptr [ecx+0Ch] ;循环不见了。可以数一下imul指令,刚好九次。可以说明00401026 mov edi,dword ptr [ecx+18h] ;这里用一个单循环完成了原来双循环的工作。00401029 imul edi,dword ptr [eax+4]

0040102D add edi,ebx

0040102F mov ebx,dword ptr [eax-4]

00401032 imul ebx,dword ptr [ecx]

00401035 add edi,ebx

00401037 mov dword ptr [edx-8],edi

0040103A mov ebx,dword ptr [eax]

0040103C imul ebx,dword ptr [ecx+10h]

00401040 mov edi,dword ptr [ecx+1Ch]

00401043 imul edi,dword ptr [eax+4]

00401047 add edi,ebx

00401049 mov ebx,dword ptr [eax-4]

0040104C imul ebx,dword ptr [ecx+4]

00401050 add edi,ebx

00401052 mov dword ptr [edx-4],edi

00401055 mov ebx,dword ptr [eax+4]

00401058 imul ebx,dword ptr [ecx+20h]

0040105C mov edi,dword ptr [ecx+14h]

0040105F imul edi,dword ptr [eax]

00401062 add edi,ebx

00401064 mov ebx,dword ptr [eax-4]

00401067 imul ebx,dword ptr [ecx+8]

0040106B add edi,ebx

0040106D mov dword ptr [edx],edi

0040106F add eax,0Ch

00401072 add edx,0Ch

00401075 dec esi ;这里开始两条,是循环指令

00401076 jne myfunction+20h (401020h)

00401078 pop edi

00401079 pop esi

}

return 0;

0040107A xor eax,eax

0040107C pop ebx

}

1-8.汇编反C练习

下面是另外一个练习题,这个练习完全是发行版本的一个函数。如果能顺利完成这个练习,那么基本C语法的反汇编结果阅读也就入门了。

我们不公布C语言的源程序,但我们尝试把它还原成C语言。这次用的完全发行版本,经过o2优化的代码。足够接近实战了。下面的汇编语言程序对应一个完整的C函数。

00401000 push ecx F

00401001 mov ecx,dword ptr [esp+10h] D

00401005 mov edx,dword ptr [esp+8] D

00401009 push ebx F

0040100A mov ebx,dword ptr [esp+4] D

0040100E push esi F

0040100F mov esi,dword ptr [esp+14h] D

00401013 push ebp F

00401014 xor eax,eax C

00401016 push edi F

00401017 jmp myfunction+20h (401020h) F

00401019 lea esp,[esp] F

00401020 mov edi,dword ptr [esi+8] D

00401023 imul edi,dword ptr [edx+eax*8+4] D

00401028 mov ebp,dword ptr [esi] D

0040102A imul ebp,dword ptr [edx+eax*8] D

0040102E add edi,ebp D

00401030 mov dword ptr [ecx],edi D

00401032 mov edi,dword ptr [esi+0Ch] D

00401035 imul edi,dword ptr [edx+eax*8+4] D

0040103A mov ebp,dword ptr [esi+4] D

0040103D imul ebp,dword ptr [edx+eax*8] D

00401041 add edi,ebp D

00401043 mov ebp,dword ptr [ecx] D

00401045 add ebp,edi D

00401047 add ebx,ebp D

00401049 mov dword ptr [ecx+4],edi D

0040104C inc eax C

0040104D add ecx,8 D

00401050 cmp eax,2 C

00401053 jl myfunction+20h (401020h) C

00401055 call rand (401138h) C

0040105A add ebx,eax D

0040105C cmp ebx,64h C

0040105F pop edi F

00401060 pop ebp F

00401061 je myfunction+7Bh (40107Bh) C

00401063 cmp ebx,6Eh C

00401066 je myfunction+88h (401088h) C

00401068 push offset string “nothing” (407114h) F

0040106D call printf (401107h) F

00401072 add esp,4 F

00401075 pop esi F

00401076 mov eax,ebx F

00401078 pop ebx F

00401079 pop ecx F

0040107A ret F

0040107B push offset string “cnt is 100” (407108h) F

00401080 call printf (401107h) F

00401085 add esp,4 F

00401088 push offset string “cnt is 110” (4070FCh) F

0040108D call printf (401107h) F

00401092 add esp,4 F

00401095 pop esi F

00401096 mov eax,ebx F

00401098 pop ebx F

00401099 pop ecx F

0040109A ret F

那么,标准的解法大致如下:

第一步是,我们要把指令分成几类,并实际的把它们区分开来,各个击破:

最先是函数调用相关代码。这些代码用于调用函数或者作为一个函数被调用。几乎凡是堆栈操作(备份寄存器或者是压入参数)我们可全部归入之,此外还有call指令,堆栈恢复。这类代码很容易识别。让我们把它们标记为F.称之为F指令。

然后是流程控制代码。涉及判断和跳转指令,以及对循环变量操作的指令。这些对应用于循环,判断语句。比较容易看出。标记为C.我们称之为C指令。

剩余的是数据处理。应该不含有函数调用,多半不含有堆栈操作,也不会含有跳转(跳转已经归入流程控制中)。对于复杂的数据处理代码,只能逐行翻译,然后多行组合。标记为D.之后我们称之为D指令。

标记已经做好了,请回头看上面的代码.当然,你可能出于不同的观点,和我做上不同的标记,或者同样的指令归类不同,这都没有关系。

第二步,我们取出其中标记为D的代码进行逐行翻译。首先标记为F的语句基本不需要翻译,他们本身就是简单的函数调用。其次标记为C的指令我们将很快将他们翻译为if,for,do或者是swtich,工作相对简单。

下面的指令取自上面的前半段的D指令。我删除了其中加杂的F指令。而C指令则是容易翻译的部分,那么我们尝试改写D指令为如下最右边的表达式,其中我用了p1,p2,p3表示函数的参数1,参数2,参数3。取参数时针对esp的偏移,随着push指令的执行而有所变动,切不可机械计算。

00401001 mov ecx,dword ptr [esp+10h] D ecx =p3

00401005 mov edx,dword ptr [esp+8] D edx =p1

0040100A mov ebx,dword ptr [esp+4] D ebx =p1

0040100F mov esi,dword ptr [esp+14h] D esi =p2

00401014 xor eax,eax C

00401020 mov edi,dword ptr [esi+8] D edi =p2[2]

00401023 imul edi,dword ptr [edx+eax*8+4] D edi*=p1[eax*2+1] 00401028 mov ebp,dword ptr [esi] D ebp = p2[0] 0040102A imul ebp,dword ptr [edx+eax*8] D ebp *= p1[eax*2] 0040102E add edi,ebp

D edi += ebp 00401030 mov dword ptr [ecx],edi

D ecx [0] = edi 00401032 mov edi,dword ptr [esi+0Ch] D edi = p2[3] 00401035 imul edi,dword ptr [edx+eax*8+4] D edi = p1[eax*2+1] 0040103A mov ebp,dword ptr [esi+4] D ebp = p2[1] 0040103D imul ebp,dword ptr [edx+eax*8]

D ebp *= p1[eax*2] 00401041 add edi,ebp D edi += ebp 00401043 mov ebp,dword ptr [ecx] D ebp =

ecx [0]

00401045 add ebp,edi D ebp += edi 00401047 add ebx,ebp D ebx += ebp 00401049 mov dword ptr [ecx+4],edi

D ecx [1] = edi 0040104C inc eax C 0040104D add ecx,8 D ecx+=8

00401050 cmp eax,2

C 00401053 jl myfunction+20h (401020h)

C

第三步,自然是表达式的合并与控制流程的结合了,前面D 系列的表达式,把中间过程除去,已经很容易得到表达式如下,其中eax 开始为0,ecx 开始为p3.

ecx [0] = p2[2]*p1[eax*2+1]+p2[0]*p1[eax*2]; ecx [1] = p2[3]*p1[eax*2+1]+p2[1]*p1[eax*2]; 此外jl 明显导致了一个循环,每次循环的更改是: eax++; ecx+=8;

eax 是循环变量。ecx 显然用于取数组位置,且二者保持固定的同步增加,可以用一个循环变量替代之,所以翻译结果如下: for(i=0;i<2;i++) { p3[i] = p2[2]*p1[2*i+1]+p2[0]*p1[2*i]; p3[i+1] = p2[3]*p1[2*i+1]+p2[1]*p1[i*2];

}

好了,以上就是三步的解法。后面的部分,也是依样画葫芦的完成,值得注意的是以下部分: 0040105C cmp ebx,64h C 0040105F pop edi F 00401060 pop ebp

F 00401061 je myfunction+7Bh (40107Bh) C 00401063 cmp ebx,6Eh

C 00401066 je myfunction+88h (401088h) C

除去其中的F 指令,连续看到cmp 和je,说明这是一个swtich.这和调试版本没有什么区别。翻译的难度也非常小。这里就不再重

述详细的过程了。

汇编语言 快速入门

“哎哟,哥们儿,还捣鼓汇编呢?那东西没用,兄弟用VB"钓"一个API就够你忙活个十天半月的,还不一定搞出来。”此君之言倒也不虚,那吾等还有无必要研他一究呢?(废话,当然有啦!要不然你写这篇文章干嘛。)别急,别急,让我把这个中原委慢慢道来:一、所有电脑语言写出的程序运行时在内存中都以机器码方式存储,机器码可以被比较准确的翻译成汇编语言,这是因为汇编语言兼容性最好,故几乎所有跟踪、调试工具(包括WIN95/98下)都是以汇编示人的,如果阁下对CRACK颇感兴趣……;二、汇编直接与硬件打交道,如果你想搞通程序在执行时在电脑中的来龙去脉,也就是搞清电脑每个组成部分究竟在干什么、究竟怎么干?一个真正的硬件发烧友,不懂这些可不行。三、如今玩DOS的多是“高手”,如能像吾一样混入(我不是高手)“高手”内部,不仅可以从“高手”朋友那儿套些黑客级“机密”,还可以自诩“高手”尽情享受强烈的虚荣感--#$%&“醒醒!” 对初学者而言,汇编的许多命令太复杂,往往学习很长时间也写不出一个漂漂亮亮的程序,以致妨碍了我们学习汇编的兴趣,不少人就此放弃。所以我个人看法学汇编,不一定要写程序,写程序确实不是汇编的强项,大家不妨玩玩DEBUG,有时CRACK出一个小软件比完成一个程序更有成就感(就像学电脑先玩游戏一样)。某些高深的指令事实上只对有经验的汇编程序员有用,对我们而言,太过高深了。为了使学习汇编语言有个好的开始,你必须要先排除那些华丽复杂的命令,将注意力集中在最重要的几个指令上(CMP LOOP MOV JNZ……)。但是想在啰里吧嗦的教科书中完成上述目标,谈何容易,所以本人整理了这篇超浓缩(用WINZIP、WINRAR…依次压迫,嘿嘿!)教程。大言不惭的说,看通本文,你完全可以“不经意”间在前辈或是后生卖弄一下DEBUG,很有成就感的,试试看!那么――这个接下来呢?――Here we go!(阅读时看不懂不要紧,下文必有分解) 因为汇编是通过CPU和内存跟硬件对话的,所以我们不得不先了解一下CPU和内存:(关于数的进制问题在此不提) CPU是可以执行电脑所有算术╱逻辑运算与基本I/O控制功能的一块芯片。一种汇编语言只能用于特定的CPU。也就是说,不同的CPU其汇编语言的指令语法亦不相同。个人电脑由1981年推出至今,其CPU发展过程为:8086→80286→80386→80486→PENTIUM →……,还有AMD、CYRIX等旁支。后面兼容前面CPU的功能,只不过多了些指令(如多能奔腾的MMX指令集)、增大了寄存器(如386的32位EAX)、增多了寄存器(如486的FS)。为确保汇编程序可以适用于各种机型,所以推荐使用8086汇编语言,其兼容性最佳。本文所提均为8086汇编语言。寄存器(Register)是CPU内部的元件,所以在寄存器之间的数据传送非常快。用途:1.可将寄存器内的数据执行算术及逻辑运算。2.存于寄存器内的地址可用来指向内存的某个位置,即寻址。3.可以用来读写数据到电脑的周边设备。8086有8个8位数据寄存器,这些8位寄存器可分别组成16位寄存器:AH&AL=AX:累加寄存器,常用于运算;BH&BL=BX:基址寄存器,常用于地址索引;CH&CL=CX:计数寄存器,常用于计数;DH&DL=DX:数据寄存器,常用于数据传递。为了运用所有的内存空间,8086设定了四个段寄存器,专门用来保存段地址:CS(Code Segment):代码段寄存器;DS(Data Segment):数据段寄存器;SS(Stack Segment):堆栈段寄存器;ES(Extra Segment):附加段寄存器。当一个程序要执行时,就要决定程序代码、数据和堆栈各要用到内存的哪些位置,通过设定段寄存器CS,DS,SS来指向这些起始位置。通常是将DS固定,而根据需要修改CS。所以,程序可以在可寻址空间小于64K的情况下被写成任意大小。所以,程序和其数据组合起来的大小,限制在DS所指的64K内,这就是COM文件不得大于64K的原因。8086以内存做为战场,用寄存器做为军事基地,以加速工作。除了前面所提的寄存器外,还有一些特殊功能的寄存器:IP(Intruction Pointer):指

软件破解入门教程

先教大家一些基础知识,学习破解其实是要和程序打交道的,汇编是破解程序的必备知识,但有可能部分朋友都没有学习过汇编语言,所以我就在这里叫大家一些简单实用的破解语句吧! ---------------------------------------------------------------------------------------------------------------- 语句:cmp a,b //cmp是比较的意思!在这里假如a=1,b=2 那么就是a与b比较大小. mov a,b //mov是赋值语句,把b的值赋给a. je/jz //就是相等就到指定位置(也叫跳转). jne/jnz //不相等就到指定位置. jmp //无条件跳转. jl/jb //若小于就跳. ja/jg //若大于就跳. jge //若大于等于就跳. 这里以一款LRC傻瓜编辑器为例,讲解一下软件的初步破解过程。大家只要认真看我的操作一定会!假如还是不明白的话提出难点帮你解决,还不行的话直接找我!有时间给你补节课!呵呵! 目标:LRC傻瓜编辑器杀杀杀~~~~~~~~~ 简介:本软件可以让你听完一首MP3歌曲,便可编辑完成一首LRC歌词。并且本软件自身还带有MP3音乐播放和LRC歌词播放功能,没注册的软件只能使用15天。 工具/原料 我们破解或给软件脱壳最常用的软件就是OD全名叫Ollydbg,界面如图: 它是一个功能很强大的工具,左上角是cpu窗口,分别是地址,机器码,汇编代码,注释;注释添加方便,而且还能即时显示函数的调用结果,返回值. 右上角是寄存器窗口,但不仅仅反映寄存器的状况,还有好多东东;双击即可改变Eflag的值,对于寄存器,指令执行后发生改变的寄存器会用红色突出显示. cpu窗口下面还有一个小窗口,显示当前操作改变的寄存器状态. 左下角是内存窗口.可以ascii或者unicode两种方式显示内存信息. 右下角的是当前堆栈情况,还有注释啊. 步骤/方法 1. 我们要想破解一个软件就是修改它的代码,我们要想在这代码的海洋里找到我们破解关键的代码确实很棘 手,所以我们必须找到一定的线索,一便我们顺藤摸瓜的找到我们想要的东东,现在的关键问题就是什么

(完整word版)汇编语言常用指令大全,推荐文档

MOV指令为双操作数指令,两个操作数中必须有一个是寄存器. MOV DST , SRC // Byte / Word 执行操作: dst = src 1.目的数可以是通用寄存器, 存储单元和段寄存器(但不允许用CS段寄存器). 2.立即数不能直接送段寄存器 3.不允许在两个存储单元直接传送数据 4.不允许在两个段寄存器间直接传送信息 PUSH入栈指令及POP出栈指令: 堆栈操作是以“后进先出”的方式进行数据操作. PUSH SRC //Word 入栈的操作数除不允许用立即数外,可以为通用寄存器,段寄存器(全部)和存储器. 入栈时高位字节先入栈,低位字节后入栈. POP DST //Word 出栈操作数除不允许用立即数和CS段寄存器外, 可以为通用寄存器,段寄存器和存储器. 执行POP SS指令后,堆栈区在存储区的位置要改变. 执行POP SP 指令后,栈顶的位置要改变. XCHG(eXCHanG)交换指令: 将两操作数值交换. XCHG OPR1, OPR2 //Byte/Word 执行操作: Tmp=OPR1 OPR1=OPR2 OPR2=Tmp 1.必须有一个操作数是在寄存器中 2.不能与段寄存器交换数据 3.存储器与存储器之间不能交换数据. XLAT(TRANSLATE)换码指令: 把一种代码转换为另一种代码. XLAT (OPR 可选) //Byte 执行操作: AL=(BX+AL) 指令执行时只使用预先已存入BX中的表格首地址,执行后,AL中内容则是所要转换的代码. LEA(Load Effective Address) 有效地址传送寄存器指令 LEA REG , SRC //指令把源操作数SRC的有效地址送到指定的寄存器中. 执行操作: REG = EAsrc 注: SRC只能是各种寻址方式的存储器操作数,REG只能是16位寄存器 MOV BX , OFFSET OPER_ONE 等价于LEA BX , OPER_ONE MOV SP , [BX] //将BX间接寻址的相继的二个存储单元的内容送入SP中 LEA SP , [BX] //将BX的内容作为存储器有效地址送入SP中 LDS(Load DS with pointer)指针送寄存器和DS指令 LDS REG , SRC //常指定SI寄存器。 执行操作: REG=(SRC), DS=(SRC+2) //将SRC指出的前二个存储单元的内容送入指令中指定的寄存器中,后二个存储单元送入DS段寄存器中。

(完整word版)汇编语言指令集合-吐血整理,推荐文档

8086/8088指令系统记忆表 数据寄存器分为: AH&AL=AX(accumulator):累加寄存器,常用于运算;在乘除等指令中指定用来存放操作数,另外,所有的I/O指令都使用这一寄存器与外界设备传送数据. BH&BL=BX(base):基址寄存器,常用于地址索引; CH&CL=CX(count):计数寄存器,常用于计数;常用于保存计算值,如在移位指令,循环(loop)和串处理指令中用作隐含的计数器. DH&DL=DX(data):数据寄存器,常用于数据传递。他们的特点是,这4个16位的寄存器可以分为高8位: AH, BH, CH, DH.以及低八位:AL,BL,CL,DL。这2组8位寄存器可以分别寻址,并单独使用。 另一组是指针寄存器和变址寄存器,包括: SP(Stack Pointer):堆栈指针,与SS配合使用,可指向目前的堆栈位置; BP(Base Pointer):基址指针寄存器,可用作SS的一个相对基址位置; SI(Source Index):源变址寄存器可用来存放相对于DS段之源变址指针; DI(Destination Index):目的变址寄存器,可用来存放相对于ES 段之目的变址指针。 指令指针IP(Instruction Pointer) 标志寄存器FR(Flag Register) OF(overflow flag) DF(direction flag) CF(carrier flag) PF(parity flag) AF(auxiliary flag) ZF(zero flag) SF(sign flag) IF(interrupt flag) TF(trap flag) 段寄存器(Segment Register) 为了运用所有的内存空间,8086设定了四个段寄存器,专门用来保存段地址: CS(Code Segment):代码段寄存器; DS(Data Segment):数据段寄存器; SS(Stack Segment):堆栈段寄存器;

一个简单的C程序反汇编解析.

一个简单的 C++程序反汇编解析 本系列主要从汇编角度研究 c++语言机制和汇编的对应关系。第一篇自然应该从最简单的开始。 c++的源代码如下: class my_class { public : my_class( { m_member = 1; } void method(int n { m_member = n; } ~my_class( { m_member = 0; } private :

int m_member; }; int _tmain(int argc, _tchar* argv[] { my_class a_class; a_class.method(10; return 0; } 可以直接 debug 的时候看到 assembly 代码,不过这样获得的代码注释比较少。比较理想的方法是利用 vc 编译器的一个选项 /fas来生成对应的汇编代码。 /fas还会在汇编代码中加入注释注明和 c++代码的对应关系,十分有助于分析。 build 代码便可以在输出目录下发现对应的 .asm 文件。本文将逐句分析汇编代码和 c++的对应关系。 首先是 winmain : _text segment _wmain proc push ebp ; 保存旧的 ebp mov ebp, esp ; ebp保存当前栈的位置 push -1 ; 建立 seh(structured exception handler链 ; -1表示表头 , 没有 prev

push __ehhandler$_wmain ; seh异常处理程序的地址 mov eax, dword ptr fs:0 ; fs:0指向 teb 的内容,头 4个字节是当前 seh 链的地址 push eax ; 保存起来 sub esp, d8h ; 分配 d8h 字节的空间 push ebx push esi push edi lea edi, dword ptr [ebp-e4h] ; e4h = d8h + 4 * 3,跳过中间 ebx, esi, edi mov ecx, 36h ; 36h*4h=d8h,也就是用 36h 个 cccccccch 填满刚才分配的 d8h 字节空间 mov eax, cccccccch rep stosd mov eax, dword ptr ___security_cookie xor eax, ebp push eax ; ebp ^ __security_cookie压栈保存 lea eax, dword ptr [ebp-0ch] ; ebp-0ch 是新的 seh 链的结构地址(刚压入栈中的栈地址 mov dword ptr fs:0, eax ; 设置到 teb 中作为当前 active 的 seh 链表末尾 到此为止栈的内容是这样的: 低地址 security cookie after xor

IAR -arm 入门教程

IAR 使用说明 关于文档(初版): 1.主要是为了给IAR的绝对新手作参考用 2.emot制件,由Zigbee & IAR 学习小组保持修订权 3.希望用IAR朋友能将它修订完善 4.任何人可无偿转载、传播本文档,无须申请许可,但请保留文档来源及标志 5.如无重大升级,请沿用主版本号 版本 版本号制作时间制定人/修改人说明 1.00 2008/7/27 emot 初版(仅供新手参考) 1.01 2010/8/19 Emot 增加 下载程序(第四章) 在线调试程序(第五章) 序: 其实IAR和keil区别也没有多大,不过很多人就是怕(当初我也怕)。怕什么呢,怕学会了,真的就是害怕学习的心理让新手觉得IAR是个不好用的或者说“还不会用的”一个工具吧。我也是一个刚毕业的小子,如果说得不妥,还请大家来点砖头,好让小组筑高起来。(Zigbee & IAR 学习小组地址是https://www.doczj.com/doc/8e15628418.html,/673) 初版我将会说明以下3个问题,IAR的安装、第一个IAR工程的建立和工作编译。这是我写的第一个使用说明,不足的以后补充吧。 一、IAR软件安装图解 1.打开IAR软件安装包进入安装界面 打开软件开发包

软件安装界面 2.按照提示步骤执行,一直到授权页面,输入序列号,IAR中有两层序列号,所以要输入两 组序列号。 输入第一组序列号

3.选择安装路径(最好默认,不默认也不影响使用) 路径选择页面

修改路径4.选择全部安装(Full) 选择全部安装5.按提示知道安装完成。

安装完成页面 二、新建第一个IAR工程 用IAR首先要新建的是工作区,而不是工程。在工作区里再建立工程,一个工作区里似乎也不能建多个工程(我试过,但没成功,不知道IAR里提出workspace的概念是为什么?)要不打IAR的help来看,说清楚也是头痛的事,先知道有要在工作空间里建工程就对了。新建IAR工作空间,首先是菜单File里选择Open再选择Workspace,为方便说明再遇到菜 单我就直接说成File-Open-Workspace这样了。看了下面图上的红圈就知道是怎么回事了。 接着就会看到一片空白。这时就是新的“办公区”了。

Linux基本反汇编结构与GDB入门

Linux下的汇编与Windows汇编最大的不同就是第一个操作数是原操作数,第二个是目的操作数,而Windows下却是相反。 1、基本操作指令 简单的操作数类型说明,一般有三种, (1)立即数操作数,也就是常数值。立即数的书写方式是“$”后面跟一个整数,比如$0x1F,这个会在后面的具体分析中见到很多。 (2)寄存器操作数,它表示某个寄存器的内容,用符号Ea来表示任意寄存器a,用引用R[Ea]来表示它的值,这是将寄存器集合看成一个数组R,用寄存器表示符作为索引。 (3)操作数是存储器引用,它会根据计算出来的地址(通常称为有效地址)访问某个存储器位置。用符号Mb[Addr]表示对存储在存储器中从地址Addr开始的b字节值的引用。通常可以省略下标b。 图1表示有多种不同的寻址模式,一个立即数偏移Imm,一个基址寄存器Eb,一个变址或索引寄存器Ei和一个伸缩因子s。有效地址被计算为Imm+R[Eb]+R[Ei]*s,对于这中寻址方式,我们可以在数组或者结构体中进行对元

注:操作数可以是立即数值、寄存器值或是来自存储器的值,伸缩因子必须是1、2、4、或者是8。从上面的图我们就可以大致了解操作数的类型了。 在操作指令中,最频繁使用的指令是执行数据传送的指令。对于传送指令的两个操作数不能都指向存储器位置(我的理解是一般存储器存储的都是地址,不能够对地址和地址进行操作)。将一个值从一个存储器位置拷到另一个存储器位置需要两条指令——第一条指令将源值加载到寄存器中,第二条将该寄存器值写入到目的位置。下面给出源操作数和目的操作数的五种可能组合。 1、movl $0x4050, %eax 立即数——寄存器 2、movl %ebp, %esp 寄存器——寄存器 3、movl (%edi, %ecx), %eax 存储器——寄存器 4、movl $-17, (%esp) 立即数——存储器 5、movl %eax, -12(%ebp) 寄存器——存储器 注意这里的指令mov可能有不同的形式,不同平台的汇编一般是有些不一样的, 结合例子来进行讲解一下指令的具体操作,在这里将会正式接触到Linux下的GCC开发环境和GDB调试器,不过都是比较简单的应用。我的Linux操作系统是Ubuntu9.10,其它版本的差别应该不大, 如果我们要编写一个程序,我们可以用Linux下自带的vi或vim编辑器,studyrush@studyrush-desktop:~/C$ vi exchange.c vi 后面加我们要创建的程序文件的名字,在这里是exchange.c studyrush@studyrush-desktop:~/C$ gcc -o exchange exchange.c gcc -o exchange exchange.c 或gcc exchange –o exchange这两者都可以对源文件进行编译,-o exchange 表示对我们要输出的文件名称,可能表达的不够准确,大家可以先熟悉一下gcc编译器,应该就会明白的了。 studyrush@studyrush-desktop:~/C$ ./exchange 点加斜线再加输出文件名就表示运行程序,下面是运行的结果。 a = 3, b = 4

反汇编基础知识

计算机寄存器分类简介: 32位CPU所含有的寄存器有: 4个数据寄存器(EAX、EBX、ECX和EDX) 2个变址和指针寄存器(ESI和EDI) 2个指针寄存器(ESP和EBP) 6个段寄存器(ES、CS、SS、DS、FS和GS) 1个指令指针寄存器(EIP) 1个标志寄存器(EFlags) 1、数据寄存器 数据寄存器主要用来保存操作数和运算结果等信息,从而节省读取操作数所需占用总线和访问存储器的时间。 32位CPU有4个32位的通用寄存器EAX、EBX、ECX和EDX。 对低16位数据的存取,不会影响高16位的数据。 这些低16位寄存器分别命名为:AX、BX、CX和DX,它和先前的CPU中的寄存器相一致。 4个16位寄存器又可分割成8个独立的8位寄存器(AX:AH-AL、BX:BH-BL、CX:CH-CL、DX:DH-DL),每个寄存器都有自己的名称,可独立存取。 程序员可利用数据寄存器的这种“可分可合”的特性,灵活地处理字/字节的信息。 寄存器EAX通常称为累加器(Accumulator),用累加器进行的操作可能需要更少时间。可用于乘、除、输入/输出等操作,使用频率很高; 寄存器EBX称为基地址寄存器(Base Register)。它可作为存储器指针来使用; 寄存器ECX称为计数寄存器(Count Register)。

在循环和字符串操作时,要用它来控制循环次数;在位操作中,当移多位时,要用CL来指明移位的位数; 寄存器EDX称为数据寄存器(Data Register)。在进行乘、除运算时,它可作为默认的操作数参与运算,也可用于存放I/O的端口地址。在16位CPU中,AX、BX、CX和DX不能作为基址和变址寄存器来存放存储单元的地址, 在32位CPU中,其32位寄存器EAX、EBX、ECX和EDX不仅可传送数据、暂存数据保存算术逻辑运算结果, 而且也可作为指针寄存器,所以,这些32位寄存器更具有通用性。 2、变址寄存器 32位CPU有2个32位通用寄存器ESI和EDI。 其低16位对应先前CPU中的SI和DI,对低16位数据的存取,不影响高16位的数据。 寄存器ESI、EDI、SI和DI称为变址寄存器(Index Register),它们主要用于存放存储单元在段内的偏移量, 用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。 变址寄存器不可分割成8位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果。 它们可作一般的存储器指针使用。在字符串操作指令的执行过程中,对它们有特定的要求,而且还具有特殊的功能。 3、指针寄存器 其低16位对应先前CPU中的BP和SP,对低16位数据的存取,不影响高16位的数据。 32位CPU有2个32位通用寄存器EBP和ESP。 它们主要用于访问堆栈内的存储单元,并且规定: EBP为基指针(Base Pointer)寄存器,用它可直接存取堆栈中的数据;

6、汇编学习从入门到精通(荐书)

汇编学习从入门到精通Step By Step 2007年12月15日星期六00:34 信息来源:https://www.doczj.com/doc/8e15628418.html,/hkbyest/archive/2007/07/22/1702065.aspx Cracker,一个充满诱惑的词。别误会,我这里说的是软件破解,想做骇客的一边去,这年头没人说骇客,都是“黑客”了,嘎嘎~ 公元1999年的炎热夏季,我捧起我哥留在家的清华黄皮本《IBM-PC汇编语言程序设计》,苦读。一个星期后我那脆弱的小心灵如玻璃般碎裂了,为了弥补伤痛我哭爹求妈弄了8k大洋配了台当时算是主流的PC,要知道那是64M内存!8.4G硬盘啊!还有传说中的Celeon 300A CPU。不过很可惜的是在当时那32k小猫当道的时代,没有宽带网络,没有软件,没有资料,没有论坛,理所当然我对伟大的计算机科学体系的第一步探索就此夭折,此时陪伴我的是那些盗版光盘中的游戏,把CRACK_XXX文件从光盘复制到硬盘成了时常的工作,偶尔看到光盘中的nfo 文件,心里也闪过一丝对破解的憧憬。 上了大学后有网可用了,慢慢地接触到了一些黑客入侵的知识,想当黑客是每一个充满好奇的小青年的神圣愿望,整天看这看那,偷偷改了下别人的网页就欢喜得好像第一次偷到鸡的黄鼠狼。 大一开设的汇编教材就是那不知版了多少次的《IBM-PC汇编语言程序设计》,凭着之前的那星期苦读,考试混了个80分。可惜当时头脑发热,大学60分万岁思想无疑更为主流,现在想想真是可惜了宝贵的学习时间。 不知不觉快毕业了,这时手头上的《黑客防线》,《黑客X档案》积了一大摞,整天注来注去的也厌烦了,校园网上的肉鸡一打一打更不知道拿来干什么。这时兴趣自然转向了crack,看着杂志上天书般的汇编代码,望望手头还算崭新的汇编课本,叹了口气,重新学那已经忘光了的汇编语言吧。咬牙再咬牙,看完寻址方式那章后我还是认输,不认不行啊,头快裂了,第三次努力终告失败。虽然此时也可以爆破一些简单的软件,虽然也知道搞破解不需要很多的汇编知识,但我还是固执地希望能学好这门基础中的基础课程。 毕业了,进入社会了,找工作,上班,换工作成了主流旋律,每天精疲力尽的哪有时间呢?在最初的中国移动到考公务员再到深圳再到家里希望的金融机构,一系列的曲折失败等待耗光了我的热情,我失业了,赋闲在家无所事事,唯一陪伴我的是那些杂志,课本,以及过时的第二台电脑。我不想工作,我对找工作有一种恐惧,我靠酒精麻醉自己,颓废一段日子后也觉得生活太过无聊了,努力看书考了个CCNA想出去,结果还是被现实的就业环境所打败。三年时间,一无所获。 再之后来到女朋友处陪伴她度过刚毕业踏入社会工作的适应时期,这段时间随便找了个电脑技术工作,每月赚那么个几百块做生活费。不过这半年让我收获比较大的就是时间充裕,接触到了不少新东西,我下定决心要把汇编学好,这时我在网上看到了别人推荐的王爽《汇编语言》,没抱什么希望在当当网购了人生中的第一次物,19块6毛,我记得很清楚,呵呵。 废话终于完了,感谢各位能看到这里,下面进入正题吧。

汇编语言入门教程

汇编语言入门教程 2007-04-29 22:04对初学者而言,汇编的许多命令太复杂,往往学习很长时间也写不出一个漂漂亮亮的程序,以致妨碍了我们学习汇编的兴趣,不少人就此放弃。所以我个人看法学汇编,不一定要写程序,写程序确实不是汇编的强项,大家不妨玩玩DEBUG,有时CRACK 出一个小软件比完成一个程序更有成就感(就像学电脑先玩游戏一样)。某些高深的指令事实上只对有经验的汇编程序员有用,对我们而言,太过高深了。为了使学习汇编语言有个好的开始,你必须要先排除那些华丽复杂的命令,将注意力集中在最重要的几个指令上(CMP LOOP MOV JNZ……)。但是想在啰里吧嗦的教科书中完成上述目标,谈何容易,所以本人整理了这篇超浓缩(用WINZIP、WINRAR…依次压迫,嘿嘿!)教程。大言不惭的说,看通本文,你完全可以“不经意”间在前辈或是后生卖弄一下DEBUG,很有成就感的,试试看!那么――这个接下来呢?――Here we go!(阅读时看不懂不要紧,下文必有分解) 因为汇编是通过CPU和内存跟硬件对话的,所以我们不得不先了解一下CPU和内存:(关于数的进制问题在此不提) CPU是可以执行电脑所有算术╱逻辑运算与基本I/O 控制功能的一块芯片。一种汇编语言只能用于特定的CPU。也就是说,不同的CPU其汇编语言的指令语法亦不相同。个人电脑由1981年推出至今,其CPU发展过程为:8086→80286→80386→80486→PENTIUM →……,还有AMD、CYRIX等旁支。后面兼容前面CPU的功能,只不过多了些指令(如多能奔腾的MMX指令集)、增大了寄存器(如386的32位EAX)、增多了寄存器(如486的FS)。为确保汇编程序可以适用于各种机型,所以推荐使用8086汇编语言,其兼容性最佳。本文所提均为8086汇编语言。寄存器(Register)是CPU内部的元件,所以在寄存器之间的数据传送非常快。用途:1.可将寄存器内的数据执行算术及逻辑运算。2.存于寄存器内的地址可用来指向内存的某个位置,即寻址。3.可以用来读写数据到电脑的周边设备。8086 有8个8位数据寄存器,这些8位寄存器可分别组成16位寄存器:AH&AL=AX:累加寄存器,常用于运算;BH&BL=BX:基址寄存器,常用于地址索引;CH&CL=CX:计数寄存器,常用于计数;DH&DL=DX:数据寄存器,常用于数据传递。为了运用所有的内存空间,8086设定了四个段寄存器,专门用来保存段地址:CS(Code Segment):代码段寄存器;DS(Data Segment):数据段寄存器;SS(Stack Segment):堆栈段寄存器;ES(Extra Segment):附加段寄存器。当一个程序要执行时,就要决定程序代码、数据和堆栈各要用到内存的哪些位置,通过设定段寄存器CS,DS,SS 来指向这些起始位置。通常是将DS固定,而根据需要修改CS。所以,程序可以在可寻址空间小于64K的情况下被写成任意大小。所以,程序和其数据组合起来的大小,限制在DS 所指的64K内,这就是COM文件不得大于64K的原因。8086以内存做为战场,用寄存器做为军事基地,以加速工作。除了前面所提的寄存器外,还有一些特殊功能的寄存器:IP(Intruction Pointer):指令指针寄存器,与CS配合使用,可跟踪程序的执行过程;SP(Stack Pointer):堆栈指针,与SS配合使用,可指向目前的堆栈位置。BP(Base Pointer):基址指针寄存器,可用作SS的一个相对基址位置;SI(Source Index):源变址寄存器可用来存放相对于DS 段之源变址指针;DI(Destination Index):目的变址寄存器,可用来存放相对于ES 段之目的变址指针。还有一个标志寄存器FR(Flag Register),有九个有意义的标志,将在下文用到时详细说明。

汇编语言标志位(CF)及一些常用指令

汇编语言标志位(CF)及一些常用指令 英文翻译: carry 进位 flag标志 NV: no overflow OV: overflow UP: up DN:down DI: disable interrupt EI: enable interrupt PL: plus NG: negative NZ: no zero ZR: zero NA: no assistant carry AC: assistant carry PO: parity odd PE: parity even 奇偶校验 NC: no carry CY: carry 汇编标志位: 标志名标志 1 标志 0 OF (溢出标志) OV NV DF (方向标志) UP DN IF (中断标志) DI EI SF (符号标志位) PL NG ZF (零标志) NZ ZR AF (辅助进位标志位) NA AC PF (奇偶标志) PO PE CF (进位标志) NC CY OF 溢出(是/否) OV OVerflow NV Not oVerflow DF 方向(减量/增量) DN DowN UP UP IF 中断(允许/关闭) EI Enable Interrupt DI Disable Interrupt SF 符号(负/正) NG NeGative PL PLus ZF 零(是/否) ZR ZeRo NZ Not Zero AF 辅助进位(是/否) AC Auxiliary Carry NA Not Auxiliary PF 奇偶(是/否) PE Parity Even PO Parity Odd CF 进位(是/否) CY CarrY NC Not Carry 英文解释: NV: no overflow OV: overflow UP: up DN:down DI: disable interrupt EI: enable interrupt PL: plus NG: negative NZ: no zero ZR: zero NA: no assistant carry AC: assistant carry PO: parity odd PE: parity even NC: no carry CY: carry

arm反汇编.

在bin文件中,就是一条条的机器指令,每条指令4个字节。 在ADS中打开一个.s文件,选择project->disassemble 可以看到汇编的机器码 汇编代码如下(ADS中的一个例程\ARM\ADSv1_2\Examples\asm\armex.s): AREA ARMex, CODE, READONL Y ; name this block of code ENTRY ; mark first instruction ; to execute start MOV r0, #10 ; Set up parameters MOV r1, #3 ADD r0, r0, r1 ; r0 = r0 + r1 stop MOV r0, #0x18 ; angel_SWIreason_ReportException LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit SWI 0x123456 ; ARM semihosting SWI END ; Mark end of file 执行project->disassemble后: ** Section #1 'ARMex' (SHT_PROGBITS) [SHF_ALLOC + SHF_EXECINSTR + SHF_ENTRYSECT] Size : 28 bytes (alignment 4) start $a ARMex 0x00000000: e3a0000a .... MOV r0,#0xa 0x00000004: e3a01003 .... MOV r1,#3 0x00000008: e0800001 .... ADD r0,r0,r1 stop 0x0000000c: e3a00018 .... MOV r0,#0x18 0x00000010: e59f1000 .... LDR r1,0x18 0x00000014: ef123456 V4.. SWI 0x123456 $d 0x00000018: 00020026 &... DCD 131110 使用UltraEdit看bin文件如下: 可以看到,与上面的一样。 其中MOV的机器码如下(ARM体系结构pdf:p156):

Windows X86-64位汇编语言入门

Windows X86-64位汇编语言入门 Windows X64汇编入门(1) 最近断断续续接触了些64位汇编的知识,这里小结一下,一是阶段学习的回顾,二是希望对64位汇编新手有所帮助。我也是刚接触这方面知识,文中肯定有错误之处,大家多指正。 文章的标题包含了本文的四方面主要内容: (1)Windows:本文是在windows环境下的汇编程序设计,调试环境为Windows Vista 64位版,调用的均为windows API。 (2)X64:本文讨论的是x64汇编,这里的x64表示AMD64和Intel的EM64T,而不包括IA64。至于三者间的区别,可自行搜索。 (3)汇编:顾名思义,本文讨论的编程语言是汇编,其它高级语言的64位编程均不属于讨论范畴。 (4)入门:既是入门,便不会很全。其一,文中有很多知识仅仅点到为止,更深入的学习留待日后努力。其二,便于类似我这样刚接触x64汇编的新手入门。 本文所有代码的调试环境:Windows Vista x64,Intel Core 2 Duo。 1. 建立开发环境 1.1 编译器的选择 对应于不同的x64汇编工具,开发环境也有所不同。最普遍的要算微软的MASM,在x64环境中,相应的编译器已经更名为ml64.exe,随Visual Studio 2005一起发布。因此,如果你是微软的忠实fans,直接安装VS2005既可。运行时,只需打开相应的64位命令行窗口(图1),便可以用ml64进行编译了。

第二个推荐的编译器是GoASM,共包含三个文件:GoASM编译器、GoLINK链接器和GoRC 资源编译器,且自带了Include目录。它的最大好外是小,不用为了学习64位汇编安装几个G 的VS。因此,本文的代码就在GoASM下编译。 第三个Yasm,因为不熟,所以不再赘述,感兴趣的朋友自行测试吧。 不同的编译器,语法会有一定差别,这在下面再说。 1.2 IDE的选择 搜遍了Internet也没有找到支持asm64的IDE,甚至连个Editor都没有。因此,最简单的方法是自行修改EditPlus的masm语法文件,这也是我采用的方法,至少可以得到语法高亮。当然,如果你懒得动手,那就用notepad吧。 没有IDE,每次编译时都要手动输入不少参数和选项,做个批处理就行了。 1.3 硬件与操作系统 硬件要求就是64位的CPU。操作系统也必须是64位的,如果在64位的CPU上安装了

OllyDBG完美教程

关键词:OD、OllyDBG、破解入门、调试专用工具、反汇编 一、OllyDBG 的安装与配置 OllyDBG 1.10 版的发布版本是个 ZIP 压缩包,只要解压到一个目录下,运行 OllyDBG.exe 就可以了。汉化版的发布版本是个 RAR 压缩包,同样只需解压到一个目录下运行 OllyDBG.exe 即可: OllyDBG 中各个窗口的功能如上图。简单解释一下各个窗口的功能,更详细的内容可以参考 TT 小组翻译的中文帮助: 反汇编窗口:显示被调试程序的反汇编代码,标题栏上的地址、HEX 数据、反汇编、注释可以通过在窗口中右击出现的菜单界面选项->隐藏标题或显示标题来进行切换是否显示。用鼠标左键点击注释标签可以切换注释显示的方式。

寄存器窗口:显示当前所选线程的 CPU 寄存器内容。同样点击标签寄存器 (FPU) 可以切换显示寄存器的方式。 信息窗口:显示反汇编窗口中选中的第一个命令的参数及一些跳转目标地址、字串等。 数据窗口:显示内存或文件的内容。右键菜单可用于切换显示方式。 堆栈窗口:显示当前线程的堆栈。 要调整上面各个窗口的大小的话,只需左键按住边框拖动,等调整好了,重新启动一下 OllyDBG 就可以生效了。 启动后我们要把插件及 UDD 的目录配置为绝对路径,点击菜单上的选项->界面,将会出来一个界面选项的对话框,我们点击其中的目录标签: 因为我这里是把 OllyDBG 解压在 F:\OllyDBG 目录下,所以相应的 UDD 目录及插件目录按图上配置。还有一个常用到的标签就是上图后面那个字体,在这里你可以更改 OllyDBG 中显示的字体。上图中其它的选项可以保留为默认,若有需要也可以自己修改。修改完以后点击确定,弹出一个对话框,说我们更改了插件路径,要重新启动 OllyDBG。在这个对话框上点确定,重新启动一下 OllyDBG,我们再到界面选项中看一下,会发现我们原先设置好的路径都已保存了。有人可能知道插件的作用,但对那个 UDD 目录

OllyICE反汇编教程及汇编命令详解

OllyICE反汇编教程及汇编命令详解[转] 2009-02-11 08:09 OllyICE反汇编教程及汇编命令详解 内容目录 计算机寄存器分类简介 计算机寄存器常用指令 一、常用指令 二、算术运算指令 三、逻辑运算指令 四、串指令 五、程序跳转指令 ------------------------------------------ 计算机寄存器分类简介: 32位CPU所含有的寄存器有: 4个数据寄存器(EAX、EBX、ECX和EDX) 2个变址和指针寄存器(ESI和EDI) 2个指针寄存器(ESP和EBP) 6个段寄存器(ES、CS、SS、DS、FS和GS) 1个指令指针寄存器(EIP) 1个标志寄存器(EFlags) 1、数据寄存器 数据寄存器主要用来保存操作数和运算结果等信息,从而节省读取操作数所需占用总线和访问存储器的时间。 32位CPU有4个32位的通用寄存器EAX、EBX、ECX和EDX。 对低16位数据的存取,不会影响高16位的数据。 这些低16位寄存器分别命名为:AX、BX、CX和DX,它和先前的CPU中的寄存器相一致。4个16位寄存器又可分割成8个独立的8位寄存器(AX:AH-AL、BX:BH-BL、CX:CH-CL、DX:DH-DL),每个寄存器都有自己的名称,可独立存取。 程序员可利用数据寄存器的这种“可分可合”的特性,灵活地处理字/字节的信息。 寄存器EAX通常称为累加器(Accumulator),用累加器进行的操作可能需要更少时间。可用于乘、除、输入/输出等操作,使用频率很高; 寄存器EBX称为基地址寄存器(Base Register)。它可作为存储器指针来使用; 寄存器ECX称为计数寄存器(Count Register)。 在循环和字符串操作时,要用它来控制循环次数;在位操作中,当移多位时,要用CL来指明移位的位数; 寄存器EDX称为数据寄存器(Data Register)。在进行乘、除运算时,它可作为默认的操作数参与运算,也可用于存放I/O的端口地址。 在16位CPU中,AX、BX、CX和DX不能作为基址和变址寄存器来存放存储单元的地址,在32位CPU中,其32位寄存器EAX、EBX、ECX和EDX不仅可传送数据、暂存数据保存算术逻辑运算结果, 而且也可作为指针寄存器,所以,这些32位寄存器更具有通用性。 2、变址寄存器 32位CPU有2个32位通用寄存器ESI和EDI。 其低16位对应先前CPU中的SI和DI,对低16位数据的存取,不影响高16位的数据。 寄存器ESI、EDI、SI和DI称为变址寄存器(Index Register),它们主要用于存放存储单元在段内的偏移量,

汇编语言入门

汇编语言入门教程 对初学者而言,汇编的许多命令太复杂,往往学习很长时间也写不出一个漂漂亮亮的程序,以致妨碍了我们学习汇编的兴趣,不少人就此放弃。所以我个人看法学汇编,不一定要写程序,写程序确实不是汇编的强项,大家不妨玩玩DEBUG,有时CRACK出一个小软件比完成一个程序更有成就感(就像学电脑先玩游戏一样)。某些高深的指令事实上只对有经验的汇编程序员有用,对我们而言,太过高深了。为了使学习汇编语言有个好的开始,你必须要先排除那些华丽复杂的命令,将注意力集中在最重要的几个指令上(CMP LOOP MOV JNZ……)。但是想在啰里吧嗦的教科书中完成上述目标,谈何容易,所以本人整理了这篇超浓缩(用WINZIP、WINRAR…依次压迫,嘿嘿!)教程。大言不惭的说,看通本文,你完全可以“不经意”间在前辈或是后生卖弄一下DEBUG,很有成就感的,试试看!那么――这个接下来呢?――Here we go!(阅读时看不懂不要紧,下文必有分解) 因为汇编是通过CPU和内存跟硬件对话的,所以我们不得不先了解一下CPU和内存:(关于数的进制问题在此不提) CPU是可以执行电脑所有算术╱逻辑运算与基本I/O 控制功能的一块芯片。一种汇编语言只能用于特定的CPU。也就是说,不同的CPU其汇编语言的指令语法亦不相同。个人电脑由1981年推出至今,其CPU发展过程为:8086→80286→80386→80486→PENTIUM →……,还有AMD、CYRIX等旁支。后面兼容前面CPU的功能,只不过多了些指令(如多能奔腾的MMX指令集)、增大了寄存器(如386的32位EAX)、增多了寄存器(如486的FS)。为确保汇编程序可以适用于各种机型,所以推荐使用8086汇编语言,其兼容性最佳。本文所提均为8086汇编语言。寄存器(Register)是CPU内部的元件,所以在寄存器之间的数据传送非常快。用途:1.可将寄存器内的数据执行算术及逻辑运算。2.存于寄存器内的地址可用来指向内存的某个位置,即寻址。3.可以用来读写数据到电脑的周边设备。8086 有8个8位数据寄存器,这些8位寄存器可分别组成16位寄存器:AH&AL=AX:累加寄存器,常用于运算;BH&BL=BX:基址寄存器,常用于地址索引;CH&CL=CX:计数寄存器,常用于计数;DH&DL=DX:数据寄存器,常用于数据传递。为了运用所有的内存空间,8086设定了四个段寄存器,专门用来保存段地址:CS(Code Segment):代码段寄存器;DS(Data Segment):数据段寄存器;SS(Stack Segment):堆栈段寄存器;ES(Extra Segment):附加段寄存器。当一个程序要执行时,就要决定程序代码、数据和堆栈各要用到内存的哪些位置,通过设定段寄存器CS,DS,SS 来指向这些起始位置。通常是将DS固定,而根据需要修改CS。所以,程序可以在可寻址空间小于64K的情况下被写成任意大小。所以,程序和其数据组合起来的大小,限制在DS 所指的64K内,这就是COM文件不得大于64K的原因。8086以内存做为战场,用寄存器做为军事基地,以加速工作。除了前面所提的寄存器外,还有一些特殊功能的寄存器:IP(Intruction Pointer):指令指针寄存器,与CS配合使用,可跟踪程序的执行过程;SP(Stack Pointer):堆栈指针,与SS配合使用,可指向目前的堆栈位置。BP(Base Pointer):基址指针寄存器,可用作SS 的一个相对基址位置;SI(Source Index):源变址寄存器可用来存放相对于DS段之源变址指针;DI(Destination Index):目的变址寄存器,可用来存放相对于ES 段之目的变址指针。还有一个标志寄存器FR(Flag Register),有九个有意义的标志,将在下文用到时详细说明。 内存是电脑运作中的关键部分,也是电脑在工作中储存信息的地方。内存组织有许多可存放

相关主题
相关文档 最新文档