汇编基础

来源:互联网 发布:滚屏截图 windows自带 编辑:程序博客网 时间:2024/05/01 08:01
本篇起我们开始讲解汇编语言,最终目标是能读懂OllyDbg或IDA等反汇编 工具 得到的汇编 代码 ,并可进行基本的高级语言嵌入汇编 程序 设计。我假设A1系列文章你已经读过,并掌握基本的高级语言程序设计方法。 对任何事物都要从ta产生的来龙去脉去 研究 ,这样...
 
本篇起我们开始讲解汇编语言,最终目标是能读懂OllyDbg或IDA等反汇编工具得到的汇编代码,并可进行基本的高级语言嵌入汇编程序设计。我假设A1系列文章你已经读过,并掌握基本的高级语言程序设计方法。 
对任何事物都要从ta产生的来龙去脉去研究,这样才能获得全面深刻的理解。话说上世纪40年代产生了第一代计算机,由于是基于二进制数制逻辑,人们可以和计算机交流的语言限于机器语言,你完全可以把它理解为某种外星语言:机器语言完全由0和1组成,当然,这属于人家计算机内部交流语言。我们都知道詹姆士.邦德代号007,知道周星星代号9527,知道圣诞节1225,情人节214…说这么多,其实是为了告诉大家:每个人的记忆力都很好的,相信我,汇编里面要你记住的常用代号不会比节日多:)。最初,为了要计算机按照人们意图行事,不得不通过机器语言与之交流,这就使得当时的程序员和远古时期的巫师一样稀少和神秘,因为机器语言容易出错,比如,你找出下面两个数字的不同: 
1000110100011101001100011100011和1000110100011101001101011100011。 
这还属于最简单的,如果是十几张纸都是0和1,让你找错,恐怕效率和正确率都不会太高。当人们意识到这个问题时,开始构造一种机制来简化编程,这样就有了汇编语言,它的思想就是互相替换:在人编写程序时候,用邦德替换007,周星星替换9527,…;在计算机执行时候,用007替换邦德,9527替换周星星,…。虽然这里说的是替换,但实际属于编译原理的东西,操作起来比较复杂,此处就不深入了。 
永远记住,汇编语言是处理器相关的,此处主要以X86系列处理器为原型讲解,AMD公司的CPU一般都是兼容IA-32架构的,所以满足X86规则的汇编程序在AMD兼容芯片一样可以运行。 
我们知道计算机几个主要配件:CPU、内存和硬盘等,而把这些配件整合到一起的就是主板,上面有各种形式的导线。下面是一张逻辑图: 


上图中的I/O设备是指输入(Input)输出(Output)设备,如鼠标、键盘和摄像头等;I/O接口指的是USB、COM等接口;存储器主要指内存。从上图我们看到由CPU引出三种总线:控制、地址和数据总线,这是CPU坐阵指挥的途径,也就是说所有CPU指令最终都要表现在总线的操作上,而汇编语言本质上就是学会通过CPU操作总线的方法。那么,好奇的你就没有什么想问的吗?记住,有时候,好奇是比知识更重要的想象力!虽然好奇害死加菲猫。那么,CPU里面是怎么个组成呢?在我们学校有这么一个传说:胡伟武同学本科毕业设计就是用单片机搭出一个类8086芯片系统,这从一个侧面表达出X86的复杂。幸运的是,大家暂时不必了解太多;不幸的是,如果你打算像我的那个去360的师兄一样坚持走底层技术路线的话,你还是有必要读读Intel指令手册的。不过,我们暂时只需做个“幸运儿”:)。 
当我们要求计算机对两个数执行求和运算时,CPU处理的流程是怎样的呢?假设要对a与b求和,a、b均已经存在于内存了。结合上面架构图来看:CPU通过控制总线发出读数据的指令,通过地址总线指明要读数据在内存中的地址,通过数据总线把数据传给CPU,CPU把数据存储在自己的寄存器中,然后通过算术逻辑部件(ALU)完成运算。这里多了两个名词:寄存器和算术逻辑部件,我将逐一讲解。 
任何事物不能凭空存在,包括数据,CPU要交互的数据大部分放在内存和硬盘:硬盘向内存传输数据的速率远低于内存向CPU传输数据的速率;内存向CPU传输数据的速率远低于CPU的处理速率,现在的CPU为了缓解这个矛盾,一般都使用高速缓存机制,因为高速缓存向CPU传输数据的速率更快。那么,问题来了:CPU接收数据后把数据存放在哪里呢?答案是寄存器,寄存器的存取速率比内存快很多,主要用来存放运算过程中的数据地址、数据及中间结果等。本篇教程是以32位处理器为原型讲解,首先要讲的是最常用的通用寄存器:EAX、EBX、ECX和EDX。这就是寄存器代号,类似9527代表周星星: 


