2023年春秋杯网络安全联赛春季赛Reverse题目复现

文章目录

  • 一.sum
    • 1. 分析程序逻辑
    • 2.解数独矩阵
    • 3.解题脚本
  • 二.Poisoned_tea_CHELL
    • 1. 重新识别函数及程序逻辑分析
    • 2.IDA动态调试(attach附加调试)
    • 3. 输入选项进行单步调试
    • 4.解题脚本
  • BWBA
  • OldSymbolicCode

一.sum

1. 分析程序逻辑

这里直接贴上当时分析的结果,根据程序行为不难猜出是数独问题(还得靠猜)
main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *mart; // rbp
  int flagValue; // r14d
  int sum; // r12d
  __int64 i; // rbx
  char chr; // al
  int tmp; // eax
  char *md5str; // rax

  mart = matrix;
  flagValue = 1;
  sum = 0;
  puts("Welcome to Solver!");
  do
  {
    for ( i = 0LL; i != 9; ++i )
    {
      if ( !mart[i] )                           // 如果矩阵元素为零则输入字符
      {
        chr = getchar();
        if ( (chr - '1') > 8u )                 // 所以输入的必须是数字,否则错误
          flagValue = 0;
        else
          mart[i] = chr - '0';                  // 数字
      }
      tmp = mart[i];
      sum += tmp;
    }
    mart += 9;                                  // 一次处理九个字符
  }
  while ( mart != &matrix[81] );                // 一共81
  if ( flagValue && verify() )                  // 盲猜是数独
  {
    puts("You Win!");
    __snprintf_chk(buf, 32LL, 1LL, 32LL, "%d"); // 405
    md5str = str2md5(buf, strlen(buf));
    __printf_chk(1LL, "flag is: flag{%s}\n\n", md5str);// flag{bbcbff5c1f1ded46c25d28119a85c6c2}
    exit(0);
  }
  puts("Again~");
  return 0;
}

verify(验证):
尤其是当时看到九个一组以及按列检查就反应过来应该是数独

__int64 verify()
{
  char *matr; // rsi
  __int64 j; // rcx
  _DWORD *CHECK; // rdi
  __int64 tmp; // rdi
  char *matr2; // rsi
  __int64 k; // rcx
  _DWORD *check2; // rdi
  __int64 chr; // rdi
  __int64 iii; // r8
  char *matr3; // r9
  __int64 len3; // rcx
  _DWORD *check3; // rdi
  int cout; // r10d
  char *matr4; // rdi
  __int64 i; // rcx
  __int64 tmpchr; // rbx

  matr = matrix;
LABEL_2:
  j = 10LL;
  CHECK = check;
  while ( j )
  {
    *CHECK++ = 0;                               // 初始化空间
    --j;
  }
  while ( 1 )
  {
    tmp = matr[j];
    if ( check[tmp] )                           // 不能访问已访问过的
      return 0LL;
    ++j;
    check[tmp] = 1;                             // 置一
    if ( j == 9 )
    {
      matr += 9;                                // 九个一组
      if ( matr != &matrix[81] )
        goto LABEL_2;
      matr2 = matrix;
LABEL_9:
      k = 10LL;
      check2 = check;
      while ( k )
      {
        *check2++ = 0;                          // 再次清空check数组
        --k;
      }
      while ( 1 )
      {
        chr = matr2[9 * k];                     // 检查第一列
        if ( check[chr] )
          return 0LL;
        ++k;
        check[chr] = 1;
        if ( k == 9 )
        {
          if ( ++matr2 != &matrix[9] )
            goto LABEL_9;
          iii = 0LL;
          while ( 2 )
          {                                     // 检查第二列
            matr3 = &matrix[iii];
            do
            {
              len3 = 10LL;
              check3 = check;
              cout = 3;
              while ( len3 )                    // 清空check
              {
                *check3++ = 0;
                --len3;
              }
              matr4 = matr3;
              while ( 2 )
              {
                for ( i = 0LL; i != 3; ++i )
                {
                  tmpchr = matr4[i];
                  if ( check[tmpchr] )          // 也是不能有1
                    return 0LL;
                  check[tmpchr] = 1;
                }
                matr4 += 9;
                if ( --cout )
                  continue;
                break;
              }
              matr3 += 3;
            }
            while ( matr3 != &matr2[iii] );
            iii += 27LL;
            if ( iii != 81 )
              continue;
            break;
          }
          return 1LL;
        }
      }
    }
  }
}

