signed

QiShunwang

“诚信为本、客户至上”

CSAPP Lab 2 Bomb小记二

2021/4/26 21:59:18   来源:

接着上一篇文章,我们来继续分析几个phase

实验过程

phase 4 破解

反汇编代码:
在这里插入图片描述
设置断点:
在这里插入图片描述
执行程序:
在这里插入图片描述
分析汇编:
446:压栈
447:传入的第二个参数%rcx
448:传入的第一个参数%rdx
449:输入” %d %d”
450-453:输入个数为2
454-457:判断输入的第一个参数的大小,该数需小于等于14,且被初始化为14
458:%esi值为0
459:%edi的值等于刚输入第一个参数的值
460:调用func4,大致上浏览一遍函数,找到传入func4的参数:%edi,%esi,%edx
我们把这三个参数分别设为x,y,z;产生的中间变量有%ecx,%eax,将它们分别设为t和m。
在这里插入图片描述
则分析func4可以得到C语言的函数: 这是一个递归调用的函数
PS:下面的函数是我在第一次做的时候错误代码示例,没有理解递归含义

int func4 (int x, int y, int z)
/* x in %rdi, y in %rsi, z in %rdx, m in %rcx, t in %rax
  y的初始值为0,z的初始值为14 */
{
  int t = z - y;
  int m= t>>31;
  t= (t + m)>>1;
  m= t + y;
  if( m > x){
  z= m – 1;
  func4( x, y, z);
  t = 2*t;  **这里的t应该是递归调用的func4的返回值,而不是上面的t**
  return t;}
  else{
  t = 0;
  if( m < x){
  y = m +1;
  func4( x, y, z);
  t = 2*t + 1; **同理,这里的t应该是递归调用func4的返回值,而不是之前的t**
  return t;}
  else return t; }
}

编写正确的函数并测试:

