ctf

看雪ctfQ3第五题-writeup

ctf题解

Posted by gxkyrftx on September 27, 2019

0 题目说明

Windows CrackMe 64位

1 分析过程

1.1 查看字符串

.rdata:000000000048B008	00000041	C	ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
.rdata:000000000048B04A	0000001B	C	please input your serial:\n
.rdata:000000000048B068	00000026	C	Welcome/to/this/very/simple/challenge
.rdata:000000000048B08E	0000000B	C	incorrect!
.rdata:000000000048B099	00000006	C	pause
.rdata:000000000048B09F	00000009	C	correct!
.rdata:000000000048B0A8	00000021	C	44e4403b63620a2075d3fb2e0a6207d2
.rdata:000000000048B0D0	000000AA	C	**************@************-************--**----*****-***-**-*****-***#**-*****--*****-******-*****-******-------******-*-----******---**-*******-****--*****************

这几个字符串很可疑,尤其是.rdata:000000000048B0A8,.rdata:000000000048B0D0,一会可以注意一下。接下来追踪correct和incorrect,correct只存在于main函数中,查看main函数。

1.2 main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
 ...

  _main();
  std::string::string((std::string *)&v26);
  std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "please input your serial:\n");
  std::operator>><char,std::char_traits<char>,std::allocator<char>>(refptr__ZSt3cin, (std::string *)&v26);
  std::string::operator+=((std::string *)&v26, "Welcome/to/this/very/simple/challenge");// 输入的字符和预置字符组合
  if ( (unsigned __int64)std::string::length((std::string *)&v26) > 85 )// 组合字符长度大于85报错
  {
    v3 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "incorrect!");
    std::ostream::operator<<(v3, refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
    system("pause");
  }
  else
  {
    std::string::string((std::string *)&v27, (const std::string *)&v26);// 将组合字符串的地址复制到v27
    v4 = !check_fun((__int64)&v27);
    std::string::~string((std::string *)&v27);
    if ( v4 )
    {
      v5 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "incorrect!");
      std::ostream::operator<<(v5, refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
      system("pause");
    }
    else
    {
      std::string::substr((std::string *)&v28, (unsigned __int64)&v26, 0i64);
      O0oo0oO0O((std::string *)&v25, (std::string *)&v28);
      std::string::~string((std::string *)&v28);
      v40 = std::string::length((std::string *)&v26);
      v6 = alloca(16 * ((unsigned __int64)(v40 + 16) >> 4));
      Str = (char *)&v20;
      std::string::substr((std::string *)&v29, (unsigned __int64)&v25, 0x12ui64);
      v7 = std::string::length((std::string *)&v29) + 1;
      std::string::substr((std::string *)&v30, (unsigned __int64)&v25, 0x12ui64);
      v8 = (const char *)std::string::c_str((std::string *)&v30);
      strncpy(&Dest, v8, v7);
      std::string::~string((std::string *)&v30);
      std::string::~string((std::string *)&v29);
      std::string::substr((std::string *)&v31, (unsigned __int64)&v26, 0i64);
      v9 = std::string::length((std::string *)&v31) + 1;
      std::string::substr((std::string *)&v32, (unsigned __int64)&v26, 0i64);
      v10 = (const char *)std::string::c_str((std::string *)&v32);
      strncpy(&v23, v10, v9);
      std::string::~string((std::string *)&v32);
      std::string::~string((std::string *)&v31);
      std::string::substr((std::string *)&v33, (unsigned __int64)&v26, 8ui64);
      v11 = std::string::length((std::string *)&v33) + 1;
      std::string::substr((std::string *)&v34, (unsigned __int64)&v26, 8ui64);
      v12 = (const char *)std::string::c_str((std::string *)&v34);
      strncpy(&v22, v12, v11);
      std::string::~string((std::string *)&v34);
      std::string::~string((std::string *)&v33);
      std::string::substr((std::string *)&v35, (unsigned __int64)&v26, 0x10ui64);
      v13 = std::string::length((std::string *)&v35) + 1;
      std::string::substr((std::string *)&v36, (unsigned __int64)&v26, 0x10ui64);
      v14 = (const char *)std::string::c_str((std::string *)&v36);
      strncpy(&v21, v14, v13);
      std::string::~string((std::string *)&v36);
      std::string::~string((std::string *)&v35);
      v15 = std::string::length((std::string *)&v26) + 1;
      v16 = (const char *)std::string::c_str((std::string *)&v26);
      strncpy(Str, v16, v15);
      Ooo0ooO0O(&Dest, &v21, &Dest);
      Ooo0OO0oo(&Dest, &v22, &Dest);
      v38 = strlen(Str);
      md5(&v20, Str, v38);
      Ooo0ooO0O(&Dest, &v23, &Dest);
      std::string::replace((std::string *)&v25, 0x12ui64, 8ui64, &Dest);
      std::string::string((std::string *)&v37, (const std::string *)&v25);
      O0oOo0Oo0((unsigned __int64)&v37);
      std::string::~string((std::string *)&v37);
      if ( (unsigned __int8)std::operator==<char>(&v20, &O0oo0OOo0) )
      {
        if ( O0ooOO00o == 4 )
          v17 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "correct!");
        else
          v17 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "incorrect!");
        std::ostream::operator<<(v17, refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
      }
      else
      {
        v18 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "incorrect!");
        std::ostream::operator<<(v18, refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
      }
      system("pause");
      std::string::~string((std::string *)&v20);
      std::string::~string((std::string *)&v25);
    }
  }
  std::string::~string((std::string *)&v26);
  return 0;
}

