探索反汇编-在Linux环境下

进行反汇编尝试-基于简单的C程序

使用Linux系统进行汇编,在Linux中使用vim编写程序,学过C语言的应该都对下面这段程序不陌生,这段程序定义了三十函数,函数名分别为g,f与main,在g函数中我们输入局部变量x的值,返回的是局部变量x+6,在f函数,返回的是g函数得到的值,在main函数中返回的是f函数的值(这个f 函数中局部变量已经指定为17)加上1。

int g(int x){
  return x+6;
}

int f(int x){
  return g(x);
}

int main(){
 return f(17)+1;
}

在这里插入图片描述
接下来要进行编译操作,使用如下指令即可,在这里 -S指的是编译.c文件使其变为.s(汇编)文件,最后加上 -m32是因为我的Linux系统是64位,要编译其为32位的汇编程序。

gcc -S com_test.c -o com_test.s -m32

在这里插入图片描述

在Linix下查看生成的.s文件,可以cat com_test.s来查看,得到的结果如下:

	.file	"com_test.c"
	.text
	.globl	g
	.type	g, @function
g:
.LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	call	__x86.get_pc_thunk.ax
	addl	$_GLOBAL_OFFSET_TABLE_, %eax
	movl	8(%ebp), %eax
	addl	$6, %eax
	popl	%ebp
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	g, .-g
	.globl	f
	.type	f, @function
f:
.LFB1:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	call	__x86.get_pc_thunk.ax
	addl	$_GLOBAL_OFFSET_TABLE_, %eax
	pushl	8(%ebp)
	call	g
	addl	$4, %esp
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE1:
	.size	f, .-f
	.globl	main
	.type	main, @function
main:
.LFB2:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	call	__x86.get_pc_thunk.ax
	addl	$_GLOBAL_OFFSET_TABLE_, %eax
	pushl	$17
	call	f
	addl	$4, %esp
	addl	$1, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE2:
	.size	main, .-main
	.section	.text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat
	.globl	__x86.get_pc_thunk.ax
	.hidden	__x86.get_pc_thunk.ax
	.type	__x86.get_pc_thunk.ax, @function
__x86.get_pc_thunk.ax:
.LFB3:
	.cfi_startproc
	movl	(%esp), %eax
	ret
	.cfi_endproc
.LFE3:
	.ident	"GCC: (Debian 10.3.0-9) 10.3.0"
	.section	.note.GNU-stack,"",@progbits

实在是让人眼花缭乱,其实对我们真正有用的只是汇编指令,所以我们要删除最前面带有".“的语句,使用如下命令,其中sed命令是可以利用脚本来处理文本文件,这里 -i是实际修改文件,[.]是找到文件中所有带”."的语句,d是删除操作。

sed -i '/[.]/d' com_test.s

执行语句后再次查看.s文件,发现简略了很多,已经剩下了对我们有用的信息,其实带有 G L O B A L O F F S E T T A B L E 变 量 的 行 也 可 以 省 略 , _GLOBAL_OFFSET_TABLE_变量的行也可以省略, GLOBALOFFSETTABLE_GLOBAL_OFFSET_TABLE_是指全局变量偏移表。
在这里插入图片描述

g:
	pushl	%ebp
	movl	%esp, %ebp
	addl	$_GLOBAL_OFFSET_TABLE_, %eax
	movl	8(%ebp), %eax
	addl	$6, %eax
	popl	%ebp
	ret
f:
	pushl	%ebp
	movl	%esp, %ebp
	addl	$_GLOBAL_OFFSET_TABLE_, %eax
	pushl	8(%ebp)
	call	g
	addl	$4, %esp
	leave
	ret
main:
	pushl	%ebp
	movl	%esp, %ebp
	addl	$_GLOBAL_OFFSET_TABLE_, %eax
	pushl	$17
	call	f
	addl	$4, %esp
	addl	$1, %eax
	leave
	ret
	movl	(%esp), %eax
	ret

汇编基础知识

有如上的汇编代码还是很懵逼,因为看不懂啊,本人才疏学浅,为了看懂这一段汇编代码所以必须去恶补这方面的知识,如果说的不对,欢迎大佬们指正。(以下说的都是x86架构下 CPU中寄存器,均为32bit,所以这也是我们之前汇编使汇编代码为32bit的原因)

X86 CPU早期只有八个寄存器
eax:是累加器,是加法与乘法的缺省(我理解为系统自动完成而不需人工干预)寄存器
ebx:基址寄存器,存放基地址
ecx:计数器
edx:放除法操作得到的余数
ebp:基址指针寄存器,含有一个指针,其中这个指针指向系统栈最上面的栈帧的栈底(帧指针)
esp:栈顶指针寄存处,存在一个指针,其中这个指针指向系统栈最上面的栈帧的栈顶,esp所在位置是内存的低地址位置,压入的数据增多会使esp的值减少,在32bit平台中,esp每次减少4-指的是int型,因为int型是4个字节(栈指针)
esi:源索引寄存器
edi:目标索引寄存器

接下来是一些操作的讲解