int func4(int x, int y, int z)
{
 int t = z - y;
 int m = t>>31;
 t =(t + m)>>1;
 m = t + y;
 if(m > x){
 z = m - 1;
 return 2*func4( x, y, z);
 }
 else{
 t = 0;
 if(m < x){
 y = m + 1;
 return 2*func4(x, y, z) + 1;
 }
 else
 return t;
 }
 
int main()
{
 for(int i=0;i<=14;i++)
  {
     if(func4(i,0,14)==0)
       printf("%d\n",i);
   }
   return 0;
}

结果为:
在这里插入图片描述
则phase_4实现了对func4的递归调用,其中func4的第一个参数取值为0-14,第二个参数初始化为0,第三个参数初始化为14。最终得到一个使得func4递归调用的返回值为0的结果输出。
可输入的结果有:0 0 / 1 0 / 3 0 / 7 0
举几个例子:
在这里插入图片描述
在这里插入图片描述
phase 4成功破解!

phase 5破解

反汇编代码:
在这里插入图片描述在这里插入图片描述
设置断点
在这里插入图片描述
执行程序
在这里插入图片描述
分析汇编:
477:调用string_length函数查看输入字符串的长度
478-480:输入字符串的字符数必须为6,跳转
在这里插入图片描述
设置%eax为0(字符计数器),跳转
在这里插入图片描述
由前面的汇编代码知,%rcx中存放第一个字符的地址
8b:%ecx的值为%rbx + %rax (对于第一个字符来说,%rax为0),即存放第 %rax 个字符的地址。
8f-92:将第 %rax 个字符的低八位放入%rdx中。
96:取%rdx的低四位
99:%edx的值为 0x4024b0 + %rdx(低四位),即以0x4024b0为基地址,%rdx 为偏移寻找到的地址的值放入%edx中。
a0:将%edx的低八位放入内存%rsp + 0x10 + %rax处
a4:%rax 的值加1(计数器),即将开始对第二个字符的操作。
六个字符串经过变换后产生的数值依次存储在内存 (%rsp+0x10)(%rsp+0x11)(%rsp+0x12)(%rsp+0x13)
(%rsp+0x14)(%rsp+0x15)中。

a8-ac:判断计数器内的值是否为6,若不为6则继以上续循环。直至等于6,继续执行。
在这里插入图片描述
b3:查看%esi中的值对应的字符串
在这里插入图片描述
b8:将数值串的首地址存入%rdi中
bd:调用string_not_equal函数判断输入字符与给定字符是否相等
这里注意,因为我们传入的参数是一些十六进制的数值,与给定字符串进行比较事,要比较的是二者的ASCII码值,若相等,成功跳出函数。
于是关键来了,怎样找出比较的数呢?我们知道,在ASCII码表中,从64-90(0x41-0x5a)是大写英文字母,从97-122(0x61-7a)是小写英文字母。那么给定字符串的ASCII码值都是八位数值(低八位)。这与之前分析的%edx的低八位正好能用来比较。
那么首先将给定字符串的ASCII码找出:
在这里插入图片描述
接着查找以0x4024b0为基地址的查找表的表项:
在这里插入图片描述
若要使得%edx的低八位与与给定字符串的数值相同,则需要在查找表中寻找对应的项。
0x66:位于查找表偏移为0x9的位置
0x6c:位于查找表偏移为0xF的位置
0x79:位于查找表偏移为0xE的位置
0x65:位于查找表偏移为0x5的位置
0x72:位于查找表偏移为0x6的位置
0x73:位于查找表偏移为0x7的位置
回溯:
偏移值为%rdx的低四位
%rdx的低八位对应第 %rax 个字符
则:低四位为0x9的字符:0x49 ( I ) 0x59 ( Y ) 0x69 ( i ) 0x79 ( y)
0xF :0x4F ( O ) 0x6F ( o )
0xE :0x4E ( N ) 0x6E ( n )
0x5 :0x45 ( E ) 0x55 ( U ) 0x65 ( e ) 0x75 ( u )
0x6 :0x46 ( F ) 0x56 ( V ) 0x66 ( f ) 0x76 ( v )
0x7 :0x47 ( G ) 0x57 ( W ) 0x67 ( g ) 0x77 ( w )
将这些字符进行组合,输入。
在这里插入图片描述
phase_5 破解!

phase 6 破解

反汇编代码:
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
设置断点:
在这里插入图片描述
执行程序
在这里插入图片描述
分析汇编:
/* %rbp %rbx %r12~%15 被调用者保存寄存器
%r10 %r11 调用者保存寄存器
%rdi %rsi %rdx %rcx %r8 %r9 依次保存输入数1~6 */
512-516:入栈(被调用者)
517:创建栈帧(注意空间大小为0x50)
518:%r13保存%rsp的地址
519:%rsi保存%rsp的地址
520:调用reab_six_numbers函数读入六个数字
521:%r14保存%rsp的地址
522:将%r12的值置为0
523:%rbp保存%rsp的值
查找%rsp存储的地址:(第一个为创建栈帧前,第二个为创建栈帧后)

在这里插入图片描述
524:%eax的值为%r13存储地址在内存单元中的值
525-528:判断%eax的值,即存储在(%rsp)中的值,该值必须小于等于6。
529:%r12的值加1
530-531:比较%r12的值,若%r12等于6,跳转;不等于6,继续执行
(1) 若%r12不等于6
在这里插入图片描述
将%r12符号扩展的值传递给%rax
将 %rsp + 4*%rax代表的地址中存储的数复制到%eax中(从这里我们可以猜测%r12代表的是循环条件 类似于 i=0;i<6;i++)
比较两个数大小: %eax(注意在第529行%r12加1,代表相对于当前数的下一个数,则这里%eax中的数为下一个数的数值)和 %rsp存储地址的数值(即第一个数)
两个数大小必须不相等,否则bomb。
在第一个数与第二个数不相等的情况下,
%ebx加1,此时%ebx值为2
判断%edx的值:若%edx小于等于5,则跳转;大于5,继续执行
(a) 若%ebx小于等于5
%edx的符号扩展的值传递给%rax
将第三个数存入%eax
第三个数与第一个数比较,两数不等
%ebx加1,判断
依次比较其他数与第一个数,确保与第一个数不等
(b) 若%edx大于5,即所有其他数与第一个数的比较结束
%r13加4,则%r13指向第二个数的地址
跳转到*0x401114
在这里插入图片描述
将第二个数的地址传入%rbp
将第二个数值传入%eax
传入的数值要小于等于6
%r12加1,即此时%r12的值为2(结合前面分析,%r12代表被比较的是第%r12个数)
之后则为第三个数与第二个数比较,第四个数与第二个数比较…….
直到全部数值比较完毕,实现输入的六个数字两两各不相同的作用
相当于C语言的函数(有助理解,不是规范代码)

bool  num_not_equal ( int a[ ] )
{  
   for ( int i=0; I !=6; i ++)
     {
        int j = i +1;
        for (  ; j <=5; j++)
           {
             if( a[ i-1 ] == a [ j ])
                 return false;
            }
     } return true;
}

(2) 若%r13等于6,则跳出循环。跳转到0x401153
在这里插入图片描述
地址%rsp+0x18正好指向输入数字存储区域之外,将该地址复制到%esi,%esi将作为条件结束的标志
将%r14(即%rsp)存储的地址复制到%rax
将%edx的值置为7
用7减去%rsp中存储的值(即第一个数),并将差放入原来输入数值的存储单元中
%rax加4,则%rax指向下一个数(第二个数)
判断结束条件,若未结束,跳转到0x401160
继续用7减去第二个数,将结果存入第二个数原来的存放单元
以此类推,以所有数分别为减数,7为被减数,得到的差存入内存单元
循环结束,即%rax等于%rsi时,继续执行
在这里插入图片描述
将%esi的值置为0
跳转到
0x401197
在这里插入图片描述
在这里插入图片描述
将位于地址%rsp + %rsi中的数(即各个内存单元内存储的数)存入%ecx中
比较该数与1,若该数小于等于1,跳转;大于1继续执行
(1) 若%ecx小于等于1
在这里插入图片描述
在这里插入图片描述
将%edx的值设置为地址0x6032d0(重置链表)
从%rsp+0x20处开始,将%rdx的值放入后者地址对应的内存单元中(创建链表节点)
%rsi加4,即指向下一个内存单元中的数
判断创建链表是否完成,若完成跳转;未完成,继续执行。
(a) 若创建未完成
在这里插入图片描述
将地址为%rsp +% %rsi处存储的值(第一个数)放入%ecx中,比较该数与1
若该数小于等于1,同样地将0x6032d0放入%rdx中,重置链表
这里的循环操作是为了建立一个以0x6032d0为首地址的链表结构,且链表中的索引值按顺序排列存储
关键部分:
在这里插入图片描述
在这里插入图片描述
这部分操作的目的即为将第n个node的地址存入对应索引n的内存单元
在这里插入图片描述
(b) 创建完成,跳转到*0x4011ab
在这里插入图片描述
%rbx存储第一个node的地址对应的内存单元的地址
(由前面可知内存单元存放的是节点地址),
%rax存储第二个node的地址对应的内存单元的地址,
%rsi存储最后一个node的地址对应的内存单元的地址(判断结束)
将%rbx的值复制给%rcx
将%rax存储地址对应的值(即第二个node的地址)放入%rdx中
关键步骤:将第二节点的地址放入第一个节点的后八个字节
%rax加8,即下一个node,以此类推,将链表连接起来。
判断是否结束,若结束跳转;未结束则继续执行

创建完成,查看链表情况:
在这里插入图片描述
我们可以发现链表的每个节点占16个字节,后八个字节为链表的下一个节点地址
前八个字节的最后四个字节存储值看起来跟索引值有关(即前面得到的7-输入数得到的差)
画个图表示一下:
在这里插入图片描述
查看链表中各节点存储情况:
在这里插入图片描述
在这里插入图片描述
我们可以发现,每个节点的最高四个字节存储着一些数值。此时不能确定该数值是什么,我们继续往下看。
创建链表结束,跳转到*0x4011d2
在这里插入图片描述
第六个节点指向的值为0
%ebp的值置为5
将第六个节点地址放入%rax
对应的值(这里的值就是之前我们不知道不清楚的存在node中的值)放入%eax
若第五个节点指向值大于第六个节点指向值,(必须大于)
将第六个节点与第五个节点交换(节点指向值大的放前面)
%ebp减1
进入循环(%ebp不等于0),直到%ebp为0
则这部分汇编检查链表中的数据是否为降序排列
分析到这里,我们已经非常清楚phase_6在干什么了
(1) 输入六个数字,记为ai
(2) 用7分别减去这六个数字,得到的值记为bi
(3) 以bi为索引,创建节点,连成链表
(4) 将链表中的数据降序排列
(5) 成功执行
回溯流程:存储的数据降序排列
在这里插入图片描述
降序排列后
在这里插入图片描述
此时节点值按顺序为 3 4 5 6 1 2
注意这些值为前面得到的7-输入数得到的差
则原来输入的数应为:
4 3 2 1 6 5

在这里插入图片描述
phase_6破解!