迷宫问题算是比较有套路可寻的一类问题,特点:
- 内存中藏着一副迷宫地图。
- 限制用户输入的为几个指定的字符,通过这些字符来控制移动的方向。
- 一般只有一个入口和出口,从入口到出口的路径就是flag。
核心思路就是通过分析代码找到迷宫的地图,分析出控制移动的字符的作用,找到入口和出口,走完迷宫。
迷宫地图大小及存在方式是不固定的,有可能是88,也有可能是55,需要具体分析,地图可以由明确的字符(如#和*,其中#代表墙壁),也可以单纯用不可显的十六进制值进行表示,迷宫具体怎么布置需要具体分析,注意, 布置并非按顺序布置, 每行都对应一个具体的行号, 你需要确定行号才能还原迷宫地图。
而被限制的字符通常会是一些方便记忆的组合(不是也没办法),如通过w(上)a(左)s(下)d(右)控制移动,或是一些奇奇怪怪的组合,各个键具体的操作需要经过分析判断,对于二维的地图, 一般出题人都会设置一个X坐标和一个Y坐标用于保存当前位置.(平面直角坐标系)。我们也可以根据这个特点来入手分析。
一般而言入口和出口都只有一个,如左上角为入口,右下角为出口,但出题人也有可能将入口设置在地图中间等等,需要根据题目进行分析判断。
至于迷宫怎么走也有说法,有些迷宫题可能要求某种特定的走法,需要写一个算法来走迷宫。
2020-HGAME maze
很典型的一道迷宫题,题目找不到了。。。。。
主函数中有⼀个while循环来判断每⼀位的输⼊,这⼏个判断还是很明显的, “wasd”4个字符很容易想到
上下左右。
v6是⼀格char类型的指针,指向的就是初始位置’d’中对应的 v6 += 4 可以理解为往右移动4列’s’的话,其实相当
于往下移动 64/4 = 16⾏’a’和’w’也是同理,那么unk_6020C4这⼀⽚的内存就存储着迷宫。
迷宫地图
unk_60243C就是出⼝,输⼊ ssssddddddsssssddwwdddssssdssdd 就能⾛出迷宫,并得到最后的flag:hgame{ssssddddddsssssddwwdddssssdssdd}
bugku Take the maze
泽佬送来的题,就是牛逼。以下是泽佬的WP,个别地方稍作修改。
题目:https://ctf.bugku.com/files/ac0ce298c7ac34faf095491e4c8d0cc4/ConsoleApplication1.exe
参考资料:
https://www.cnblogs.com/sweetbaby/p/10920746.html
https://www.cnblogs.com/reddest/p/10121550.html
https://www.52pojie.cn/thread-674404-1-1.html
https://www.sohu.com/a/227635770_354899
https://blog.csdn.net/charlesgodx/article/details/84866365
将文件载入到IDA中,待IDA分析文件时,先看看程序是是否加壳:
还好没有壳,要不然IDA的分析白费了。。。
在IDA中搜索字符串,如下图所示:
在图中我们可以注意到有“flag.png”,猜测当我们输入正确的字符串后,会打印出正确的flag图片,那爆破一下不就行了。。。
直接将文件扔到OD中,找到flag.png,双击进入:
想方设法让程序执行到这里就行了,在OD中跳到“show me your key:”的位置,执行到此:
在输入之前在地址007157B0处下断点(根据%s可以判断call 0070D846大致类似于C语言中的printf函数),F9执行,再黑窗口中输入“123456789”,回车执行,接下来F8单步执行。
当程序执行到地址007157D7的call 0070BF7D后,程序退出。
重启OD来到上面的je short 007157E3处,修改寄存器中的zero flag标志位为1,实现跳转,F8继续
来到:
此时跳转未实现,修改标志位已经不行了,将jge short 00715864修改为解密jmp short 00715864,F8执行:
同样修改标志位,步过几个call之后程序退出:
出来二维码,扫描一下,用QR_Research扫描一下:
emmm。。。。。。。。。老老实实走迷宫吧。。。
看IDA,双击“show me your key:”,然后右键交叉引用来到图标视图,F5查看伪代码
一点一点的看,sub_45E9C1函数明显为printf函数,修改一下。sub_45D846(“%s”, (unsigned int)v4);应为scanf语句(%s),修改一下,其中的v4也可以修改一下,为了简便就修改为input string吧。
遇见sub_45B1C7函数,双击看一下:,反调试函数,前面已经证明过它对OD无效…
给sub_45B1C7函数重命名为DebuggerPresent,接下来的sub_45C748函数一层层的双击进去,如下图。
看不懂诶,应该是某种加密函数,将sub_45C748重命名为Encodestring,接下来来到for循环验证,双击sub_45BF7D();有:key error,给他重命名一下为ErrorAndExit。对for循环中的数字按下R键
继续向下来到sub_45E593,由于下面有done!!!The flag is your input\n,将sub_45E593重命名为Great(真的不知道给它命名什么才好…),sub_45D9C7(4);和sub_45E1B5();分别命名为print1和print2。将注释加上,成果如下图:
由于IDABug原因(应为:v5^=1u;)
接下来进入Great函数看一下,在LABEL_25:中有byte_541168[v8],看一下是什么字符串:
R键后:
byte_541168[v8]为delru0123456789,给byte_541168[v8]重命名为delru0123456789。
出现了4个新函数:sub_45CC4D、sub_45D0A3、sub_45CB0D、sub_45D0E9,看了一遍发现每个函数的长相类似,那只看第一个就好了,进入sub_45CC4D:
在这里注意:容易忽略的一个地方就是反编译出来的函数类型,并不是所有的函数都有返回值,就像这4个函数一样:函数返回以后没有对其他进行赋值操作,那么,这四个函数应该都是void类型的函数,然而,反编译出来的却是int型….修改一下类型:
舒服多了,回头再看sub_463480(就是那个switch函数),我想着sub_463480传进来的参数不是char 类型的嘛,怎么变成了int类型:修改一下:
舒服….是时候拿出犹大了,是时候按下R键了
继续分析:
在前面已经知道a1是我们传进函数的字符串,修改一下名称:Str;
前两个switch前分别取传进来参数的两个字符(每循环一次取两个,那就将v6重命名为Pop),
按下R键之后可以看出,最后一个switch中v8起到控制方向的作用,重命名为dir
在函数sub_463480的参数delru0123456789[v7]中可以看出v7是走几步,重命名为Step,再将4个函数重命名,如下图
这样,我们可以得出一个结论:前两个switch共用数组delru0123456789,并且第一个switch控制方向,第二个switch控制步数,最后一个switch起到校验和移动的作用。
整理一下:输入的字符串经过处理后得到新的字符串->第一个switch取第单数个新字符控制方向,第二个switch取第偶数个新字符步数->校验->移动,dir范围是dlru;step范围是5-15
由于step范围是5-15,那么在数组中对应的数字为0-9,对应ASCII码的范围是48-57,那么delru0123456789[Step]的参数为0-9,双击进入Down_Check函数,因此a2的范围是0-9。(a1也就是v5,v5一开始为0)
在这四个函数中,运算基本上都和26有关。在计算机中,迷宫的存在就相当于数组的地址一样—-连续,既然要想实现迷宫的上下移动(在竖直方向上不偏移位置),那么在这道题的地址中移动就要满足26的整数倍,这样才能实现迷宫的上下移动。在sub_463480函数中,因为此函数为BOOL型的函数,因此它的返回值只有false和true,也就是说他有返回值,在此函数中,能返回布尔型的只有一个地方,如下图所示:
当v5==311时,返回true,而311 = 1126 + 25,也就是说长宽至少为1226,从左上角移动到右下角即可。
并且Down_Check函数是先判断再移动,也就是i的值必须写入到a1(v5)中才算成功,基于v5一开始为0,那么毫无疑问v5为已经移动的步数,并且起点为迷宫的开头。
那么迷宫长什么样子?从四个函数中的异或我们可以反推出迷宫:
dword_540548[i] ^ dword_540068[i]
dword_5404DC[i] ^dword_53FFFC[i]
dword_5404E4[i] ^ dword_540004[i]
dword_540478[i] ^ dword_53FF98[i]
可以用 IDA的idc脚本dump出地图:
*idc脚本语法:https://blog.csdn.net/charlesgodx/article/details/84866365
auto i;
for(i = 0;i <= 311; ++i){
if(Dword(0x540548 + i 4) ^ Dword(0x540068 + i 4))
Message(“.”);
else
Message(“↓”);
if(Dword(0x5404DC + i 4) ^ Dword(0x53FFFC + i 4))
Message(“.”);
else
Message(“←”);
if(Dword(0x5404E4 + i 4) ^ Dword(0x540004 + i 4))
Message(“.”);
else
Message(“→”);
if(Dword(0x540478 + i 4) ^ Dword(0x53FF98 + i 4))
Message(“.”);
else
Message(“↑”);
Message(“ “);
if(!((i + 1) % 26))
Message(“\n”);
}
return 0;
i * 4的原因是Dword为4字节
这里把dlru替换为箭头使地图看着更加直观,每个格子中的箭头代表下一步可以往哪个方向走,例如:![8BXE%JYZ5U94NLD0848X~4.png为下一步可以向下走,为可以向上下右三个方向走。
写出解法字符串:06360836063b0839073e0639
然后把06360836063b0839073e0639输进去,验证一下,结果还是不对….
想起来之前有一个看不懂的加密函数:
原函数为sub_45C748,打开OD直接调试看看结果,因为输入的字符串必须为24位,所以输入
012345678901234567891234,回车,在数据窗口中跟随有:0000000000::>>::’&**%’%#
得到:0000000000::>>::’&%’%#,但加密还是看不懂….
偶然我把0000000000::>>::’&%’%#输入,惊奇的发现:
嘿嘿…反过来不就行了?!(骚操作)
将06360836063b0839073e0639代入跟随数据窗口得到:
结果为07154=518?9i<5=6!&!v$#%.
zsctf{07154=518?9i<5=6!&!v$#%.Docupa}
自己写的解密代码:
两次异或为其本身
为什么是:str[16]^=1u;呢?
难道是双击v5和input string后,他们两个在一起的原因?(如下图)
晕…….
附上参考博客的解题脚本:
结果为07154=518?9i<5=6!&!v$#%. zsctf{07154=518?9i<5=6!&!v$#%.Docupa}