本文讲述如何用半天时间学会一门汇编的诀窍。在学习汇编过程,最好用Visual Studio调试,打开汇编模式,把栈视图和寄存器视图都打开。函数调用使用
cdecl
,在调试过程中使用汇编单步。
很多程序员都觉得汇编是可怕的编程语言,感觉很难学,繁多的指令,各种寄存器,寻址方式和CPU机制紧密相关,一切都让人望而却步。其实,汇编相对众多编程语言来说,是一门非常简单的语言:它没有奇技淫巧式的语法,也没有各种全家桶式的框架。它之所以显得非常难掌握的原因:
但在现实生活中,还是有不少地方用到汇编语言,除了搞嵌入式之外,在C/C++,OC之类的语言,在定位程序崩溃,内存泄露,逆向破解,漏洞挖掘和分析,恶意软件分析,都会用到。
所以,还是需要学一下汇编的。如何学呢?重要是把它和程序员平时面临的问题和熟识的语言建立一种联系。这和学数理化差不多,数理化学得好的人,基本上都会把抽象思维和现实世界建立某种联系。
在所有的编程语言中,这三样东西基本上是不可或缺的:
所以,重要是建立这三样东西在高级编程语言C/C++和汇编的对应关系。对于什么指令,寄存器,寻址方式和CPU机制,就先不管。
在高级编程语言里,函数的参数传递是通过变量或数值,返回值是通过变量或数值。那么在汇编里呢?在汇编里,参数传递和返回结果叫做调用约定。
调用约定传递参数和返回值,是通过寄存器和栈来完成的。那么,就要看传递参数用到什么寄存器,返回值用到什么样的寄存器。由于寄存器数量有限,就演变成这些问题:
在x86
下,在cdecl
调用方式,基本上,参数都是通过栈来传递,返回值是通过eax
传递,栈是由esp来控制,而this指针是通过ecx
(windows下)或栈(Linux)。函数桢用ebp
指向。返回地址在栈上。
在mips
下,参数都是通过a0-a7
传递的,多余的则放在栈上,通过sp
来指向,而返回值往往一般只通过v0
返回。this指针一般作为第一个参数用a0
传递。函数桢用fp
指向。返回地址放在ra
。
在sparc
下,则会比较奇怪。传递时是通过o0-o6
来传递,但在函数执行时则从i0-i6
来取,当然超过是在放在栈上。而返回值则通过i0
传递,调用者则从o0
来取。栈是通过sp
指向。函数桢用fp
指向,返回地址在i7
。
在iOS
下,参数是通过x0-x3
传递,返回值也是通过x0
。由于没有进行调试,只是在IDA进行逆向,所以其它不清楚。
这上面是我曾经搞过的CPU平台,其中x86
和sparc
是08-10年时,mips
是11年-12年接触的。iOS
是在2020年搞了一天,只是为了看看jailbreak
反检测机制。
从上面来看,可以需要了解的寄存器少了很多,而且需要了解的寄存器都是有关联的。而且这些知识点可以这样掌握:
return 1+1
的操作,了解返回值是放在哪个寄存器的。this
指针如何传递。本人的coredump
系列第三章就是这个思路。详情请见开发目录
无论高级语言是怎样的,它编译成二进制文件后,它的执行逻辑应该是不变的。也就是说,顺序执行,在机器码层面还是顺序执行; 条件执行在机器码层面还是条件执行;循环执行在机器码层面还是循环执行;函数调用在机器码层面还是函数调用(不优化,不内联的情况下)。程序的执行顺序就构成了程序的骨架,也就是说,由于汇编和机器码是一一对应的,在汇编中,只要找到if/else/switch/continue/break/while/do/for
之类以及函数调用的对应指令或特征,就可以把汇编和高级语言对应起来。
在x86
下,break, while(1), for( ;; ), continue
对应于jmp
,区别在于break
跳转的地址是向后,if/else/switch/for/do/while
对应于一些jnz, jz, jne, je, jnb, jbe,jg
之类的指令。函数调用是call
,函数返回是ret
。
在mips
下,break, while(1), for( ;; ), continue
对应于j,jr
,区别在于break
跳转的地址是向后, ``if/else/switch/for/do/while对应于
bne,beq,函数调用对应
jal,返回对应于
jr $ra`。
在sparc
下,break, while(1), for( ;; ), continue
对应于ba
,区别在于break
跳转的地址是向后, if/else/switch/for/do/while
对应于bne,be,ble,bg,bge
之类,函数调用对应call
,返回对应于jmpl
。
对这些平台来说,只要掌握上面的指令,就可以在函数里,把几万行的汇编代码分成一小块一小块来分析,每小块的其它指令查手册就行了。
本人的coredump
系列第四章也是这个思路,详情请见开发目录
从上面可以看到要学习汇编的思路:无非先把高级语言的函数和汇编的函数对应起来,再把高级语言函数里的执行顺序和汇编的指令对应起来,把函数分成一小块一小块代码段来分析。那么,对这些代码段的指令,基本查手册就行了。
那么,这些代码段具体做了什么事呢?或者说,操作了哪些数据呢?因为只是从汇编的角度来说,很多时候只能管中窥豹,看了之后不明其然,根本不知道是操作什么数据。
所以,就需要看一下各种数据结构在汇编里的操作特征。基本上,C/C++语言本身的数据结构也只是如下几种:
编写程序分别操作这些数据结构类型,看看它们在汇编里的表现是如何。只要掌握了它们的特征,那么即使只看汇编代码,也知道是在处理什么类型的数据。
本人coredump
系列的第五,六章就是这种思路,详情请见开发目录
暗号:9138e