2019 RoarCTF RE 部分 WriteUp

2019-10-18 7,160

坦克大战

本题虽然放在misc目录但个人感觉是逆向题,下载压缩包打开后是这样的:

image.png

可以看目录得知是unity框架写的游戏:
image.png

直接找到“Assembly-CSharp.dll”使用dnspy进行分析,很容易找到位于MapManager类的游戏判胜的逻辑函数public static void WinGame():

 public static void WinGame()
 {
     if (!MapManager.winGame && (MapManager.nDestroyNum == 4 || MapManager.nDestroyNum == 5))
     {
         string text = "clearlove9";
         for (int i = 0; i < 21; i++)
         {
             for (int j = 0; j < 17; j++)
             {
                 text += MapManager.MapState[i, j].ToString();
             }
         }
         string a = MapManager.Sha1(text);
         if (a == "3F649F708AAFA7A0A94138DC3022F6EA611E8D01")
         {
             FlagText._instance.gameObject.SetActive(true);
             FlagText.str = "RoarCTF{wm-" + MapManager.Md5(text) + "}";
             MapManager.winGame = true;
         }
     }
 }

WinGame函数被游戏每当刷新调用实时判断游戏是否获胜,当Manager.nDestroyNum 的数值等于4或5时,判断sha1(“clearlove9”+mapstate)是否为指定值,如果是则flag为"RoarCTF{wm-" + MapManager.Md5(text) + "}",这个md5是“clearlove9”+mapdata的md5的前十个字符。

MapState为游戏当前的地图数据,观察游戏初始时的地图数据(21x17)可以得出:

image.png  

8 空的

1 砖头

4 水

5 草

2 钢铁

0 家(炸了之后就是9)

其中只有砖头和家可以打碎,打碎后砖头变成空(1),家变成爆炸状态(9)。

总共有65个砖头,一个家,使用脚本遍历所有情况即可:

 import hashlib
 data =[
     [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8],
     [8, 8, 4, 5, 8, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 4, 8],
     [8, 2, 8, 1, 8, 8, 5, 1, 8, 8, 8, 1, 8, 1, 8, 4, 8],
     [8, 5, 8, 2, 8, 8, 8, 8, 1, 8, 8, 4, 8, 1, 1, 5, 8],
     [8, 8, 8, 8, 2, 4, 8, 1, 1, 8, 8, 1, 8, 5, 1, 5, 8],
     [8, 8, 8, 8, 5, 8, 8, 1, 5, 1, 8, 8, 8, 1, 8, 8, 8],
     [8, 8, 8, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 8, 1, 5, 8],
     [8, 1, 8, 8, 1, 8, 8, 1, 1, 4, 8, 8, 8, 8, 8, 1, 8],
     [8, 4, 1, 8, 8, 5, 1, 8, 8, 8, 8, 8, 4, 2, 8, 8, 8],
     [1, 1, 8, 5, 8, 2, 8, 5, 1, 4, 8, 8, 8, 1, 5, 1, 8],
     [9, 1, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8],
     [1, 1, 8, 1, 8, 8, 2, 1, 8, 8, 5, 2, 1, 8, 8, 8, 8],
     [8, 8, 8, 8, 4, 8, 8, 2, 1, 1, 8, 2, 1, 8, 1, 8, 8],
     [8, 1, 1, 8, 8, 4, 4, 1, 8, 4, 2, 4, 8, 4, 8, 8, 8],
     [8, 4, 8, 8, 1, 2, 8, 8, 8, 8, 1, 8, 8, 1, 8, 1, 8],
     [8, 1, 1, 5, 8, 8, 8, 8, 8, 8, 8, 8, 1, 8, 8, 8, 8],
     [8, 8, 1, 1, 5, 2, 8, 8, 8, 8, 8, 8, 8, 8, 2, 8, 8],
     [8, 8, 4, 8, 1, 8, 2, 8, 1, 5, 8, 8, 4, 8, 8, 8, 8],
     [8, 8, 2, 8, 1, 8, 8, 1, 8, 8, 1, 8, 2, 2, 5, 8, 8],
     [8, 2, 1, 8, 8, 8, 8, 2, 8, 4, 5, 8, 1, 1, 2, 5, 8],
     [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8]
     ]
 
 text = ''
 for i in range(21):
     for j in range(17):
         text += str(data[i][j])
 text = list(text)
 def go(data,index,num):
     if num == 3:
         temp = ''.join(data)
         if hashlib.sha1('clearlove9'+temp).hexdigest() == '3f649f708aafa7a0a94138dc3022f6ea611e8d01':
             key = hashlib.md5('clearlove9'+temp).hexdigest().upper()[:10]
             flag = "RoarCTF{wm-"+key+"}"
             print(flag)
         return
     if index == 21*17:
         return
     if data[index] == '1':
         temp = list(data)
         temp[index] = '8'
         go(temp,index+1,num+1)
     go(data,index+1,num)
 if __name__ == "__main__":
     go(text,0,0)

