Jason GNSS Beginner

What happens when compiling and decompiling programs?

2020-04-22
Jason Ding

IDE(Integrated Development Environment,集成开发环境)为软件开发提供了一站式服务,这是优点,同时也是缺点,这种一站式的服务使其占用了大量系统资源(以Visual Studio为例,安装VS往往需要占用10~20G甚至个更多的空间),启动/运行速度慢,需要较高的硬件配置,并且隐藏了底层(程序构建过程),有时候给程序维护带来困难。相比于IDE,Atom/Sublime Text/Notepad++/VS Code等文本编辑器,占用资源少,打开速度快,支持大量插件,程序员们能够将其打造成专属于自己的IDE,完全掌握代码的方方面面,深受广大程序员喜爱。

要想知道程序的构建中发生了什么,就要知道IDE在背后帮我们做了什么,一个完整的IDE集成了编辑器、编译器、汇编器和链接器等工具,这些工具的作用和功能如下:

工具 作用 功能
编辑器(editor) 编写及编辑代码 编写源代码
编译器(compiler) 检查用户代码的一些语法错误,将其编译成汇编代码 预编译,编译
汇编器(assembler) 将汇编代码汇编成具有一定功能的目标代码 汇编
链接器(linker) 将目标代码链接成可执行文件 链接

一个完整的程序从编写到跑起来一般需要进行这些步骤:源文件编辑–>预处理–>编译–>汇编–>链接,以下以Hello World为例简单介绍每个步骤。

源文件编辑

一个c++ hello world:

#include <iostream>
using namespace std;
int main()
{
	cout<<"Hello World!"<<endl;
	return 0;
}
预处理(Preprocessing)
g++ -o HelloWorld.i -E HelloWorld.cpp

在预处理过程中,计算机做了这些事情:

  • 展开所有的宏定义,消除“#define”;
  • 处理所有的预编译指令,如“#if”,”#ifdef”等;
  • 处理“#include”预编译指令,将包含文件插入到该预编译的位置;
  • 删除所有的注释“/**/”,“//”等;
  • 添加行号和文件名标识,以便编译时编译器产生调试用的行号信息以及错误提醒。

这一步不会做出任何安全性和合法性检查。

编译(Compilig)
g++ -o HelloWorld.s -S Helloworld.i

编译过程生成汇编文件HelloWorld.s,这一步编译器将进行:

  • 词法分析:将源代码文件的字符序列划分为一系列的记号;
  • 语法分析器将对产生的记号进行语法分析,产生语法树,一步步判断如何执表达式操作;如果存在括号不匹配或者表达式错误,编译器会报告错误;
  • 语义分析:由语法阶段完成的分析并没有赋予表达式或者其他实际的意义,如加减乘法,需要语义分析才能赋予其真正的意义;
  • 代码优化:对代码进行优化处理;
  • 汇总所有符号。
汇编(Assembling)
g++ -o Helloworld.o -C Helloworld.s

汇编过程中,生成二进制文件Helloworld.o,也就是机器语言对应的文件。

链接(Linking)
g++ -o Helloworld.exe Helloworld.cpp

链接指的是调用链接器ID来链接程序运行需要的目标文件,及其依赖的库文件,最后生成可执行文件。链接分为“静态链接”和“动态链接”,静态链接和动态链接分别链接静态库和动态库,静态库和动态库均为二进制文件,用于存放功能的函数实现。其中静态库在编译时会被访问(被集成),编译后可以删除,而动态库则在程序执行时被访问,编译时不要需要,不能删除。(win和linux下的动态库文件名后缀分别为.dll和.so)

  • 静态链接:指在编译阶段直接把静态库加入到可执行文件中,这样生成的可执行文件相对较大.
  • 动态链接:在链接阶段仅加入一些描述信息,可执行文件运行时再把相应动态库加载到内存.

Decompile