有的同学看到上面的图可能像魔兽争霸一样:混乱之至了,怎么又有AH、AL和AX,这是什么啊?我们来论资排辈一下,《天龙八部》里面少林觉字辈高于玄字辈,因为他们老师的辈分不同所导致;那么把从AL到EAX按照位数排下辈:(AL、AH[8位])<AX[16位]<EAX[32位]。其实它们指的是同一个东西的不同部分,好比一个手由5个指头组成,一个人有2个手:但自始自终都是指的一个人,只是范围不同而已。同理EBX、ECX和EDX与EAX结构和命名类似,比如EBX也有BX、BH和BL。这样做的目的是提供最大的灵活性,比如有时你只需要存一个8位的值,那么就没必要占用全部寄存器,只需要用一部分即可:这与宿舍类似,一个宿舍假设可以容纳4个人,那么只有一个学生的时候占一张床即可,不必全部占满。 


除了通用寄存器,还有变址和指针寄存器(EDI/ESI/ESP/EBP)、指令指针寄存器(EIP)、标志寄存器(EFlags):这些都是32位的寄存器。CPU中16位的寄存器是段寄存器,也就是说段寄存器这个房子的平米数比前面的寄存器要少,最大可以存16bit。 


各种寄存器的主要用途如下表: 
寄存器    代号    主要用途 
累加器    EAX    算术运算、存储中间结果、函数返回值 
基地址寄存器    EBX    基地址指针 
计数器    ECX    循环计数、移位操作计数、重复操作计数 
数据寄存器    EDX    乘除运算、存储中间结果 
源变址寄存器    ESI    存储指针、串指令的源操作数指针 
源目标变址寄存器    EDI    存储指针、串指令的目的操作数指针 
基地址指针    EBP    存储指针、存取堆栈指针 
栈顶指针    ESP    堆栈的栈顶指针 
这节暂时讲到这里。


[高级综合教程] 游戏辅助工具开发教程-从入门到精通之A2_2篇
入门, 教程, 游戏, 工具, 辅助
本帖最后由 老A 于 2010-9-7 10:17 编辑 


讲了这么多天理论知识有的同学可能有些迷茫和厌倦了,你看,教室后门那个穿夹克的同学都睡着了。所以今天我们来小小的演习一下,囿于目前大家的知识储备,下面的练习只能归为局部演习,等你们羽翼丰满的时候我会领着大家实战。千万记住:欲速则不达。 
首先我们需要自己编一个小游戏:玩家和怪物打斗,通过文字输出战斗情况。代码如下: 


执行界面如下,怪物和玩家的血量依次递减: 


包括以后真正的实战,我们要非常明确自己的目标:这里的目标是把玩家血量改为递增,即玩家被怪物攻击一次血量增加一个伤害值大小的数值。在上节教程,我们讲过与CPU交互的数据主要存储在内存中,那我们要做的就是找到内存中存储玩家血量数据的地址,因为CPU是通过地址总线来定位数据,这也就是我们第一步要做的事情:找地址,工具使用大家耳熟能详的CheatEngine。步骤如下: 


视频中的相关代码和程序打包下载:点击下载。 
视频的清晰版下载:点击下载。 
下面讲解一下外挂程序的细节: 


