学习汇编语言

104

我决定学习汇编语言。这样做的主要原因是可以理解反汇编代码并可能能够编写更高效的代码部分(例如,通过C++),做一些像代码洞等的事情。我看到有无数不同版本的汇编语言,那么,针对我的目的,应该从哪里开始?应该学习什么类型的汇编语言?我想通过先编写一些简单程序(例如计算器)来学习,但目标本身将是为了熟悉它,以便我能理解IDA Pro显示的代码。

我正在使用Windows操作系统(如果有任何区别的话)。

编辑:所以,似乎每个人都指向MASM。虽然我明白它具有高级功能,非常适用于汇编代码程序员,但这不是我要寻找的东西。它似乎有if、invoke等指令,在流行的反汇编器(如IDA)中没有显示。因此,如果可能的话,我想听听任何用ASM进行我所询问的目的(在IDA中阅读反汇编的exe代码)的人的意见,而不仅仅是“一般”的汇编程序员。

编辑:好的。我已经在学习汇编语言。我正在学习MASM,不使用我不需要的高级功能。我现在正在尝试在C++的__asm指令中测试我的代码,这样我就可以比使用MASM从头开始编写所有内容更快地尝试各种东西。


类似于https://dev59.com/53M_5IYBdhLWcg3wfTO7的问题。 - TrueWill
是的,我也在阅读那篇文章。但我的问题更加“专注”,我想说。 - devoured elysium
如果你使用的是Windows系统,目标(即处理器和指令集)就是x86或x86-64。除非你使用另一台机器、MCU板或者使用模拟器。所以,问题是应该使用哪个汇编器?还是你真正想问的是要针对哪种架构?个人而言,我喜欢m68k系列芯片上漂亮的正交指令集,可惜啊。 - dmckee --- ex-moderator kitten
2
似乎有if、invoke等指令 - 这些是宏(“MASM”中的“M”),即使汇编器支持它们,您也不必使用它们。 - ChrisW
3
给这个问题第65个赞是一个艰难的决定,因为64是一个如此美丽的数字。 - 735Tesla
如果您正在使用微软的汇编器,自从它从5.x版本更改为6.x版本(在MSDOS时代),它就被称为ML而不是MASM。6.x版本是添加了高级内容,如.if ...的版本。Microsoft Visual C / C ++和Visual Studio包括ML.EXE和ML64.EXE(64位版本)。如果您创建一个项目并将.asm文件添加到该项目中,则Visual Studio应该创建一个自定义构建步骤来调用ML或ML64(它会提示您询问是否可以创建自定义构建步骤)。 - rcgldr
22个回答

