【计算机系统(2)】逆向工程

本文详述了一次通过GDB调试工具和objdump反汇编工具分析二进制程序的过程,以解决一系列需要输入特定字符串或数字才能过关的挑战。作者通过分析汇编代码,揭示了每个关卡的通关条件,例如字符串比较、数组关系和循环逻辑,最终找到了各个阶段的正确答案。
摘要由CSDN通过智能技术生成

一、 实验目标与要求:

  1. 理解程序(控制语句、函数、返回值、堆栈结构)是如何运行的
  2. 掌握GDB调试工具和objdump反汇编工具

二、实验环境:

  1. 计算机(Intel CPU)
  2. Linux64位操作系统(Ubuntu 17)
  3. GDB调试工具
  4. objdump反汇编工具

三、实验方法与步骤:

本实验设计为一个黑客拆解二进制炸弹的游戏。我们仅给黑客(同学)提供一个二进制可执行文件bomb_64和主函数所在的源程序bomb_64.c,不提供每个关卡的源代码。程序运行中有6个关卡(6个phase),每个关卡需要用户输入正确的字符串或数字才能通关,否则会引爆炸弹(打印出一条错误信息,并导致评分下降)!

要求同学运用GDB调试工具和objdump反汇编工具,通过分析汇编代码找到在每个phase程序段中,引导程序跳转到“explode_bomb”程序段的地方,并分析其成功跳转的条件,以此为突破口寻找应该在命令行输入何种字符串来通关。

本实验需解决Phase_1(15)、Phase_2(15)、Phase_3(15)、Phase_4(15)、Phase_5(15)、Phase_6(10)。通过截图+文字的形式把实验过程写在实验报告上,最后并撰写实验结论与心得(15)。


四、实验过程及内容:

首先将bomb_64文件反编译,并把反编译后的汇编代码存放到1.txt文件中

Phase 1

查看第一关代码

已知%rsp是x86_64中的一个特殊的寄存器,它保存了当前线程的栈顶地址。在函数调用时,函数会将一些寄存器的值和临时变量保存在栈中,因此为了保存这些元素,需要将栈顶指针减小以腾出空间。因此在第一行中就是在栈上为本函数的执行分配了8个字节的空间

而在第三行中调用了地址为0x40123d的函数strings_not_equal,根据函数名我们可以猜测该函数的作用是判断两字符串是否相等。而在第二行中把地址0x401af8保存在了寄存器%esi中,猜测是将该地址处的值作为参数传入到strings_not_equal函数中进行比较。

比较完成后,发现第六行是一个条件跳转指令,当返回值为0的时候跳转到地址0x400e87处,即炸弹爆炸的下一行,该行进行了将栈指针还原的操作。

因此可以推测当字符串与被保存在0x401af8中的字符串相等时,返回值为0,此时答案正确,否则炸弹爆炸

使用gdb获取在地址0x401af8中的字符串。

进行测试,输入Science isn’t about why, it’s about why not?

答案正确。

Phase 2

查看第二关代码

 

考虑到本题的考点是循环语句和数组,并且发现在400ea7处调用了函数read_six_numbers,因此猜测与包含6个元素的数组有关,并且在read_six_numbers函数中出现了sscanf的字眼,该单词是用户输入的意思,那么猜测该函数实现的功能就是读取用户输入的包含6个元素的数组。

先尝试找到条件跳转语句,发现在400ed1和400ed4中,当寄存器%r13中的值与%rbp中的值不相等时,跳转回到400eba处,因此控制循环的变量与%r13和%rbp有关。我们称该条件跳转语句为条件跳转1

先单独对%r13进行分析,发现只在400e9b,400eaf和400eef处出现过寄存器%r13。而在400e9b和400eef处,只是在函数被调用时将该寄存器原先的值保存在栈中,并在函数返回前将这个值还给寄存器,因此只需要关注在400eaf处的语句。在此处进行了一个赋值操作,令%r13 = 栈顶指针 + 12。

对%r13分析完后,对%rbp进行追踪。除去函数首尾对该寄存器的入栈保存操作以外,在400eac,400eba,400ebd,400ec0,400ecd,400ed1中出现了对%rbp寄存器的操作。在400eac处将栈顶指针赋值给了%rbp,在400eba处,将%rbp中的值赋值给了寄存器%rbx,然后在400ebd中将rbp + 12处的地址(即栈顶指针 + 12)赋值给了寄存器%eax。

在400ec0处出现了一个条件判断语句,如果%eax中保存地址中保存的值与%rbp中保存的地址中保存的值相等(即*(栈顶指针 + 12) == *栈顶指针),则跳转到400eca。我们将这一条件跳转语句称为条件跳转2

接着往下看,在400ecd处是对%rbp中的值进行%rbp += 4的操作,接着就是在400ed1中对%r13和%rbp中的比较操作。如果%r13中的值与%rbp中的值不相等,则跳转到上方400eba处。