需要讲解的函数有2个:OpenProcess和WriteProcessMemory。 
1、OpenProcess(dwDesiredAccess,bInheritHandle,dwProcessId) 
第一个参数:对被打开进程的访问权,设置为PROCESS_ALL_ACCESS,即所有访问权; 
第二个参数:打开进程创建的子进程是否可以继承该访问权限,设置为NULL; 
第三个参数:要打开的进程的ID值,即我们在任务管理器里看到的PID(看不到的同学,通过查看->选择列->勾选PID)。 
此函数执行成功的话我们就得到了目标进程的句柄,你可以暂时把句柄理解系统为进程指定的身份证号。 
2、WriteProcessMemory(hProcess,lpBaseAddress,lpBuffer,nSize,lpNumberOfBytesWritten) 
第一个参数:要写入内存的目标进程句柄,已经通过上面函数得到了; 
第二个参数:要写入内存的地址指针,就是我们通过OllyDbg得到的那个地址; 
第三个参数:要写入的数据,即我们修改sub指令为add指令; 
第四个参数:要写入数据的大小; 
第五个参数:可选参数,与本处无关,设置为NULL。 
本节我们给出了一个完整的Demo演示,实战中不会这么顺利很简单。希望这篇教程能够提起大家的兴趣,以迎接真正的挑战:当你掌握了足够技术后,如果说还有什么能够阻挡你,只有你的想象力!来源:www.guahai.com


本帖最后由 老A 于 2010-9-7 10:18 编辑 


本节主要讲解汇编中的内存管理。 
CPU对内存的管理的最小单元是字节(8个bit),为了标识不同单元,就给每个单元一个编号,这个编号就是我们下面说的物理地址:地址是一个无符号的二进制整数,但为了书写方便一般写成十六进制形式,如0×410234等。下面首先讲解32位CPU的内存管理模式:实模式和保护模式。 
一、实模式 
物理地址=段地址×16+偏移量 
段地址存储在段寄存器中,即CS(代码段寄存器)、DS(数据段寄存器)、ES(附加段寄存器)、SS(堆栈段寄存器)、FS(附加段寄存器)、GS(附加段寄存器),其中FS和GS是80386及以后型号的CPU增加的寄存器。在实际编程中,遵循的原则如下表所示: 
指令操作    缺省的段寄存器    可选的段寄存器    偏移量 
取指令    CS         IP 
堆栈操作    SS         SP 
取操作数    DS    CS、ES、SS    有效地址 
串操作    DS/ES         SI/DI 
BP指针寄存器    SS    CS、DS、ES    有效地址 
简单解释一下上表,比如取操作数,缺省用DS作为段寄存器,偏移量为有效地址,DS:[100H],方括号中的是有效地址,冒号前的DS作为段基地址,假设此时DS的值为0×23E6H,那么内存单元的物理地址为:0×23E6H×16+100H=0×23F60H。所谓缺省的段寄存器,是说如果地址表达式为[100H],就认为DS是段基地址。为了便于理解,你可以这么去想:你要去宿舍楼找小明,首先需要知道楼层(段基地址),到楼层后再通过房间号(偏移地址),这样就找到小明了。 
内存最小单元式字节,但是我们经常接触的是字(2个字节)或双字(4个字节)。前面讲过,内存最小单元的编号就是地址,那么寻址字或双字的时候以哪个最小单元的地址为准呢?先来看个示意图: 


地址为23451H的字节的数值为:12H; 
地址为23451H的字的数值为:3412H; 
地址为23451H的双字的数值为:78563412H。 
Intel的CPU规定:高(低)地址存储单元存储高(低)字节。所以地址为23451H的字的数值的低字节为低地址23451H处的12H,高字节为23452H处的34H。 
二、保护模式 
此时的地址计算与实模式完全不同,段寄存器存储的不再是段基地址。对于32位CPU来讲,可以使用32根地址线访问内存,排列组合一下可以算出总共可以访问2^32Byte,即4GB。顾名思义,保护模式涉及到地址空间优先级等安全属性的描述,段寄存器就用来保存这些安全描述信息,但由于信息太多,需要64位长的数据才可存储完整,这个存储安全信息的64位长的数据有个专门的称谓:段描述符(Segment Descriptor)。16位的段寄存器是不够用的,为了解决这个问题。80X86CPU引入了两个新的寄存器:全局描述符表寄存器GDTR(48位)和局部描述符表寄存器LDTR(16位)。原理就是充分利用内存空间,实现机制如下: 