以下均为AT&T格式,与intel格式有些许不同
l指的是双字(相当于intel中的dword)
pushl是指压栈
movl是指将第一个操作数(寄存器的内容...)复制到第二个操作数(寄存器的内容...)
addl是指加法指令,将操作数加到累加器中并存到累加器eax中
call是调用指令,这时,程序就会去找调用的函数标签,并为该函数建立一个新的帧
leave是将栈指针指向帧指针,然后pop备份的原帧指针到%EBP,32位汇编中相当于如下命令:
movl %ebp,%esp
popl %ebp  (回到ebp的旧值)
popl是出栈
ret是执行call之前的地址

对之前的汇编代码进行分析

要理解汇编代码就要对内存模型有一定理解,学过数据结构应该都知道栈吧,栈是LIFO的结构,也就是后进先出的结构。
首先介绍下堆(heap):程序运行时,OS会自动分配一段内存,这段内存里存储着数据与程序,需要注意的是程序运行完后这段内存不会自己消失,也就是需要自己去释放内存,Java中的垃圾回收机制就是干的这个事。
栈帧(stack):是函数运行时临时占用的区域,至于帧,我的理解是就是一个一个函数体,因为是临时占用的所以运行结束后会自动回收。
详见下图(画的很丑,请见谅):
在这里插入图片描述
这是一段内存结构,最下面是低地址,最上面是高地址,初始esp(栈指针),ebp(帧指针)指向最上面也就是最高的地址。因为我们的函数有三个函数体(我认为可以看成三个帧,这三个帧构成一个堆栈结构-如果说的不对,请大佬们指正),分别g,f,与main但是学过C语言的都知道,最先执行的是main函数,汇编也一样,首先是main入栈,main调用了f函数,所以f函数入栈,f函数调用了g函数,所以g函数入栈。每个帧里面是这个函数体的变量等值,比如局部变量等。

需要注意的是,由于栈是从高地址开始的,所以入栈其实相当于是esp减而不是加,也就是从高地址到低地址。出栈则是相反。

接下来对整体开始分析(以下均为上面为代码,下面为分析的格式):
一开始ebp与esp均在最高位置

	pushl	%ebp

压入ebp,ebp的值(%ebp)是0,同时esp的值被修改,也就是esp的位置减去1(或者说四个字节)

	movl	%esp, %ebp

相当于esp的值赋值给ebp,相当于ebp的值也变了,变为与esp的值一样

	pushl	$17

即将17压入,esp会向下减1(或者说四字节),ebp不变

call	f

在这里开始调用f函数,在这里需要压入的是eip(24),也就是这个call f下一句的地址这个24是行数(addl $4, %esp)

pushl	%ebp

进入f函数体,执行f函数的第一句又将f的ebp压入栈中,esp减1

movl	%esp, %ebp

这里esp,ebp又到了一个位置,相当于ebp向下减去2

pushl	8(%ebp)

这是变址寻址,相当于ebp向上移动2(8相当于2个字节)即退出f函数体

call	g

调用g函数,eip(15)压入栈,esp又-1

pushl	%ebp

这里进入了g函数,压入%ebp,%ebp的值为0,esp又向下移1

movl	%esp, %ebp

将esp内容复制给ebp,相当于esp与ebp又到了同一个位置

movl	8(%ebp), %eax

还记得eax是累加器吧,这里相当于将ebp的位置加上2即向上移动2。

addl	$6, %eax

这一段没啥说的,就是加法,之前eax的值为8,现在加了6值变为14。

popl	%ebp

这一段需要特别注意,因为 比较难理解,对于ebp相当于回到了之前的ebp,esp会减1。

ret

esp的位置会指到之前的eip(15)那个位置(相当于回到了f),eip会回到15(行)的位置

addl	$4, %esp

又是加法指令,使esp的值变为4(向上移动1)

leave(相当于有两条指令)

这里相当于撤销函数堆栈即esp和ebp的位置一样了都到了上一个%esp的位置(movl %ebp %esp)。然后ebp回到之前的%ebp位置,esp再加个1即向上移动一个位置(popl %ebp)。

ret

esp移回到eip(24)的位置,然后eip指向24(addl $4, %esp)

addl	$4, %esp

esp的值加上1,相当于指针往上移1个位置

addl	$1, %eax

eax的值加上1

leave

这里相当于撤销函数堆栈即esp和ebp的位置一样了都到了上一个%esp的位置(movl %ebp %esp)。然后ebp回到之前的%ebp位置,esp再加个1即向上移动一个位置(popl %ebp)。

ret

返回

movl	(%esp), %eax

将esp的值赋值给eax

ret

这样形容也许很枯燥,接下来会用图的样式展示一下esp与ebp是如何移动的(图很丑,请见谅)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
终于结束了。。。。眼睛都看花了,用OD或者IDA这些工具分析不行吗?

计算机如何工作

对于这个问题我想用之前做的一个笔记来做总结:
在这里插入图片描述
这就是冯诺依曼模型机(SISD-单指令流单数据流)的工作方式。

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值