1.2.1 check_fun

参数是组合字符串,然后进入checkfun函数。

通过调试可以发现,函数对参数中的第一个字符进行判断,通过第一个字符,经过梅森旋转算法,生成了三个随机数,然后将随机数与密钥keytea同时放入btea算法求解。

并且发现,相同的字母加密成为相同的内容

_BOOL1 __fastcall check_fun(__int64 a1)
{
    ...
  v1 = (char *)std::string::operator[](a1, 0i64);
  std::mersenne_twister_engine<unsigned int,32ull,624ull,397ull,31ull,2567483615u,11ull,4294967295u,7ull,2636928640u,15ull,4022730752u,18ull,1812433253u>::mersenne_twister_engine(
    (__int64)&v4,
    *v1);
  v9 = std::mersenne_twister_engine<unsigned int,32ull,624ull,397ull,31ull,2567483615u,11ull,4294967295u,7ull,2636928640u,15ull,4022730752u,18ull,1812433253u>::operator()((__int64)&v4);
  v8 = std::mersenne_twister_engine<unsigned int,32ull,624ull,397ull,31ull,2567483615u,11ull,4294967295u,7ull,2636928640u,15ull,4022730752u,18ull,1812433253u>::operator()((__int64)&v4);
  v2 = std::mersenne_twister_engine<unsigned int,32ull,624ull,397ull,31ull,2567483615u,11ull,4294967295u,7ull,2636928640u,15ull,4022730752u,18ull,1812433253u>::operator()((__int64)&v4);
  v7 = v2;
  v5 = v9;
  v6 = v2;
  btea(&v5, 2, &keytea);
  return v8 - (v5 + v6) == 0xA504A8F7;
}

1.2.1.1 btea

btea算法链接https://blog.csdn.net/libinjlu/article/details/43030369,是一种微型加密算法,算法密钥btea已知,故可以反解第一个字符。

