引言
不得不说,这绝对是是我并不长的CTF生涯中做过的最魔幻的一道逆向题。
看上去非常简单但就是做不来,由这道题也可以发现自己其实很多基础的知识也没有掌握好。
下来看了各位师傅的wp再加上自己操作了半天才搞明白是怎么回事。pizzatql!!!
正片
dbl与输入的38位flag进行比较,嗯,显而易见。
sub_13F3 函数,读入flag的两个字符转化成数字进行迭代运算,然后输出,嗯!
unk数据不知道是多少,但是我们知道flag前两位肯定是‘fl’,于是我们决定写脚本反求unk的值,这里选用double浮点数并输出19位小数。
//注:以下为原理演示,非完整代码
//先介绍一下拼接函数,将a,b拼在一起,但因为是小端储存,所以最后输出要倒一下
//例:输入cpy(a,b) 输出9897
int cpy(int a,int b){
int n,m;
m = a / 10;
if(m == 0) n = 10;
else if(m > 0 && m < 10) n = 100;
else n = 1000;
return b * n + a;
}
//下面是逆推的一部分
for(int i = num - 1;i >=8225; i--)
v = (2.718281828459045 - v) / i;
printf("%.19lf",v);
发现unk的值是0.0003304500204990972。
唉,这不就直接顺推爆破就行了嘛,于是写出爆破脚本输出:inf,inf,inf。。。
我直接???
行吧,可能是哪里内存炸了吧,想想看有没有其他路。
既然我们可以逆推算unk,有了unk我们也能逆推算flag吧,好的再写个脚本。
const double eps = 1e-8;
//a==b 浮点数降低误差比较
#define Equ(a,b) (fabs((a)-(b))<1e-8)
//布尔函数,num为cpy输出,如果该num经逆运算处理后符合unk,则为真,否则为假
bool cmp(int num,double a){
double v = a;
for(int i = num - 1;i >=8225; i--){
v = (2.718281828459045 - v) / i;
}
if(Equ(v,unk)) return 1;
else return 0;
}
//flag输出部分,a为dbl数组
for(int n = 0;n < 19; n+=2)
for(int i = 32;i < 127; i++)
for(int j = 32;j < 127; j++){
int num = cpy(i,j);
//printf("%d %d\n%d\n",i,j,num);
if(cmp(num,a[n])){
flag[n] = i;
flag[n + 1] = j;
}
}
printf("%s",flag);
结果,flag输出为“~~~~~~~~~~~~~~~”……
神奇的事情就来了,经过几次探索和研究大佬wp,最终发现,逆推函数,无论读入的字符串值为多少,
无论读入dbl数组的值为多少,最终v的值都会等于unk=0.0003304500204990972
wtf???为了更直观的表示,我们在逆推函数处理过程中打个表
附上完整打表脚本:
#include <stdio.h>
#include <math.h>
#include <windows.h>
const double eps = 1e-8;
//a==b
#define Equ(a,b) (fabs((a)-(b))<1e-8)
double unk = 0.0003304500204990972;
/*double func(int a1)
{
int i;
double v3 = 0.0003304500204990972;
for ( i = 8225; i < a1; ++i ){
v3 = 2.718281828459045 - (double)i * v3;
printf("%.19lf\n",v3);
}
return v3;
}
*/
int cpy(int a,int b){
int n,m;
m = a / 10;
if(m == 0) n = 10;
else if(m > 0 && m < 10) n = 100;
else n = 1000;
return b * n + a;
}
bool cmp(int num,double a){
double v = a;
for(int i = num - 1;i >=8225; i--){
v = (2.718281828459045 - v) / i;
printf("%d %.19lf\n",i,v);
}
if(Equ(v,unk)) return 1;
else return 0;
}
int main()
{
double a[]={0.00009794904266317233, 0.00010270456917442, 0.00009194256152777895,0.0001090322021913372, 0.0001112636336217534, 0.0001007442677411854,0.0001112636336217534, 0.0001047063607908828, 0.0001112818534005219,0.0001046861985862495, 0.0001112818534005219, 0.000108992856167966,0.0001112636336217534, 0.0001090234561758122, 0.0001113183108652088,0.0001006882924839248, 0.0001112590796092291, 0.0001089841164633298,0.00008468431512187874};
char flag[38]={},dflag[38]={};
/*for(int n = 0;n < 19; n+=2)
for(int i = 32;i < 127; i++)
for(int j = 32;j < 127; j++){
int num = cpy(i,j);
//printf("%d %d\n%d\n",i,j,num);
if(cmp(num,a[n])){
flag[n] = i;
flag[n + 1] = j;
}
}
printf("%s",flag);*/
int s1 = 'z',s2 = 'z';
int num = cpy(s1,s2);
int k = cmp(num,a[1]);
printf("%d\n",k);
system("pause");
return 0;
}
//若是dbl[1]则121121位后五位0.0000222580811153681 0.0000222589221288727 0.0000222591043943769 0.0000222592866682606 0.0000222594689451295
//若是dbl[0]则121121位后五位0.0000222581200564717 0.0000222589221285538 0.0000222591043943769 0.0000222592866682606 0.0000222594689451295
替换dbl值比较发现从最开始处理三位后,就因为精度的问题后面每一个值都与i值一一对应,直到8225位时正好时unk的值
那么我们可以发现,这就像数组一样,与i一一对应。
例如:
当输入fl时,正好对应i值108102,而此时对应的值正好是dbl[0] = 0.00009794904266317233.
也就是v[108102] = 0.00009794904266317233;
那么我们将dbl后面的数在这个数组里一一对应的i值找出来,对应成字符并拼接,就可以得出flag
思路大概就是这样,(我觉得已经讲的非常清楚了)
最后的解密脚本不想写了,直接附上答案:flag{saam_dim_gei_lei_jam_caa_sin_laa}
一些小总结(吐槽)
所以这到底是个什么鬼题?考智商的吧……
其实最开始是卡在一个非常蠢的点上的:sub_13F3居然是读入两个字符……
证据就是ida括号里写着的数据大小。从此以后ida自己给的数据类型我是一个字都不会信了
还有,unk就是unknown,本来要靠动调之类的调出来的,比如这题内存中的unk值根本就不是求出来的那个
这份wp只是我自己总结的一点思路,参考参考就好,其他师傅们还有更多神奇的解法,我太菜了自然是看不懂了,嗯!
就这样吧,摸了。
- 本文链接:http://example.com/2021/06/16/2021%E5%BC%BA%E7%BD%91%E6%9D%AFezmath/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。