ChrisXisaer反编译技术探索!第一章-第二节 进入汇编的世界!

Posted chris.xisaier

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ChrisXisaer反编译技术探索!第一章-第二节 进入汇编的世界!相关的知识,希望对你有一定的参考价值。

  能再次见到你真是太好了,我们一起进入汇编语言的世界,来一探究竟吧。

  上一节我们简单的介绍了一些反编译的基础理论知识,包括可执行文件在不同操作系统上的格式问题,以及可执行文件的的各个段之间存储的数据信息。当然我们的主要研究对象就是.text段的内容,由于反编译类软件呈现给我们的是某个中间语言环境,比如INTEL汇编 或者MSIL,因此我们要对这些语言有个初步的认识,以便我们可以逆向出源代码的结构和逻辑关系。我们先不要理会各种处理器之间汇编语法的差异。也不要在意处理器是ARM,INTEL 还是AMD,更不要在意处理器是16位,32位还是64位。

  我们先来理清一下思路,前面我说过这不是一本教授汇编语言的书籍,只是在反编译过程中用到汇编语言的情况下会引用介绍就像下面这一段编码:

.text:0000000000401130 sub_401130 proc near ; DATA XREF: .pdata:0000000000529018↓o
.text:0000000000401130
.text:0000000000401130 var_18 = qword ptr -18h
.text:0000000000401130
.text:0000000000401130 sub rsp, 38h
.text:0000000000401134 mov rax, cs:off_526C20
.text:000000000040113B lea r8, qword_52C010
.text:0000000000401142 lea rdx, qword_52C018
.text:0000000000401149 lea rcx, dword_52C020
.text:0000000000401150 mov eax, [rax]
.text:0000000000401152 mov cs:dword_52C000, eax
.text:0000000000401158 lea rax, dword_52C000
.text:000000000040115F mov [rsp+38h+var_18], rax
.text:0000000000401164 mov rax, cs:off_526BE0
.text:000000000040116B mov r9d, [rax]
.text:000000000040116E call __getmainargs
.text:0000000000401173 nop
.text:0000000000401174 add rsp, 38h
.text:0000000000401178 retn
.text:0000000000401178 sub_401130 endp

这是IDAPRO反编译软件反编译出的一段代码。

第一句.text:0000000000401130 sub_401130 proc near ; DATA XREF: .pdata:0000000000529018↓o 标识了这是一个函数的开始(过程方法),near表示段内近调用,DATA XREF: .pdata:0000000000529018↓o 表示了在数据段中的一个交叉引用地址。这个函数从这条语句开始一直到.text:0000000000401178 sub_401130 endp 结束。记住在反编译呈现的每行指令中 每个.text 后面的内存地址都代表了一个字节的存储单元,开头的四个.text:0000000000401130 共占用了4个字节 所以 你看到的text:0000000000401134 mov rax, cs:off_526C20 是从401134开始的.

  我现在来讲解一下这个过程并简单说明一下各种汇编指令的作用。.首先 text:0000000000401130 var_18 = qword ptr -18h 将一个局部变量赋值,即将指针保存地址减去18h的地址保存在这个变量中。接下来.text:0000000000401130 sub rsp, 38h,将堆栈指针RSP 减去38h 也就是将堆栈分配38H 字节空间的容量,这些分配的内存空间就是为了保存即将调用函数getmainargs的参数所需的空间:

.text:0000000000401134 mov rax, cs:off_526C20
.text:000000000040113B lea r8, qword_52C010
.text:0000000000401142 lea rdx, qword_52C018
.text:0000000000401149 lea rcx, dword_52C020
.text:0000000000401150 mov eax, [rax]
.text:0000000000401152 mov cs:dword_52C000, eax
.text:0000000000401158 lea rax, dword_52C000
.text:000000000040115F mov [rsp+38h+var_18], rax
.text:0000000000401164 mov rax, cs:off_526BE0
.text:000000000040116B mov r9d, [rax]

这些都是赋值语句用来初始化传递给__getmainargs的相关参数,至于这个函数是用来干什么的我们不用深究。因为那不是本函数过程的一部分。当然.text:000000000040116E call __getmainargs这句就是在调用这个函数。.text:0000000000401173 nop 单纯的只是为了字节对齐。接下来的两句是恢复分配的函数堆栈空间并返回。

.text:0000000000401174 add rsp, 38h

.text:0000000000401178 retn