46
我已经做了很多次,并且还在继续。在你主要的目标是阅读而不是编写汇编语言时,我觉得这适用。
编写自己的反汇编器。不是为了制作下一个最伟大的反汇编器,这只是为了你自己。目标是学习指令集。无论是在新平台上学习汇编语言,还是记忆曾经了解的平台的汇编语言。从只有几行代码开始,例如添加寄存器,在二进制输出和在输入端添加越来越复杂的指令之间来回反汇编,您可以:
1)学习特定处理器的指令集
2)了解如何在该处理器上编写汇编代码的微妙之处,以便您可以在每个指令中扭动每个操作码位
3)您比使用该指令集来谋生的大多数工程师更好地了解指令集
在您的情况下,有几个问题,我通常建议从ARM指令集开始,因为今天出货的基于ARM的产品比其他任何产品(包括x86计算机)都要多。但是可能性是,您现在正在使用ARM并且不知道足够的汇编语言来编写启动代码或其他例程,了解ARM可能或可能不帮助您所要做的事情。第二个更重要的原因是首选ARM,因为指令长度是固定大小和对齐的。反汇编可变长度指令像x86可以成为你的噩梦作为你的第一个项目,而这里的目标是学习指令集而不是创建研究项目。第三,ARM是一种做得很好的指令集,寄存器是平等创建的,并且没有单独的特殊微妙之处。
因此,您将必须弄清楚要从哪个处理器开始。我建议首先使用msp430或ARM,然后是ARM第一或第二,然后是混乱的x86。无论使用什么平台,任何值得使用的平台都有来自供应商的数据手册或程序员参考手册,其中包括指令集以及操作码(机器语言的位和字节)的编码。为了学习编译器的工作原理和如何编写编译器不必努力的代码,了解几个指令集并看到相同高级代码在每个指令集上使用每个编译器的实现方式,每个优化设置都是有好处的。您不想进行代码优化,只是发现您已经使其更适合一个编译器/平台,但对于每个其他编译器/平台都更差。希望能对可变长度指令集进行反汇编,而不是像ARM一样从开头开始每四个字节线性地遍历内存,或者像MSP430一样每两个字节进行遍历(虽然MSP430具有可变长度指令,但如果您从中断向量表的入口点开始线性地遍历内存,则仍可以通过)。 对于可变长度指令,您需要根据向量表或有关处理器引导方式的知识找到一个入口点,并按执行顺序跟踪代码。 您必须完全解码每个指令以知道使用了多少字节,然后如果该指令不是无条件分支,则假设该指令之后的下一个字节是另一个指令。 您还必须存储所有可能的分支地址,并假定这些是更多指令的起始字节地址。 我曾经成功过一次,通过二进制文件进行了多次遍历。 从入口点开始,我将该字节标记为指令的起始,然后线性地解码内存,直到遇到无条件分支为止。 所有分支目标都被标记为指令的起始地址。 我通过二进制文件进行了多次遍历,直到没有发现新的分支目标。 如果在任何时候找到例如3个字节的指令,但由于某种原因您已经将第二个字节标记为指令的起始,则会出现问题。 如果代码是由高级编译器生成的,则不应发生这种情况,除非编译器正在执行某些恶意操作;如果代码有手写汇编程序(例如旧的街机游戏),那么可能会有永远无法发生的条件分支,例如r0 = 0后跳转到非零位置。 您可能需要手动从二进制文件中删除这些操作以继续进行。 对于您的即时目标,我假设您将在x86上工作,我建议使用gcc工具,mingw32是在Windows上使用gcc工具的简单方法,如果您的目标是x86。 如果不是,则mingw32加上msys是从binutils和gcc源代码生成交叉编译器的绝佳平台(通常非常容易)。 mingw32比cygwin更具优势,例如程序速度显着更快,而且您避免了cygwin dll地狱。 gcc和binutils使您可以使用C或汇编语言编写代码并对其进行反汇编,并且有更多的网页可以教您如何实现其中任何一个或全部三个。 如果您将使用可变长度指令集进行此操作,则强烈建议您使用包含反汇编程序的工具集。 例如,针对x86的第三方反汇编器将是一个具有挑战性的工具,因为您永远不知道它是否已正确反汇编。 其中一些还取决于操作系统,目标是将模块编译为包含标记指令和数据的二进制格式,以便反汇编程序可以做出更准确的工作。 对于此主要目标的另一个选择是拥有一个可以直接编译到汇编语言的工具以供您检查,然后希望它在编译到二进制格式时创建相同的指令。简短回答您的问题:编写一个反汇编器来学习指令集。我建议从类似ARM的RISC架构开始,这种架构相对容易学习。一旦您掌握了一个指令集,其他指令集都会变得更加容易学习,通常在几个小时内就可以掌握。通过参考手册和语法,第三个指令集后,您甚至可以立即开始编写代码。值得使用的所有处理器都有描述指令的数据手册或参考手册。学习RISC处理器(例如ARM)和CISC处理器(例如x86),以便了解它们之间的差异,例如必须使用寄存器进行所有操作或者能够直接在内存上执行操作等。在调整高级代码时,为多个处理器进行编译并比较输出结果。您将学到的最重要的是,无论高级代码编写得多好,编译器的质量和所做的优化选择都会对实际指令产生巨大影响。我建议使用llvm和gcc(与binutils一起),它们都具有跨平台和多目标功能,并且都具有优化器。此外,它们都是免费的,您可以轻松地从源代码中构建针对各种目标处理器的交叉编译器。

1
谢谢回复。但是我甚至不知道如何编写反汇编器。 - devoured elysium
9
"编写自己的反汇编器" - 我同意,这是我学习它的最佳方式。("但我甚至不知道如何编写反汇编器"是怎么回事?)哈哈。 - slashmais
我跟你一起去!刚买了一台MSP430和一本相关的书籍... :) - Pepe
1
我有一些 msp430 的示例 http://github.com/dwelch67/,以及一些指令集模拟器,可用于实验和学习汇编等。 - old_timer
我非常、非常喜欢这个想法。 - Millie Smith
说实话,我不理解你的回答。也许我很蠢,我不会争辩这一点,但我真的很想知道如何编写反汇编器?我认为你和我们之间存在很大的差距。你怎么能通过制作反汇编器来学习语言?你甚至是从哪里获取了制作反汇编器的知识呢? - ma1169

39

MASM32 开始学习,然后再看看 FASM。但是你会发现学 MASM 很有趣。