在操作系统启动后,GDTR指向一块内存区域,该区域的起始地址为GDTR的16到47位指示的地址,长度为GDTR的0到15位指示的长度。这块内存被称作全局描述符表(GDT),因为它存储了操作系统使用的代码段、数据段和堆栈段的全部信息;同时还存储了用户建立的执行任务的信息(局部描述符表LDT索引),该信息记录了各个任务内部代码段、数据段等的描述表的起始地址,可以理解成各个任务描述表的起始地址都存在这里,通过它可以定位所有任务的内存相关信息。 
在保护模式下,地址格式为XXXX:YYYYYYYY,XXXX表示段选择器,为DS、CS、FS等段寄存器;YYYYYYYY为偏移地址。我们要做的是通过XXXX确定描述符,然后找到基地址,加上偏移地址,进而得到有效的线性地址,此时才能定位内存单元。步骤如下: 
首先检查XXXX的第2位是否为0:为0表示描述符在GDT中,其位置由XXXX的高13位确定;不为0表示描述符在LDT中,要找LDT首先需要找到它的基址,而基址存储在GDT中,其位置由LDTR的高13位确定,得到LDT基址后,我们进一步通过XXXX的高13位在LDT中确定目标内存单元的基址,然后结合偏移地址就得到了线性地址。 
今天就讲到这里,下节将开始寻址方式的讲解。来源:www.guahai.com


本帖最后由 老A 于 2010-9-7 10:18 编辑 


本节主要讲解汇编中的操作数寻址。 
先来一张IDA反汇编的截图: 


我们看到汇编代码的形式为: 
指令 操作数1[可选],操作数2[可选] 
中括号([])内的可选,指的是操作数的个数取决于指令的类型:如果是单操作数指令,比如push指令,那么操作数2(下简称op2)是没有的;如果是双操作数指令,比如mov,两个操作数都有;如果是无操作数指令则后面什么也不跟。在进一步讲解之前,我先简单讲下mov指令,该指令的作用是把op2的值复制到op1。从…到…是一个具有方向的句式,我们以后把op1叫做目的操作数,op2则叫源操作数,因为是从op2到op1╮(╯_╰)╭。虽然下面是以mov指令作为讲解的对象,以下规则是可以拓展到所有指令的。 
毛主席教导我们:有的放矢,抓住老鼠的猫就是好猫!哎,抓老鼠不是邓爷爷教导的吗?汗…下面我的讲解和大部分教科书的方式有很大不同,但是我可以保证你看完之后获得的信息量>=教科书。相信我,没错的:)。 
首先思考一下:指令存在的目的是什么?CPU是做什么的?我们一般称CPU为中央处理器,处理什么呢?当然是二进制数据了,我偷偷告诉你,其实CPU只会加减法而已。这样说来,Intel也不是什么高科技公司嘛[龙芯大笑]。我们小学就学过加减法,不就是两个数相加减吗?是的,所谓寻址,就是告诉CPU做加减法的数存在哪里,仅此而已。前面已经讲过,计算机里面能存储数据的地方有:硬盘等外部存储设备、内存、高速缓存和寄存器。我们可以无视硬盘和高速缓存:前者不直接和CPU打交道,后者暂时不直接和我们打交道。这样的话就我们关心的就只有内存和寄存器了,那我们要做的就是搞明白在汇编里面是如何表示内存地址的.为什么不管寄存器寻址呢?因为寄存器属于CPU内部固件,所以CPU可以直接访问。就好比,你去别人家拜访的话首先要知道别人的地址;在自己家里的话,想去哪个房间直接去喽,当然不要地址了!那么我们下面主要讲的就是内存地址的表示,上节教程说过,32位CPU表示地址的格式为: 


第一个是段选择子,第二个是偏移地址[又叫有效地址]。至于得到这2个参数后如何寻址上节已经说过了。对于这2个参数在汇编语言中是如何给出的呢?比如我把op2复制到op1,按道理说,应该这样写:mov eax,ds:[1234H]。这句中的ds是数据段寄存器作为选择子,后面的[1234]表示偏移地址,记住一定要加中括号!不过,Intel的游戏规则是这么说的:你这么做,是可以的,但经过我们砖家的研究发现啊,大部分情况使用默认的段选择子更省事。说完这些,Inte在它的指令手册(参见:Vol1 3-29页)中给出了默认的段选择子的规则(下面的英语比高考阅读简单多了): 


