攻防世界逆向高手题之reverse-for-the-holy-grail-350
继续开启全栈梦想之逆向之旅~
这题是攻防世界逆向高手题的reverse-for-the-holy-grail-350
下载附件,照例扔入exeinfope中查看信息:
64位ELF文件,无壳,运行一下程序看一下主要显示字符串信息:
然后照例扔入IDA64中查看伪代码信息,有main函数看main函数:
.
.
(这里积累第一个经验)
C++前面做题经验中说过了看得是最后那个函数名,因为C++是面向对象的,没有使用"using namespace 类名; "
时就没有对应的命名空间,就只能用长类名导入函数。
.
附上以前的笔记:
所以下图1~4的红框都是C++的cout
输出函数和cin
输入函数,但是cin输入的都是v11,v11能被覆盖就说明不是关键,4、5红框中cin的对象是&userIn[abi:cxx11]
,这是一个地址,应该就是我们要的flag了
.
.
(这里积累第二个经验)
然后后面代码就渐渐看不懂了,只能从后面往前推,最后一个Auuuuuuuugh
是我们前面运行时显示的,由它出发跟踪到v4,前面v4 = stringMod(v9);
有一个自定义函数,而自定义函数通常是关键。然后再前面的_M_construct<char *>
结构体应该是简单的赋值把,把&userIn[abi:cxx11]
先给了v7再给了v9。(看不懂的话自能当它不是关键了,不然总是觉得_M_construct<char *>
会有什么隐藏操作的话就没法做了。)
.
.
(这里积累第三个经验)
双击跟踪入stringMod函数,代码有点长,以为有冗余代码,按照以前的经验,查找与输入相关的代码,如下面红框所示:a1是输入的flag,a1给了v2,v2给了v14,v14给了v5和v6,所以这里没有冗余代码,都是关键代码:
.
.
(这里积累第四个经验)
分析第一个循环,有点意思,循环条件是v3 != v1
,而v1 = a1[1]
,v3是从0开始的数,v1是一个字符,怎么也是从32开始的。所以这里一开始用从0一直循环到字符的ASCII码
,明显超出了flag输入的位数,一开始我也很懵,后来发现后面有flag位数限制,所以这里超了范围又有什么关系呢,后面限制回来不就行了?(后面限制了这个flag位数为18位)
然后就是这里的if语句,除法符号 ‘/’ 会有余数
,所以这里必须是3的倍数,这里的v4=-1一开始我不确定执行有没有影响,后来发现最后return (unsigned int)(v7 * v4);
语句表明这个if语句不能执行,所以 i 为 3 的倍数时flag必须等于firstchar
数组内的元素。
那么第一个循环就确认了flag的0、3、6、9、12、15、18位。
.
.
(这里积累第五个经验)
接下来分析第二个循环,这里必经一个循环,循环条件也是很有意思&v15 != (__int64 *)v6
取v15的地址和v6作比较,v6也是一个地址,关键是v6是rsi源地址寄存器寄存器
,没法跟踪栈内位置。后来查了很多资料,有人说rsi相当于rbp的位置,如第二幅图所示,v15地址是rbp-18
,所以循环条件是18,也就是输入的flag的位数。
但是也有人说rsi
是rsp
的位置,所以这里相差是48,和前面一样超出了flag的位数,但是最后的循环中限制了取18位进行flag操作,所以也不影响。(我更支持这个!)
那么这里必经的循环就是简单的异或操作的,和以前总结的一层、二层加密
一样,第一个循环的数没有加密,后面循环的数都是二层加密,解密时要考虑异或回来。
.
.
(这里积累第6个经验)
分析最后一个循环,这个循环比较复杂,所以放上总图一步步来:
.
.
首先v8从1开始,循环条件是到19,所以这里就是前面flag位数的最终确定和根源,把最重要的东西放在最后面也是够呛的,加大了题目难度。
.
.
然后后面就是不同位数的逻辑代码了,因为只有18位,所以一个个手算其实更方便:
先看v11的变化,v11从0开始:
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0
再看v9的变化,v9从0开始,下面v9与v11对应:
0 01
1 12
2 23
3 34
4 45
5 56
6 67
7 78
8 89
9 910
10 10
然后看v5的变化,v5是输入flag的首地址但是经过异或加密
,和第一个循环一样if成立则对应固定的异或后
的位数:
0 12
3 45
6 78
9 1011
12 1314
15 1617
18
标注的是v5对应的位数,也就是thirdchar数组对应着异或后的flag的2,5,8,11,14,17位
同样的在看第二个if条件对应的位数,这里v10和v15的关系有前又有后,其实跟踪起来还是很绕的,但是前面是2,5,8、0,3,6。也应该要想到这里是1,4,7。18位的flag三层循环对应三份位数。
v11:
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0
v10从1开始,v10 *= *v5,因为v10在v5之前,所以v10永远比v5少一:
1 v5[0]v5[0]v5[1]
1 v5[3]v5[3]v5[4]
1 v5[6]v5[6]v5[7]
1 v5[9]v5[9]v5[10]
1 v5[12]v5[12]v5[13]
1 v5[15]v5[15]v5[16]
v5,因为v5在v10赋值之后,所以v5永远比v10多一:
01
2 34
5 67
8 910
11 1213
14 1516
17 18
.
.
流程基本梳理完了,现在可以写脚本了,在那之前先dump下三个数组内容:
addr=[0x601840,0x601860,0x601880]list1=[]for i in range(6):for a in addr:list1.append(Dword(a+4*i))print(list1)
.
.
逆向逻辑脚本:
key1=[65, 105, 110, 69, 111, 97,]key2=[751, 708, 732, 711, 734, 764]key3=[471, 12, 580, 606, 147, 108]flag=[0 for i in range(18)]index=0for i in range(0,18,3):flag[i]=key1[index]index+=1print(flag)v7=666xor=[]for i in range(18):xor.append(v7)v7+=v7%5print(xor)index2=0for i in range(2,18,3):flag[i]=key2[index2]^xor[i]index2+=1print(flag)index3=0for i in range(1,18,3):for a in range(32,128,1):if ((flag[i-1]^xor[i-1])*(a^xor[i]))%(flag[i+1]^xor[i+1])==key3[index3]:flag[i]=aindex3+=1breakprint('tuctf{'+''.join(map(chr,flag))+'}')
.
.
(这里积累第7个经验)
注意后面的逆向要考虑回一层的异或加密,所以这里直接把异或加密位数导出来,因为flag只有18位,所以我们也取前18位即可。
然后就是这个v10 % *v5 != masterArray[v9]
的逆向,这个很难逆,我看了很多资料都显示直接正向爆破。因为i-1
和i+1
在前面循环中都获取了,所以这里可以直接用,然后注意时刻考虑一层的异或加密即可。
.
.
结果:
.
.
总结:
1:
(这里积累第一个经验) C++前面做题经验中说过了看得是最后那个函数名,因为C++是面向对象的,没有使用
"using namespace 类名; "
时就没有对应的命名空间,就只能用长类名导入函数。 . 附上以前的笔记:.
2:
(这里积累第二个经验)
然后后面代码就渐渐看不懂了,只能从后面往前推,最后一个
Auuuuuuuugh
是我们前面运行时显示的,由它出发跟踪到v4,前面v4 = stringMod(v9);
有一个自定义函数,而自定义函数通常是关键。然后再前面的_M_construct<char *>
结构体应该是简单的赋值把,把&userIn[abi:cxx11]
先给了v7再给了v9。(看不懂的话自能当它不是关键了,不然总是觉得_M_construct<char *>
会有什么隐藏操作的话就没法做了。)
3:
(这里积累第三个经验)
双击跟踪入stringMod函数,代码有点长,以为有冗余代码,按照以前的经验,查找与输入相关的代码,如下面红框所示:a1是输入的flag,a1给了v2,v2给了v14,v14给了v5和v6,所以这里没有冗余代码,都是关键代码。
4:
(这里积累第四个经验) 分析第一个循环,有点意思,循环条件是
v3 != v1
,而v1 = a1[1]
,v3是从0开始的数,v1是一个字符,怎么也是从32开始的。所以这里一开始用从0一直循环到字符的ASCII码
,明显超出了flag输入的位数,一开始我也很懵,后来发现后面有flag位数限制,所以这里超了范围又有什么关系呢,后面限制回来不就行了?(后面限制了这个flag位数为18位).
然后就是这里的if语句,除法符号 ‘/’ 会有
余数
,所以这里必须是3的倍数,这里的v4=-1一开始我不确定执行有没有影响,后来发现最后return (unsigned int)(v7 * v4);
语句表明这个if语句不能执行,所以 i 为 3 的倍数时flag必须等于firstchar
数组内的元素。那么第一个循环就确认了flag的0、3、6、9、12、15、18位。
5:
(这里积累第五个经验) 接下来分析第二个循环,这里必经一个循环,循环条件也是很有意思
&v15 != (__int64 *)v6
取v15的地址和v6作比较,v6也是一个地址,关键是v6是rsi源地址寄存器寄存器
,没法跟踪栈内位置。后来查了很多资料,有人说rsi相当于rbp的位置,如第二幅图所示,v15地址是rbp-18
,所以循环条件是18,也就是输入的flag的位数。.
但是也有人说
rsi
是rsp
的位置,所以这里相差是48,和前面一样超出了flag的位数,但是最后的循环中限制了取18位进行flag操作,所以也不影响。(我更支持这个!).
那么这里必经的循环就是简单的异或操作的,和以前总结的
一层、二层加密
一样,第一个循环的数没有加密,后面循环的数都是二层加密,解密时要考虑异或回来。
6:
(这里积累第6个经验) 分析最后一个循环,这个循环比较复杂,所以放上总图一步步来:
.
首先v8从1开始,循环条件是到19,所以这里就是前面flag位数的最终确定和根源,把最重要的东西放在最后面也是够呛的,加大了题目难度。
.
然后后面就是不同位数的逻辑代码了,因为只有18位,所以一个个手算其实更方便:
.
先看v11的变化,v11从0开始:
0 1
2
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0.
再看v9的变化,v9从0开始,下面v9与v11对应:
0 0
1
1 12
2 23
3 34
4 45
5 56
6 67
7 78
8 89
9 910
10 10.
然后看v5的变化,v5是输入flag的首地址
但是经过异或加密
,和第一个循环一样if成立则对应固定的异或后
的位数:0 1
2
3 45
6 78
9 1011
12 1314
15 1617
18.
标注的是v5对应的位数,也就是thirdchar数组对应着异或后的flag的2,5,8,11,14,17位
.
同样的在看第二个if条件对应的位数,这里v10和v15的关系有前又有后,其实跟踪起来还是很绕的,但是前面是2,5,8、0,3,6。也应该要想到这里是1,4,7。18位的flag三层循环对应三份位数。
.
v11:
0 1
2
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0 12
0.
v10从1开始,v10 *= *v5,因为v10在v5之前,所以v10永远比v5少一:
1 v5[0]
v5[0]v5[1]
1 v5[3]v5[3]v5[4]
1 v5[6]v5[6]v5[7]
1 v5[9]v5[9]v5[10]
1 v5[12]v5[12]v5[13]
1 v5[15]v5[15]v5[16]
.
.
v5,因为v5在v10赋值之后,所以v5永远比v10多一:
0
1
2 34
5 67
8 910
11 1213
14 1516
17 18
7:
(这里积累第7个经验)
注意后面的逆向要考虑回一层的异或加密,所以这里直接把异或加密位数导出来,因为flag只有18位,所以我们也取前18位即可。
然后就是这个
v10 % *v5 != masterArray[v9]
的逆向,这个很难逆,我看了很多资料都显示直接正向爆破。因为i-1
和i+1
在前面循环中都获取了,所以这里可以直接用,然后注意时刻考虑一层的异或加密即可。
解毕!敬礼!