编译指的是将高级程序语言源码转换为可执行文件,反编译则是编译的逆过程。一般来说反编可以把可执行文件转化为汇编代码,但是通常不能变成高级语言源代码(也有一些例外,比如.Net Reflector能够反编译出C#源码)。

在一些情况下,比如源码丢失或者无法获取源码,却要修复exe的bug或者单纯仅仅只是修改(如启用/停用)程序中的某个方法或者功能,就可以通过反编译工具来实现,这相对于完全破解以获取软件的原理/算法/结构等等要简单得多。以下为一些网页上推荐的反编译工具:

以下为使用Ollydbg汇编分析调试器对HelloWorld.exe进行反汇编的部分结果:

Address   Hex dump          Command                                  Comments
00401242  |> \8B1D F8814000 MOV EBX,DWORD PTR DS:[<&msvcrt._iob>]
00401248  |.  894424 04     MOV DWORD PTR SS:[ESP+4],EAX             ; /mode
0040124C  |.  A3 0C404000   MOV DWORD PTR DS:[40400C],EAX            ; |
00401251  |.  8B43 10       MOV EAX,DWORD PTR DS:[EBX+10]            ; |
00401254  |.  890424        MOV DWORD PTR SS:[ESP],EAX               ; |fd => [74AD4610] = 0
00401257  |.  E8 382A0000   CALL <JMP.&msvcrt._setmode>              ; \MSVCRT._setmode
0040125C  |.  A1 24704000   MOV EAX,DWORD PTR DS:[407024]
00401261  |.  894424 04     MOV DWORD PTR SS:[ESP+4],EAX             ; /mode => [407024] = _O_RDONLY
00401265  |.  8B43 30       MOV EAX,DWORD PTR DS:[EBX+30]            ; |
00401268  |.  890424        MOV DWORD PTR SS:[ESP],EAX               ; |fd
0040126B  |.  E8 242A0000   CALL <JMP.&msvcrt._setmode>              ; \MSVCRT._setmode
00401270  |.  A1 24704000   MOV EAX,DWORD PTR DS:[407024]
00401275  |.  894424 04     MOV DWORD PTR SS:[ESP+4],EAX             ; /mode => [407024] = _O_RDONLY
00401279  |.  8B43 50       MOV EAX,DWORD PTR DS:[EBX+50]            ; |
0040127C  |.  890424        MOV DWORD PTR SS:[ESP],EAX               ; |fd
0040127F  |.  E8 102A0000   CALL <JMP.&msvcrt._setmode>              ; \MSVCRT._setmode

上表中:

  • 第一列是指令在内存中存放的地址;
  • 第二列是汇编语言中的操作码,只有CPU能对它唯一识别,Ollydbg的作用就是根据操作码解读出易于人类阅读的汇编语言;
  • 第三列为反汇编的目标了,即汇编代码;
  • 第四列是Ollydbg提供的注释,可能是windows api,或者是一些可读的信息。

为了保护劳动成果,开发者们常常通过加壳来阻止反编译,加壳的目的是防止源代码被查看,常用的方式是在二进制的程序中植入一段代码,在运行的时候优先取得程序的控制权,阻止外部程序或软件对加壳程序的反汇编分析或者动态分析。

加壳本身只是提高了被反编译的难度,并不能杜绝反编译,例如一些反编译工具集成了了脱壳方法。事实上,只要是CPU能识别的语言,人类都能够识别,区别只是难度高低。完全杜绝反编译一般是不可能的,除非程序完全与用户隔离,比如运行在服务器上,各种防止反编译的方法仅仅只是提高被反编译的门槛。

相关资源

[1]. 程序编译的过程. https://www.jianshu.com/p/9a4d1dffa821

[2]. 在运行exe可执行文件前到底发生了什么?https://zhuanlan.zhihu.com/p/121063581

[3]. 推荐.Net、C# 逆向反编译四大工具利器. https://blog.csdn.net/zhangmiaoping23/article/details/80813161

[4]. EXE反编译方法及工具. https://blog.csdn.net/lqw_6/article/details/78143919

[5]. 从零开始的程序逆向之路. https://www.cnblogs.com/ichunqiu/p/9355253.html

附录 HelloWold编译过程中的中间文件

A. HelloWorld.cpp (源码文件,1kb)

#include <iostream>
using namespace std;
int main()
{
	cout<<"Hello World!"<<endl;
	return 0;
}

B. HelloWorld.i (预编译后文件,668kb,共25328行)

# 1 "HelloWorld.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "HelloWorld.cpp"
# 1 "c:\\mingw\\lib\\gcc\\mingw32\\9.2.0\\include\\c++\\iostream" 1 3
# 36 "c:\\mingw\\lib\\gcc\\mingw32\\9.2.0\\include\\c++\\iostream" 3
       
# 37 "c:\\mingw\\lib\\gcc\\mingw32\\9.2.0\\include\\c++\\iostream" 3

# 1 "c:\\mingw\\lib\\gcc\\mingw32\\9.2.0\\include\\c++\\mingw32\\bits\\c++config.h" 1 3
# 252 "c:\\mingw\\lib\\gcc\\mingw32\\9.2.0\\include\\c++\\mingw32\\bits\\c++config.h" 3

# 252 "c:\\mingw\\lib\\gcc\\mingw32\\9.2.0\\include\\c++\\mingw32\\bits\\c++config.h" 3
namespace std
{
  typedef unsigned int size_t;
  typedef int ptrdiff_t;
  typedef decltype(nullptr) nullptr_t;
}

...

# 2 "HelloWorld.cpp" 2
# 2 "HelloWorld.cpp"
using namespace std;
int main()
{
 cout<<"Hello World!"<<endl;
 return 0;
}

C. HelloWorld.s (汇编文件,3kb,共116行)

	.file	"HelloWorld.cpp"
	.text
	.section .rdata,"dr"
__ZStL19piecewise_construct:
	.space 1
.lcomm __ZStL8__ioinit,1,1
	.def	___main;	.scl	2;	.type	32;	.endef
LC0:
	.ascii "Hello World!\0"
	.text
	.globl	_main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
LFB1515:
	.cfi_startproc
	leal	4(%esp), %ecx
	.cfi_def_cfa 1, 0
	andl	$-16, %esp
	pushl	-4(%ecx)
	pushl	%ebp
	.cfi_escape 0x10,0x5,0x2,0x75,0
	movl	%esp, %ebp
	pushl	%ecx
	.cfi_escape 0xf,0x3,0x75,0x7c,0x6
	subl	$20, %esp
	call	___main
	movl	$LC0, 4(%esp)
	movl	$__ZSt4cout, (%esp)
	call	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	movl	$__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, (%esp)
	movl	%eax, %ecx
	call	__ZNSolsEPFRSoS_E
	subl	$4, %esp
	movl	$0, %eax
	movl	-4(%ebp), %ecx
	.cfi_def_cfa 1, 0
	leave
	.cfi_restore 5
	leal	-4(%ecx), %esp
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1515:
	.def	___tcf_0;	.scl	3;	.type	32;	.endef
___tcf_0:
LFB1993:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$8, %esp
	movl	$__ZStL8__ioinit, %ecx
	call	__ZNSt8ios_base4InitD1Ev
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1993:
	.def	__Z41__static_initialization_and_destruction_0ii;	.scl	3;	.type	32;	.endef
__Z41__static_initialization_and_destruction_0ii:
LFB1992:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$24, %esp
	cmpl	$1, 8(%ebp)
	jne	L6
	cmpl	$65535, 12(%ebp)
	jne	L6
	movl	$__ZStL8__ioinit, %ecx
	call	__ZNSt8ios_base4InitC1Ev
	movl	$___tcf_0, (%esp)
	call	_atexit
L6:
	nop
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1992:
	.def	__GLOBAL__sub_I_main;	.scl	3;	.type	32;	.endef
__GLOBAL__sub_I_main:
LFB1994:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$24, %esp
	movl	$65535, 4(%esp)
	movl	$1, (%esp)
	call	__Z41__static_initialization_and_destruction_0ii
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1994:
	.section	.ctors,"w"
	.align 4
	.long	__GLOBAL__sub_I_main
	.ident	"GCC: (MinGW.org GCC Build-20200227-1) 9.2.0"
	.def	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc;	.scl	2;	.type	32;	.endef
	.def	__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_;	.scl	2;	.type	32;	.endef
	.def	__ZNSolsEPFRSoS_E;	.scl	2;	.type	32;	.endef
	.def	__ZNSt8ios_base4InitD1Ev;	.scl	2;	.type	32;	.endef
	.def	__ZNSt8ios_base4InitC1Ev;	.scl	2;	.type	32;	.endef
	.def	_atexit;	.scl	2;	.type	32;	.endef

D. HelloWorld.o (二进制文件,45kb,可执行文件HelloWorld.exe同为45kb)


上一篇 GithubDownload

Comments

Content