由上表我们看到:代码段默认的选择子是CS,堆栈段默认的选择子是SS,数据段默认的选择子是DS,字符串默认的选择子是ES。返回来看最上面那张反汇编图,我们看下地址为00401004处的汇编代码:mov     esi, [esp+48h+hInstance]。中括号里面的是偏移地址(hInstance和48H一样,都是数值,在运行时由Windows系统传递具体数值进行初始化),但是没有指明选择子,由于是要把堆栈中的数据复制到ESI中,结合上面的规定,我们知道选择子是SS。所以还原一下这个语句应该这么写:mov     esi, ss:[esp+48h+hInstance]。有同学发问了:“你怎么知道复制的数据在堆栈呢?”能提出这个说明这位同学是认真思考了,我来回答一下:凡是看到中括号第一个是ESP或EBP,那么选择子就是SS,别忘了ESP可是叫做堆栈指针寄存器,那可不是盖的!再来看下地址为00401036处的汇编代码:call ds:LoadIconA。这句是呼叫函数LoadIconA载入图标,函数一般存放在代码段,但是这个函数存放的地址却是数据段,因为Intel规定的代码段默认的选择子是cs,所以此处必须指明为ds,CPU才知道此处以ds作为选择子。其它的情况如果是字符串指令,则是ES,否则都用DS。 
呵呵,我认为还算通俗易懂啊。今天先到这里。来源:www.guahai.com


本节主要讲解汇编中的指令。 
下载一个指令查看器,用于初期查询之用。在讲指令之前,我先简单说下汇编中有关变量定义的相关内容。 
一、标识符和表达式 
汇编中,标号、内存变量、子程序名和宏定义名等都叫做标识符,最多由31个字母、数字和特殊字符(?、@、_、$)等组成,不能以数字开头。汇编中是不区分大小写的。并且标识符不能和编译器保留关键字重名,如: 
正确的:msg、_Student、?iRand 
错误的:2boys、mov、eax 
这里需要注意的是,ABCH和0ABCH,前者是标识符,后者是16进制数。在实际编程中一般要求十六进制数以0或0x开头。 
1、内存变量定义 
一般我们常用的有字节(byte)、字(word)和双字(doubleword),浮点数在后面会涉及。定义变量的一般形式是: 
变量名 类型 表达式1,表达式2,……表达式n 
表达式个数取决于实际定义;变量名命名规则上面已经介绍;类型常用的有db(byte)、dw(word)、dd(doubleword)。各表达式之间用逗号隔开,最后一个表达式没有逗号;如果变量没有在定义时初始,表达式用问号(?)代替。例子如下: 
1)、定义一个名为iPlayerHP的单字变量,初始值为200 
iPlayer  dw 200 
2)、定义一个名为msgname的ASCII字符串变量,初始值为’I am Figo Yao.’ 
msgname  db ‘I am Figo Yao.’ 
3)、定义一个名为iTime的双字变量,初始值不确定 
iTime  dd ? 
4)、定义一个长度为100的,名为qqNUM的双字数组变量,初始值不确定 
qqNum  dd 100  dup(?) 
dup是关键字,用在重复定义的情形。 
其一般用法如下: 
次数  dup  (表达式1,表达式2,……表达式n) 
括号中是被重复的部分,如 
qqNum  dd 100  dup(10000,10010) 
这个表达式的结果是:qqNum数组长度是200,数值是10000和10010交替重复。即 
qqNum[200] = {10000,10010,10000,10010…10000,10010,10000,10010}; 
5)、结构类型定义 
其语法为: 
结构变量名  STRUC 
数据定义 
结构变量名  ENDS 
比如我们先来定义一个时间结构: 
stTime  struc 
iHour  db  0    //偏移量是0 
iMin     db  0    //偏移量是1 
iSec      db  0    //偏移量是2 
stTime  struc 
对于没有名字的结构体,也可以用偏移量来访问。比如我们初始化一个stTime的结构体(假设eax指向分配的地址)和一个保存秒的变量: 
TestTime    stTime <10,25,0> 
TestSec    db ? 
TestSec    equ TestTime.iSec 
我们看到取结构体分量使用的是点(.)加变量名。equ是等于的意思,属于关键字。和上面最后一句等价的语句是: 
TestHour    equ byte ptr [eax+2] 
ptr是指明数据长度,属于关键字,一般用法为: 
数据类型    ptr    地址 
操作的结果是指明操作数的地址和类型。因为我们第二种获取结构体变量的方法使用了偏移量,得到存放数据的地址后,还需要告诉CPU从这个地址读取什么类型的数据,因为我们的秒变量是字节,所以ptr前面的是byte。 
二、汇编指令 
首先规定一些代号以便于讲解: 
reg:寄存器 
mem:内存单元 
imm:立即数 
这些代号后面如果跟数字,则表示是多少bit,如:reg16表示16bit的寄存器;立即数的意思是直接的具体的数值,如0×1234H。 
下面的内容大体和教材分类一样: 
1、数据传输指令 
1)、mov指令(move) 
mov指令的格式如下: 
mov  op1,op2 
[mov   reg/mem,reg/mem/imm] 
执行的结果是op2的值被传送(复制)到op1。 
如: 
mov  eax,0×1234h 
mov  dword ptr [40995b],ecx 
mov  es,dx 