1
我听说过MASM。如果我没记错的话,它有很多“高级”功能,在反汇编的代码中是看不到的。如果可以的话,我想用与大多数反汇编器输出的代码完全相同的东西编程。 - devoured elysium
1
这基本上就像编写操作码一样,这并没有什么意义。学习MASM32将帮助您了解代码在调试器中的外观。您可能还想查看OllyDbg:http://www.ollydbg.de/ - Noon Silk
7
你不懂汇编语言,需要学习它。操作码是一个数字,调试器会尝试将操作码转换为相应的指令(有时很难)。你需要了解基本的指令,学习MASM可以帮助你实现这一点。除此之外,无需再多说。 - Noon Silk
5
你不必使用所有的MASM功能,只因为它们存在;如果你认为这样做会学到更多,你可以让事情变得难以阅读。 - JasonTrue
3
MASM,带着它的怪癖、漏洞和所谓的高级特性,比我能想到的任何东西都更容易让汇编程序员——无论是初学者还是专家——感到困惑。 - I. J. Kennedy
显示剩余4条评论

32

手写汇编语言代码和编译器生成的汇编语言代码在高层次上看起来经常有很大不同。当然,程序的内部结构会非常相似(毕竟,只有那么多种不同的方法可以编码 a = b + c),但是当你尝试逆向工程时,这些都不是问题。编译器将为即使是简单的可执行文件添加大量样板代码:上一次我比较,GCC编译的“Hello World”大约是4kB,在汇编中手写则只有100字节左右。在Windows上更糟糕:上一次我比较(尽管这是在上个世纪),我能让我的Windows编译器生成的最小“Hello World”是52kB!通常这些样板代码只执行一次,如果有的话,所以它并不太影响程序速度--就像我上面说的,程序的核心部分,大部分执行时间所花费的部分,无论是编译还是手写,通常都是相似的。

总之,这意味着专业的汇编程序员和专业的反汇编程序员是两个不同的专业。通常他们被认为是同一个人,但它们实际上是不同的,学习如何成为优秀的汇编编码者对于学习逆向工程没有太大帮助。

你需要做的是从IntelAMD下载IA-32和AMD64(两者一起涵盖)架构手册,并查阅关于指令和操作码的早期章节。可以阅读一两篇汇编语言的教程,以了解汇编语言的基础知识。然后选择一个感兴趣的小型示例程序并将其反汇编:逐步跟踪其控制流程并尝试理解它在做什么。看看是否可以对其进行修补以执行其他功能。然后再尝试另一个程序,并重复此过程,直到你足够自信去实现更有用的目标。你可能会对“破解挑战”感兴趣,这是由逆向工程社区制作的挑战,供有兴趣的人尝试并希望在此过程中学到东西。这些挑战的难度从基础(从这里开始!)到不可能都有。
最重要的是你只需要练习。就像其他许多学科一样,对于逆向工程,练习使人更加熟练...或者至少是更好

我知道当你使用高级语言编译任何东西时,会产生很多“垃圾”代码,如果直接用汇编语言编写,则不需要这些代码。我也理解专业汇编程序员和专业反汇编程序员之间的区别。但几乎所有其他事情都可以说同样的话。 - devoured elysium
3
我的担忧是,虽然理论上我可以阅读论文并了解它们的含义,但在我开始自己写东西之前,我不相信我会真正理解它。你说我可以通过更改代码的小部分来入手,但要做到这一点,我必须首先知道IDA Pro(例如)使用的汇编语言的“风味”。 - devoured elysium
另外,MSVC++使用什么来进行内联汇编代码?MASM? - devoured elysium

14
我会与大多数答案背道而驰,并推荐 Knuth 的 MMIX 变种 MIPS RISC 架构。虽然它不像 x86 或 ARM 汇编语言那样实用(这些在现实工作中也不是很关键 …;-),但它将为您解锁 Knuth 最新版本的算法和数据结构深度低级理解最伟大的杰作 -- TAOCP,“计算机程序设计艺术”。我引用的两个 URL 的链接是开始探索这种可能性的好方法!

11

(我不知道你是否和我一样对汇编感到兴奋)

一款用于实验汇编的简单工具已经安装在您的电脑上。

转到“开始”菜单->运行,然后键入 debug

debug (命令)

debug 是 DOS、MS-DOS、OS/2 和 Microsoft Windows(仅 x86 版本,不是 x64)中的一个命令,它运行程序 debug.exe(或早期版本的 DOS 中的 DEBUG.COM)。Debug 可以作为汇编器、反汇编器或十六进制转储程序,允许用户交互式地检查内存内容(以汇编语言、十六进制或 ASCII 表示),进行修改,并选择性地执行 COM、EXE 和其他文件类型。它还有几个子命令,可用于访问特定的磁盘扇区、I/O 端口和内存地址。MS-DOS Debug 运行在 16 位进程级别,因此仅限于 16 位计算机程序。FreeDOS Debug 有一个支持 32 位 DPMI 程序的 "DEBUGX" 版本。

