这题居然只有我一个人做出来,让我很吃惊。
因为这道题目体积很小,所以9月14日PWN的三道题都做完的时候,我就打开看了一眼。
程序很简单,没有弯弯绕绕,是我喜欢的风格。里面直接判断输入长度然后转换成指令,也很干脆明了。
唯一有点难度的就是对虚拟指令以及数据的分析工作,大概花费了我两个小时的工作(因为中间还干其他事情去了)。
下面的分析是我这两个小时的结果:
1、虚拟指令只有9条,算上7代表的6条指令也一共只有9+6-1=14条;
2、可以使用的寄存器只有5个(算上i可以是6个),实际可以保存数据用于操作的只有2个;
3、有一块长度为128的ram(4143F0)和一块长度为128的rom(413FF0),且ram里的数只能和ram里的数运算。
4、寻址方式只有一种,很单一,因此如果要完成数据的操作,需要反复加减i来移动指针取值(而且加i要比减i多一个),导致算法不可能太复杂。
5、ram的取值和最终结果的取值都是0-0x7f的范围,连续无重复。rom里面的是0-0xff范围,间断无重复。
根据ram的规律,相信很多人一下就反应过来了,这应该是一个交换排序操作,而且因为操作9里面的case5的大小比较才能有条件的设置标志寄存器,所以这是一个大小排序问题。
那么数据交换的方法其实有很多,但是根据第二条用于操作的寄存器只有2个,那么只有异或和+-两种交换方式,由于题目里没有类似a=b-a的运算,所以这个题目里能够用到的只能是a=a^b,b=b^a,a=a^b这种形式,直接排除了其他的运算。
然后因为地址范围是128,而题中又给出了reg1_4157E4 = dw_4143F0[reg1_4157E4];这样的操作,所以下标除了是i,也可以是ram里面的数据,但不能是rom。
所以这道题目的意图很明显,就是找出一种操作,把ram里的数据按照rom里的规律进行排序。
题目分析到这里,我就觉得很简单,应该只需要一点脑洞就能做出来,分应该不多,于是做其他题目去了。(主要是不知道这点脑洞需要多长时间。)
直到某一天晚上说这道题目给出了提示。。。。才发现当初的“简单题”居然还没有人攻破。
于是那一天的第二天,被传家之宝里面算法绕晕的我开始继续研究这道题。
因为经过了127*127轮的排序,所以ram里面的初值是什么其实意义不大,关键是最终结果和rom里面的关系,而且因为排序依据只能是大小,rom又不能做下标,所以参与比较的数据只能是rom[i],rom[ram[i]],rom[ram[ram[i]]]……这种形式,而且i只有i++和i--的移位方式所以肯定是相邻i的比较。(为什么不考虑i和ram[i]的比较?因为题目给的是127轮啊,刚好是128个数之间的间隔;而且rom不能为下标又必须和rom扯上关系,只能拿来当比较对象了。)
把ram的最终结果放到ram里面,把rom[i],rom[ram[i]],rom[ram[ram[i]]]打印成3竖列,都不用分析就可以看到第二列存在很明显的大小顺序。
既然弄清楚了排序的目标,那么很快就可以用c实现对应的排序算法,然后转化成目标指令。刚开始写的时候发现有近40个字节,于是开始精简,直到32个字节的时候再也精简不下去了(当然也没有精简的动力了),代码果然短小精悍,是我喜欢的类型。因为实现排序的算法,很多指令顺序可以调换,于是我把最满意的结果输到程序里
Wrong! You are close to the result!
意料之中,我把结果换换顺序
Wrong! You are close to the result!
意料之中,我把结果再换换顺序
Wrong! You are close to the result!
意料之中,我把结果再再换换顺序
Wrong! You are close to the result!
意料之中,我把结果再再再换换顺序
。。。
一个半小时过去了。。。
Wrong! You are close to the result!
。。。
我放弃了,看了我的脑回路果然还是和出题人不一样啊。
只能祭出我的vs工具,开始写代码,因为不会密码,没有sha256的c代码,没法实时计算hash是否和目标相等,所以只能把符合条件的所有指令打印出来用py计算。
我先遍历那些最有可能、长得最后看的结果,然后一步步增加枚举量,最后可以得出正确hash的指令枚举代码片段如下:
void prt2(FILE *fp,BYTE *table)
{
char Tmp;
char p[30];
char sTmp[10];
if(table[0])
{
strcpy(p,"4304274931429304290");
}
else
{
strcpy(p,"4204374921439204390");
}
if(table[1])
{
Tmp = p[1];
p[1] = p[2];
p[2] = Tmp;
}
if(table[2])
{
memcpy(sTmp,p + 4,5);
memcpy(p+5,sTmp,5);
p[4] = '1';
}
if(table[3])
{
memcpy(sTmp,p + 11,3);
memcpy(p+12,sTmp,3);
p[11] = '0';
}
if(table[4])
{
memcpy(sTmp,p + 16,2);
memcpy(p+17,sTmp,2);
p[16] = '0';
}
if(table[5])
{
if(table[2])
{
memcpy(sTmp,p + 0,6);
memcpy(p+2,sTmp,6);
p[0] = '7';
p[1] = '4';
}
else
{
memcpy(sTmp,p + 0,5);
memcpy(p+2,sTmp,5);
p[0] = '7';
p[1] = '4';
}
}
fprintf(fp, p);
}
void prt1(FILE *fp,BYTE *table)
{
char p[4][10] = {"4562","14563","8","75"};
for(int i=0;i<4; i++)
{
for(int j=0;j<4; j++)
{
if(table[j] == i)
fprintf(fp,p[j]);
}
}
}
因为条件没有枚举完就得到答案45621456375897443042931429304290了,所以我最后得到的txt只有不到30K,但是整个遍历随着情况的增加是指数级上升的,有兴趣的同学可以试试把所有满足排序的指令枚举出来看看究竟有多少。