__int64 __fastcall btea(unsigned int *a1, signed int a2, unsigned int *a3)
{
    ...
  v19 = a1[a2 - 1];
  v18 = *a1;
  v16 = 0;
  if ( a2 <= 1 )
  {
    if ( a2 >= -1 )
    {
      result = 1i64;
    }
    else
    {
      v20 = -a2;
      for ( i = -1640531527 * (52 / -a2 + 6); i; i += 1640531527 )
      {
        v12 = (i >> 2) & 3;
        for ( j = v20 - 1; j; --j )
        {
          v8 = a1[j - 1];
          v9 = &a1[j];
          *v9 -= ((4 * v18 ^ (v8 >> 5)) + ((v18 >> 3) ^ 16 * v8)) ^ ((v18 ^ i) + (v8 ^ a3[v12 ^ j & 3]));
          v18 = *v9;
        }
        v10 = a1[v20 - 1];
        *a1 -= ((4 * v18 ^ (v10 >> 5)) + ((v18 >> 3) ^ 16 * v10)) ^ ((v18 ^ i) + (v10 ^ a3[v12]));
        v18 = *a1;
      }
      result = 0i64;
    }
  }
  else
  {
    v13 = 52 / a2 + 6;
    while ( 1 )
    {
      v6 = v13--;
      if ( v6 == 0 )
        break;
      v16 -= 1640531527;
      v11 = (v16 >> 2) & 3;
      for ( k = 0; a2 - 1 > k; ++k )
      {
        v3 = a1[k + 1];
        v4 = &a1[k];
        *v4 += ((4 * v3 ^ (v19 >> 5)) + ((v3 >> 3) ^ 16 * v19)) ^ ((v3 ^ v16) + (v19 ^ a3[v11 ^ k & 3]));
        v19 = *v4;
      }
      v5 = &a1[a2 - 1];
      *v5 += ((4 * *a1 ^ (v19 >> 5)) + ((*a1 >> 3) ^ 16 * v19)) ^ ((*a1 ^ v16) + (v19 ^ a3[v11 ^ k & 3]));
      v19 = *v5;
    }
    result = 0i64;
  }
  return result;
}

1.2.2 解第一个字符

脚本如下,参考了百度百科中梅森旋转算法https://baike.baidu.com/item/%E6%A2%85%E6%A3%AE%E6%97%8B%E8%BD%AC%E7%AE%97%E6%B3%95/22800014?fr=aladdin,博客BTEA算法C语言实现https://blog.csdn.net/libinjlu/article/details/43030369,以及writeup https://bbs.pediy.com/thread-254655.htm

_DELTA = 0x9E3779B9    
def _int32(x):
    # Get the 32 least significant bits.
    return int(0xFFFFFFFF & x)
 
class MT19937:
 
    def __init__(self, seed):
        # Initialize the index to 0
        self.index = 624
        self.mt = [0] * 624
        self.mt[0] = seed  # Initialize the initial state to the seed
        for i in range(1, 624):
            self.mt[i] = _int32(
                1812433253 * (self.mt[i - 1] ^ self.mt[i - 1] >> 30) + i)
        #print map(hex,self.mt)
 
    def extract_number(self):
        if self.index >= 624:
            self.twist()
 
        y = self.mt[self.index]
 
        # Right shift by 11 bits
        y = y ^ y >> 11
        # Shift y left by 7 and take the bitwise and of 2636928640
        y = y ^ y << 7 & 2636928640
        # Shift y left by 15 and take the bitwise and of y and 4022730752
        y = y ^ y << 15 & 4022730752
        # Right shift by 18 bits
        y = y ^ y >> 18
 
        self.index = self.index + 1
 
        return _int32(y)
 
    def twist(self):
        for i in range(624):
            # Get the most significant bit and add it to the less significant
            # bits of the next number
            y = _int32((self.mt[i] & 0x80000000) +
                       (self.mt[(i + 1) % 624] & 0x7fffffff))
            self.mt[i] = self.mt[(i + 397) % 624] ^ y >> 1
 
            if y % 2 != 0:
                self.mt[i] = self.mt[i] ^ 0x9908b0df
        self.index = 0