但是mov有几条规定需要注意: 
a、两个操作数类型必须相同,即同为db、dw或dd等类型。 
b、mov指令不能用于改变cs寄存器,否则会导致程序异常错误。如mov  cs,ax 是禁止的;但mov  ax,cs 是允许的。 
c、两个操作数不能同为下列段寄存器的组合:DS、ED、SS、FS、GS,如:mov  es,ds 是错误的用法,如果一定要把ds的数据传给es,可以如下操作: 
mov  ax,ds 
mov  es,ax 
d、两个操作数不能同为内存单元,如mov  [1234h],[2345h] 是错误的用法。 
e、指令指针EIP不能作为mov的操作数。 
2)、movsx/movzx(传送填充指令) 
2a)、movsx是符号填充指令(move with sign-extend) 
movsx  op1,op2 
一般op2的bit数比op1少,在mov中是禁止这么传送数据的,但是movsx可以,执行结果是op1低位由op2填充,op1剩下的高位由op2的符号位填充。 
如: 
movsx  eax,10H 
执行完后,eax的低五位为二进制10000,从第六个bit起的高位均填充1。 
2b)、movzx是零填充指令(move with zero-extend) 
与符号填充唯一的区别是使用0来填充高位。 
3)、交换指令xchg(Exchange) 
格式如下: 
xchg  reg/mem,reg/mem 
xchg  op1,op2 
执行结果是op1中存储原来op2的数据,op2中存储原来op1的数据。 
4)、取有效地址指令lea(load effective address) 
格式如下: 
lea  reg,mem 
lea  op1,op2 
执行结果是内存单元op2的有效地址(偏移地址)赋给op1。 
在汇编中还有一种获取有效地址的方法,使用offset伪指令,如: 
lea  eax,dword ptr [1234H]和mov  eax,offset  dword ptr [1234H]是等价的,但是后者的执行速度更快。不过当去堆栈处地址时候是不可以用offset的,因为offset在编译时实际上编译器会先求出变量地址,然后用地址作为立即数进而合成机器码;而lea是CPU的指令,是在程序运行时候求的,所以可以处理动态地址,如:lea  eax,dword ptr [ebp-5ch] 的情况就不可以用offset。 
5)、堆栈操作指令 
进出栈操作 
push  reg/mem/imm 
执行push指令后堆栈指针ESP依据进栈数据类型自动修改,如push  eax,因为eax是4个字节,所以push执行完后,esp=esp-4,[esp]<-eax。与之对应的是pop出栈指令。 
pusha/pushad 
执行该指令会依次把EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI存入堆栈,这些通用寄存器的值是执行指令前一瞬间的快照值,即要执行这条指令的一瞬间,我们把此时的通用寄存器的值记录下来,那么这条指令就是把我们记录的值按照上面顺序依次存入堆栈。pusha是16bit的版本,pushad是32bit的版本。与之对应的是popa和popad,这条指令是把刚才存储的快照依照和上面存储过程的逆序恢复。 
这节就暂时讲到这里,下次我们接着讲指令。来源:www.guahai.com


本节承接上节继续讲汇编中的指令。 
一、标志位操作指令 


