-
-
[原创] 第八题 薛定谔之猫 WriteUp
-
发表于: 2018-7-1 19:51 3436
-
首先拿到程序,发现程序非常的复杂,IDA反编译代码中存在很多函数传进去了63个参数,猜测应该是将一个很大的对象直接传了进去。
首先发现程序里有奇怪的一个handler:
将第一个参数先与固定值异或,作为起始地址,然后带着起始地址和长度进入一个哈希函数,计算出哈希值后跳转过去。这里可以发现,被哈希的字节在代码段中,所以用0xCC做软件断点的调试方法在这里行不通,需要使用硬件断点来将程序断下。当然,也可以人工计算出哈希值,然后patch掉这个函数,用正确的函数替换。
经过很多次的调试,发现程序中操作的那个巨大的对象,实际上是一个任意进制大整数的对象。定义大概是这样:
一段一段地看程序:
读入最长100字节的Flag,判断是否为字母数字,然后以01234……ABCD……abcd……为字符集,将Flag作为62进制数储存。
这个神奇的handler居然还有返回某个数的功能……这里返回的是0x100。
这里的作用是在内存中生成了一个1-256的表,存1-256这些数字作为256进制大整数。
这里标完函数之后,就显得很清晰,经典的进制转换结构,把62进制的Flag转换为256进制bytes。(话说不是有个进制转换的函数吗,为啥不用那个= = )
这里就开始check了,读出前四个字节,判断范围在(1, 0x18),并且bytes[0] + 4 == bytes[1], bytes[1] + bytes[2] + bytes[3] + 4 == 总长度。
这里是按顺序读4段并放到4个大整数对象中。稍微整理一下可以得到这样的结构:(按实际数字顺序)
其中a和前面4字节合起来作为b。
载了固定的两个字符串成64进制大整数,然后将之前的a转换成36进制bytes。
这里这两个函数比较重要。将a转成的36进制bytes传给4010A0函数,算一下,然后再算一下整个256进制bytes的哈希。
4010A0函数:
这个函数我懵逼了很久……一开始将36进制字节转回数字,然后拆成低位和高位,分别扔进那个case里面去做操作……验证要0x1F次操作全部成功完成,v6的值变为0. 这个东西,实际上是一个汉诺塔小游戏。
a b c分别为3根柱子,用二进制位表示每一个权值的盘子(所以取二进制最后一个1,实际上是取到最上的一块盘子),权值小的放在权值大的上面,0x1F刚好是(11111),即5个盘子权值分别为16 8 4 2 1,最少的移动次序正好是31次。用0-5表示6种移动,两次合并为一个36进制字节。
手动玩一玩,得到一个序列:
// 这里似乎存在多解……没有check最后放在第2个还是第3个上
合并相邻的两个操作:(最后多余的一个必须是0)
转为36进制bytes:
// 怎么看上去这么正常……怕不是这本来是单独的一道题……
转为数字:
这个就是a。
接下来是哈希函数,但是到这里的时候信息明显不足,这里选择暂且patch程序跳过这个check,继续向下分析。
这里乍一看是去check c的立方+d的立方=b的立方,但上过高中的人都知道,这个方程是不存在整数解的。再看下面有个除法,若除数为零则扔出一个异常。如果前面的步骤全部正确,这里这个除数必须为零,即这个异常必须被触发。
看一看Exception Handler结构体,发现是跳到这里:
显然,就是为下面的lpMem = 2创造条件的。
这里计算了c+(-d)和2c+4d,然后和之前载入的两个64进制数比较。可以解出c和d。
这样的话,a c d全部确定,且长度域也确定,剩下4个字节,根据哈希函数返回0确定。
爆破得到:
全部连起来得到数字
转为62进制,即为Flag
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)