此时我们可以发现,%rbp的初始值为栈顶指针,而在每次与%r13中的值(栈顶元素 + 12)进行比较时,都要进行一次 + 4的操作,很容易想到会进行三次循环。设栈顶指针为top,第一次循环为top + 4 != top + 12,第二次为 top + 8 != top + 12,第三次为 top + 12 == top + 12,此时退出循环。

那么由条件跳转1们可以确定循环的次数,但是循环体内的内容是什么呢?因为每次条件跳转1满足后跳转到了条件跳转2的上方,所以我们再看条件跳转2。该条件是将*(栈顶指针+12)与(*栈顶指针)进行比较,累计比较三次则可以比较三组数,加起来正好是六个数。因此我们需要满足输入的六个数中a[0] == a[3],a[1] == a[4], a[2] == a[5]

往下看发现还有一处可能会引起炸弹爆炸,与寄存器%r12d有关。

往上找发现%r12d寄存器在循环开始前(400eb4)先将该寄存器初始化为0。每次循环体内部都使得%r12d += *(%rbx),而每次循环体开始时都进行赋值操作:%rbp = %rbx,所以经过三次循环后%r12d = a[0] + a[1] + a[2]。

在400ed6处对%r12d中的值进行了判断,如果为0则会引爆炸弹。因此需要满足a[0] + a[1] + a[2] != 0

综上所述,正确答案应该满足a[0] == a[3],a[1] == a[4], a[2] == a[5]且a[0] + a[1] + a[2] != 0

进行测试:

 

 

答案正确。

Phase 3

查看第三关代码

可以看到在前面的几行中,出现了一个用户输入函数sscanf的调用,查阅资料发现sscanf函数有多个参数,第一个参数为用户输入的字符串,第二个参数为拆分字符串的格式,后面的参数用于储存解析后的数据。返回值为有x个数据成功转换,如果成功转换了x个数据,那么返回值为x。

 

发现在sscanf调用的上方出现了对一个地址0x401ebe的操作,猜测其中保存了对用户输入的字符串进行拆分的格式。

输入指令进行查看,发现用户输入的字符串被拆分为两个整形数,分别保存在%rcx和%rdx寄存器中,其中%rcx中的值为*(栈顶指针+8),%rdx中的值为*(栈顶指针+12),并且%rdx是第一个参数,%rcx是第二个参数。

输入函数的返回值被保存在%eax寄存器中,如果返回值<=1时引爆炸弹,以保证用户输入参数个数的正确性。

往下看,当%rdx中的值转化为无符号整数后,如果 > 7则引爆炸弹,因此只能%rdx中的值控制在 0 – 7,因为如果%rdx中的值为负数,转化成无符号整数的话会变成一个极大的数。

再往下,将%rdx中的值保存在%eax寄存器中,然后根据%eax中零扩展后的值确定偏移量,跳转到0x401b60 + 8 * %eax中保存的地址。由于%eax为0 - 7也就是有八个值,因此通过指令查看0x401b60往下的连续8个地址

输入gdb指令x/8xg 0x401b60,其中8为查看8个地址,x表示用十六进制格式显示变量,g表示一个地址单元的长度为八字节。

因此可得当%rdx的值为0时,跳转到0x400f32处,将0x217保存在%eax中,再跳转到0x400f74,比较%rcx中的值是否等于0x217,如果相等则是一个可行解。也就是说,

当第一个参数为0时,第二个参数应该为535

同理可以得到当第一个参数为1时,第二个参数为926

当第一个参数为2时,第二个参数为214

当第一个参数为3时,第二个参数为339

当第一个参数为4时,第二个参数为119

当第一个参数为5时,第二个参数为352

当第一个参数为6时,第二个参数为919

当第一个参数为7时,第二个参数为412

进行测试

 

   

可知答案正确。

Phase 4

查看第四关代码

观察前几行代码,发现与第三关中几乎一样,0x401ec1中保存的时拆分用户输入字符串的格式,那么同样用指令查看0x401ec1中的值。

可知本关只需要输入一个整数,并且保存在%rdx寄存器保存的地址(即栈顶指针+12)中。

接下来判断输入的整数个数,如果不为1,则引爆炸弹。

再往下判断输入整数的数值,如果 > 0 则跳过炸弹,也就是说输入的整数不能 <= 0。

然后将该整数作为参数传入函数func4,执行完函数后,将返回值与0x37进行比较,如果相等,则答案正确;如果不等,引爆炸弹。那么我们就要func4函数的返回值等于0x37

查看函数func4代码

除去前面的保存寄存器值和开辟栈空间的操作。设传入的参数为x,将x的值保存到寄存器%ebx中,比较1与x的大小,如果x <= 1,则直接返回,此时返回值不等于55,故参数应该满足x > 1

