这道题呢是在“墨者学院”上看到的,题目是“逆向分析实训-exe(第2题)“,文末有惊喜,本着学习逆向的想法就去做了下这道题,虽然结果变成了C语言代码审计。。。
另外在做这道题的过程中参考了另外一个大佬的文章,在末尾会给出链接。。。
本人只是一只想学习逆向的菜鸟,文章里可能有错误会不准确的地方,如果有发现请告诉我,非常感谢,没钱的那种~~~穷人,就剩这点脾气了
接下来就开始干活了
IDA打开文件,F5一下,进入main:(菜鸟的自我修养,F5)
看到有success,这个应该就是关键了,要输出success需要满足第一个if(图中1)为假,else里嵌套的第一个if(图中2)为真,小白也不会玩,看到success就直接进入了if ( Sudu::check((Sudu *)&v14) )这个语句,双击跟进这个函数:(这里用血的教训证明了,按部就班的从第一个条件看着走不好吗,啊?)
要返回为真,这里的三个函数的返回值均需要为真,好吧,那就一个个的看,我能怎么办呢,我也不想看,第一个check_block,这里需要注意传入的参数为一个指针的int类型:
这段代码需要结合题目是数独来分析(当然,要是你先看的第一个条件就会好理解很多了),看了老久(我会告诉你我是从大佬的文章里知道的吗,菜鸟不要面子的吗),确认他是对给的数独进行分析,如果满足数独对块的要求,就是每个小的3*3块都是1到9不同的数字,返回值为1,就这样看其实很头疼(大佬没说为什么。。。就只好自己看了。。。),给出自己的分析过程,希望有用:
首先注意这三个地方,第一处,将v2里的值全置为1(除了v2[0],后一样,不用0-8而用1-9是因为1-9刚好是数独可以填的数字),中间将v2里的某几个值置为0(暂且这样说吧),然后最后又对v2的所有值进行判断,如果全部置为0了,return 1,而这是我们要的,否则 return 0,也就是,只要 *(_DWORD *)(a1 + 4 * (v3 + 9 * v4)) 的值为1到9就return 1,(a1 + 4 * (v3 + 9 * v4)这个的结果是一个整数(a1是一个int类型的值,暂且不管具体是什么),准确的说是一个指针的int值,*(_DWORD *)(a1 + 4 * (v3 + 9 * v4)实现了对这个指针所指的地址进行取值,为了确认他的运行逻辑,把这段代码贴出来跑一遍:(出题的大佬真是厉害,数组下标跟数独的1-9能联系到一块,膜拜~~~)
这里为了更直观,对原来的数除了4,因为一个int类型的数占了4位,a1取0也是为了更直观,观察结果可以发现,每次取的9个值刚好是数独的一个3*3小块,并依次遍历了9个这样的小块,好,到这里已经差不多得出了check_block这个函数的作用为分析数独的3*3小块是否符合数独的要求,传入的参数a1即为数独数组的指针的int值(这里暂且如此推测,后面有证明,另外数组指针的值为数组的起始地址,即a1是数独存放数组的起始位置的int值)。
分析完check_block,接下来是check_col和check_row,这里就不一一分析了,分别是判断每行每列是否都是1-9这9个不同的数字(我说我一一分析了,不是直接从大佬的文章里得出的结论,你们都信了对不对)。
到这里差不多知道check()函数为判断数独结果是否满足要求。。。有点扎心。。。菜鸟的路太坎坷了。。。
回头看需要满足的两个条件:
第二个已经差不多明白是咋回事了,接下来就是第一个条件了,看着心累的不行,就是前面balabala一堆跟1的异或(相同为真,不同为假)结果要为0,也就是前面的balabala的值也要为1,即是真。好吧,前面只有一个set_sudu函数,跟进去:
真不想看了,但还是要微笑,看看要return 1,别让他return 0就好了,不能return 0那么异或的结果要为0,即是(unsigned __int8)Sudu::set_number((Sudu *)(v10 / 9), v10 % 9, v8 - 48, v5)返回值要为1,跟进去看看set_number:(多留意一下参数的传递,会有用的)
返回的是result,要么a4为0(这里为什么要求是0留在后面说),要么以下的判断结果为假,即是每一个小条件为假:
if ( (signed int)this < 0
|| (signed int)this > 8
|| a3 < 0
|| a3 > 8
|| *(_DWORD *)(a1 + 4 * (a3 + 9 * (_DWORD)this))
|| a4 <= 0
|| a4 > 9 )
结合传进来的参数分析,这里是判断的内容为数独表的行列是否为9行9列,且写入数据的地方是否为0(这点很重要,这也是结果为什么要将最后的flag里将原有的数据位置为0,因为如果写入的数据不为0,而这个写入的位置处也非0的话会返回0,就fail了,不注意看后面就懵了),a4是否为1至9,如果满足这些条件就将a4的值写入。否则不写入保持原来的数据,那么a4应该就是我们的输入了,回头看确认一下:
这里可以看到:a4 = a2-48,0的ascii码值为48,这里将char转int,在往前看a2:
是我们的输入,确认了,那么到这里可以确认,这里是将我们的输入写进初始的存放数独表的数组里,如果输入合法且不为0,那么将输入的值写入正确的位置,如果为0就保留原来的数字(解答了为什么a4为0)。
到这里逻辑差不多已经明白了,有那么一个初始的存放数独的数组,set_sudu函数在将我们的答案写入初始的数独里,check函数在检查我们最终的答案是否正确,正确就输出success,现在我们就需要去找我们的存放初始数独表的数组了:(回到最初的起点)
从这里我们可以找到我们要找的这个数组就是v14了,而这也符合我们在分析check_block函数时的猜测“a1是存放数独数组的指针的int值”,那就去看看v14一路风风雨雨都经历了些什么。首先是:
Sudu::Sudu(&v14);
跟进去看看:
这里分配了空间,继续往下:
Sudu::set_data((int)&v14, (Sudu *)&_data_start__, v5);
跟进去:
这里很明显是在往数独表里填数据,将data_start里的数据填到我们的数组表里,接下来去找data_start里的数据,双击跳转到存放数据的地方:
人傻,没办法,只好一个个的读出来了,这里是可以用内存读取的方法,给全部dump下来,得到的结果是(右边绿色部分是存储的值):
0000 0000 7000 5000 0000 0000 0000 6000 0000 0000 2000 0000 0000 0100 0000 0000 0000 7000 9000 ···后面一大串,就不全列出来了,每4位取开头的数字就行,同理,一个int占4位,IDA把十进制的值写在了开头(这点不敢肯定~~~猜的~~~有大佬知道的望告知),反正复制一下粘贴出来,整理一下,可以得到:
007500060
020010007
900030400
201000000
030100005
000000710
400008200
005900080
080001003
就是写进去的数独表了。
到这里,逻辑差不多就清晰了,这个程序呢就是在解数独,首先有一个存放初始数独的数组,之后开始往里填数字,之后验证是否满足要求,满足要求的就输出success,否则就fail,现在就去在线解一下数独:
解出的结果为:
把结果输出出来就是:
347589162 528416937 916237458 261875349 739164825 854392716 493658271 175923684 682741593在将原来表里的数据位置置为0,原因见前面的解释,这就是答案了。
解出来一个答案了当然是试一下:
大功告成。大佬们轻喷,?
终于完成了,其实我更适合当一条咸鱼,最后附上大佬文章的链接:
参考:https://www.52pojie.cn/thread-623244-1-1.html