教程:


如果你想了解在IDA Pro(或OllyDbg)中看到的代码,你需要学习编译代码的结构。我推荐这本书Reversing: Secrets of Reverse Engineering

当我开始学习汇编语言时(15年前),我尝试使用debug进行了几周的实验。
请注意,debug在基本机器级别上工作,没有高级汇编语言命令。
现在来看一个简单的例子:

键入下面的程序,然后给出a以开始编写汇编代码,最后给出g来运行它。 alt text (INT 21如果AH寄存器设置为2,则在屏幕上显示存储在DL寄存器中的ASCII字符--INT 20终止程序)

2
@ericp,您不必按下ctrl-c。例如,您可以键入a和[enter]以开始编写汇编代码。如果您连续按两次[enter],则退出汇编模式。键入g和[enter]以运行它(默认情况下偏移量为100)。 - Nick Dandoulakis
1
@user,它只是写下了这个网站的名称 :-) - Nick Dandoulakis
@JanusTroelsen 那些数字(53、74、61等)是'S' 't' 'a'的ASCII代码...每个Int21调用一次只打印一个字符!这就是为什么汇编不比其他语言更快的原因 :) - doug65536
@NickDandoulakis,链接失效了...... - Pacerier
调试是一种绝对史诗般的工具。我在90年代还是一个贫穷的中学生时就用它来帮助理解PC架构。虽然我怀疑它现在有多少用处,但即使在32位出现后,它也变得严重受限。一旦你超越了一些简单的Hello World调试,你需要继续前进。我认为windbg/ntsd是调试的继承者。 - David Betz
显示剩余2条评论

8
我发现黑客攻防艺术是一个有趣且实用的入门读物... 不能说我曾经直接应用过这些知识,但那并不是我读它的原因。它可以让你更好地理解代码编译后的指令,这在理解一些微妙的错误时有时会很有用。

不要被书名吓到。大部分的前半部分都是以Eric Raymond的意义上的“黑客”为主题:创造性、出人意料、几乎是偷偷摸摸的方法来解决棘手的问题。我(也许你)对安全方面不太感兴趣。


7
我不建议一开始就学习汇编语言编写程序,尤其是在使用Windows的x86架构下。因为有很多奇怪的特殊情况,学习它们会有点无意义。例如,许多指令假设你正在操作一个未明确命名的寄存器,并且其他指令在某些寄存器上工作,但在其他寄存器上则不行。
我建议先了解所需体系结构的基础知识,然后直接跳进去,尝试理解编译器的输出。准备好英特尔手册,然后深入研究编译器的输出。将感兴趣的代码隔离到一个小函数中,这样您可以确保完全理解整个过程。
我认为基础知识包括:
- 寄存器:有多少个,它们的名称和大小是什么? - 操作数顺序:`add eax,ebx`表示“将ebx添加到eax并将结果存储在eax中”。 - 浮点数单元(FPU):了解浮点数堆栈的基础知识以及如何转换成/从fp。 - 寻址方式:[base + offset * multiplier],但乘数只能是1、2或4(或者也可能是8)。 - 调用约定:如何将参数传递给函数?
很多时候,编译器发出的指令可能会让人感到惊讶。将其视为一个谜题,尝试弄清楚为什么编译器认为这是个好主意。这将教给您很多东西。
阅读Agner Fog手册(特别是指令列表),也可能有所帮助。它会告诉你每个指令大致的成本,虽然在现代处理器上直接量化这一点比较困难。但它会帮助解释为什么编译器不得不费尽心思避免发出`idiv`指令。
我唯一的建议是,在可以选择时始终使用英特尔语法,而不是AT&T语法。以前我对此持中立态度,直到有一天我意识到两者之间有些指令完全不同(例如,在AT&T语法中,`movslq`是`movsxd`)。由于所有手册都是使用英特尔语法编写的,因此请坚持使用它。
祝你好运!

3

我开始学习MIPS,这是一种非常紧凑的32位架构。它是一种精简指令集,但这正是使初学者容易掌握的原因。即使您不会被复杂性淹没,您仍然可以理解汇编如何工作。您甚至可以下载一个好用的小型IDE,以便编译您的MIPS代码:点击此处。 一旦您掌握了它,我认为转向更复杂的架构将更容易。至少那是我想的 :) 在这一点上,您将拥有内存分配和管理、逻辑流程、调试、测试等基本知识。