往下看,出现了一个操作将x-1的值作为参数传入函数func4中,将这个返回值保存在%ebx寄存器中,再往下还有一个操作将x-2的值作为参数传入函数func4中。将这两次调用操作的返回值相加并返回。func4(x) = func4(x - 1) + func4(x - 2)

由于func4(0) = func4(1) = 1

func4(2) = func4(1) + func4(0) = 2

func4(3) = func4(2) + func4(1) = 3

func4(4) = func4(3) + func4(2) = 5

func4(5) = func4(4) + func4(3) = 8

func4(6) = func4(5) + func4(4) = 13

func4(7) = func4(6) + func4(5) = 21

func4(8) = func4(7) + func4(6) = 34

func4(9) = func4(8) + func4(7) = 55

因此需要传入的参数为9

进行测试

答案正确。

Phase 5

查看第五关代码

前几行也是对用户输入的一些控制,需要输入两个整型参数,第一个参数保存在地址(栈顶指针 + 12)处,第二个参数保存在(栈顶指针 + 8)处。

接下来将输入的第一个参数保存在%eax寄存器中,然后将这个值与0xf进行按位与操作,即只保留低4位,其他位清0。将操作后的值保存在%eax寄存器和栈顶指针+12处。然后进行判断,如果该参数的低4位都为1,则引爆炸弹

接下来将%ecx寄存器和%edx寄存器清零。

再往下几行进入了一个循环。首先将%edx寄存器内保存的值 + 1,然后是一条cltq指令,查阅资料发现该指令总是以寄存器%eax作为源,%rax为符号扩展结果的目的。大的效果与指令movslq $eax,%rax完全一致,不过编码更加紧凑。

0x401048这一行,取出了内存中的一个地址中的值并将其保存在%eax寄存器中,该地址为0x401ba0 + %rax * 4,而0 <= %rax <= 15。容易联想到以0x401ba0起始的一连串地址中保存的应该是一个大小为16的整形数组。

则上述代码可以写成这样的一个循环

int rcx = 0;
int rdx = 0;
do {
    rdx++;
    rax = a[rax];
    rcx += rax;
} while (rax != 15)

则需要让rax == 15才能跳出循环。

循环结束后,将%rdx中的值与12进行比较,如果不相等则引爆炸弹。而在循环体中发现每循环一次rdx++,所以%rdx是用于控制循环次数的,控制循环次数为12

接着将%rcx寄存器与输入的第二个参数进行比较,如果不相等则引爆炸弹。在循环体中发现rcx中保存的值是每次循环更新rax的值的和。

接下来只需要满足输入的第一个参数在经过12次循环之后的值等于15,然后将累加和作为第二个参数即可通过。

查询该数组中的值

为节省时间,在编译器中进行计算,得到结果为7和93

进行测试

答案正确。

Phase 6

查看第六关代码

发现0x4010e7处调用了一个名为strtol的函数,查阅资料发现该函数的作用是把参数 str 所指向的字符串根据给定的 base 转换为一个长整数,猜测在0x4010dd行是对参数base 的设置,该函数将用户输入的字符串转换为一个十进制的长整数。

往下看,经过strtlon函数转换后得到的返回值被保存在地址0x602780处,然后将这个数作为参数传入fun6函数中。

对fun6的返回值进行连续三次+8的操作,然后将处理后的值与传入fun6函数的参数(即输入转换后的数)进行比较,如果不等则引爆炸弹。

那么问题的关键就在于fun6函数对传入的参数做了怎么样的处理,我们先查看fun6函数的代码。

可见该函数异常复杂,涉及到了很多寄存器的赋值和条件跳转等操作,因此我们先尝试使用gdb设置断点进行调试,看看能不能找出什么规律。

设置断点

尝试输入1,此时%rax寄存器中的值为600,%edx中的值为1。

再次尝试输入2,此时%rax寄存器中的值依然为600,%edx中的值为2

 

猜测输入的参数都被转换成了600这个值,尝试输入600。此时两寄存器中的值相等。

运行测试

答案正确。


五、实验结论:

  • Phase_1

答案为Science isn't about why, it's about why not?

  • Phase_2

答案为6个数:a、b、c、d、e、f,且需要满足a=d,b=e,c=f,a+b+c!=0

  • Phase_3

答案可以是下面中的任意一组:

(0,535)(1,926)(2,214)(3,339)(4,119)(5,352)(6,919)(7,412)

  • Phase_4

答案为数字9

  • Phase_5

答案为7 和93

  • Phase_6

答案为600


六、心得体会:

通过本次实验,对汇编代码有了更深入的了解,对数组在汇编代码中的访问也更加了解,并且为了更好地通过本实验的关卡,也学习了更多gdb命令以帮助调试程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值