2.解数独矩阵

先跟进martix然后将数据读取出来
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第1张图片
然后询问ChatGpt直接得到答案
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第2张图片
以及给出解题代码和运行结果:
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第3张图片
然后填数独就可以过了

3.解题脚本

#include
int main()
{
    int sum = 0;
    unsigned char matrix[81] =
    {
        5,   3,   0,   0,   7,   0,   0,   0,   0,   6,
        0,   0,   1,   9,   5,   0,   0,   0,   0,   9,
        8,   0,   0,   0,   0,   6,   0,   8,   0,   0,
        0,   6,   0,   0,   0,   3,   4,   0,   0,   8,
        0,   3,   0,   0,   1,   7,   0,   0,   0,   2,
        0,   0,   0,   6,   0,   6,   0,   0,   0,   0,
        2,   8,   0,   0,   0,   0,   4,   1,   9,   0,
        0,   5,   0,   0,   0,   0,   8,   0,   0,   7,
        9
    };
    int grid[81] = {
    5, 3, 4, 6, 7, 8, 9, 1, 2,
    6, 7, 2, 1, 9, 5, 3, 4, 8,
    1, 9, 8, 3, 4, 2, 5, 6, 7,
    8, 5, 9, 7, 6, 1, 4, 2, 3,
    4, 2, 6, 8, 5, 3, 7, 9, 1,
    7, 1, 3, 9, 2, 4, 8, 5, 6,
    9, 6, 1, 5, 3, 7, 2, 8, 4,
    2, 8, 7, 4, 1, 9, 6, 3, 5,
    3, 4, 5, 2, 8, 6, 1, 7, 9
    };
     int len = 0;
    for (int i = 0; i < 81; i++)
    {
        if (!matrix[i])
        {
            len++;
            printf("%d", grid[i]);//输出需要填充的序列
            //468912723481342575971422657913948591537428763345261
        }
    }
    return 0;
}

linux下运行程序,输入序列:468912723481342575971422657913948591537428763345261
即可得到答案:
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第4张图片

二.Poisoned_tea_CHELL

1. 重新识别函数及程序逻辑分析

这里可能是识别问题,找到这三块红色区域查看汇编
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第5张图片
然后可以发现这里莫名其妙定义了四个字节数据,按u取消掉下方指令定义
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第6张图片
然后从这段数据头(0x763处)按p识别为函数,即可可以看到tea逻辑
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第7张图片
另外两块红色区域同理,重新识别后可以看到主函数(如果看不到那就对tea函数交叉引用向上找)
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第8张图片
这里给出我分析美化后的主函数:
不过这里有一个关键问题就是他的key和关键数据并不能通过静态分析看到,所以需要动态分析了

