今天想说说汇编语言。这东西听着离我们很远,其实很多搞安全的人都得用它。我最近因为好奇逆向分析,就去翻了一些资料,发现这玩意儿是绕不过去的坎。
说实话,开始看不懂。全是mov、add、jmp这种词,还有EAX、ECX这种寄存器名字,像是外星语。但后来明白,它其实就是机器能直接听懂的语言的“翻译版”。电脑只认0和1,比如加法可能是01000000这种码,人记不住。于是就用了助记符,像ADD、SUB,看着顺眼多了。
以前程序员真用纸带打孔写程序,一个孔代表1,没孔是0。累不说,改一行代码得重打一整条。后来有了汇编,至少能用文字写了。虽然还是得懂硬件,但总比二进制友好。而且它跟CPU结构绑得特别紧,X86和ARM的汇编完全不一样,换个平台代码就跑不了。
我在网上看个例子,算2的4次方。用高级语言就是pow(2, 4)或直接写16。但在汇编里,得一步步来。有人写了四条指令:先mov ax,2 把2放进寄存器ax,然后三次 add ax,ax,每次把ax自己加自己,结果就是16。没有乘法,全靠加,但电脑算这个飞快。
寄存器是关键。CPU里有几小块高速存储,叫寄存器,比如EAX、EBX。它们比内存快得多。程序运行时,数据先从内存搬到寄存器,CPU才能处理。比如MOV指令就是搬数据,ADD是计算,JMP是跳转到别的位置继续执行。
逆向分析这行,汇编是基本功。你想知道一个软件到底在干啥,尤其是病毒或者破解软件,就得把它拆开看。但软件发布出来都是编译好的二进制文件,看不到源码。这时候就得反汇编,把01代码变回汇编语言,一行行看它做了啥。
比如有个程序疑似盗号,你扔进IDA或者OD这类工具,它能显示出汇编代码。你从头跟一遍,可能发现它在偷偷读取剪贴板,或者连接某个奇怪的IP地址。这时候你就知道它不对劲。查bug也一样,有时候程序崩溃,日志没用,就得用调试器单步执行,看哪条指令出的问题。
静态分析和动态调试是两个套路。静态就是不动它,光看代码结构,适合分析病毒,免得它真跑了。动态是真运行,在旁边盯着,随时暂停、改数据。OD在动态这块很牛,可以下断点,比如bp MessageBoxA,只要程序想弹窗,立马停住,让你看上下文。
还有栈和堆的区别。栈是自动管理的,函数调用完了就清掉。堆是手动申请释放的,搞不好就内存泄漏。汇编里push和pop就是操作栈的。每次调函数,参数和返回地址都压进栈,函数执行完再弹出来。看反汇编的时候,esp、ebp这几个寄存器的变动特别重要,能帮你理清函数调用关系。
大小端也得注意。数据存内存顺序不同,X86一般是小端,低位字节放低地址。比如0x12345678,存进去是78-56-34-12这个顺序。如果你读内存看到一串数字倒着来,别懵,那是正常的。
PDB文件是调试用的。微软的编译器会生成这个,存了源码行号、变量名这些信息。有它的话,调试器能把汇编和原代码对应起来,省事不少。但发布软件通常不带PDB,所以逆向时大多只能看到光秃秃的汇编。
有时候编译器还会优化代码,比如把变量直接塞寄存器,不放内存。这样你在内存里找不到它的值。得在VS里关掉优化才能调试清楚。还有像MOVSX和MOVZX,是数据扩展指令,一个带符号延拓,一个补零,处理不同类型数据转换用的。
标志寄存器也挺烦。ZF、SF、CF这些,一条指令执行完,状态就变了。比如cmp比较两个数,结果不保存,但ZF根据是不是相等设成0或1,后面jmp指令就看这个跳不跳。分析条件判断时得反复看这些标志位。
lea指令不是搬数据,是算地址。比如lea eax, [ebx+4],是把ebx+4这个地址放进eax,而不是把那个地址里的内容取出来。刚学容易搞混。
学这东西没啥捷径,就是多练。我现在还在啃8086汇编,老古董了,但原理通的。寄存器、指令、内存访问,搞清楚了再看现代x64也没那么吓人。虽然写应用用不上,但你知道程序在底层是怎么跑的,心里踏实。