最终得到flag:

RoarCTF{wm-805CEC3545}

polyre

本题使用ollvm进行混淆,使用腾讯实验室的deflat脚本进行处理。

image.png

这样可读性就增加了不少,容易看出程序验证逻辑为:

 for each_8_bytes in input:
 for _ in range(64):
         if int64(each_8_bytes) >= 0:
             each_8_bytes *= 2
     else:
         Each_8_bytes *= 2
         Each_8_bytes ^= 0xB0004B7679FA26B3
 校验每个each_8_bytes == 指定常数

这里队友直接用z3解出来了:

 from z3 import *
 x = [BitVec('x[%d]'%i,64) for i in xrange(6)]
 s = Solver()
 cipher = [0xBC8FF26D43536296, 0x520100780530EE16, 0x4DC0B5EA935F08EC, 0x342B90AFD853F450, 0x8B250EBCAA2C3681, 0x55759F81A2C68AE4]
 for i in xrange(6):
     for _ in xrange(64):
         x[i] = If(x[i] > 0, 2 *x[i], ((2 * x[i]) ^ 0xB0004B7679FA26B3))
     s.add(x[i] == cipher[i])
 
 if s.check() == sat:
     m = s.model()
     import struct
     dic = [(str(m[i])[-2:-1], struct.pack('<Q', m[m[i]].as_long().real)) for i in xrange(6)]
     dic.sort(key = lambda x: x[0], reverse = False)
     flag = ''.join(map(lambda x: x[1], dic))
     print flag
 else:
     print 'no'


后来想了一下这个是可以逆向算的,也就是说加密的每个分支通过加密后的数据是可以推算出来的:

因为每次乘以2后,数据最低位为0,而抑或0xB0004B7679FA26B3之后数据最低位必然为1,这样根据最低为是否为1即可反向推出上一次是走了哪个分支。

 

driverCuora

本题是linux驱动逆向,但是和逆用户层感觉没啥区别,就是不会调试。。。

大致流程如下:

1.     初始化函数中,从/home/ppprocce/ssooor文件中读取一个17字节的路径,并计算了一个类似于crc32的东西:

   v2 = -1;
   v3 = a1 + a2;
   while ( a1 != v3 )
   {
     ++a1;
     v5 = 0;
     v6 = 0;
     do
     {
       v4 = *(unsigned __int8 *)(a1 - 1);
       if ( _bittest(&v4, v6) )
         v5 |= 1 << (7 - v6);
       ++v6;
     }
     while ( v6 != 8 );
     v2 ^= *(unsigned __int8 *)(a1 - 1) << 24;
     v7 = 8;
     do
     {
       if ( v2 >= 0 )
         v2 *= 2;
       else
         v2 = 2 * v2 ^ 0x4C11DB7;
       --v7;
     }
     while ( v7 );
   }

对之前读到的路径字符串每个字节为一轮进行计算,先把每个字节按bit倒序,然后算crc32的一轮,初始为-1,二项式常数0x4C11DB7,这样算出来的“crc32“把低16bit在倒序,然后整个“crc32”取反。

