汇编基础
来源:互联网 发布:滚屏截图 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
本篇起我们开始讲解汇编语言,最终目标是能读懂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
- 汇编学习--汇编基础
- 汇编基础
- 汇编基础
- 汇编基础
- 汇编基础
- 汇编基础
- 汇编基础
- 汇编基础
- 汇编基础
- 汇编基础
- 汇编基础
- 汇编基础
- 汇编基础
- 汇编基础
- 汇编基础的基础
- 汇编基础-栈基础
- 汇编基础--cmp汇编指令
- 汇编基础--CMP汇编指令
- VS2010运行慢之 解决方法
- 关于基于web浏览器实现分布式计算构想
- 猜数字游戏——复杂版
- 3DS Max plugin 编程三,utility
- Mysql 关键字 保留字
- 汇编基础
- 购物车实现基本
- 在winxp下使用windbg的配置问题
- FreeMarker系列学习笔记(2)
- tyvj P1014 惩罚游戏 区间动态规划
- javascript this 和执行上下文 之大不同
- Linux 下的截图工具 scrot
- Spring事务配置的五种方式
- FreeMarker系列学习笔记(1)