def encrypt(v,n,k):     
    #if str == '': return str   
    #v = _str2long(str, True)     
    #k = _str2long(key.ljust(16, "\0"), False)   
     
    #n = len(v) - 1  
    n=n-1
    z = v[n]   
    y = v[0]   
    sum = 0    
    q = 6 + 52 // (n + 1)
    #print map(hex,v)
    #print n
    #print map(hex,k)
    while q > 0:   
        sum = (sum + _DELTA) & 0xffffffff  
        e = sum >> 2 & 3     
        for p in xrange(n):   
            y = v[p + 1]     
            v[p] = (v[p] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))) & 0xffffffff    
            z = v[p]   
        y = v[0]   
        v[n] = (v[n] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[n & 3 ^ e] ^ z))) & 0xffffffff    
        z = v[n]   
        q -= 1     
    return v
k=[0x67452301, 0x0EFCDAB89, 0x98BADCFE, 0x10325476]
n=2
for i in range(0xFF):
    MT=MT19937(i)
    ret1=MT.extract_number()
    ret2=MT.extract_number()
    ret3=MT.extract_number()
    v=[ret1,ret3]
    v=encrypt(v,n,k)
    if ret2 - (v[0] + v[1]) == 0xA504A8F7:
        print "Found:",i,chr(i)
        break

可以解出,第一位为V

1.3 O0oo0oO0O

函数主要进行对“输入+预置“字符串进行base64解码,根据”=”判断解码的位数