1、进位CF标志操作指令 
清零标志位CLC:CF<-0 
置位标志位STC:CF<-1 
标志位取反CMC:CF<-NOT CF 
2、方向位DF操作指令 
清零方向位CLD:DF<-0 
置位方向位STD:DF<-1 
3、中断允许位IF操作指令 
清零CLI:IF<-0 
置位STI:IF<-1 
4、取标志位操作指令 
LAHF:AH<-EFlags低8位 
SAHF:EFlags低八位<-AH 
5、标志位堆栈操作指令 
PUSHF/PUSHFD:16bit/32bit标志位进栈 
POPF/POPFD:16bit/32bit标志位出栈 
二、算术运算指令 
1、加法指令 
1)、普通加法指令ADD 
ADD  reg/mem,reg/mem/imm 
受影响标志位:AF/CF/OF/PF/SF/ZF 
2)、带进位的加法ADC 
ADC  reg/mem,reg/mem/imm 
受影响标志位:AF/CF/OF/PF/SF/ZF 
该指令和1)中唯一不同的是除了执行1)中加法外还要加上CF。 
3)、加1指令INC 
INC  reg/mem 
受影响标志位:AF/OF/PF/SF/ZF 
4)、交换加法指令XADD 
XADD  reg/mem,reg 
该指令首先交换两个操作数的值,然再做加法,相当于以下两条指令: 
XCHG  reg/mem,reg 
ADD  reg.mem,reg 
2、减法指令[和加法指令相反] 
1)、普通减法SUB 
2)、带借位的减法SBB 
除了1)中减法还有减去CF 
3)、减1指令DEC 
4)、求补指令NEG 
NEG  reg/mem 
受影响标志位:AF/CF/OF/PF/SF/ZF 
执行结果:操作数=0-操作数 
3、乘法指令 
分为带符号乘法和无符号乘法,区别在于:数据的最高位是作为符号还是数值参与运算。 
1)、无符号乘法MUL 
MUL  reg/mem 
受影响标志位:CF/OF 
我们知道乘法要有乘数和被乘数,指令中只有一个操作数,所以我们必须知道第二个操作数在哪里。Intel处理的方式是,被乘数提前存在EAX中,然后与给出的乘数相乘,结果放在规定的寄存器中: 
乘数位数    隐含的被乘数    乘积存放位置[高-低]    举例 
8    AL    AX    MUL  CL 
16    AX    DX-AX    NUL  CX 
32    EAX    EDX-EAX    MUL  ECX 
2)、有符号乘法IMUL 
IMUL  reg/mem 
受影响标志位:CF/OF 
其操作数都作为有符号数相乘。 
4、除法指令 
1)无符号除法 
DIV  reg/mem 
不影响标志位 
被除数默认存放规则如下: 
除数位数    隐含的被除数    商    余数 
8    AX    AL    AH 
16    DX-AX    AX    DX 
32    EDX-EAX    EAX    EDX 
2)有符号除法 
IDIV  reg/mem 
受影响标志位:AF/CF/OF/PF/SF/ZF 
三、逻辑运算指令 
1、逻辑与AND指令 
AND  reg/mem,reg/mem/imm 
受影响的标志位:CF(0)/OF(0)/SF/PF/ZF 
执行过程:源操作数和目的操作数进行逻辑与运算,结果存放在目的操作数。 
2、逻辑或OR指令 
OR  reg/mem,reg/mem/imm 
受影响的标志位:CF(0)/OF(0)/SF/PF/ZF 
执行过程:源操作数和目的操作数进行逻辑或运算,结果存放在目的操作数。 
3、逻辑非NOT指令 
NOT  reg/mem 
受影响的标志位:无 
执行过程:把操作数按位取反。 
4、逻辑异与XOR指令 
XOR  reg/mem,reg/mem/imm 
受影响的标志位:CF(0)/OF(0)/SF/PF/ZF 
执行过程:源操作数和目的操作数进行逻辑异与运算,结果存放在目的操作数。 
异或就是两数相同则结果为0,反之为1。 
5、测试指令TEST 
TEST  reg/mem,reg/mem/imm 
受影响的标志位:SF/PF/ZF 
test类似and指令,但只是逻辑与进行测试,根据结果修改标志位,却不改变操作数的值。 
四、跳转指令 
1、无条件跳转指令 
JUMP  reg/mem/imm 
执行结果是跳转到指定地址,然后继续执行代码。按照Intel指令手册上的说法,jump一共有4中不同的跳法,这比起跳大神的神棍的跳法可少多了: 
1)、Near Jump 
这种跳法为啥叫near呢,因为它跳来跳去都是在一个代码段跳,所以又称作段内转移。这就好比你遛狗,总是不出你的县城,这就叫near遛:) 
2)、Short Jump 
Short Jump是Near Jump的一种,但是比Near还有Near,它跳转的范围是[-128,127]。这时候你可能只是在你的村子溜溜狗而已,太Short了。 
3)、Far Jump 
一看Far就是那么有气势,此时跳转的范围已经扩大到其他段,你连其他段都能跳,本段当然更可以了,但是要注意,此时你可以跳转到的目标代码的特权级别必须和当前代码相同:如果你是用户权限,你跳转到系统权限的代码就会出错。这时候你已经厌倦在小县城遛狗了,终于,你鼓起勇气,决定去丈母娘的县城遛狗╮(╯_╰)╭。 
4)、Task Switch 
这个跳转一下就从你当前任务的代码到别的任务里面执行代码了,可以说是跳的最远的。 
2、条件跳转指令 
顾名思义,得依据条件来跳转,在这里,条件指的是标志寄存器的一个或多个标志位,如果满足条件就跳转否则继续执行跳转指令下面的代码。在介绍指令的时候,首先说下助记符中字母的含义: 
E:Equal[相等] 
A:Above[大于] 
B:Below[小于] 
G:Greater Than[大于] 
L:Less Than[小于] 
N:Not[不] 
条件跳转大体上分为2类: 
直接标志位测试 
助记符    测试条件    跳转条件 
JC    CF=1    有进位 
JNC    CF=0    无进位 
JZ/JE    ZF=1    为0或相等 
JNZ/JNE    ZF=0    不为0或不等 
JS    SF=1    负数 
JNS    SF=0    正数 
JO    OF=1    溢出 
JNO    OF=0    无溢出 
JP/JPE    PF=1    偶数 
JNP/JPO    PF=0    奇数 
间接标志位测试 
类别    助记符    测试条件    跳转条件 
无符号数    JA/JNBE 
JAE/JNB 
JB/JNAE 
JBE/JNA 
    CF=0且ZF=0 