__int64 __fastcall Main()
{
  __int64 result; // rax
  int i; // [rsp+Ch] [rbp-464h]
  int j; // [rsp+10h] [rbp-460h]
  int chr1; // [rsp+14h] [rbp-45Ch] BYREF
  int chr2; // [rsp+18h] [rbp-458h]
  int v5; // [rsp+1Ch] [rbp-454h]
  int key[8]; // [rsp+20h] [rbp-450h] BYREF
  int d1; // [rsp+40h] [rbp-430h]
  int d2; // [rsp+44h] [rbp-42Ch]
  int b1; // [rsp+48h] [rbp-428h]
  int b2; // [rsp+4Ch] [rbp-424h]
  int v11; // [rsp+50h] [rbp-420h]
  int buffer[258]; // [rsp+60h] [rbp-410h] BYREF
  unsigned __int64 v13; // [rsp+468h] [rbp-8h]

  v13 = __readfsqword(0x28u);
  key[0] = 5;
  key[1] = 2;
  key[2] = dword_7FA16F6F8464;                  // 猜测也是一位数,可以爆破
  key[3] = dword_7FA16F6F8454;
  key[4] = 0;
  memset(buffer, 0, 0x400uLL);
  sub_7FA16F6F5524();
  sub_7FA16F6F5554();
  sub_7FA16F6F5594();                           // 这几个函数找不到
  sub_7FA16F6F5574(&unk_7FA16F6F6469, buffer);
  chr1 = 0;
  chr2 = 0;
  v5 = 0;
  for ( i = 0; buffer[i]; i += 2 )              // 也就是每次对buffer的两个字符进行tea加密
  {
    chr1 = buffer[i];
    chr2 = buffer[i + 1];
    Tea(dword_7FA16F6F8474, &chr1, key);        // 一次加密两个字符
    buffer[i] = chr1;                           // 更新buffer串
    buffer[i + 1] = chr2;
  }
  d1 = 0;
  d2 = 0;
  b1 = 0;
  b2 = 0;
  v11 = 0;
  for ( j = 0; buffer[j]; j += 2 )              // 比较函数
  {
    d1 = desStr[j];
    d2 = desStr[j + 1];
    b1 = buffer[j];
    b2 = buffer[j + 1];
    if ( d1 != b1 || d2 != b2 )
      break;
  }
  sub_7FA16F6F5524();
  result = 0LL;
  if ( v13 != __readfsqword(0x28u) )
    return sub_7FA16F6F5544();
  return result;
}

2.IDA动态调试(attach附加调试)

如果直接使用ida远程动调会提示错误:
Input file is a dynamic library, it cannot be run by itself.
Please specify the host application (Debugger, Process options)
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第9张图片
提示这是个动态库文件,需要附加进程进行调试,那么我们可以使用IDA的附加调试功能

  1. linux虚拟机使用root权限运行ida的linux_server64
    必须使用root权限,否则后续附加调试会失败,这是由于IDA服务端的权限不够
    2023年春秋杯网络安全联赛春季赛Reverse题目复现_第10张图片
  2. 运行程序
    2023年春秋杯网络安全联赛春季赛Reverse题目复现_第11张图片
  3. 附加调试
    Debugger>Attach to process
    2023年春秋杯网络安全联赛春季赛Reverse题目复现_第12张图片然后找到毒tea进程,双击即可
    2023年春秋杯网络安全联赛春季赛Reverse题目复现_第13张图片
    选择same
    2023年春秋杯网络安全联赛春季赛Reverse题目复现_第14张图片

3. 输入选项进行单步调试

成功附加后会发现按f7或者f8都没反应,此时程序等待输出,先到linux虚拟机输入一个选项
如果输入1,后续调试比较难找,总之就是找到Loading字符串,注意不能跳过了
(直接搜索字符串搜索不到,这题应该是有SMC的操作对程序代码进行解密)
下方的call sun_7f006f7c2536就是主函数,跟进重新识别即可看到逻辑(这里不做详细介绍,以选项2为主)
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第15张图片

如果输入2
一直按f7,最后可以发现程序会卡住,并且还能看见InputFlag字符串,这里就是主函数了
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第16张图片
往上翻找到起始地址,对loc_loc_7F3BB0FB6536按p识别为函数就可以看到主函数逻辑了
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第17张图片
然后还有一些没有被正确识别的函数,需要手动跟进按p识别为函数,然后回到反汇编界面按f5重新识别即可
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第18张图片
key的值:
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第19张图片
tea加密循环次数的值(所以这个tea循环36次而非32次):
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第20张图片
加密后的data,跟进可以提取出数据
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第21张图片

