catalyst-system

将可执行文件放在kali虚拟机中运行发现出现了欢迎的一段文字以及漫长的一个loading…..
1
就这样?不甘心,于是把可执行文件丢ida里逆向,根据string(Welcome to Catalyst systems)定位到汇编代码位置:
2
按F5生成c代码,可以看到整个可执行文件的结构:

puts("\x1B[0mWelcome to Catalyst systems");
printf("Loading", a2);
fflush(stdout);
for ( i = 0; i <= 29; ++i )
{
  v4 = rand();
  sleep(v4 % (3 * i + 1));
  putchar(46);
  fflush(stdout);
}
putchar(10);
printf("Username: ");
__isoc99_scanf("%s", v8);
printf("Password: ", v8);
__isoc99_scanf("%s", v7);
printf("Logging in", v7);
fflush(stdout);
for ( j = 0; j <= 29; ++j )
{
  v5 = rand();
  sleep(v5 % (j + 1));
  putchar(46);
  fflush(stdout);
}
putchar(10);
sub_400C9A((__int64)v8);
sub_400CDD(v8);
sub_4008F7((__int64)v8);
sub_400977(v8, v7);
sub_400876((__int64)v8, (__int64)v7);

可以发现在loading之后有一个循环结构,而且用了sleep,于是把这个29用ida的patch改为2,此外在输入了用户名和密码之后还有一个没什么用的循环结构,同理改为2。这样就不会再有那么漫长的无谓等待了。把patch之后的可执行文件放到kali下执行:

Welcome to Catalyst systems
Loading...
Username: test
Password: test
Logging in...
invalid username or password

可以看到有对用户名和密码的验证逻辑于是看主函数中调用的几个函数。
在查看sub_400CDD(v8)函数的时候发现内部有对用户名的验证:

signed __int64 __fastcall sub_400CDD(unsigned int *a1)
{
  signed __int64 result; // rax
  __int64 v2; // [rsp+10h] [rbp-20h]
  __int64 v3; // [rsp+18h] [rbp-18h]
  __int64 v4; // [rsp+20h] [rbp-10h]

  v4 = *a1;
  v3 = a1[1];
  v2 = a1[2];
  if ( v4 - v3 + v2 != 1550207830
    || v3 + 3 * (v2 + v4) != 12465522610LL
    || (result = 3651346623716053780LL, v2 * v3 != 3651346623716053780LL) )
  {
    puts("invalid username or password");
    exit(0);
  }
  return result;
}

可以整理出三个公式:

v4 - v3 + v2 = 1550207830
v3 + 3 * (v2 + v4) = 12465522610LL
v2 * v3 = 3651346623716053780LL

这里就可以通过计算v2、v3和v4来得到username。
sub_400977(v8, v7)方法中有对password进行验证的逻辑:

for ( i = 0; *((_BYTE *)a2 + i); ++i )
  {
    if ( (*((_BYTE *)a2 + i) <= 96 || *((_BYTE *)a2 + i) > 122)
      && (*((_BYTE *)a2 + i) <= 64 || *((_BYTE *)a2 + i) > 90)
      && (*((_BYTE *)a2 + i) <= 47 || *((_BYTE *)a2 + i) > 57) )
    {
      puts("invalid username or password");
      exit(0);
    }
  }
  srand(a1[1] + *a1 + a1[2]);
  v2 = *a2;
  if ( v2 - rand() != 1441465642 )
  {
    puts("invalid username or password");
    exit(0);
  }
  v3 = a2[1];
  if ( v3 - rand() != 251096121 )
  {
    puts("invalid username or password");
    exit(0);
  }
  v4 = a2[2];
  if ( v4 - rand() != -870437532 )
  {
    puts("invalid username or password");
    exit(0);
  }
  v5 = a2[3];
  if ( v5 - rand() != -944322827 )
  {
    puts("invalid username or password");
    exit(0);
  }
  v6 = a2[4];
  if ( v6 - rand() != 647240698 )
  {
    puts("invalid username or password");
    exit(0);
  }
  v7 = a2[5];
  if ( v7 - rand() != 638382323 )
  {
    puts("invalid username or password");
    exit(0);
  }
  v8 = a2[6];
  if ( v8 - rand() != 282381039 )
  {
    puts("invalid username or password");
    exit(0);
  }
  v9 = a2[7];
  if ( v9 - rand() != -966334428 )
  {
    puts("invalid username or password");
    exit(0);
  }
  v10 = a2[8];
  if ( v10 - rand() != -58112612 )
  {
    puts("invalid username or password");
    exit(0);
  }
  v11 = a2[9];
  v12 = v11 - rand();
  result = v12;
  if ( v12 != 605226810 )
  {
    puts("invalid username or password");
    exit(0);
  }

可以看到方法中使用了用户名几个字段的和作为随机数种子,而且后面的password的计算也需要随机数参与计算,所以这里我们不能直接通过静态分析来得出结论。但是我们也知道只要随机数种子固定,每次的随机都是伪随机,所以我们可以编写程序来计算密码。代码来自https://0xd13a.github.io/ctfs/alexctf2017/catalyst-system/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    unsigned int rax3, rax4, rax5;

    char u[12];
    char p[40];
    char key[] = {0x42, 0x13, 0x27, 0x62, 0x41, 0x35, 0x6b, 0x0f, 0x7b, 0x46, 0x3c, 0x3e, 0x67, 0x0c, 0x08, 0x59, 0x44, 0x72, 0x36, 0x05, 0x0f, 0x15, 0x54, 0x43, 0x38, 0x17, 0x1d, 0x18, 0x08, 0x0e, 0x5c, 0x31, 0x21, 0x16, 0x02, 0x09, 0x18, 0x14, 0x54, 0x59};

    rax4 = (0x2e700c7b2L - 0x5c664b56L * 3) / 4;
    rax5 = 0x32ac30689a6ad314L / rax4;
    rax3 = 0x5c664b56L - rax5 + rax4;

    ((unsigned int*)u)[0] = rax3;
    ((unsigned int*)u)[1] = rax4;
    ((unsigned int*)u)[2] = rax5;

    printf("username: %.*s\n", sizeof(u), u);

    srand(rax3 + rax4 + rax5);

    ((unsigned int*)p)[0] = 0x55eb052a + rand();
    ((unsigned int*)p)[1] = 0x0ef76c39 + rand();
    ((unsigned int*)p)[2] = 0xcc1e2d64 + rand();
    ((unsigned int*)p)[3] = 0xc7b6c6f5 + rand();
    ((unsigned int*)p)[4] = 0x26941bfa + rand();
    ((unsigned int*)p)[5] = 0x260cf0f3 + rand();
    ((unsigned int*)p)[6] = 0x10d4caef + rand();
    ((unsigned int*)p)[7] = 0xc666e824 + rand();
    ((unsigned int*)p)[8] = 0xfc89459c + rand();
    ((unsigned int*)p)[9] = 0x2413073a + rand();
    printf("password: %.*s\n", sizeof(p), p);

    printf("flag: ALEXCTF{");
    for (int i = 0; i < sizeof(p); i++) {
        printf("%c", p[i] ^ key[i]);
    }
    printf("}\n");
}
$ gcc -o solve solve.c && ./solve  
username: catalyst_ceo
password: sLSVpQ4vK3cGWyW86AiZhggwLHBjmx9CRspVGggj
flag: ALEXCTF{1_t41d_y0u_y0u_ar3__gr34t__reverser__s33}

转自实验吧,原文作者实验吧ID vincentvan