CF=0 
CF=1 
CF=1或ZF=1 
    大于/大于等于 
大于等于 
小于/小于等于 
小于等于 


有符号数    JG/JNLE 
JGE/JNL 
JL/JNGE 
JLE/JNG 
    ZF=0且SF=OF 
SF=OF 
SF!=OF 
SF!=OF或ZF=1 
    大于/大于等于 
大于等于 
小于/小于等于 
小于等于 


以上是根据Intel指令手册(3-542 vol.2A)整理,有些教材的跳转条件是错误的。 
五、其他指令 
本教程不可能把所有指令都介绍完整,有些指令需要在实践的过程中自己查阅手册,学习一门技能从来都不是一蹴而就的。 
1、nop指令 
nop是空指令,就是占位,除此没有任何作用。 
2、call指令 
call  reg/mem/imm 
call指令执行的过程是,首先把当前EIP或CS加EIP压入堆栈,然后跳转到call后操作数指定地址执行代码。手册里介绍了四种跳转,这里只讲下Near Call和Far Call。前者call呼叫的函数在同一代码段,此时只需把EIP压栈即可;而后者call呼叫的是不同段,需要把当前段的选择子和EIP均压入栈。 
3、ret/retn/retf 
ret  reg/mem/imm[可选] 
retn是近返回;retf是远返回。 
至此汇编语言的基本内容就介绍完了。来源:www.guahai.com


本节我们以红警2[共和国之辉]为上手的第一个游戏,首先是作为复习,其次是引入新内容:马上我将带领大家学习Win32编程。时间过得真快,希望你这段时间也有一定的进步。 
首先给出源码和Bin文件,这次有控制台和Win32两个版本,阅读源码时候最好结合MSDN。 
Win32版本的截图: 




控制台版本的截图: 


一个小Tip:红警窗口模式,红警安装完后有一个叫做Ra2.exe的文件,把它的快捷键发送到桌面,然后右键->属性,在路径名最后输入-win启动参数: 


所有源码是在VC6上编写并编译的。下载完文件的读者很快会发现控制台的程序比Win32还大,这两个执行文件我没有做任何处理。前面的章节我们分析过一个文字性质的游戏Demo,这里涉及的大部分原理性知识是一样的。重复的知识我就不深入讲解了。 
在使用CheatEngine定位需要修改的地址后,我们就开始动手了。这次不必像上次那样还得自己输入游戏进程的PID,我们交给程序来做。 
源码还是比较简单的,有疑问的话看看前面的章节的那次讲解。如果还不明白,可以留言。下次开始我就要进入Windows应用程序设计的讲解了。来源:www.guahai.com





















原创粉丝点击