NYCTF reverse ---------single

这道题目是南京邮电CTF平台的reverse题目single(最后一道,但不是最有难度的一道)

题目链接如下:https://cgctf.nuptsast.com/challenges#Re

不多讲,拖进IDA分析,如下:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char s; // [rsp+0h] [rbp-70h]
  unsigned __int64 v5; // [rsp+68h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  memset(&s, 0, 0x64uLL);
  puts("Input string:");
  scanf("%s", &s);
  sub_40070E(&s);                               // 简单限制字符范围、长度
  sub_40078B(&s, (__int64)&unk_602080);
  sub_400AD4((__int64)&unk_602080);
  puts("Congratulations!");
  printf("flag{%s}", &s);
  return 0LL;
}

在总体上,可以清楚的分析出程序的大致逻辑:读入flag,经过三个函数的检查来判断是否是真正的flag。

下面逐个分析这三个函数。

1.

size_t __fastcall sub_40070E(const char *a1)
{
  size_t result; // rax
  int i; // [rsp+1Ch] [rbp-14h]

  if ( strlen(a1) > 0x51 )
    wrong();
  for ( i = 0; ; ++i )
  {
    result = strlen(a1);
    if ( i >= result )
      break;
    if ( a1[i] <= 47 || a1[i] > 57 )            // flag字符ASICL在[48,57]
      wrong();
  }
  return result;
}

 意图很明显,限制flag的长度不超过0x51,且flag的组成字符只限于0\1\2\3\4\5\6\7\8\9。

2.

size_t __fastcall sub_40078B(const char *a1, __int64 a2)
{
  size_t result; // rax
  int i; // [rsp+1Ch] [rbp-14h]

  for ( i = 0; ; ++i )
  {
    result = strlen(a1);
    if ( i >= result )
      break;
    if ( a1[i] != 48 )                          // a[i]!='0'
    {
      if ( !a1[i] || *(_BYTE *)(i + a2) )       // if ( a2[i]!=0 )
        wrong();                                // flag不为0的时候,会改变
      *(_BYTE *)(i + a2) = a1[i] - 48;          // a2[i]=a1[i]-'0'
    }
  }
  return result;
}

 

这是一个通过遍历flag的每一个字符来为一个数组a2初始化的函数,逻辑就是初始化的规则:

当flag[i]!='0'时,a2[i]一定为0(否则flag错误),并且要重新赋值a2[i]=flag[i]-'0';

3.

unsigned __int64 __fastcall sub_400AD4(__int64 a1)
{
  sub_400833(a1);
  sub_4008FE(a1);
  return sub_4009C9(a1);
}

 第三个函数其实就是判断的关键所在了,这里面有三个子函数。逐个分析

(1)

unsigned __int64 __fastcall sub_400833(__int64 a1)
{
  signed int i; // [rsp+18h] [rbp-28h]
  signed int j; // [rsp+1Ch] [rbp-24h]
  signed int k; // [rsp+1Ch] [rbp-24h]
  char s[24]; // [rsp+20h] [rbp-20h]
  unsigned __int64 v6; // [rsp+38h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  for ( i = 0; i <= 8; ++i )
  {
    memset(s, 0, 0xAuLL);
    for ( j = 0; j <= 8; ++j )
      ++s[*(unsigned __int8 *)(9 * i + j + a1)];// s[ a1[9*i+j] ]++
    for ( k = 1; k <= 9; ++k )
    {
      if ( s[k] != 1 )
        wrong();
    }                                           // 每一行都必须全有1\2\3\4\5\6\7\8\9
  }
  return __readfsqword(0x28u) ^ v6;
}

 这里面需要尤其注意,  ++s[*(unsigned __int8 *)(9 * i + j + a1)]和9*9的嵌套循环 足以让你怀疑是一个9*9的数组。

而需要注意到,判断部分是在for(i=0;i<9;i++)i里面的,这就很容易知道其实就是需要满足每一行必须有1\2\3\4\5\6\7\8\9

(2)和(1)逻辑一样,不过是限制每列都有1\2\3\4\5\6\7\8\9

(3)

unsigned __int64 __fastcall sub_4009C9(__int64 a1)
{
  signed int i; // [rsp+1Ch] [rbp-34h]
  int j; // [rsp+20h] [rbp-30h]
  signed int l; // [rsp+20h] [rbp-30h]
  int k; // [rsp+24h] [rbp-2Ch]
  signed int v6; // [rsp+28h] [rbp-28h]
  signed int v7; // [rsp+2Ch] [rbp-24h]
  char s[24]; // [rsp+30h] [rbp-20h]
  unsigned __int64 v9; // [rsp+48h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  v6 = 3;
  v7 = 3;
  for ( i = 0; i <= 8; ++i )
  {
    memset(s, 0, 0xAuLL);
    for ( j = v6 - 3; j < v6; ++j )
    {
      for ( k = v7 - 3; k < v7; ++k )
        ++s[*(unsigned __int8 *)(9 * j + k + a1)];// 从上到下 从左到右的 不重合的  3*3需要是1\2\3\4\5\6\7\8\9
    }
    for ( l = 1; l <= 9; ++l )
    {
      if ( s[l] != 1 )
        wrong();
    }
    if ( v7 == 9 )
    {
      v7 = 3;
      v6 += 3;
    }
    else
    {
      v7 += 3;
    }
  }
  return __readfsqword(0x28u) ^ v9;
}

分析一下嵌套循环以及判断函数,发现其实还是相似的。不同的是,这里每次判断的九个数是3*3的小数组,按照从上到下,从左到右不重合的顺序检查。

这样一来,其实可以知道这是一个“数独”游戏。根据原有数据,在线填写之后,不要忘记了 把原有数据更改为0.就达到了flag.

 

flag{401095728057800001802040305000321589500479002923586000105060203300008950269750804}。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值