4.解题脚本

#include
void Tea(int len, unsigned int* buffer, int* key)
{
	int i; // [rsp+24h] [rbp-14h]
	unsigned int v5; // [rsp+28h] [rbp-10h]
	unsigned int v6; // [rsp+2Ch] [rbp-Ch]
	unsigned int v7; // [rsp+30h] [rbp-8h]

	v5 = *buffer;
	v6 = buffer[1];
	v7 = 0xd9b6d99c;
	for (i = 0; i < len; ++i)
	{
		v6 -= (v5 + ((v5 >> 5) ^ (16 * v5))) ^ (key[(v7 >> 11) & 3] + v7);
		v7 += 0x41104111;
		v5 -= (v6 + ((v6 >> 5) ^ (16 * v6))) ^ (key[v7 & 3] + v7);
	}
	*buffer = v5;
	buffer[1] = v6;
}
int main()
{
	int  buffer[14] =
	{
	  -318921983,
	  1639894517,
	  -1197577091,
	  -835265432,
	  1265521566,
	  1680782596,
	  1425658684,
	  1829167973,
	  -360235693,
	  -1537112825,
	  -676229584,
	  -1000652734,
	  0,
	  0,
	};
	int key[5] = { 5,2,9,7,0 };
	int chr[2] = { 0 };
	for (int i = 0; buffer[i]; i += 2)              // 也就是每次对buffer的两个字符进行tea加密
	{
		chr[0] = buffer[i];
		chr[1] = buffer[i + 1];
		Tea(36, chr, key);						// 一次加密两个字符
		buffer[i] = chr[0];                           // 更新buffer串
		buffer[i + 1] = chr[1];
	}
	unsigned char* p = (unsigned char*)buffer;
	printf("%s", p);
	//Thisisflag{cdfec405-3f4b-457e-92fe-f6446098ee2e}
	return 0;
}

BWBA

简单分析了一下程序逻辑
2023年春秋杯网络安全联赛春季赛Reverse题目复现_第22张图片
这里复现时询问ChatGpt回答我的是FFT算法,而不是DCT,让其输出解密脚本也一直错误,看来还是得多了解一下这个算法
参考大佬文章:2023春秋杯春季赛WP-REVERSE(AK)
能用cv2库函数进行处理

import numpy as np
import cv2

x = np.array([370.75,234.362,-58.0834,59.8212,88.8221,-30.2406,21.8316,49.9781,-33.5259,2.69675,43.5386,-30.2925,-28.0754,27.593,-2.53962,-27.1883,-5.60777,-0.263937,6.80326,8.03022,-6.34681,-0.89506,-6.80685,-13.6088,27.0958,29.8439,-21.7688,-20.6925,-13.2155,-37.0994,2.23679,37.6699,-3.5,9.85188,57.2806,13.5715,-20.7184,8.6816,3.59369,-4.5302,4.22203,-28.8166,-23.695,31.2268,6.58823,-39.9966,-20.7877,-19.7624,-22.031,16.3285,2.07557,-26.2521,16.1914,18.3976,-26.9295,3.03769,41.0412,20.2598,14.991,6.99392,-22.3752,-7.24466,8.96299,-10.4874], dtype=np.float64)
x = cv2.idct(x)
x = x.ravel()
flag=''

for i in range(len(x)):
    flag+=chr(round(x[i]))
print(flag)#flag{9ab488a7-5b11-1b15-04f2-c230704ecf72}

关于DCT算法这几篇文章不错:

  1. DCT离散余弦变换
  2. 详解离散余弦变换(DCT)

OldSymbolicCode

你可能感兴趣的:(Reverse,Reverse,Ctf,春秋杯网络安全联赛,经验分享,逆向工程)