开始逆向!
把文件拖入到ExeinfoPE里面,发现无壳,用IDA打开。
现在你进入了IDA,按shift+F12进入字串列表
you用错了,句子罚写十遍!
一眼就看见了flag{ 。双击‘flag{’字串。Ctrl+x找到引用后按F5查看函数伪代码。
答案很明显了,这个v5就是flag。由于本人习惯从近到远,因此我选择先看flag所在这一块的if里的函数sub_400917。双击进入。
sub_400917
三层循环,看起来好像有点麻烦……硬看想看出来点啥还是有难度的,不如代数看看吧。为了方便,我把unk_601060简写为unk。
首先让i,j为0,k从1到4,两个if对应的分别是
unk+0==unk+1,2,3,4
unk+0==unk+5,10,15,20
后面同理。五个一组不能相等……该不会是5x5的数独吧?
点开unk看看,果然是25个,不过有些空位。估计会有用,先给他填上。
挺好写的,答案是0421421430。肯定有用,先记下来。
然后我们回到main函数,看看if上面的sub_40881
sub_40881
有点像是输入。双击这些byte_6010系列,你发现对应的就是上面unk_601060空缺的部分。看样子v7里面存的就是刚才数独的答案。其实这个时候你就可以去试试了,程序要在linux里面打开,输入后会发现是错的。
继续看上面的sub_400807。
sub_400807
完了,这回是真蒙了。好多都是用的动调,翻了好久才找到答案……原来这是一棵树的中序遍历……哪怕现在看我也看不出来……
不过动调确实爽。这个函数全是递归和赋值,并没有修改值的过程。那么我只要动调查v7的排序和输入排序的关系也能明白这个函数到底在干什么。
这里的a1(对应main里面的v4)明显是个数组,另外假如a1真就单纯是个数的话这个函数就是个死循环……
v4给v7提供值,现在疑惑就在v4身上了。看看400807上面的sub_400758。
sub_400758
值得一提的是,第八行if判断中在v5的值上面按‘r’键会直接转换成ascii码对应的字符,按‘h’键会在十进制与十六进制之间转换。
嗯……递归……2a+1和2a+2……
懂的也都明白是啥了。这其实就是在生成一棵树,方法就是横着排。如果你学过大小根堆的话印象应该会更深一点。
这里放一下小根堆的定义(来源百度百科)。小根堆的特点是任意根都不大于它的子树。
如果有一个关键字的集合K={k0,k1,k2, ..., kn-1}, 把所有元素按完全二叉树的顺序存储方式存放在一个一维数组中,并且满足ki <= k2i+1 且 ki <= k2i+2 (i = 0, 1, ..., (n-2)/2 向上取整)则称这个集合为小根堆。
最上面的if看不看其实也就那样了。大概率就是个输入判断。
整个流程还是比较清楚的:你输入v5,v4是用v5生成的一个奇怪东西(其实是树,假设没看出来),它经过某种排列(其实是中序遍历)后存到了v7里面,v7就是5x5数独的答案。
如果你成功的看出来他是棵树并且用的是中序遍历,那么恭喜你这道题解完了。这种方式形成的树结构固定,只要往里面代数就可以了。
这里给出flag
flag{1134240024}
假如你没看出来,那么就要用到“逃课”方法:动态调试了。
动态调试
准备工作
首先你需要一个linux系统。没有的话优先推荐kali,里面有好多打misc能用到的软件。linux的具体安装方法网上有很多就不说了。然后启动,先把要调试的程序放里面。
值得一提的是,虚拟机和大部分安卓模拟器不共存。如果像我一样贪玩的话可以选择国际版的蓝叠模拟器。
之后,找到你ida的文件夹,打开里面的dbgsrv,把linux_server64拖到你的linux里面。
打开终端,输入
ip a
找到inet后面对应的ip地址,192.168开头的。
再输入
./linux_server64
来启动ida的服务端。如果成功会显示“Listening on 0.0.0.0:23946...”
它会默认占用23946这个端口。如果这个端口被使用会提示别的消息。一般来说重启一下虚拟机就好
打开ida中的调试器->选择调试器->remote linux debugger
注意,本人用的ida是中文破解版的,如果你的是全英的话可以参考我图中的位置。
之后调试器->进程选项
你会打开这么一个窗口。在主机名称里输入你刚才得到的192开头的ip,然后确定即可。
然后点击上方的绿三角启动就可以啦!
因为我这里犯懒没调文件位置,所以会有file missing这种提示。反正他也能找到
点击使用发现就可以了。
启动之后
你的ida就会进入一片蓝状态。虽然说变蓝了,除了多了几个窗口外和正常状态下并没有什么区别。
和刚才一样,shift+F12打开字串,或者左上角的跳转->跳转到函数(对应快捷键ctrl+p)找到main函数,按f5转化成伪代码
之后在伪代码那里右键->同步到->RIP,IDA View-RIP
其实不勾选也可以,不过这样会更方便一点。这回在伪代码中选中的行会变成绿色,同时在旁边的IDA View-RIP中也会以绿色提示伪代码选中行所对应的位置。
这时我们就要把前面没有分析的输入检测函数sub_4006D6分析一下了。
sub_4006D6
函数很简单,要求输入是十位并且在0-4之内(ascii码上‘/’的下一位就是'0')。
这个时候问题就来了。前面已经知道程序操作是对数字进行某种排列,十位0-4之间肯定有重复数字,如果想要试出来对应位置就需要不止一次输入。我想输入0-9,该怎么办呢?
想解决很简单,要么让他返回1,要么就让他不跳转。
开始调试
如果选择返回1会稍微有点麻烦。在sub_4006D6里选择return 0,View-RIP中就会显示对应的部分
在mov eax,0那里右键->keypatch->patcher(对应快捷键ctrl+alt+k)
在Assembly里把0改成1 点patch就行了。
在我这里他会连着显示,第二个点取消即可
需要注意的是这里要修改两次,因为有两个判断(十位和0-4)。第一次修改后对应的代码会发生变化,按照上面的操作再进行一次就可以了。
第二种方法就是直接修改跳转。在main中找到第一个if对应的位置
按照上面的方法,把jz改成jnz就可以了。
汇编语言中jz表示为0跳转,jnz表示不为0跳转
后面同理,找到第二个if的位置并也把jz修改成jnz。
再往后划一点,插入一个断点以防止程序结束自动退出。我把断点插到了“}”的位置
右键->插入断点(对应快捷键F2)或者点那个小蓝点都可以。插入成功该行会变红。
这回就万事大吉了。在linux的终端中输入数字0123456789即可。
先不用管linux输出的是什么,直接去ida看v7是什么。双击v7。
这样他的顺序就出来了。按照这个顺序对前面数独答案处理一下就可以了。
flag{1134240024}
说点别的
说实话这道题用的东西确实不少,当时懒嘛遇到调试的题就想着先多练练静态的吧,结果连着好几道都是动调的题。这回了解了之后感觉原来真的没什么。
找的东西确实好多啊。又是动调方法又是查询ip又是各种在wp上看其他佬是怎么修改函数的……于是我就写详细了一点,别人看着也方便。