std::string *__fastcall O0oo0oO0O(std::string *a1, std::string *a2)
{
  ...
  v24 = a1;
  v25 = a2;
  std::allocator<char>::allocator((char *)&v18 + 54);
  std::string::string(&v19, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", &v20);
  std::allocator<char>::~allocator(&v20);
  v22 = std::string::length(v25);
  std::allocator<char>::allocator(&v21);
  std::string::string(v24, &unk_48B049, &v21);
  std::allocator<char>::~allocator(&v21);
  if ( std::string::find(v25, 61, 0i64) && (unsigned __int64)std::string::find(v25, 61, 0i64) <= 0x23 )
    v2 = std::string::find(v25, 61, 0i64);      // 根据“=”号的位置,判断解码位数
  else
    v2 = v22;
  v22 = v2;
  for ( i = 0; v22 - 4 >= i; i += 4 )           // b64变换过程
  {
    v3 = (char *)std::string::operator[](v25, i);
    std::string::find((std::string *)&v19, *v3, 0i64);
    v4 = (char *)std::string::operator[](v25, i + 1);
    std::string::find((std::string *)&v19, *v4, 0i64);
    std::string::operator+=(v24);
    v5 = (char *)std::string::operator[](v25, i + 1);
    std::string::find((std::string *)&v19, *v5, 0i64);
    v6 = (char *)std::string::operator[](v25, i + 2);
    std::string::find((std::string *)&v19, *v6, 0i64);
    std::string::operator+=(v24);
    v7 = (char *)std::string::operator[](v25, i + 2);
    std::string::find((std::string *)&v19, *v7, 0i64);
    v8 = (char *)std::string::operator[](v25, i + 3);
    std::string::find((std::string *)&v19, *v8, 0i64);
    std::string::operator+=(v24);
  }
  v9 = std::string::length(v25);
  if ( v9 - std::string::find(v25, 61, 0i64) == 1 )
  {
    v10 = (char *)std::string::operator[](v25, i);
    std::string::find((std::string *)&v19, *v10, 0i64);
    v11 = (char *)std::string::operator[](v25, i + 1);
    std::string::find((std::string *)&v19, *v11, 0i64);
    std::string::operator+=(v24);
    v12 = (char *)std::string::operator[](v25, i + 1);
    std::string::find((std::string *)&v19, *v12, 0i64);
    v13 = (char *)std::string::operator[](v25, i + 2);
    std::string::find((std::string *)&v19, *v13, 0i64);
    std::string::operator+=(v24);
  }
  else
  {
    v14 = std::string::length(v25);
    if ( v14 - std::string::find(v25, 61, 0i64) == 2 )
    {
      v15 = (char *)std::string::operator[](v25, i);
      std::string::find((std::string *)&v19, *v15, 0i64);
      v16 = (char *)std::string::operator[](v25, i + 1);
      std::string::find((std::string *)&v19, *v16, 0i64);
      std::string::operator+=(v24);
    }
  }
  std::string::~string((std::string *)&v19);
  return v24;
}

1.4 分割

经过base64解码之后,将结果分割,分为3组

 std::string::~string((std::string *)&v28);
      v40 = std::string::length((std::string *)&v26);// 组合的字符串
      v6 = alloca(16 * ((unsigned __int64)(v40 + 16) >> 4));
      Str = (char *)&v20;
      std::string::substr((std::string *)&v29, (const std::string *)&v25, 18ui64, 0xFFFFFFFFFFFFFFFFui64);
      v7 = std::string::length((std::string *)&v29) + 1;
      std::string::substr((std::string *)&v30, (const std::string *)&v25, 18ui64, 0xFFFFFFFFFFFFFFFFui64);
      v8 = (const char *)std::string::c_str((std::string *)&v30);// 分成两部分
      strncpy(&Dest, v8, v7);                   // 18位以后的数据
      std::string::~string((std::string *)&v30);
      std::string::~string((std::string *)&v29);
      std::string::substr((std::string *)&v31, (const std::string *)&v26, 0i64, 8ui64);// substr切分字符串,将base64之后的结果从0开始读8位
      v9 = std::string::length((std::string *)&v31) + 1;
      std::string::substr((std::string *)&v32, (const std::string *)&v26, 0i64, 8ui64);
      v10 = (const char *)std::string::c_str((std::string *)&v32);// 转换为字符串数组
      strncpy(&v23, v10, v9);                   // 然后赋值
      std::string::~string((std::string *)&v32);
      std::string::~string((std::string *)&v31);
      std::string::substr((std::string *)&v33, (const std::string *)&v26, 8ui64, 8ui64);// substr切分字符串,将base64之后的结果从8开始读8位
      v11 = std::string::length((std::string *)&v33) + 1;
      std::string::substr((std::string *)&v34, (const std::string *)&v26, 8ui64, 8ui64);
      v12 = (const char *)std::string::c_str((std::string *)&v34);// 转换为字符串数组
      strncpy(&v22, v12, v11);                  // 然后赋值
      std::string::~string((std::string *)&v34);
      std::string::~string((std::string *)&v33);
      std::string::substr((std::string *)&v35, (const std::string *)&v26, 16ui64, 8ui64);// substr切分字符串,将base64之后的结果从16开始读8位
      v13 = std::string::length((std::string *)&v35) + 1;
      std::string::substr((std::string *)&v36, (const std::string *)&v26, 16ui64, 8ui64);
      v14 = (const char *)std::string::c_str((std::string *)&v36);// 转换为字符串数组
      strncpy(&v21, v14, v13);                  // 然后赋值
      std::string::~string((std::string *)&v36);
      std::string::~string((std::string *)&v35);
      v15 = std::string::length((std::string *)&v26) + 1;
      v16 = (const char *)std::string::c_str((std::string *)&v26);

1.5 Ooo0ooO0O(&Dest, &v21, &Dest)

这是des的解密算法,解密内容为 base64 解码的数据 18 位以后的数据,密钥为【16:24】.

上面的具体算法判断主要有以下几点:

1.16轮密钥密钥编排,追踪密钥的流程,可以找到如下代码:

__int64 __fastcall OOoo0oO0O(const int *a1, int (*a2)[48])
{
 ...
  v17 = a2;
  v10 = 1;
  v9 = 2;
  memset(v5, 0, sizeof(v5));
  memset(v8, 0, sizeof(v8));
  memset(v6, 0, sizeof(v6));
  memset(v7, 0, sizeof(v7));
  result = OOoo0Oo0O(a1, v5, &OOoOOoo00);       // 初始置换,64bit->56bit
  for ( i = 0; i <= 27; ++i )
  {
    v3[i] = v5[i];
    result = i;
    v4[i] = v5[i + 28];
  }
  v15 = 0;
  for ( j = 1; j <= 16; ++j )                   // 16轮密钥
  {
    if ( j != 1 && j != 2 && j != 9 && j != 16 )// 左移两位
    {
      v15 += v9;
      OOoo0O0Oo(v3, &v6[28 * (j - 1)], v15);
      result = OOoo0O0Oo(v4, &v7[28 * (j - 1)], v15);
    }
    else                                        // 左移一位
    {
      v15 += v10;
      OOoo0O0Oo(v3, &v6[28 * (j - 1)], v15);
      result = OOoo0O0Oo(v4, &v7[28 * (j - 1)], v15);
    }
  }
  for ( k = 0; k <= 15; ++k )
  {
    for ( l = 0; l <= 27; ++l )
    {
      v8[l + 56i64 * k] = v6[l + 28i64 * k];
      result = l + 28 + 56i64 * k;
      v8[result] = v7[l + 28i64 * k];
    }
  }
  for ( m = 0; m <= 15; ++m )
    result = OOoooO0O0(&v8[56 * m], v17[m], &OOOo00oOo);// 获取48位密钥Ki
  return result;
}

des中的密钥编排有如下规则:

  • 初始置换PC-1(置换选择1):去除校验位,将64位密钥缩短为56位
  • 循环移位:将第一步得到的56位密钥分为C0和D0两个部分,长度均为28位的左右两部分将周期性的左移一位或者两位,移动的具体位数取决于轮数i,规则如下:
    • 在第i=1,2,9,16轮中,左移一位
    • 其他轮中,左移两位
  • 置换选择2(PC-2):通过再一次置换从56位密钥(C0和D0)中获取48位轮密钥ki

1.6 Ooo0OO0oo(&Dest, &v22, &Dest);

对应于des解密算法,这个函数也有相同的特点,是des加密算法,加密内容为base64解密后18位,密钥为【8:16】位

接下来又是一个1.5 Ooo0ooO0O,des解密,密钥为【0:8】位

小结一下,也就是3des算法

1.7 O0oOo0Oo0((unsigned __int64)&v37);

赋值之后开始走迷宫, 判断是迷宫,因为发现了全局变量Oooo0O0Oo,它的引用来自函数static_initialization_and_destruction_0(int a1, int a2)

2 迷宫分析

2.1 static_initialization_and_destruction_0(int a1, int a2)

初始化一个迷宫

int __fastcall __static_initialization_and_destruction_0(int a1, int a2)
{
  int result; // eax
  char v3; // [rsp+2Eh] [rbp-52h]
  char v4; // [rsp+2Fh] [rbp-51h]

  if ( a1 == 1 && a2 == 0xFFFF )
  {
    std::ios_base::Init::Init((std::ios_base::Init *)&std::__ioinit);
    atexit(_tcf_0);
    std::allocator<char>::allocator(&v3);
    std::string::string(&O0oo0OOo0, "44e4403b63620a2075d3fb2e0a6207d2", &v3);// 赋值一个md5,后面会进行比较
    std::allocator<char>::~allocator(&v3);
    atexit(_tcf_1);
    std::allocator<char>::allocator(&v4);
    std::string::string(
      &Oooo0O0Oo,
      "**************@************-************--**----*****-***-**-*****-***#**-*****--*****-******-*****-******-------*"
      "*****-*-----******---**-*******-****--*****************",
      &v4);                                     // 赋值一个字符串,初始化一个迷宫
    std::allocator<char>::~allocator(&v4);
    result = atexit(_tcf_2);
  }
  return result;
}

2.2 迷宫行进方向

由函数O0oO00ooO(std::string *a1)定义,内容如下:

_BOOL8 __fastcall O0oO00ooO(std::string *a1)
{
  ...
  v7 = a1;
  v6 = std::string::find((std::string *)&Oooo0O0Oo, 64, 0i64);
  for ( i = 0i64; std::string::length(v7) > i; ++i )
  {
    for ( j = 6; j >= 0; j -= 2 )
    {
      v1 = ((signed int)*(unsigned __int8 *)std::string::operator[](v7, i) >> j) & 3;
      if ( v1 == 1 )
      {
        v6 += 13;
      }
      else if ( v1 > 1 )
      {
        if ( v1 == 2 )
        {
          --v6;
        }
        else if ( v1 == 3 )
        {
          ++v6;
        }
      }
      else if ( !v1 )
      {
        v6 -= 13;
      }
      v2 = *(_BYTE *)std::string::operator[]((std::string *)&Oooo0O0Oo, v6) != '-'
        && *(_BYTE *)std::string::operator[]((std::string *)&Oooo0O0Oo, v6) != '#';
      if ( v2 )
        return 0i64;
    }
  }
  return *(_BYTE *)std::string::operator[]((std::string *)&Oooo0O0Oo, v6) == '#';
}

迷宫13个字符一行,然后移动方向定义为:1,向下;2,向左;3,向右;0,向上。

2.3 初始状态

简单写脚本查看初始化状态:

import re
sn="**************@************-************--**----*****-***-**-*****-***#**-*****--*****-******-*****-******-------******-*-----******---**-*******-****--*****************"
b=re.findall(r'.{13}',sn)
for i in ralnge(len(b)):
	print b[i]
*************
*@***********
*-***********
*--**----****
*-***-**-****
*-***#**-****
*--*****-****
**-*****-****
**-------****
**-*-----****
**---**-*****
**-****--****
*************

2.4 交换迷宫行函数

这个函数O0oOo0Oo0(4, 5),起到了交换两行的作用,两个参数分别代表要交换的两行,起到了交换两行的作用,代码不好理解,这个可以通过动态调试看到

__int64 __fastcall O0oOo0Oo0(int a1, int a2)
{
  ...

  v6 = a1 - 1;
  v7 = a2 - 1;
  std::string::substr((std::string *)(&v3 + 6), (const std::string *)&Oooo0O0Oo, (a1 - 1) * O0ooO0oO0, O0ooO0oO0);
  std::string::substr((std::string *)&v4, (const std::string *)&Oooo0O0Oo, v7 * O0ooO0oO0, O0ooO0oO0);
  std::string::replace((std::string *)&Oooo0O0Oo, v6 * O0ooO0oO0, O0ooO0oO0, (const std::string *)&v4);
  std::string::replace((std::string *)&Oooo0O0Oo, v7 * O0ooO0oO0, O0ooO0oO0, (const std::string *)&v5);
  std::string::~string((std::string *)&v4);
  return std::string::~string((std::string *)&v5);
}

2.5 迷宫函数

迷宫函数为O0oOo0Oo0(unsigned __int64 a1),参数为输入数据转换的数据,也就是走法,上下左右。判断的逻辑就是根据输入数据,判断是否能从“@”走到“#”。如果走到了,则O0ooOO00o ++。迷宫函数中,迷宫经历了三次变换,加上初始的迷宫,总共四个。

__int64 __fastcall O0oOo0Oo0(unsigned __int64 a1)
{
 ...
  v7 = (const std::string *)a1;
  std::string::substr((std::string *)(&v2 + 4), (const std::string *)a1, 0i64, 6ui64);
  O0ooOO00o += (unsigned __int8)O0oO00ooO((std::string *)&v3);// 第一次判断有没有通过
  std::string::~string((std::string *)&v3);
  O0oOo0Oo0(4, 5);
  O0oOo0Oo0(9, 10);
  O0oOo0Oo0(10, 11);
  std::string::substr((std::string *)&v4, v7, 6ui64, 6ui64);
  O0ooOO00o += (unsigned __int8)O0oO00ooO((std::string *)&v4);// 迷宫变换,第二次判断有没有通过
  std::string::~string((std::string *)&v4);
  O0oOo0Oo0(5, 7);
  O0oOo0Oo0(9, 12);
  O0oOo0Oo0(11, 12);
  std::string::substr((std::string *)&v5, v7, 0xCui64, 6ui64);
  O0ooOO00o += (unsigned __int8)O0oO00ooO((std::string *)&v5);// 迷宫变换,第三次判断有没有通过
  std::string::~string((std::string *)&v5);
  O0oOo0Oo0(2, 12);
  O0oOOo0o0(7, 9);
  O0oOOo0o0(5, 3);
  O0oOOo0o0(5, 11);
  O0oOOo0o0(4, 7);
  O0oOOo0o0(4, 8);
  std::string::substr((std::string *)&v6, v7, 0x12ui64, 0xFFFFFFFFFFFFFFFFui64);
  O0ooOO00o += (unsigned __int8)O0oO00ooO((std::string *)&v6);// 迷宫变换,第四次判断有没有通过
  return std::string::~string((std::string *)&v6);
}

通过动态调试,直接可以扣出来四个迷宫,四个不同的迷宫分别为

1
*************
*@***********
*-***********
*--**----****
*-***-**-****
*-***#**-****
*--*****-****
**-*****-****
**-------****
**-*-----****
**---**-*****
**-****--****
*************
2
*************
*@***********
*-***********
*-***-**-****
*--**----****
*-***#**-****
*--*****-****
**-*****-****
**-*-----****
**---**-*****
**-------****
**-****--****
*************
3
*************
*@***********
*-***********
*-***-**-****
*--*****-****
*-***#**-****
*--**----****
**-*****-****
**-****--****
**---**-*****
**-*-----****
**-------****
*************
4
*************
**-------****
*--*****-****
*-***-**-****
*--*****-****
*-***#**-****
*-***-**-****
*-***-**-****
*--**----****
**---**-*****
*--*****-****
*@***********
*************

其对应的走法分别为,其中走路的方向按照2.2中的定义来,将每一组数字为4进制编码

1
1111 
1311
3333
3300
0002
2211
2
1111
1311
1330
3333
0000
2221
3
1111
1311
1331
3330
0300
2220
4
0300
2000
0003
0333
3331
1111
1122
2000

将其转换为16进制:

0x55,0x75,0xFF,0xF0,0x02,0xA5,0x55,0x75,0x7C,0xFF,0x00,0xA9,0x55,0x75,0x7D,0xFC,0x30,0xA8,0x30,0x80,0x03,0x3F,0xFD,0x55,0x5A,0x80

3 破解思路

理一下整体流程:

  1. 输入36个字符base64解码后变成26个 刚好和上面的 6 + 6 + 6 +8 对应起来
  2. 将输入的字符串的[16:24] 作为密钥加密由base64 解码后的 数据[18:26]
  3. 利用输入的[8:16]作为密钥解密上一步加密的结果
  4. 利用 输入的[0:8] 作为密钥加密上一步解密的结果
  5. 用 解密后的[0:18]+后面 输入的的8个字符作为迷宫步数来走

破解思路就是根据走迷宫的步数逆推

import base64 as base
from Crypto.Cipher import DES
input=base.b64encode(''.join(map(chr,[0x55,0x75,0xff,0xf0,0x2,0xa5, 0x55,0x75,0x7c,0xff,0x0,0xa9, 0x55,0x75,0x7d,0xfc,0x30,0xa8, 0x30,0x80,0x3,0x3f,0xfd,0x55,0x5a,0x80])))
key1=input[0:8]
D=DES.new(key1,DES.MODE_ECB)
dest=''
f=B.b64decode(input)[18:]
dest=D.encrypt(f)
key2=input[8:16]
D=DES.new(key2,DES.MODE_ECB)
dest=D.decrypt(dest)
key3=input[16:24]
D=DES.new(key3,DES.MODE_ECB)
dest=D.encrypt(dest)
f=input[:24]+base.b64encode(dest)
print 'flag is :'+f

得到flag:VXX/8AKlVXV8/wCpVXV9/DCogeWKJMtrmeY=


本文访问量: