题目信息

第一次见还要开容器的逆向题,做完也明显感觉到这道题有点pwn的意思

附件名称为server,猜测就是运行在服务器上的程序

查壳

64位的.elf程序,先用ida看看

逆向分析

程序总体逻辑概览

看了一圈没什么头绪,直接从main()函数入手

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int packet; // eax
  _BYTE v5[1040]; // [rsp+0h] [rbp-1848h] BYREF
  _QWORD v6[130]; // [rsp+410h] [rbp-1438h] BYREF
  _BYTE v7[4136]; // [rsp+820h] [rbp-1028h] BYREF

  memset(v6, 0, sizeof(v6));
  strcpy((char *)&v6[1], "player");
  game_reset(&v6[9], argv, envp);
  if ( !(unsigned int)db_open(v6, "game.db") )
    return 1;
  if ( !(unsigned int)db_ensure_schema(v6[0]) || !(unsigned int)db_ensure_player(v6[0], &v6[1], 50) )
  {
    db_close(v6[0]);
    return 1;
  }
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stderr, 0, 2, 0);
  while ( 1 )
  {
    packet = protocol_read_packet(stdin, (__int64)v5);
    if ( !packet )
      break;
    if ( packet < 0 )
    {
      protocol_write_response(stdout);
    }
    else
    {
      handler_handle_packet((const char *)v6, v5, v7, 0x1000u);
      if ( !(unsigned int)protocol_write_response(stdout) )
        break;
    }
  }
  db_close(v6[0]);
  return 0;
}

不说了上mcp

main
├── 初始化阶段
│   ├── game_reset          # 重置游戏状态
│   ├── db_open             # 打开数据库 (game.db)
│   ├── db_ensure_schema    # 确保数据库表存在
│   └── db_ensure_player    # 确保玩家记录存在 (初始50分)
│
├── 主循环 (while True)
│   ├── protocol_read_packet    # 读取客户端数据包
│   │
│   └── handler_handle_packet   # 处理8种命令
│       │
│       ├── CMD 1: 获取Flag
│       │   ├── db_get_top_player    # 获取第一名玩家
│       │   └── getenv("GZCTF_FLAG") # 读取环境变量
│       │
│       ├── CMD 2: 获取玩家信息
│       │   └── db_get_player        # 查询玩家数据
│       │
│       ├── CMD 3: 获取排行榜
│       │   └── rank_format_overview # 格式化排行榜
│       │
│       ├── CMD 4: 重置挑战
│       │   ├── db_reset_challenge_state  # 重置AI对手分数
│       │   │   └── fixed_scores[7]        # 固定分数表
│       │   ├── game_reset                 # 重置棋盘
│       │   └── db_get_player              # 读取玩家信息
│       │
│       ├── CMD 5: 开始游戏
│       │   ├── db_pick_default_opponent   # 随机匹配AI对手 (score<=50)
│       │   └── game_start                 # 初始化游戏
│       │
│       ├── CMD 6: 获取棋盘
│       │   └── handle_get_board
│       │       └── game_serialize_board  # 序列化棋盘 (15x15)
│       │
│       ├── CMD 7: 落子 (核心游戏逻辑)
│       │   ├── game_parse_move           # 解析坐标 (如 "7,7")
│       │   ├── game_apply_player_move    # 玩家落子 (value=1)
│       │   ├── game_has_winner           # 检查是否获胜 (连成5子)
│       │   │
│       │   ├── if 玩家未获胜:
│       │   │   ├── ai_choose_move        # AI选择最佳位置
│       │   │   │   ├── is_winning_move_constprop_0  # 检查是否能赢
│       │   │   │   └── is_winning_move             # 检查能否阻止玩家赢
│       │   │   ├── game_apply_ai_move    # AI落子 (value=2)
│       │   │   ├── game_has_winner       # 检查AI是否获胜
│       │   │   └── game_is_full          # 检查棋盘是否满
│       │   │
│       │   └── settle_game               # 结算游戏 (关键!)
│       │       ├── db_get_player ×2      # 获取双方信息
│       │       ├── score_apply_buggy_update  # 计算玩家分数 [漏洞]
│       │       ├── score_apply_update    # 计算AI分数
│       │       ├── score_calculate_delta # 计算delta值
│       │       └── db_update_player_score ×2  # 更新数据库
│       │
│       └── CMD 8: 认输/结束游戏
│           ├── game_is_empty             # 检查是否无有效落子
│           ├── db_get_player ×2          # 获取双方
│           ├── game_reset                 # 重置棋盘
│           └── settle_game               # 结算 (平局或判负)
│
└── db_close         # 关闭数据库

简单来说这是一个基于客户端-服务器的ai五子棋游戏,服务器为玩家随机匹配ai对手,玩家通过下五子棋战胜对手获得积分,积分的计算类似ELO机制(这也是题目名字的来源),当玩家来到积分榜第一名才能查看flag,下面我们来一步一步分析

数据包格式

客户端与服务器间的交互是通过交换数据包完成的,题目并未提供与服务器建立连接的方式,我们需要自己写脚本发送与接收数据包来与服务器进行交互,才能玩这个游戏,但前提是我们要了解数据包的格式

protocol_read_packet()和 protocol_write_response()两个函数分别负责读取来自客户端的数据包和向客户端发送数据包,从中我们能看出数据包的格式

游戏逻辑

命令集

handler_handle_packet()函数负责处理玩家的各种命令

int __fastcall handler_handle_packet(const char *a1, _BYTE *a2, void *a3, size_t a4)
{
  const char *v6; // rdx
  __int64 v7; // rdx
  int result; // eax
  char *v9; // rax
  const char *v10; // rcx
  __int64 v11; // rdx
  int v12; // r9d
  int v13; // r8d
  const char *v14; // rcx
  _BYTE v15[68]; // [rsp+0h] [rbp-C8h] BYREF
  int v16; // [rsp+44h] [rbp-84h]
  int v17; // [rsp+48h] [rbp-80h]
  char v18[4]; // [rsp+50h] [rbp-78h] BYREF
  char s1[64]; // [rsp+54h] [rbp-74h] BYREF
  int v20; // [rsp+94h] [rbp-34h]
  int v21; // [rsp+98h] [rbp-30h]

  memset(a3, 0, a4);
  switch ( *a2 )
  {
    case 1:
      if ( !(unsigned int)db_get_top_player(*(_QWORD *)a1, v18) )
        return snprintf((char *)a3, a4, "DB error");
      if ( strcmp(s1, a1 + 8) )
        return snprintf((char *)a3, a4, "Permission denied");
      v9 = getenv("GZCTF_FLAG");
      v10 = v9;
      if ( v9 )
      {
        if ( !*v9 )
          v10 = "Fail to get flag from environment variable";
      }
      else
      {
        v10 = "Fail to get flag from environment variable";
      }
      return snprintf((char *)a3, a4, "%s", v10);
    case 2:
      if ( !(unsigned int)db_get_player(*(_QWORD *)a1, a1 + 8, v15) )
        goto LABEL_25;
      v12 = v17;
      v13 = v16;
      v14 = "OK";
      goto LABEL_23;
    case 3:
      return rank_format_overview(a1, a3, a4);
    case 4:
      if ( !(unsigned int)db_reset_challenge_state(*(_QWORD *)a1, a1 + 8, 50) )
        goto LABEL_25;
      game_reset(a1 + 72, a1 + 8, v11);
      if ( !(unsigned int)db_get_player(*(_QWORD *)a1, a1 + 8, v18) )
        goto LABEL_25;
      v12 = v21;
      v13 = v20;
      v14 = "Reset";
LABEL_23:
      result = snprintf((char *)a3, a4, "%s Score=%u Rank=%d", v14, v13, v12);
      break;
    case 5:
      if ( *((_DWORD *)a1 + 243) )
        return snprintf((char *)a3, a4, "Game already in progress");
      if ( !(unsigned int)db_pick_default_opponent(*(_QWORD *)a1, a1 + 8, v18) )
        return snprintf((char *)a3, a4, "No opponent available");
      game_start(a1 + 72, s1);
      return snprintf((char *)a3, a4, "Matched %s Score=%u You=Black", s1, v20);
    case 6:
      return handle_get_board(a1, a3, a4);
    case 7:
      return handle_move(a1, a2, a3, a4);
    case 8:
      v6 = "No active game";
      if ( !*((_DWORD *)a1 + 243) )
        goto LABEL_27;
      if ( (unsigned int)game_is_empty(a1 + 72, 0, "No active game") )
      {
        if ( (unsigned int)db_get_player(*(_QWORD *)a1, a1 + 8, v15)
          && (unsigned int)db_get_player(*(_QWORD *)a1, a1 + 976, v18) )
        {
          game_reset(a1 + 72, a1 + 976, v7);
          result = snprintf((char *)a3, a4, "Draw Delta=0 Score=%u Rank=%d OppDelta=0 OppScore=%u", v16, v17, v20);
        }
        else
        {
LABEL_25:
          result = snprintf((char *)a3, a4, "DB error");
        }
      }
      else
      {
        result = settle_game(a1, "Resign", a3, a4, 0.0);
      }
      break;
    default:
      v6 = "Unknown command";
LABEL_27:
      result = snprintf((char *)a3, a4, v6);
      break;
  }
  return result;
}

通过上面的函数我们可以分析出各个命令的功能

CMD功能
1获取flag(需取得第一名)
2获取玩家信息
3获取当前排行榜
4重置挑战
5匹配对手,开始游戏
6查看棋盘
7落子
8认输

积分机制

积分机制在score_apply_buggy_update()函数中

__int64 __fastcall score_apply_buggy_update(unsigned int a1, int a2, int *a3, double a4)
{
  double v5; // xmm2_8
  int v6; // eax

  v5 = (a4 - 1.0 / (pow(10.0, ((double)a2 - (double)(int)a1) / 50.0) + 1.0)) * 20.0;
  v6 = lround(v5);
  if ( a3 )
    *a3 = v6;
  if ( a1 > 9 || v6 >= 0 )
    return a1 + v6;
  else
    return a1;
}

计分公式很简单

v5 = (a4 - 1/(10^((a2-a1)/50) + 1)) * 20.0;
v6 = lround(v5);#取整

分数变化的具体值取决于玩家双方的分数差

这里有个关键的地方

if ( a1 > 9 || v6 >= 0 )
    return a1 + v6;
  else
    return a1;

当玩家分数小于等于9分时,无论输赢分数将保持不变

交互脚本

弄清楚了数据包格式和客户端与服务器的交互逻辑,就可以写脚本连接服务器玩游戏了

#game_client.py
#用法:python game_client.py <server_ip> <port>
#!/usr/bin/env python3

import socket

import struct

import sys

import re

  

MAGIC = b'GAME'

  

COLOR_EMPTY = '\033[90m'

COLOR_PLAYER = '\033[91m'

COLOR_AI = '\033[94m'

COLOR_COORD = '\033[92m'

COLOR_RESET = '\033[0m'

  

def visualize_board(response):

    match = re.search(r'Board=([012/]+)', response)

    if not match:

        print(response)

        return

    board_str = match.group(1)

    rows = board_str.split('/')

    if len(rows) != 15:

        print(response)

        return

    print(f"\n{COLOR_COORD}   " + " ".join(f"{i:2}" for i in range(15)) + COLOR_RESET)

    print(f"{COLOR_COORD}   " + "── " * 15 + COLOR_RESET)

    for y in range(15):

        row = rows[y]

        row_content = []

        for x in range(15):

            cell = row[x]

            if cell == '0':

                row_content.append(f"{COLOR_EMPTY} ·{COLOR_RESET}")

            elif cell == '1':

                row_content.append(f"{COLOR_PLAYER} X{COLOR_RESET}")

            elif cell == '2':

                row_content.append(f"{COLOR_AI} O{COLOR_RESET}")

            else:

                row_content.append(f" {cell}")

        print(f"{COLOR_COORD}{y:2}│{COLOR_RESET}" + "".join(row_content))

    print(f"{COLOR_COORD}   " + "── " * 15 + COLOR_RESET)

    print(f"{COLOR_COORD}   " + " ".join(f"{i:2}" for i in range(15)) + COLOR_RESET)

    print(f"\n{COLOR_PLAYER}X = 你(黑){COLOR_RESET} | {COLOR_AI}O = AI(白){COLOR_RESET} | {COLOR_EMPTY}· = 空{COLOR_RESET}")

  
  

class GameClient:

    def __init__(self, host, port):

        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        self.sock.connect((host, port))

    def send_packet(self, cmd, data=b''):

        packet = bytes([cmd]) + data

        length = 9 + len(data)

        msg = MAGIC + struct.pack('>I', length) + packet

        self.sock.sendall(msg)

    def recv_packet(self):

        magic = self.sock.recv(4)

        if magic != MAGIC:

            return None, None

        length_bytes = self.sock.recv(4)

        length = struct.unpack('>I', length_bytes)[0]

        remaining = length - 8

        data = b''

        while len(data) < remaining:

            chunk = self.sock.recv(remaining - len(data))

            if not chunk:

                break

            data += chunk

        return data[0], data[1:].decode()

    def cmd1_get_flag(self, player_name):

        self.send_packet(1, player_name.encode())

        cmd, resp = self.recv_packet()

        return resp

    def cmd2_get_player(self, player_name):

        self.send_packet(2, player_name.encode())

        cmd, resp = self.recv_packet()

        return resp

    def cmd3_get_rank(self):

        self.send_packet(3, b'')

        cmd, resp = self.recv_packet()

        return resp

    def cmd4_reset(self, player_name):

        self.send_packet(4, player_name.encode())

        cmd, resp = self.recv_packet()

        return resp

    def cmd5_start_game(self, player_name):

        self.send_packet(5, player_name.encode())

        cmd, resp = self.recv_packet()

        return resp

    def cmd6_get_board(self):

        self.send_packet(6, b'')

        cmd, resp = self.recv_packet()

        return resp

    def cmd7_move(self, x, y):

        self.send_packet(7, f"{x},{y}".encode())

        cmd, resp = self.recv_packet()

        return resp

    def cmd8_resign(self):

        self.send_packet(8, b'')

        cmd, resp = self.recv_packet()

        return resp

    def close(self):

        self.sock.close()

  
  

def main():

    if len(sys.argv) != 3:

        print(f"Usage: {sys.argv[0]} <host> <port>")

        sys.exit(1)

    host, port = sys.argv[1], int(sys.argv[2])

    client = GameClient(host, port)

    player = "player"

    while True:

        print("\n=== Menu ===")

        print("1. Get Flag")

        print("2. Get Player Info")

        print("3. Get Rank")

        print("4. Reset Challenge")

        print("5. Start Game")

        print("6. Get Board")

        print("7. Make Move")

        print("8. Resign")

        print("q. Quit")

        choice = input("Choice: ").strip()

        if choice == 'q':

            break

        try:

            if choice == '1':

                print(client.cmd1_get_flag(player))

            elif choice == '2':

                print(client.cmd2_get_player(player))

            elif choice == '3':

                print(client.cmd3_get_rank())

            elif choice == '4':

                print(client.cmd4_reset(player))

            elif choice == '5':

                print(client.cmd5_start_game(player))

            elif choice == '6':

                resp = client.cmd6_get_board()

                visualize_board(resp)

            elif choice == '7':

                move = input("Enter move (x,y): ").strip()

                x, y = move.split(',')

                resp = client.cmd7_move(int(x), int(y))

                visualize_board(resp)

                print(resp)

            elif choice == '8':

                print(client.cmd8_resign())

        except Exception as e:

            print(f"Error: {e}")

    client.close()

  
  

if __name__ == "__main__":

    main()

取胜策略

因为ai玩家有上千个,让我们不断地下五子棋战胜取得第一是不可能的

但是积分机制中存在一个下溢出漏洞,可以让我们轻松获得高分

在score_apply_buggy_update()函数中

__int64 __fastcall score_apply_buggy_update(unsigned int a1, int a2, int *a3, double a4)

a1为玩家分数,是一个无符号整数,当我们不断让自己扣分直到分数变为负数,a1就会下溢出为一个超大的数,从而取得第一

但是我们不能无脑输,因为当我们分数小于等于9分时,分数将会锁定,上面的分析我们得知,游戏失败时我们最少扣10分,因此我们可以制定策略,让自己在最后一局前扣到9分,然后再输一局扣10分,刚好得到-1分

由此我们可以制定下面的策略

步骤操作赛前分数delta赛后分数
1reset50-50
2赢10分对手50+353
3输20分对手53-1637
4输20分对手37-1423
5输30分对手23-815
6输38分对手15-510
7输7分对手10-11-1 -> 4294967295

我们每一轮都需要挑选特定分数的对手,如果没有匹配到想要的对手就直接投降然后重新匹配

接下来我们需要确保每一轮必赢或必输,这个很简单,因为本题的ai对手其实挺蠢的,而且我们还是执黑的先手

  • 必赢:(5,5), (5,6), (5,7), (5,8), (5,9)
  • 必输:(0,0), (0,2), (0,4), (0,6), (0,8), (0,10)

这样就可以写脚本利用漏洞取胜了

#exploit.py
#用法:python exploit.py <server_ip> <port>
#!/usr/bin/env python3

import socket

import struct

import sys

import re

import time

  

MAGIC = b'GAME'

  

win_moves = [(5,5), (5,6), (5,7), (5,8), (5,9)]

lose_moves = [(0,0), (0,2), (0,4), (0,6), (0,8), (0,10)]

  

class GameClient:

    def __init__(self, host, port):

        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        self.sock.settimeout(30)

        self.sock.connect((host, port))

    def send_packet(self, cmd, data=b''):

        packet = bytes([cmd]) + data

        length = 9 + len(data)

        msg = MAGIC + struct.pack('>I', length) + packet

        self.sock.sendall(msg)

    def recv_packet(self):

        magic = self.sock.recv(4)

        if magic != MAGIC:

            return None, None

        length_bytes = self.sock.recv(4)

        length = struct.unpack('>I', length_bytes)[0]

        remaining = length - 8

        data = b''

        while len(data) < remaining:

            chunk = self.sock.recv(remaining - len(data))

            if not chunk:

                break

            data += chunk

        return data[0], data[1:].decode()

    def cmd1_get_flag(self, player_name):

        self.send_packet(1, player_name.encode())

        cmd, resp = self.recv_packet()

        return resp

    def cmd4_reset(self, player_name):

        self.send_packet(4, player_name.encode())

        cmd, resp = self.recv_packet()

        return resp

    def cmd5_start_game(self, player_name):

        self.send_packet(5, player_name.encode())

        cmd, resp = self.recv_packet()

        return cmd, resp

    def cmd7_move(self, x, y):

        self.send_packet(7, f"{x},{y}".encode())

        cmd, resp = self.recv_packet()

        return cmd, resp

    def cmd8_resign(self):

        self.send_packet(8, b'')

        cmd, resp = self.recv_packet()

        return cmd, resp

    def get_player_info(self, player_name):

        self.send_packet(2, player_name.encode())

        cmd, resp = self.recv_packet()

        return resp

    def get_rank(self):

        self.send_packet(3, b'')

        cmd, resp = self.recv_packet()

        return resp

    def close(self):

        self.sock.close()

  
  

def wait_for_match(client, player_name, target_score, max_retries=50):

    for i in range(max_retries):

        cmd, resp = client.cmd5_start_game(player_name)

        match = re.search(r'Score=(\d+)', resp)

        if match:

            opp_score = int(match.group(1))

            if opp_score == target_score:

                print(f"  [+] 找到目标对手,分数={target_score}")

                return resp

            else:

                print(f"  [-] 对手分数={opp_score},不匹配目标={target_score},重试...")

                client.cmd8_resign()

                time.sleep(0.3)

        else:

            print(f"  [-] 匹配失败: {resp}")

    print(f"  [!] 达到最大重试次数({max_retries})")

    return None

  
  

def win_game(client, moves):

    for x, y in moves:

        cmd, resp = client.cmd7_move(x, y)

        if "Win" in resp or "Lose" in resp or "Draw" in resp:

            return resp

    return None

  
  

def lose_game(client, moves):

    for x, y in moves:

        cmd, resp = client.cmd7_move(x, y)

        if "Win" in resp or "Lose" in resp or "Draw" in resp:

            return resp

    return None

  
  

def main():

    if len(sys.argv) != 3:

        print(f"Usage: {sys.argv[0]} <host> <port>")

        sys.exit(1)

    host, port = sys.argv[1], int(sys.argv[2])

    player = "player"

    client = GameClient(host, port)

    print("[*] Step 1: Reset 挑战")

    print(client.cmd4_reset(player))

    time.sleep(0.5)

    print("\n[*] Step 2: 赢10分对手")

    wait_for_match(client, player, 10)

    resp = win_game(client, win_moves)

    print(f"  结果: {resp}")

    print(client.get_player_info(player))

    time.sleep(0.5)

    print("\n[*] Step 3: 输20分对手")

    wait_for_match(client, player, 20)

    resp = lose_game(client, lose_moves)

    print(f"  结果: {resp}")

    print(client.get_player_info(player))

    time.sleep(0.5)

    print("\n[*] Step 4: 再输20分对手")

    wait_for_match(client, player, 20)

    resp = lose_game(client, lose_moves)

    print(f"  结果: {resp}")

    print(client.get_player_info(player))

    time.sleep(0.5)

    print("\n[*] Step 5: 输30分对手")

    wait_for_match(client, player, 30)

    resp = lose_game(client, lose_moves)

    print(f"  结果: {resp}")

    print(client.get_player_info(player))

    time.sleep(0.5)

    print("\n[*] Step 6: 输38分对手")

    wait_for_match(client, player, 38)

    resp = lose_game(client, lose_moves)

    print(f"  结果: {resp}")

    print(client.get_player_info(player))

    time.sleep(0.5)

    print("\n[*] Step 7: 输7分对手")

    wait_for_match(client, player, 7)

    resp = lose_game(client, lose_moves)

    print(f"  结果: {resp}")

    print(client.get_player_info(player))

    print("\n[*] 查看排行榜")

    print(client.get_rank())

    print("\n[*] 获取FLAG")

    print(client.cmd1_get_flag(player))

    client.close()

  
  

if __name__ == "__main__":

    main()
    
'''
输出示例:
[*] Step 1: Reset 挑战
Reset Score=50 Rank=1002

[*] Step 2: 赢10分对手
  [-] 对手分数=30,不匹配目标=10,重试...
  [-] 对手分数=40,不匹配目标=10,重试...
  [-] 对手分数=30,不匹配目标=10,重试...
  [+] 找到目标对手,分数=10
  结果: Win Delta=3 Score=53 Rank=1002 OppDelta=-3 OppScore=7
OK Score=53 Rank=1002

[*] Step 3: 输20分对手
  [+] 找到目标对手,分数=20
  结果: Lose Delta=-16 Score=37 Rank=1004 OppDelta=16 OppScore=36
OK Score=37 Rank=1004

[*] Step 4: 再输20分对手
  [-] 对手分数=50,不匹配目标=20,重试...
  [-] 对手分数=36,不匹配目标=20,重试...
  [-] 对手分数=50,不匹配目标=20,重试...
  [-] 对手分数=40,不匹配目标=20,重试...
  [-] 对手分数=30,不匹配目标=20,重试...
  [-] 对手分数=30,不匹配目标=20,重试...
  [-] 对手分数=36,不匹配目标=20,重试...
  [-] 对手分数=7,不匹配目标=20,重试...
  [-] 对手分数=7,不匹配目标=20,重试...
  [-] 对手分数=50,不匹配目标=20,重试...
  [-] 对手分数=30,不匹配目标=20,重试...
  [+] 找到目标对手,分数=20
  结果: Lose Delta=-14 Score=23 Rank=1007 OppDelta=14 OppScore=34
OK Score=23 Rank=1007

[*] Step 5: 输30分对手
  [-] 对手分数=34,不匹配目标=30,重试...
  [-] 对手分数=40,不匹配目标=30,重试...
  [-] 对手分数=50,不匹配目标=30,重试...
  [-] 对手分数=7,不匹配目标=30,重试...
  [-] 对手分数=34,不匹配目标=30,重试...
  [-] 对手分数=40,不匹配目标=30,重试...
  [-] 对手分数=50,不匹配目标=30,重试...
  [-] 对手分数=7,不匹配目标=30,重试...
  [-] 对手分数=7,不匹配目标=30,重试...
  [+] 找到目标对手,分数=30
  结果: Lose Delta=-8 Score=15 Rank=1007 OppDelta=8 OppScore=38
OK Score=15 Rank=1007

[*] Step 6: 输38分对手
  [-] 对手分数=36,不匹配目标=38,重试...
  [-] 对手分数=50,不匹配目标=38,重试...
  [-] 对手分数=50,不匹配目标=38,重试...
  [-] 对手分数=36,不匹配目标=38,重试...
  [-] 对手分数=36,不匹配目标=38,重试...
  [-] 对手分数=34,不匹配目标=38,重试...
  [-] 对手分数=36,不匹配目标=38,重试...
  [-] 对手分数=50,不匹配目标=38,重试...
  [-] 对手分数=50,不匹配目标=38,重试...
  [-] 对手分数=34,不匹配目标=38,重试...
  [-] 对手分数=34,不匹配目标=38,重试...
  [-] 对手分数=40,不匹配目标=38,重试...
  [-] 对手分数=34,不匹配目标=38,重试...
  [-] 对手分数=36,不匹配目标=38,重试...
  [-] 对手分数=40,不匹配目标=38,重试...
  [-] 对手分数=50,不匹配目标=38,重试...
  [-] 对手分数=36,不匹配目标=38,重试...
  [-] 对手分数=50,不匹配目标=38,重试...
  [-] 对手分数=36,不匹配目标=38,重试...
  [-] 对手分数=7,不匹配目标=38,重试...
  [-] 对手分数=7,不匹配目标=38,重试...
  [-] 对手分数=7,不匹配目标=38,重试...
  [-] 对手分数=50,不匹配目标=38,重试...
  [-] 对手分数=34,不匹配目标=38,重试...
  [-] 对手分数=36,不匹配目标=38,重试...
  [-] 对手分数=36,不匹配目标=38,重试...
  [-] 对手分数=34,不匹配目标=38,重试...
  [-] 对手分数=40,不匹配目标=38,重试...
  [-] 对手分数=40,不匹配目标=38,重试...
  [-] 对手分数=34,不匹配目标=38,重试...
  [-] 对手分数=34,不匹配目标=38,重试...
  [-] 对手分数=36,不匹配目标=38,重试...
  [-] 对手分数=40,不匹配目标=38,重试...
  [-] 对手分数=34,不匹配目标=38,重试...
  [-] 对手分数=40,不匹配目标=38,重试...
  [-] 对手分数=7,不匹配目标=38,重试...
  [-] 对手分数=34,不匹配目标=38,重试...
  [+] 找到目标对手,分数=38
  结果: Lose Delta=-5 Score=10 Rank=1007 OppDelta=5 OppScore=43
OK Score=10 Rank=1007

[*] Step 7: 输7分对手
  [-] 对手分数=43,不匹配目标=7,重试...
  [-] 对手分数=36,不匹配目标=7,重试...
  [-] 对手分数=40,不匹配目标=7,重试...
  [-] 对手分数=40,不匹配目标=7,重试...
  [-] 对手分数=43,不匹配目标=7,重试...
  [-] 对手分数=50,不匹配目标=7,重试...
  [-] 对手分数=34,不匹配目标=7,重试...
  [-] 对手分数=50,不匹配目标=7,重试...
  [+] 找到目标对手,分数=7
  结果: Lose Delta=-11 Score=4294967295 Rank=1 OppDelta=11 OppScore=18
OK Score=4294967295 Rank=1

[*] 查看排行榜
Score=4294967295 Rank=1 Total=1008 Top=4294967295 Nearby: #1 player(4294967295) #2 星辰冰封(2000000) #3 星辰不语(4996) #4 浅笑听雨(4989) #5 北离堆叠(4989) #6 陌上眷恋(4972) #7 微茫交织(4971)

[*] 获取FLAG
NCTF{256cfe60-965d-4388-aff5-b95a1fc331ac}
'''

获得flag:

NCTF{256cfe60-965d-4388-aff5-b95a1fc331ac}