.text:0000000000401178 sub_401130 endp 这句话代码表示了过程的完整结束。下面我们把他逆向成C++代码应该是这个样子:

__int64 sub_401130()

dword_52C000 = unk_52C600; //这是一个未知地址因为程序还没有运行,所以我们用UNK=UNKNOW 来标注这个地址
return _getmainargs(&dword_52C020, &qword_52C018, &qword_52C010, unk_415030, &dword_52C000);

这个函数用它调用的_getmainatrs 的返回值当作本函数的返回值。注意这个函数的最后一个参数就是我们本函数局部变量保存的地址。

我们再来看一个函数:

.text:0000000000401510 sub_401510 proc near ; CODE XREF: sub_401530+7↓j
.text:0000000000401510 ; sub_40FBF0+48↓j ...
.text:0000000000401510 sub rsp, 28h
.text:0000000000401514 call _onexit
.text:0000000000401519 test rax, rax
.text:000000000040151C setz al
.text:000000000040151F movzx eax, al
.text:0000000000401522 neg eax
.text:0000000000401524 add rsp, 28h
.text:0000000000401528 retn
.text:0000000000401528 sub_401510 endp

同样前面的几行和刚才的都是一样的,不一样的地方在这几行:

.text:0000000000401519 test rax, rax
.text:000000000040151C setz al
.text:000000000040151F movzx eax, al
.text:0000000000401522 neg eax

.text:0000000000401519 test rax, rax这样是用来判断两个寄存器逻辑与操作的,setz al意思是当flag z标志被设定时,al寄存器设1,否则al为0。这样就省去了条件跳转,保证了流水线.setz指令是把ZF标志的值传给一个字节型操作数,ZF=1时结果为1,ZF=0时结果为0。movzx,是将 al 复制到 eax,注意 al 是 eax 最低 8 位的意思,因此这里实际上做的只是把 eax 的高位用 0 填充,eax 是函数的返回值。最后一句.text:0000000000401522 neg eax比较难理解:

 

格式:NEG OPR
执行的操作:(OPR)<-- —(OPR)
亦即把操作数按位求反后末位加1,因而执行的操作也可表示为:
(OPR)<-- 0FFFFH — (OPR) + 1
NEG指令对标志的影响与用零作减法的SUB指令一样。就是对eax的值求反后末位加1在存入EAX 并当返回值返回。

我们逆向了这个函数的样子:

__int64 __fastcall sub_401510(int (__cdecl *a1)())

return (unsigned int)-(onexit(a1) == 0i64);

读到这里可能你跟我一样有些许疑问,这样一堆数字编号的程序即使我知道了内部的运行过程也不清楚这个函数到底有什么用。没错,那是应为这是从一大段汇编代码中摘出的一小部分函数片段。我在这里仅仅作为举例子。要想搞清楚每个函数是什么意思,它在程序中扮演的角色。我们还要结合我们所使用的工具,通过获取函数签名。来判断函数的直译名字以及猜测或揣摩作者给函数命名的意图。还要结合其他相关函数才能最终确定一个函数的作用到底是什么。所以不要着急通过后面的探索我们终将能够领略逆向工程的魅力。它不仅能够让我们了解程序是如何翻译给CPU执行的,还让我们深入理解了编译器到底对我们的原始代码做了什么工作。这将有助于以后我们看到其他代码的时候第一时间排查出那些是编译器生成的代码,那些是程序所有者编写的代码。

  当然这样的例子我在后面的章节中也会经常用到,学习汇编语言其实和学习C语言一样。只是因为它的数据存储方式由变量或者常量,变成了寄存器。给广大初学者带来了劝退提示。不过只要你能静下心来看完一两段汇编代码,你就会领略到其中的奥秘。

  在下一节中我们将一起探索目前我们能够使用的一些经典反编译器及相关使用方法。当然这也不是一章一节能够讲完的。我将分别讲解2种反编译器的使用方法。再重申以便这不是教你如何使用工具的书籍,这是让你一起跟我探索反编译技术的文章。我们的重点是如何进行反编译工程。

以上是关于ChrisXisaer反编译技术探索!第一章-第二节 进入汇编的世界!的主要内容,如果未能解决你的问题,请参考以下文章

第一章 反汇编简介

第一章 反汇编简介

LaTeX中如何定义使\section编译之后显示第一章,第二章这种

编译技术图示(第一章 编译概述)

llvm教程

编译原理-第一章 引论-C和Java编译系统