接着上一篇文章,我们来继续分析几个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破解!