编译和反编译(一)

      编译不是技能,可以交给编译器完成。然而反编译是需要特别的技巧的。其中关键的步骤就是汇编和反汇编。这是连接机器语言和汇编语言的桥梁。注意我这里说的编译是指把高级语言转化为汇编语言,反编译则是把汇编语言转化为高级语言,如C。汇编语言和机器语言之间是一一对应的,所以没有什么技术含量,因此有很多工具能实现这一过程,如等等用到的objdump
      高级语言到汇编语言本来是容易的,但是出于优化目的,这个过程就变得很复杂,编译器承担这一重任,可以说编译器是比操作系统更有内涵的。。在利益的驱动下(人懒),这一过程越来越完美。然而汇编到高级语言的转换却没有那么容易,也没有好的工具。仔细想想为什么。我自己觉得,1. 有些汇编是有扰乱的编码的,没办法转换。2. 很简单的例子,我完全不知寄存器里的是什么数据类型,链表?int?char?数组?机器忠实的执行指令,这些东西完全不关它的事,因此汇编里完全没有必要指定这些数据类型。3. 逻辑,如果有可能完成转换,那必定是充满goto的,因为汇编里只有jmp类型的命令。难以恢复成for,while。然后形成一种科学,叫逆向工程。当然也不是没有可能,比如hex-rays的IDA Pro和Hex-Rays Decompiler,太贵。所以只能手动尝试。

简单汇编:

      Linux和Unix用GAS,Intel却另外搞出一套,不知道为什么。掌握基础的汇编才能看得反汇编后的代码。基本的指令 mov, jmp, cmp, push, pop ,除了知道这些还有了解相应体系结构的寄存器作用和支持的内存寻址方式。大概这书讲得很清楚了。当然学习汇编最好的方式还是跟编译器学。

编译器的汇编:

      具体的我就不说了,我就提一些我遇到的问题。下面是简单例子,取两个数的最小数。

stackframe-cdecl
From http://unixwiz.net/techtips/win32-callconv-asm.html

      非常简单的程序,但是他的内涵是很丰富的。首先看看他的没有经过优化的32位代码。在看懂代码之前,必须了解一下,计算机的stack长什么,或者说是怎么约定的。如上图所示(还有更详细的)。当调用一个函数时,先要传递参数,还要保存之前函数的位置,还有分配现在函数的变量空间。在32位机器上,是先把old ebp 压入stack,然后ebp = esp,彻底进入现有函数。其中ebp+4是返回地址(下一个rip),ebp+8是arg1,ebp+12是arg2,依次类推。我们知道stack是向低位增长的,因此每分配一个局部变量,esp就会减4(相应数据大小)。这个设计,图和代码都表示的很清楚。然后这里有个注意点,就是cmp,在gas里的解释如下:cmp src, dest   :compute dest – src and set flags accordingly。而nasm正好相反。这个flags怎么设置,详见wiki

      然后再来看看经过O1优化的min.c,  gcc -O1 -S min.c -o min.s -m32 -fno-asynchronous-unwind-tables 。结果真是吓尿了,所以gcc的优化还是很必要的。不仅代码规模小了,而且效率高了,直接吊了!但是这里有个问题,看 arg1 变成 4(%esp)了,按照前面来看不是应该 8(%esp)吗。这位仁兄跟我一样的困惑。原因就是这里没有push ebp。之前push ebp时,esp = esp – 4。然后就懂了。

      然后是64位的代码。首先的不同是参数不再使用stack传递,直接使用寄存器,依次是%rdi, %rsi, %rdx, %rcx, %r8 and %r9,(注意是4B类型)然后用stack,详细见abi,链接3。这里有个问题,为什么是-20(%rbp),这里关系到两件事,一是rsp不变,因为red zone的设计。二是stack align,gcc默认对齐到16 bytes。 可以参考手册进行测试。 gcc -S min.c -o min.s -fno-asynchronous-unwind-tables -mpreferred-stack-boundary=4 -momit-leaf-frame-pointer

      最后是64位的优化版本,根本不需要操作内存。看到代码,我只想说这就是进步!

小试身手:

      大概理解了汇编,所以要试着人工反编译。只能借助最原始的gdb和objdump。忽然发现这篇篇幅有点长,因此这里只提个头。Computer Systems: A Programmer’s Perspective非常好的一本书,提供了Lab。第二个实验,破解炸弹,我做的版本不太一样,可以一试,非常有意思。很有成就感。

链接:

  1.  反编译工具
  2. Reengineering from Assembler to C via FermaT Transformations
  3. Hex-Rays
  4. x86-64 API,   Stack frame layout on x86-64Intel x86 Function-call Conventions, ret
  5. simple tutorial
  6. optimization assembly
  7. Calling Conventions, Red zone, Stack align