3
我最近参加了一门计算机系统课程。其中一个主题是汇编语言作为与硬件通信的工具。
对我而言,如果不理解计算机系统的细节,就无法完全掌握汇编语言的知识。理解这一点,可以更好地理解为什么在一个处理器架构上的汇编指令很好,但在另一个架构上却很糟糕。
基于此,我倾向于推荐我的课本: 《计算机系统:程序员的视角》
它确实涵盖了x86汇编,但这本书的范围远不止如此。它涵盖了处理器流水线和内存作为高速缓存、虚拟内存系统等内容。所有这些都可能影响如何针对给定特性优化汇编代码。 Computer Systems:A programmer's perspective (来源:cmu.edu

3
使用调试工具是一个有趣的建议,可以用许多巧妙的技巧来完成。然而,对于现代操作系统来说,学习16位汇编可能会稍微不太有用。相反,考虑使用ntsd.exe。它内置于Windows XP中(不幸的是,在Server 2003及以上版本中被取消了),因此它是一个方便的学习工具,因为它非常广泛地可用。
话虽如此,XP中的原始版本存在许多错误。如果你真的想使用它(或者cdb、windbg,它们本质上是具有相同命令语法和调试后端的不同接口),你应该安装免费的windows debugging tools软件包。
该软件包中包含的debugger.chm文件在尝试弄清楚不寻常的语法时特别有用。
ntsd的好处在于你可以将其弹出到任何你附近的XP机器上,并用它来进行汇编或反汇编。它是一个极好的X86汇编学习工具。例如(使用cdb,因为它是inline在dos提示符中,否则完全相同):(由于它们与主题无关,所以跳过符号错误--还有,我希望这种格式有效,这是我的第一篇文章)。
C:\Documents and Settings\User>cdb calc

Microsoft (R) Windows Debugger Version 6.10.0003.233 X86
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: calc
Symbol search path is: *** Invalid ***
Executable search path is:
ModLoad: 01000000 0101f000   calc.exe
ModLoad: 7c900000 7c9b2000   ntdll.dll
ModLoad: 7c800000 7c8f6000   C:\WINDOWS\system32\kernel32.dll
ModLoad: 7c9c0000 7d1d7000   C:\WINDOWS\system32\SHELL32.dll
ModLoad: 77dd0000 77e6b000   C:\WINDOWS\system32\ADVAPI32.dll
ModLoad: 77e70000 77f02000   C:\WINDOWS\system32\RPCRT4.dll
ModLoad: 77fe0000 77ff1000   C:\WINDOWS\system32\Secur32.dll
ModLoad: 77f10000 77f59000   C:\WINDOWS\system32\GDI32.dll
ModLoad: 7e410000 7e4a1000   C:\WINDOWS\system32\USER32.dll
ModLoad: 77c10000 77c68000   C:\WINDOWS\system32\msvcrt.dll
ModLoad: 77f60000 77fd6000   C:\WINDOWS\system32\SHLWAPI.dll
(f2c.208): Break instruction exception - code 80000003 (first chance)
eax=001a1eb4 ebx=7ffd6000 ecx=00000007 edx=00000080 esi=001a1f48 edi=001a1eb4
eip=7c90120e esp=0007fb20 ebp=0007fc94 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!DbgBreakPoint:
7c90120e cc              int     3
0:000> r eax
eax=001a1eb4
0:000> r eax=0
0:000> a eip
7c90120e add eax,0x100
7c901213
0:000> u eip
ntdll!DbgBreakPoint:
7c90120e 0500010000      add     eax,100h
7c901213 c3              ret
7c901214 8bff            mov     edi,edi
7c901216 8b442404        mov     eax,dword ptr [esp+4]
7c90121a cc              int     3
7c90121b c20400          ret     4
ntdll!NtCurrentTeb:
7c90121e 64a118000000    mov     eax,dword ptr fs:[00000018h]
7c901224 c3              ret
0:000> t
eax=00000100 ebx=7ffd6000 ecx=00000007 edx=00000080 esi=001a1f48 edi=001a1eb4
eip=7c901213 esp=0007fb20 ebp=0007fc94 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
ntdll!DbgUserBreakPoint+0x1:
7c901213 c3              ret
0:000>`

此外,在您使用IDA时,请务必查看Chris Eagle的《IDA Pro Book》(未链接,因为StackOverflow不允许我在第一篇帖子中发布两个以上的链接)。这是目前最好的参考资料。

1
Chris Eagle的书很棒,必须要为Sk3wl of r00t加点爱心 ;) - mrduclaw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接