ACTF2020 Oruga
做题之前我得到了学长给的一个hint:这是一道地图题
我当时不明白为什么学长要强调这个,之前我做的地图题都很简单,很快就能看出来解法。
直到我在ida里卡住不得已看题解之后才理解。故以下内容非做题历程,而是总结视角:
首先定位main函数:
{
__int64 result; // rax
int i; // [rsp+0h] [rbp-40h]
char s1[6]; // [rsp+4h] [rbp-3Ch] BYREF
char s2[6]; // [rsp+Ah] [rbp-36h] BYREF
char input[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v8; // [rsp+38h] [rbp-8h]
v8 = __readfsqword(0x28u);
memset(input, 0, 0x19uLL);
printf("Tell me the flag:");
scanf("%s", input);
strcpy(s2, "actf{");
for ( i = 0; i <= 4; ++i )
s1[i] = input[i];
s1[5] = 0;
if ( !strcmp(s1, s2) )
{
if ( check(input) )
printf("That's True Flag!");
else
printf("don't stop trying...");
result = 0LL;
}
else
{
printf("Format false!");
result = 0LL;
}
return result;
}
从main函数不难看出,input即flag,并且flag前五个字符是actf{
先验证前五个字符,然后通过check
函数验证flag后续内容。
进入check
函数:
{
int v2; // [rsp+Ch] [rbp-Ch]
int v3; // [rsp+10h] [rbp-8h]
int v4; // [rsp+14h] [rbp-4h]
v2 = 0;
v3 = 5;
v4 = 0;
while ( map[v2] != 33 )
{
v2 -= v4;
if ( *(input + v3) != 'W' || v4 == -16 )
{
if ( *(input + v3) != 'E' || v4 == 1 )
{
if ( *(input + v3) != 'M' || v4 == 16 )
{
if ( *(input + v3) != 'J' || v4 == -1 )
return 0LL;
v4 = -1;
}
else
{
v4 = 16;
}
}
else
{
v4 = 1;
}
}
else
{
v4 = -16;
}
++v3;
while ( !map[v2] )
{
if ( v4 == -1 && (v2 & 15) == 0 )
return 0LL;
if ( v4 == 1 && v2 % 16 == 15 )
return 0LL;
if ( v4 == 16 && (v2 - 240) <= 15 )
return 0LL;
if ( v4 == -16 && (v2 + 15) <= 30 )
return 0LL;
v2 += v4;
}
}
return *(input + v3) == '}';
}
函数很长,第一步要做的并不是闷头读代码。首先先确认传参的类型是否正确。根据main函数,显然,传参应该是一个数组input。我的数组学的很差,这里放一下菜鸟教程的介绍:
(
可见这里的input是一个指针。我的ida并未识别,这里需要手动填上*
成功修改类型之后,看见这行代码( *(input + v3)
则立刻能意识到,此时开始探究的是input数组每一位上的内容了。
已知地图题分为以下几部分:
-一个存放地图格子的大数组
-上下左右移动键位
-初始位置/终止位置
-边界
-一些退出条件,如触碰边界等
-其他。
显然map数组是存放地图格子的数组,而上下左右移动的判定应该在地图边界判定之前,此时虽然还没有细读代码,但应该知道第一串if的作用是判断上下左右的位移。
由于map[v2]!=33是移动循环的前提条件,可见出口就是33了。而十进制的33对应16进制的21,也就意味着当移动到地图中数字21
时,则停止.
点开map数组,通过hex窗口导出地图:
浮于表面的信息就到这里,剩下的信息需要仔细读代码。但既然已经知道是地图题,那么适当的猜测应该会加快做题的速度。
v3 = 5;
v4 = 0;
已知第二个while是根据v2来进行判断的:while ( !map[v2] )
因此不妨猜测v2就是当前所在的坐标。
if ( *(input + v3) != 'W' || v4 == -16 )
{
if ( *(input + v3) != 'E' || v4 == 1 )
{
if ( *(input + v3) != 'M' || v4 == 16 )
{
if ( *(input + v3) != 'J' || v4 == -1 )
return 0LL;
v4 = -1;
}
在这里,可以看见if语句先对input数组的第v3-4位进行验证,如果不是WEMJ这四个字母,那么就直接return 0。这意味着flag里只有WEMJ这四个字母。
接着再看v4,我们知道v2在后续的验证中是有复现的,而v2取决于v4:v2=v2-v4
假如v4=-1,那么v2=v2+1。也就是v2坐标右移一位。那么就可以猜测,v41意味着左移一位,v4-16意味着下移一位,v4==16意味着上移一位。
则地图应该是16个数字为一行的。
同时点开map数组并选择array
此时会发现map总共有256个数字,正好是1616,则地图是一个1616的正方形。第二个while也可以为这个猜测作证。
{
if ( v4 == -1 && (v2 & 15) == 0 )
return 0LL;
if ( v4 == 1 && v2 % 16 == 15 )
return 0LL;
if ( v4 == 16 && (v2 - 240) <= 15 )
return 0LL;
if ( v4 == -16 && (v2 + 15) <= 30 )
return 0LL;
v2 += v4;
}
第二个while函数意味着,map上的数字若不为零,则要进入判定并且v2=v2+v4
。换句话说,如果map上数字是0,那么就不用进入循环,同时v2也不会发生改变。
先把判断条件放一边,暂时不分析。v2如果发生了改变,对此时这个while函数的下一个循环有什么影响呢?答案是影响循环的继续。v2每次+v4,意味着每次都朝着同一个方向移动一格,map[v2]发生改变,则下一个循环未必能进入。直到map[v2]不是00的时候,v2不再变化,坐标不再改变,同时进入第一个while的新循环。
这意味着坐标在map[v2]!=0之前会持续的朝着某个方向移动。这也就是本题的特殊规则。
再分析第二个while里四个if返回0的条件。
( v4 == -1 && (v2 & 15) == 0 )
当v216*n且v4-1;即位于最左侧一列同时得到了左移一位的指令
( v4 == 1 && v2 % 16 == 15 )
当v215*n且v41;即位于最右侧一列同时得到了右移一位的指令
( v4 == 16 && (v2 - 240) <= 15 )
这句我不懂,v2是0-255之间的数字,无论v2为何值,似乎只要v4是16就会触发这个if条件。但是猜测一下,这个是最底下一行的时候向下位移则返回0的意思。
( v4 == -16 && (v2 + 15) <= 30 )
当v2属于0-15的时候不能向上移动。
这是一个边界判定。
以上是这个地图的全部规则。
接下来开始手走地图。
依据上述分析可知,v416下移,v4-16上移,v4-1左移,v41右移。
{
if ( *(input + v3) != 'E' || v4 == 1 )
{
if ( *(input + v3) != 'M' || v4 == 16 )
{
if ( *(input + v3) != 'J' || v4 == -1 )
return 0LL;
v4 = -1;
}
else
{
v4 = 16;
}
}
else
{
v4 = 1;
}
}
else
{
v4 = -16;
}
据这段代码可知,例如希望v4=16,则要么v4=16要么*(input + v3) != 'M' 触发else使得v4=16.
因此可将WEMJ分别和上右下左对应起来。
则flag为:actf{MEWEMEWJMEWJM}