2.     在驱动退出回调中,将之前的路径字符串冒泡排序,然后每个字节^1,看是否等于给定数据,(这里可以获取到乱序的正确路径///CFRpTagframol),然后还会对crc32进行校验,不对则直接退出。

3.     若是正确,会从刚刚的路径中读取对应的文件,并对文件中的内容(即flag),进行校验,校验逻辑为:

 Data1 = 正确路径*2
 Data2 = flag #34字节
 For byte1,byte2 in data1,data2:
     ROL(byte1^0x55+byte2^0xAA,2) == table1[i]
     ((Byte2^0x55+byte1^0xAA)>>2) | ((byte1^0x55+byte2^0xAA)<<6) == table2[i]


当时的思路是首先算出正确路径的顺序,想到全排序爆破,但是失败了,主要由于:
1.ida的f5出来的crc32那段代码有点问题,后来才发现。

2.实际试了爆破速度爆不完。

 #include <stdio.h>
 #include <stdlib.h>
 __int64 sub_F5(__int64 a1, unsigned int a2)
 {
   int v2; // edx
   __int64 v3; // r11
   int v4; // esi
   int v5; // er8
   unsigned int v6; // eax
   signed int v7; // eax
   int v8; // eax
   int v9; // esi
 
   v2 = -1;
   v3 = a1 + a2;
   while ( a1 != v3 )
   {
     ++a1;
     v5 = 0;
     v6 = 0;
     do
     {
       v4 = *(unsigned __int8 *)(a1 - 1);
       if ( (v4>>v6)&1 )
         v5 |= 1 << (7 - v6);
       ++v6;
     }                 //这里f5给出的伪代码和实际不符
     while ( v6 != 8 );
     v2 ^= *(unsigned __int8 *)(a1 - 1) << 24;
     v7 = 8;
     do
     {
       if ( v2 >= 0 )
         v2 *= 2;
       else
         v2 = 2 * v2 ^ 0x4C11DB7;
       --v7;
     }
     while ( v7 );
   }
   v8 = 0;
   v9 = 0;
   do
   {
     if ( v2 & (1 << v9) )
       v8 |= 1 << (15 - v9);
     ++v9;
   }
   while ( v9 != 32 );
   return (unsigned int)~v8;
 }
 
 
 #define N 17
 char a[17] = {47, 47, 47, 67, 70, 82, 84, 97, 97, 102, 103, 108, 109, 111, 112, 114, 116};
 void swap(int i, int offset)
 {
     int temp;
     temp = a[offset];
     a[offset] = a[i];
     a[i] = temp;
 }
 
 void print()
 {
     if(sub_F5((__int64)&a,17)==0x5A9E037C)
     {
         int i;
         for(i = 0; i < 17; i++)
             printf(" %c ",a[i]);
         printf("\n");
     }
 }
 void perm(int offset)
 {
     int i, temp;
     if(offset == N-1)
     {
         print();
         return;
     }
     for(i = offset; i < N; i++)
     {
         swap(i, offset);
         perm(offset + 1);
         swap(i, offset);
     }
 }
 int main()
 {
     perm(0);
 }


最后路径是通过flag猜出来的。。。首先根据flag前面是RoarCTF{反向猜出路径前几个,然后根据猜测补齐。

最终得到/tmp/RoarCTF/flag,根据算法直接解密:

 res = [0x000000C9,0x0000009B,0x0000000C,0x000000F7,0x0000008D,0x00000014,0x00000098,0x00000014,0x00000010,0x00000077,0x00000022,0x00000063,0x000000F0,0x000000A0,0x000000DC,0x000000DB,0x00000037,0x0000004D,0x00000058,0x000000EF,0x00000013,0x000000BD,0x00000096,0x000000BC,0x00000090,0x00000084,0x000000BB,0x0000001B,0x000000C7,0x00000025,0x000000F3,0x0000005C,0x000000FE,0x00000024]
 #这个表就是之前说的table1
 for i in range(len(res)):
     res[i] = ((res[i]>>2)|(res[i]<<6))&0xff
 #这里反过来算就好了
 data1 ='/tmp/RoarCTF/flag'*2
 data1 = map(ord,data1)
 data2 = [0]*34
 for i in range(34):
     data2[i] = ((res[i]-(data1[i]^0x55))&0xff)^0xAA
 print ''.join(chr(i%128) for i in data2)

所以这里只用了第一个table就出来了,后来想想其实根本不用猜路径,直接不管crc32把路径和flag都当作符号变量,直接根据两个table表的数据用z3解就行了,虽然没试但感觉应该能解出来。

 

math

这题需要解一个浮点数方程用sympy没有解出来,后来用室友的matlab发现可以秒解。

程序首先验证了一个输入是否为sqrt(2),好像测试用的,没啥用。

然后再有两个输入x y,满足如下条件:

 1.((a-(sqrt(2*sqrt(5)+10)+1))*(sqrt(5)-1)-(b-1)*((sqrt(2*sqrt(5)+10)+1)-(sqrt(2*sqrt(5)+10)+1)))**2/((b-1)**2+(a-(sqrt(2*sqrt(5)+10)+1))**2)==0.38197**2
 2. ((a-1)*(sqrt(5)-1)-(b-1)*((sqrt(2*sqrt(5)+10)+1)-1))**2/((b-1)**2+(a-1)**2)==(sqrt(5)-1)**2
 3.a=x*cos(y)
 4.b=x*sin(y)

其中0.38197这个常数是用一系列随机数生成的,具体的生成好像很复杂。。。这个数值我是通过二分法直接手工爆破的。

使用matlab解方程:

 syms a b x y;
 [a_,b_,x_,y_]=vpasolve([((a-1)*(sqrt(5)-1)-(b-1)*((sqrt(2*sqrt(5)+10)+1)-1))^2/((b-1)^2+(a-1)^2)==(sqrt(5)-1)^2,((a-(sqrt(2*sqrt(5)+10)+1))*(sqrt(5)-1)-(b-1)*((sqrt(2*sqrt(5)+10)+1)-(sqrt(2*sqrt(5)+10)+1)))^2/((b-1)^2+(a-(sqrt(2*sqrt(5)+10)+1))^2)==0.38197^2,a==x*cos(y),b==x*sin(y)],[a,b,x,y]);

最终得到:

X = -5.2057229787614023021024241671716

Y = 35.228348316759497246192425417098

image.png

本文作者:剑神

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/115908.html

Tags:
评论  (0)
快来写下你的想法吧!

剑神

文章数:2 积分: 60

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号